magi-code 0.102.2

Repository-aware CLI coding agent for terminal work
Documentation
# Negotiated local-agent tool cards

`tool.cards` adds structured live and saved cards to existing activities. Clients opt in during initialization. Cards describe observed tool results; they do not change execution, permissions, or approval routing.

Both projections use [shared tool-card facts](tool-card-projection.md), independent of terminal layout. Raw tool-result and metadata objects are not exposed. The [local-agent protocol](local-agent-protocol.md) still defines envelopes, admission, and transport limits. For a client workflow, see [Display local-agent tool cards](local-agent-tool-cards-handoff.md).

## Negotiation and advertisement

Without `client.tools`, `initialize` params are exactly `{ "required_capabilities": [string, ...] }`, at most **16 request names**. A client supporting this entire schema requests `tool.cards` alongside needed existing names. Independently negotiating [client-provided tools](local-agent-client-tools.md) requires `client_tools` and permits 18 request names. There is no speculative event subscription or second initialization after admission.

An implementing backend must:

1. Validate parameters and all required capabilities before configuration, authentication, filesystem creation, MCP startup or any other startup effect. Unknown `tool.cards` support returns the existing `unsupported_capability` response and leaves initialization unadmitted.
2. Without a `tool.cards` request, omit `tool.cards`, its descriptor, rich events and rich history fields. Base-only clients receive the existing ordered sixteen-capability list; separately negotiated extensions follow their own advertisement contracts.
3. With a supported `tool.cards` request, append `tool.cards` to that base list and include `extensions["tool.cards"]` below. This yields seventeen response names, or eighteen when `client.tools` is also negotiated. Existing `limits` values do not change.
4. Admit initialization once. An admitted startup, including one that later fails, cannot be retried in the same child; `already_initialized` and normal restart rules apply.

Descriptor fields are required; values are fixed for schema 1:

```json
{"schema_version":1,"event":"tool.card","history_field":"tool_card","snapshot_parts":1,"card_bytes":16384,"core_bytes":2048,"cards_per_run":4096,"snapshots_per_run":16384,"run_snapshot_bytes":33554432,"retained_card_bytes":8388608,"blocks_per_card":16,"rows_per_block":32,"diff_lines_per_block":128,"children_per_block":32,"children_depth":8,"string_bytes":1024,"title_bytes":256,"metadata_bytes":512,"preview_bytes":4096}
```

Request-name count and advertised-response count are separate. A rich client must accept exactly the negotiated semantics and a response list bounded to seventeen names, or eighteen with `client.tools`. Other capabilities need a separately agreed advertisement policy. Duplicate required names do not gain additional capacity; clients send unique names.

After successful rich initialization, the client validates all required capability names and the schema-1 descriptor before admitting turns or history display. Missing/mismatched descriptor or bounds fail closed; successful admission is not an effect-free refusal and cannot trigger same-child base fallback.

On a correlated `unsupported_capability` response to the first, unadmitted initialization only, a client may retry once with a fresh request ID and a base-only required list it already supports. Do not retry on timeout, disconnect, startup failure, `already_initialized` or an uncertain response. All request IDs, including rejected ones, count toward the existing 4096-ID lifetime. Fallback cannot replay `turn.start`, prompts, tools or accepted mutations. No events from the refused extension may precede fallback.

Protocol version 1 is retained only under these opt-in conditions. An unconditional seventeenth advertised capability is not backward compatible. Changing base activity meanings, required history fields or existing bounds requires a protocol-version agreement rather than this extension.

## Card schema

Each card is a complete replacement snapshot. All fields below are required, including nullable ones; no arbitrary object fields carry display data. Integers are nonnegative JSON integers bounded to the JavaScript safe-integer range unless a smaller bound is specified. Strings are sanitized UTF-8, never terminal markup. IDs use the existing 1–64-byte ASCII ID grammar.

