feat(harness-state): add the observed-harness-state envelope record - #319
Conversation
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>
|
Validation note (full workspace suite, this host): |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c8913eda68
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…erivations 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>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ad62a26214
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…, 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>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d503968b4e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…c 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>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: cce245c42c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
… 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>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c669388c56
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…ever 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>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f070115eae
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…ements, 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>
… 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>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6f38bb426a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…oud 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>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8044c8131b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…O 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>
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>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0c8bc39553
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| // the true successor out permanently. New sequences are minted only by [`claim`], | ||
| // the written act, and adopted from it. | ||
| None => match on_disk.as_ref() { | ||
| None => 1, |
There was a problem hiding this comment.
Persist the initial token-only ownership sequence
Fresh evidence after the sequence-floor fix is this virgin token-only path: it creates sequence 1 without updating .harness-state.seq. If that record later becomes unreadable, a replacement claim sees neither a parseable sequence nor a floor and reuses sequence 1; the lingering token-only predecessor also holds sequence 1, so its next observation passes the current.seq > seq fence and can replace the successor's record. Persist the floor when establishing this initial ownership so an unreadable first record cannot recreate equal-sequence owners.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in a8d5915 on schickling/hs-01-envelope: the virgin token-only path now persists the .harness-state.seq floor through the same loud stage-and-rename as claim_locked when it mints sequence one, so if that record later goes unreadable, a replacement claim continues past it instead of re-minting an equal sequence and alternating ownership with the lingering writer. Regression fixture: a_virgin_token_only_write_persists_the_sequence_floor.
Posted on behalf of @schickling
| field | value |
|---|---|
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 |
There was a problem hiding this comment.
Fixed in a8d5915 on schickling/hs-01-envelope: the virgin token-only path now persists the .harness-state.seq floor through the same loud stage-and-rename as claim_locked when it mints sequence one, so if that record later goes unreadable, a replacement claim continues past it instead of re-minting an equal sequence and alternating ownership with the lingering writer. Regression fixture: a_virgin_token_only_write_persists_the_sequence_floor.
Posted on behalf of @schickling
| field | value |
|---|---|
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 |
… 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
…ver 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
First slice of #268 (the activity-lease slice of #162): the generic observed-harness-state envelope.
src/harness_state.rs: aharness-staterecord besidestatuscarryingstate ∈ idle|active|child|ended(childreserved, no producer) ×blockedOn ∈ none|human×inputBuffer ∈ empty|nonempty|unknown, plus diagnosticreason, terminalexit,ptySession,sinceMs,writtenAtMs, and a monotonictransitionscounter (byte-distinct writes + compatible future history).HARNESS_STATE_STALEdeliberately not an alias ofSTATUS_STALE).unknownis derived-only through a single indeterminate constructor with a distinct reason per absence (malformed-record,stale,future-skew,session-dead); a missing record reads as no observation, notunknown.SessionLiveness): positive death downgrades a fresh live record tounknown; an indeterminate probe downgrades nothing;endedsurvives its writer by design.11 unit tests incl. byte-distinctness, coalescing, restart counter continuity, forward-vocabulary degradation to indeterminate.
Cycle-2 hardening + the
askaxis: session-owned writes (takeover write-through, producers mark session boundaries, foreign-schema records never re-stamped or coalesced),ptySessionmandatory for live states with theunfenced-recordderivation, IO errors read indeterminate rather than absent, andblockedOngains the machine-readableask ∈ none|permission|question|reviewkind (reviewreserved — no producer emits it) so consumers never branch onreason.Cycle-3: ownership became structural — the record carries the writing session's incarnation token (wrappers mint it and export it to their sibling hook/channel processes); coalescing, heartbeat eligibility, and terminal suppression compare token equality, never timestamps, deciding same-millisecond takeovers and lingering-predecessor heartbeats correctly. Every landed write is byte-distinct via strictly monotonic per-record stamps.
Cycle-4: ownership gained a direction — a monotonic
seqonly a session claim advances; stragglers from superseded sessions are refused in live and terminal paths (no timestamp comparison anywhere), stamps are never inherited from beyond the future-skew trust bound, and the two bounded residuals (same-instant dual claims; hook-environment visibility) are stated in the spec.Cycle-5: claims are now WRITTEN acts — a session claims by writing an exitless
ended (superseded)takeover record under the record lock (racing claimers mint distinct sequences; a predecessor's fresh live record is superseded at relaunch), token-only writers never claim (adopt-or-refuse), and terminal suppression applies only to exit-bearing records so the claim placeholder never fences its own session.Cycle-7: wrapperless claims are atomic (eligibility + written takeover under one lock; a wrapper's fresh placeholder counts as owned), reads are tri-state (unreadable bytes are never a virgin seat), and sequence exhaustion refuses claims loudly.
Cycle-8: a
.harness-state.seqfloor sidecar (written stage-and-rename under the record lock, loud on failure) keeps claim sequences monotonic across unreadable records, and writer-side reads are tri-state — onlyNotFoundis absence; any other IO failure is an unreadable record that refuses virgin-seat treatment.Part of the #268 stack (#319→#326). Vocabulary defined here — the DING-R12 words #268 cites come from the still-open #123, not the tree.
🤖 Generated with Claude Code
Refinement pass (adversarial review + live smoke): writer operations now take the record's cross-process flock and treat the on-disk record as authoritative (a stale process cannot resurrect what it read before a peer's write; a wrapper heartbeat re-stamps the newest state, including hook-written ones); an unchanged observation is a no-op until the refresh cadence is due — the live smoke measured an SSE producer restating ~3×/s, which must never 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; an unreadable record is indeterminate rather than absent; andobserve_unless_ended()lets a producer whose terminal record comes from a sibling process drop queued live frames after it.