feat(runtime): bump session.local_t via ResolveSessionForEnvelope (closes §M13 sub-program)#33
Conversation
Closes the §M13 sub-program `session-actor-agent-lookup` flagged at T=168. Surfaced concretely T=174 ~00:45 by Guido during Phase A: 24 acknowledged Cowork rewrites left session:sam.laptop-cowork-workspace.local_t = 0 because the agent-actor inferred path skipped bumpSessionLocalT entirely. The old code in Apply (~line 173) and ApplyProgram (~line 293) only incremented local_t when env.Actor was itself a session node. Agent actors — which is the common case once §M11 inferred-session resolution went live — fell through. Result: heartbeat dead for almost all work. Fix uses operad.ResolveSessionForEnvelope (already used by §M11 gate) to identify the resolved session URN regardless of: - session-as-actor (existing path, regression-checked) - agent-as-actor with single has-occupant (inferred — the new fix path) - agent-as-actor with explicit env.SessionURN Multi-session safety intrinsic: ResolveSessionAmbiguous returns no session URN, so no bump fires. The §M11 gate would have rejected the envelope upstream regardless. Kernel-actor system-internal envelopes (sweep emissions, infrastructure ADDs through allowlist) take ResolveSessionAbsent → no bump. Correct; they aren't session-scoped work. ApplyProgram dedupe key changes from env.Actor to the resolved session URN, so a batch where two agents both occupy the same session ticks local_t once (not twice). Tests added in m13_localt_test.go cover all 7 paths: - actor-is-session (regression) - agent-actor inferred (the fix) - agent-actor explicit - ambiguous (gate rejects, no bump) - kernel-actor allowlist (no bump) - batch dedupe by session, not actor - batch ticks each session once when actors span sessions go test ./... clean. Doctrine note in ffs0: kb/research/kernel/20260417-t187-kernel-proper.md §M13 paragraph cites this PR as the closing path (T=175 update). Plan: ~/.claude/plans/valiant-kindling-sunrise.md Phase E.2. Design: ffs0/dev/scripts/ops/t175-e2-kernel-pr-bump-session-local-t.md. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR fixes session local_t not incrementing for agent-actor rewrites by resolving the session context via operad.ResolveSessionForEnvelope (explicit/inferred/session-actor), and applies the bump once per resolved session in batched programs.
Changes:
- Add
Runtime.resolveSessionForBumpto map resolver outcomes to a(sessionURN, ok)decision for bumpinglocal_t. - Update
Runtime.applyWithOptionsto bumplocal_tbased on resolved session context (not justactorNode.TypeID == "session"). - Update
Runtime.ApplyProgramto dedupe bumps by resolved session (not by actor), and add §M13-focused tests.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| internal/kernel/runtime.go | Uses session-context resolver to decide local_t bumps in Apply and ApplyProgram, and dedupes bumps by session in batches. |
| internal/kernel/m13_localt_test.go | Adds tests covering explicit/inferred/session-actor bumping and batch dedupe behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // §M13: increment local_t for the resolved session, regardless of | ||
| // whether actor is the session itself, an agent inferring via | ||
| // has-occupant, or an explicit env.SessionURN. Closes the | ||
| // session-actor-agent-lookup sub-program from T=168. | ||
| if sessionURN, ok := rt.resolveSessionForBump(env); ok { | ||
| rt.bumpSessionLocalT(sessionURN) | ||
| } |
There was a problem hiding this comment.
resolveSessionForBump() is called after rt.state has already been advanced to next. Because ResolveSessionForEnvelope depends on has-occupant relations, an envelope that changes occupancy (e.g., UNLINK/LINK rotating the actor out of their only session) can cause the post-apply resolver to return Absent/Ambiguous and skip bumping local_t, even though the envelope was accepted under §M11 using the pre-state session context. Consider resolving the session to bump against the pre-apply state (e.g., compute sessionURN before fold.Evaluate/rt.state=next and then bump using that captured URN).
| // §M13: bump local_t once per unique resolved session in the batch. | ||
| // Dedupe by session URN (not by actor) so a single batch from one agent | ||
| // to one session counts as one tick, while multi-session batches tick | ||
| // each session once. Closes the session-actor-agent-lookup sub-program | ||
| // from T=168. | ||
| { | ||
| seen := make(map[graph.URN]bool) | ||
| seenSessions := make(map[graph.URN]bool) | ||
| for _, env := range envelopes { | ||
| if !seen[env.Actor] { | ||
| seen[env.Actor] = true | ||
| if actorNode, ok := rt.state.Nodes[env.Actor]; ok && actorNode.TypeID == "session" { | ||
| rt.bumpSessionLocalT(env.Actor) | ||
| } | ||
| sessionURN, ok := rt.resolveSessionForBump(env) | ||
| if !ok { | ||
| continue | ||
| } | ||
| if !seenSessions[sessionURN] { | ||
| seenSessions[sessionURN] = true | ||
| rt.bumpSessionLocalT(sessionURN) | ||
| } |
There was a problem hiding this comment.
ApplyProgram resolves sessions for bumping local_t against rt.state after the whole program has been folded and committed. If the program itself rotates/changes has-occupant relations for the actor(s), the post-commit state can resolve to a different session (or Absent) than the session context that §M11 validated against the batch-initial state, leading to wrong/missed local_t bumps. Suggest collecting the sessions-to-bump during the preflight (or by resolving against the batch-initial state used for §M11) and then bumping those captured session URNs once after commit.
| // TestApply_NoBump_Ambiguous — agent occupying TWO sessions emits without | ||
| // explicit SessionURN. §M11 gate rejects upstream (returns Ambiguous); | ||
| // neither session ticks. Defensive double-check at the bump path. | ||
| func TestApply_NoBump_Ambiguous(t *testing.T) { | ||
| rt := newM13Runtime(t) | ||
| agentURN := graph.URN("urn:moos:agent:claude") | ||
| sessionA := graph.URN("urn:moos:session:sam.a") | ||
| sessionB := graph.URN("urn:moos:session:sam.b") | ||
| injectOccupancy(rt, sessionA, agentURN, "agent") | ||
| injectOccupancy(rt, sessionB, agentURN, "agent") | ||
|
|
||
| env := graph.Envelope{ | ||
| RewriteType: graph.ADD, | ||
| Actor: agentURN, | ||
| NodeURN: "urn:moos:program:x", | ||
| TypeID: "program", | ||
| } | ||
| if _, err := rt.Apply(env); err == nil { | ||
| t.Fatalf("ambiguous liveness should reject; got nil error") | ||
| } |
There was a problem hiding this comment.
There isn’t a regression test covering an envelope/program that modifies occupancy (e.g., UNLINK/LINK rotating the actor out of their only has-occupant session) where session resolution can change between pre- and post-apply state. Adding a test that asserts the originating session still gets its local_t tick would help prevent subtle future breakage.
Addresses Copilot review on PR #33: resolveSessionForBump was running after rt.state had been advanced to `next`, so envelopes that rotate has-occupant (e.g. UNLINK + re-LINK) would resolve against post-state and miss/misroute the bump despite §M11 having validated against pre-state. Apply path: capture sessionURN BEFORE rt.state = next; bump after commit using captured URN. ApplyProgram path: capture all sessions-to-bump against batch-initial rt.state (mirroring §M11 preflight semantics — both checkLivenessM11 and the bump now resolve against the same view), populate the dedupe set, then bump after commit. Adds TestApply_BumpsLocalT_RotatesOccupancy: emits an UNLINK that removes the only has-occupant edge between actor and session and asserts that local_t still ticks. Without the pre-state capture the test fails with local_t=0 because post-state resolution returns Absent. Test registry extended with WF19 declaration so UNLINK validation passes; mirrors v3.10+ D19.1 grammar_fragment shape minimally. go test ./... clean. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
Thanks @copilot — caught a real correctness bug. Pushed Pre-vs-post state for resolution (lines 177 + 305): Both Regression test for occupancy rotation: added The test registry needed a minimal WF19 declaration so the UNLINK validates — added with
|
Confirmed — |
Summary
Closes the §M13 sub-program
session-actor-agent-lookupflagged at T=168 inkb/research/kernel/20260417-t187-kernel-proper.mdline 430, surfaced concretely at T=174 ~00:45 CEST during Phase A on hp-laptop: 24 acknowledged Cowork rewrites leftsession:sam.laptop-cowork-workspace.local_t = 0because the agent-actor inferred-session path skippedbumpSessionLocalTentirely.The old guard in
Apply(~line 173) andApplyProgram(~line 293) only fired whenenv.Actorwas itself a session node. Agent actors — which became the common case once §M11 inferred-session resolution went live — fell through.What changes
Runtime.resolveSessionForBumpwrapsoperad.ResolveSessionForEnvelopeand returns(sessionURN, ok)for the three resolver outcomes that should tick local_t (Explicit, Inferred, ActorIsSession), and("", false)for the three that shouldn''t (Ambiguous, ExplicitMismatch, Absent).Applyswaps theactorNode.TypeID == "session"check forresolveSessionForBump.ApplyProgramswaps dedupe-by-actor for dedupe-by-resolved-session — a batch where two agents both occupy one session ticks once, batches spanning sessions tick each session once.Multi-session safety is intrinsic: ambiguous envelopes never reach the bump path because §M11 rejects upstream; if one slips through (defensive), the resolver returns
Ambiguous→ no bump. Kernel-actor system-internal envelopes (sweep, infrastructure ADDs through the allowlist) takeAbsent→ no bump, which is correct (they''re not session-scoped work).Files
internal/kernel/runtime.go— 3 hunks:Applypath,ApplyProgrampath, new helper afterbumpSessionLocalTinternal/kernel/m13_localt_test.go— new file, 7 testsTest plan
7 cases in
m13_localt_test.go, all passing:TestApply_BumpsLocalT_ActorIsSession— regression check on existing session-actor pathTestApply_BumpsLocalT_ActorIsAgent_Inferred— the fix — single-session agent emits without explicit SessionURN; reverse-lookup resolves; local_t ticksTestApply_BumpsLocalT_ActorIsAgent_Explicit— agent emits with matching env.SessionURNTestApply_NoBump_Ambiguous— agent occupies two sessions; gate rejects; neither session ticksTestApply_NoBump_KernelActor— kernel-actor allowlist envelope; no session ticksTestApplyProgram_DedupesBySessionNotActor— two-agent batch on one session ticks onceTestApplyProgram_DedupesAcrossAgents— two-agent batch on two sessions, each ticks oncego test ./...clean across all packages.Post-merge — rebuild + restart sequence
hp-z440.{primary, lola, menno, moos}) via federation startup script. Verify each/healthzreports new binary tip.kernel:hp-laptop.primary. Verify/healthz+ log replay clean.kb/research/kernel/20260417-t187-kernel-proper.md§M13 paragraph with this PR''s merge SHA + verification log_seq.Cross-references
~/.claude/plans/valiant-kindling-sunrise.mdPhase E.2 (private workspace)ffs0/dev/scripts/ops/t175-e2-kernel-pr-bump-session-local-t.md(private workspace)kb/research/kernel/20260417-t187-kernel-proper.md§M13 (private workspace; T=175 update paragraph cites this PR)🤖 Generated with Claude Code