Repository navigation
feat: fence stale dispatch results with generations, and prepare the queue for loop attempts - #423
Merged
Merged
Conversation
rsharath
approved these changes
Oct 25, 2026
…eld tasks, loop attempt routing Every claim and reclaim of a dispatch task now bumps a generation, and every state change a worker result can cause (complete, fail, mark-running, hold, lease renewal) is guarded on that generation and a live claim. Before this, dispatchComplete updated by id alone, so a worker replaced after a reclaim could overwrite its successor's outcome or resurrect a task already terminal. A fenced result is a no-op, logged and audited as dispatch.stale_result. Adds a non-terminal `held` status that reclaim never selects, with hold/unhold on the store and dispatcher, and a `attempt` task kind carrying run_id/attempt_id for Loops (docs/loop-engineering-design.md §5, §6, §13 B). Attempts route to optional host hooks (spawnAttemptWorker, continueAttempt, onAttemptSettled, onAttemptWaiting), fail closed on a host without them, emit no fleet events, are never torn down by the dispatcher, report an approval wedge to the loop, and are never re-queued: a reclaim settles as `reclaimed` and any failure as `failed`. Additive migration: run_id, attempt_id, generation (default 0) via #addColumnIfMissing, plus a partial run_id index after the ALTERs. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…on, keep attempts off the fleet board Round-1 audit of the dispatch-generations slice: - web: classify the new `held` dispatch status (fleet lanes → awaiting, with an exhaustive default; attention queue → approval, deduped against the session's own; panel badge), fixing the TS2366 build break. - held_until column + DispatchConfig.holdMaxMs (default 24h). Reclaim settles an expired hold, and a hold placed by a previous boot of this daemon, as `reclaimed`; held attempts still count toward maxConcurrentWorkers. - Dispatcher.cancel(taskId, reason) for attempts in any non-terminal state, bumping the generation and reporting onAttemptSettled(task, 'failed'). - dispatchHold is attempt-only; hold/unhold/cancel are scoped by owner_daemon. - unhold: a hold from a previous boot settles `reclaimed` (never reads the worker's idle as success); a gone worker fails at once; a worker still waiting on an approval re-fires onAttemptWaiting. - late-start: only a session created in that call is destroyed; a late attempt worker is reported via the new onAttemptOrphanWorker hook. - #execute's catch unwatches before the stale-generation return. - attempts are excluded from fleet_tasks, the board snapshot, its deltas and its counters. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ts to 2h
- Dispatcher.unhold returns an UnholdOutcome ("resumed" | "waiting" |
"finished" | "settled" | "refused") instead of a boolean that was true only
when the attempt ended up running and watched again.
- dispatch.holdMaxMs added to DispatchSchema, the CodeoidConfig type and the
settings manifest; the default drops from 24h to 2h, since a held attempt
occupies a worker slot.
- Dispatcher.hold documents that unhold, cancel or expiry is mandatory: a held
worker is unwatched.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
saucam
force-pushed
the
feat/loops-dispatch-generations
branch
from
October 25, 2026 02:05
254ebdd to
b392d41
Compare
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.
What this changes
The dispatch queue becomes safe for long-running, interruptible work, and ready to run loop attempts (docs/loop-engineering-design.md §5–§6). It does this by extending the existing queue rather than adding a second one.
held). An attempt waiting on a person (an approval outside its profile) can be held, so its lease doesn't expire underneath it. A hold lapses afterdispatch.holdMaxMs(default 2 hours), and any restart settles it. Only attempts can be held, and only by the daemon that owns them.attemptkind runs on a loop's behalf. It is never retried by the queue (the loop decides), emits no fleet events (no conductor turns), and doesn't tear down its worker.Dispatcher.cancelstops one in any state.heldstatus.No loop uses this yet; the controller lands separately.
Review
Two independent audit rounds.
unholdreports an explicit outcome, andholdMaxMsis configurable.Full suite: 3800 pass / 0 fail. Web typecheck and fleet tests pass.
For agents: implementation
store.ts:dispatch_taskscolumnsrun_id,attempt_id,generation INTEGER NOT NULL DEFAULT 0andheld_until, plus theidx_dispatch_runindex.dispatchClaimNextanddispatchReclaimStale.generation = ? AND status IN ('claimed','running'); touch takes{id, generation}.owner_daemon. Prior-boot holds and expired holds settle asreclaimed.kind != 'attempt'.dispatch.ts:dispatch.stale_resultaudit.spawnAttemptWorker,continueAttempt,onAttemptSettled,onAttemptWaiting,onAttemptOrphanWorker.hold,unhold(returnsUnholdOutcome) andcancel.freshflag.config.ts:dispatch.holdMaxMs, also in the settings manifest.FleetTaskWire: theattemptkind,heldstatus,runId/attemptId.fleet-lanes.ts(heldmaps to awaiting, with anassertNever),attention.ts, andSessionListPane.src/tests/dispatch-generations.test.ts, 40+ cases.🤖 Generated with Claude Code