diff --git a/docs/vrs/01-ding/02-codex/spec.md b/docs/vrs/01-ding/02-codex/spec.md index 71087902..43985872 100644 --- a/docs/vrs/01-ding/02-codex/spec.md +++ b/docs/vrs/01-ding/02-codex/spec.md @@ -1,13 +1,231 @@ # Codex harness specification -The screen grammar by which DING recognizes a Codex composer. It realizes -[`../requirements.md`](../requirements.md) through the mechanism in -[`../spec.md`](../spec.md). +This document defines native app-server delivery and the legacy Codex screen +grammar. It realizes [`../requirements.md`](../requirements.md) through the +selection and durability rules in [`../spec.md`](../spec.md). ## Status Active. +## Native app-server transport + +`deliver "app-server"` selects this transport. st2 delivers through the Codex +app-server control protocol. It does not inspect the rendered screen, write to +the composer, or start the legacy `ding` sidecar. + +The native path uses typed user input only. It must never use +`thread/inject_items`. Raw injection does not start an idle turn, does not have +the typed user-message receipt below, and has different persistence behavior. + +### Controlled launch and thread identity + +st2 starts one app-server daemon for the declared agent on a host-local Unix +socket. It opens and initializes its control connection before it starts the +Codex TUI with `codex --remote unix://PATH`. The control client must be able to +observe `thread/started` before the TUI can create a new thread. + +The app server and remote TUI must load one effective authored configuration. +st2 forwards the app-server-supported global `config`, `enable`, `disable`, and +`strict-config` arguments to the server. It keeps TUI-only model, policy, +workspace, authentication, and prompt arguments on the TUI command. In +particular, an authored project-trust override must reach the server that loads +project-local config and hooks; forwarding it only to the TUI is a delivery +configuration error. + +Codex 0.145.0 and 0.146.0 do not apply +`--dangerously-bypass-hook-trust` to the startup review of a persistent remote +resume. When that flag is authored for a resumed session, st2 must use a +bounded hook-trust preflight before it starts the TUI. The preflight starts the +same exact app-server binary with the same effective authored configuration. It +calls typed `hooks/list` for the controlled workspace and reads the exact hook +key, current hash, managed state, and trust state. It then stops only that owned +preflight process. + +The final app server receives a session-only `hooks.state` projection for each +hook that the provider reports as `untrusted` or `modified`. The projection +contains only the provider key and its exact current hash. It does not persist +hook trust or change user configuration. Hooks that are already `trusted` or +`managed` are not projected. A rejected request, a different working +directory, an unknown trust state, a missing typed field, or conflicting hashes +must stop launch before the TUI starts. The wrapper trace records the preflight +stages and projected hook count, but it must not record hook keys, commands, or +hashes. Without the authored bypass flag, st2 must not create this projection. + +For a new session, st2 records the thread ID from `thread/started`. For a +resumed session, st2 starts the TUI with the recorded thread ID. Its initialized +control client calls `thread/loaded/list` until the result contains that exact +ID. This typed result must arrive before the control client calls +`thread/resume`. A started TUI process or a connected control socket does not +prove that the TUI loaded the thread. The loaded-thread wait must expire before +the outer thread-binding wait, so its specific provider error reaches the +wrapper trace. + +After this observation, the control client calls `thread/resume` for the exact +ID. st2 binds the new runtime incarnation only from the successful response. It +must not wait for a new `thread/started` notification or an unchanged status +notification on the control connection. A timeout, resume error, or different +returned thread ID leaves the prior binding non-current and every message +unread. st2 does not infer ownership from `thread/list`, a working directory, a +process, or a PTY. Those surfaces do not identify which TUI owns a thread. + +The thread binding is persistent runtime state. It includes the exact agent +runtime incarnation that owns it. st2 rejects a binding from a prior +incarnation. A missing, conflicting, or stale binding makes the native +transport unavailable and leaves every message unread. + +The control client remains subscribed to these events for the bound thread: + +- `thread/status/changed`, +- `turn/started`, +- `turn/completed`, and +- `item/started` and `item/completed`. + +`ThreadStatus` distinguishes idle and active states. It does not carry the +active turn ID. Only the turn lifecycle supplies that ID. + +### Delivery state machine + +The adapter applies this rule to the bound thread and a bounded body-bearing +FIFO inbox view. The view contains the largest complete prefix that fits 16 +messages and 16 KiB. It never truncates a body. If the head does not fit, the +view identifies that message without its body, and all later messages remain +unread behind it. + +The byte limit bounds inbox input handed to one model inference. The message +limit bounds the action set created by a burst of small messages. These values +are transport bounds, not efficiency thresholds. Their token and inference cost +remains unmeasured under `DING-R15` through `DING-R18`. + +| Observed state | Request | Result | +| --- | --- | --- | +| Idle | `turn/start` with typed text | Start a turn and wake Codex | +| Active regular turn with exact current ID | `turn/steer` with typed text and `expectedTurnId` | Queue input on that turn | +| Review or manual compaction | None | Hold until a later idle state | +| No active turn ID, conflicting events, or stale ID | None | Reconcile state and hold | + +Every `turn/start` and `turn/steer` request includes a stable +`clientUserMessageId` derived from the recipient, thread binding, and FIFO head +filename. The identifier controls duplicate transport for the delivered view; +it does not settle any included message. The adapter sends no turn-level +overrides with `turn/steer`. + +`turn/steer` must use the exact ID from the latest unmatched `turn/started` +event. A `turn/completed` event clears that ID. A steering error for no active +turn, a stale `expectedTurnId`, review, or compaction is a hold result. The +adapter must not fall back to `turn/start` or `thread/inject_items` in the same +attempt. + +The adapter marks review and compaction as non-steerable when +`enteredReviewMode` or `contextCompaction` item events appear. The app server +can reject a request before those events arrive. The same hold rule applies to +that race. + +### Typed acceptance receipt + +A JSON-RPC success response is not a delivery receipt. A returned turn ID is +not a delivery receipt. st2 completes the DING attempt only after this event: + +```text +item/completed + item.type = "userMessage" + item.clientId = + threadId = +``` + +`item/completed` is the authoritative item state. An `item/started` event may +show progress, but it does not complete delivery. An item for another client +ID, thread, or runtime incarnation does not complete delivery. + +The adapter records submission state before it sends a request. If the control +connection closes after submission and before the typed receipt, the attempt is +ambiguous. On reconnect, st2 resumes the bound thread and reconciles its typed +user-message history before it sends that client ID again. + +The app server does not promise duplicate rejection for +`clientUserMessageId`. st2 therefore owns duplicate control. It persists one +accepted receipt for the FIFO head that identifies the delivered view and its +runtime binding. Watcher events, poll events, reconnects, and supervisor +restarts consult that receipt before they send. Archive precedence removes +obsolete receipt state. + +### Durable inbox and shutdown + +The selected catalog inbox remains authoritative. Native delivery does not +archive or delete any included message. The agent handles and archives each +message through normal message commands. + +Before each attempt, the adapter runs the normal message sweep. An archive +record for the identifying FIFO head wins. A held, rejected, disconnected, +unknown, or ambiguous attempt leaves every message unread and retryable. + +Control transport close is a normal adapter stop. The adapter sends nothing +after close. A restarted adapter must restore the exact thread binding and +duplicate-control state before it attempts delivery. + +The wrapper must keep a diagnostic trace for its current run. It creates the +trace only after it holds the exclusive runtime-owner lock. The trace records +startup stages and the full terminal error chain. It must not record authored +argument values, prompts, or message bodies. The next exclusive owner +truncates the file before it starts, so the trace has a fixed one-run retention +bound. A failed launch must leave enough trace data to distinguish +server-socket setup from TUI thread binding. + +### Post-settlement observability + +Version 1 is not a delivery audit log. Its accepted delivery state exists to +prevent duplicate transport while the identifying inbox head remains unread. +Archive precedence removes that state after the agent archives the head. The +archive proves agent settlement. After that removal, st2 cannot prove which +transport method, turn ID, client ID, or acceptance time delivered the message. +It must not make that historical claim. + +**Codex-DQ1 Post-settlement audit:** Decide whether a later contract should +retain a bounded transport audit after archive. That contract must define the +retained fields, privacy and redaction rules, retention and resource bounds, +and failure behavior before implementation. Version 1 deliberately defines no +duration, size, or pass threshold and retains no post-settlement audit record. + +### Remote TUI evidence + +The native transport is not accepted until a live `codex --remote` test proves +all of these results against the same app server and control client: + +- The normal TUI can start a new bound thread and resume that exact thread. +- On resume, `thread/loaded/list` contains the preserved thread before the + control client calls `thread/resume`. +- The resumed TUI consumes its authored initial prompt and runs its + `SessionStart` resume hook. A started process or a successful control resume + is not sufficient evidence. +- For a resumed session with authored hook-trust bypass, a typed preflight + projects the exact current hook hashes for that invocation. The TUI does not + stop at hook review, the hooks run, and the user configuration remains + byte-identical. +- An idle inbox message produces one typed `turn/start` user message and + observable agent work. +- A message during a regular active turn produces one typed `turn/steer` user + message without corrupting terminal input or the active turn. +- Review, compaction, stale-turn, and no-active-turn states hold the message. +- The inbox file remains until the agent reads and archives it. +- A deliberate protocol or receipt break makes the test fail. + +The evidence must also record every user-visible difference between a local +TUI and the remote TUI. Known protocol limits are not silently treated as +parity. + +Codex app-server and its remote transport are provider experimental surfaces. +The implementation pins its accepted protocol schema to a finite set of exact, +tested Codex versions. Adding a version requires a schema comparison for every +request, response, and event path used by this adapter plus the live remote TUI +acceptance above. The current set is Codex CLI 0.145.0 and 0.146.0. Any other +version, or an incompatible schema or event change, makes delivery unavailable +and leaves the inbox unread. + +## Legacy screen transport + +The remaining sections define the unchanged screen grammar selected by +`ding`. + ## Locating the composer This harness is located against the **raw** screen, including escape sequences, diff --git a/docs/vrs/01-ding/requirements.md b/docs/vrs/01-ding/requirements.md index 130f72fa..2020cefe 100644 --- a/docs/vrs/01-ding/requirements.md +++ b/docs/vrs/01-ding/requirements.md @@ -20,10 +20,10 @@ is in [`spec.md`](./spec.md). ## Assumptions -- **DING-A01 Rendered screens only:** The only available evidence about a - composer's state is a rendered terminal screen. No maintained harness exposes - an evented idle signal, so every precondition below is a measured heuristic - over text. `DQ2` in [`../spec.md`](../spec.md) tracks closing that gap. +`DING-A01 Rendered screens only` is retired. Rendered-screen evidence is a +limit of the legacy `ding` transport. It is not an assumption for a maintained +harness that declares a native `deliver` transport. + - **DING-A02 Cooperative human:** The human sharing a pane is not adversarial. A screen that deliberately imitates another harness's composer is a correctness concern, not a security boundary, consistent with `A02`. @@ -41,21 +41,43 @@ is in [`spec.md`](./spec.md). ## Requirements -### Must preserve initial transport and gate every retry - -- **DING-R01 Combined initial transport:** A fresh notice uses one bounded PTY - transaction containing the bracketed paste, the accepted 0.5 second delay, - and Return. Ownership is recorded before that command starts. Composer - heuristics do not split or suppress this initial transport. -- **DING-R02 Two adjacent retained-safe retry observations:** A later bare - Return is permitted only for a transport-owned payload whose exact notice is - still the complete composer and is classified `RetainedSafe` in two - immediately adjacent inspections. The final observation is adjacent to the - Return itself. Any change, block, or uncertainty prevents retry submission. -- **DING-R03 Fail-closed receipt and retry:** After the initial transport, a - changed composer, a human draft, an active turn, a modal, an unreadable - screen, an unrecognized harness, and a bounded observation timeout never - become `Delivered` and receive no retry input. Anything not positively +### Must select one explicit transport + +- **DING-R11 Declared transport:** An agent selects at most one delivery + transport. `ding` selects the legacy screen transport. `deliver "mcp"` + selects the Claude native transport. `deliver "app-server"` selects the + Codex native transport. A declaration with neither node has no DING delivery. + A declaration with both nodes, multiple `deliver` nodes, or an unsupported + `deliver` value is invalid. +- **DING-R12 No transport inference:** st2 does not infer a native transport + from an agent command or a screen. Native delivery uses the declared adapter. + A binary that supports `deliver` validates its value and its mutual exclusion + with `ding`. +- **DING-R13 Durable native delivery:** The inbox file remains the source of + truth for native delivery. Only the adapter's declared success condition can + complete a delivery attempt. A closed, unavailable, stale, or unknown native + transport leaves the message unread and retryable. Archive precedence and + restart recovery remain unchanged. +- **DING-R14 Missing transport report:** Doctor reports an active agent that + declares neither `ding` nor `deliver`. The omission remains a valid opt-out + and does not block the agent. The report makes a no-delivery state visible. + +### Must preserve legacy transport and gate every legacy retry + +- **DING-R01 Combined initial transport:** A fresh legacy notice uses one + bounded PTY transaction containing the bracketed paste, the accepted 0.5 + second delay, and Return. Ownership is recorded before that command starts. + Composer heuristics do not split or suppress this initial transport. +- **DING-R02 Two adjacent retained-safe retry observations:** A later legacy + bare Return is permitted only for a transport-owned payload whose exact + notice is still the complete composer and is classified `RetainedSafe` in + two immediately adjacent inspections. The final observation is adjacent to + the Return itself. Any change, block, or uncertainty prevents retry + submission. +- **DING-R03 Fail-closed receipt and retry:** After the initial legacy + transport, a changed composer, a human draft, an active turn, a modal, an + unreadable screen, an unrecognized harness, and a bounded observation timeout + never become `Delivered` and receive no retry input. Anything not positively understood retains staged ownership. On an inspect-only staged retry, a maintained adapter may positively prove that the exact owned payload is no longer retained; that proof relinquishes ownership only when an archive @@ -101,6 +123,27 @@ is in [`spec.md`](./spec.md). archived staged head. Unread, unreadable, unrecognized, and ambiguous attempts retain staged ownership and retry by inspection without re-pasting. +### Must make delivery efficiency measurable + +- **DING-R15 Delivered-message cost unit:** Every efficiency result uses one + delivered inbox message as its cost unit. A positive legacy receipt counts its + exact FIFO head. A positive native receipt counts each complete inbox message + in its accepted view. Held attempts, retries, overflow, and an oversized-head + metadata fallback do not increase the delivered-message count. Results do not + use turns, sessions, requests, or attempts as the cost unit. +- **DING-R16 Inference and tool-crossing cost:** Evidence reports model + inferences per delivered message and model tool-boundary crossings per + delivered message as separate values. It does not infer either value from a + provider turn count. +- **DING-R17 Token cost classes:** Evidence reports input, output, cache-read + input, and cache-creation input tokens per delivered message as separate + values. An unavailable token class is `unknown`, not zero. +- **DING-R18 Comparable evidence:** A transport comparison is valid only when + the provider exposes the requested counts and the experiment attributes them + to the same isolated target messages. A transport that cannot be measured for + a requested metric is incomparable for that metric. No numeric pass threshold + exists until accepted evaluation evidence establishes it. + ## Evidence Each guarantee above is pinned by a named test in diff --git a/docs/vrs/01-ding/spec.md b/docs/vrs/01-ding/spec.md index 3d432022..b5a0e886 100644 --- a/docs/vrs/01-ding/spec.md +++ b/docs/vrs/01-ding/spec.md @@ -10,6 +10,79 @@ specified in [`01-claude/spec.md`](./01-claude/spec.md) and Active. A map to the implementation and its evidence, not a replacement for the tests. +## Delivery selection + +Delivery is opt-in. An agent declaration selects one transport: + +| Declaration | Transport | +| --- | --- | +| `ding` | Legacy screen transport | +| `deliver "mcp"` | Claude native MCP transport | +| `deliver "app-server"` | Codex native app-server transport | +| Neither node | No delivery | + +The selector table defines the transport contract, not current implementation +parity. Current st2 implements the Codex app-server adapter. It does not +implement the production Claude MCP adapter. The parser accepts `deliver +"mcp"`, leaves the authored Claude launch unchanged, and derives no legacy +`ding` sidecar. A Claude agent that selects it therefore receives no DING. Do +not deploy that selector until the production adapter exists. The Claude +channel specification and standalone probe are not that adapter. + +`ding` and `deliver` are mutually exclusive. More than one `deliver` node is +invalid. Any other `deliver` value is invalid. st2 does not infer a transport +from the agent command because command arguments are opaque. + +The native selector is a new `deliver` node. A binary released before this +contract ignores that unknown agent child. It lowers a valid `deliver`-only +agent with no delivery sidecar. The agent receives no DING. It does not silently +use the legacy screen transport. This is a visible delivery outage. + +A binary that supports `deliver` validates its value and its mutual exclusion +with `ding`. Native delivery must not be encoded as an argument to `ding`, +because a pre-change parser would accept that form as legacy `ding` and use the +wrong transport. Doctor reports an active agent that declares no transport. The +report does not make the valid no-delivery opt-out an error. + +The durable inbox is the source of truth for every transport. An archive with +the same message name wins. A native adapter completes delivery only after its +declared provider-specific success condition. If the adapter is closed, +unavailable, stale, or in an unknown state, it sends no unsafe input and leaves +the inbox message unread for retry. Native adapters do not use the +rendered-screen classifier. + +## Efficiency accounting + +Delivery efficiency is measured per delivered inbox message, not per provider +turn or session (`DING-R15`). The denominator is the exact number of messages +covered by a positive receipt: one FIFO head for legacy delivery, or the +complete messages in one accepted native view. A held or failed attempt, a +retry, overflow, and an oversized-head metadata fallback have a denominator of +zero. Correctness evidence reports those outcomes separately; it does not hide +them inside a cost average. + +Each experiment reports these values separately (`DING-R16`, `DING-R17`): + +| Metric | Current requirement status | +| --- | --- | +| Model inferences per delivered message | Unmeasured; no pass threshold | +| Model tool-boundary crossings per delivered message | Unmeasured; no pass threshold | +| Input tokens per delivered message | Unmeasured; no pass threshold | +| Output tokens per delivered message | Unmeasured; no pass threshold | +| Cache-read input tokens per delivered message | Unmeasured; no pass threshold | +| Cache-creation input tokens per delivered message | Unmeasured; no pass threshold | + +A provider turn is not evidence of one inference or a fixed number of tool +crossings. Counts must come from the provider's authoritative event or usage +surface. If a provider omits a requested count, the result is `unknown`. If the +experiment cannot isolate the target messages or expose comparable counts for +both transports, that metric is incomparable rather than zero or improved +(`DING-R18`). Accepted evaluation evidence can fill the baseline and threshold; +the specification does not invent either value. + +The rest of this document defines the unchanged legacy screen transport. The +native wire contracts are in each maintained harness specification. + ## Composer states One inspection of a rendered screen, evaluated against one exact expected @@ -45,11 +118,11 @@ staged retry ─► receipt ─┬─ Accepted ─────────── └─ RetainedBlocked / Unproven ─────────────► Staged ``` -Fresh delivery preserves the production transport: one bounded PTY transaction -contains a bracketed paste, a 0.5 second delay, and Return (`DING-R01`). -Ownership is recorded immediately before that transaction. The production path -does not inspect the composer first and does not use the separate staging -helper. +Fresh legacy delivery preserves the production transport: one bounded PTY +transaction contains a bracketed paste, a 0.5 second delay, and Return +(`DING-R01`). Ownership is recorded immediately before that transaction. The +production path does not inspect the composer first and does not use the +separate staging helper. Every failure of that terminal command or of the following receipt observation resolves to `Staged` (`DING-R07`): the paste and Return may already have reached @@ -148,10 +221,11 @@ Declared `busy` never suppresses delivery; only fresh `dnd` defers it ## Known limits -- Idle proof depends on footer chrome that a harness may render differently - across permission or approval modes. A harness whose footer is not recognized - in a given mode defers indefinitely rather than delivering. This is an - explicit limit per `T01`, and each harness spec states which modes it proves. -- The classifier is a measured heuristic over rendered text, not an evented - signal, so a renderer change can defer delivery until the grammar is updated. - Tracked as `DQ2` in [`../spec.md`](../spec.md). +- Legacy idle proof depends on footer chrome that a harness may render + differently across permission or approval modes. A harness whose footer is + not recognized in a given mode defers indefinitely rather than delivering. + This is an explicit limit per `T01`, and each harness spec states which modes + it proves. +- The legacy classifier is a measured heuristic over rendered text, not an + evented signal, so a renderer change can defer legacy delivery until the + grammar is updated. Maintained native transports do not use this classifier. diff --git a/docs/vrs/02-agent-spec/spec.md b/docs/vrs/02-agent-spec/spec.md index 5ef2aedb..e7b524d3 100644 --- a/docs/vrs/02-agent-spec/spec.md +++ b/docs/vrs/02-agent-spec/spec.md @@ -358,10 +358,13 @@ Authoring: [pinned complete declaration][evals-fields]. st2 source:

