diff --git a/AGENT-SPEC.md b/AGENT-SPEC.md index 366f526e..a6d15d50 100644 --- a/AGENT-SPEC.md +++ b/AGENT-SPEC.md @@ -44,6 +44,8 @@ owns product work. Shipped declarations must not contain a developer's absolute ```kdl agent "" { identity "" + name "" + description "" host "" role "worker" type "service" @@ -83,6 +85,8 @@ Supported agent children are: | Node | Meaning | |---|---| | `identity "…"` | Overrides the positional/path-derived identity. | +| `name "…"` | Optional, non-unique human-facing name. It never routes or selects work. | +| `description "…"` | Optional enduring responsibility boundary. It never grants authority. | | `host "…"` | Execution host. The canonical folder path and content should agree. | | `role "…"` | Optional metadata with no execution behavior. | | `type "service"` | Optional; `service` is the only accepted value and the default. | @@ -103,6 +107,51 @@ Canonical declarations normally omit `type`. Unknown non-render children may be extension syntax, and required behavior must never depend on them. `schedule` is explicitly reserved and rejected. Unknown render directives are errors. +## Stable identity and presentation + +The positional or child `identity` remains the stable automation ID. It alone +owns bus routing, supervisor edges, task IDs, durable state paths, resources, +authorization, and lifecycle reconciliation. Existing positional and child +identity grammar remains valid; roster JSON continues to expose the stable bus +ID as `identity`. + +`name` and `description` are optional presentation metadata. Explicit values +must be non-empty, trimmed, single-line, and free of control characters. Limits +are 160 Unicode scalars for `name` and 1,000 for `description`; omission means +absent. Names need not be unique and are never st2 message, status, resource, +authorization, or lifecycle aliases. The retired sibling `name` file is not a +fallback source. + +The roster exposes separate nullable `name` and `description` fields. Mutable +canonical KDL can be edited source-preservingly with: + +```console +st2 rename +st2 rename --clear +st2 describe +st2 describe --clear +``` + +An agent may edit itself; a declared supervisor ancestor may edit a descendant; +an operator with no `ST_AGENT` may edit a selected mutable catalog. Peer edits +fail. JSON, TOML, and declarations marked `meta { managed-by "nix" }` are +readable but refuse these live authoring commands. Concurrent commands serialize +through the catalog lock and preserve both accepted field edits. + +Every managed PTY receives the owned tags +`agent.presentation.schema=1`, `agent.actor.path=.`, and the +optional `agent.presentation.description`. Only the primary `agent` PTY maps +Agent Spec `name` to native `displayName`; secondary PTYs retain their existing +task-specific display behavior. st2 reconciles this envelope through exact PTY +IDs with one atomic metadata patch, preserves unrelated tags, clears removed +owned fields, emits one coherent `metadata_change` event per real transition, +and emits no event for a no-op. Presentation reconciliation never replaces the +PTY process or its durable Agent Spec state. + +This contract is proved model-free by +[`agent-presentation-contract`](./cells/agent-presentation-contract/) and +[`agent-presentation-continuity`](./cells/agent-presentation-continuity/). + The restart defaults are 3 attempts per 60 seconds, no delay, and `mode "delay"`. Durations accept bare seconds or `ms`, `s|sec|secs`, `m|min|mins`, `h|hr|hrs`, and `d|day|days`. `mode "delay"` keeps retrying with the window reset; `mode "fail"` parks the task after attempts are exhausted and sends one best-effort diff --git a/CATALOG.md b/CATALOG.md index 233e7a66..36bec7f6 100644 --- a/CATALOG.md +++ b/CATALOG.md @@ -1,7 +1,7 @@ # Current eval catalog and evidence -This is the sole current corpus surface: **58 included cells** (18 model-free, +This is the sole current corpus surface: **60 included cells** (20 model-free, 40 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 @@ -17,6 +17,8 @@ harness-native loader plus canonical hook file. | Cell | Harness | Model(s) / effort | Model seats | Cost | Timeout | Held-out judges | Latest accepted PASS | Last recorded run | |---|---|---|---:|---|---|---:|---|---| | `adopt-only-migration` | model-free | — | 0 | none | `90s` | 6 | — | **NO STRUCTURED RUN** | +| `agent-presentation-continuity` | model-free | — | 0 | none | `90s` | 5 | — | **NO STRUCTURED RUN** | +| `agent-presentation-contract` | model-free | — | 0 | none | `90s` | 8 | — | **NO STRUCTURED RUN** | | `agent-spec-resource-bindings` | model-free | — | 0 | none | `60s` | 4 | — | **NO STRUCTURED RUN** | | `assignment-contract-cold-assignment` | Codex | `gpt-5.6-sol` / medium | 2 | medium | `1200s` | 3 | — | **NO STRUCTURED RUN** | | `assignment-contract-cold-focus` | Codex | `gpt-5.6-sol` / medium | 2 | medium | `1200s` | 3 | — | **NO STRUCTURED RUN** | @@ -95,6 +97,8 @@ while the last-run column makes a recorded failure distinct from a cell with no ## Harness hook exclusions - `adopt-only-migration` / `cell` — deterministic native adoption/replacement lifecycle probe; no harness seat +- `agent-presentation-continuity` / `cell` — deterministic native presentation continuity probe; no harness seat +- `agent-presentation-contract` / `cell` — deterministic native Agent Spec and PTY presentation probe; no harness seat - `agent-spec-resource-bindings` / `cell` — deterministic native Agent Spec Resource-envelope probe; no harness seat - `context-resource-continuity` / `cell` — deterministic native context/resource restart probe; no harness seat - `docs` / `judge:cold-reader` — one-shot offline Claude print grader; no bus identity, DING, or hook surface @@ -114,7 +118,7 @@ while the last-run column makes a recorded failure distinct from a cell with no - `targeted-reconcile-isolation` / `cell` — deterministic selected reconcile isolation probe; no bus-connected agent - `two-networks-coexist` / `cell` — deterministic catalog-isolation probe; no harness seat -These **19** rows are generated from structured exclusions. A cell-level row is required for +These **21** 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. diff --git a/cells/agent-presentation-continuity/README.md b/cells/agent-presentation-continuity/README.md new file mode 100644 index 00000000..1d1f279e --- /dev/null +++ b/cells/agent-presentation-continuity/README.md @@ -0,0 +1,20 @@ +# agent-presentation-continuity + +Model-free acceptance coverage for adding, changing, and clearing Agent Spec +presentation metadata without changing stable identity or the live process +generation. + +The cell begins with the existing positional stable ID and a conflicting +legacy sibling `name` file. It proves that Agent Spec is authoritative: absent +presentation does not fall back to the sibling file, and declared `name` and +`description` appear in the roster after an in-place catalog +edit. Across add, repeat, change, and clear reconciliations it requires the +same PTY ID, PID, creation timestamp, and single `session_start` event. + +The same transitions must preserve unread and archived messages, context, +decisions, resources, presence, and terminal transcript. Cleanup uses the +existing explicit retirement lifecycle and leaves no PTY state. + +This cell does not retire the existing `identity` grammar. Schema versioning +and breaking grammar changes belong to compoundingtech/st2#127; this cell is +the additive evidence for compoundingtech/st2#128. diff --git a/cells/agent-presentation-continuity/agent-presentation-continuity.kdl b/cells/agent-presentation-continuity/agent-presentation-continuity.kdl new file mode 100644 index 00000000..cbbf849c --- /dev/null +++ b/cells/agent-presentation-continuity/agent-presentation-continuity.kdl @@ -0,0 +1,27 @@ +// Additive Agent Spec presentation changes preserve stable identity, runtime generation, and durable state. +eval { + copy "./fixture" + max-timeout "90s" + + run "continuity" { + command "bash ./continuity.sh" + } + + judges { + judge "AUTHORITY - Agent Spec presentation ignores the retired sibling name file" { + exec "grep -Fqx PRESENTATION-AUTHORITY-GREEN-a128 $RUNS_DIR/continuity.out" + } + judge "ROSTER - add, change, repeat, and clear project exact presentation fields" { + exec "grep -Fqx PRESENTATION-ROSTER-GREEN-a128 $RUNS_DIR/continuity.out" + } + judge "GENERATION - every presentation-only reconcile preserves PTY id, pid, createdAt, and one session start" { + exec "grep -Fqx PRESENTATION-GENERATION-GREEN-a128 $RUNS_DIR/continuity.out" + } + judge "DURABILITY - inbox, archive, context, decisions, resources, presence, and transcript survive" { + exec "grep -Fqx PRESENTATION-DURABILITY-GREEN-a128 $RUNS_DIR/continuity.out" + } + judge "CLEANUP - explicit retirement leaves no retained PTY state" { + exec "grep -Fqx PRESENTATION-CLEANUP-GREEN-a128 $RUNS_DIR/continuity.out" + } + } +} diff --git a/cells/agent-presentation-continuity/fixture/continuity.sh b/cells/agent-presentation-continuity/fixture/continuity.sh new file mode 100755 index 00000000..31c482ef --- /dev/null +++ b/cells/agent-presentation-continuity/fixture/continuity.sh @@ -0,0 +1,160 @@ +#!/usr/bin/env bash +set -euo pipefail + +root="${CATALOG:?CATALOG must be set}" +net="$root/net" +spec="$net/agents/pc/worker/agent.kdl" +original="$root/worker.original.kdl" +pty_id="pc.worker" +export PTY_ROOT="$net/pty" +export XDG_STATE_HOME="$root/state" + +cp "$spec" "$original" + +pty_at() { + env -u PTY_SESSION PTY_ROOT="$PTY_ROOT" pty "$@" +} + +session() { + pty_at list --json | jq -cer --arg id "$pty_id" '.[] | select(.name == $id)' +} + +generation() { + session | jq -c '{name,pid,createdAt}' +} + +start_count() { + jq -s '[.[] | select(.type == "session_start")] | length' "$PTY_ROOT/$pty_id.events.jsonl" +} + +wait_running() { + for _ in $(seq 1 100); do + test "$(session | jq -r '.status')" = running && return 0 + sleep 0.05 + done + echo "presentation fixture did not become ready" >&2 + return 1 +} + +retire() { + grep -Fq 'retired #true' "$spec" || sed -i '/role "worker"/a\\ retired #true' "$spec" +} + +cleanup() { + if test -f "$spec"; then + retire 2>/dev/null || true + st2 up --once --catalog "$net" --host pc >/dev/null 2>&1 || true + fi + pty_at kill "$pty_id" >/dev/null 2>&1 || true + pty_at rm "$pty_id" >/dev/null 2>&1 || true + cp "$original" "$spec" 2>/dev/null || true +} +trap cleanup EXIT + +st2 validate --catalog "$net" --host pc --strict >/dev/null +st2 agents --catalog "$net" --host pc --json >"$root/roster-absent.json" +jq -e ' + length == 2 and + (.[] | select(.identity == "pc.worker") | .name == null and .description == null) +' "$root/roster-absent.json" >/dev/null +echo "PRESENTATION-AUTHORITY-GREEN-a128" + +st2 up --once --catalog "$net" --host pc >"$root/launch.out" +grep -Fq 'launched (1): pc.worker' "$root/launch.out" +wait_running +before="$(generation)" +test "$(start_count)" -eq 1 + +st2 status "$pty_id" --set busy --catalog "$net" --host pc --as "$pty_id" >/dev/null +printf '%s\n' CONTEXT-NOW-a128 | st2 context write "$pty_id" --catalog "$net" --as "$pty_id" +st2 context append "$pty_id" --catalog "$net" --as "$pty_id" \ + --decision DECISION-a128 --why DECISION-WHY-a128 +st2 resource add https://example.invalid/presentation-a128 \ + --catalog "$net" --as "$pty_id" --title PRESENTATION-RESOURCE-a128 \ + --tag presentation,continuity --relation output >"$root/resource-ref" + +st2 message send "$pty_id" --catalog "$net" --host pc --as pc.sender \ + --subject ARCHIVED-a128 >/dev/null <<'MSG' +ARCHIVED-BODY-a128 +MSG +archived_path=("$net/agents/pc/worker/resources/inbox/"*.md) +test "${#archived_path[@]}" -eq 1 +archived_name="$(basename "${archived_path[0]}")" +st2 message archive "$pty_id" "$archived_name" --catalog "$net" --host pc \ + --as "$pty_id" >/dev/null +st2 message send "$pty_id" --catalog "$net" --host pc --as pc.sender \ + --subject UNREAD-a128 >/dev/null <<'MSG' +UNREAD-BODY-a128 +MSG + +for _ in $(seq 1 100); do + pty_at peek --plain "$pty_id" 2>/dev/null | grep -Fq PRESENTATION-TRANSCRIPT-a128 && break + sleep 0.05 +done +pty_at peek --plain "$pty_id" | grep -Fq PRESENTATION-TRANSCRIPT-a128 + +add_fields() { + sed -i '/role "worker"/a\\ description "Owns durable identity acceptance"\n name "Evidence Worker"' "$spec" +} + +assert_generation() { + test "$(generation)" = "$before" + test "$(start_count)" -eq 1 +} + +add_fields +st2 validate --catalog "$net" --host pc --strict >/dev/null +st2 up --once --catalog "$net" --host pc >"$root/add.out" +grep -Fq 'adopted (1): worker' "$root/add.out" +test -z "$(sed -n '/launched (/p;/torn down (/p' "$root/add.out")" +assert_generation +st2 agents --catalog "$net" --host pc --json >"$root/roster-added.json" +jq -e ' + .[] | select(.identity == "pc.worker") | + .name == "Evidence Worker" and + .description == "Owns durable identity acceptance" +' "$root/roster-added.json" >/dev/null + +st2 up --once --catalog "$net" --host pc >"$root/repeat.out" +grep -Fq 'adopted (1): worker' "$root/repeat.out" +assert_generation + +sed -i 's/name "Evidence Worker"/name "Shared Presentation"/' "$spec" +sed -i 's/description "Owns durable identity acceptance"/description "Owns changed identity acceptance"/' "$spec" +st2 up --once --catalog "$net" --host pc >"$root/change.out" +assert_generation +st2 agents --catalog "$net" --host pc --json >"$root/roster-changed.json" +jq -e ' + .[] | select(.identity == "pc.worker") | + .name == "Shared Presentation" and + .description == "Owns changed identity acceptance" +' "$root/roster-changed.json" >/dev/null + +sed -i '/^ name "/d;/^ description "/d' "$spec" +st2 up --once --catalog "$net" --host pc >"$root/clear.out" +assert_generation +st2 agents --catalog "$net" --host pc --json >"$root/roster-cleared.json" +jq -e ' + .[] | select(.identity == "pc.worker") | + .name == null and .description == null +' "$root/roster-cleared.json" >/dev/null +echo "PRESENTATION-ROSTER-GREEN-a128" +echo "PRESENTATION-GENERATION-GREEN-a128" + +test "$(st2 status "$pty_id" --catalog "$net" --host pc --as "$pty_id")" = busy +st2 context read "$pty_id" --catalog "$net" --as "$pty_id" | grep -Fq CONTEXT-NOW-a128 +st2 context read "$pty_id" --catalog "$net" --as "$pty_id" --decisions | grep -Fq DECISION-WHY-a128 +st2 resource ls --catalog "$net" --as "$pty_id" | grep -Fq https://example.invalid/presentation-a128 +test -f "$net/agents/pc/worker/resources/archive/$archived_name" +grep -Fq ARCHIVED-BODY-a128 "$net/agents/pc/worker/resources/archive/$archived_name" +grep -Fq UNREAD-BODY-a128 "$net/agents/pc/worker/resources/inbox/"*.md +pty_at peek --plain "$pty_id" | grep -Fq PRESENTATION-TRANSCRIPT-a128 +echo "PRESENTATION-DURABILITY-GREEN-a128" + +retire +st2 up --once --catalog "$net" --host pc >/dev/null +pty_at rm "$pty_id" >/dev/null 2>&1 || true +cp "$original" "$spec" +trap - EXIT +test "$(pty_at list --json | jq 'length')" -eq 0 +echo "PRESENTATION-CLEANUP-GREEN-a128" diff --git a/cells/agent-presentation-continuity/fixture/net/agents/pc/sender/agent.kdl b/cells/agent-presentation-continuity/fixture/net/agents/pc/sender/agent.kdl new file mode 100644 index 00000000..781b531a --- /dev/null +++ b/cells/agent-presentation-continuity/fixture/net/agents/pc/sender/agent.kdl @@ -0,0 +1,8 @@ +agent "sender" { + identity "sender" + host "pc" + role "fixture" + retired #true + workspace "$CATALOG/workspace" + command "true" +} diff --git a/cells/agent-presentation-continuity/fixture/net/agents/pc/worker/agent.kdl b/cells/agent-presentation-continuity/fixture/net/agents/pc/worker/agent.kdl new file mode 100644 index 00000000..f6be1249 --- /dev/null +++ b/cells/agent-presentation-continuity/fixture/net/agents/pc/worker/agent.kdl @@ -0,0 +1,10 @@ +agent "worker" { + identity "worker" + host "pc" + role "worker" + workspace "$CATALOG/workspace" + env { + ST_AGENT "pc.worker" + } + command "exec bash \"$CATALOG/../task.sh\"" +} diff --git a/cells/agent-presentation-continuity/fixture/net/agents/pc/worker/name b/cells/agent-presentation-continuity/fixture/net/agents/pc/worker/name new file mode 100644 index 00000000..710e111d --- /dev/null +++ b/cells/agent-presentation-continuity/fixture/net/agents/pc/worker/name @@ -0,0 +1 @@ +RETIRED-SIBLING-NAME-MUST-NOT-SURFACE diff --git a/cells/agent-presentation-continuity/fixture/net/workspace/.keep b/cells/agent-presentation-continuity/fixture/net/workspace/.keep new file mode 100644 index 00000000..ee8c1ee4 --- /dev/null +++ b/cells/agent-presentation-continuity/fixture/net/workspace/.keep @@ -0,0 +1 @@ +fixture diff --git a/cells/agent-presentation-continuity/fixture/task.sh b/cells/agent-presentation-continuity/fixture/task.sh new file mode 100755 index 00000000..b08aab86 --- /dev/null +++ b/cells/agent-presentation-continuity/fixture/task.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +set -euo pipefail + +printf 'PRESENTATION-TRANSCRIPT-a128\n' +exec sleep 300 diff --git a/cells/agent-presentation-contract/README.md b/cells/agent-presentation-contract/README.md new file mode 100644 index 00000000..b7ddcc5b --- /dev/null +++ b/cells/agent-presentation-contract/README.md @@ -0,0 +1,29 @@ +# agent-presentation-contract + +Model-free acceptance coverage for stable Agent Spec identity and mutable +presentation metadata across the native st2 and PTY boundary. + +Two live agents deliberately share one display name. Stable IDs remain the +only st2 routing, authorization, task, and lifecycle keys, while PTY human +lookup follows its exact-ID-first and fail-closed ambiguity contract. Changing +or clearing `name` and `description` updates the roster and managed +PTY metadata without changing the PTY ID, PID, creation timestamp, or process +generation. Repeating an unchanged reconciliation emits no metadata event. +Name and description accept at most 160 and 1,000 Unicode scalars respectively; +empty, untrimmed, multiline, and control text is refused. + +Every managed PTY receives the same owned presentation schema, actor path, +and description tags. Only the primary agent PTY receives Agent Spec +`name` as its native display name; a secondary task keeps its task-specific +display behavior across presentation changes. + +The same black-box fixture exercises source-preserving presentation authoring: +operator, self, and supervisor edits succeed; peer edits, Nix-owned sources, +and JSON/TOML declarations fail with classified receipts. Concurrent edits to +different presentation fields serialize through the catalog lock and preserve +both accepted values. + +An explicit exact-ID primary-process kill is the lifecycle control: normal +reconciliation must report the primary as restarted and create a new generation +without disturbing its secondary task or sibling agent. Explicit retirement +remains the cleanup boundary. diff --git a/cells/agent-presentation-contract/agent-presentation-contract.kdl b/cells/agent-presentation-contract/agent-presentation-contract.kdl new file mode 100644 index 00000000..a463752f --- /dev/null +++ b/cells/agent-presentation-contract/agent-presentation-contract.kdl @@ -0,0 +1,36 @@ +// Stable-ID routing plus non-unique, mutable presentation projected into live PTY metadata. +eval { + copy "./fixture" + max-timeout "90s" + + run "contract" { + command "bash ./contract.sh" + } + + judges { + judge "AUTHORING - presentation edits preserve identity and enforce self, supervisor, ownership, and format boundaries" { + exec "grep -Fqx PRESENTATION-AUTHORING-GREEN-b128 $RUNS_DIR/contract.out" + } + judge "SCHEMA - presentation fields enforce Unicode-scalar bounds and roster output is deterministic" { + exec "grep -Fqx PRESENTATION-SCHEMA-GREEN-b128 $RUNS_DIR/contract.out" + } + judge "DUPLICATES - duplicate names are valid while ambiguous PTY references fail closed with stable IDs" { + exec "grep -Fqx PRESENTATION-DUPLICATES-GREEN-b128 $RUNS_DIR/contract.out" + } + judge "ROUTING - st2 message and presence targets resolve stable IDs only" { + exec "grep -Fqx PRESENTATION-ROUTING-GREEN-b128 $RUNS_DIR/contract.out" + } + judge "PROJECTION - name and description changes and removal fully update PTY metadata" { + exec "grep -Fqx PRESENTATION-PROJECTION-GREEN-b128 $RUNS_DIR/contract.out" + } + judge "IDEMPOTENCE - unchanged presentation emits no event and preserves the runtime generation" { + exec "grep -Fqx PRESENTATION-IDEMPOTENCE-GREEN-b128 $RUNS_DIR/contract.out" + } + judge "LIFECYCLE CONTROL - an exact dead primary process is restarted without disturbing sibling tasks" { + exec "grep -Fqx PRESENTATION-LIFECYCLE-CONTROL-GREEN-b128 $RUNS_DIR/contract.out" + } + judge "CLEANUP - explicit retirement leaves no live or retained PTY state" { + exec "grep -Fqx PRESENTATION-CLEANUP-GREEN-b128 $RUNS_DIR/contract.out" + } + } +} diff --git a/cells/agent-presentation-contract/fixture/contract.sh b/cells/agent-presentation-contract/fixture/contract.sh new file mode 100755 index 00000000..c9c6a45f --- /dev/null +++ b/cells/agent-presentation-contract/fixture/contract.sh @@ -0,0 +1,503 @@ +#!/usr/bin/env bash +set -euo pipefail + +root="${CATALOG:?CATALOG must be set}" +net="$root/net" +alpha_spec="$net/agents/ap/alpha/agent.kdl" +beta_spec="$net/agents/ap/beta/agent.kdl" +alpha_original="$root/alpha.original.kdl" +beta_original="$root/beta.original.kdl" +export PTY_ROOT="$net/pty" +export XDG_STATE_HOME="$root/state" + +cp "$alpha_spec" "$alpha_original" +cp "$beta_spec" "$beta_original" + +pty_at() { + env -u PTY_SESSION PTY_ROOT="$PTY_ROOT" pty "$@" +} + +session() { + id="$1" + pty_at list --json | jq -cer --arg id "$id" '.[] | select(.name == $id)' +} + +generation() { + session "$1" | jq -c '{name,pid,createdAt}' +} + +presentation() { + session "$1" | jq -c '{displayName,tags}' +} + +event_count() { + id="$1" + type="$2" + jq -s --arg type "$type" '[.[] | select(.type == $type)] | length' "$PTY_ROOT/$id.events.jsonl" +} + +last_metadata_event() { + local id="$1" + jq -cs '[.[] | select(.type == "metadata_change")] | last' "$PTY_ROOT/$id.events.jsonl" +} + +wait_running() { + id="$1" + for _ in $(seq 1 100); do + test "$(session "$id" | jq -r '.status')" = running && return 0 + sleep 0.05 + done + printf '%s did not become ready\n' "$id" >&2 + return 1 +} + +retire() { + spec="$1" + grep -Fq 'retired #true' "$spec" || sed -i '/role "worker"/a\\ retired #true' "$spec" +} + +cleanup() { + for spec in "$alpha_spec" "$beta_spec"; do + test ! -f "$spec" || retire "$spec" 2>/dev/null || true + done + st2 up --once --catalog "$net" --host ap >/dev/null 2>&1 || true + for id in ap.alpha ap.alpha.helper ap.beta; do + pty_at kill "$id" >/dev/null 2>&1 || true + pty_at rm "$id" >/dev/null 2>&1 || true + done + cp "$alpha_original" "$alpha_spec" 2>/dev/null || true + cp "$beta_original" "$beta_spec" 2>/dev/null || true +} +trap cleanup EXIT + +invalid="$root/invalid" +mkdir -p "$invalid/agents/ap/empty" "$invalid/workspace" +cat >"$invalid/agents/ap/empty/agent.kdl" <<'KDL' +agent "empty" { + identity "empty" + host "ap" + name "" + description "" + workspace "$CATALOG/workspace" + command "true" +} +KDL +set +e +st2 validate --catalog "$invalid" --host ap --strict >"$root/invalid.out" 2>&1 +invalid_rc=$? +set -e +test "$invalid_rc" -ne 0 + +authoring="$root/authoring" +mkdir -p \ + "$authoring/agents/ap/root" \ + "$authoring/agents/ap/child" \ + "$authoring/agents/ap/grandchild" \ + "$authoring/agents/ap/sibling" \ + "$authoring/agents/ap/nix" \ + "$authoring/agents/ap/json" \ + "$authoring/agents/ap/toml" +cat >"$authoring/agents/ap/root/agent.kdl" <<'KDL' +// preserve-root-comment-b128 +agent "root" { + identity "root" + host "ap" + role "fixture" + meta { managed-by "catalog"; keep "unchanged" } + command "true" +} +KDL +cat >"$authoring/agents/ap/child/agent.kdl" <<'KDL' +agent "child" { + identity "child" + host "ap" + role "fixture" + supervisor "ap.root" + meta { managed-by "catalog" } + command "true" +} +KDL +cat >"$authoring/agents/ap/grandchild/agent.kdl" <<'KDL' +agent "grandchild" { + identity "grandchild" + host "ap" + role "fixture" + supervisor "ap.child" + meta { managed-by "catalog" } + command "true" +} +KDL +cat >"$authoring/agents/ap/sibling/agent.kdl" <<'KDL' +agent "sibling" { + identity "sibling" + host "ap" + role "fixture" + supervisor "ap.root" + meta { managed-by "catalog" } + command "true" +} +KDL +cat >"$authoring/agents/ap/nix/agent.kdl" <<'KDL' +agent "nix" { + identity "nix" + host "ap" + role "fixture" + supervisor "ap.root" + meta { managed-by "nix" } + command "true" +} +KDL +cat >"$authoring/agents/ap/json/agent.json" <<'JSON' +{"identity":"json","host":"ap","role":"fixture","command":"true"} +JSON +cat >"$authoring/agents/ap/toml/agent.toml" <<'TOML' +identity = "toml" +host = "ap" +role = "fixture" +command = "true" +TOML + +env -u ST_AGENT st2 --catalog "$authoring" rename ap.root "Root Operator" --host ap --json \ + | jq -e '. == {result:"changed",identity:"ap.root",field:"name",value:"Root Operator",retired:false}' \ + >/dev/null + +name_limit="$(jq -nr '"é" * 160')" +description_limit="$(jq -nr '"é" * 1000')" +for boundary in \ + "rename|$name_limit" \ + "describe|$description_limit" +do + IFS='|' read -r command value <<<"$boundary" + env -u ST_AGENT st2 --catalog "$authoring" "$command" ap.root "$value" --host ap --json \ + | jq -e --arg value "$value" '.result == "changed" and .value == $value' >/dev/null +done +st2 validate --catalog "$authoring" --host ap --strict >/dev/null + +expect_invalid_presentation() { + local command="$1" + local value="$2" + set +e + env -u ST_AGENT st2 --catalog "$authoring" "$command" ap.root "$value" --host ap --json \ + >"$root/invalid-presentation.json" 2>"$root/invalid-presentation.err" + invalid_presentation_rc=$? + set -e + test "$invalid_presentation_rc" -ne 0 + jq -e '.result == "error" and .code == "invalid-presentation"' \ + "$root/invalid-presentation.json" >/dev/null +} +expect_invalid_presentation rename "$(jq -nr '"é" * 161')" +expect_invalid_presentation describe "$(jq -nr '"é" * 1001')" +expect_invalid_presentation rename "" +expect_invalid_presentation rename " leading" +expect_invalid_presentation rename "trailing " +expect_invalid_presentation rename $'two\nlines' +expect_invalid_presentation rename $'control\x01character' +expect_invalid_presentation describe "" +expect_invalid_presentation describe " leading" +expect_invalid_presentation describe "trailing " +expect_invalid_presentation describe $'two\nlines' +expect_invalid_presentation describe $'control\x01character' +env -u ST_AGENT st2 --catalog "$authoring" rename ap.root "Root Operator" --host ap --json >/dev/null +env -u ST_AGENT st2 --catalog "$authoring" describe ap.root --clear --host ap --json >/dev/null + +ST_AGENT=ap.root st2 --catalog "$authoring" describe ap.child "Owned by root" --host ap --json \ + | jq -e '.result == "changed" and .identity == "ap.child" and .field == "description"' >/dev/null +ST_AGENT=ap.root st2 --catalog "$authoring" describe ap.grandchild "Owned by ancestor" --host ap --json \ + | jq -e '.result == "changed" and .identity == "ap.grandchild" and .field == "description" and .value == "Owned by ancestor"' >/dev/null +grep -Fq 'description "Owned by ancestor"' "$authoring/agents/ap/grandchild/agent.kdl" +ST_AGENT=ap.child st2 --catalog "$authoring" rename ap.child "Child Self" --host ap --json \ + | jq -e '.result == "changed" and .identity == "ap.child" and .field == "name"' >/dev/null +ST_AGENT=ap.child st2 --catalog "$authoring" rename ap.child "Child Self" --host ap --json \ + | jq -e '.result == "unchanged" and .value == "Child Self"' >/dev/null + +ST_AGENT=ap.root st2 --catalog "$authoring" rename ap.child "Concurrent Child" --host ap --json \ + >"$root/concurrent-name.json" & +concurrent_name_pid=$! +ST_AGENT=ap.root st2 --catalog "$authoring" describe ap.child "Concurrent description" --host ap --json \ + >"$root/concurrent-description.json" & +concurrent_description_pid=$! +wait "$concurrent_name_pid" +wait "$concurrent_description_pid" +jq -e '.result == "changed" and .field == "name" and .value == "Concurrent Child"' \ + "$root/concurrent-name.json" >/dev/null +jq -e '.result == "changed" and .field == "description" and .value == "Concurrent description"' \ + "$root/concurrent-description.json" >/dev/null +grep -Fq 'name "Concurrent Child"' "$authoring/agents/ap/child/agent.kdl" +grep -Fq 'description "Concurrent description"' "$authoring/agents/ap/child/agent.kdl" +grep -Fq 'supervisor "ap.root"' "$authoring/agents/ap/child/agent.kdl" +grep -Fq 'meta { managed-by "catalog" }' "$authoring/agents/ap/child/agent.kdl" +grep -Fq 'command "true"' "$authoring/agents/ap/child/agent.kdl" + +for refusal in \ + 'ap.child|rename|ap.sibling|peer denied|presentation-not-authorized' \ + 'ap.root|describe|ap.nix|nix denied|nix-managed-declaration' \ + 'operator|describe|ap.json|json denied|unsupported-declaration-format' \ + 'operator|describe|ap.toml|toml denied|unsupported-declaration-format' +do + IFS='|' read -r actor command target value code <<<"$refusal" + if test "$actor" = operator; then + actor_env=(env -u ST_AGENT) + else + actor_env=(env "ST_AGENT=$actor") + fi + set +e + "${actor_env[@]}" st2 --catalog "$authoring" "$command" "$target" "$value" --host ap --json \ + >"$root/$target-$command-refusal.json" 2>"$root/$target-$command-refusal.err" + refusal_rc=$? + set -e + test "$refusal_rc" -ne 0 + jq -e --arg code "$code" '.result == "error" and .code == $code' \ + "$root/$target-$command-refusal.json" >/dev/null +done + +ST_AGENT=ap.root st2 --catalog "$authoring" describe ap.child --clear --host ap --json \ + | jq -e '.result == "changed" and .value == null' >/dev/null +env -u ST_AGENT st2 --catalog "$authoring" rename ap.root --clear --host ap --json \ + | jq -e '. == {result:"changed",identity:"ap.root",field:"name",value:null,retired:false}' >/dev/null +! grep -Fq 'name "' "$authoring/agents/ap/root/agent.kdl" +grep -Fqx '// preserve-root-comment-b128' "$authoring/agents/ap/root/agent.kdl" +grep -Fq 'keep "unchanged"' "$authoring/agents/ap/root/agent.kdl" +grep -Fq 'identity "root"' "$authoring/agents/ap/root/agent.kdl" +echo "PRESENTATION-AUTHORING-GREEN-b128" + +st2 validate --catalog "$net" --host ap --strict >/dev/null +st2 agents --catalog "$net" --host ap --json >"$root/roster.json" +st2 agents --catalog "$net" --host ap --json >"$root/roster-repeat.json" +cmp "$root/roster.json" "$root/roster-repeat.json" +jq -e ' + [ .[] | select(.identity == "ap.alpha" or .identity == "ap.beta") ] == [ + { + "identity": "ap.alpha", + "status": "offline", + "name": "Shared Worker", + "description": "Owns alpha acceptance", + "retired": false, + "resources": [] + }, + { + "identity": "ap.beta", + "status": "offline", + "name": "Shared Worker", + "description": "Owns beta acceptance", + "retired": false, + "resources": [] + } + ] +' "$root/roster.json" >/dev/null +echo "PRESENTATION-SCHEMA-GREEN-b128" + +st2 up --once --catalog "$net" --host ap >"$root/launch.out" +wait_running ap.alpha +wait_running ap.alpha.helper +wait_running ap.beta +alpha_before="$(generation ap.alpha)" +alpha_helper_before="$(generation ap.alpha.helper)" +alpha_helper_display="$(session ap.alpha.helper | jq -c '.displayName // null')" +beta_before="$(generation ap.beta)" +test "$(event_count ap.alpha session_start)" -eq 1 +test "$(event_count ap.alpha.helper session_start)" -eq 1 +test "$(event_count ap.beta session_start)" -eq 1 +test "$(session ap.alpha | jq -r '.displayName')" = "Shared Worker" +test "$(session ap.beta | jq -r '.displayName')" = "Shared Worker" +test "$(session ap.alpha.helper | jq -r '.displayName // "absent"')" != "Shared Worker" +jq -e ' + .tags["agent.presentation.schema"] == "1" and + .tags["agent.actor.path"] == "ap.alpha" and + .tags["agent.presentation.description"] == "Owns alpha acceptance" and + (.tags | has("agent.presentation.name") | not) +' <<<"$(session ap.alpha)" >/dev/null +jq -e ' + .tags["agent.presentation.schema"] == "1" and + .tags["agent.actor.path"] == "ap.beta" and + .tags["agent.presentation.description"] == "Owns beta acceptance" and + (.tags | has("agent.presentation.name") | not) +' <<<"$(session ap.beta)" >/dev/null +jq -e ' + .tags["purpose"] == "secondary" and + .tags["agent.presentation.schema"] == "1" and + .tags["agent.actor.path"] == "ap.alpha" and + .tags["agent.presentation.description"] == "Owns alpha acceptance" and + (.tags | has("agent.presentation.name") | not) +' <<<"$(session ap.alpha.helper)" >/dev/null + +set +e +pty_at peek --plain "Shared Worker" >"$root/ambiguous.out" 2>"$root/ambiguous.err" +ambiguous_rc=$? +set -e +test "$ambiguous_rc" -ne 0 +grep -Fq ap.alpha "$root/ambiguous.err" +grep -Fq ap.beta "$root/ambiguous.err" +test "$(grep -o 'ap\.alpha\|ap\.beta' "$root/ambiguous.err" | paste -sd, -)" = ap.alpha,ap.beta +pty_at peek --plain ap.alpha | grep -Fq alpha-GENERATION-1-b128 +echo "PRESENTATION-DUPLICATES-GREEN-b128" + +st2 message send ap.alpha --catalog "$net" --host ap --as ap.sender \ + --subject STABLE-ID-ROUTING-b128 >/dev/null <<'MSG' +STABLE-ID-BODY-b128 +MSG +grep -Fq STABLE-ID-BODY-b128 "$net/agents/ap/alpha/resources/inbox/"*.md +set +e +st2 message send "Shared Worker" --catalog "$net" --host ap --as ap.sender \ + --subject DISPLAY-MUST-NOT-ROUTE-b128 \ + >"$root/display-route.out" 2>"$root/display-route.err" <<'MSG' +DISPLAY-MUST-NOT-ROUTE-b128 +MSG +display_route_rc=$? +st2 status "Shared Worker" --set busy --catalog "$net" --host ap --as ap.sender \ + >"$root/display-status.out" 2>"$root/display-status.err" +display_status_rc=$? +st2 message send "Owns alpha acceptance" --catalog "$net" --host ap --as ap.sender \ + --subject DESCRIPTION-MUST-NOT-ROUTE-b128 \ + >"$root/description-route.out" 2>"$root/description-route.err" <<'MSG' +DESCRIPTION-MUST-NOT-ROUTE-b128 +MSG +description_route_rc=$? +set -e +test "$display_route_rc" -ne 0 +test "$display_status_rc" -ne 0 +test "$description_route_rc" -ne 0 +! grep -RFq DISPLAY-MUST-NOT-ROUTE-b128 "$net/agents/ap" +! grep -RFq DESCRIPTION-MUST-NOT-ROUTE-b128 "$net" +echo "PRESENTATION-ROUTING-GREEN-b128" + +pty_at tag ap.alpha external.keep=untouched >/dev/null +alpha_metadata_events="$(event_count ap.alpha metadata_change)" +alpha_display_events="$(event_count ap.alpha display_name_change)" +alpha_tag_events="$(event_count ap.alpha tags_change)" +sed -i 's/name "Shared Worker"/name "ap.beta"/' "$alpha_spec" +st2 up --once --catalog "$net" --host ap >"$root/id-collision.out" +test "$(generation ap.alpha)" = "$alpha_before" +test "$(generation ap.alpha.helper)" = "$alpha_helper_before" +test "$(generation ap.beta)" = "$beta_before" +test "$(session ap.alpha | jq -r '.displayName')" = ap.beta +test "$(session ap.alpha.helper | jq -c '.displayName // null')" = "$alpha_helper_display" +jq -e ' + .tags["external.keep"] == "untouched" and + .tags["agent.presentation.schema"] == "1" and + .tags["agent.actor.path"] == "ap.alpha" and + .tags["agent.presentation.description"] == "Owns alpha acceptance" +' <<<"$(session ap.alpha)" >/dev/null +test "$(event_count ap.alpha metadata_change)" -eq $((alpha_metadata_events + 1)) +test "$(event_count ap.alpha display_name_change)" -eq "$alpha_display_events" +test "$(event_count ap.alpha tags_change)" -eq "$alpha_tag_events" +jq -e ' + .previous == {displayName:"Shared Worker"} and + .value == {displayName:"ap.beta"} +' <<<"$(last_metadata_event ap.alpha)" >/dev/null +pty_at peek --plain ap.beta | grep -Fq beta-GENERATION-1-b128 + +sed -i 's/name "ap.beta"/name "Alpha Renamed"/' "$alpha_spec" +sed -i 's/description "Owns alpha acceptance"/description "Owns renamed alpha acceptance"/' "$alpha_spec" +old_projection="$(presentation ap.alpha)" +rm -f "$root/change.done" +( + while ! test -f "$root/change.done"; do + presentation ap.alpha >>"$root/change-observations.jsonl" + done +) & +observer_pid=$! +st2 up --once --catalog "$net" --host ap >"$root/change.out" +touch "$root/change.done" +wait "$observer_pid" +test "$(generation ap.alpha)" = "$alpha_before" +test "$(generation ap.alpha.helper)" = "$alpha_helper_before" +test "$(generation ap.beta)" = "$beta_before" +test "$(event_count ap.alpha session_start)" -eq 1 +test "$(session ap.alpha | jq -r '.displayName')" = "Alpha Renamed" +new_projection="$(presentation ap.alpha)" +jq -s -e --argjson old "$old_projection" --argjson new "$new_projection" \ + 'length > 0 and all(. == $old or . == $new)' "$root/change-observations.jsonl" >/dev/null +jq -e ' + .tags["external.keep"] == "untouched" and + .tags["agent.presentation.schema"] == "1" and + .tags["agent.actor.path"] == "ap.alpha" and + .tags["agent.presentation.description"] == "Owns renamed alpha acceptance" and + (.tags | has("agent.presentation.name") | not) +' <<<"$(session ap.alpha)" >/dev/null +jq -e ' + .tags["purpose"] == "secondary" and + .tags["agent.presentation.schema"] == "1" and + .tags["agent.actor.path"] == "ap.alpha" and + .tags["agent.presentation.description"] == "Owns renamed alpha acceptance" +' <<<"$(session ap.alpha.helper)" >/dev/null +test "$(session ap.alpha.helper | jq -c '.displayName // null')" = "$alpha_helper_display" +test "$(event_count ap.alpha metadata_change)" -eq $((alpha_metadata_events + 2)) +test "$(event_count ap.alpha display_name_change)" -eq "$alpha_display_events" +test "$(event_count ap.alpha tags_change)" -eq "$alpha_tag_events" +jq -e ' + .previous == { + displayName:"ap.beta", + tags:{"agent.presentation.description":"Owns alpha acceptance"} + } and + .value == { + displayName:"Alpha Renamed", + tags:{"agent.presentation.description":"Owns renamed alpha acceptance"} + } +' <<<"$(last_metadata_event ap.alpha)" >/dev/null + +changed_events="$(wc -l <"$PTY_ROOT/ap.alpha.events.jsonl")" +st2 up --once --catalog "$net" --host ap >"$root/idempotent.out" +test "$(generation ap.alpha)" = "$alpha_before" +test "$(wc -l <"$PTY_ROOT/ap.alpha.events.jsonl")" -eq "$changed_events" +echo "PRESENTATION-IDEMPOTENCE-GREEN-b128" + +sed -i '/^ name "/d;/^ description "/d' "$alpha_spec" +st2 up --once --catalog "$net" --host ap >"$root/clear.out" +test "$(generation ap.alpha)" = "$alpha_before" +test "$(generation ap.alpha.helper)" = "$alpha_helper_before" +test "$(session ap.alpha | jq -r '.displayName // "absent"')" = absent +test "$(event_count ap.alpha metadata_change)" -eq $((alpha_metadata_events + 3)) +test "$(event_count ap.alpha display_name_change)" -eq "$alpha_display_events" +test "$(event_count ap.alpha tags_change)" -eq "$alpha_tag_events" +jq -e ' + .previous == { + displayName:"Alpha Renamed", + tags:{"agent.presentation.description":"Owns renamed alpha acceptance"} + } and + .value == { + displayName:null, + tags:{"agent.presentation.description":null} + } +' <<<"$(last_metadata_event ap.alpha)" >/dev/null +jq -e ' + .tags["external.keep"] == "untouched" and + .tags["agent.presentation.schema"] == "1" and + .tags["agent.actor.path"] == "ap.alpha" and + (.tags | has("agent.presentation.description") | not) and + (.tags | has("agent.presentation.name") | not) +' <<<"$(session ap.alpha)" >/dev/null +jq -e ' + .tags["purpose"] == "secondary" and + .tags["agent.presentation.schema"] == "1" and + .tags["agent.actor.path"] == "ap.alpha" and + (.tags | has("agent.presentation.description") | not) +' <<<"$(session ap.alpha.helper)" >/dev/null +test "$(session ap.alpha.helper | jq -c '.displayName // null')" = "$alpha_helper_display" +st2 agents --catalog "$net" --host ap --json >"$root/cleared-roster.json" +jq -e '.[] | select(.identity == "ap.alpha") | .name == null and .description == null' \ + "$root/cleared-roster.json" >/dev/null +echo "PRESENTATION-PROJECTION-GREEN-b128" + +pty_at kill ap.alpha >/dev/null +test "$(session ap.alpha | jq -r '.status')" != running +st2 up --once --catalog "$net" --host ap >"$root/lifecycle.out" +wait_running ap.alpha +grep -Fq 'restarted (1): ap.alpha' "$root/lifecycle.out" +test "$(cat "$net/alpha-count")" -eq 2 +test "$(generation ap.alpha)" != "$alpha_before" +test "$(event_count ap.alpha session_start)" -eq 1 +test "$(generation ap.alpha.helper)" = "$alpha_helper_before" +test "$(generation ap.beta)" = "$beta_before" +echo "PRESENTATION-LIFECYCLE-CONTROL-GREEN-b128" + +retire "$alpha_spec" +retire "$beta_spec" +st2 up --once --catalog "$net" --host ap >/dev/null +for id in ap.alpha ap.alpha.helper ap.beta; do + pty_at rm "$id" >/dev/null 2>&1 || true +done +cp "$alpha_original" "$alpha_spec" +cp "$beta_original" "$beta_spec" +trap - EXIT +test "$(pty_at list --json | jq 'length')" -eq 0 +echo "PRESENTATION-CLEANUP-GREEN-b128" diff --git a/cells/agent-presentation-contract/fixture/net/agents/ap/alpha/agent.kdl b/cells/agent-presentation-contract/fixture/net/agents/ap/alpha/agent.kdl new file mode 100644 index 00000000..412a092a --- /dev/null +++ b/cells/agent-presentation-contract/fixture/net/agents/ap/alpha/agent.kdl @@ -0,0 +1,20 @@ +agent "alpha" { + identity "alpha" + host "ap" + role "worker" + name "Shared Worker" + description "Owns alpha acceptance" + workspace "$CATALOG/workspace" + env { + ST_AGENT "ap.alpha" + } + command "exec bash \"$CATALOG/../task.sh\" alpha" + pty "helper" { + id "ap.alpha.helper" + command "exec sleep 300" + tags purpose="secondary" + env { + ST_AGENT "ap.alpha" + } + } +} diff --git a/cells/agent-presentation-contract/fixture/net/agents/ap/beta/agent.kdl b/cells/agent-presentation-contract/fixture/net/agents/ap/beta/agent.kdl new file mode 100644 index 00000000..53891cb5 --- /dev/null +++ b/cells/agent-presentation-contract/fixture/net/agents/ap/beta/agent.kdl @@ -0,0 +1,12 @@ +agent "beta" { + identity "beta" + host "ap" + role "worker" + name "Shared Worker" + description "Owns beta acceptance" + workspace "$CATALOG/workspace" + env { + ST_AGENT "ap.beta" + } + command "exec bash \"$CATALOG/../task.sh\" beta" +} diff --git a/cells/agent-presentation-contract/fixture/net/agents/ap/sender/agent.kdl b/cells/agent-presentation-contract/fixture/net/agents/ap/sender/agent.kdl new file mode 100644 index 00000000..06ef2ddc --- /dev/null +++ b/cells/agent-presentation-contract/fixture/net/agents/ap/sender/agent.kdl @@ -0,0 +1,8 @@ +agent "sender" { + identity "sender" + host "ap" + role "fixture" + retired #true + workspace "$CATALOG/workspace" + command "true" +} diff --git a/cells/agent-presentation-contract/fixture/net/workspace/.keep b/cells/agent-presentation-contract/fixture/net/workspace/.keep new file mode 100644 index 00000000..ee8c1ee4 --- /dev/null +++ b/cells/agent-presentation-contract/fixture/net/workspace/.keep @@ -0,0 +1 @@ +fixture diff --git a/cells/agent-presentation-contract/fixture/task.sh b/cells/agent-presentation-contract/fixture/task.sh new file mode 100755 index 00000000..5b3724b2 --- /dev/null +++ b/cells/agent-presentation-contract/fixture/task.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +set -euo pipefail + +label="${1:?task label required}" +count_file="${CATALOG:?CATALOG must be set}/$label-count" +count=0 +test ! -f "$count_file" || count="$(cat "$count_file")" +count=$((count + 1)) +printf '%s\n' "$count" >"$count_file" +printf '%s-GENERATION-%s-b128\n' "$label" "$count" +exec sleep 300 diff --git a/docs/vrs/spec.md b/docs/vrs/spec.md index ab2b1f31..546917f9 100644 --- a/docs/vrs/spec.md +++ b/docs/vrs/spec.md @@ -81,6 +81,12 @@ and continues or stops. A speculative claim cannot become normative merely by editing the spec. A proposed behavior change updates both the canonical contract and its maintained proof cells before any implementation claims conformance. +- The model-free `agent-presentation-contract` and + `agent-presentation-continuity` cells prove the stable-identity/presentation + split, constrained authoring, exact-ID routing, atomic PTY projection, + no-restart reconciliation, durable-state continuity, and lifecycle controls. + Their accepted evidence is extracted into the stable identity and + presentation section of `AGENT-SPEC.md`. - Harness differences are recorded when the same contract needs different Claude and Codex mechanisms. They do not silently change the contract. - **R11:** Adding a scenario does not require a custom runner path. Its diff --git a/evidence/harness-exclusions.tsv b/evidence/harness-exclusions.tsv index 2ac5f66a..074e9efc 100644 --- a/evidence/harness-exclusions.tsv +++ b/evidence/harness-exclusions.tsv @@ -1,5 +1,7 @@ cell surface reason adopt-only-migration cell deterministic native adoption/replacement lifecycle probe; no harness seat +agent-presentation-continuity cell deterministic native presentation continuity probe; no harness seat +agent-presentation-contract cell deterministic native Agent Spec and PTY presentation probe; no harness seat agent-spec-resource-bindings cell deterministic native Agent Spec Resource-envelope probe; no harness seat context-resource-continuity cell deterministic native context/resource restart probe; no harness seat docs judge:cold-reader one-shot offline Claude print grader; no bus identity, DING, or hook surface