diff --git a/docs/vrs/.decisions/0005-pi-delivers-natively-through-an-injected-extension.md b/docs/vrs/.decisions/0005-pi-delivers-natively-through-an-injected-extension.md index b8cbdc27..1535b420 100644 --- a/docs/vrs/.decisions/0005-pi-delivers-natively-through-an-injected-extension.md +++ b/docs/vrs/.decisions/0005-pi-delivers-natively-through-an-injected-extension.md @@ -11,7 +11,7 @@ the workspace-trust dialog. Codex gets a dedicated app-server daemon, an observe before the interactive client, thread-ownership binding, and a hard version pin — 4940 lines. Underneath both sits DING, the screen-scraping transport that exists because neither harness offered anything better at the time, and whose safety rests on a synchronous adjacent composer proof -([`0004`](0004-only-a-synchronous-proof-authorizes-a-pty-write.md)). +([`0001-ding-harness-dispatch-is-positional-and-harness-owned`](0001-ding-harness-dispatch-is-positional-and-harness-owned.md)). Adding pi asks which of these shapes it should take. pi answers the question itself: it has no MCP, no app-server, and no lifecycle-hook mechanism. What it has is an extension API that runs **inside** @@ -65,7 +65,7 @@ reproducible with no credentials. Full record and captures: pi is a natively-delivered harness. It declares `deliver "pi-channel"` or a typed `pi {}` driver, and **no pi arm is added to the DING registry**. The existing rule that refuses `ding` together with `deliver` (`crates/agent-spec/src/spec.rs`) is what keeps a pi agent off the PTY write path -entirely, so [`0004`](0004-only-a-synchronous-proof-authorizes-a-pty-write.md) is untouched. +entirely, so [`0001-ding-harness-dispatch-is-positional-and-harness-owned`](0001-ding-harness-dispatch-is-positional-and-harness-owned.md) is untouched. Four things follow, each chosen against a specific failure it prevents: diff --git a/docs/vrs/.decisions/0007-child-output-capture-is-bounded-and-tail-preserving.md b/docs/vrs/.decisions/0007-child-output-capture-is-bounded-and-tail-preserving.md index 8c24be3e..43a67668 100644 --- a/docs/vrs/.decisions/0007-child-output-capture-is-bounded-and-tail-preserving.md +++ b/docs/vrs/.decisions/0007-child-output-capture-is-bounded-and-tail-preserving.md @@ -42,10 +42,19 @@ the bash judge that can deadlock. 4. **Eval run steps stream to their log files** instead of buffering, and the bash judge uses null stdio (only its exit status is consumed). -Rejected alternatives: disk spill references for oversized diagnostics -(spill-file lifecycle for no demonstrated consumer) and streaming every -shell-out to log files exec-backend style (changes every error path; revisit -only if a consumer needs full oversized diagnostics). +## Options + +| Option | Result | Reason | +| --- | --- | --- | +| Cap diagnostics; keep named payload capture complete | Selected | Bounds memory while preserving structured payload parsing. | +| Spill oversized diagnostics to disk | Rejected | Adds spill-file lifecycle without a demonstrated consumer. | +| Stream every shell-out to log files | Rejected | Changes every error path; revisit only if a consumer needs complete oversized diagnostics. | + +## Evidence and Argument + +The reproduced 16 MiB and eight-way concurrent probes showed peak RSS scaling +with child output. The selected cap removes that scaling while retaining the +tail that carries failure diagnostics. ## Consequences diff --git a/docs/vrs/.delta/DELTA-004-stream-dedup-horizon.md b/docs/vrs/.delta/DELTA-004-stream-dedup-horizon.md index 30179cec..6c5f79b7 100644 --- a/docs/vrs/.delta/DELTA-004-stream-dedup-horizon.md +++ b/docs/vrs/.delta/DELTA-004-stream-dedup-horizon.md @@ -1,6 +1,13 @@ # DELTA-004: stream deduplication is bounded to the receipt ring -## Current mismatch +Status: open + +## Divergence + +Ratified requirements promise replay identity beyond the shipped bounded +receipt horizon. + +## VRS Ratified [`STREAM-R04`](../04-stream/requirements.md) promises that replaying an event identity always returns its original filename, including after the @@ -8,6 +15,8 @@ event is archived. [`STREAM-R05`](../04-stream/requirements.md) says correctness never depends on the bounded ring because unread inbox copies and archive receipts anchor replay identity. +## Implementation + The shipped implementation deliberately keeps only 128 receipts per stream and performs no inbox or archive identity scan. Within that horizon, replay is idempotent and conflicting content fails. After eviction, the same event ID is @@ -15,15 +24,17 @@ honestly accepted as a new event. Archive receipts remain authoritative for their known filenames during crash recovery, but they are not an index from `(stream, event-id)` to filename. -## Why the implementation differs - Searching every archive would make emit cost proportional to retained stream history and contradict the bounded-state goal. An unread-only fallback would make idempotency change when an agent archives an event. A bounded receipt window gives a precise operational contract and keeps ingress work independent of inbox/archive history. -## Required resolution +## Direction + +update VRS + +## Resolution Signal Requirements are protected. Maintainer approval is required to amend STREAM-R04/R05 to make the retained receipt horizon the idempotency boundary. diff --git a/docs/vrs/.experiments/2026-08-18-pi-harness-integration.md b/docs/vrs/.experiments/2026-08-18-pi-harness-integration.md index e491a4a9..adfd590d 100644 --- a/docs/vrs/.experiments/2026-08-18-pi-harness-integration.md +++ b/docs/vrs/.experiments/2026-08-18-pi-harness-integration.md @@ -6,13 +6,15 @@ Subject: `pi` — the pi coding agent CLI, npm `@earendil-works/pi-coding-agent` `earendil-works/pi`. The older `@mariozechner/pi-coding-agent` (0.73.1) is npm-deprecated in favour of it. `pi` was not on this host's `PATH`; 0.84.2 was installed into a scratch prefix for these runs. +## Method + All runs used a local fake OpenAI-completions server rather than a real provider, so every result is reproducible with no credentials and no network. Artifacts are in [`2026-08-18-pi-captures/`](2026-08-18-pi-captures/): the prototype extension (`st2-channel.ts`), the fake provider registration (`fake-provider.ts`), the fake model server (`fake-llm.mjs`), and the two event captures cited below. -## Why this matters for st2 +## Question st2's two existing harnesses each solve native delivery a different way, and neither is cheap: @@ -26,7 +28,7 @@ pi's extension API changes what is available. An extension runs **inside** the i process, can inject a user message, and sees a full lifecycle event stream. The measurements below were taken to decide whether that is real. -## Established facts (measured, not asserted) +## Result | Fact | Evidence | |---|---| @@ -49,14 +51,14 @@ were taken to decide whether that is real. - No app-server daemon, no observer pre-connection, no thread binding, no protocol version pin: the channel is in-process and the injection point is a documented API call. - No screen scraping on the delivery path. The synchronous-proof rule in - [`../.decisions/0004`](../.decisions/0004-only-a-synchronous-proof-authorizes-a-pty-write.md) + [`0001-ding-harness-dispatch-is-positional-and-harness-owned`](../.decisions/0001-ding-harness-dispatch-is-positional-and-harness-owned.md) governs PTY writes; a natively-delivered agent never enters that path (`crates/agent-spec/src/spec.rs:887` refuses `ding` together with `deliver`). - No `pretrust.rs` analogue: `-a` is a launch flag, so nothing mutates ambient user config and the multi-spawn lost-update race that motivated batching for Claude cannot arise. - Presence still needs a liveness owner, because SIGKILL is silent. This is unchanged from Claude. -## The implemented slice +## Conclusion The design these measurements support is implemented on this branch and recorded as [decision 0005](../.decisions/0005-pi-delivers-natively-through-an-injected-extension.md). One @@ -140,6 +142,12 @@ Three behaviours were added on review and each was verified, not assumed calling it, which is the silent failure that would turn every mid-turn delivery into a plain send. +## VRS Impact + +The measurements ground the pi native-delivery specification, its in-process +idle proof, session-replacement channel ownership, and type-checked extension +boundary. + ## Host context worth recording This host already runs pi under a separate agent-management system: two global extensions diff --git a/docs/vrs/02-agent-spec/requirements.md b/docs/vrs/02-agent-spec/requirements.md index 1e85290c..8ef4d1a5 100644 --- a/docs/vrs/02-agent-spec/requirements.md +++ b/docs/vrs/02-agent-spec/requirements.md @@ -23,13 +23,24 @@ and host-local runtime state are sufficient. It requires no compare-and-swap implementation gaps. The root st2 VRS has authority for st2 behavior; the canonical evals Agent Spec remains the authoring authority. -Field lookup: [F01](./spec.md#f01), [F02](./spec.md#f02), -[F03](./spec.md#f03), [F04](./spec.md#f04), [F05](./spec.md#f05), -[F06](./spec.md#f06), [F07](./spec.md#f07), [F08](./spec.md#f08), -[F09](./spec.md#f09), [F10](./spec.md#f10), [F11](./spec.md#f11), -[F12](./spec.md#f12), [F13](./spec.md#f13), [F14](./spec.md#f14), -[F15](./spec.md#f15), [F16](./spec.md#f16), [F17](./spec.md#f17), and F18-F20 -in the same field-rules specification. +Field lookup: [F01](./spec.md#f01-source-form-or-path), +[F02](./spec.md#f02-agent-id-and-legacy-identity), +[F03](./spec.md#f03-host), [F04](./spec.md#f04-type), +[F05](./spec.md#f05-role), [F06](./spec.md#f06-workspace), +[F07](./spec.md#f07-resource-name-or-uri), +[F08](./spec.md#f08-render-operation-template-or-resolved-target), +[F09](./spec.md#f09-task-set-pty-exec-or-compact-ding), +[F10](./spec.md#f10-task-name-or-explicit-id), +[F11](./spec.md#f11-spawn-inputs), +[F12](./spec.md#f12-future-policy-r31), +[F13](./spec.md#f13-retired-true), +[F14](./spec.md#f14-compact-agent-fields), +[F15](./spec.md#f15-provider-and-ignored-fields), +[F16](./spec.md#f16-invalid-or-incomplete-state), +[F17](./spec.md#f17-agent-name-and-description), +[F18](./spec.md#f18-desired-state-and-reason), +[F19](./spec.md#f19-agent-stream), and +[F20](./spec.md#f20-agent-address). ## Shared invariants diff --git a/docs/vrs/02-agent-spec/spec.md b/docs/vrs/02-agent-spec/spec.md index 4e5cbc9d..1f27e09f 100644 --- a/docs/vrs/02-agent-spec/spec.md +++ b/docs/vrs/02-agent-spec/spec.md @@ -185,7 +185,7 @@ core publication and do not claim managed admission. ## Field rules -

F01 Source form or path

+### F01 Source form or path Formatting, comments, order, and a source path change are `no-op` only when all normalized fields, render plans, task IDs, fallback `cwd`, exact resolved paths, @@ -197,7 +197,7 @@ Authoring: [pinned discovery, identity, and host][evals-discovery]. st2 source: [KDL parser](../../../crates/agent-spec/src/kdl_format.rs). Evidence: [discovery](../../../crates/agent-spec/src/discovery.rs). -

F02 Agent id and legacy identity

+### F02 Agent `id` and legacy `identity` The target `id` field is the immutable catalog-global agent ID. IDs are unique across live and structurally archived subjects, across hosts and desired states. @@ -238,7 +238,7 @@ writer emits it yet; positional `identity` is still the durable key. That gap is fenced by [DELTA-003](../.delta/DELTA-003-agent-address-not-implemented.md). -

F03 host

+### F03 `host` Each supervisor evaluates only local membership. A complete present-to-absent change removes locally; absent-to-present adds locally. This is not migration, @@ -249,7 +249,7 @@ Authoring: [pinned discovery and host][evals-discovery]. st2 source: [`AgentSpec::host`](../../../crates/agent-spec/src/spec.rs). Evidence: [host filtering](../../../src/reconcile.rs). -

F04 type