F14 Compact agent fields

-Compact `command`, `argv`, `env`, `lifecycle`, and `ding` convert to the -generated agent PTY and derived sidecar. The tasks use F09, F11, and F12; -`ding` carries the dependency on the generated agent task described there. -Compact syntax adds no other behavior. +Compact `command`, `argv`, `env`, and `lifecycle` define the generated agent +PTY. Bare `ding` selects the derived legacy screen sidecar. `deliver "mcp"` +selects native Claude delivery. `deliver "app-server"` selects native Codex +delivery. The two selector nodes are mutually exclusive. More than one +`deliver` node and any other value are invalid. Neither node means no delivery. +Each generated transport depends on the generated agent task. The tasks use +F09, F11, and F12. Compact syntax adds no other behavior. Authoring: [pinned compact tasks][evals-tasks]. That document and st2 `9887b28` predate compact `argv` and `lifecycle`. Current st2 source: @@ -370,9 +373,10 @@ predate compact `argv` and `lifecycle`. Current st2 source:

F15 Provider and ignored fields

-Core st2 ignores `harness`, `model`, `persona`, `permissions`, `transport`, -`strategy`, `meta`, and provider extensions. They do not change core equality, -wake behavior, or actions. Providers may convert them into F05 through F14; +Core st2 ignores `harness`, `model`, `persona`, `permissions`, the legacy +render-only `transport` field, `strategy`, `meta`, and provider extensions. +They do not change core equality, wake behavior, or actions. Providers may +convert them into F05 through F14; core acts only on that concrete output. Authoring: [pinned complete declaration][evals-fields]. st2 source: diff --git a/docs/vrs/ontology.md b/docs/vrs/ontology.md index 38606fb0..e57b46cb 100644 --- a/docs/vrs/ontology.md +++ b/docs/vrs/ontology.md @@ -154,9 +154,12 @@ Authority: [`message::Message`](../../src/message.rs#L26-L46) ### DING -The terminal notification that makes an agent aware of unread messages. +The delivery signal that makes an agent aware of unread messages. A declaration +selects either the legacy terminal transport or one provider-native control +transport. -Authority: [DING module contract](../../src/ding/mod.rs#L1-L14) +Authority: [DING specification](./01-ding/spec.md) and +[DING module contract](../../src/ding/mod.rs#L1-L14) ## Collision rules @@ -172,7 +175,7 @@ Authority: [DING module contract](../../src/ding/mod.rs#L1-L14) value and [bus ID](../../crates/agent-spec/src/spec.rs#L203-L211) for the host-qualified address. - Use [message](../../src/message.rs#L26-L46) for the durable record and - [DING](../../src/ding/mod.rs#L1-L14) for its terminal notification. + [DING](./01-ding/spec.md) for its delivery signal. ## Avoid diff --git a/docs/vrs/requirements.md b/docs/vrs/requirements.md index dc519e72..18bd6ab9 100644 --- a/docs/vrs/requirements.md +++ b/docs/vrs/requirements.md @@ -84,7 +84,10 @@ accepted. - **R05 DING/archive semantics:** Inbox delivery, archive precedence, retries, suppression, and restart recovery are deterministic and tested. DING may interrupt agent work, but it must not alter or submit a human's active draft; - an unknown interaction state defers delivery. + an unknown interaction state defers delivery. A declaration selects either + the legacy screen transport or one supported native transport. st2 does not + infer a native transport from an opaque command. A failed or unavailable + transport leaves the inbox message unread and retryable. - **R06 Restartable launch definitions:** A restarted PTY or exec receives the complete effective launch definition, including environment and supported launch fields. diff --git a/docs/vrs/spec.md b/docs/vrs/spec.md index 561aa7ce..a894f073 100644 --- a/docs/vrs/spec.md +++ b/docs/vrs/spec.md @@ -664,6 +664,34 @@ atomic inbox file → DING attempt → agent reads → archive receipt composer cannot create a short-lived PTY probe on every inbox poll. Inbox reads do not wake the sidecar; only mutations bypass its bounded poll cadence. +### Declared native DING delivery (DQ2 resolution) + +An agent selects at most one delivery transport. `ding` keeps the legacy +screen transport unchanged. `deliver "mcp"` selects native Claude delivery. +`deliver "app-server"` selects native Codex delivery. An agent with neither +node has no delivery. The two node forms are mutually exclusive, multiple +`deliver` nodes are invalid, and an unknown `deliver` value is invalid. + +The transport is explicit because an agent command is opaque. A binary released +before this contract ignores the unknown `deliver` child. It lowers a valid +`deliver`-only declaration with no delivery sidecar. The agent receives no DING +instead of receiving a DING through the legacy screen transport. This is a +visible delivery outage. A binary that supports `deliver` validates its value +and its mutual exclusion with `ding`. + +Native delivery is not an argument to `ding`, which a pre-change parser would +accept as legacy delivery and route through the wrong transport. Doctor reports +an active agent that declares neither transport. The no-delivery form remains a +valid opt-out and does not block the agent. + +Native delivery replaces rendered-screen inference for maintained Claude and +Codex agents that select it. Each native adapter uses its provider's evented +control channel and its provider-specific success condition. A closed, +unavailable, stale, or unknown channel sends no unsafe input. The durable inbox +message remains unread and retryable. Archive precedence, suppression, and +restart recovery remain the same for every transport. The legacy classifier +remains available only for agents that select `ding`. + ## State and scope - **R08:** Presence and activity status are separate signals. The catalog must @@ -785,15 +813,6 @@ the resident supervisor continues to reconcile the complete local catalog. boundary, and execution receipts are not yet specified. A successful executable eval and Nathan's approval should resolve this before adding scheduler requirements. -- **DQ2 Safe DING delivery:** Bounded observation now replaces the fixed - paste-to-Return delay: maintained Codex and Claude composers must be - positively empty before paste and show the exact staged notice twice before - a separate Return. Human, modal, active, changed, timed-out, and unknown - states fail closed, with staged-payload ownership preventing duplicate paste. - This measured screen heuristic is still not an evented proof and renderer - changes may defer delivery. Resolve the remaining gap with a stronger evented - signal or other measured classifier; a small on-device model is an optional - experiment, not a required architecture. - **DQ3 Catalog agent state:** Define the catalog paths, schemas, freshness rules, and atomic update semantics for presence, activity status, current plan, and current plan step. Prove that stale state is distinguishable and