Version. 0.1
Date. 2026-08-20
Status. CLOSED. Fourth CR of the H0 lane (loomworks-cr-brief-h0-native-habitat-v0_1.md §2 W4). Engine f786b83..bb2c806. Predecessors: CR-2026-226 (W7), CR-2026-227 (W1), CR-2026-229 (W2). Remaining before H0 is complete and traversable end to end: CR-2026-228, and per-binding session minting.
Vocabulary. This CR is the first written under loomworks-standing-note-we-do-not-build-for-demonstration-v0_1. The seven-step traversal is the H0 end-to-end traversal — an integration test of the runtime, with its acceptance role intact. Presentation artifacts are Boundaries-side work and sequence nothing here. Earlier CRs in this lane use the retired word and are not rewritten; the standing note is where a reader meets the correction.
Runtime direction: no conflict. O6, O7 (agent_session_silent call site), O8, O9.
Rulings cited. R3 — discharged, see §2. R1, R2; the CR-2026-227 rulings; DR-01 as amended, DR-02, DR-15, DR-18.
> Live sessions emit a heartbeat at a configured interval. "Running now" = a query over live sessions. A session that stops heartbeating without sign-out raises a session-event after a grace window (with dedupe) — loud, but not spammy.
Ruling R3 set heartbeat 10s, alarm at 3 missed beats, dedupe 5 minutes, and said that if engine load argues for slower, say so in the W4 CR and propose the number. It does not. The defaults ship as ruled. The sweep is one indexed query per scheduler tick against a partial index on live sessions; there is no load argument to make.
The grace window is derived as missed_beats × interval, not configured separately. Two independent knobs could be set to a 25-second window against a 10-second beat — a window no sequence of missed beats produces. Deriving it means the numbers cannot drift into incoherence.
agents/presence.py — record_heartbeat, sweep_silent_sessions, the three clock functions, SilentSession.
The heartbeat is evidence of presence, never an extension. Expiry reads started_at and never last_heartbeat_at. An expired session that beats is still expired, and that is asserted directly. A keepalive would be an extension by the back door, and DR-02 forecloses extension — renewal is a fresh request. Keeping the two clocks separate is what makes that structural rather than a matter of care.
Silence ends the session as abandoned, and does not merely report it. A live session row is a standing grant. An agent that has stopped heartbeating is, as far as the substrate can tell, gone; leaving its grant open means the record says an agent is working when nothing is. Tightening never waits (DR-15) and the safe default is the tighter outcome. If the agent was only slow, it requests a fresh session — the same rule renewal follows everywhere.
started_at is the fallback for a session that never beat, so such a session counts as silent rather than as permanently new.
Dedupe is per identity, not per session. The case it exists for is an agent that crashes, restarts, mints and crashes again — which without dedupe alarms every thirty seconds forever. **The operator-relevant fact is this agent keeps dying, and that is one fact however many sessions it burns through. A suppressed alarm still ends its session**: dedupe is about noise and never about the tightening, asserted separately because the failure would be silent grants left open, which is the opposite of what dedupe is for.
It rides scheduler_loop, per the Step 3 approval — and runs first, in its own transaction. Ending an abandoned session is a tightening and must not wait behind cadence work that may fail. A sweep failure is logged and the tick continues to cadences, the same contract per-cadence failures already have.
"Which agents are running right now" needed nothing from W4. It is get_live_sessions, and substrate machinery is absent from it by construction — system-kind actors hold no agent identity and can mint no session.
Stated limitation rather than discovered. The dedupe ledger is in-process; N scheduler processes could emit up to N alarms per window. The sweep itself is concurrency-safe — sessions end with a conditional UPDATE — so the failure mode is duplicate noise, never a missed alarm or a double-ended session.
test_h0_end_to_end_traversal runs mint → work → warn → stop → freeze → unfreeze → walk against the runtime in one test. The walk asserts one identity, four sessions, four distinct ends: abandoned (the silent one), signed_out (the clean one), killed (the frozen one), and one live.
It runs the shaping path because shaping is where the identity model is complete. The render path is ungoverned by sessions and cannot be traversed — a system fact, recorded in standing-notes/loomworks-scoping-per-binding-session-minting-v0_1.md, not a gap in this test and not a choice about what is easier to show.
H0 acceptance 1 — "every agent action at the named chokepoints carries (agent UUID, session id); an unlabeled agent action is impossible, not just unusual" — is a property of the running system, and it is currently false.
Two things stand between the runtime and the claim, and after the 2026-08-20 rulings they are exactly these:
| # | What | State |
|---|---|---|
| 1 | CR-2026-228 — the own-key ungated path. gated_room_complete returns before the gate when key_source != "system", so an agent on an own-key engagement reaches a model through no chokepoint. There is nothing there to label. | Open. Not yet opened as a CR. |
| 2 | Per-binding session minting. Render specialists hold per-binding durable identity while sessions mint per role; there is no role a specialist can mint under, so the render path mints nothing. | Open, filed as named scoping work with its blocker and dependencies stated. |
That is the whole list. Yes — it is still true. W4 added no new dependency: presence operates on sessions that exist and neither helps nor hinders either item.
Said as a property rather than a plan: the runtime today can produce an unlabeled agent action by two routes — an own-key model call, and any render. Both are stated as system facts in the record. Neither is managed as an exposure, and neither is avoided by choosing what to run.
Suite 3888 → 3901 passed, 68 skipped, 0 failed; the +13 is exactly the new test file. Ruff clean. mypy 536 errors, all covered by the 336-entry baseline, baseline file untouched. No migration — last_heartbeat_at was reserved in 0114 precisely so W4 would need none.
The anchor-priority count pin spoke again, 28 → 29 for agent_session_silent. Second time in this lane that literal has caught an addition rather than letting it land silently — the standing note filed at the W1 close continues to be right about which shape speaks.
2026-09-09 is defended for H0 complete and traversable end to end. It is a forcing function, not an event. W4 came in without a migration and without new dependencies. What remains is CR-2026-228 and the per-binding scoping item, and the second of those is design work with a real blocker that must not be improvised to close faster.