| Field | Type and meaning |
| --- | --- |
| `schema_version` | Integer `1`. |
| `card_id` | Same opaque ID as `activity_id`; not a second card identity. |
| `activity_id` | ID of the enhanced base activity. Live: child/session/run scope. Saved: session/history-revision scope, equal to entry `message_id`. |
| `parent_activity_id` | ID in the same scope or null; never a raw internal task ID. |
| `parent_state` | `root`, `available`, `missing`, `omitted`. Root requires null parent; available requires a projected parent. Missing means source ancestry unavailable; omitted means known ancestry excluded by projection bounds/privacy. Missing/omitted may have null ID. |
| `task_id` | Opaque mapped delegated-task identity in the same scope, or null when absent/unavailable. No cross-session lookup meaning. |
| `tool_kind` | `read`, `search`, `write`, `edit`, `command`, `web`, `mcp`, `code_mode`, `subagent`, `other`. Unrecognized backend tool names use `other`, not a new wire enum. |
| `state` | `running` or `finished`. This is display lifecycle, not transport/persistence state. |
| `outcome` | Null while running; otherwise `success`, `failure`, `cancelled`, `interrupted` or `unknown`. |
| `changes` | `not_applicable`, `none`, `applied`, `partial`, `unknown`. Not-applicable means a non-mutating operation by contract; none requires evidence no changes occurred. Applied means observed changes completed; partial means observed changes with incomplete application. Missing evidence is unknown. Independent of outcome; cancellation never implies rollback. |
| `revision` | Integer starting at 0, strictly increasing for each emitted replacement of this card in its scope. Immutable once emitted. |
| `origin` | `live` or `saved`; saved means projection from durable records, not a live-card durability claim. |
| `detail` | Exactly `{ "availability": "complete"\|"truncated"\|"unavailable"\|"none", "reason": reason\|null }`, as defined below. |
| `title` | Safe selected title, at most 256 UTF-8 bytes; fixed safe tool-kind label when source title cannot be shown. Never parse it to recover outcome. |
| `metadata` | Fixed object described below. |
| `blocks` | Ordered array of typed display blocks, at most 16. |
| `omissions` | Array of explicit omission objects, at most one per subject listed below. Empty only when nothing was omitted. |

`success` requires authoritative tool completion, not an assistant claim, streamed preview, shell output or run completion. `failure` requires an observed failed result. `cancelled` requires observed tool cancellation; merely requesting cancellation does not establish it. `interrupted` means the backend has evidence execution ended without a tool terminal record (for example, durable interrupted-turn evidence). `unknown` means outcome evidence is insufficient. On lost transport, the client marks unfinished live cards locally unknown; it must not invent a backend revision or claim cancellation. Partial/applied changes survive failure or cancellation and remain explicit. Missing change evidence uses `unknown`, not `none`.

`detail.availability=complete` means all safely displayable detail from this source fits, not unrestricted access to raw results. `truncated` means selected detail was shortened or excluded; `unavailable` means no safe detail can be provided; `none` means this activity has no display detail by design. Complete/none require null reason; truncated/unavailable require a reason. Reasons: `size_limit`, `budget_limit`, `redacted`, `not_recorded`, `unsupported_source`, `missing_source`. No free-form failure explanation. Summaries cannot substitute for these typed facts.

`metadata` is exactly:

```json
{"tool_name":null,"resource_path":null,"working_directory":null,"public_protection":{"decision":"not_reported","summary":null}}
```

First three values are nullable selected display strings, each at most 512 UTF-8 bytes. `tool_name` is a sanitized name, never executable routing information. `public_protection.decision` is `not_reported`, `allowed`, `blocked`, `approval_required` or `denied`; it records the latest authoritative public decision, not a private assessment or complete audit trail. An observed approval may advance approval-required to allowed/denied; detail-only updates cannot erase a known decision to not-reported. Nullable summary at most 256 UTF-8 bytes contains only the backend's public protection summary. Preserve known blocking/denial/approval decisions even when summary text is unavailable, with a metadata omission for excluded summary text. No private scores, assessments, evidence, policy prompts or reasoning. This object cannot authorize execution; only existing `approval.requested`/`approval.answer` exchanges do that.

An omission object is exactly `{ "subject": subject, "reason": reason, "omitted_count": integer|null }`. Subjects: `title`, `metadata`, `blocks`, `rows`, `text`, `diff_lines`, `children`, `progress`, `ancestry`, `updates`. Reasons use the detail reason enum. Count is the actual omitted item count when known, otherwise null; text counts Unicode scalar values, not bytes. Never invent totals. Merge same-subject omissions without double counting; any unknown count makes the merged count null. When multiple reasons apply, use the first in this priority order: redacted, missing-source, not-recorded, unsupported-source, budget-limit, size-limit (wire values retain underscores). Apply the same order to `detail.reason`. These indicators describe detail loss, not loss of outcome or public safety facts.

