Skip to content

Commit f0868c3

Browse files
feat(harness-context): the pi and omp producers over one extension path (#380)
* feat(harness-context): the pi and omp producers One extension path, two harness tags. `ctx.getContextUsage()` answers the whole fill triple in one call on both harnesses and rides the ctx of every lifecycle event, so neither producer keeps an accumulator or reads a second source. The two are separate producers because the identical call means different things, which is HC-T03's second version-coupled constant: pi's `tokens` is the last assistant message's `totalTokens` (input + output + cacheRead + cacheWrite), omp's is its prompt figure alone. Measured 2026-08-29 in the credential-free labs against pi 0.84.2 and omp 18.0.9, reproduced on 18.0.3. The fixtures carry both numbers from each capture and assert the one the producer must NOT publish, so a bump that silently swaps the meaning fails rather than publishing a differently-meaning number under the same field name. Emit cadence is deliberately liberal and holds no policy: st2's write guard quantizes to 1% of the window, so at most one write per bucket entered however chatty the harness is. The finest boundary is what matters — turn-boundary-only observation was measured at 92% of pre-compaction warnings missed, because the wedge case is a single long turn, and `message_end` defeats that since each tool call and its result form their own assistant message. Three measurements shaped the compaction path. Inside pi's own `session_compact` handler `getContextUsage()` ALREADY reports `{tokens: null, percent: null}` while the window stays populated, and `getEntries()` already counts the new entry — so one frame carries the withheld reading, the trigger, and the durable count, and lands as one write. That pairing is load-bearing: an edge always writes while a withheld percent has no bucket, so an edge sent alone would publish the stale pre-compaction numbers beside it and the truth would wait for the heartbeat. omp's event names no reason at all, so its trigger is `unknown`; it does call its auto-compaction "idle" and "threshold" internally, but those words are not projected onto the event and inventing one would be a claim no capture supports. `costUsd` is the last assistant message's `usage.cost.total`, held in the stash and restated on every frame: the record replaces a reading's fields wholesale — deliberately, so a withheld number is never fabricated — so a frame from an event carrying no cost would otherwise erase it. `sessionTotalTokens` stays null on both, per HC-R16: summing it needs a producer-side accumulator whose correctness depends on having seen every message. The version pins extend the gates that exist rather than adding a mechanism. omp's launch gate admits a minor series and the fixture pins the exact builds a meaning was measured on; a new test fails if the fixture ever claims a build the gate would refuse to launch. pi has no runtime gate, so its fixture pins the flake tarball the extension check already type-checks and smokes against. Both smokes now drive each handler with three contexts — bare, fully populated, and one whose every telemetry pull throws. The bare one alone never executed the producer's body at all, which is precisely the blind spot that once let a TDZ ship green. A compile-time pin on pi's own declarations keeps the type gate's teeth where the runtime guards widen the view. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> agent-identity: dev3.direct.claude.4f8m9tm7 agent-persona: generalist agent-supervisor: unavailable agent-tool: Claude Code agent-tool-version: 2.1.250 agent-runtime: Claude Code 2.1.250 tooling-profile: dotfiles@a1a5f89 * docs(vrs): record what the pi and omp producers measured, and one delta The spec's Status section claimed no producer ships, which two of five now do. HC-R11 is partially satisfied and says so; the three unshipped harnesses still read `context: null`, which stays the honest state rather than a defect. Three facts settled while implementing pi's producer, each shaping the code rather than only its constants, and each recorded in the pi section: the nulls are already present inside pi's own `session_compact` handler (so the edge and the withheld reading are one frame and one write, which the write policy makes load-bearing rather than an optimization); `message_end` is the emit boundary because the wedge case is a single long turn; and `costUsd` must be restated from a producer-side hold, because the record replaces a reading's fields wholesale and a frame from a cost-free event would otherwise erase it. The omp section gains its two divergences from that path — omp does not null its reading at the edge, and has no `agent_settled` — and states how the version pin extends the existing launch gate. The gate admits a minor series and the fixture pins exact builds; those answer different questions and are allowed to differ, but a test now fails if they drift, because a build the fixture claims to have measured but the gate refuses to launch is evidence for a version nothing runs. The compaction table gains the one degradation path a "harness-durable" row has: a producer that cannot read the session store still sends the edge, and st2 counts it. The scope narrows silently, which is exactly why it is written down. DELTA-005 records a real contradiction rather than papering over it. HC-R16 says the record carries "the harness-reported session cost", but pi and omp report a per-message figure, and the spec's own producer table already said so — the two halves disagreed before any producer existed. Summing to a session total needs the producer-side accumulator HC-R16 refuses one field over, for `sessionTotalTokens`, for the reason that transfers unchanged: a sum whose correctness depends on having seen every message, taken by an extension that may have joined a running session, is worse than an honest smaller number. The *Harness context discipline* invariant row gains the producer half — a harness's own numerator, pinned to the build it was measured on, so neither harness's arithmetic can ship under the other's tag — with the eight new proofs named. No new row: three producers do not exist yet, and a row claiming all-harness fixture discipline would be false. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> agent-identity: dev3.direct.claude.4f8m9tm7 agent-persona: generalist agent-supervisor: unavailable agent-tool: Claude Code agent-tool-version: 2.1.250 agent-runtime: Claude Code 2.1.250 tooling-profile: dotfiles@a1a5f89 * test(harness-context): make the extension smoke prove the wire, not just the load The frame envelope between the shipped assets and `pi_channel` has its two halves in different languages in different files, and nothing coupled them. With `true` as the channel binary the frames went into a pipe nobody read, so the smoke proved "does not throw" and the Rust fixtures decoded frames written by hand in the tests. Flatten the reading or rename a key and all of it stays green while the record is never written again — a failure indistinguishable from the pre-producer state where every declaration's context reads null, and the same shape as the replication include list that Replicated-path discipline already pins names for. The channel is now a recorder that speaks a protocol-1 hello and appends every frame, and each smoke reads them back: a context frame is emitted at all, the reading carries all five keys, the numerator is each harness's own (23,425 for pi; 22,500 and explicitly not 22,525 for omp), the percent is unclamped, an edge whose session store could not be read still arrives countless, and pi's withheld reading rides the same frame as its edge. Verified non-vacuous: flattening the envelope fails the check with "a context frame must carry a `reading` object". Frames are selected by predicate rather than position, because which of the three contexts produced a given frame is not what is under test and asserting on an index would break whenever the loop gains a case. Also tolerate a fractional token count off the wire. Both harnesses round today — pi's fallback estimator is `Math.ceil(chars / 4)`, checked in the pinned tarball — so this decodes nothing observed. It exists because the failure if one ever stops rounding is silent in the worst direction: a strict integer parse returns `None` for `1234.75` and the producer would WITHHOLD a reading the harness actually had. Withholding is reserved for a harness saying it does not know (HC-R03), and spending it on a JSON number shape would make the record lie about which of those two happened. The percent leg already parsed as a float because pi and omp genuinely emit one there; the operands were stricter for no measured reason. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> agent-identity: dev3.direct.claude.4f8m9tm7 agent-persona: generalist agent-supervisor: unavailable agent-tool: Claude Code agent-tool-version: 2.1.250 agent-runtime: Claude Code 2.1.250 tooling-profile: dotfiles@a1a5f89 --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent f5ffdec commit f0868c3

11 files changed

Lines changed: 1407 additions & 57 deletions

File tree

INVARIANTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ materialization, messaging, DING, or presence must preserve them.
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` |
2424
| **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` and `harness-context` records with their locks and staged siblings, 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` |
2525
| **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` |
26-
| **Harness context discipline** | The numeric `harness-context` record is a sibling of `harness-state` and shares none of its ownership machinery: `incarnation` is provenance and never a fence, and a straggler's write lands rather than being refused. A reading is written only when it enters a different bucket of `usedPercent`, when a compaction edge arrives, or when the record is older than the heartbeat, so a chatty producer cannot inflate the write rate; every landed write is byte-distinct, and freshness comes from the record's own `observedAtMs`, never file mtime. A withheld value is carried as `null` and never fabricated from zero, the previous reading, or a division st2 could have done itself, and a reading above the window is carried unclamped. There is no `unknown` on this axis: past the horizon the reading is returned marked `stale` with its age, surviving every derivation the categorical record makes, while an unparseable record, a foreign schema, an uninterpretable harness, and an untrustworthy clock each read as absent. The relaunch claim removes the record, and Doctor's high-reading and stale-record lines are advisory in both directions and never change its exit status. | `src/harness_context.rs::a_reading_inside_the_written_bucket_does_not_write_and_a_crossing_does`; `src/harness_context.rs::a_withheld_percent_has_no_bucket_and_only_a_compaction_or_heartbeat_writes`; `src/harness_context.rs::a_record_older_than_the_heartbeat_is_rewritten_and_every_landed_write_is_byte_distinct`; `src/harness_context.rs::a_compaction_always_lands_with_its_trigger_and_may_carry_a_durable_count`; `src/harness_context.rs::withheld_values_are_null_and_are_never_fabricated`; `src/harness_context.rs::a_reading_above_the_window_is_carried_unclamped`; `src/harness_context.rs::a_stale_reading_is_returned_with_its_age_rather_than_derived_away`; `src/harness_context.rs::freshness_comes_from_the_record_bytes_and_never_from_file_mtime`; `src/harness_context.rs::additive_fields_decode_but_foreign_schema_and_harness_read_as_nothing`; `src/harness_context.rs::a_record_beyond_the_future_skew_bound_reads_as_nothing`; `src/harness_context.rs::a_straggler_lands_and_is_visible_as_provenance_rather_than_being_refused`; `src/harness_state.rs::the_relaunch_claim_removes_the_harness_context_record`; `src/agents.rs::context_is_a_fourth_axis_that_survives_an_indeterminate_observed_state`; `tests/doctor.rs::harness_context_doctor_lines_are_advisory_and_never_change_the_exit_status` |
26+
| **Harness context discipline** | The numeric `harness-context` record is a sibling of `harness-state` and shares none of its ownership machinery: `incarnation` is provenance and never a fence, and a straggler's write lands rather than being refused. A reading is written only when it enters a different bucket of `usedPercent`, when a compaction edge arrives, or when the record is older than the heartbeat, so a chatty producer cannot inflate the write rate; every landed write is byte-distinct, and freshness comes from the record's own `observedAtMs`, never file mtime. A withheld value is carried as `null` and never fabricated from zero, the previous reading, or a division st2 could have done itself, and a reading above the window is carried unclamped. There is no `unknown` on this axis: past the horizon the reading is returned marked `stale` with its age, surviving every derivation the categorical record makes, while an unparseable record, a foreign schema, an uninterpretable harness, and an untrustworthy clock each read as absent. The relaunch claim removes the record, and Doctor's high-reading and stale-record lines are advisory in both directions and never change its exit status. Where a producer ships, its numerator is the harness's own and is pinned to the build it was measured on: pi's `tokens` is the last assistant message's `totalTokens` and omp's is its prompt figure, so neither harness's arithmetic can be published under the other's tag, and a harness bump that changes a number's meaning without changing its shape fails a fixture rather than shipping. A harness that positively reports it does not know its own occupancy is forwarded as withheld in the same write as the compaction edge that emptied it, a compaction edge whose harness names no reason yields `unknown` rather than an invented word, and a durable count the producer cannot read narrows to st2 counting edges rather than losing them. | `src/harness_context.rs::a_reading_inside_the_written_bucket_does_not_write_and_a_crossing_does`; `src/harness_context.rs::a_withheld_percent_has_no_bucket_and_only_a_compaction_or_heartbeat_writes`; `src/harness_context.rs::a_record_older_than_the_heartbeat_is_rewritten_and_every_landed_write_is_byte_distinct`; `src/harness_context.rs::a_compaction_always_lands_with_its_trigger_and_may_carry_a_durable_count`; `src/harness_context.rs::withheld_values_are_null_and_are_never_fabricated`; `src/harness_context.rs::a_reading_above_the_window_is_carried_unclamped`; `src/harness_context.rs::a_stale_reading_is_returned_with_its_age_rather_than_derived_away`; `src/harness_context.rs::freshness_comes_from_the_record_bytes_and_never_from_file_mtime`; `src/harness_context.rs::additive_fields_decode_but_foreign_schema_and_harness_read_as_nothing`; `src/harness_context.rs::a_record_beyond_the_future_skew_bound_reads_as_nothing`; `src/harness_context.rs::a_straggler_lands_and_is_visible_as_provenance_rather_than_being_refused`; `src/harness_state.rs::the_relaunch_claim_removes_the_harness_context_record`; `src/agents.rs::context_is_a_fourth_axis_that_survives_an_indeterminate_observed_state`; `tests/doctor.rs::harness_context_doctor_lines_are_advisory_and_never_change_the_exit_status`; `src/pi_channel.rs::the_pi_0_84_2_fixture_pins_total_tokens_as_the_numerator`; `src/pi_channel.rs::the_omp_18_0_9_fixture_pins_prompt_input_as_the_numerator`; `src/pi_channel.rs::a_pi_compaction_withholds_the_reading_it_emptied_in_the_same_write`; `src/pi_channel.rs::an_omp_compaction_yields_unknown_because_the_event_names_no_reason`; `src/pi_channel.rs::an_unreadable_durable_count_degrades_to_counting_edges_not_to_losing_them`; `src/pi_channel.rs::context_frames_decode_conservatively_or_not_at_all`; `src/pi_channel.rs::the_measured_pi_release_is_the_one_the_extension_gate_pins`; `src/omp_session.rs::the_measured_context_builds_are_admitted_by_this_gate` |
2727
| **Replicated-path discipline** | st2 pins the exact driver-record names it expects the replication transport's include list to carry — `harness-state` and `harness-context` — and both readers derive their paths from that list, so a rename cannot silently stop replication in a repository that does not own the list. A harness-context write never creates a staging file inside the agent directory: it stages outside the agent subtree, on the same filesystem, and leaves the record plus its own permanent lock and nothing else behind. `harness-state` still stages beside itself, pending its own change; the shared write helper takes the staging directory as an argument precisely because the two answer this differently. | `src/harness_context.rs::the_replicated_driver_record_names_are_pinned`; `src/harness_context.rs::writes_stage_outside_the_agent_subtree_and_leave_nothing_behind` |
2828
| **Status-line slot chaining** | Claude's `statusLine` is a single slot whose winning declaration replaces the others outright, and `.claude/settings.local.json` — the file st2 materializes — wins over every other settings file. So st2's entry there is a tee: it records the reading and then invokes the operator's own renderer, resolved from `$ST_CLAUDE_STATUSLINE_RENDERER` and then `~/.claude/statusline-renderer.json`, first hit wins and never both. Where no renderer resolves the payload is passed through byte-for-byte, and a recording that fails still renders — the degraded case is always a status line, never a blank one. | `tests/claude_statusline.rs::the_tee_records_the_reading_and_hands_the_same_payload_to_the_env_renderer`; `tests/claude_statusline.rs::the_operator_file_supplies_the_renderer_when_no_variable_does`; `tests/claude_statusline.rs::the_variable_wins_over_the_file_and_the_file_is_never_also_run`; `tests/claude_statusline.rs::with_no_renderer_the_tee_passes_its_stdin_through_byte_for_byte`; `tests/claude_statusline.rs::a_recording_failure_still_renders_the_status_line`; `tests/claude_statusline.rs::without_st2_on_path_the_script_itself_falls_back_to_the_payload`; `tests/claude_statusline.rs::the_rendered_registration_names_the_chaining_tee_and_carries_the_whole_slot` |
2929
| **Native-driver diagnostics are bounded and recoverable** | One additive-tolerant `st2.driver-diagnostic.v1` snapshot exposes the earliest failing native-driver boundary through closed stage/reason/source/support vocabulary and origin-timed evidence age. Absent, malformed, foreign, unknown, and wrongly paired records never read healthy. Stage recovery clears only its own failure and removes the record after final recovery without changing prompt submission, retry, read-back, or archive semantics. Roster and Doctor share the typed reader/repair policy; telemetry labels contain only bounded stage/reason/source/support/outcome values, never versions, identities, prompts, messages, or paths. | `src/driver_diagnostic.rs::every_stage_reason_and_source_has_bounded_wire_vocabulary`; `src/driver_diagnostic.rs::additive_fields_decode_but_malformed_foreign_and_unknown_records_are_indeterminate`; `src/driver_diagnostic.rs::recovery_clears_only_its_stage_and_reveals_the_next_failure`; `src/opencode_session.rs::delivery_and_read_back_boundaries_publish_and_clear_diagnostics_without_changing_retry`; `src/metrics.rs::driver_diagnostic_metric_attributes_are_exactly_the_bounded_axes`; `tests/doctor.rs::native_driver_diagnostic_roster_and_doctor_agree_and_recovery_clears` |
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# DELTA-005: `costUsd` is a per-message figure on pi and omp, not a session cost
2+
3+
Status: open
4+
5+
## Divergence
6+
7+
Ratified [`HC-R16`](../08-harness-context/requirements.md) says the record
8+
carries "the harness-reported **session** cost", and the record's own field
9+
description in [`08-harness-context/spec.md`](../08-harness-context/spec.md)
10+
repeats it: "`costUsd` is the harness-reported session cost, in the harness's own
11+
accounting."
12+
13+
The pi and omp producers shipped on 2026-08-29 publish the **last assistant
14+
message's** `usage.cost.total`, which is what those two harnesses actually
15+
report.
16+
17+
The other three harnesses are not affected: Claude's `cost.total_cost_usd` and
18+
OpenCode's `session.info.cost` genuinely are session totals, and Codex reports no
19+
cost at all.
20+
21+
## VRS
22+
23+
[HC-R16](../08-harness-context/requirements.md) requires the adjacent facts to be
24+
carried as the harness reported them and nothing more, and names the cost fact a
25+
session cost. The spec's own producer table — HC-R16's "which adjacent facts each
26+
channel supplies" — already said "per-message `usage.cost.total`" for both rows,
27+
so the two halves of the spec disagreed with each other before any producer
28+
existed, and the implementation had to pick one. The pi and omp producer sections
29+
in [`08-harness-context/spec.md`](../08-harness-context/spec.md) state the
30+
divergence at the point of use.
31+
32+
## Implementation
33+
34+
Turning pi's or omp's per-message cost into a session total means summing every
35+
message's `usage.cost.total` in the producer. That is precisely the
36+
producer-side accumulator HC-R16 already refuses one field over, for
37+
`sessionTotalTokens`, and the reasoning transfers unchanged: the sum's
38+
correctness depends on having observed every message, an extension loaded into a
39+
session that is already running has not, and a half-observed total is a worse
40+
answer than an honest smaller one. Nothing else in st2 reconciles this number —
41+
HC-R16's "carried as what the harness reported and nothing more" is the binding
42+
half of the requirement, and a fabricated sum would violate it in order to
43+
satisfy the word "session".
44+
45+
The alternative — writing `null` — was rejected because the per-message figure
46+
is real, is what pi and omp show their own operators, and is strictly more
47+
information than nothing.
48+
49+
One consequence is load-bearing in the implementation and is documented in the
50+
producer sections: because the record replaces a reading's fields wholesale,
51+
a frame emitted from an event that carries no cost would *erase* the published
52+
one. The extension holds the last assistant cost and restates it on every frame,
53+
and clears the hold on session replacement.
54+
55+
## Direction
56+
57+
update VRS
58+
59+
## Resolution Signal
60+
61+
HC-R16 amended so that the adjacent cost fact is "the harness-reported cost, at
62+
whatever scope that harness reports it, stated per harness in the spec's producer
63+
table" — which is what all five rows then describe consistently — and the
64+
record's field description in the spec widened to match. Until then, the producer
65+
table is the operative statement for pi and omp, and consumers must read the
66+
`harness` discriminator before comparing this field across harnesses — a
67+
comparison that is already required for `usedTokens`, whose meaning differs
68+
between pi and omp for unrelated reasons (HC-T03).

0 commit comments

Comments
 (0)