Skip to content
jonny981Public

About

Durable workflows for agent teams in TypeScript: named roles, evals that decide the next step, and a person's approval where you want one. Runs Claude Code, Codex and their kin as a team. No server, no database.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

5 stars

Watchers

0 watching

Forks

Latest commit

 

History

1,097 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Obversa

Model how your team really works.

license: MIT node >=22.12 TypeScript strict npm: @obversa/runtime CI

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.

Install

npm install @obversa/obversa

Node.js 22.12 or later. @obversa/obversa installs the runtime and every plugin but five engines, which you install on their own: npm install @obversa/engine-jev-api for Jev, npm install @obversa/engine-openai-decisions for OpenAI's Decisions API, npm install @obversa/engine-devin-cli for Devin, npm install @obversa/engine-mastra @mastra/core for a Mastra agent and npm install @obversa/engine-openai-agents @openai/agents zod for an OpenAI Agents SDK agent. 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.

Familiar patterns

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.

Get a second opinion

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, for a finding tagged block too. The
        // rounds end when Jev stops them or the grader passes the draft.
        refine: judge(judgeSeat),
      }),

In workflow(), the reviewer has to come from a different model family from the writer. Get a second opinion explains the review loop.

Know when to stop

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:

            judge: judge(judgeSeat),

Here Jev makes the judgement, and the returns to the builder end when Jev stops them or the review passes. Jev answers one question per finding, a score from cosmetic to blocking, and two about the round: product_decision, a noul asking whether a person must settle a product decision first, and stop_reason, a choice of how the run should end, none_applies included. Jev decides a blocking finding too. Each answer is gated on the confidence it reports: a finding goes back only at or over the send-back bar for its level, a stop that would end the run needs the stop bar, and a none_applies or an answer under the hand-off bar goes to a person. To bound the returns as well, pass judge(judgeSeat, { cap: 4 }): it allows four returns to the writer, and Jev reads the review of the last one too. A product_decision Jev asks for at the cap asks a person, and a change they ask for buys exactly one more round. Findings Jev still calls impactful change the ending: a count falling from the round before grants one more round, and a count that is not falling pauses the run to ask a person. The person can name "act" or "hold" for each finding. A finding they hold joins minorFindings. A finding Jev acted on that they do not name goes back with their answer, unless they approve the work. When nothing goes back, the work stands as a pass with no extra build. A run never ends as a silent fail while findings Jev called impactful are still open. A finding Jev holds back, scored cosmetic or minor or under its confidence bar, does not cost a round, whatever the reviewer tagged it: Jev collects it on minorFindings, with its reviewer, its level and its confidence, for a person to read before the work lands. Know when to stop shows the questions and a complete writing example.

Ask a panel

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.

Ask a person

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.

Automate the routine work

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.

Pick up unfinished work

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.

Complete workflows

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 Build and test the change, check every requirement, review it with three model families and approve the exact bytes.

Browse the workflows for the full files and examples from other fields.

Deliver a feature

The feature team takes a ticket through seven steps:

  1. Build. One seat builds the change from the ticket.
  2. Checks. The ticket's tests run. A red test goes back to the builder with its output.
  3. Goal check. A seat from another model family confirms that every requirement in the ticket was met.
  4. Review battery. Three reviewers from three model families review the change at the same time.
  5. Synthesis. Their reviews become one list.
  6. Judge. Jev scores each finding cosmetic, minor, impactful or blocking, each with a confidence it reports. An impactful or blocking finding at or over its confidence bar goes back to the builder; every other finding is held back for a person to read.
  7. Approval. Attended, a person approves the exact bytes of every file the change adds, changes or deletes. With --unattended, the change lands without asking.
      nodes: {
        build: {
          desc: `Make the change in ${files.join(', ')}. Do not change ${tests.join(', ')}.`,
          gate: 'The ticket is met and its tests pass.',
          agent: builder,
          writes: files,
          // The node's own refinement, each round: the ticket's tests first,
          // a red one goes straight back with its output; then the goal
          // check; then the three reviewers as one panel; then Jev scores
          // each kept finding cosmetic to blocking, a block included, with
          // no cap; a held-back one is collected for a person to read.
          refine: review({
            checks: [['node', '--test', ...tests]],
            goal: goalCheck(goalSeat, { target: 'build', text: ticket.brief }),
            reviewers: [
              { name: 'codex', seat: reviewers.codex },
              { name: 'gemini', seat: reviewers.gemini },
              { name: 'grok', seat: reviewers.grok },
            ],
            // The merge asks for no tools, a call Codex refuses; Grok takes it.
            synthesise: reviewers.grok,
            judge: judge(judgeSeat),
          }),
        },
        ...(attended ? { approve: { needs: 'build', job: approve(start!) } } : {}),
      },

Each ticket runs in its own worktree, and the change lands on your branch when the run passes. examples/teams/feature-team-backlog.ts points the same team at a folder of tickets and delivers them one at a time. The feature delivery file is examples/teams/feature-delivery.ts.

Engines

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-devin-cli Devin Devin, 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
@obversa/engine-mastra an agent you built with Mastra @mastra/core and what your agent needs
@obversa/engine-openai-agents an agent you built with the OpenAI Agents SDK @openai/agents, zod and what your agent needs

Write your own against the engine contract in @obversa/api; it must pass the conformance kit.

Where a workflow lives

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.

Documentation

obversa.ai/docs: the first run, the concepts, the patterns, examples by field, and how Obversa sits beside LangGraph, CrewAI, Temporal, Claude Code subagents and eve.

Working on Obversa

24 publishable packages. packages/ holds the nine 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/rdc ranks, decides and curates memory candidates, @obversa/builtin-workflows ships three ready-made teams and a workflow that proposes changes to a workflow from its record, @obversa/obversa is the install above, and @obversa/surface with @obversa/surface-diff is the local review page. plugins/ holds the fifteen adapters: the eleven 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.

License

Obversa uses the MIT License. Report security problems as described in the security policy.

About

Durable workflows for agent teams in TypeScript: named roles, evals that decide the next step, and a person's approval where you want one. Runs Claude Code, Codex and their kin as a team. No server, no database.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

5 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages