Teams in Obversa review each other's work (and know when to stop), pick up where they left off, and keep you in the loop when you're needed. They always follow process, and they improve themselves over time.
The outer graph as four tiers, with the human above themFour tiers, exploded, with the human above them. The human talks to the interactive agents on the top tier: a terminal, or your own product with a workflow running inside it at run time. The tier shows a terminal with the question and its answer. Under it, Obversa workflows, in TypeScript. Under that, the tools and harnesses the workflow runs: agents, decision models, micro UIs, backend services, test runners. At the bottom, plain file records: append-only JSONL, one file per run. A thread runs from the human down to the agents, and another from the centre of the workflows tier down to the centre of the record, with a pulse running along it: the workflow writes the record.
Runs on the tools and plans you already pay for.
Claude Code
Codex
Grok
OpenCode
Devin
Claude Agent SDK
Anthropic API
OpenRouter
jevJev
Mastra
OpenAI Agents SDK
They review each other's work, and know when to stop
The way a good team works, run by agents. You brief them. They work, review each other, and decide what is worth doing. Then they refine it until it holds.
You brief the team. What the work is for, which files, what must hold. Every role starts from the same brief, and you leave them to it.
They go back to their desks and work. Each role works on its own, in a fresh context: the brief, its files, its notes.
The writer takes the draft to a colleague. A reviewer from another model family reads it against the brief and leaves notes. Nobody marks their own homework.
The judge decides what is worth doing. A small decision model reads each note against what the work is for: act on this one, skip that one. It also says whether another pass would change anything.
The team refines it. The writer runs again with only the notes that matter. The reviewer reads the new draft. When it holds, the judge stops the loop.
A team at work: the brief, the desks, the review, the judge, the fix
stage('draft', {
agent: 'write',
writes: 'posts/draft.md',
// two reviewers, another model family
reviewedBy: 'review',
// act or skip, note by note; stop when it holds
refine: judge(jev()),
}),
An office seen from above. You come in and brief three colleagues at the whiteboard, then leave. They go back to their desks. The writer types a page and carries it to the reviewer, who reads it and leaves two notes. The reviewer takes it to the judge at a standing desk, who stamps one note act and strikes the other. The judge walks it back to the writer with one change. The writer fixes the line, the reviewer reads it again and says good, and the judge stamps it: it holds.
A team is a few words in one file: a writer and a reviewer, a panel that needs two of three to agree, a judge, a person who signs off. The same words build a feature or write a post. Launch it from a script, or from your own code at run time.
A run posts to your team's channel as it goes, so nobody has to watch a terminal. When a decision is outside the team's remit, the run asks in the channel and pauses there until the right person answers.
Your channel follows the run. One message for each moment: the run started, a step finished, work went back with the reviewer's reason, a question is waiting, and how it ended. Slack and Mattermost incoming webhooks show it as it is, so those channels need no code.
It asks about the exact thing. For a change you need to see first, the question names the exact bytes, and nothing acts on them until you've approved those bytes. A file that changed while you were deciding is not what you approved, so nothing lands.
It asks however suits the person answering. A page you supply, a diff, a plain yes or no. The run opens it and takes the answer back.
A no with notes goes back to the writer. No answer, and the team waits.
Nobody waits at a terminal. The question goes to the channel. The run records it and exits, with nothing left running. Once someone has answered, a schedule or your own code starts it again, and it picks up exactly where it left off.
Your team, or your customer. The person who answers is whoever owns that decision on your team, or your customer when a workflow runs inside your product.
A question from the team, posted to the team's channelA phone shows the message a webhook posted to the team's channel: the feature team asks for approval of the new settings page, with a small picture of the design, what changed (a billing tab and a theme toggle, two files, exact bytes listed), and Yes, No and a comments box for the answer. The run has exited. When someone answers, it picks up where it left off.
The team's channel gets the question. As the team asked it, through the channel's incoming webhook.
The run has exited. Nothing is left running while someone decides.
Someone answers. A schedule or your own code starts the run again, and it picks up where it left off.
127.0.0.1:54264
A real run's own page. This run was told to wait at the desk for the answer instead of exiting, so the page stays up: each step and its state, the question, and the record as it is written.
Each role on the team is a tool you already use: Claude Code, Codex, Grok, OpenCode or Devin on the plan you already pay for, or an agent you've built in Mastra or the OpenAI Agents SDK. Obversa is the team around them, in one TypeScript file.
One team, every tool's strengths. Every agent tool is good at something, and a role goes to whichever one is best for the job. Put them on one team and what each does well is on the team too. Changing the tool changes a line or two, never the workflow.
No second API bill. The command-line tools run on your subscriptions. The API models are there when you want them.
Nothing locks you in. Tools, memory and workflow shapes are plugins over a public contract, all TypeScript. Replace any of them or write your own. Use it at build time from a script, or at run time inside the product you ship.
Pick who writes. The reviewers stay another model family.
Context engineering, built in. Every role starts fresh, every run is remembered, and the why behind a change can live in its commit. No vector database, no embeddings.
Coming soon: memory curation
Every agent knows what matters. Before each step, a fast decision model weighs candidates from your repository's history, knowledge base and rules, and passes the agent only what bears on its task. Rules that apply are checked against the result.
Retrieve, then rerank. Cheap signals rank everything first: which files the task touches, what changes together, what is fresh. The decision model reads only the shortlist: 150 candidates in under a second.
A knowledge base that tends itself. Memory decay: notes no step uses fade, and a second decision model keeps, promotes, merges or retires them. Git keeps every version, so the team can always recover what it believed, and when.
Memory that nests like your company. The handbook reaches every agent, a team's docs reach that team, a role's guide reaches that role. Exclude anything from anyone, for blind reviews and honest evals.
Every run leaves a record: each pass, every note and what changed because of it, every judge answer, and every answer a person gave. The team reads its own record and proposes one change to how it works.
The team changes its own workflow file, one diff at a time. After a run, it reads its own record, proposes one change as a normal diff, and cites the line of the record that justifies it.
Another model family checks it, and you say yes. A reviewer from a different family reads the change first. Nothing changes without your yes.
Kept only if it scores better. The change runs on your own tasks, several times each, as the workflow is and with the change. It is checked on tasks it was not tuned on, so a change that only fits the tuning tasks is caught.
Within limits you set, attended or automatic. Say which files and which settings a change may touch, and which it never may. Approve each kept change yourself, or let the numbers decide inside those limits; anything outside them still comes to you.
release-notes.tsone change, proposed from the record
- const brief = 'Write the release notes for version 2.0, one line per change.';+ const brief = 'Write the release notes for version 2.0, one line per change, then the upgrade steps.';
The change from the example in the docs, where scripted stand-ins play the models.
whyfrom the run's own record
evidence
The check sent the first draft back: the notes had no upgrade steps. The brief never asked for them.
review
A model from another family reads the change: it adds to the brief and keeps the check.
you
Apply this change? Yes. The next run starts from the changed file.
They always follow process
Install it, then describe the team in a file.
One package. Then roles, reviews, the judge and the moments that need you, in one TypeScript file you can check in and share.
$ npm install @obversa/obversa \
@obversa/engine-jev-api
# claude() and codex() are built in# jev() is a separate package$ cat team.ts
workflow('editorial', {
brief: briefFromFile('briefs/post.md'),
roles: {
write: claude('claude-sonnet-4-5'),
grade: [codex('gpt-5.6-luna')],
editor: person('Publish this post?'),
},
stages: [
stage('draft', {
agent: 'write', writes: 'posts/draft.md',
reviewedBy: 'grade', refine: judge(jev()),
}),
stage('publish', {
input: 'editor', sendsBackTo: 'draft',
}),
],
})
Run it from wherever the work is.
A script, or your own product at run time. A writer can work in its own git worktree. Every step is recorded.
$ npx tsx team.ts
# or call run() inside your own product
They pause, and pick up exactly where they left off.
A crash, a restart, a closed laptop, or a question only you can answer: the run resumes at the step it was on and never repeats finished work. A run that is waiting for you doesn't sit there burning a process. It exits, and a schedule or your own code starts it again from its record once your answer is in. No server, no database, no account.
{"kind":"run:start","runId":"editorial-2k7",
"source":{"path":"team.ts","sha256":"…"}}
{"kind":"engine:usage","path":["draft"],
"model":"claude-sonnet-4-5","role":"writer",
"usage":{"kind":"reported",
"inputTokens":18422,"outputTokens":2310},
"cost":{"kind":"estimated","usd":0.09,
"entry":"claude-sonnet-4-5"},
"billing":"subscription"}
{"kind":"refine:judge","target":"draft",
"round":1,"route":"again",
"reason":"one finding to act on",
"findings":[{"id":"f1","decision":"act",
"reason":"the brief asks for a date"},
{"id":"f2","decision":"skip",
"reason":"style only"}]}
{"kind":"job:end","path":["publish"],
"label":"editor",
"asked":{"gateId":"publish",
"question":"Publish this post?"},
"outcome":{"status":"pass",
"summary":"approved"}}
{"kind":"run:end","outcome":{"status":"pass"},
"usage":{"inputTokens":61204,
"outputTokens":7930}}
Under the hood
A meta-harness, or the outer graph. One TypeScript API above the agent harnesses and frameworks you run. Claude Code, Codex, Grok, OpenCode and Devin are engines; so are the Claude Agent SDK, the Anthropic API, Jev, Mastra agents and OpenAI Agents SDK agents. Engines are plugins over a public contract, and so are memory and the workflow shapes themselves.
Durable execution without a server. Every run is an append-only record of events in plain files. State is derived from the record, so a fresh process resumes at the interrupted step, and a step that was mid-flight runs again only if it is marked safe to retry.
A review loop with a judge. The evaluator-optimizer pattern with a twist: reviewers from another model family, findings that go back to the writer, and an LLM-as-judge that rules on each finding and decides whether another round is worth it. A hard cap is optional.
Human in the loop, by contract. A callback gate records the question, waits or exits, and carries on once the answer is in. Approval can be bound to the exact bytes it covers. A webhook carries the question to you.
Agent evals. A check between an attempt and the next step decides what happens next: a test, a judge, a panel with a threshold, a tournament, a score. With hill climbing, a proposed change to a workflow is scored on a fixed task set, several runs per task, with held-out tasks, and kept only when the workflow does better.
Isolation and memory. A git worktree per writer, when you ask for one. Each seat's context is curated, not accumulated. Memory in git or markdown, behind a one-method port you can replace.
Shapes of a team as primitives. Writer and reviewer, a review panel with a pass threshold, a panel that votes on merged findings, and a tournament that keeps the best of n. Graph forms are plain packages over one contract, so you can write your own.
Also included
macOS and Linux
A token budget for a run
A fallback tool when one goes down
The cost of a run, priced from its record
A rate limit waited out, or a pause you resume
A page for each run, in your browser
Stand-in models for tests
Coming soon
Answer from Slack
One Slack thread per run, and Yes and No on the question in the channel. The run carries on from the answer.
Run a step on another machine
One step of a run on another machine. The orchestrator and the record stay on yours.
Fast and deliberate, side by side
A fast typed decision first. When its confidence is under the bar, the step goes to a deliberate model.
Sidekick
A fast model watches the builder work and only chips in when it goes off the brief, skips a step or misses key logic.
Watch every run from one place
A stream of the run's events and a published shape for its state, so a view can be ours or yours.
A question that shows you the real thing
The run hands you the built thing itself, running, reachable from a phone.