## Typed display blocks

Each block has a `type` discriminator and exactly the fields specified here. No HTML, styles, arbitrary JSON, executable actions, embedded images or recursively nested blocks. All nullable fields must be present. General display strings are at most 1024 UTF-8 bytes unless an explicit field bound overrides it; byte and serialized-card limits also apply to aggregate content. Line numbers are positive integers when known.

| `type` | Remaining fields |
| --- | --- |
| `text` | `text:string` (at most 4096 UTF-8 bytes); `format:"plain"\|"code"`; `language:string\|null` (at most 64 bytes, display hint only). |
| `resources` | `rows:[resource]`, at most 32. Resource exactly `kind:"file"\|"url"\|"resource"`, `label:string`, `location:string\|null`, `availability:"available"\|"unavailable"\|"unknown"`. Availability concerns source evidence, not permission to open/fetch. |
| `results` | `rows:[result]`, at most 32. Result exactly `label:string`, `value:string\|null`, `outcome:"success"\|"failure"\|"partial"\|"unknown"`. Row outcomes do not replace card outcome. |
| `command` | `command:string\|null` (at most 4096 UTF-8 bytes), `working_directory:string\|null` (at most 512 bytes), `exit_code:integer\|null` (signed 32-bit), `signal:string\|null` (at most 64 bytes). Unknown/redacted command is null with a text omission; no raw environment, stdin or secret arguments. A known exit code is a result fact, not the sole overall success rule. |
| `diff` | `old_path:string\|null`, `new_path:string\|null` (each at most 512 bytes), `application:"proposed"\|"applied"\|"partial"\|"unknown"`, `lines:[diff_line]` (at most 128). Paths/lines describe a selected preview, not a patch to apply. |
| `children` | `children:[child]` (at most 32), `total:integer\|null`. Child exactly `activity_id:string`, `task_id:string\|null`, `title:string` (at most 256 bytes), `state:"running"\|"finished"`, `outcome:card_outcome\|null`, `changes:card_changes`. State/outcome rules match card rules. Children reference separately projected activities, never embed cards. Total is known source count or null. |
| `progress` | `label:string`, `completed:integer\|null`, `total:integer\|null`, `unit:"items"\|"bytes"\|"steps"\|"unknown"`. Null means not known. When both counts known, completed ≤ total. Counts do not establish success. |

Diff line exactly `{ "kind": "file_header"|"hunk_header"|"context"|"added"|"removed"|"no_newline", "text":string, "old_line":integer|null, "new_line":integer|null }`. Text excludes an added/removed/context prefix; kind is authoritative. Context uses both known line numbers, added uses only new, removed only old. Header and no-newline rows use null numbers. Unknown source numbers stay null. Hunk/file headers are data, not something clients parse to infer line kinds. Truncated content uses omissions, never fake context or invented lines. `application=applied` requires evidence of the actual applied operation; proposed input or rereading today's file cannot establish historical application.

### Resource-operation facts

Read, Grep, AST Grep, Find and List Files prepend supported Results/Resources with [exact labels, nullable values and count meanings](resource-operation-card-facts.md). Consumers preserve zero versus unknown and distinguish scan completeness from display completeness. Shared bounded selection and display-only persistence supply live/Mission Control/exact saved facts; no raw metadata endpoint or tool replay.

### Web, Image and Control facts

Web, View Image and Magi Control prepend Results rows with [fixed labels, units and null semantics](web-image-control-card-facts.md). Control exposes only public operation, recorded usage and request status; private compaction context and raw output never enter display. Image text is a recorded report, not a new visual observation.

### Code Mode and subagent facts

Code Mode and subagents expose [fixed execution/task labels, units, null semantics and persistence](execution-task-card-facts.md). Batch totals come from host-owned telemetry, not visible child rows. Warning/path/activity omissions reflect current selection; safety facts survive detail reduction. Missing legacy telemetry remains unavailable.

### Public Bash notices

Bash cards prepend one supported `results` block with four rows in this exact order:

| `label` | Selected `value` |
| --- | --- |
| `Humanize protection` | Allowlisted public `humanize_protection.decision` label, or null. |
| `Bash protection` | Allowlisted public `bash_protection.decision` label, or null. |
| `File tracking` | Fixed label for an allowlisted `bash_file_tracking.status`, or null. |
| `Inferred file activity` | Bounded aggregate of selected source entries, or null. |