+### F04 `type` An omitted value and `service` have the same effect. Any other value refuses changes to the related agent, tasks, and files before launch, write, or teardown. @@ -258,7 +258,7 @@ Authoring: [pinned complete declaration][evals-fields]. st2 source: [`JobType` and `RawSpec::job_type`](../../../crates/agent-spec/src/spec.rs). Evidence: [validation](../../../src/validate.rs). -

F05 role

+### F05 `role` Update observable declaration metadata only. Do not change the fingerprint, workspace files, notification state, or a healthy task. @@ -267,7 +267,7 @@ Authoring: [pinned complete declaration][evals-fields]. st2 source: [`AgentSpec::role`](../../../crates/agent-spec/src/spec.rs). Evidence: [KDL lowering](../../../crates/agent-spec/src/kdl_format.rs). -

F06 workspace

+### F06 `workspace` For a healthy survivor, keep the process and commit the new live context. After commit, write one durable event with the old and new paths, then try @@ -281,7 +281,7 @@ Authoring: [pinned complete declaration][evals-fields]. st2 source: [`AgentSpec::workspace`](../../../crates/agent-spec/src/spec.rs). Evidence: [`cwd` resolution](../../../src/run.rs). -

F07 Resource name or uri

+### F07 Resource `name` or `uri` Update Resource data without changing the launch fingerprint. Notify a survivor once after commit. New or replaced work reads the latest state at boot @@ -292,7 +292,7 @@ st2 `9887b28` predate Resource bindings. Current st2 source: [`Resource`](../../../crates/agent-spec/src/spec.rs). Evidence: [declared Resource projection](../../../src/agents.rs). -

F08 render {} operation, template, or resolved target

+### F08 `render {}` operation, template, or resolved target Prove ownership for every affected local owner before writing. Conflicts refuse all affected owners. Write changed bytes and enforce the declared mode. The @@ -307,7 +307,7 @@ declaration. Authoring: [pinned render contract][evals-render]. st2 implementation and evidence: [materializer](../../../src/materialize.rs). -

F09 Task set: pty, exec, or compact ding

+### F09 Task set: `pty`, `exec`, or compact `ding` Add only the unique missing child. Remove and clean only an old child with exact ownership proof. A compact DING is a derived child: it starts only after its @@ -329,7 +329,7 @@ Authoring: [pinned compact and explicit tasks][evals-tasks]. st2 source: [`Task` and `TaskKind`](../../../crates/agent-spec/src/spec.rs). Evidence: [task reconciliation](../../../src/reconcile.rs). -

F10 Task name or explicit id

+### F10 Task `name` or explicit `id` Remove the exact old ID and add the new ID. Do not infer one incarnation. Report both actions, or `hold` or `refuse` when ownership proof is missing. @@ -338,7 +338,7 @@ Authoring: [pinned explicit tasks][evals-tasks]. st2 source: [`Task::name` and `Task::id`](../../../crates/agent-spec/src/spec.rs). Evidence: [task reconciliation](../../../src/reconcile.rs). -

F11 Spawn inputs

+### F11 Spawn inputs Task `kind`, `command`, `argv`, explicit `cwd`, and task `env`, plus agent `env`, `tags`, `supervisor`, and any other start input form the versioned launch @@ -354,7 +354,7 @@ Authoring: [pinned tasks][evals-tasks] and [environment][evals-environment]. The Current st2 source: [`AgentSpec` and `Task`](../../../crates/agent-spec/src/spec.rs). Evidence: [spawn construction](../../../src/run.rs). -

