Skip to content

[EPIC-D][LOU-D44] AgentExecutor.fork() creates a runnable session from a historical checkpoint - #104

Merged
LinuxDevil merged 3 commits into
mainfrom
lou-d44-fork-from-step
Oct 1, 2026
Merged

LinuxDevil merged 3 commits into
mainfrom
lou-d44-fork-from-step

Conversation

@LinuxDevil

Copy link
Copy Markdown
Owner

What

  • AgentExecutor.fork({ sessionId, fromStep, newSessionId?, checkpointStore, patch? }) (delegates to the new src/execution/fork.ts). It picks the newest history entry of fromStep (D43 history()) and applies patch in this order: messages(messages), businessState, toolResult: { toolCallId, result }, appendInput. The tool result is replaced in place, keeping its position, call id and tool name; if the call has no result yet, the result is recorded and the load-time splitPendingTurn puts it behind its turn. appendInput queues a user message. The result is saved as the 'in-progress' checkpoint of newSessionId (default <sessionId>.fork-<n>, the first free n), with approvalId dropped. The call returns { sessionId, step, checkpoint }, and the original session's checkpoint and history are not touched. execute({ sessionId: fork.sessionId, checkpointStore, input: [] }) continues the fork.

  • Errors:

    • A missing step or unknown session throws SDKError with the new registry code LOUSHY_CHECKPOINT_NOT_FOUND. The message lists the steps that are kept.
    • A store without history(), a newSessionId that already has a checkpoint, or a toolCallId that is not in the transcript throws ConfigurationError.
  • agent.fork(sessionId, { fromStep, newSessionId?, patch? }) on createAgent() agents runs over store.checkpoints. Without a store it rejects with LOUSHY_CONFIG_MISSING_CHECKPOINT_STORE. agent.resume(fork.sessionId) continues the fork.

  • compareTrajectories(a, b) in src/evals/drift.ts reuses the D46 diffTrajectories. It takes two Checkpoints or two Message[] and returns:

    • each run's steps, one per assistant turn, with the text and the tool calls (id, name, normalized args, result)
    • divergedAt: the first step that differs; tool call ids are ignored
    • drift: the tool order, argument, step count and finish reason diff

    It is exported with TrajectoryComparison, TrajectoryStep and DriftEntry from ./evals and the root.

  • New types ForkOptions, ForkPatch and ForkResult in checkpoint.ts.

  • Docs: a "Fork and replay" section in docs/durable-execution.md, a LOUSHY_CHECKPOINT_NOT_FOUND section in docs/errors.md, regenerated llms-full.txt, and a CHANGELOG entry under Added.

Why

This is differentiator 3 of .agent-loop/AUDIT.md §7 (Agent Forge time-travel), built on the D43 checkpoint history (#98). It lets you fork a run at step N with a "what if" change and replay it. compareTrajectories gives Forge (D45) the data for a side-by-side view.

Acceptance criteria

  • AgentExecutor.fork() loads the history entry at fromStep. A missing step throws LOUSHY_CHECKPOINT_NOT_FOUND, which is in the LOU-D2 registry and documented in docs/errors.md.
  • The patch is applied: a replaced tool result keeps the transcript provider-valid, and appendInput queues a user message.
  • The patched checkpoint becomes the current checkpoint of newSessionId (default <sessionId>.fork-<n>) with status in-progress. The original is untouched. The call returns { sessionId, step, checkpoint }.
  • execute({ sessionId: newSessionId, checkpointStore, input: [] }) and agent.resume(newSessionId) continue the fork.
  • agent.fork(sessionId, options) on createAgent agents with a checkpoint store. Only the SimpleAgent.fork member, the method and one type import were added to src/createAgent.ts.
  • compareTrajectories(a: Checkpoint | Message[], b) returns the step and tool-call diff, reusing src/evals/drift.ts.
  • Tests with mockModel + memoryStore():
    • fork at step 1 of a 3-step run with a patched tool result, resume, and get a different trajectory while the original checkpoint and history stay unchanged
    • fork with appendInput, plus the messages and businessState patches
    • a result recorded for a pending call
    • errors for a missing step, an unknown session, a store without history and a taken id
    • agent.fork through createAgent, with and without a store
  • Docs: the durable-execution.md "Fork and replay" section, regenerated llms files and a CHANGELOG entry under Added.

How verified

  • npx tsc --noEmit: exit 0
  • npm run lint: 0 errors, 404 warnings. The changed and new files add no warnings; the one any warning in AgentExecutor.ts at line 127 was already there.
  • npm run test:types: Type Errors: no errors
  • npx vitest run (after the build): Test Files 1 failed | 153 passed (154); Tests 1 failed | 2231 passed | 3 skipped (2235).
    • The one failure is src/deploy/adapters/cloudflare.test.ts > real wrangler tooling > wrangler dev serves the worker on the local workerd runtime: /health never answered within 90s.
    • That is the sandbox environment (local workerd runtime). It also failed when re-run on its own, and this PR does not touch deploy code.
    • New tests: src/execution/fork.test.ts 6/6, src/evals/drift.test.ts 9/9.
  • npm run build && npm run build --workspace=packages/create-loushy-agent: both exit 0
  • npm run docs:verify-snippets -- --skip-build: all 117 snippets type-check against src/, and 6 also run cleanly
  • npm run docs:llms:check: exit 0
  • npm run test:coverage: same single wrangler failure as above, so no coverage report was written. I re-ran it with --coverage.reportOnFailure (same tests, nothing skipped) to get the coverage file for fallow. Coverage: fork.ts 98.59% lines, drift.ts 100% lines.
  • npm run fallow: exit 0, "No issues found", dead exports 0.0%. The only clone group was already there (AnthropicProvider/OpenAIProvider).

Not in this PR: reusing recordReplay cassettes for the unchanged prefix. That was a nice-to-have; a fork does not re-run the prefix anyway, because it resumes from the checkpoint.

🤖 Generated with Claude Code

https://claude.ai/code/session_016bY29ibQgv3MDdjAbUT4YR


Generated by Claude Code

claude added 3 commits October 1, 2026 16:23
…m a historical checkpoint

AgentExecutor.fork({ sessionId, fromStep, newSessionId?, checkpointStore, patch? })
takes the newest history entry of fromStep, applies the patch (messages
rewrite, businessState, a tool result replaced in place, appendInput queued
as a user message) and saves it as the in-progress checkpoint of a new
session (default <sessionId>.fork-<n>), leaving the original untouched.
execute({ sessionId, checkpointStore, input: [] }) or agent.resume()
continues the fork. A missing step throws LOUSHY_CHECKPOINT_NOT_FOUND (new
registry code, documented in docs/errors.md).

createAgent() agents gain agent.fork(sessionId, options) over
store.checkpoints. compareTrajectories(a, b) in src/evals/drift.ts compares
two checkpoints or transcripts step by step (steps, first diverging step and
the eval --drift diff) for Forge's side-by-side view.

Docs: durable-execution.md "Fork and replay", llms-full.txt regenerated,
CHANGELOG (Added).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bY29ibQgv3MDdjAbUT4YR
…fter multimodal parts (LOU-V11)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bY29ibQgv3MDdjAbUT4YR
@LinuxDevil
LinuxDevil merged commit d0b8d1c into main Oct 1, 2026
1 check failed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants