From 63e03173fce515c0493a8fda5ca6e6802b517913 Mon Sep 17 00:00:00 2001 From: Nathan Herald Date: Mon, 10 Aug 2026 17:52:09 +0200 Subject: [PATCH 1/3] Specify declared native DING delivery --- docs/vrs/01-ding/requirements.md | 57 +++++++++++++++++++++----------- docs/vrs/01-ding/spec.md | 55 +++++++++++++++++++++++------- docs/vrs/requirements.md | 5 ++- docs/vrs/spec.md | 29 +++++++++++----- 4 files changed, 105 insertions(+), 41 deletions(-) diff --git a/docs/vrs/01-ding/requirements.md b/docs/vrs/01-ding/requirements.md index 130f72fa..41437f0c 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,40 @@ 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 does not know the `deliver` node rejects that declaration + instead of treating it as legacy delivery. +- **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. + +### 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 diff --git a/docs/vrs/01-ding/spec.md b/docs/vrs/01-ding/spec.md index 3d432022..9a50b17c 100644 --- a/docs/vrs/01-ding/spec.md +++ b/docs/vrs/01-ding/spec.md @@ -10,6 +10,36 @@ 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 | + +`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 that does not implement +the node rejects the declaration. Native delivery must not be encoded as an +argument to `ding`, because an older parser would accept that form as legacy +`ding` and silently use the wrong transport. + +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. + +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 +75,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 +178,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/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..7e3aed5e 100644 --- a/docs/vrs/spec.md +++ b/docs/vrs/spec.md @@ -664,6 +664,26 @@ 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. The native +selector is a new node so an older binary rejects the declaration. It is not an +argument to `ding`, which an older parser could accept as legacy delivery. + +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 +805,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 From 5e52199ebf67ed60410520f6462a07889cb64a66 Mon Sep 17 00:00:00 2001 From: Nathan Herald Date: Mon, 10 Aug 2026 17:57:36 +0200 Subject: [PATCH 2/3] Correct native delivery compatibility boundary --- docs/vrs/01-ding/requirements.md | 7 +++++-- docs/vrs/01-ding/spec.md | 14 ++++++++++---- docs/vrs/spec.md | 14 +++++++++++--- 3 files changed, 26 insertions(+), 9 deletions(-) diff --git a/docs/vrs/01-ding/requirements.md b/docs/vrs/01-ding/requirements.md index 41437f0c..3fe9a55d 100644 --- a/docs/vrs/01-ding/requirements.md +++ b/docs/vrs/01-ding/requirements.md @@ -51,13 +51,16 @@ harness that declares a native `deliver` transport. `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 does not know the `deliver` node rejects that declaration - instead of treating it as legacy delivery. + 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 diff --git a/docs/vrs/01-ding/spec.md b/docs/vrs/01-ding/spec.md index 9a50b17c..65b114d1 100644 --- a/docs/vrs/01-ding/spec.md +++ b/docs/vrs/01-ding/spec.md @@ -25,10 +25,16 @@ Delivery is opt-in. An agent declaration selects one transport: 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 that does not implement -the node rejects the declaration. Native delivery must not be encoded as an -argument to `ding`, because an older parser would accept that form as legacy -`ding` and silently use the wrong transport. +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 diff --git a/docs/vrs/spec.md b/docs/vrs/spec.md index 7e3aed5e..a894f073 100644 --- a/docs/vrs/spec.md +++ b/docs/vrs/spec.md @@ -672,9 +672,17 @@ screen transport unchanged. `deliver "mcp"` selects native Claude delivery. 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. The native -selector is a new node so an older binary rejects the declaration. It is not an -argument to `ding`, which an older parser could accept as legacy delivery. +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 From 2db8a8c498627701617754b2414a490621156955 Mon Sep 17 00:00:00 2001 From: Nathan Herald Date: Mon, 10 Aug 2026 18:07:32 +0200 Subject: [PATCH 3/3] Align DING ontology with native delivery --- docs/vrs/02-agent-spec/spec.md | 18 +++++++++++------- docs/vrs/ontology.md | 9 ++++++--- 2 files changed, 17 insertions(+), 10 deletions(-) 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