Each row has `outcome:"unknown"`: these notices do not establish execution success or filesystem effects. Identify rows by exact labels, not their display prose. They are separate from intent, command, output counts and failure explanation. No additional fields, capability, block type or schema version is required.

Public protection labels are `allowed`, `allowed automatically`, `allowed by user`, `allowed by API failure policy`, `denied by API failure policy`, `denied by user`, `denied; approval unavailable`, `denied; needs humanizing`, `approval required`, `no added prose`, and `no comments or documentation`. Unknown decision strings are unavailable, never copied. The combined `metadata.public_protection` decision selects the strongest known protection across both rows; an independent quarantine remains blocking. An update to one protection cannot erase the other protection's retained decision. Notices cannot answer an approval or authorize execution.

| Tracking source status | Fixed public value |
| --- | --- |
| `completed` | `completed` |
| `command_limit` | `skipped (command limit)` |
| `canceled` | `skipped (canceled)` |
| `busy` | `skipped (worker busy)` |
| `unavailable` | `skipped (worker unavailable)` |
| `timed_out` | `timed out` |
| `error` | `assessment failed` |
| `redacted` | `skipped (sensitive command)` |
| `unsupported` | `skipped (unsupported command)` |
| `no_candidates` | `skipped (no literal paths)` |
| `command_failed` | `skipped (command did not succeed)` |

Unknown tracking status suppresses new inferred activity selection and records `rows:unsupported_source`. A missing report is null, not completed or zero. Mission Control uses the same selected labels/values and displays null values as `not reported`.

Aggregate selection examines only the first 32 source entries. Valid nonempty paths deduplicate internally; operations `read`, `write`, `edit`, and `read_write` contribute R/W/E bits per unique path. Paths and source rows never enter these notices. The aggregate states the supported selected file and R/W/E counts, selected first-entry count, source entry count, unsupported-entry count, and `not verified effects`. A shortened or partly unsupported selection explicitly says `full totals unknown`; zero supported files then means zero within supported selection, not zero full activity. An explicitly empty array has a complete zero selection; an absent array remains null even when tracking completed.

Missing notice values record a `rows` omission with `missing_source` live or `not_recorded` saved. Excluded entries record `rows:size_limit`; malformed/unsupported selected entries record `rows:unsupported_source`. Counts are known excluded entries or missing notice values, merged under ordinary same-subject priority/unknown-count rules. Every string and the whole block still consume existing escaped card/run/history budgets. The notice block occupies one of 16 block slots; excluding other blocks reports `blocks:size_limit`. Core reduction may omit notices but preserves authoritative combined decision, outcome and effects.

Late selected metadata merges only supplied notice values, retaining prior selected values and their selection-loss evidence. It does not replace command/output blocks, restart execution or change terminal outcome/effects. Saved cards reconstruct notices only from recorded call/result detail through the same selector. Ephemeral-only reports are not newly persisted and remain `not_recorded` after exact reopen; no current-file scan or history rewrite supplies missing facts.

### Bash execution notices

Terminal Bash result detail adds a separate supported `results` block, after intent and command, with these fixed labels in order. Protection/tracking notices remain separate and first. Consumers identify labels, not terminal prose; all row outcomes remain `unknown` and never override authoritative card outcome, exit code or effects.

| `label` | Selected `value` |
| --- | --- |
| `Command timeout` | Reported `timed_out`: `not timed out`, `timed out after Ns` for an available recorded timeout argument, or `timed out; timeout duration unavailable`. Missing flag → null. Argument is configured timeout, not measured elapsed duration. |
| `Output truncation` | Independent stdout/stderr flags: each `truncated`, `not truncated` or `truncation unavailable`. Both flags missing → null. |
| `Cleanup warning` | First nonempty sanitized line of supplied cleanup warning, or null. No inferred cleanup completion or rollback. |
| `Failure explanation` | First nonempty sanitized stderr line for a failed result; non-stream errors may use selected normalized error output when both streams are absent. Identical timeout/truncation/cleanup text is not repeated. Missing explanation → null. |
| `Output lines` | Sum of `.lines()` in available **retained sanitized** stdout/stderr, with available stream names and `full output total unknown`. No available streams → null. Available empty streams → reported zero, not unavailable. |

