You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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>
Copy file name to clipboardExpand all lines: docs/vrs/05-harness-state/spec.md
+66-31Lines changed: 66 additions & 31 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -55,10 +55,11 @@ One JSON object, atomically written (tmp sibling + rename), newline-terminated:
55
55
"harness": "codex | claude | pi | opencode",
56
56
"state": "idle | active | child | ended",
57
57
"blockedOn": "none | human",
58
+
"ask": "none | permission | question | review",
58
59
"inputBuffer": "empty | nonempty | unknown",
59
60
"reason": "<diagnostic, optional>",
60
61
"exit": "<ended only: e.g. 'exit 0', 'signal 9', optional>",
61
-
"ptySession": "<session name for the liveness cross-check, optional>",
62
+
"ptySession": "<the wrapper's runtime/task ID; required for live states>",
62
63
"sinceMs": 1787690000000,
63
64
"writtenAtMs": 1787690300000,
64
65
"transitions": 41
@@ -71,11 +72,29 @@ Field rules, matching `src/harness_state.rs`:
71
72
(`DQ-H3`). Readers decode both, plus unrecognized future words as `unknown`.
72
73
-`blockedOn` unrecognized words decode indeterminate, never `none` — a v2
73
74
axis value must not read as "not blocked".
75
+
-`ask` names what kind of human ask holds the harness, machine-readably —
76
+
the axis consumers filter on (`reason` stays diagnostic). Meaningful only
77
+
while `blockedOn` is `human`; writers emit `none` otherwise, records from
78
+
writers predating the axis default to `none`, and unrecognized future words
79
+
decode indeterminate.
74
80
-`reason` is diagnostic only; no consumer branches on it.
81
+
-`ptySession` fences every live state: a writer refuses a live observation
82
+
that names no session (only `ended` may omit it), because a live record the
83
+
probe cannot check would stay definite straight through an external SIGKILL.
75
84
-`sinceMs` is when the current state was entered and survives heartbeat
76
85
re-stamps; `writtenAtMs` is the heartbeat. `transitions` is a monotonic
77
86
counter continued across writer restarts; with `writtenAtMs` it keeps every
78
87
write byte-distinct.
88
+
- Writes are session-owned. A restatement is a no-op only against a record
89
+
the same session already wrote: a new session's first observation always
90
+
writes through a matching fresh predecessor (otherwise the takeover never
91
+
claims the record and the heartbeat-eligibility gate lets it quietly age
92
+
out mid-turn), and every producer marks its session boundary discontinuous
93
+
— a long-lived writer starts interrupted, Claude's `SessionStart` hook
94
+
interrupts — so `sinceMs` never spans a restart. Heartbeats and coalescing
95
+
never touch a record whose schema or session the writer does not own; a
96
+
foreign-schema record is left byte-identical by heartbeats and replaced
97
+
wholesale by a genuine observation.
79
98
- Deserialization is additive-tolerant (no `deny_unknown_fields`): a reader
80
99
may be older than its writer.
81
100
@@ -90,11 +109,14 @@ What a reader reports, in evaluation order:
90
109
| Evidence | Reads as | Reason |
91
110
| --- | --- | --- |
92
111
| No record file | no observation (`null`) | never observed ≠ `unknown`|
112
+
| File exists but cannot be read |`unknown`|`unreadable-record`; an IO error is indeterminate, never absence |
93
113
| Unparseable / non-v1-shaped bytes |`unknown`|`malformed-record`; never falls back to mtime |
114
+
|`schema` is not `st2.harness-state.v1`|`unknown`|`unsupported-schema`; a future schema's words may be spelled like this version's while meaning something else |
94
115
|`writtenAtMs` > now + 60 s |`unknown`|`future-skew`|
95
116
|`writtenAtMs` ≤ now − 15 min |`unknown`|`stale`|
96
117
| Literal `unknown` state (never written by this crate) |`unknown`|`literal-unknown`|
97
118
| Live state, same-host probe proves `ptySession` dead |`unknown`|`session-dead`|
119
+
| Live state, probe available, record names no `ptySession`|`unknown`|`unfenced-record`; nothing to check is not the same as checked |
98
120
| Live state, probe indeterminate | the recorded state | unprovable evidence downgrades nothing |
99
121
|`ended`, any probe result |`ended`| a terminal record outlives its writer |
100
122
| Otherwise | the recorded tuple | — |
@@ -116,24 +138,29 @@ The projection reads the state the control pump already maintains; it adds no
116
138
observation path. `Held` never enters the published vocabulary — it is the
117
139
complement of steerable, a delivery predicate (decision 0001's boundary).
118
140
119
-
|`CodexObservedState`| state | blockedOn | reason |
120
-
| --- | --- | --- | --- |
121
-
|`AwaitingStatus`|*withhold*| — | no evidence yet; the record ages |
0 commit comments