# Jobs & hand-offs

You spawned it, you own it — the follow-up contract, and the only signals that finish a job.

## Hand-off protocol — you spawned it, you own it

Handing a task to a pane is not done when the spawn returns; it is done when
you have **read the result, verified it, and reported back** to whoever gave
you the task. The loop:

1. Spawn with a `prompt` (either a prompt or an explicit `spawner` opens the
   contract), and identify yourself: pass `spawner` (your pane id —
   every agent pane has it in `$MANDECK_PANE`; the CLI and MCP server send it
   automatically). Estimate how long the work should take and pass
   `check_after` minutes (default 10).
2. While it runs, poll `GET /v1/events?since=<seq>` or check `mandeck waiting`
   — a pane that bells needs an answer, not patience.
3. When it stops (or your `check_after` passes), read its `screen` /
   `messages`, **verify the work actually happened** — a confident final
   message is not evidence — then report the outcome.

The deck enforces this. A spawn that carried a prompt and goes unchecked gets
collected: the deck types a `[mandeck follow-up]` reminder into the spawner's
own terminal, emits a `needs-review` event, and if still ignored (or the
spawner is gone) rings the human's attention bell on the pane. Reading the
pane's screen or transcript settles the contract.

**`needs-review` is not job `done`.** Follow-up settle means someone looked (or
the deck collected). The tasked work may still be in flight, sitting at a
prompt, or waiting on a human. Job state is the other signal — see below.

## Job lifecycle — when is the work done?

Pane liveness is the wrong question. `live: false` means the session record
ended or the process exited. A pane that answered and sits at a prompt stays
`live`. `needs-review` means the follow-up contract was collected. None of
those mean the tasked work finished.

A `POST /v1/spawn` creates a **job** (`job` in the spawn body) with one of:

| state | means |
|---|---|
| `running` | tasked work is in flight (or we cannot tell yet) |
| `waiting-you` | the pane's attention bell is up (`needsYou`) and the job is not terminal |
| `done` | the tasked work finished — only via an explicit signal |
| `failed` | the tasked work errored — only via an explicit signal |

**How `done` / `failed` are decided (do not lie):**

The deck does **not** infer completion from silence, a prompt, `live: false`,
unfocus, follow-up collection (`needs-review`), or the pane closing. The only
terminal signals are:

1. `POST /v1/jobs/:id` with `{"state":"done"}` or `{"state":"failed"}`
2. `/opt/homebrew/bin/mandeck job done` / `job fail` (same POST; uses
   `$MANDECK_JOB` if you omit the id)
3. The agent prints the exact token `[mandeck job-done]` or
   `[mandeck job-failed]` **alone at the start of its own line** (e.g. via
   `echo`; picked up on the next job GET). Mentioning the token mid-sentence
   — "I'll print [mandeck job-done] when I finish" — deliberately does NOT
   count, so instructions about the protocol can't finish a job by accident

A pane that is still `running` after it "looks finished" is honest: we do not
have the signal. `job wait` then times out. That is the correct answer, not a
Cloud-style completion fake.

Every API-spawned pane gets `MANDECK_JOB` (and `MANDECK_PANE`) exported. If
you are the tasked agent, finish with:

```bash
/opt/homebrew/bin/mandeck job done
# or print: [mandeck job-done]
```

`GET /v1/jobs/:id` includes `report.messages` / `report.screen` so a bot can
fetch the transcript without guessing routes. Reading the job does not settle
follow-up; `job report` (which hits session messages/screen) does, because
that is a real check.

**Waiting and the follow-up contract.** A long `job wait` polls only the job,
so it does not settle the spawn's follow-up contract — if the work runs past
`check_after` (default 10 min), the spawner still receives the
`[mandeck follow-up]` reminder mid-wait. Set `check_after` comfortably longer
than your wait timeout; the contract settles the moment you `job report`.
Collection sweeps on a ~30s tick, so even a tiny `check_after` fires within
about half a minute, not instantly.
