Skip to content
Draft
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
85 changes: 58 additions & 27 deletions AGENT-SPEC.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Canonical st2 agent specification

This is the sole agent-authoring specification for this repository. It is pinned to st2
[`9887b2842222def0838c2cd82e6c24c218f7efa6`](https://github.com/compoundingtech/st2/commit/9887b2842222def0838c2cd82e6c24c218f7efa6)
(`0.1.0`, source `9887b28`). It documents the hand-authored KDL accepted at that commit. Do not infer
[`c6846f6239329f0803142afc06c15a07b93937c1`](https://github.com/compoundingtech/st2/commit/c6846f6239329f0803142afc06c15a07b93937c1)
(`0.1.0`, source `c6846f6`). It documents the hand-authored KDL accepted at that commit. Do not infer
additional fields or commands from older corpus fixtures.

st2 runs long-lived `service` agents made of interactive `pty` tasks and terminal-free `exec` tasks.
Expand Down Expand Up @@ -45,6 +45,7 @@ agent "<identity>" {
supervisor "<host>.<supervisor-identity>"
retired #false
keep #false
lifecycle "service"

restart {
attempts 3
Expand All @@ -57,7 +58,7 @@ agent "<identity>" {
ST_AGENT "<host>.<identity>"
}

command #"<interactive harness command>"#
argv "<interactive-harness>" "<argument>"
ding

render {
Expand All @@ -82,9 +83,11 @@ Supported agent children are:
| `supervisor "…"` | Optional bare identity or full bus id for crash-loop routing. |
| `retired #true` | Decommission the declaration on the next reconciliation. Edit this flag; do not delete a live declaration to retire it. |
| `keep #true` | Freeze dead evidence and suppress collection/restart for every task; retirement still stops live tasks. |
| `lifecycle "service"|"adopt-only"` | Reconciliation policy for the compact task. Explicit tasks declare their own policy. |
| `restart { … }` | Optional service restart policy. |
| `env { KEY "value" }` | Environment inherited by the compact agent task and sidecars. |
| `command "…"` | Compact interactive task named `agent`. |
| `argv "program" "arg"…` | Preferred compact interactive task named `agent`; direct program invocation with preserved argument boundaries. |
| `command "…"` | Compact interactive shell task named `agent`; use only when shell syntax is intentional. |
| `ding` | Compact native DING sidecar named `ding`. |
| `pty "name" { … }` | Explicit interactive task. |
| `exec "name" { … }` | Explicit non-interactive task. |
Expand All @@ -102,30 +105,33 @@ rely on that permissiveness.

## Compact and explicit tasks

The canonical compact pair:
The canonical compact pair uses direct argv:

```kdl
command #"<harness command>"#
argv "<harness>" "<argument>"
ding
```

lowers to an interactive `pty "agent"` and a non-interactive `exec "ding"` sidecar. Do not declare
both `command` and `pty "agent"`, or both `ding` and `exec "ding"`.
both compact `argv` and compact `command`; do not combine either compact launch with `pty "agent"`;
and do not declare both `ding` and `exec "ding"`.

The compact task has id equal to the agent bus id, tag `role=agent`, and inherited agent environment. The
derived DING task has id `<bus-id>.ding` and inherited environment. A service is runnable only when at least
one authored task has a command; the derived sidecar alone is insufficient, including on a retired
declaration. Task names are sorted lexically after lowering.
one authored task has exactly one `argv` or `command`; `argv` must have a non-empty program element. The
derived sidecar alone is insufficient, including on a retired declaration. Task names are sorted lexically
after lowering.

Use explicit tasks only when the agent needs an additional managed process or task-specific
configuration:

```kdl
pty "agent" {
id "<host>.<identity>"
command #"<interactive harness command>"#
argv "<interactive-harness>" "<argument>"
cwd "/absolute/workspace/or/$CATALOG/path"
keep #false
lifecycle "service"
tags role="agent" purpose="subject"
env {
ST_AGENT "<host>.<identity>"
Expand All @@ -137,18 +143,25 @@ exec "helper" {
command #"exec ./long-running-helper"#
cwd "/absolute/workspace/or/$CATALOG/path"
keep #false
lifecycle "adopt-only"
tags purpose="fixture"
env {
EXAMPLE "value"
}
}
```

Each explicit task supports only `id`, `command`, `cwd`, `keep`, `tags`, and `env`. A nameless task is a
validation error. Agent-level environment is parsed before tasks and inherited independent of declaration
order; task-level values override it. A missing `cwd` falls back to agent workspace, then the spec directory.
Commands run through `sh -c`. Interactive harnesses belong in `pty`; terminal-free daemons and helpers belong
in `exec`.
Each explicit task supports only `id`, `argv`, `command`, `cwd`, `keep`, `lifecycle`, `tags`, and `env`.
A nameless task, empty `argv`, or task carrying both `argv` and `command` is a validation error. Agent-level
environment is parsed before tasks and inherited independent of declaration order; task-level values override
it. A missing `cwd` falls back to agent workspace, then the spec directory.

`argv` resolves a bare element zero through the task environment's `PATH`, expands st2 environment references
in each element, preserves argument boundaries, and launches the program directly without a shell. `command`
is opaque shell source run verbatim under `sh -c`; use it only for intentional shell behavior such as pipes,
redirections, compound commands, or shell expansion. Writing `exec` inside `command` can remove the inner
shell after it interprets that source, but it does not turn the declaration into direct argv. Interactive
harnesses belong in `pty`; terminal-free daemons and helpers belong in `exec`.

Interactive tasks run detached through the PTY runtime with stable lifecycle ids. Exec tasks allocate no
terminal, run in their own detached process group, append diagnostics below `<catalog>/logs/`, and retain one
Expand All @@ -159,7 +172,8 @@ what stops them.
## Environment and expansion

Expansion recognizes `$VAR`, `${VAR}`, and `$$` for a literal dollar. Unset variables remain literal. Task
environment values, tags, and cwd expand before spawn; the command remains opaque for its `sh -c`.
environment values, tags, cwd, and each direct argv element expand before spawn; command remains opaque for
its `sh -c`.

st2 defaults `CATALOG` and `ST_ROOT` to the catalog. Effective `PTY_ROOT` is a non-empty ambient value or
`<catalog>/pty`; authored task environment cannot override it. st2 supplies `ST_HOOKS` only when an installed
Expand Down Expand Up @@ -225,13 +239,21 @@ misclassified as an empty registry.
Reconciliation is per task:

- active and alive: adopt;
- missing: launch only that task;
- dead non-keep: preserve bounded diagnostics, collect, and restart according to policy;
- missing service task: launch only that task;
- dead non-keep service task: preserve bounded diagnostics, collect, and restart according to policy;
- dead keep: freeze evidence without collection or restart;
- missing or dead `adopt-only` task: report `held` without collection, replacement, or launch;
- retired and live: stop even when keep is set;
- retired and dead non-keep: final collection;
- unrendered or unrunnable: do not launch.

`lifecycle "adopt-only"` is a migration fence, not a restart policy. st2 may adopt an already-live generation
but may not create a missing generation or reap and replace a dead one. Declare it at agent level for the
compact task or inside each applicable explicit task. Explicit tasks that omit it independently default to
`service`; they do not inherit the compact-task policy. Deliberately restore `service` (or remove the field)
to authorize normal launch/replacement. `retired #true` is the separate teardown authority and takes
precedence.

`role` has no branch in lifecycle behavior. `supervisor` is used for `ST_SUPERVISOR` and best-effort crash-loop
routing. New exact Codex launches require verified installed hooks and workspace pretrust; already live/adopted
Codex tasks are not stopped by that launch gate.
Expand Down Expand Up @@ -267,7 +289,7 @@ agent "<identity>" {
ST_AGENT "<host>.<identity>"
}

command #"exec claude --model claude-sonnet-5 --effort medium --permission-mode bypassPermissions '<boot prompt>'"#
argv "claude" "--model" "claude-sonnet-5" "--effort" "medium" "--permission-mode" "bypassPermissions" "<boot prompt>"
ding

render {
Expand Down Expand Up @@ -334,7 +356,7 @@ agent "<identity>" {
ST_AGENT "<host>.<identity>"
}

command #"exec codex --model gpt-5.6-sol -c 'model_reasoning_effort="medium"' --dangerously-bypass-approvals-and-sandbox --dangerously-bypass-hook-trust '<boot prompt>'"#
argv "codex" "--model" "gpt-5.6-sol" "-c" "model_reasoning_effort=\"medium\"" "--dangerously-bypass-approvals-and-sandbox" "--dangerously-bypass-hook-trust" "<boot prompt>"
ding

render {
Expand Down Expand Up @@ -409,7 +431,7 @@ native DING wakeups. Structured exceptions are in `evidence/harness-exclusions.t
At `agents/example/worker/agent.kdl`, path-derived identity and host make this the smallest valid service:

```kdl
agent { command "true" }
agent { argv "true" }
```

At the pinned source it validates as one agent with zero errors and warnings. Production declarations should
Expand All @@ -421,7 +443,7 @@ example, create every `$CATALOG`-rooted workspace it names.

## Free authoring gate

These commands parse and materialize without starting a model seat:
These commands parse and materialize without starting a model agent:

```sh
st2 validate --catalog "$CATALOG" --host <host> --strict
Expand All @@ -432,8 +454,17 @@ Inspect the declaration, every referenced template, and every workspace destinat
materialization command. Materialization is byte-idempotent and does not imply hook installation. Starting
the network is a separate, explicitly authorized action.

For source `9887b28`, the accepted Linux executable has SHA256
`d49d44fd4f3f6f655455c212353a469fefa956082bedf22163deb767d8a36a0d`; its published archive has SHA256
`32ee103bd17ccb3e155ac63d816a3906c2470a3c98e3cc04b56e5a67138b9927`. `bin/check-corpus.sh` verifies
the variable-age version contract, exact installed binary, embedded full source commit, strict semantic
validation, fixture resets, and the rest of the model-free corpus gate before an eval may run.
For source `c6846f6239329f0803142afc06c15a07b93937c1`, the accepted artifact is the immutable Nix package:

- source `flake.lock` byte SHA256
`aa547f85b21a8a8787adaa9f2a3ad37d55246d355248388e2ec38bd85a830141`;
- derivation `/nix/store/nrhfzsarya6ny7wdmy64i9vwgax965a8-st2-0.1.0.drv`;
- output `/nix/store/z4wj1y20wq00n02gpknnz45fdi2kyc7h-st2-0.1.0`, NAR
`sha256-w3OSHKt96U0aWMzW2FHom6Ii1NvikdXqmJ6wJ1xGJNA=` (5,147,208 bytes);
- version prefix `st2 0.1.0+c6846f6` and packaged executable SHA256
`25276f30a9cfb287e1a9f72318fada2936402343e958160d46a0a7270ab2db2b`.

`bin/check-st2-package-provenance.sh` verifies the whole chain and selects that exact binary rather than an
ambient `st2`. Its mutation gate rejects a same-version substitute. `bin/check-st2-pin-consistency.sh` keeps
this active spec, the README, and executable preflight aligned. `bin/check-corpus.sh` runs semantic validation
and every model-free gate with the pinned package before an eval may run.
54 changes: 33 additions & 21 deletions CATALOG.md
Original file line number Diff line number Diff line change
@@ -1,26 +1,34 @@
<!-- Generated by bin/generate-catalog.sh from cell KDL and evidence/*.tsv. Do not edit. -->
# Current eval catalog and evidence

This is the sole current corpus surface: **45 included cells** (14 model-free,
31 model-backed) and **4 retired exclusions**. Inventory fields are derived from the active
This is the sole current corpus surface: **53 included cells** (18 model-free,
35 model-backed) and **4 retired exclusions**. Inventory fields are derived from the active
KDL and executable judge scripts. Latest accepted PASS evidence remains distinct from the append-only run
history: a failed last run is visible without being advertised as accepted evidence, and cells with no
structured run row say so explicitly.

Every model launch is explicitly pinned by `bin/check-model-policy.sh`: Claude uses
`claude-sonnet-5` at medium effort and Codex uses `gpt-5.6-sol` at medium reasoning effort.
Every bus-connected model seat is mechanically checked for the event-first DING lifecycle and its
Every bus-connected model agent is mechanically checked for the event-first DING lifecycle and its
harness-native loader plus canonical hook file.

## Included overnight inventory

| Cell | Harness | Model(s) / effort | Model seats | Cost | Timeout | Held-out judges | Latest accepted PASS | Last recorded run |
| Cell | Harness | Model(s) / effort | Model agents | Cost | Timeout | Held-out judges | Latest accepted PASS | Last recorded run |
|---|---|---|---:|---|---|---:|---|---|
| `agent-new-bundle-contract` | model-free | — | 0 | none | `60s` | 6 | — | **NO STRUCTURED RUN** |
| `agent-new-confirmation-contract` | model-free | — | 0 | none | `30s` | 4 | — | **NO STRUCTURED RUN** |
| `agent-new-creation-lifecycle` | model-free | — | 0 | none | `180s` | 5 | — | **NO STRUCTURED RUN** |
| `agent-new-interview` | Claude | `claude-sonnet-5` / medium | 1 | low | `300s` | 5 | — | **NO STRUCTURED RUN** |
| `agent-new-interview-hard-constraint` | Claude | `claude-sonnet-5` / medium | 1 | low | `300s` | 4 | — | **NO STRUCTURED RUN** |
| `agent-new-interview-investigation` | Claude | `claude-sonnet-5` / medium | 1 | low | `300s` | 4 | — | **NO STRUCTURED RUN** |
| `agent-new-interview-small-fix` | Claude | `claude-sonnet-5` / medium | 1 | low | `300s` | 4 | — | **NO STRUCTURED RUN** |
| `canonical-agent-runtime-smoke` | model-free | — | 0 | none | `30s` | 7 | — | **NO STRUCTURED RUN** |
| `context-resource-continuity` | model-free | — | 0 | none | `90s` | 4 | — | **NO STRUCTURED RUN** |
| `crash-ding` | mixed | `claude-sonnet-5+gpt-5.6-sol` / medium | 4 | high | `180s` | 5 | — | **NO STRUCTURED RUN** |
| `ding-mode` | Claude | `claude-sonnet-5` / medium | 2 | medium | `1200s` | 4 | — | **NO STRUCTURED RUN** |
| `ding-reply` | Claude | `claude-sonnet-5` / medium | 1 | low | `420s` | 2 | — | **NO STRUCTURED RUN** |
| `docs` | Claude | `claude-sonnet-5` / medium | 3 | high | `1200s` | 5 | — | **NO STRUCTURED RUN** |
| `docs` | Claude | `claude-sonnet-5` / medium | 2 | medium | `1200s` | 5 | — | **NO STRUCTURED RUN** |
| `exec-lifecycle-logging` | model-free | — | 0 | none | `90s` | 5 | — | **NO STRUCTURED RUN** |
| `feature-fit` | Claude | `claude-sonnet-5` / medium | 2 | medium | `1200s` | 5 | — | **NO STRUCTURED RUN** |
| `fork-in-the-road` | Claude | `claude-sonnet-5` / medium | 4 | high | `1200s` | 5 | — | **NO STRUCTURED RUN** |
Expand Down Expand Up @@ -74,30 +82,34 @@ while the last-run column makes a recorded failure distinct from a cell with no
- `clean-compose` — retired composition-overlay discriminator
- `compose-config-load` — retired composition/config-loader discriminator
- `compose-global-skill` — retired composition/global-skill discriminator
- `team-standup` — retired runtime seat-generation discriminator
- `team-standup` — retired runtime agent-generation discriminator

`hook-integrity` is not excluded: it is now a model-free, current-native
`st2 up --materialize-only` hook installation and idempotency test.

## Harness hook exclusions

- `context-resource-continuity` / `cell` — deterministic native context/resource restart probe; no harness seat
- `agent-new-bundle-contract` / `cell` — deterministic semantic-intent lowering and mutation probe; no harness agent
- `agent-new-confirmation-contract` / `cell` — deterministic pre-threshold human-confirmation boundary; no harness agent
- `agent-new-creation-lifecycle` / `cell` — successor-gated real Axe lifecycle matrix with no provider model
- `canonical-agent-runtime-smoke` / `cell` — deterministic canonical Agent Spec boot, kickoff, reply, and teardown probe; no harness agent
- `context-resource-continuity` / `cell` — deterministic native context/resource restart probe; no harness agent
- `docs` / `judge:cold-reader` — one-shot offline Claude print grader; no bus identity, DING, or hook surface
- `exec-lifecycle-logging` / `cell` — deterministic native exec lifecycle probe; no harness seat
- `hook-integrity` / `cell` — deterministic native hook-installation probe; no live harness seat
- `host-lock-health-negatives` / `cell` — deterministic native doctor negative probe; no harness seat
- `pty-attach-only` / `cell` — deterministic PTY dead-attach policy probe; no harness seat
- `pty-send-peek` / `cell` — deterministic PTY transport probe; no harness seat
- `presence-ding-matrix` / `cell` — deterministic native presence and DING probe; no harness seat
- `reconcile-retire-keep` / `cell` — deterministic native reconcile lifecycle probe; no harness seat
- `render-target-safety` / `cell` — deterministic native render-policy probe; no harness seat
- `strict-validation-json` / `cell` — deterministic native validation-diagnostics probe; no harness seat
- `st2-doctor-structure` / `cell` — deterministic catalog-health probe; no harness seat
- `st2-network` / `cell` — deterministic catalog/message round-trip probe; no harness seat
- `exec-lifecycle-logging` / `cell` — deterministic native exec lifecycle probe; no harness agent
- `hook-integrity` / `cell` — deterministic native hook-installation probe; no live harness agent
- `host-lock-health-negatives` / `cell` — deterministic native doctor negative probe; no harness agent
- `pty-attach-only` / `cell` — deterministic PTY dead-attach policy probe; no harness agent
- `pty-send-peek` / `cell` — deterministic PTY transport probe; no harness agent
- `presence-ding-matrix` / `cell` — deterministic native presence and DING probe; no harness agent
- `reconcile-retire-keep` / `cell` — deterministic native reconcile lifecycle probe; no harness agent
- `render-target-safety` / `cell` — deterministic native render-policy probe; no harness agent
- `strict-validation-json` / `cell` — deterministic native validation-diagnostics probe; no harness agent
- `st2-doctor-structure` / `cell` — deterministic catalog-health probe; no harness agent
- `st2-network` / `cell` — deterministic catalog/message round-trip probe; no harness agent
- `targeted-reconcile-isolation` / `cell` — deterministic selected reconcile isolation probe; no bus-connected agent
- `two-networks-coexist` / `cell` — deterministic catalog-isolation probe; no harness seat
- `two-networks-coexist` / `cell` — deterministic catalog-isolation probe; no harness agent

These **15** rows are generated from structured exclusions. A cell-level row is required for
These **19** rows are generated from structured exclusions. A cell-level row is required for
every derived model-free cell. The one-shot docs cold reader is a model-backed grader, but not a bus-connected
agent: it has no st2 identity, DING sidecar, or lifecycle hooks.

Expand All @@ -109,7 +121,7 @@ Run the complete model-free preflight:
bin/check-corpus.sh
```

Preview the exact lexical run order, harness, model, effort, seat count, cost band, and timeout:
Preview the exact lexical run order, harness, model, effort, agent count, cost band, and timeout:

```sh
bin/overnight.sh --dry-run
Expand Down
Loading