---
title: "Harden it"
url: https://services.dev/docs/grow/review
captured: [grow-review-tabs.txt, grow-review.txt]
---
# Harden it

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

> Add a review step that attacks every translation before the buyer sees it, and watch the redo it sends back.

A review step attacks the work before the buyer sees it: one AI reviewer per lens flags what's wrong, and the work goes back for a redo. It's how a draft is hardened before anyone signs it. This is Grow 1. Each page adds one key to the four-line Translation from [your first paid job](https://services.dev/docs.md), shows the whole file, runs `check`, runs one job on the four buyer surfaces, and ends on what changed across all seven.

## Add the step

Write each step as its own file under `grow/`, so `diff` can compare them. `check` counts 8 lines, the ones inside `Service({ … })`:

```ts title="grow/review.ts"
import { Service } from 'services.dev'

// Grow 1, harden it: two AI reviewers attack every translation at once, one for meaning and one for
// terms, and flag it back for a redo until it holds, at most twice, inside the AI budget.
export default Service({
  name: 'Translation',
  input: { document: 'file', to: 'language: a BCP 47 tag, such as fr-CA' },
  output: { document: 'file' },
  deliver: {
    translate: 'ai',
    harden: { review: 'adversarial', lenses: ['meaning', 'terms'], rounds: 2 },
  },
  price: 49,
})
```

`translate` is the AI doing the work. `harden` is a [review step](https://services.dev/docs/service/review.md) of it.

| Key | What it does | Default |
|---|---|---|
| `review` | `'adversarial'` attacks the step it reviews; `'affirmative'` extends it, "yes, and", for creative work | required |
| `of` | The step it reviews | the step before |
| `lenses` | One AI reviewer per lens, all at once | one reviewer |
| `rounds` | How many times it may flag the work back and review again, inside the AI budget | 3 |
| `exhausted` | Where work still flagged after the last round goes: a step, such as a person's second look, or an end | the job stops at $0 |
| `by` | A role whose member reviews instead of AI, for work only a credential may pass | AI |

## Check it

`check` reads `services/`, so put the step there:

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

```txt title="magic values: metadata.sandbox.review = flag_once"
services/translation.ts · 8 lines · $49 per job, within 48 hours, charged on delivery.
  who     AI delivers every job.
  steps   translate · ai → harden · ai · adversarial · 2 lenses
  agent   order_translation · 24 of 30 words · bought job_7Hq2 · $49.00 · no person
  ledger  +$49.00 · AI $1.26 (example) · you $47.74
  margin  at least $39.20 a job (80%) · a job that stops costs you at most $9.80
  apps    customer portal · owner console

mcp     13 buyer tools · 4,486 of 8,000 tokens · 9 more with ?toolset=sandbox
✓ ready to sell · 1 Service · AI delivers it alone · it keeps at least 80% of every job
  next    npx services.dev dev

AI and money are simulated until you wire them. $0.42 a call is an example.
```

The `steps` line reads the graph back. AI still delivers every job alone, and the floor holds at 80%: every round spends from one AI budget, capped at a fifth of the price.

## Run one job

The magic value `metadata.sandbox.review = flag_once` makes the first round flag the work, so the redo runs without waiting for a real miss:

**CLI**

```bash title="CLI"
npx services.dev translation document=https://example.com/lease.pdf to=fr-CA --max 49 --key grow --metadata sandbox.review=flag_once
```

```txt title="magic values: metadata.sandbox.review = flag_once"
job_7Hq2 · working · $49.00 held
job_7Hq2 · delivered · lease.fr-CA.pdf · $49.00 charged
  record  translate · ai | harden · ai · adversarial · 2 lenses · flagged | translate · ai · redo | harden · ai · adversarial · 2 lenses · pass
  ledger  +$49.00 · AI $2.52 · you $46.48
  next    npx services.dev accept job_7Hq2
```

**API**

```bash title="API"
curl -X POST http://localhost:8787/jobs -H 'content-type: application/json' -d '{"service":"translation","document":{"url":"https://example.com/lease.pdf","name":"lease.pdf"},"to":"fr-CA","key":"grow","max_usd":49,"metadata":{"sandbox":{"review":"flag_once"}}}'
```

```txt title="202 · job_7Hq2 · working · $49.00 held · magic values: metadata.sandbox.review = flag_once"
  next    get_job {"id":"job_7Hq2"}

$ curl http://localhost:8787/jobs/job_7Hq2?wait=50
← 200 · job_7Hq2 · delivered · lease.fr-CA.pdf · $49.00 charged
```

**MCP**

```txt title="MCP"
→ tools/call order_translation {"document":{"url":"https://example.com/lease.pdf","name":"lease.pdf"},"to":"fr-CA","key":"grow","max_usd":49,"metadata":{"sandbox":{"review":"flag_once"}}}
```

```txt title="magic values: metadata.sandbox.review = flag_once"
← job_7Hq2 · working · $49.00 held
  next    get_job {"id":"job_7Hq2"}

→ resources/read job://job_7Hq2?wait=50
← job_7Hq2 · delivered · lease.fr-CA.pdf · $49.00 charged
```

**SDK**

```ts title="SDK"
await lingua.translation.order({"document":{"url":"https://example.com/lease.pdf","name":"lease.pdf"},"to":"fr-CA","key":"grow","max_usd":49,"metadata":{"sandbox":{"review":"flag_once"}}})
```

```txt title="magic values: metadata.sandbox.review = flag_once"
← job_7Hq2 · working · $49.00 held
  next    get_job {"id":"job_7Hq2"}

await lingua.jobs.wait("job_7Hq2")
← job_7Hq2 · delivered · lease.fr-CA.pdf · $49.00 charged
```

The record keeps both rounds, the flag and the redo:

```txt
→ POST /translation {"document":"lease.pdf","to":"fr-CA","key":"grow","max_usd":49,"metadata":{"sandbox":{"review":"flag_once"}}}
```

```txt title="magic values: metadata.sandbox.review = flag_once"
← job_7Hq2 · delivered · lease.fr-CA.pdf · $49.00 charged
  record  translate · ai | harden · ai · adversarial · 2 lenses · flagged | translate · ai · redo | harden · ai · adversarial · 2 lenses · pass
  ledger  +$49.00 · AI $2.52 · you $46.48
```

The redo cost a second pass: $2.52 of AI, where `check` priced a clean run at $1.26. Both sit under the $9.80 cap, and you kept $46.48.

## What changed

```bash
npx services.dev diff services/translation.ts grow/review.ts
```

```txt title="magic values: metadata.sandbox.review = flag_once"
services/translation.ts (4 lines) → grow/review.ts (8 lines)
Translation · changed deliver

API   ~ operation service.quote.translation  GET /translation.json  answers.deliver
      ~ operation brand.manifest             GET /manifest.json  answers.services
CLI   no change
MCP   no change
SDK   ~ object job_ (translation)            Job<"translation">  + stage: "translate" | "harden"
Site  ~ operation service.quote.translation  /translation.json  deliver
Docs  no change
App   no change
```

Only the steps moved: the quote's `deliver`, the manifest and the SDK's `stage` union. No call is new, so the CLI, MCP, Docs and App don't change. [The step graph](https://services.dev/docs/service/graph.md) adds branches, loops and joins.

In a brand, [the legal example](https://services.dev/docs/examples/legal/provisional-patent.md) reviews a draft through four lenses, three rounds, and its `exhausted` sends what's still flagged to a credentialed second look.

## Next

[Ask the buyer](https://services.dev/docs/grow/ask.md): a question the buyer answers before the AI starts.

---

- Up: [Your first paid job](https://services.dev/docs.md)
- Previous: [Your own Service](https://services.dev/docs/own.md)
- Next: [Ask the buyer](https://services.dev/docs/grow/ask.md)
