Fix --skip-unchanged deadlocking a goal loop that is the only writer of its tree (#217 item 13) - #222
Conversation
…of its tree (#217) A goal loop is the only writer of its own tree, and it only writes once woken — so skipping every poll whose workspace fingerprint matches the last failing run strands an idle loop forever: the tree cannot change until the loop is woken, and the loop is only woken on a tree change. Now the skip only applies while the session is busy. Idle plus unchanged is wake-worthy: the predicate runs again (the only path on which an external watcher's change is ever seen) and the failure is re-delivered even when it reads the same as the last one. The help text no longer recommends the exact case that deadlocked, and node create warns when --skip-unchanged is paired with a predicate.
Review verdict: ✅ ApproveReviewed against issue #217 item 13 and verified on the PR head ( Correctness vs the reported deadlockThe pre-fix skip (
Details that check out:
One non-blocking nit: the new idle tests exercise the Verification on the PR branch
Fixes the bug, no regressions found. Approving. |
Review follow-up on #222: re-delivering the relay every poll on an idle, unchanged tree turns the deadlock fix into a token burner — each relay is a full agent turn, and the failure-tail dedup exists precisely to bound that. The skip path now spends its one re-delivery per fingerprint: a new failing run at a changed tree makes it available again, and polls stay quiet until then. A session with no presence reading stays skipped, now stated in the tree: the relay refuses to tell a session it cannot see idle, so falling through would only buy the predicate's price for a wake that cannot land. And the create-time advice now also prints from node update when --skip-unchanged true turns the flag on for a goal loop with a predicate, best-effort against the top-level graph the client already loads.
|
All three findings addressed in 88cf3e7:
Suite at 1299 passing; lint/format clean. |
The deadlock
With
--skip-unchanged, every goal poll captured a workspace fingerprint (git rev-parse HEAD+git status --porcelainhashed) and returned early if it matched the one recorded at the last failing run — no predicate re-run, no failure relayed (GraphStore.swift:2053-2062 at 0.1.56).For a loop that is the only writer of its tree, that is a fixed point: the loop only writes once woken, and waking was gated on a tree change. After a turn that ends without moving the tree, every poll skips forever — the ~60 min silence from issue #217 item 13.
The fix
reawakenedFingerprints) then holds the skip until the tree moves; a new failing run at a changed fingerprint makes one more wake available. Bounded: one extra predicate run and one extra relay per frozen tree, never a relay per poll — the unbounded agent-turn spend the failure-tail dedup exists to prevent stays prevented.node createandnode update --skip-unchanged trueprint the advice on stderr (stdout stays the rendered graph) viaGraphcodeCommand.createWarnings(for:)/.updateWarnings(for:currentNode:). The update path is best-effort: a node not visible at the top level (a sub-graph child — item 15) warns nobody rather than warning wrongly.Tests
Six tests in
PredicateFeedbackTestscover the new behaviour:anIdleLoopOnAnUnchangedTreeIsWokenOnceNotEveryPoll— the item 13 regression, encoding the bound: wake once, then quiet (predicate runs and deliveries both stop).aTreeChangeBuysOneMoreWakeOnTheNextFreeze— the bound is per fingerprint, not per loop lifetime.aPresencelessSessionStaysSkippedRatherThanPayingForAWakeThatCannotLand— nil presence takes the skip.anIdleLoopOnAnUnchangedTreeStillNoticesThePredicatePassing— a CI-style predicate going green on an unchanged tree still resolves the node, on the wake poll.theCreateWarningFiresOnlyForSkipUnchangedPairedWithAPredicateandtheUpdateWarningFiresWhenSkipUnchangedIsTurnedOnForAPredicatedGoalLoop— the warnings gate exactly on goal + predicate + flag.Full suite: 1299 tests passed. swiftlint 0 errors; swift-format strict clean on the touched files.
Refs #217 (item 13).