F12 Future policy (R31)

+### F12 Future policy (R31) ```text canonical catalog folder + host = supervisor scope @@ -416,7 +416,7 @@ Authoring: [pinned complete declaration][evals-fields]. The [`execute`](../../../src/run.rs). Evidence: [policy planning](../../../src/reconcile.rs). -

F13 retired #true

+### F13 `retired #true` Fence, stop, and clean every declared task ID with exact ownership proof, and prevent relaunch. Retirement preserves the agent ID, removes the subject from @@ -429,7 +429,7 @@ Authoring: [pinned complete declaration][evals-fields]. st2 source: [`AgentDesiredState`](../../../crates/agent-spec/src/spec.rs). Evidence: [retirement planning](../../../src/reconcile.rs). -

F14 Compact agent fields

+### 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; @@ -441,7 +441,7 @@ predate compact `argv` and `lifecycle`. Current st2 source: [KDL fields](../../../crates/agent-spec/src/kdl_format.rs). Evidence: [`RawSpec` lowering](../../../crates/agent-spec/src/spec.rs). -

F15 Provider and ignored fields

+### F15 Provider and ignored fields Core st2 ignores `harness`, `model`, `persona`, `permissions`, `transport`, `strategy`, `meta`, and provider extensions. They do not change core equality, @@ -452,7 +452,7 @@ Authoring: [pinned complete declaration][evals-fields]. st2 source: [KDL field boundary](../../../crates/agent-spec/src/kdl_format.rs). Evidence: [`RawSpec` lowering](../../../crates/agent-spec/src/spec.rs). -

F16 Invalid or incomplete state

+### F16 Invalid or incomplete state Refuse changes to an agent, task, or file when its desired or actual state is unreadable, invalid, ambiguous, or conflicting. Keep last-known-good ownership @@ -464,7 +464,7 @@ source: [`RawSpec` and `AgentSpec`](../../../crates/agent-spec/src/spec.rs). Evidence: [validation](../../../src/validate.rs) and [reconciliation](../../../src/reconcile.rs). -

F18 desired-state and reason

+### F18 `desired-state` and `reason` `desired-state` is one of `running`, `suspended`, or `retired`. Its omission is running. A suspended or new-style retired declaration carries exactly one @@ -517,7 +517,7 @@ st2 source: [`AgentDesiredState`](../../../crates/agent-spec/src/spec.rs), [authoring](../../../tests/agent_desired_state.rs), and [planning](../../../tests/reconcile.rs). -

F17 Agent name and description

+### F17 Agent `name` and `description` Update observable declaration and runtime presentation metadata only. Neither field participates in agent ID, address routing, selection, authorization, @@ -546,11 +546,11 @@ change lands. st2 source: [`AgentSpec`](../../../crates/agent-spec/src/spec.rs), Evidence: parser, roster, exact-ID metadata, and no-restart presentation tests. -

F19 Agent stream

+### F19 Agent `stream` A `stream "" {}` declares one agent-owned event ingress endpoint. Names -are 1..=40 characters matching -`[a-z0-9]([a-z0-9-]*[a-z0-9])?` and cannot collide with an authored task named +are 1..=40 lowercase alphanumeric characters with optional interior hyphens +and cannot collide with an authored task named `stream-`. The declaration contains at most one launch: `command` is an opaque shell command, `argv` is a non-empty structured argument vector, and an empty body means external ingress. Unknown children, including the reserved @@ -571,7 +571,7 @@ lands. st2 source: [`Stream`](../../../crates/agent-spec/src/spec.rs), [`streams_are_typed_and_only_launched_streams_lower_to_derived_exec_tasks`](../../../crates/agent-spec/tests/discovery.rs) and stream lifecycle tests in [`tests/run.rs`](../../../tests/run.rs). -

F20 Agent address

