Repository navigation
LOU-U7, LOU-U8, LOU-U9: durable execution gaps (approval mid-batch, resume with new input, checkpoint after each LLM turn) - #68
Merged
Conversation
LOU-U7: an approval in the middle of a tool batch kept the transcript invalid - calls after the paused one never ran and got no result. The approval snapshot now records `remainingToolCalls`; resumeAfterApproval() records the paused call's result (or rejection) and AgentExecutor runs the remaining calls through the normal batch path, so they can run, fail validation or pause again (the resumed run defaults to the same approvalStore). Snapshots without the field resume as before (later calls do not run) but each unanswered call gets a "not run" error result. LOU-U8: execute() with a sessionId now honours new input. Checkpoints carry a `status`: an unfinished run resumes and new input is queued behind its pending tool results (a re-sent starting message is a retry, not appended); a finished run is kept (no longer deleted) and continues as a conversation; a run awaiting approval throws SessionAwaitingApprovalError. LOU-U9: the model's tool-call turn is checkpointed before any tool runs, and a resumed transcript whose last turn has unanswered calls runs exactly those calls without calling the model. Tools receive `toolCallId` in their execute options (non-sandbox path) as an idempotency key for the at-least-once re-run. Tests that encoded the old behaviour (checkpoint deleted on finish, input ignored on resume, later batch calls not run, empty execute options, checkpoint save counts) were updated. Docs: docs/durable-execution.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Resolves overlaps with LOU-V2 (agent.stream()): the resumed pending tool calls are bracketed by the same step events, and pauseForApproval writes the awaiting-approval checkpoint before emitting approvalRequested. Also removes the conflict markers main's README carried. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-execution Keeps both intents: checkpoints carry status/approvalId and LOU-V5 usage; approval snapshots carry remainingToolCalls and usage; resumed pending and remaining tool calls run through the same batch path, so they get the same accounting, and a resumed run continues the stored totals (a new turn of a finished session starts from zero). ToolRunContext gains toolCallId, so the tool execute options carry toolCallId, abortSignal and onDelegatedUsage. New tests assert usage continuity across crash-resume and approval resume. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
runRegistry's synthetic flow result and debugController.test's onLLMResponse call predate LOU-V5's RunUsage / three-argument onLLMResponse (main's agent-forge server typecheck failed on them); compileHooks.test now matches the tool options with objectContaining since they also carry onDelegatedUsage. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Takes main's agent-forge LOU-V5 fixes (emptyRunUsage, objectContaining) over the equivalent ones on this branch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ecution Resolves conflicts between the durable-execution changes and main's sub-agent inheritance work: - AgentExecutor: approval snapshots store the base agent (main) and the queued input at the end (LOU-U8); savePause writes the approval record first, then the 'awaiting-approval' checkpoint, for tool and sub-agent pauses alike. The sub-agent suspension list now lives in runBatch, which the durable-execution branch split out of runStep. - resume: the sub-agent resume path (LOU-Y1) is kept and the turn's remaining calls are still closed/left pending per LOU-U7; the approved tool gets toolCallId (LOU-U9) alongside its sub-agent scope. - toolCallExecution: tools get both onDelegatedUsage and toolCallId. - subagentRuntime: a run paused on a sub-agent keeps queued input in its snapshot, like a run paused on a tool. - README/llms-full.txt: keep the durable-execution link and main's "Sub-agents and delegation" heading. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F3s6hrJ2zhYHbSjvVCSEjZ
This was referenced Oct 1, 2026
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
Three durable-execution correctness gaps, fixed together because they share one mechanism: on start,
AgentExecutornow finds tool calls of the transcript's last model turn that have no result (splitPendingTurn(), newsrc/execution/transcript.ts) and runs exactly those through the normal LOU-V3 batch path before calling the model.ExecutionSnapshot.remainingToolCallsrecords the calls after the paused one.resumeAfterApproval()records the paused call's result (approved) or a structured{ error, note }rejection, then the executor runs the remaining calls. They can run, fail validation, or pause again with a new approval, so the chain keeps going. The resumed run now uses the sameapprovalStoreby default. Snapshots saved before this change (no field) resume the way they did: later calls do not run. Each of them gets a "not run" error result so the transcript is valid.Checkpoint.status: 'in-progress' | 'awaiting-approval' | 'finished'(plusapprovalId):input: []just resumes.steps,usageandtoolCallsrestart at zero.SessionAwaitingApprovalError(sessionId,approvalId). It namesresumeAfterApproval.toolCallIdin their execute options (non-sandbox path; the sandbox path andmessagesstay with LOU-U15) to use as an idempotency key.Why queue instead of reject for (a)
Rejecting would break the most valuable case: interrupt-and-redirect, where the user aborts and sends a correction. Queueing is always provider-valid because the message is placed after the pending results. Treating the starting message as a retry keeps "call execute() again with the same request after a crash" working without duplicating the question.
Behaviour changes (tests updated, called out)
'finished'instead of deleted. Tests that asserted deletion or "fresh input on a reused session" were changed:AgentExecutor.test.ts,resume.test.ts,kvCheckpointStore.test.ts,cloudflare.checkpoint.test.ts,cancellation.test.ts, and agent-forgerunRegistry.test.ts. The known-failing agent-forge teststop() then run() resumes from the last checkpointnow passes.parallelToolCalls.test.ts) now asserts they run once, in call order.toolCallId. ThreetoHaveBeenCalledWith(args, {})assertions were updated, one of them in agent-forgecompileHooks.test.ts.How verified
On the merged result (origin/main
d2cdf24merged at5407ed2):npx tsc --noEmit: exit 0npm run lint:✖ 433 problems (0 errors, 433 warnings)(same count as main; none in touched files)npm run build && npm run build --workspace=packages/create-loushy-agent: exit 0npm run test:coverage:Test Files 130 passed (130),Tests 1921 passed | 4 skipped (1925). Earlier runs hit load-related timing flakes inhttp.test.ts,guardrails.test.tsandsandbox.test.ts; they pass in isolation and on rerun.npm run fallow: exit 0,✓ No issues found,✓ 0 above thresholdnpm run test:types: 19 passed, no type errorsnpm run docs:llms:check: exit 0npm run docs:verify-snippets:all 80 snippet(s) type-check against src/; 6 also run cleanlytypecheckandtypecheck:serverexit 0;npx vitest run108 passed;npm run test:server105 passedNew tests are in
src/execution/durableExecution.test.ts(mockModel + KVCheckpointStore + a JSON round-tripping approval store, with simulated crashes viaPropagatingToolErrorand a failing store write). They cover:model.calls.lengthproving the model is not re-called;Store round-trips:
checkpoint.test.ts,kvCheckpointStore.test.ts,ApprovalGate.test.ts. The SQLite store keeps opaque JSON and is already covered by its contract tests.Notes for reviewer
AgentExecutor.tschanges stay focused: pending-call resume,runBatchsplit out ofrunToolCalls, checkpoint after the model turn, status-aware pause/finish. When merging LOU-V2 (agent.stream()),runStepOrAbortbecame generic over a step thunk, so resumed pending calls get the same step events.docs/durable-execution.md, covering what is checkpointed when, resume semantics, the at-least-once caveat with atoolCallIdidempotency example, compatibility and limits. Also short updates todocs/api-overview.md,docs/sessions.mdand README.src/execution: agent-forgerunRegistry.ts/abortableProvider.ts(doc comments only) and two agent-forge tests.resumeAfterApproval()withoutcheckpointStoreleaves the session marked awaiting-approval. This is documented, but could be detected.🤖 Generated with Claude Code