fix: close three seams the message-machine refactor left open

agent-loop lifecycle: dispose drains machine.done to true quiescence.
cancel()'s own running-to-idle transition can legitimately re-enter
through an automation listener (goal-session's idle drive runs
synchronously to its first await) and replace done with a fresh
admission after the single capture; teardown now re-cancels and
re-awaits until the slot stabilizes, so the scope never unwinds under a
live run.

tools: a nested concludeTurn() stages on its own execution and promotes
to the enclosing composite only on the call's authoritative successful
verdict. A post-execute policy that converts the nested success into an
error no longer lets a recovering composite stop the turn on a failed
terminal operation (the Code Mode structured-output shape).

goal-session: the driver owns its round durability barrier again. The
loop's persistence is eager write-behind with no turn-end flush, so the
old post-turn agent/error signal for flush failures never fires; a
settled round now sets needsCheckpoint and re-enters drive, flushing
before the next reservation and disarming on failure instead of queueing
an autonomous round on state that was never persisted.
This commit is contained in:
_Kerman
2026-07-26 20:50:02 +08:00
parent 338da9f2e0
commit 2a51ef85fb
6 changed files with 161 additions and 3 deletions

View File

@@ -192,6 +192,14 @@ export function apply(ctx: Context): void {
if (!attempt.stale) applyOutcome(state, goal, outcome)
}
if (!readyToDrive(state)) return
// The loop's persistence is eager write-behind with no turn-end flush,
// so this driver owns the round's durability barrier: checkpoint the
// settled round before reserving another (re-entering drive through
// the flush path above), disarming on failure instead of queueing an
// autonomous round on state that was never persisted.
state.needsCheckpoint = true
state.requested = true
return
}
const goal = currentGoal(state)