Count scope deliberately stays retained/sanitized even for success: normalization can redact/remove lines and source output can be bounded. No verified pre-normalization full-stream count is selected. A single available stream does not imply other stream empty; its unavailable counterpart is explicit. No client may relabel this count as complete raw process output. Exit 0 with authoritative failed truncation/cleanup remains failure.

Timeout, truncation and cleanup selection is independent, including simultaneous facts. Failure explanation cannot suppress safety notices. Strings use existing 1024-byte limits; multiline explanations retain only first nonempty line with `text:size_limit` for omitted lines. Redacted selected text retains `text:redacted`. Null rows use ordinary missing-source/not-recorded omissions. Running/absent-result detail does not invent execution notices. Saved cards use the same normalized recorded call/result source; absent timeout arguments stay unavailable.

Under card/run/history detail pressure, preserve command exit code in a command block with nullable omitted preview, and populated timeout/truncation/cleanup rows. Core cleanup text may shorten to 128 UTF-8 bytes with explicit omission. Optional failure/count/intent/protection-summary text can be omitted; authoritative outcome, effects and combined protection decision remain. If required escaped safety core exceeds reserved 2048 bytes, normal content-limit failure applies, not silent safety loss. No new wire field/type, capability, persistence record or provider-facing presentation is added.

Hierarchy must be acyclic, at most eight parent edges deep, with references in the same identity scope. Missing parents render as explicitly unresolved, never attach to a guessed task. Excluded child identities receive a children omission; unknown totals remain null. Each child is enhanced at its existing activity position, not duplicated as another transcript card under every parent. Parent child rows are navigation/display summaries, not additional authoritative copies: never overwrite a separately received child card with a parent row. Pending saved references may resolve on later pages of the same revision; do not infer missing ancestry merely from page order.

## Live event and ordering

Negotiated event `tool.card` uses the existing event envelope and exactly these required data fields: `session_id`, `run_id`, `activity_id`, `part=0`, `last_part=true`, `card`. Data activity ID must equal both card IDs; origin must be live. Schema 1 deliberately has **one atomic part per snapshot**, not arbitrarily chunked JSON. Clients replace the snapshot, never concatenate cards.

Ordering requirements:

- `turn.start` admission and `input.persisted` precede any card. Flush/reconcile the preceding assistant segment at the tool boundary before the base `activity(state=started)`; emit initial card after that base activity. Cards enhance that position, not the latest assistant message.
- Keep base `activity` lifecycle/name/summary unchanged for non-rich consumers. Multiple snapshots with the same ID update one activity. Do not create a duplicate card for the base summary.
- Interim progress/preview updates may coalesce **before** assigning a revision or enqueueing. Once emitted, a revision is immutable; later snapshots replace all its detail, including removed blocks. Revision gaps are permitted, backwards revisions are invalid, equal revisions must have identical content. A gap does not require append reconstruction.
- An observed tool finish emits the unchanged base completed/failed activity first, then its final rich snapshot. Rich cancelled/partial/unknown distinctions refine the coarse base failure state; clients must not infer them from base summaries. Existing base completion is not rewritten into a different protocol enum.
- No running snapshot may overwrite a finished card. Retain authoritative outcome, changes and public protection decisions across later detail-only updates. Omissions always describe the current replacement's content; they may clear only when that content is supplied, not merely after a tool finishes. A finished card may receive a higher revision with additional safe detail before run terminal, but cannot reverse its outcome without a new protocol agreement.
- Before `run.finished`, resolve every admitted unfinished card with observed cancelled/interrupted/unknown facts and deliver its final snapshot, after required assistant reconciliation. Do not fabricate a base tool finish without evidence. A successful run cannot turn an unknown tool into a success.
- `run.finished` remains once, after worker/cleanup accounting; no card event follows it. Run outcome/persistence and tool outcomes are independent. A complete displayed card proves neither durable append nor successful run completion.

Every enqueued card snapshot is critical, using existing bounded delivery; no best-effort authoritative card drops. On output failure, normal `output_unavailable`, cancellation and process failure policy applies. Final delivery after disconnect is not guaranteed; clients retain explicit uncertain state and never replay tools to fill it.

## Bounds and oversized detail

All descriptor bounds are maxima, not allocations or permission to read extra source content. Bound source work before projection; reuse existing guarded file/tool-output readers and cancellation. Never read unrestricted raw output to fill a small card. Projection, sanitization and persistence stay on existing workers, not the protocol/UI thread.

