Model how your team really works.
Obversa lets you describe how a team works and run it with agents, code and people. One agent writes, another reviews, and a judge decides whether another revision is worth doing. The workflow carries the feedback back to the writer. A person makes the calls you leave to them.
Put that process in a TypeScript file. Use the agent CLIs you already have signed in, or give a role an API engine. Tests run as commands, reviews return their notes, and the run keeps its record in a plain file.
Use the same patterns to review a research brief, shape an article, answer a support ticket or deliver a feature. Notes go back to the writer. A check decides whether work continues. A recorded workflow can continue from its file after an interruption. No server, no database.
npm install @obversa/obversaNode.js 22.12 or later. @obversa/obversa installs the runtime and every
plugin but the Jev engine, which is npm install @obversa/engine-jev-api.
Use Claude Code, Codex, Grok or OpenCode with the command line tools you
already have signed in, or use an API engine.
Installation covers
setup. First run walks
through a complete file.
These are the parts of a process you recognise from working with people. Each snippet comes from a runnable example. Follow its link for the full file, inputs and result.
A writer drafts a post and a different model reads it against the house style. The writer gets the review notes and revises the draft. In the editorial example, Claude writes, Codex reviews, and a person is the editor:
roles: {
write: engines.claude('claude-sonnet-4-5'),
grade: [engines.codex('gpt-5.6-luna')],
editor: person('Publish this post?'),
},stage() from @obversa/runtime gives the writer a reviewer and a budget
for revisions:
stage('draft', {
agent: 'write',
writes: 'posts/draft.md',
desc: 'Write the post from the brief, in the house style.',
gate: 'The draft holds against every rule in style/house.md, as read by a grader from another model family.',
reviewedBy: 'grade',
// The judge. After a round the grader did not pass, Jev reads the
// findings and the rounds so far and says whether another round is
// worth it; a finding tagged block goes back without asking. The
// cap is the backstop: three rounds at most, whatever the judge says.
refine: judge(judgeSeat, { cap: 3 }),
}),In workflow(), the reviewer has to come from a different model family
from the writer. Get a second opinion
explains the review loop.
A review can suggest changes that make little difference to the result.
Give a decision model the job of weighing those notes against the
purpose of the work and the revisions so far. judge() from
@obversa/runtime sets that stopping rule inside a dag() workflow:
maxKickbacks: { implement: judge(judgeSeat, { cap: 4 }) },Here Jev makes the judgement, with a hard cap of four returns to the writer. A blocking finding goes back to the writer without asking the judge, and it still counts toward the cap. Know when to stop shows the questions and a complete writing example.
Give the work to several reviewers and decide how many must agree. Each
opinion stays in the result, including dissent. A workflow() review
stage names the panel and the number of approvals it needs:
stage('review', {
panel: 'review',
agree: 1,
desc: 'Have both reviewers read the change and count the acceptances.',
gate: 'At least one reviewer has accepted.',
sendsBackTo: 'implement',
}),This example needs one approval from its two reviewers. Set agree to
two when both must accept. Ask a panel
shows the full team and what happens when a reviewer cannot answer.
A model's review can be enough to request another draft. Publishing may
need an editor's decision. The editorial workflow asks the person named
in its editor role after the model review passes:
stage('publish', {
input: 'editor',
desc: 'Put the graded draft in front of the editor.',
gate: 'The editor has said publish.',
sendsBackTo: 'draft',
}),With no answer the run pauses. A refusal with notes goes to the writer. Approval completes this example; a separate action would publish the post. Ask a person shows how the question and answer become part of the run.
Let a script check the facts while a model writes the report. The weekly report example uses a command to check that the draft includes every incident. Its exit code decides whether the writer tries again:
stage('check', {
run: [process.execPath, 'tools/check-report.mjs'],
desc: 'Fail the draft if an incident from the facts is missing.',
gate: 'The check exits 0.',
sendsBackTo: 'draft',
}),The workflow runs the command directly. The model spends its turn on the draft. Automate the routine work shows the same pattern with a test command.
A weekly report stops after gathering its facts. The next worker opens
the saved record and continues with the draft, without gathering those
facts again. run() from @obversa/runtime resumes the same workflow:
const second = await run(createReport(), {
recordTo: record,
resume: true,
onEvent: watch('worker-2', worker2),
});For steps declared with workflow() or dag(), the recovery rule is:
Steps that finished are never repeated. A step that was mid-flight when the worker died runs again only if its binding declares it safe to retry; otherwise the run pauses and asks a person to reconcile it before it continues, so uncertain work is never repeated silently. The complete example shows which stages each worker ran.
The pattern collection also covers comparing different approaches, bringing a team together, and preparing context before handing over a task. Patterns can be combined inside a larger workflow.
A real use case combines these patterns around an outcome:
| Workflow | How the team works |
|---|---|
| Review an article | A writer drafts, a different model reviews, and an editor decides whether it is ready to publish. |
| Read research papers | A model summarises supplied papers, a person selects the notes to keep, and those notes inform an answer. |
| Handle support tickets | Classify each ticket, draft a reply, and refer uncertain or sensitive cases to a person. |
| Prepare a shortlist | Apply rules in code, compare rankings from two models, and ask a person to choose the shortlist. |
| Deliver a feature | Plan, implement, test, review and ask a person to approve the change. |
The feature delivery file is examples/teams/feature-delivery.ts.
Browse the workflows for the full
files and examples from other fields.
A seat names the tool, the provider, the model family and the model.
| package | drives | needs |
|---|---|---|
@obversa/engine-claude-cli |
Claude Code, one fresh process per attempt | Claude Code, signed in |
@obversa/engine-codex-cli |
Codex | Codex, signed in |
@obversa/engine-grok-cli |
Grok | the Grok command line tool |
@obversa/engine-opencode-cli |
OpenCode | the OpenCode command line tool |
@obversa/engine-claude-agent-sdk |
the Claude Agent SDK | Claude auth |
@obversa/engine-anthropic-api |
the Anthropic API | an API key |
@obversa/engine-jev-api |
Jev, typed decisions over recorded state | a Jev endpoint and API key |
Write your own against the engine contract in @obversa/api; it must pass
the conformance kit.
Keep a workflow in a central collection and point each run at a repository
with run(job, { cwd }), keep it beside the code it works on, or import it
from a service and call run() when the service decides. It runs the same
way from each.
Where a workflow lives.
docs.obversa.ai: the first run, the concepts, the patterns, examples by field, and how Obversa sits beside LangGraph, CrewAI, Temporal, Claude Code subagents and eve.
19 publishable packages. packages/ holds the eight that define the
product: @obversa/runtime runs a workflow, @obversa/api holds the
engine and memory contracts, @obversa/core runs bounded child processes,
@obversa/runner supervises stored runs, @obversa/builtin-workflows
ships three ready-made teams, @obversa/obversa is the install above,
and @obversa/surface with @obversa/surface-diff is the local review
page. plugins/ holds the eleven adapters: the seven engines above, three
memories, and a notifier that posts run events to a URL.
AGENTS.md is the guide for anyone who changes this repository: setup, the checks, and the rules every change follows.
Obversa uses the MIT License. Report security problems as described in the security policy.