Commit 06e3e5b
feat(harness-state): add the observed-harness-state envelope record (#319)
* feat(harness-state): add the observed-harness-state envelope record
The driver-owned catalog record of what a harness is seen doing:
state (idle|active|child|ended, unknown derived-only) x blockedOn x
inputBuffer, with presence-record transport discipline (embedded origin
timestamp, atomic byte-distinct writes, own staleness constants) and a
session-liveness cross-check hook where indeterminate probes downgrade
nothing.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(harness-state): serialize writers, bound restatements, and gate derivations
Review-pass hardening of the envelope: every writer operation takes the
record's cross-process flock and treats the on-disk record as the
authoritative current state (a stale process can no longer resurrect what
it read before a peer's write, and a wrapper heartbeat re-stamps the
newest state incl. hook-written ones); an unchanged observation is a
no-op while the record is fresh and a heartbeat-equivalent re-stamp once
the refresh cadence is due (measured: an SSE producer restated ~3x/s —
restatements must not reach the transport); interrupt() marks evidence
discontinuity so a restated tuple cannot claim continuity across an
unproven interval; a predecessor session's record is never
heartbeat-eligible; the schema discriminator gates interpretation
(unsupported-schema, never definite words from an alien schema); an
unreadable record is indeterminate, never absence; and
observe_unless_ended() lets a producer whose terminal record comes from a
sibling process drop queued live frames after it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(harness-state): session-owned coalescing, schema-guarded rewrites, fenced live records, and the ask axis
A new session's first observation always writes through a matching fresh
predecessor (and marks itself discontinuous where the producer says so),
heartbeats and coalescing never touch a record whose schema or session
this writer does not own, live observations must name their pty session
(unfenced live records read unknown under a probe), IO errors are
indeterminate rather than absence, and blockedOn gains the
machine-readable ask kind (none|permission|question|review).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(harness-state): incarnation-token ownership and strictly monotonic stamps
Ownership is token equality, never a timestamp comparison: coalescing,
heartbeat eligibility, and terminal suppression all require the record to
carry this session's incarnation token (additive field, empty in
pre-token records, which no session owns). Same-millisecond takeovers and
lingering predecessor writers are both decided correctly, sibling writer
processes share one token, and every landed write is byte-distinct via a
per-record strictly monotonic stamp (bounded +1ms skew per write).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(harness-state): directional ownership sequences and trusted stamp inheritance
Ownership gains a direction: only a session claim advances the record's
monotonic seq (to disk+1), sibling writers adopt the claimer's exported
token+seq, and any writer whose claim is below the on-disk sequence is a
straggler whose live and terminal writes are refused — a lingering
predecessor can no longer replace its successor's record through the
takeover write-through. Stamps are only inherited from records inside the
future-skew trust bound (a poisoned or overflowing stamp resets to the
writer's clock, saturating throughout).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(harness-state): claims are written acts, and token-only writers never claim
A session claim is now a write under the record lock — an exitless
ended(superseded) takeover record carrying the new token and the next
sequence — so racing claimers mint distinct sequences (the dual-claim
residual dissolves) and a predecessor's still-fresh live record is
superseded at relaunch, where the pty-name probe cannot tell sessions
apart. Token-only writers adopt or start virgin records but are refused
against foreign tokens: sequences are minted only by the claim. Terminal
suppression applies only to exit-bearing records, so a session's own
claim placeholder never fences its first frames.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(harness-state): schema-refusing writers, trusted-freshness restatements, and validated asks
Non-claiming writers refuse foreign-schema records outright (their
serde-default sequence of zero is below every claim; only the written
claim supersedes an unsupported schema), an unchanged restatement is a
no-op only under a stamp a reader would trust (a beyond-skew leftover
falls through and repairs to the writer's clock), and the ask axis is
validated at the write boundary — never Unknown, and only beside a
human block.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(harness-state): wrapperless claims never supersede a live wrapper record
Found by the cycle-6 self-review of the wrapperless-succession fix: a
hook fired by any interactive session inheriting the project-scoped
registration could otherwise claim over the live wrapper and fence it
out until restart. Wrapperless claimers take over nothing, fellow
wrapperless tokens, terminal records, and staleness — never a live
wrapper-kept record.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(harness-state): atomic wrapperless claims, tri-state reads, and loud sequence exhaustion
The wrapperless claim is one act under the record lock — eligibility and
the written takeover together, so a hooks-only SessionStart racing a
wrapper's startup cannot steal the sequence between the wrapper's read
and its write — and a wrapper's fresh claim placeholder counts as owned
while an abandoned one ages into claimability. Reads are tri-state:
unreadable bytes are never a virgin seat (non-claiming writers refuse;
only the written claim supersedes, restarting sequence and counter). A
saturated sequence refuses claims loudly instead of minting shared
ownership.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(harness-state): a sequence floor across unreadable records, and IO failures are never absence
The claim writes a floor sidecar under the record lock, so a claim after
record damage continues past the damaged sequence instead of restarting
below a lingering predecessor (who would replace it and fence the new
session out); read failures other than NotFound are Unreadable, never a
virgin seat.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(harness-state): the sequence floor writes loudly and atomically
The floor exists to protect claims when the record goes unreadable, so its
own write must not fail silently or tear: stage-and-rename like the record,
and log the failure instead of swallowing it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(harness-state): the virgin token-only write persists the sequence floor
Initial ownership is established by more than the claim: a token-only
writer's first write on a virgin seat mints sequence one, so it now
persists the floor sidecar through the same loud stage-and-rename as
claim_locked — if that record later goes unreadable, a replacement claim
continues past it instead of colliding with the lingering writer.
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
* fix(harness-state): a fresh claim placeholder reads indeterminate, never definite ended
The claim placeholder is a fence, not an observation: the session wrote it
at startup and has observed nothing yet. A live seat whose harness never
publishes its first frame promptly — pi's extension failing open, for one —
read as dead for the whole freshness horizon while its process ran. Readers
now derive indeterminate with the distinct reason 'claimed' from a fresh
exitless superseded record; fencing, aging, and persistence are unchanged.
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 e282a7e commit 06e3e5b
2 files changed
Lines changed: 1961 additions & 0 deletions
0 commit comments