# Result Protocol

## Successful Results

Every successful prompt cycle ends with a `crew_report` result:

```ts
{
  outcome: "completed";
  report: string;
}
{
  outcome: "needs_input";
  report: string;
  needs: string;
}
```

`report` is non-blank and contains the subagent's complete role-specific final answer; no second summary follows. `needs` is required and non-blank only for `needs_input`, and is forbidden for `completed`. Unknown fields are rejected.

The final tool-call batch must contain only one successful `crew_report` call. Task tools may be used in earlier batches. Acceptance is judged from the cycle's last assistant message and the corresponding tool result.

If a normal cycle ends without a valid final report, pi-crew sends one finalization follow-up prompt automatically, visible in the child session. A second reportless normal ending becomes a terminal contract error. Technical `error` and `aborted` endings receive no reminder.

Validation is cycle-local: every `crew_spawn` and `crew_respond` starts fresh. A report from an earlier cycle cannot satisfy a later one.

## Tool Results

All pi-crew-produced tool results are JSON in model context. TUI renderers build readable confirmations and cards separately; `details` are not model-bound. Discovery (`crew_list`), open-session snapshots (`crew_status`), asynchronous results, and automatic context messages retain their existing JSON shapes.

Acknowledgements omit redundant `ok` and `action` fields: Pi already associates each result with its tool call and marks errors separately. Spawn, respond, and done return the pi-crew subagent ID, not the Pi session ID:

```json
{"subagent_id":"worker-123"}
```

Abort returns the actual affected IDs:

```json
{"aborted_ids":["worker-123"],"missing_ids":[],"foreign_ids":[]}
```

The `crew_report` acknowledgement is `{}`. Tool arguments already carry the assignment or follow-up, so acknowledgements do not repeat those values. Asynchronous deliveries retain `subagent_id` and `brief` so parallel results remain identifiable.

The `report` acknowledgement goes to the child, not the owner. The owner receives the validated result through asynchronous delivery; `report` remains a string that can contain Markdown.

Errors originating in pi-crew tool execution are serialized as `{"error":"..."}` and still thrown, so Pi marks the tool result as an error. The TUI displays only the error text. Errors generated by Pi itself, such as schema rejection before a tool executes, are outside this contract and may remain plain text. JSON changes neither non-blocking execution nor termination, validation, or ownership rules.

## Lifecycle

- `running`: the subagent is working and cannot receive follow-up messages.
- `completed`: verify the report, request corrections if needed, then close it.
- `needs_input`: answer the requested intervention through `crew_respond`.
- `error` or `aborted`: terminal; the child session is disposed.

Both successful outcomes remain visible and abortable until explicitly closed with `crew_done`. Follow-ups retain the child's own conversation; send only new information, not a restated task. Use a new spawn for a different deliverable.

`crew_spawn` and `crew_respond` return without waiting for the prompt cycle. A follow-up starts a new cycle, moving the subagent back to `running`.

## Owner Delivery

Delivered model context is JSON, separate from TUI presentation. Successful deliveries preserve the validated result exactly:

```json
{
  "subagent_id": "worker-123",
  "brief": "task label",
  "result": {
    "outcome": "needs_input",
    "report": "The implementation needs a product decision before proceeding.",
    "needs": "Should expired invitations be deleted or retained?"
  }
}
```

Error deliveries use the same identity fields with `status` and `error`, never a fabricated successful result. Aborts are TUI-only entries, not model context: the owner already knows from `crew_abort` or is shutting down.

Every delivered `completed`, `needs_input`, or `error` result uses `{ deliverAs: "steer", triggerTurn: true }`:

| Owner state | Delivery                                                                        |
| ----------- | ------------------------------------------------------------------------------- |
| Idle        | Delivered immediately and triggers a turn                                       |
| Streaming   | Queued as steer into the running turn                                           |
| Inactive    | Queued; pending messages older than 24 hours are dropped when the owner returns |

Results are not held back while peers are still running. When several results feed one next step, orchestration must wait for the whole batch before synthesis or the next stage. See the [skill](../skills/pi-crew/SKILL.md#integrate) and the workflow's own acceptance rules.

On `/new`, `/resume`, and `/reload`, background work is preserved and delivery reconnects when its owner becomes active. A runtime-version change during reload aborts the stale runtime's subagents before replacing it. On `/fork` and `/clone`, active subagents—including those awaiting an owner action—and their queued results move to the new session. Quitting Pi aborts active subagents.

## Owner Status Context

`crew_list` returns definition discovery and warnings as JSON. `crew_status` returns the current owner's open sessions as JSON. Their TUI renderers use structured details rather than copying Markdown into model context.

Pi-crew also appends a compact `crew_status` JSON snapshot containing only open-session IDs and statuses when that state changes. Snapshots are appended before an owner run starts or at the end of a turn, after tool results. Changes between these boundaries are coalesced. Closing the last session emits an empty list once; a session that never had open work receives nothing.

A `crew_reminder` JSON message lists only sessions awaiting owner action after ten owner model calls since the last snapshot or reminder. Running sessions do not generate reminders. A snapshot takes priority over a reminder and resets its counter. The interval is an initial policy choice, not a measured optimum.

Context observation counts model calls. Snapshot deduplication reads the compaction-aware session record, not the SDK loop's message list: a persisted turn-end message can be absent from that list even without compaction. Before each model request, pi-crew inserts only its missing persisted status/reminder messages at their recorded positions. Each has a unique ID in non-model-bound details, so an already-present message is not duplicated. Existing messages are never replaced, removed, or reordered. If compaction removes a snapshot from the session context, a fresh snapshot is appended at the next safe boundary while open work exists; compacted messages are never replayed. Status messages are model-visible but hidden in the TUI; they never start an owner turn. There are no timers, automatic tool calls, automatic closes, or final-answer gates.

Previous messages and the system prompt remain unchanged. Repaired messages stay in the same position on subsequent requests instead of moving to the end. The offline SDK regression test checks the actual provider-bound message prefix across requests, including the next owner run; provider cache-hit behavior still requires provider-specific measurement. Snapshots are historical observations, not live authority: the newest snapshot supersedes older ones. Verify completed results and use `crew_done` when no follow-up remains; use `crew_respond` for corrections or requested input.

## TUI Presentation

Results appear as Markdown cards rather than protocol JSON. Each card starts with the outcome icon, subagent ID, and task label, followed by the unchanged report. Needs-input cards put a **Needs** section before the report; error cards show their explanation.

Cards are rendered from structured result details, not copied display text in model context. Aborts render as compact TUI-only cards with the reason and never trigger an owner turn. See [Results and Sessions](../README.md#results-and-sessions) for everyday TUI usage.
