# review

> **Preview.** This page describes the interface WIRK launches with. The hosted service is in private staging and access is by invitation. What is live today: /docs/index.md#what-is-live-today

A proposal is a change waiting for a person. `review` decides proposals: **accept** applies the change exactly as previewed, **reject** closes it, **defer** keeps it waiting.

People decide proposals, directly or by asking their agent to. Nobody accepts their own proposal: the service refuses it with `not_authorized` ("You proposed this; someone else accepts it"). The author may still withdraw it (reject) or defer it.

## Find and read proposals

```
wirk status
wirk query proposal=proposed,deferred
wirk query c4a1e902
```

Fetching a proposal shows exactly what it would change:

```
c4a1e902 · proposal · proposed · r1 · by nightly-agents (agent) · changed 1h ago
Mark webhook retries completed

The retry fix merged in 4f2a9c1 and the retry tests pass.

Diff
  ~ 5c1e7a90 r3 Retry failed webhooks
      status in_progress → completed
decide with: review c4a1e902@1 ACTION --reason REASON
more: query c4a1e902 depth=all
```

`r1` is the proposal's revision. Every decision, including defer, makes a new one.

## Decide

```
wirk review c4a1e902@1 accept --reason 'Verified: tests pass on main'
wirk review c4a1e902@1 e7b35d16@2 defer --reason 'Wait for the load test'
```

The last word is the action; every other one is `ID@N`. The CLI requires a real reason and refuses the placeholders `ACTION` and `REASON`, so a pasted `decide with:` line decides nothing until you fill it in.

MCP: `wirk_review` with the request body below; `request_id` is optional there.

## Request

```
POST /v2/review
Authorization: Bearer <token>

{"request_id": "r-5d0e3a9c71",
 "decisions": [
  {"id": "c4a1e902",
   "revision": 1,
   "action": "accept",
   "reason": "Tests pass on main"}]}
```

| Key | Notes |
|---|---|
| `decisions` | Required. 1–32; each is decided on its own |
| `id` | A full (`proposal_…`) or short proposal ID |
| `revision` | The `rN` you read |
| `action` | `accept`, `reject` or `defer` |
| `reason` | Up to 2,048 characters. The CLI and MCP server require one |
| `request_id` | 1–200 characters. Required on the wire; the CLI and MCP server make one when you leave it out |
| `workspace_id`, `format` | As everywhere |

## Response

```
Reviewed r-5d0e3a9c71 · 1 decision: 1 decided
  accepted c4a1e902 r1 → r2
```

```json
{"ok": true,
 "data": {"request_id": "r-5d0e3a9c71",
          "results": [{"id": "proposal_c4a1e902…", "action": "accept", "outcome": "accepted", "r": 2}]},
 "errors": [], "notices": [], "page": {"complete": true, "next_cursor": null}, "timing_ms": {"total": 7.9}}
```

Each decision stands on its own. When some are decided and others are not, the answer is HTTP 207 and says which and why:

```
Reviewed r-9a4c2e6b18 · 3 decisions: 1 decided · 1 conflicted · 1 skipped
  accepted c4a1e902 r1 → r2
  conflicted e7b35d16 · basis_changed: the proposal is at r3 (deferred by alice); you read r2
    Fetch it again (query e7b35d16) and decide at the revision it shows, under a new request_id.
  skipped 3a1f9c20 · not_authorized: you proposed this; someone else accepts it
```

Accepting can also conflict when an item the proposal changes has moved on since it was proposed. Nothing is half-applied: a proposal applies whole or not at all. A proposal that creates or changes context is decided by whoever maintains that context, or by an administrator.

## Retries

As with [write](/docs/write.md#retries): resend the identical body with the same `request_id` after an uncertain result, or look it up with `wirk query receipt=r-5d0e3a9c71`.

All codes: [Errors](/docs/errors.md).
