---
title: "Review steps"
url: https://services.dev/docs/service/review
captured: [gate-step-unreachable.txt, legal-walk.txt, review.txt, tax-check.txt]
---
# Review steps

URL: https://services.dev/docs/service/review

> A review step hardens the work before it, or extends it. Adversarial lenses attack it and flag it back; affirmative lenses propose additions and keep the ones that hold.

A review answers one question: did the work hold up before anyone saw it?

## Smallest form

```ts title="grow/review.ts"
    translate: 'ai',
    harden: { review: 'adversarial', lenses: ['meaning', 'terms'], rounds: 2 },
```

```bash
npx services.dev check
```

```txt title="magic values: none"
  steps   translate · ai → harden · ai · adversarial · 2 lenses
```

## Every form

**On the Service**

| Key | Written | Left out | Does |
|---|---|---|---|
| `review` | `Review` | `'adversarial'` | How the AI checks every job before it ships: `'adversarial'`, or `'affirmative'` for creative work. |

**On a review step**

| Key | Written | Left out | Does |
|---|---|---|---|
| `review` | `Review` | required | `'adversarial'` attacks the work and flags it back; `'affirmative'` proposes additions and keeps the ones that hold. |
| `of` | `<step>` | the step before | The step reviewed. |
| `lenses` | `string[]` | one | One AI reviewer per lens, in parallel. |
| `rounds` | `number` | 3 | Flag the work back and review again at most this many times, inside the AI budget. |
| `by` | `RoleWord` | none | A person with this role reviews instead of AI. |
| `exhausted` | `<step> \| <end>` | the job stops at $0 | Where still-flagged work goes once `rounds` run out: a step (a person, with review/&lt;step>.json as their packet) or an end. |

## Every AI job gets one

Leave reviews out, and the last AI step is reviewed once, adversarially: `draft · ai → review · ai · adversarial`. Write a review step to choose its lenses, its rounds and where it sits. `review: 'affirmative'` on the Service makes the default one extend instead.

## Adversarial hardens

Each lens is one AI reviewer, and the lenses run at once. A finding over the bar flags the work back to the step it reviews, the AI redoes it with the findings as feedback, and the review runs again, up to `rounds` times:

```txt
→ POST /localization {"strings":"strings.json","listing":"listing.txt","to":"de","max_usd":49,"key":"flag-once","metadata":{"sandbox":{"review":"flag_once"}}}
```

```txt title="magic values: metadata.sandbox.review = flag_once"
  record  terms · ai | strings · ai | listing · ai | qa · ai · adversarial · 3 lenses · flagged | strings · ai · redo | qa · ai · adversarial · 3 lenses · pass
```

Each round writes `review/<step>.json` to the job: the findings, a signal per lens, and the verdict. The next person at the job reads it in their packet. Each lens keeps its own find rate across jobs, and an eval scores it against real outcomes before a change ships: [Quality](https://services.dev/docs/operate/quality.md).

```bash
curl http://localhost:8787/owner/jobs/job_7Hq6/export   # review/qa.json, round 0
```

```json title="magic values: metadata.sandbox.review = flag_once"
{
  "step": "qa",
  "of": "strings",
  "kind": "adversarial",
  "round": 0,
  "lenses": [
    "placeholders",
    "length",
    "tone"
  ],
  "findings": [
    {
      "lens": "placeholders",
      "severity": "high",
      "says": "Simulated: placeholders found a problem in strings."
    }
  ],
  "signals": {
    "placeholders": true,
    "length": false,
    "tone": false
  },
  "verdict": "flagged"
}
```

## Affirmative extends

"Yes, and." Each lens proposes an addition, an adversarial pass judges each one, and only the additions that survive are folded into the work:

```ts title="services/store-copy.ts"
    copy: 'ai',
    // Yes, and: each lens proposes an addition, and only the ones that survive an adversarial pass are folded in.
    extend: { review: 'affirmative', lenses: ['examples', 'keywords'] },
```

```txt
→ POST /store-copy {"app":"a budgeting app for couples","to":"de","max_usd":29,"key":"yes-and","metadata":{"sandbox":{"review":"flag_once"}}}
```

```txt title="magic values: metadata.sandbox.review = flag_once"
  record  copy · ai | extend · ai · affirmative · 2 lenses · pass · 1 folded
```

## When the rounds run out

Every round spends from the job's AI budget. With `exhausted`, work that's still flagged goes to a step you name, such as a person with the review as their packet, or to an end. Without it, the job stops and the buyer pays $0.

## A person reviews

`by: '<role>'` makes a person the reviewer instead of AI. Put reviews before anyone signs: `check` fails `review_after_attest` when a person would attest work no review has passed.

## Seven surfaces

No call of its own: it changes what the job carries, on every surface that reads one.

Events: `review.done`.

[Operations](https://services.dev/docs/reference/operations.md) has every column of every row.

## Refusals

| `check` gate | Why |
|---|---|
| [`review_after_attest`](https://services.dev/docs/reference/check/review_after_attest.md) | A person attests work no review has passed: the review runs after the signature. |

| Written | What TypeScript says |
|---|---|
| `review: 'none', price: 49` | `Type '"none"' is not assignable to type 'Review \| undefined'.` |
| `deliver: { draft: 'ai', examine: { review: 'adversarial', exhausted: 'nobody' } }, price: 49` | `Type '"nobody"' is not assignable to type '"draft" \| "examine" \| undefined'.` |

## In the example brands

The lines that use it in the example brands, as their files write them, and what `check` read aloud for each file.

```ts title="legal · services/contract-review.ts · 16 lines"
    attack: { review: 'adversarial' },
```

```txt title="npx services.dev check · captured/legal-walk.txt"
services/contract-review.ts · 16 lines · $99 per job, within 48 hours, charged on acceptance.
  steps   consent · client · consent → flag · ai → attack · ai · adversarial → advise · attorney · client · stamped · 2-item checklist → send · ai
```

[The legal example](https://services.dev/docs/examples/legal.md) runs every one of its files, captured.

```ts title="tax · services/amended-return.ts · 22 lines"
    check: { review: 'adversarial', lenses: ['what changed', 'numeric tie-out'] },
```

```txt title="npx services.dev check · captured/tax-check.txt"
services/amended-return.ts · 22 lines · $900 per job, within 30 days, charged when file or aar is accepted.
  steps   compute · code · cites depreciation and carryforward schedules → draft · ai · cites the workpapers and the records the organizer took → check · ai · adversarial · 2 lenses → prepare · preparer · * · 1-item checklist → sign · signer · * · stamped in the paid-preparer block → authorize · owner · sign → file · IRS and states, unless a partnership → aar · IRS · Form 8082, for a partnership · emits k1.adjusted
```

[The tax example](https://services.dev/docs/examples/tax.md) runs every one of its files, captured.

---

- Up: [Service()](https://services.dev/docs/service.md)
- Previous: [AI steps and briefs](https://services.dev/docs/service/ai.md)
- Next: [Asks: a ceremony without an account](https://services.dev/docs/service/ask.md)
