Skip to content

Latest commit

 

History

History
223 lines (191 loc) · 12 KB

File metadata and controls

223 lines (191 loc) · 12 KB

st2 specification

This document specifies st2's current implementation contract. It builds on requirements.md.

Status

Active. This is a concise map to the implementation and its evidence, not a replacement for the README, CLI help, KDL examples, or tests.

Scope

st2 validates a declared agent fleet, materializes agent workspaces, launches host-local work, adopts and supervises independently surviving tasks, and delivers messages. The agent grammar and harness-facing contract remain canonical in compoundingtech/evals/AGENT-SPEC.md.

Resource bindings (R20-R21)

An agent may directly declare zero or more generic Resource bindings:

resource "work" _tag="github-issue" uri="github-issue://example/project/123"

The positional name is an agent-local semantic role, _tag is the concrete type discriminator, and uri is the exact RFC 3986 absolute resource identity, preserved byte-for-byte without normalization. Declaration order has no meaning and binding names are unique within one agent. The public agent-spec read model preserves the bindings in name order across canonical KDL and the supported TOML/JSON forms. st2 agents --json projects the same descriptors for language-neutral inspection.

st2 validates only this portable envelope. It does not define downstream type schemas, resolve targets, infer authority from URI possession, or attach required/optional, access, readiness, or lifecycle semantics. Those concerns remain outside the generic binding contract. A Resource binding is declaration metadata and is absent from task launch targets; changing only Resource bindings adopts an already-live task without stop, replacement, or relaunch.

The unresolved-resource runtime discussion remains tracked in st2#60, read-oriented renderer integration in st2#61, and the portable Agent Spec envelope in evals#41.

Host-local scheduling and supervision

hand-authored KDL
       │
       ▼
validate ──► materialize ──► host-local st2 scheduler/reconciler
                                      │
                             ┌────────┴────────┐
                             ▼                 ▼
                        PTY / exec       DING sidecar
                             │                 │
                             └──── state + bus ┘
                                      ▲
                                      │
                            one intelligent root agent
                         observes · recovers · escalates
  • R01–R03: Fleet validation separates structural errors from selected-host runtime facts. Materialization is inspectable and host reconciliation starts only declarations pinned to the local host. Discovery is recursive: an explicit identity and host pair is authoritative independent of the declaration's path, whose parent remains the state/resource anchor. When either field is omitted, the path supplies defaults and mismatches remain diagnostic. Dot-prefixed folders, including .managed and .retired, are ordinary declaration space; only .git and .st2 directories at any depth, the catalog root's pty child, and a declaration parent's resources, archive, and inbox children are excluded. A resolved workspace-relative render destination has one coherent desired state across the active local fleet: byte-equivalent idempotent claims may share it, while incompatible claims fail every conflicting owner before the first workspace write. Targeted reconciliation checks the selected owner against the full fleet, so selection cannot bypass this ownership boundary.

  • R04: Each machine schedules and reconciles only its pinned work. The st2 loop is deterministic; exactly one declared root agent provides intelligent host-local supervision, bounded recovery, and escalation. Filesystem reads never wake reconciliation; only create, modify, rename, or remove events may wake it before the bounded timer.

  • R06: st2 passes the complete effective task definition to the underlying launcher so manual and supervised restarts are equivalent. Harness readiness that depends on a dynamically selected account belongs to that declared command. In particular, reconciliation never mutates an ambient Codex config before launch: the command may select an account-specific CODEX_HOME only after st2 starts it. st2 pretrust remains an explicit operator utility for commands that intentionally use the ambient Claude and Codex configs.

  • R07: Hook bundles are explicit, content-addressed, installed separately, and verified before materialization references them. Their receipts use the same resolved build identity as the binary's version surfaces for both hermetic package builds and source builds. Installation automatically accepts an ordered upgrade; --replace is the explicit exact-state authority for a downgrade, an unorderable build, or an unreadable receipt. Shipped hooks resolve Bash through PATH; the Nix package executes their integration gate with Bash and jq declared. Runtime materialization verifies the invoking binary's own content-addressed set, independent of which installed binary the receipt currently selects, so old and new supervisors can overlap during cutover. hooks verify-own exposes that read-only capability to package activation tooling.

  • R11: st2 up is a replaceable control plane, not the lifetime owner of its agents. Normal exit, forced termination, binary replacement, and restart leave every running agent PID and creation identity unchanged. The new control plane adopts those processes and starts only missing work; it does not duplicate them. Agent stop or retirement requires a separate explicit lifecycle action.

    Executable acceptance starts an agent, terminates st2 up normally and with a forced kill, verifies the agent remains alive and usable, replaces the st2 binary, starts the control plane again, and proves adoption with the same agent PID/creation identity and no duplicate process.

  • Session registry: A catalog owns the pty registry holding its tasks. <catalog>/pty is the default; a catalog may declare another so that one host can share a single registry across catalogs. Resolution is an exported PTY_ROOT, then the catalog's declaration, then the default, applied uniformly to spawn, list, kill, and the bus environment st2 hands to native tools, so every reader that can resolve the catalog agrees about where its sessions are. A declaration whose field set does not match fails st2 validate rather than resolving silently back to the default. Runtime observation has a short outer deadline so a wedged client fails the pass closed instead of hanging reconciliation. The deadline is containment, not the mechanism for admitting a larger fleet.