Measure compact UTF-8 JSON **after sanitization and JSON escaping**, with actual keys, nulls, numbers and arrays. `card_bytes=16384` applies to serialized card object. `core_bytes=2048` applies to reduced object with fixed fallback title, nullable display metadata and required omissions; populated Bash execution safety rows and exit code remain in supported blocks. Known outcome/change/protection/identity facts remain. Raw string maxima alone do not prove fit: quotes, backslashes and escaped controls can expand JSON. Reduce strings only at Unicode boundaries, and report each reduction. If safe metadata/title cannot fit core, use nullable metadata/fixed title with explicit omissions; do not shorten IDs or safety enum values.

Additional rules:

- Full live envelope including LF must remain ≤65,536 bytes. A history **result**, including session/revision/cursor/page fields and all entries/cards, must remain ≤32,768 serialized bytes; its enclosing response including LF must also fit output frame. Size the first activity text part together with its atomic card; smaller summary chunks continue in subsequent numbered parts without losing text. Emit fewer history entries rather than exceeding bytes.
- At most 4096 card identities/run, sharing the existing activity identity budget, not an additional 4096 tools. Backend retained serialized card state totals ≤8 MiB/run. Retain required cores; optional safe detail may be released after delivery and explicitly unavailable in a later replacement. This bounds backend retention, not a promise of a full revision cache. Bound mapping/collection counts as well as content bytes; no retained raw details or unbounded revision log. Clients bound their own caches within identity/card/cumulative run limits.
- At most 16,384 emitted snapshots/run and 32 MiB cumulative serialized card bytes/run, counting every revision. At run admission reserve 4096 initial cores and 4096 terminal cores, each ≤2048 bytes, plus their 8192 snapshot slots. This 16 MiB reserve cannot be spent on previews; it covers every identity admitted under the existing activity bound. Initial/final detail above core size and all interim updates use only remaining capacity. Release unused reservations after run terminal, not to speculative updates during the run.
- Intermediate limits coalesce progress and omit preview/update detail with `budget_limit`, without suppressing changed public safety facts or authoritative outcome. If required facts cannot be represented under bounds, fail visibly with existing `content_limit`/diagnostic and normal terminal accounting, never silently declare cards complete. The client marks any undelivered final cards unknown.
- No card chunking or detail-fetch endpoint in schema 1. Oversized selected blocks first shorten/exclude optional preview content with explicit counts/reasons. If no useful detail fits, send the core with `detail.availability=unavailable` and `reason=size_limit` or `budget_limit`. Preserve identity, outcome, change state and public protection decision. Omitted content is not a hidden invitation to fetch raw results.
- History retains the existing 8 MiB **entire serialized entry projection** and 10,000-part budgets, including rich cards, not a separate allowance. Reduce optional card detail deterministically before freezing the revision; if required history facts still exceed bounds, return `history_limit`/`content_limit`, never a falsely complete page. Stored reads remain 64 MiB/100,000 lines.
- Nesting remains 32 containers; hierarchy uses references, not nested objects. Existing 32-event channel, 32-output-frame queue, 2000 ms critical delivery/output/cleanup bounds, cooperative cancellation and 4096-request lifetime are unchanged. Cards have no additional unbounded queue or deadline reset.

## Negotiated saved history

Without negotiation, `history.page` entries are exactly the base shape and summary policy. With negotiation, an `activity` entry adds `tool_card:card|null` **only on `part=0`**. All base fields remain required, with the same kinds, text-part concatenation, `partial` and `last_part` semantics. No new entry kind. Non-activity entries and later text parts do not carry this field. A null card means the source activity cannot be safely mapped; retain its base summary and mark rich detail unavailable locally, without inventing typed outcome facts.

For a nonnull saved card, both card IDs equal the entry message ID, origin is saved, state is finished, revision is 0 within this immutable history revision, and live-only instance/run IDs are not required or persisted. Parent/task IDs are remapped into that snapshot scope. Full cards use the same schema and safe projection as live output, not an independent frontend interpretation. A saved tool terminal result establishes outcome; absent evidence uses interrupted or unknown under the rules above. Legacy missing previews use `not_recorded`; inaccessible safe source uses `missing_source`; unsupported source uses `unsupported_source`; redacted detail uses `redacted`. Missing detail never means successful execution or no applied changes.