+### F20 Agent `address` `address` is an optional mutable semantic alias for human routing. Its omission uses positional `identity` as the effective legacy address. Its presence diff --git a/docs/vrs/02-doctor/requirements.md b/docs/vrs/02-doctor/requirements.md index 81d53530..931249ea 100644 --- a/docs/vrs/02-doctor/requirements.md +++ b/docs/vrs/02-doctor/requirements.md @@ -1,8 +1,8 @@ # Doctor requirements Doctor checks one catalog from one host. It follows the root -[vision](../vision.md). It refines [R03](../requirements.md#L46-L47), -[R04](../requirements.md#L51-L54), and [R08](../requirements.md#L92-L95). +[vision](../vision.md). It refines [R03](../requirements.md), +[R04](../requirements.md), and [R08](../requirements.md). It does not define fleet health. ## Requirements @@ -34,6 +34,6 @@ It does not define fleet health. is a failed check that says the agent cannot send. An unavailable ledger is healthy because no sender history exists. -The [Retirement health invariant](../../../INVARIANTS.md#L20) and its tests prove +The [Retirement health invariant](../../../INVARIANTS.md) and its tests prove the retirement rule. The [specification](spec.md) owns the mechanism, check groups, and known gaps. diff --git a/docs/vrs/02-doctor/spec.md b/docs/vrs/02-doctor/spec.md index f815d3f0..6ddf43ad 100644 --- a/docs/vrs/02-doctor/spec.md +++ b/docs/vrs/02-doctor/spec.md @@ -6,7 +6,7 @@ filenames, exact report text, or fallback behavior. ## Scope and inputs -The [`doctor` CLI](../../../src/main.rs#L240-L252) selects a catalog and a host. +The [`doctor` CLI](../../../src/main.rs) selects a catalog and a host. Shared catalog rules resolve the catalog. The caller can select a host, or st2 can detect the local host. The `--require-supervisor` flag requires a resident `st2 up` process. Without this flag, manual and one-shot operation are valid. @@ -14,26 +14,26 @@ The report names the resolved catalog and host. ## Checks -[`doctor_cmd`](../../../src/main.rs#L988-L1130) is the implementation authority. +[`doctor_cmd`](../../../src/main.rs) is the implementation authority. It performs these check groups: - **Environment:** The required runtime tools are available - ([source](../../../src/main.rs#L997-L1003)). + ([source](../../../src/main.rs)). - **Supervision:** The host mode matches the caller's request - ([source](../../../src/main.rs#L1005-L1028); [tests](../../../tests/doctor.rs#L40-L92)). + ([source](../../../src/main.rs); [tests](../../../tests/doctor.rs)). - **Catalog:** The selected catalog has no discovery errors - ([source](../../../src/main.rs#L1030-L1040)). + ([source](../../../src/main.rs)). - **Runtime:** Runtime state is readable, and each active local task is alive - ([Doctor source](../../../src/main.rs#L1041-L1102); [unified runtime view](../../../src/run.rs#L687-L700)). + ([Doctor source](../../../src/main.rs); [unified runtime view](../../../src/run.rs)). The PTY probe has a fixed deadline and closed standard input - ([probe source](../../../src/run.rs#L443-L458); [tests](../../../tests/doctor.rs#L95-L194)). + ([probe source](../../../src/run.rs); [tests](../../../tests/doctor.rs)). - **Presence:** Each active local declaration has maintained presence - ([source](../../../src/main.rs#L1103-L1121)). + ([source](../../../src/main.rs)). - **Outbound messages:** Each local declaration has a valid sender ledger. An unavailable ledger is healthy because the agent has not sent a message. An invalid ledger reports that the agent cannot send. Doctor does not create or repair sender state. - **Retirement:** Every task record for a retired local declaration is absent - ([source](../../../src/main.rs#L1068-L1089); [tests](../../../tests/doctor.rs#L196-L302)). + ([source](../../../src/main.rs); [tests](../../../tests/doctor.rs)). - **Suspension:** No task of a suspended local declaration is alive. A dead record is accepted only when the task or agent is keep-pinned; presence is not required. This remains distinct from retirement's full-record absence @@ -44,9 +44,9 @@ It performs these check groups: Doctor writes a human-readable report for the resolved catalog and host. Each failure has a subject label and available detail. Doctor returns zero only when it diagnoses no problem. The formatter and result are in -[`report_check`](../../../src/main.rs#L1165-L1175) and -[`doctor_cmd`](../../../src/main.rs#L1124-L1129). The -[clean-path test](../../../tests/native_only.rs#L243-L369) proves that the CLI +[`report_check`](../../../src/main.rs) and +[`doctor_cmd`](../../../src/main.rs). The +[clean-path test](../../../tests/native_only.rs) proves that the CLI does not need a predecessor transport. ## Open questions @@ -55,13 +55,13 @@ These are implementation gaps or unsettled product choices, not requirements: - **Malformed presence:** Unreadable or invalid fresh presence becomes `offline`. Should Doctor report corruption separately? - ([source](../../../src/status.rs#L70-L95)) + ([source](../../../src/status.rs)) - **Exec enumeration bound:** Exec record enumeration is synchronous file-system work. It is outside the PTY deadline. What deadline should cover the full - diagnosis? ([source](../../../src/exec_backend.rs#L190-L217)) + diagnosis? ([source](../../../src/exec_backend.rs)) - **Tool dependency:** Doctor requires `pty` when a host has only exec tasks. Should the declared tasks select the required tools? - ([source](../../../src/main.rs#L997-L1003)) + ([source](../../../src/main.rs)) - **Machine output:** Doctor has no stable machine-readable report. Should it provide structured findings in addition to the exit status? - ([CLI surface](../../../src/main.rs#L240-L252)) + ([CLI surface](../../../src/main.rs)) diff --git a/docs/vrs/05-harness-state/.experiments/2026-08-23-claude-batched-permission.md b/docs/vrs/05-harness-state/.experiments/2026-08-23-claude-batched-permission.md index 33830528..9d3df3ae 100644 --- a/docs/vrs/05-harness-state/.experiments/2026-08-23-claude-batched-permission.md +++ b/docs/vrs/05-harness-state/.experiments/2026-08-23-claude-batched-permission.md @@ -1,5 +1,7 @@ # Claude batched tool calls under an open permission prompt +## Question + Date: 2026-08-23. Binary: Claude Code 2.1.237 (Fable 5 bundle), Linux, driven under `pty`. Purpose: take the capture DQ-H1 (and #268 §C) said was missing — a batch where one call needs permission — and settle whether the shipped blocked-exit rule @@ -21,7 +23,7 @@ line and always exit 0, for: `PreToolUse`, `PostToolUse`, `PermissionRequest`, ` Four short turns total, ≈$0.61. -## Measured sequences +## Result **Run 2, `-p`, batch allowlisted-first** (`echo a` + `touch scratch-file.txt`): @@ -54,7 +56,7 @@ Execution is strictly serial: the first call's `Post` precedes the second call's registered): after `PreToolUse` + `PermissionRequest`, denial produced **zero further events** — no `PostToolUse`, no `Stop`, no `PermissionDenied`. The turn ends silently. -## Findings +## Conclusion 1. **Execution serializes around an open permission prompt.** In both orderings no hook event fires while a prompt is up — a parallel-batched allowlisted call waits out the grant. The diff --git a/docs/vrs/05-harness-state/.experiments/2026-08-23-opencode-surface.md b/docs/vrs/05-harness-state/.experiments/2026-08-23-opencode-surface.md index cff25654..c61bd2e4 100644 --- a/docs/vrs/05-harness-state/.experiments/2026-08-23-opencode-surface.md +++ b/docs/vrs/05-harness-state/.experiments/2026-08-23-opencode-surface.md @@ -1,11 +1,13 @@ # OpenCode's server surface, measured for the driver +## Question + 2026-08-23, OpenCode 1.18.19 (`/home/schickling/.nix-profile/bin/opencode`), Linux, isolated `XDG_DATA_HOME`/`XDG_CONFIG_HOME`, headless `opencode serve --port 43123 --print-logs`. The free anonymous model (`opencode/big-pickle`) answered prompts with no credentials, so every claim below is reproducible without an API key. -## What was established +## Result **The TUI is a server.** `opencode` (TUI, the default command) starts a server on `--port` / `--hostname` exactly like `opencode serve`; `opencode attach ` exists for the reverse @@ -48,7 +50,7 @@ Because the server serves its own OpenAPI document, a live `/doc` subset check a covers the shape while a version list covers the semantics — the hybrid of the Codex `SUPPORTED_CODEX_CLI_VERSIONS` pattern and the pi type-check pattern. -## Reproduction +## Method ``` XDG_DATA_HOME=$S/data XDG_CONFIG_HOME=$S/config opencode serve --port 43123 --print-logs @@ -112,7 +114,12 @@ its `parts` again into that message — a resend after a *transiently failed* re text inside the message, not the message. The pump's read-back-before-resend rule is therefore load-bearing, not just polite. -## Limits +## Conclusion + +The measured server, SSE, receipt, and version-gate surfaces support an +evented OpenCode driver; the remaining constraints are: + +### Limits - A v2 surface (`/api/event`, `/api/session/{id}/wait`, `permission.v2.*`) coexists with the legacy one probed here; the driver pins the legacy arms via the `/doc` check. diff --git a/docs/vrs/06-omp-driver/.experiments/2026-09-05-omp-provider-credential-rejection.md b/docs/vrs/06-omp-driver/.experiments/2026-09-05-omp-provider-credential-rejection.md index 1fbe8f02..c918ad93 100644 --- a/docs/vrs/06-omp-driver/.experiments/2026-09-05-omp-provider-credential-rejection.md +++ b/docs/vrs/06-omp-driver/.experiments/2026-09-05-omp-provider-credential-rejection.md @@ -93,7 +93,7 @@ Four facts fall out of that table. end is unambiguous in the other direction: `stopReason: "stop"` with no error fields is the recovery edge. -## Consequence +## Conclusion The omp extension forwards omp's own words — the bounded prose plus `errorId` — on a `type: "turn"` frame at every turn that actually ended, and st2 owns the @@ -102,3 +102,9 @@ verdict, as it does for every other producer: `Source::TurnResult` + on the observed-state record. `errorStatus` is deliberately NOT on the wire: it classifies nothing this record needs, and it is already inside the prose omp puts in `errorMessage` (`"403 {…}"`). + +## VRS Impact + +The result grounds omp provider-auth classification in the `errorId` bitfield, +including the negative capacity and policy flags, rather than HTTP status or +error prose. diff --git a/docs/vrs/ontology.md b/docs/vrs/ontology.md index fa2e88e9..7e588167 100644 --- a/docs/vrs/ontology.md +++ b/docs/vrs/ontology.md @@ -15,28 +15,28 @@ Terms without an independent authority are deliberately absent. The actor st2 models. Authority: [Agent Spec overview](https://github.com/compoundingtech/evals/blob/main/AGENT-SPEC.md#canonical-agent-specification); -[R10 agent-only identity](requirements.md#L98-L99) +[R10 agent-only identity](requirements.md) ### agent declaration The authored KDL representation of one agent. Authority: [Agent Spec discovery and declaration shape](https://github.com/compoundingtech/evals/blob/main/AGENT-SPEC.md#discovery-identity-and-host); -[R02 canonical KDL](requirements.md#L36-L45) +[R02 canonical KDL](requirements.md) ### agent runtime A running instance of an agent declaration. -Authority: [declared runtime vision](vision.md#L16-L26); -[`AgentSpec` runtime model](../../crates/agent-spec/src/spec.rs#L24-L50) +Authority: [declared runtime vision](vision.md); +[`AgentSpec` runtime model](../../crates/agent-spec/src/spec.rs) ### agent task A terminal-backed or terminal-free unit declared for an agent. Authority: [Agent Spec task contract](https://github.com/compoundingtech/evals/blob/main/AGENT-SPEC.md#compact-and-explicit-tasks); -[`Task`](../../crates/agent-spec/src/spec.rs#L121-L148) +[`Task`](../../crates/agent-spec/src/spec.rs) ### runtime resource target @@ -135,13 +135,13 @@ Authority: [R25 bounded presentation](requirements.md); The selected folder containing agent declarations and catalog-backed state. Authority: [Agent Spec catalog boundary](https://github.com/compoundingtech/evals/blob/main/AGENT-SPEC.md#discovery-identity-and-host); -[catalog selection](../../src/main.rs#L867-L927) +[catalog selection](../../src/main.rs) ### catalog root The filesystem path selected as the catalog for a command. -Authority: [catalog path resolution](../../src/main.rs#L867-L927) +Authority: [catalog path resolution](../../src/main.rs) ### supervisor scope @@ -156,15 +156,15 @@ Authority: [R31 reachable restart bounds](requirements.md); The agent assigned host-local health observation, recovery, and escalation. -Authority: [R04 root supervision](requirements.md#L51-L54); -[vision](vision.md#L21-L22) +Authority: [R04 root supervision](requirements.md); +[vision](vision.md) ### control plane The replaceable `st2 up` process that reconciles host-local work. -Authority: [R11 control-plane replacement safety](requirements.md#L83-L88); -[`up_loop`](../../src/run.rs#L1440-L1452) +Authority: [R11 control-plane replacement safety](requirements.md); +[`up_loop`](../../src/run.rs) ### supervisor run @@ -178,21 +178,21 @@ Authority: [R31 reachable restart bounds](requirements.md); The agent reference carried by a declaration for supervisory routing. -Authority: [`AgentSpec::supervisor`](../../crates/agent-spec/src/spec.rs#L24-L50); +Authority: [`AgentSpec::supervisor`](../../crates/agent-spec/src/spec.rs); [Agent Spec field](https://github.com/compoundingtech/evals/blob/main/AGENT-SPEC.md#complete-declaration-shape) ### presence The agent-authored availability signal read from catalog-backed state. -Authority: [`status::State`](../../src/status.rs#L24-L45); -[R08 catalog observability](requirements.md#L92-L95) +Authority: [`status::State`](../../src/status.rs); +[R08 catalog observability](requirements.md) ### session state st2's runtime observation of whether a task record is alive. -Authority: [`reconcile::Session`](../../src/reconcile.rs#L16-L26) +Authority: [`reconcile::Session`](../../src/reconcile.rs) ### observed harness state @@ -226,7 +226,7 @@ version gate, API gate, event stream, state seed, delivery, or read-back. A stage owns its bounded reasons and sources; a reason paired with another stage is unknown evidence rather than a best-effort match. -Authority: [native driver diagnostic snapshot](05-harness-state/spec.md#native-driver-diagnostic-snapshot-ohs-r11ohs-r15) +Authority: [native driver diagnostic snapshot](05-harness-state/spec.md#native-driver-diagnostic-snapshot-ohs-r11ohs-r16) ### diagnostic evidence age @@ -234,7 +234,7 @@ Reader-derived elapsed time since the current native driver diagnostic was observed. The origin timestamp is durable; age is a projection and never file mtime. -Authority: [native driver diagnostic snapshot](05-harness-state/spec.md#native-driver-diagnostic-snapshot-ohs-r11ohs-r15) +Authority: [native driver diagnostic snapshot](05-harness-state/spec.md#native-driver-diagnostic-snapshot-ohs-r11ohs-r16) ### harness context record @@ -306,7 +306,7 @@ One concept, one edge: a Resource is reached through a [Resource binding](#resource-binding). The [linked record](#linked-record-retired) plane that once shared the word is retired. -Authority: [R20 portable Resource bindings](requirements.md#L161-L168); +Authority: [R20 portable Resource bindings](requirements.md); [issue #61 resolution](https://github.com/compoundingtech/st2/issues/61) ### Resource binding @@ -321,8 +321,8 @@ healthy work. A binding says what an agent *is for* — the work it reads and the durable state carriers it owns. It is not a record of what the agent produced. -Authority: [`Resource`](../../crates/agent-spec/src/spec.rs#L239-L245) — the -live contract; [R20](requirements.md#L161-L168); [R21](requirements.md#L169-L172). +Authority: [`Resource`](../../crates/agent-spec/src/spec.rs) — the +live contract; [R20](requirements.md); [R21](requirements.md). The canonical [Agent Spec Resource bindings](https://github.com/compoundingtech/evals/blob/main/AGENT-SPEC.md#agent-spec-resource-bindings) anchor still describes the pre-#307 envelope of name and `uri` only, and would reject the required `reason`; it is pending sync (07-resource DQ-R8). @@ -395,7 +395,7 @@ for [Resource bindings](#resource-binding): a binding names a carrier by URI and the carrier's bytes live here. `dev.schickling.agent-goal:///` realizes as `resources/goal.md`; `decision-tree:///` realizes as `resources/context/decisions/`. Identity is the URI; the path is realization, -and st2 does not resolve one into the other ([R20](requirements.md#L161-L168)). +and st2 does not resolve one into the other ([R20](requirements.md)). Authority: [`message::with_resolved_state_dir`](../../src/message.rs); [07-resource spec](07-resource/spec.md) @@ -412,7 +412,7 @@ Addressed as a [Resource binding](#resource-binding) under the scheme `st2 context`; resolving the scheme is a catalog's choice via an optional [Resource Profile](07-resource-profile/requirements.md), not something st2 ships. -Authority: [R09 state continuity](requirements.md#L131-L132); +Authority: [R09 state continuity](requirements.md); [`context`](../../src/context.rs); [decision 0012](.decisions/0012-working-state-is-a-declared-carrier.md) @@ -495,14 +495,14 @@ Authority: [R27 typed agent desired state](requirements.md) Comparing declared host-local work with observed runtime state to produce a plan. -Authority: [`reconcile`](../../src/reconcile.rs#L1-L10); -[reconcile pass](../../src/run.rs#L1007-L1017) +Authority: [`reconcile`](../../src/reconcile.rs); +[reconcile pass](../../src/run.rs) ### materialization Rendering catalog-declared content into an agent workspace. -Authority: [`materialize_catalog`](../../src/materialize.rs#L837-L850) +Authority: [`materialize_catalog`](../../src/materialize.rs) ### message @@ -558,7 +558,7 @@ Authority: [MESSAGE-R07 and MESSAGE-R08](03-message/requirements.md); The terminal notification that makes an agent aware of unread messages. -Authority: [DING module contract](../../src/ding/mod.rs#L1-L14) +Authority: [DING module contract](../../src/ding/mod.rs) ### stream @@ -644,16 +644,16 @@ outside this family entirely. ## Collision rules -- Qualify **root** as [root agent](requirements.md#L51-L54) or - [catalog root](../../src/main.rs#L867-L927). Bare *root* does not identify +- Qualify **root** as [root agent](requirements.md) or + [catalog root](../../src/main.rs). Bare *root* does not identify which concept is meant. -- Qualify **supervisor** as [control plane](requirements.md#L83-L88) or - [declared supervisor](../../crates/agent-spec/src/spec.rs#L24-L50). +- Qualify **supervisor** as [control plane](requirements.md) or + [declared supervisor](../../crates/agent-spec/src/spec.rs). - Use **supervisor run** for one live control-plane incarnation and **declared supervisor** for the agent reference used in supervisory routing. **Control plane** names the replaceable process concept, not an owning run. -- Use [presence](../../src/status.rs#L24-L45) for the agent-authored signal and - [session state](../../src/reconcile.rs#L16-L26) for runtime liveness. Avoid +- Use [presence](../../src/status.rs) for the agent-authored signal and + [session state](../../src/reconcile.rs) for runtime liveness. Avoid bare *agent status* when either could be meant. - Use [observed harness state](05-harness-state/requirements.md) for the driver-observed activity signal. It is a third axis beside presence and @@ -707,8 +707,8 @@ outside this family entirely. - Use [compaction trigger](#compaction-trigger) for the harness's own stated reason. `unknown` is a value, not a failure, and st2 never infers a trigger a harness did not state. -- Use [message](../../src/message.rs#L26-L46) for the durable record and - [DING](../../src/ding/mod.rs#L1-L14) for its terminal notification. +- Use [message](../../src/message.rs) for the durable record and + [DING](../../src/ding/mod.rs) for its terminal notification. - Qualify **event**: a bare *event* in stream context is the durable [event](04-stream/requirements.md) record; the R13–R15 filesystem-watcher usage is a **watcher event**. New requirements text keeps the qualification. @@ -724,7 +724,7 @@ outside this family entirely. **seat** is not canonical st2 language. Choose by meaning: [agent](https://github.com/compoundingtech/evals/blob/main/AGENT-SPEC.md#canonical-agent-specification) for the actor, [agent declaration](https://github.com/compoundingtech/evals/blob/main/AGENT-SPEC.md#complete-declaration-shape) -for the catalog entry, and [agent runtime](vision.md#L16-L24) for a running +for the catalog entry, and [agent runtime](vision.md) for a running instance. For admission, use an already-authoritative admission term only if one exists; this index does not canonize one. diff --git a/docs/vrs/spec.md b/docs/vrs/spec.md index 2eb58994..67cd2f9d 100644 --- a/docs/vrs/spec.md +++ b/docs/vrs/spec.md @@ -695,7 +695,7 @@ validate ──► materialize ──► host-local st2 scheduler/reconciler successful launch spends its declared budget. Each completed pass supplies the exact task IDs it proved alive; uninterrupted observed liveness may forgive a fail-mode budget according to the - [restart field contract](./02-agent-spec/spec.md#f12), while an unobserved task + [restart field contract](./02-agent-spec/spec.md#f12-future-policy-r31), while an unobserved task loses accrued recovery uptime. A pass that exits before execution neither supplies a liveness observation nor closes the accounting pass. [PR #191](https://github.com/compoundingtech/st2/pull/191) provides cadence,