Fix routing misdelivery of buffered steering (#70) - #5
Merged
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Audit: agent-service routing path
Follow-on to #58. Scope: the routing path in the engine — how messages / context / steering reach the right running seat (the "live-agent registry + invoke lane"). I enumerated every routing surface, gave each a real / not-real verdict, and fixed the real ones with tests. Nothing here needed to touch beckett core; the fixes live entirely in
bored's engine/run layer.Routing surfaces enumerated
nudge()broadcast returns only the last seat's receiptrouteSignal/routeArmSignaldeliver a done-signal to the wrong cursor/armdeliverWorkerEventprocesses late events for already-resolved seatsThe two real fixes
1 — Node-targeted steering lost its target when buffered.
nudge(ref, text, node)addressed to node A, buffered because A had no live seat, was queued carrying only itstext. The fold then drained the entirependingSteersbuffer on the nextseat_spawnedof any node, folding it into that seat's brief. So steering meant for lane A could land in lane B's brief — a genuine misdelivery, exactly the class of bug the invoke-lane / live-agent-registry work exposes.Fix: the queued
nudge_deliveredevent andpendingSteersnow carry the targetnode. The fold drains — and the seat brief collects — only steers addressed to the spawning node or to no node in particular (s.node == null || s.node === node), using the same predicate on both sides so what leaves the buffer is exactly what the seat reads. Untargeted steers (the common case) behave exactly as before. Backward-compatible with pre-fix event logs (missingnode⇒ untargeted ⇒ old behavior).2 — A steer a reaped seat dropped was silently lost.
The in-memory live-seat registry can still list a seat whose child has already exited (the
finishedevent is in flight but not yet folded).handle.nudge()returns"dropped", and the engine recorded the drop without buffering — the operator's steering vanished even though a replacement seat was about to spawn.Fix: when every targeted live seat drops the steer, the engine re-queues it (as a
queuednudge_delivered) so the replacement seat picks it up. If any seat accepts it, behavior is unchanged.Tests
test/engine-worker.test.ts, in the steering (§5.5) block:Full suite: 171 passed, typecheck clean.
Notes on the non-issues (so they aren't re-litigated)
nudgebroadcasts to every live arm. The gap is only the narrow window where a steer is buffered before any arm spawns; then only the first arm'sseat_spawneddrains it. Delivering a buffered steer to all arms of a fanout visit needs batch/arm-count awareness and has ambiguous semantics ("should it reach arms that already spawned?"), so it's left as a documented limitation rather than churned on. This behavior is unchanged from before the fix.nudge()returns the last seat's receipt when broadcasting to multiple live seats; every per-seat receipt is still journalled as its ownnudge_delivered. Cosmetic.(node, visit, arm)from the seat state, and late events forfinished/aborted/refusedseats are dropped up front — both verified correct, no change.queuedreceipt tells the operator it's buffered; not-delivering is strictly better than mis-delivering.Leave this PR unmerged per the ticket.
🤖 Generated with Claude Code