Message lifecycle

atomic inbox file → DING attempt → agent reads → archive receipt
       └──────── archive with same filename wins ────────┘
  • R05: A matching archive filename makes an inbox copy handled; stale duplicates are removed without another DING. Fresh dnd suppresses delivery; busy does not. Failed delivery remains retryable. Sidecar restart emits a bounded recovery notice instead of replaying the inbox. Delivery may wake an agent while it is working, but an active or uncertain human composer must be left untouched. Unsafe delivery retries use a bounded backoff so an active 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.

State and scope

  • R08: Presence and activity status are separate signals. The catalog must also expose the agent's current plan and step with explicit freshness so a human or supervising agent can understand progress without PTY inspection. Current presence/status files provide only part of this contract; the canonical plan-progress shape is not yet specified.
  • R09: Durable work state is external to the model transcript and is restored into replacement sessions through declared workspace files and verified hooks.
  • R10: Fleet identities are agents. General-purpose identity kinds are unsupported.

The owner updates this spec whenever implementation changes. Changing vision.md or requirements.md requires Nathan's explicit approval.

Event contracts (R13–R15)

An event is evidence, not permission to run the world. The reconciler retains path and kind, maps them to the affected identity or template dependency set, and computes the smallest desired-versus-actual delta. One declaration affects only that agent; a template affects only its dependents. A no-op performs zero PTY queries, launches, teardowns, materialization, or writes.

Watchers are deny-by-default. The classifier/action contract is:

Event Minimal action
declaration-space **/agent.kdl create/modify/remove validate, materialize, and converge that agent and derived tasks
referenced _templates/** mutation converge dependent agents only
inbox create/archive/remove DING consumer only; supervisor no-op
plan/resource/status mutation specialized consumer only; supervisor no-op
PTY/exec/log/PID/socket/lock/temp/backup/read/open/unknown no-op

Startup, timer, watcher overflow/loss, and ambiguity are bounded full-audit fallbacks. Accepted streams use head/tail coalescing: immediate head response, one quiet tail after a burst, and a hard maximum. Executable proof covers positive declaration/template wakes, negative runtime/bus events, bounded discovery/materialization/PTY queries and writes, continuous-event starvation, and no-op desired-equals-actual behavior.

Targeted reconciliation (R19)

st2 up --materialize-only --task <host.agent.task> resolves one exact local task before writing and renders only its owning agent. st2 up --once --task <host.agent.task> performs the same owner-only materialization, then inspects PTY/exec state and executes a plan containing only that task. Unknown, ambiguous, and wrong-host selectors refuse before writes or runner inspection; unrelated discovery diagnostics remain visible without preventing the selected owner/task path.

st2 up --materialize-only --agent <id> remains the agent-wide rendering selector. Targeted task reconciliation is intentionally bounded to --once; the resident supervisor continues to reconcile the complete local catalog.

Open design questions

  • DQ1 Scheduled work: The vision includes per-machine schedulers that form a distributed workflow engine, but the KDL shape, event inbox, deduplication 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 that a supervisor can follow plan progress without inspecting a PTY before adding the shape to AGENT-SPEC.md.