Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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**
Expand Down Expand Up @@ -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:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
19 changes: 15 additions & 4 deletions docs/vrs/.delta/DELTA-004-stream-dedup-horizon.md
Original file line number Diff line number Diff line change
@@ -1,29 +1,40 @@
# 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
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
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.
Expand Down
16 changes: 12 additions & 4 deletions docs/vrs/.experiments/2026-08-18-pi-harness-integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand All @@ -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 |
|---|---|
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
25 changes: 18 additions & 7 deletions docs/vrs/02-agent-spec/requirements.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
44 changes: 22 additions & 22 deletions docs/vrs/02-agent-spec/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ core publication and do not claim managed admission.

## Field rules

<h3 id="f01">F01 Source form or path</h3>
### 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,
Expand All @@ -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).

<h3 id="f02">F02 Agent <code>id</code> and legacy <code>identity</code></h3>
### 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.
Expand Down Expand Up @@ -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).

<h3 id="f03">F03 <code>host</code></h3>
### 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,
Expand All @@ -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).

<h3 id="f04">F04 <code>type</code></h3>
### 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.
Expand All @@ -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).

<h3 id="f05">F05 <code>role</code></h3>
### F05 `role`

Update observable declaration metadata only. Do not change the fingerprint,
workspace files, notification state, or a healthy task.
Expand All @@ -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).

<h3 id="f06">F06 <code>workspace</code></h3>
### 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
Expand All @@ -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).

<h3 id="f07">F07 Resource <code>name</code> or <code>uri</code></h3>
### 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
Expand All @@ -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).

<h3 id="f08">F08 <code>render {}</code> operation, template, or resolved target</h3>
### 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
Expand All @@ -307,7 +307,7 @@ declaration.
Authoring: [pinned render contract][evals-render]. st2 implementation and
evidence: [materializer](../../../src/materialize.rs).

<h3 id="f09">F09 Task set: <code>pty</code>, <code>exec</code>, or compact <code>ding</code></h3>
### 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
Expand All @@ -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).

<h3 id="f10">F10 Task <code>name</code> or explicit <code>id</code></h3>
### 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.
Expand All @@ -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).

<h3 id="f11">F11 Spawn inputs</h3>
### 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
Expand All @@ -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).

<h3 id="f12">F12 Future policy (R31)</h3>
### F12 Future policy (R31)

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

<h3 id="f13">F13 <code>retired #true</code></h3>
### 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
Expand All @@ -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).

<h3 id="f14">F14 Compact agent fields</h3>
### 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;
Expand All @@ -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).

<h3 id="f15">F15 Provider and ignored fields</h3>
### F15 Provider and ignored fields

Core st2 ignores `harness`, `model`, `persona`, `permissions`, `transport`,
`strategy`, `meta`, and provider extensions. They do not change core equality,
Expand All @@ -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).

<h3 id="f16">F16 Invalid or incomplete state</h3>
### 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
Expand All @@ -464,7 +464,7 @@ source: [`RawSpec` and `AgentSpec`](../../../crates/agent-spec/src/spec.rs).
Evidence: [validation](../../../src/validate.rs) and
[reconciliation](../../../src/reconcile.rs).

<h3 id="f18">F18 <code>desired-state</code> and <code>reason</code></h3>
### 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
Expand Down Expand Up @@ -517,7 +517,7 @@ st2 source: [`AgentDesiredState`](../../../crates/agent-spec/src/spec.rs),
[authoring](../../../tests/agent_desired_state.rs), and
[planning](../../../tests/reconcile.rs).

<h3 id="f17">F17 Agent <code>name</code> and <code>description</code></h3>
### F17 Agent `name` and `description`

Update observable declaration and runtime presentation metadata only. Neither
field participates in agent ID, address routing, selection, authorization,
Expand Down Expand Up @@ -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.


<h3 id="f19">F19 Agent <code>stream</code></h3>
### F19 Agent `stream`

A `stream "<name>" {}` 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-<name>`. 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
Expand All @@ -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).

<h3 id="f20">F20 Agent <code>address</code></h3>
### 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
Expand Down
6 changes: 3 additions & 3 deletions docs/vrs/02-doctor/requirements.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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.
Loading
Loading