Text parts retain their existing immutable contiguous numbering. Schema 1's card is atomic, attached once to the first text part, even if summary text spans subsequent pages. Its `part=0` is not a second card stream. Repeated page responses for the same revision/entry must have identical card content. Budget selection/redaction occurs before freezing the snapshot; do not lazily add blocks on later pages. Mutation, appends, compaction or display-record changes invalidate the whole revision stickily; reopen explicitly. Cursor rules, ordering, stale errors and history worker ownership stay unchanged.

Saved IDs are not promised to equal live IDs or IDs after reopening/compaction. After `run.finished`, replace the live transcript projection with pages of the authoritative returned history revision (or explicitly reopen if null); do not append saved cards to live copies or match by human title. Keep drafts separate. `complete=true` means the existing history completeness contract, not that every card has detailed previews. Per-card unavailable/truncated fields remain visible even on a complete page.

Applied-diff persistence is display-only. Required execution records retain outcome, committed-file and mutation facts; separate preview records retain bounded sanitized applied evidence. Include their bytes in existing storage/append/read budgets. No retroactive reconstruction from current files, attempted arguments or arbitrary raw output. Display append failure preserves execution truth, surfaces persistence degradation and omits the unpersisted preview from final live/saved detail. Never imply rollback or resend accepted tools.

### Display-only session records

The owned session log stores `tool_applied_preview` after its required `tool_result` or `code_mode_tool_result`. A result with an applied-preview source carries a unique sibling `display_id`; raw `ToolResult.display` remains excluded from serialization and deserialization. Result status and structured execution evidence, not the preview, establish success, cancellation and partial changes.

Preview payload:

```json
{"display_id":"unique-result-display-id","preview":{"schema_version":1,"diff":"-before\n+after\n","truncated":false}}
```

Code Mode records also carry the same `parent_call_id` and `sequence` as their execution result. Hydration joins unique display IDs within the recorded session, requires matching scope and later record order, and refuses duplicate result IDs, duplicate previews, malformed/newer preview schemas or mismatched parents/sequences. Such detail stays unavailable; results retain independent execution facts. Unknown session event kinds remain readable without card projection; malformed required call/result data rejects exact open before replacing attachment.

Schema 1 preview bounds: 8192 serialized UTF-8 bytes including JSON escaping, 128 lines, 1024 UTF-8 bytes per line. Redact multiline secrets and sanitize controls before selection. Never split a UTF-8 scalar; mark truncation explicitly. Live final and Mission Control/local-agent history hydration consume this same selected source. Ordinary preview appends consume an 8 MiB/run allowance under the existing 64 MiB session ceiling; nested Code Mode uses its existing owned history budget. JSONL-durable metadata failure does not erase a stored preview. Failed or uncertain preview append does not establish saved availability; preserve execution and require explicit reconciliation rather than replay.

Partial Hash Edit retains previews from completed operations, including confirmed effects followed by durability/reporting errors. An incomplete move cannot label its attempted patch as applied; missing portion remains omitted. Deletes use recorded pre-edit content against an empty after-state. Reopen never reads current file contents to reconstruct those changes.

Saved attachment uses turn, top-level/Code Mode scope, recorded parent and real call ID. Duplicate identities within that scope, including reuse after completion, produce null rich attachments. Top-level and nested IDs do not collide. A Code Mode parent must be an unambiguous earlier recorded Code Mode call in the same turn; other ancestry remains unavailable. Preserve assistant boundaries, snapshot parts, public decisions and authoritative outcomes.

Display events stay local-only and never enter provider tool-result replay/context. Existing tolerant unknown-event handling preserves readable histories without schema migration or personal-history rewriting. Reopen performs no provider generation or opportunistic filesystem scan.

## Privacy and decoder policy

Allowlist only selected sanitized tool names/titles, commands without secrets, paths, resource/result rows, bounded output previews, semantic diff content, mapped task hierarchy/progress and public protection summaries. Run every string through shared redaction and control sanitization before sizing/storage/delivery. Redaction may produce null metadata or explicit preview omissions. Resource URLs/paths are inert display data; consumers must not automatically open, fetch, execute or apply them. No credential paths/files, tokens, environment dumps, headers, raw provider/tool responses, private protection assessments, raw reasoning or unrestricted metadata objects. Read-only display does not relax protections on obtaining source data.

