Skip to content

Commit d4ba08c

Browse files
schicklingclaudeschickling-assistant
authored
chore(harness-state): integration pass — docs reconciliation and proven invariant rows (#326)
* docs(invariants): add the two observed-harness-state rows their tests now prove Scoped delivery-input wakeups and the observed-harness-state discipline (derived-only unknown, byte-distinct writes, evidence-gated heartbeat, terminal-before-escalation), per CLAUDE.md's green-test gate. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(harness-state): prove the terminal record lands before SIGKILL escalation A real-wrapper subprocess proof: the wrapper runs as its own process group with a TERM-ignoring provider, st2's stop path escalates after STOP_GRACE, and the record reads ended/signal 9 — the clause the discipline row previously claimed without a covering test. The row's wording now also names the driver processes (wrapper, channel, hooks) per the multi-writer design, the restatement bound, and the new proofs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(vrs): measured limits, pi ownership split, and the DQ-H2 resolution The pi section states the channel/wrapper ownership split and the agent_settled idle edge with its why; reader-side limits gain the pty-kill pidfile removal and the codex >=0.148 pin refusal; DQ-H2 records the measured 3 Hz failure mode and its envelope fix; the ontology entry drops the reserved word 'working'. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(harness-state): synchronize the teardown proof on the provider's installed trap The status file only proved the wrapper spawned the child; a slow scheduler could let SIGTERM kill the provider inside the grace window and pass the test without exercising escalation. The provider now writes a ready marker after trap '' TERM, and the signal waits for it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(vrs): session-owned writes, fenced live records, and the ask axis Spec, requirements, and ontology align with cycle 2: the session-takeover write-through rule, ptySession required for live states (unfenced-record derivation), unreadable-record and unsupported-schema rows, the ask kind across the vocabulary and all four producer tables, stop-aware codex startup, question re-seeding on reconnect, and the probe root resolving exactly as the runner does. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(vrs): token ownership, honest Draft residuals, and the cycle-3 producer rules The Draft rationale names the real residuals (DQ-H5 and the eventless deny window) now that both captures are folded in; the record contract states incarnation-token ownership and monotonic stamps; codex documents pending-retry evidence, pi its terminal-only observer, opencode its atomic seed and full gate subset, and claude union supersession, late-hook fencing, and the hooks-only limitation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(vrs): directional ownership, preflight-first stop handling, and the cycle-4 producer rules Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(harness-state): the teardown matrix covers opencode's own stop implementation Escalation writes the cover record before the group SIGKILL, a grace-window reap rewrites it with the real exit, and the discipline row's proof list names both — proven against the real wrapper binary. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(vrs): state the dual-claim tie and hook-environment residuals Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * style: format the opencode teardown additions Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(vrs): dissolve the dual-claim residual and state the pty-kill window honestly Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(harness-state): escalation proofs assert the wrapper's own SIGKILL death A wrapper that exited any other way never exercised the escalation path; both stop-implementation escalation cases now pin the wrapper's exit signal. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(vrs): cycle-6 producer rules — guarded wrapperless claims, legacy-seat coverage, atomic seeds, degrade-not-die launches Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(vrs): cycle-7 rules — atomic wrapperless claims, stop-aware startup waits, shape-gated seeds, silence horizons Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(vrs): the opencode seed and poison guarantees match the shipped projection Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(vrs): the integration layer's claim statement is floor-aware too Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(vrs): the claim placeholder is documented as a fence, and DELTA-005 stays deleted The projection table gains the claimed row and both claim statements match the shipped reader: a fresh exitless superseded placeholder derives indeterminate (reason 'claimed') until the session's first real observation. The seed/poison guarantee now states the widened rule — any session's unreadable status word poisons, sticky terminals outrank it. The DELTA-005 file, whose resolution rule was met when the producers landed, is deleted so the status claim and the tree agree. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> agent-identity: unknown agent-persona: generalist agent-supervisor: unavailable agent-tool: OMP agent-tool-version: 18.0.3 agent-runtime: OMP 18.0.3 tooling-profile: dotfiles@f33cd9c-dirty --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: schickling-assistant <261620128+schickling-assistant@users.noreply.github.com>
1 parent 802fe21 commit d4ba08c

6 files changed

Lines changed: 425 additions & 87 deletions

File tree

INVARIANTS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ materialization, messaging, DING, or presence must preserve them.
2121
| **Agent-declared presence discipline** | The shipped bus contract requires agents to declare `busy` before executing work, use `available` only while yielding or ready, and reserve `dnd` for an explicit hold. Both native harnesses materialize that contract. Busy remains observable but does not suppress DING; fresh `dnd` is the only delivery gate. | `tests/native_only.rs::clean_path_executes_the_maintained_native_authoring_guide`; `src/ding/mod.rs::pending_delivery_ignores_busy_but_respects_fresh_dnd_archive_and_retry` |
2222
| **Stable roster JSON** | `st2 agents --json [--enrich]` preserves field names, order, null handling, presence, typed desired state and rationale, the retirement compatibility projection, opaque declared Resource descriptors, origin-timed activity, inbox counts, and the appended `observedState` object. Declared presence, desired lifecycle, and observed harness state are three independent axes: none is derived from another, and a missing observed record is `null`, never a derived `unknown`. | `src/agents.rs::agents_json_has_stable_wire_shape`; `src/agents.rs::agents_json_preserves_opaque_declared_resource_descriptors`; `src/agents.rs::observed_state_joins_declared_presence_without_touching_either`; `tests/status_agents.rs::roster_json_and_human_output_distinguish_retirement_from_presence`; `tests/status_agents.rs::roster_keeps_presence_separate_from_suspended_desired_state`; `tests/status_agents.rs::roster_uses_version_1_origin_time_for_last_activity` |
2323
| **Agent-declared presence** | Refresh preserves non-DND declared status and advances the version 1 heartbeat. A missing status starts as `available`. Legacy DND migrates without renewing its hold. Version 1 DND is not refreshed. Stale, malformed, or implausibly future heartbeats read as `unknown`. The outer Codex, Claude, and pi session wrappers own a five-minute heartbeat while their provider remains alive. | `src/status.rs::refresh_preserves_value_and_changes_heartbeat_bytes`; `src/status.rs::refresh_upgrades_legacy_dnd_without_renewing_the_hold`; `src/status.rs::refresh_missing_writes_available_default`; `src/status.rs::version_1_staleness_and_future_skew_are_bounded`; `src/status.rs::malformed_versioned_record_is_unknown_without_mtime_fallback`; `src/claude_session.rs::idle_provider_refreshes_presence_without_mcp_input`; `src/pi_session.rs::idle_pi_provider_refreshes_presence_without_channel_input`; `src/codex_app_server.rs::inbox_fallback_does_not_write_a_fifteen_second_presence_heartbeat` |
24+
| **Scoped delivery-input wakeups** | Native delivery pumps watch only their inputs: the agent's `resources/inbox` subtree and its `status` file. Runtime records written beside them by the pump's own process group — presence temp siblings, the `harness-state` record, stream state — never wake delivery, so a producer that writes on every turn boundary cannot pump its own delivery loop. | `src/watch.rs::delivery_watcher_ignores_runtime_records_but_wakes_on_inbox_and_status` |
25+
| **Observed harness state discipline** | The `harness-state` record is written only by the owning session's driver processes — wrapper, channel, or hooks — serialized by a cross-process lock and coalesced against the on-disk record, atomically and byte-distinct on every write that lands, with freshness from its embedded timestamp and never file mtime; restating an unchanged state touches the record only when the refresh cadence is due. `unknown` is derived, never written: staleness, future skew, malformation, an unsupported schema, and a provably dead pty session each read as `unknown` with a distinct reason; an indeterminate liveness probe downgrades nothing; a missing record is no observation rather than `unknown`; no absence derives a definite state. A writer that loses sight of its harness stops heartbeating instead of refreshing a state it cannot see, and a predecessor session's record is never re-stamped. A reaped provider yields a terminal `ended` record carrying its real exit — written before the stop path's SIGKILL escalation and rewritten from the escalation cover when a grace-window reap observes the real status, proven against the real wrapper binaries of both stop implementations — and never a live state. | `src/harness_state.rs::unknown_state_is_derived_and_cannot_be_written`; `src/harness_state.rs::malformed_record_is_unknown_without_mtime_fallback`; `src/harness_state.rs::staleness_and_future_skew_derive_unknown_with_distinct_reasons`; `src/harness_state.rs::a_dead_session_reads_unknown_even_while_fresh_but_ended_survives`; `src/harness_state.rs::every_landed_write_is_byte_distinct_and_fresh_restatements_do_not_write`; `src/harness_state.rs::a_chatty_producer_restating_its_state_causes_zero_writes`; `src/harness_state.rs::concurrent_writers_defer_to_the_on_disk_record_not_their_cache`; `src/harness_state.rs::a_predecessor_sessions_record_is_never_heartbeat_eligible`; `src/harness_state.rs::missing_record_reads_as_none_not_unknown`; `src/codex_app_server.rs::pump_publishes_observations_and_stops_heartbeating_on_evidence_loss`; `src/claude_session.rs::a_provider_killed_mid_turn_reads_ended_rather_than_active`; `src/claude_session.rs::a_clean_provider_exit_writes_the_terminal_record`; `tests/harness_state_teardown.rs::stop_escalation_writes_the_terminal_record_before_sigkill`; `tests/harness_state_teardown.rs::opencode_stop_escalation_writes_the_cover_record_before_sigkill`; `tests/harness_state_teardown.rs::opencode_graceful_stop_records_the_real_reaped_exit` |
2426
| **Retirement health** | A retired declaration is healthy only after every declared task ID is absent. Any live or dead declared task record reports incomplete retirement; retired declarations do not require presence. Live declarations retain their existing task and presence checks. | `tests/doctor.rs::retired_declaration_is_healthy_when_tasks_and_presence_are_absent`; `tests/doctor.rs::retired_declaration_is_unhealthy_while_a_declared_task_is_alive`; `tests/doctor.rs::retired_declaration_is_unhealthy_while_a_dead_task_record_remains` |
2527
| **Suspension health** | A suspended declaration is healthy when no declared task is live and every retained dead record is explicitly keep-pinned. It requires no presence, but this weaker result never proves retirement. Resume preserves ordinary keep and adopt-only policy. | `tests/doctor.rs::suspended_declaration_is_healthy_when_tasks_are_absent_without_presence`; `tests/doctor.rs::suspended_declaration_distinguishes_live_dead_keep_and_dead_nonkeep`; `tests/reconcile.rs::resuming_uses_ordinary_reconcile_and_does_not_override_keep` |
2628
| **Crash loops surface** | A task parked by a fail-mode restart policy notifies its supervisor once over the bus. | `tests/run.rs::surface_crash_loop_notifies_the_supervisor_over_the_bus` |

docs/vrs/.delta/DELTA-005-harness-state-specified-ahead-of-full-implementation.md

Lines changed: 0 additions & 44 deletions
This file was deleted.

docs/vrs/05-harness-state/open-questions.md

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,17 @@ hypotheses.
2525
waits on) and it under-reports nothing, but the state axis says `active`
2626
while the model is not running. Resolves by: a Claude build whose denial
2727
emits any hook event; until then the deny window is the pinned limit.
28-
- **DQ-H2 Transport cost of per-transition writes.** Presence refreshes every
29-
five minutes; turn boundaries are far more frequent, and burst coalescing
30-
measured 4 transitions per turn 0.1–0.4 ms apart. No measurement establishes
31-
what per-transition replicated writes cost on a real catalog under a real
32-
transport (OHS-T01 accepts this for v1). Resolves by: measuring write and
33-
sync volume on a live catalog; if unacceptable, a minimum-interval
34-
coalescing window is the tuning knob, at the cost of spinner latency.
28+
- **DQ-H2 Transport cost of per-transition writes — measured 2026-08-23,
29+
resolved by the restatement guard.** The live smoke run caught the failure
30+
mode: the OpenCode producer restated its state per SSE frame and the
31+
envelope re-stamped every restatement — 679 byte-distinct writes in 221 s
32+
(~2.7/s while idle). The envelope now makes an unchanged observation a
33+
no-op until the refresh cadence is due, so a seat writes on transitions
34+
plus at most one re-stamp per five minutes (a Claude turn measured 3
35+
writes; a pi turn 2–3). What remains open is only the fleet-scale sync
36+
question: nothing yet measures what transition-rate writes cost a
37+
600-seat catalog's transport over a day. Resolves by: that measurement on
38+
a live catalog.
3539
- **DQ-H3 `child` has no producer.** The word is reserved because the tuple's
3640
reasoning needs it (a long-running foreground command is neither the model
3741
working nor idle), but the producer that would have supplied it — the PTY

0 commit comments

Comments
 (0)