magi-code 0.80.1

Repository-aware CLI coding agent for terminal work
Documentation
# Application resources (Unix v2)

The Unix daemon negotiates one profile, `application_resources_v1`, for source-only session resources and application-executed callbacks. Both retired `application_callbacks_inline_v1` and `application_callbacks_session_v1` tokens reject initialization. The old inline `application:{tool,skill,...}` turn shape is rejected. Stdio wire v1 is retired; ordinary wire v2 remains unchanged by profile negotiation. This guide is the normative shipped contract; [RFC-0003](../rfc/0003-session-client-resources.md) records the approved decisions and remaining delivery proof.

## Negotiate and claim

Request `application_resources_v1` in `initialize.payload.requested_capabilities`. The server echoes it in `negotiated_capabilities`; subsequent capabilities repeat the immutable selection. Check the complete response before mutation. `application_callbacks` advertises bounds, source kinds, registry and reconnect-evidence support. Selected operations include registry methods, executor/result methods and `turn.application_evidence`; selected events add `turn.application_tool_call` and `turn.application_tool_cancel`. No activity flags change.

Executor rotation uses one pending verifier per executor and a fixed 60-second expiry. See [executor authority](#executor-authority-and-execution) below.

All registry methods require the session ID and its current control token, including list. List has a null operation ID; mutations require fresh operation IDs. Executor authority alone cannot read or change a registry. Foreign controllers are rejected before registry access.

## Registry methods

| Method | Payload |
| --- | --- |
| `session.resources.list` | `{}` |
| `session.resources.register` | `{expected_revision:null_or_ID,resource:Resource}` |
| `session.resources.unregister` | `{resource_id:ID,expected_revision:ID}` |
| `session.resources.clear` | `{expected_registry_revision:ID}` |

Create requires a null expected revision. Replacement requires the current resource revision and the same kind/name; unrelated resource changes do not invalidate it. Replacement preserves the resource ID and allocates a fresh revision ID, even for identical bytes. Stale revisions and kind collisions return `conflict`. Register returns `{entry,registry_revision}`. Unregister returns `{resource_id,removed_revision,registry_revision}`; an absent ID returns `resource_not_found`. Recreation receives a new resource ID.

List returns `{registry_revision:ID,entries:[Descriptor]}`, sorted by kind/name, without bodies or paths. First list creates a bounded empty registry with an unpredictable revision. Every change allocates a fresh registry revision. Clear returns `{registry_revision,removed_count}`; clearing an already-empty registry leaves its revision unchanged. Empty registries remain while claimed and are removed when unclaimed.

Registration accepts only `{kind:"tool"|"skill",name,description,source}`. Legacy contract/content registration shapes are rejected.

Tools use the schema and timeout rules below. Skills are plain captured instruction text, not executable packages. Names share one namespace and reject built-in/MCP/discovered-skill/disabled-tool collisions. Registration and turn preparation check the loaded catalog. Skills are combined in name order, separated by newlines.

### Captured sources

Source is strictly `{kind:"inline",content:Text}` or `{kind:"workspace_file",path:RelativePath}`. Tool content is strict JSON with `input_schema`, `output_schema`, and optional `timeout_ms`; duplicate keys, unknown fields and invalid schema/timeout values reject. Skill content is plain UTF-8 instructions. No includes or execution are supported.

Workspace files are captured once by one daemon-wide worker, after controller authorization and expected-resource-revision/capacity checks. Paths must be workspace-relative, at most 1,024 bytes, without empty or dot components. Every opened component uses directory-relative no-follow semantics. Symlinks, multiple hard links, nonregular files, invalid UTF-8, NULs, credential-like paths and backend state are rejected. Existing checkpoint credential/session/state exclusions are reused. Reads are bounded to source limit plus one; device/inode/link count/size/mtime/ctime are compared before and after reading and against the final path. Detected changes reject; this is not an atomic-snapshot guarantee against hostile in-place writers.

One capture slot reserves 128 KiB inside the daemon's 8 MiB resource budget. Other registrations return `configuration_busy` while it is occupied; list, unregister, clear, Stop and unrelated turns remain available. Publication rechecks connection, control generation, operation state, deadline, resource revision, catalog and budgets. Disconnect, takeover or an expired request discards staging. OS filesystem waits are not forcibly interrupted; the slot remains charged until the worker returns. No source file is opened during execution.

Source registration returns `{registry_revision,entry}`. Its descriptor contains `resource_id`, `revision_id`, `kind`, `name`, `description`, `content_sha256`, `revision_sha256`, `captured_bytes`, and `registered_by:{instance_id,connection_id,operation_id,grant_generation}`. IDs are backend-issued: replacement preserves the resource ID and allocates a fresh revision ID; removal/recreation allocates a new resource ID. Attribution is immutable per revision and identifies a connection action, not a verified person. No grant token, executor proof or source path enters the descriptor.

Content SHA-256 covers exact captured bytes. Revision SHA-256 uses UTF-8 domain `magi-application-resource-v1`, followed by kind, name, description and captured bytes, each prefixed by its unsigned 64-bit big-endian byte length. Metadata-only replacement changes the revision hash but not the content hash. Exact bytes remain memory-only and count toward retained snapshot storage. Source bodies must not contain credentials: instructions and contracts reach the provider, and derived conversation values may be durable.


Nothing is installed or restored after daemon restart. Unclaimed idle registries expire after 15 minutes. A current controller, turn preparation, or accepted work through worker cleanup prevents expiry; the idle timer starts only after these protections end. Reclaim resets the timer. At the deadline, expiry wins over a new claim. Expiry removes future eligibility without extending accepted-outcome retention. Registry tables hold no session writer lease and do not prevent daemon idle shutdown. Operation lookup follows ordinary v2 mutation reconciliation; it does not retain resource bodies.

## Accept the complete snapshot

Send `turn.start` with the ordinary prompt and control token, plus:

```json
{
  "application": {
    "expected_registry_revision": "LISTED_REVISION",
    "executor_id": "CONFIRMED_EXECUTOR",
    "executor_generation": 1,
    "tool_authorizations": [{"resource_id":"RESOURCE_ID","revision_id":"REVISION_ID","revision_sha256":"REVISION_HASH"}]
  }
}
```

Every selection requires `application.tool_authorizations:[{resource_id,revision_id,revision_sha256}]`, exactly matching its complete tool set without duplicates. Missing, extra or stale entries reject. Skill-only and empty snapshots require `[]`. The initiating controller must hold the executor's confirmed attachment and supply its current generation; skill-only turns require both executor members to be present and null. Omitting application resources while a nonempty registry exists rejects with `unsupported_capability`, including on ordinary v2 connections. An empty registry permits an ordinary turn without `application`.

The coordinator captures the complete snapshot before asynchronous settings preparation. It rechecks controller and executor authority before worker admission. Changes during preparation or execution affect later requests, never the captured turn. Failed preparation drops its snapshot. Unregister and clear cannot remove an accepted tool's schema or instructions; these remain retained through worker cleanup.

The acceptance response and durable user record contain `application_manifest:{profile,registry_revision,resources,tools}`. `resources` contains descriptors; `tools` contains callback manifests. Tool manifests, dispatch events, result submissions and reconnect call evidence carry the exact accepted `resource_id`, `revision_id`, and source `revision_sha256`. Results must echo all three; mismatches reject without settling the call. Bodies, paths and secrets are not persisted in manifests; descriptor attribution stays local and is excluded from provider history.

Source-based accepted manifests also contain `accepted_controller:{instance_id,connection_id,operation_id,grant_generation}` and `accepted_executor:{executor_id,executor_generation}` (null for skill-only turns). Preparation does not assign these fields: the coordinator stamps them after admission authority checks, before worker dispatch. They describe the initiating turn operation, not registration or the current controller. Acceptance, durable user metadata and reconnect evidence use identical values. Resume, executor rotation and control takeover never rewrite them; grant tokens and executor secrets are excluded.

Selected claim snapshots include `application_manifest` in each non-null `turn` or `terminal`. Selected live terminal payloads and accepted/terminal `operation.lookup.result` include the same immutable manifest. Ordinary v2 omits these fields, even after takeover of a selected turn. A selected turn without `application` reports null; explicitly selecting an empty registry reports its manifest with empty resources/tools and null executor. An absent turn or expired terminal remains null, not an empty manifest.

Before worker admission, the encoded `{application_manifest:...}` field must fit 18,000 bytes or the turn rejects with `limit_exceeded`. This size is subtracted from the existing 20,000-byte encoded assistant-text budget, leaving the existing snapshot envelope reserve intact. Escaping counts toward both limits. Workers reject an overflowing delta before accepting or persisting it; accepted text is never truncated. This keeps assistant-bearing claim snapshots within 24 KiB and payloads/records within 32/64 KiB.

Execution uses the existing main-turn dispatcher. Tools are not inherited by subagents. Application instructions, schemas, arguments, and results reach the provider; keep credentials out of them. Contracts and call bindings persist before dispatch, and results persist before provider continuation. A registry change never transfers executor authority.

## Reconnect evidence

After negotiating this profile and claiming control, call `turn.application_evidence` with the session ID, current control token, null operation ID, and `{turn_id:ID}`. Executor attachment or result authority alone does not authorize reads. Ordinary v2 cannot use this method.

The response is `{turn_id,application_manifest,calls}`. The manifest is the accepted descriptor/tool manifest, unaffected by unregister, replacement, or executor rotation. Calls contain `call_id`, `provider_call_id`, `name`, accepted `executor_id`/`executor_generation`, `revision_sha256`, `status`, `external_effects`, `wait_kind`, and `remaining_ms`; source tools also contain `resource_id` and `revision_id`. Status is `waiting_for_executor`, `pending`, `succeeded`, `failed`, or `cancelled`. Waiting calls report `wait_kind:availability` or `execution` and a server-computed remaining monotonic duration; settled calls report null for both fields. Reads never renew a deadline. Effects are `not_dispatched` before dispatch, `uncertain` after dispatch without a reported outcome (including timeout/cancellation), or `reported` after an accepted success/error result. Reported failure does not imply rollback. Reads never redispatch work or expose arguments, result bodies, schemas, instructions, source paths, or resume secrets.

Call evidence intentionally has its own response budget rather than sharing assistant-bearing claim/terminal envelopes. Only the immutable manifest is shared. Evidence reserves at most 30 KiB encoded payload per turn (including worst-case status and turn identity), leaving envelope space within the 64 KiB record limit. At most 256 turns and 32 calls per turn are retained; byte limits may reject earlier. Turn capacity is checked before worker admission and call capacity before callback acceptance/dispatch. Exhausted call evidence capacity fails the tool without dispatch.

Evidence remains memory-only while its turn operation is retained: active work does not expire; settled evidence expires with the existing 600-second outcome, not the registry's 900-second idle timer. Reads do not extend retention. Unknown, expired, or foreign-session turn IDs return `unknown_turn` after controller authorization. Retained descriptors do not keep resource bodies or writer leases alive. Daemon restart loses this evidence.

## Bounds

The capability response advertises:

- 16 tools and 16 skills per session;
- 32 KiB per complete registry storage accounting, including normalized resources, exact source bytes and descriptors (so individual source maxima do not guarantee admission);
- 16 KiB combined skill text, including reserved newline separators;
- 256 retained snapshots, 1,024 retained resource entries, and 8 MiB retained bytes (normalized snapshots plus combined instruction text) across the daemon;
- 256 registry sessions, including claimed empty registries;
- 900,000 ms unclaimed registry idle timeout (`registry_idle_timeout_ms`);
- source maxima of 32 KiB per tool and 16 KiB per skill, one capture per daemon, and a 1,024-byte relative source path;
- name, description, schema, value, executor, callback-count, and timeout bounds below.

Old snapshots held by preparation or active workers remain charged after replacement or clear. A replacement reserves its whole snapshot in addition to the previous one; shared snapshot references count once. Clear removes future eligibility without releasing pinned capacity; its empty registry retains a revision until unclaimed. Snapshot copies used for validation are synchronous and bounded by the request/snapshot limits. Existing 32 KiB payload, 64 KiB record, and operation/connection limits still apply. Saturation rejects with `limit_exceeded`; it does not evict accepted resources.

Immutable contracts and combined instructions are shared across captures and callbacks, not copied per tool. Each snapshot reserves its combined instruction allocation once in addition to its normalized resource bytes. These limits are resource accounting, not a bound on total process memory.

## Delivery status

Backend implementation is complete. Linux execution and separate Companion consumer preflight and application delivery remain unverified. The negotiated token identifies this documented implementation, not completion of every RFC delivery requirement. Independent security review remains required; it is not a mathematical security guarantee.

Catalog checks retain startup-discovered names before skill enablement filtering, reserve configured disabled skill/tool names case-insensitively, and reserve all built-in names and the entire `mcp_` namespace.

## Verification

```sh
cargo test --quiet --lib application_profile_tests
cargo test --quiet --lib service::application::tests
cargo test --quiet --test unix_daemon_process application_callback_
```

The independent JSON process fixture runs the shipped daemon with a local HTTP provider. It captures two tool contracts and skill instructions from workspace files, then holds the first provider response after acceptance while the files are changed, resources unregistered by identity/revision, and source-only replacements registered. Both original callbacks still use and require their original revision tuples; provider continuation retains the original instructions and definitions. Later turns see replacements. Additional process tests reject symlinks, hard links, unsafe paths, oversize/invalid bytes and malformed tool JSON, and verify exact hashes, stable resource IDs, fresh revision IDs, attribution and path/body privacy. Coordinator tests deterministically discard staged capture after control loss, same-resource replacement or deadline expiry.

Fake-time tests check exact registry expiry, reclaim reset, old captured instructions after replacement, disconnected preparation and worker cleanup protection, writer-lease release, and independent 600-second terminal-outcome expiry.

Executed on macOS. Linux and a separate Companion application remain unverified.

## Executor authority and execution

Executor methods use null session/control and fresh operation IDs:

| Method | Payload |
| --- | --- |
| `executor.create` | `{}` |
| `executor.confirm`, `executor.resume` | `{executor_id,resume_secret}` |
| `executor.rotate`, `executor.release` | `{executor_id,executor_generation}` |
| `executor.rotate_confirm` | `{executor_id,executor_generation,resume_secret}` |

Creation returns `{executor_id,executor_generation:1,resume_secret}`; instance identity is in the envelope. Confirm on the issuing connection within 60 seconds. Resume confirms a live provisional executor or reattaches a confirmed one, increments generation, fences the old socket, and returns identity/generation plus bounded `pending_calls`, never arguments. One executor attaches per connection, at most 256 per daemon. Unpinned disconnected executors expire after 15 minutes. Release invalidates proof, never transfers calls.

Secrets are 32 random bytes encoded as base64url; the daemon retains only constant-time-compared SHA-256 verifiers. Only direct create/rotate responses disclose them. Lost create replies cannot recover secrets. For all six executor methods, `operation.lookup` retains only historical acknowledgment metadata: `status` (default `completed`), and `executor_id`, `executor_generation`, `pending_timeout_ms` when returned. Ordinary v2 connections can read these acknowledgments without executor authority; they do not establish current authority. Lookup never retains executor proofs, `pending_calls`, callback identities, effects, or timing evidence. Authorized direct confirm/resume responses still return bounded pending-call evidence. This restriction does not change selected-profile turn-manifest lookup. Keep proofs in private memory or an OS credential store.

### Rotate without losing reconnect authority

Rotation returns a fresh candidate secret with `status:rotation_pending,pending_timeout_ms:60000`, preserving the old proof. A new rotation replaces the candidate. Confirmation promotes it without changing generation. Resume with the candidate also promotes it and fences the old socket; resume with the old proof cancels the candidate. Expiry wins at the deadline. Lost rotate reply: retain the old proof and rotate again or resume. Lost confirmation reply: retain both proofs, try new-proof resume, then old-proof recovery if the candidate expired. Duplicate operations return `operation_already_known`; lookup is historical status, never a secret or current-authority guarantee. Fresh repeated confirmation returns `stale_connection`. Disconnect does not extend expiry; release/restart discards both proofs.

### Execute once

Callback events are reliable executor-only messages outside the session-control sequence; operation ID, grant generation and live sequence are null. Queue overflow disconnects, never authorizes redispatch. Call payload:

```text
{turn_id,call_id,provider_call_id,executor_id,executor_generation,
 resource_id,revision_id,revision_sha256,name,arguments,timeout_ms}
```

Enforce application permissions before acting. Retain the original result and call ID until reconciliation; never execute again to recover a lost response. Submit `turn.application_tool_result` with the pinned session, null control, fresh operation ID and:

```text
{turn_id,call_id,executor_id,executor_generation,resource_id,revision_id,
 revision_sha256,outcome:{status:"success",value:JSON}}
```

Failure outcome is `{status:"error",code:"execution_failed"|"permission_denied",message:Text}`. Acknowledgment `{call_id,status:"accepted"}` means admitted for persistence, not already durable. Invalid content/binding returns `invalid_payload` and leaves the call pending. Stale authority returns `stale_connection`; unknown, settled, cancelled or expired calls return `unknown_turn`. After resume, submit the original result with the new generation. Resume does not claim control, recover arguments, renew deadlines or redispatch.

### Callback bounds

- Timeout: integer 1,000–600,000 ms, default 120,000. Null, fractional, string and out-of-range values reject. Execution time starts at dispatch admission. An absent executor waits at most 60 seconds from callback need; reconnect resets neither deadline.
- Stop cancels; timeout/availability failure fails the turn. Synthetic failures identify `tool_timeout`, `executor_unavailable` or `cancelled`, with `uncertain` versus `not_dispatched` effects. Terminal error codes remain ordinary v2. Cancellation events use reason `cancelled` or `tool_timeout`; they are advisory, not rollback guarantees.
- At most 32 calls across the daemon, including settled evidence for active turns. Each call reserves the complete resume payload within 30 KiB; byte bounds may reject earlier without dispatch. Provider call IDs are nonempty, at most 128 bytes, unique within a turn.
- Names: 48 bytes, `[a-z][a-z0-9_]*`; descriptions: 512 bytes; combined instructions: 16 KiB; argument/result JSON: 16 KiB; errors: 512 bytes.
- Schemas: root object, `additionalProperties:false`, at most 32 required properties with names at most 48 bytes. Only strings (`maxLength` 1–4096 Unicode scalars), booleans and bounded safe integers. Nested schemas, extra keywords and duplicate JSON members reject.