> ## Documentation Index
> Fetch the complete documentation index at: https://obversa.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Built-in Workflows

> @obversa/builtin-workflows: three ready-made teams, a writer and reviewer, a review panel, and feature delivery, and a workflow that improves a workflow from its record, as jobs for run().

`@obversa/builtin-workflows` ships three teams you configure instead of
write: a writer and a reviewer, a review panel with a threshold, and a
nine-stage feature delivery. Give one its brief, workspace, expected files,
test command and engine seats, and it returns a job for `run` from
`@obversa/runtime`. To declare your own roles and stages, use `workflow()`
from the [Runtime](/docs/packages/runtime#declare-a-team) instead; this package
doesn't export the builders.

The package also ships `improveWorkflow`. It reads the record of a
workflow's run, proposes one change to the workflow file, and applies it
when a person says yes. [Workflows That Improve
Themselves](/docs/patterns/workflows-that-improve-themselves) shows it end to
end.

## Install

<CodeGroup>
  ```bash npm theme={null}
  npm install @obversa/builtin-workflows
  ```

  ```bash pnpm theme={null}
  pnpm add @obversa/builtin-workflows
  ```
</CodeGroup>

Included in `@obversa/obversa`.

## Quickstart

A writer, a real Node check and a reviewer, with scripted engines so it
runs offline:

```ts examples/builtin-workflows.ts (excerpt) {1,5-6,7-11} theme={null}
  const job = writerReviewerPair({
    brief: 'Write answer.txt containing 42 followed by a newline.',
    workspace,
    files: ['answer.txt'],
    writer,
    reviewer,
    test: {
      command: process.execPath,
      args: ['-e', 'require("node:assert/strict").equal(require("node:fs").readFileSync("answer.txt", "utf8"), "42\\n")'],
      timeoutMs: 5_000,
    },
  });
  const result = await run(job, { recordTo: join(workspace, 'run.jsonl') });
```

The writer writes the file, the test command checks it, and the reviewer
reads it back; a rejection returns the findings to the writer. Run it with
`npx tsx builtin-workflows.ts`:

```json Output theme={null}
{
  "status": "pass",
  "writes": 1,
  "reviews": 1
}
```

## Choose a recipe

| Function | What it runs | Configuration |
| - | - | - |
| `writerReviewerPair` | A writer, the test command, and a reviewer whose findings return to the writer. | `PairConfig`: `writer`, `reviewer`, optional `maxKickbacks`. |
| `thresholdPanel` | An implementation, the test command, and a panel that needs `threshold` acceptances. | `PanelConfig`: `implement`, `reviewers`, `threshold`, optional `maxKickbacks`. |
| `featureDelivery` | Research, requirements, planning, tests, implementation, review, approval, evidence and learning. | `FeatureDeliveryConfig`: `analyse`, `implement`, `reviewers`, `reviewThreshold`, `approve`, `testFiles`, optional `maxKickbacks`. |

Every configuration extends `TeamInput`. A seat is a `TeamSeat` from
`@obversa/api`, an engine with its declared identity:

```ts examples/builtin-workflows.ts (excerpt) {1-2,7-10} theme={null}
  const writer: TeamSeat = {
    engine: new MockEngine(() => {
      writes += 1;
      writeFileSync(answer, '42\n');
      return JSON.stringify({ status: 'pass', summary: 'Wrote the answer.' });
    }),
    identity: {
      adapter: 'mock', provider: 'local', modelFamily: 'writer',
      model: 'writer-offline', tools: ['Write'],
    },
  };
```

## Read a review decision

`outcomeFromAgentText(text, target?)` reads a reviewer's reply: the first
JSON decision object becomes a pass, or findings aimed at `target`.
`INVALID_TEAM_DECISION` is the message for a reply that holds no decision.
Both are re-exported from `@obversa/runtime/workflow-support`.

## Options

### `TeamInput`

| Field | Type | Default | Description |
| - | - | - | - |
| `brief` | `string` | required | The work, as every seat reads it. |
| `workspace` | `string` | required | The directory the team writes in. |
| `files` | `readonly string[]` | required | The files the brief expects written. |
| `test` | `TestCommand` | required | `{ command, args, timeoutMs? }`, run after the writer. |

### Seats and thresholds

| Field | Type | Default | Description |
| - | - | - | - |
| `writer`, `reviewer`, `implement`, `analyse`, `approve` | `TeamSeat` | required | One engine with its identity per role. |
| `reviewers` | `readonly ReviewerSeat[]` | required | Each a `name`, a `seat` and an optional review `scope`. |
| `threshold`, `reviewThreshold` | `number` | required | How many reviewers must accept. |
| `testFiles` | `readonly string[]` | required for `featureDelivery` | The test files written before the code; every entry must be in `files`. |
| `maxKickbacks` | `KickbackBudget` | none | How often a review may run the writer again. |

## Errors

* **A reply with no decision** is marked `INVALID_TEAM_DECISION` by
  `outcomeFromAgentText`.
* **Seats from one model family** on both sides of a review are refused
  before any model runs.

<Accordion title="Full file">
  ```ts examples/builtin-workflows.ts theme={null}
  import assert from 'node:assert/strict';
  import { readFileSync, writeFileSync } from 'node:fs';
  import { mkdtemp, realpath, rm } from 'node:fs/promises';
  import { tmpdir } from 'node:os';
  import { join } from 'node:path';

  import type { TeamSeat } from '@obversa/api';
  import { writerReviewerPair } from '@obversa/builtin-workflows';
  import { MockEngine } from '@obversa/core/testing';
  import { run } from '@obversa/runtime';

  const workspace = await realpath(await mkdtemp(join(tmpdir(), 'obversa-pair-')));
  const answer = join(workspace, 'answer.txt');
  let writes = 0;
  let reviews = 0;

  try {
    const writer: TeamSeat = {
      engine: new MockEngine(() => {
        writes += 1;
        writeFileSync(answer, '42\n');
        return JSON.stringify({ status: 'pass', summary: 'Wrote the answer.' });
      }),
      identity: {
        adapter: 'mock', provider: 'local', modelFamily: 'writer',
        model: 'writer-offline', tools: ['Write'],
      },
    };
    const reviewer: TeamSeat = {
      engine: new MockEngine(() => {
        reviews += 1;
        assert.equal(readFileSync(answer, 'utf8'), '42\n');
        return JSON.stringify({ status: 'pass', summary: 'The answer matches the brief.' });
      }),
      identity: {
        adapter: 'mock', provider: 'local', modelFamily: 'reviewer',
        model: 'reviewer-offline', tools: ['Read'],
      },
    };
    const job = writerReviewerPair({
      brief: 'Write answer.txt containing 42 followed by a newline.',
      workspace,
      files: ['answer.txt'],
      writer,
      reviewer,
      test: {
        command: process.execPath,
        args: ['-e', 'require("node:assert/strict").equal(require("node:fs").readFileSync("answer.txt", "utf8"), "42\\n")'],
        timeoutMs: 5_000,
      },
    });
    const result = await run(job, { recordTo: join(workspace, 'run.jsonl') });
    assert.equal(result.outcome.status, 'pass');
    assert.equal(writes, 1);
    assert.equal(reviews, 1);
    console.log(JSON.stringify({ status: result.outcome.status, writes, reviews }, null, 2));
  } finally {
    await rm(workspace, { recursive: true, force: true });
  }
  ```
</Accordion>

## API

* **`writerReviewerPair(config)`**, **`thresholdPanel(config)`**,
  **`featureDelivery(config)`**: the three recipes.
  [Choose a recipe](#choose-a-recipe).
* **`improveWorkflow(config)`**: one change to a workflow file from its
  run's record, applied on a person's yes. `ImproveWorkflowConfig` takes
  `record`, `workflow`, `proposer` and `reviewer`.
  [Workflows That Improve Themselves](/docs/patterns/workflows-that-improve-themselves).
* **`outcomeFromAgentText`**, **`INVALID_TEAM_DECISION`**: read a
  reviewer's reply. [Read a review decision](#read-a-review-decision).
* **Types**: `PairConfig`, `PanelConfig`, `FeatureDeliveryConfig`,
  `ImproveWorkflowConfig`, `TeamInput`, `TestCommand`, `ReviewerSeat`.

## Next steps

* [A writer and a reviewer](/docs/patterns/writer-and-reviewer): the same pair
  declared with `workflow()` and run on real engines.
* [Runtime](/docs/packages/runtime): `workflow`, `stage` and `person` for a team
  of your own.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.