Clients fail closed on unnegotiated `tool.card`, unknown schema versions, block types, enums, missing required fields, duplicate JSON fields, mismatched identities, invalid part/revision semantics or exceeded bounds. Do not render unsupported safety/outcome semantics as generic text. New enum/block/schema semantics require a new negotiated capability agreement; never silently send schema 2 to a schema-1 client.

Bounded future unknown object fields may be ignored only as inert optional display annotations: still count their bytes/depth toward existing bounds, never execute them, and never use them to override required facts. Producers may add such fields only when ignoring them preserves every safety/outcome/identity meaning. This does not allow arbitrary metadata/result passthrough or unknown typed blocks. The frozen schema's documented fields remain required.

## Synthetic sanitized examples

These are invented schema examples, not process captures or native/provider acceptance. First initialization requests rich semantics; an unsupported backend returns the correlated effect-free error, then the client retries base-only with a different ID:

```json
{"protocol":"magi-code.local-agent","version":1,"id":"init-rich","method":"initialize","params":{"required_capabilities":["status","shutdown","session.open","history.page","turn.start","turn.cancel","tool.cards"]}}
{"protocol":"magi-code.local-agent","version":1,"type":"response","instance_id":"child-1","seq":1,"id":"init-rich","error":{"code":"unsupported_capability"}}
{"protocol":"magi-code.local-agent","version":1,"id":"init-base","method":"initialize","params":{"required_capabilities":["status","shutdown","session.open","history.page","turn.start","turn.cancel"]}}
```

On a different, rich-admitted connection, after base activity `a1` started and then failed, this final snapshot records observed cancellation **with partial applied changes**, not rollback:

```json
{"protocol":"magi-code.local-agent","version":1,"type":"event","instance_id":"child-2","seq":9,"event":"tool.card","data":{"session_id":"example-session","run_id":"run-1","activity_id":"a1","part":0,"last_part":true,"card":{"schema_version":1,"card_id":"a1","activity_id":"a1","parent_activity_id":null,"parent_state":"root","task_id":null,"tool_kind":"edit","state":"finished","outcome":"cancelled","changes":"partial","revision":2,"origin":"live","detail":{"availability":"truncated","reason":"size_limit"},"title":"Edit example.rs","metadata":{"tool_name":"hash_edit","resource_path":"example.rs","working_directory":null,"public_protection":{"decision":"allowed","summary":null}},"blocks":[{"type":"diff","old_path":"example.rs","new_path":"example.rs","application":"partial","lines":[{"kind":"removed","text":"let count = 1;","old_line":4,"new_line":null},{"kind":"added","text":"let count = 2;","old_line":null,"new_line":4}]}],"omissions":[{"subject":"diff_lines","reason":"size_limit","omitted_count":null}]}}}
```

A saved activity entry from a legacy interrupted turn, with no recorded preview (history result/envelope omitted):

```json
{"message_id":"saved-a1","kind":"activity","text":"Command interrupted","partial":true,"part":0,"last_part":true,"tool_card":{"schema_version":1,"card_id":"saved-a1","activity_id":"saved-a1","parent_activity_id":null,"parent_state":"missing","task_id":null,"tool_kind":"command","state":"finished","outcome":"interrupted","changes":"unknown","revision":0,"origin":"saved","detail":{"availability":"unavailable","reason":"not_recorded"},"title":"Command","metadata":{"tool_name":null,"resource_path":null,"working_directory":null,"public_protection":{"decision":"not_reported","summary":null}},"blocks":[],"omissions":[{"subject":"blocks","reason":"not_recorded","omitted_count":null},{"subject":"ancestry","reason":"not_recorded","omitted_count":null}]}}
```

## Compatibility matrix

| Client / backend | Required behavior |
| --- | --- |
| Published non-rich client / implementing rich backend | Original sixteen advertised names, original history shape, no rich events/descriptors. No client update required for base initialization. |
| Rich client / current or older backend without extension | Effect-free `unsupported_capability`; optional one fresh-ID base-only retry before startup. No rich decoding or replay. |
| Rich client / implementing rich backend, no rich request | Base-only behavior, even though client knows schema. |
| Rich client / implementing rich backend, explicit request | Seventeen advertised names plus schema-1 descriptor; same base facts enhanced by live snapshots and saved field. |
| Any client / admitted startup failure or uncertain disconnect | No same-child initialization fallback or uncertain work replay; explicit recovery/restart. |
| Schema-1 client / unsupported schema, enum or block | Fail closed; implementation must not advertise/send those semantics under `tool.cards`. |