Skip to content

[EPIC-D][LOU-D45.1] Agent Forge time-travel API: checkpoint history, fork from a step, compare trajectories - #112

Merged
LinuxDevil merged 2 commits into
mainfrom
lou-d45-forge-time-travel
Oct 1, 2026
Merged

LinuxDevil merged 2 commits into
mainfrom
lou-d45-forge-time-travel

Conversation

@LinuxDevil

Copy link
Copy Markdown
Owner

What

First half of LOU-D45 (Agent Forge time travel). It is split to stay within the ~200-line budget. The whole ticket is about 390 code lines, not counting comments. This PR has the server and store half, at 192 code lines. The client half (History tab) is ready on lou-d45.2-forge-history-panel, stacked on this branch, and will be opened once this merges.

  • Store: Forge's FileCheckpointStore now implements history(). It is a bounded per-session ring with the same helpers and semantics as LocalStorageCheckpointStore: historyLimit (default 50, 0 keeps none) and delete({ keepHistory }). The ring is stored at .loushy/agents/<agentId>/checkpoint-history/<sessionId>.json. This closes the Forge part of LOU-D43.2.
  • Contract reuse: the SDK's CheckpointStore.history contract suite moves into src/execution/__fixtures__/checkpointHistoryContract.ts as describeCheckpointHistoryContract(name, factory). The SDK test runs it for its 3 stores, unchanged with 33 tests, and Forge runs it against FileCheckpointStore. This is test-only code under src/; no SDK source changed.
  • Registry (RunManager):
    • history(runId) returns one row per step of the latest execution: the newest checkpoint of each step, with status, savedAt, finish reason, tool calls with results, and the tokens and cost of that step's model call.
    • fork(runId, fromStep, patch) calls AgentExecutor.fork() and then starts the fork through the registry's own launch(), using the source run's spec. The fork therefore gets the same lifecycle, status, WS stream, chat and approvals as any run.
    • compare(a, b) runs compareTrajectories() on the two latest checkpoints.
  • Routes:
    • GET /runs/:id/history: 404 when the run has no history.
    • POST /runs/:id/fork with body { fromStep, patch?: { toolResult?, appendInput?, businessState? } }: validated (400 for bad input, including unknown patch keys). An unknown tool call id returns 400, and an unknown run or step returns 404. Success is 202 { runId, fromStep, status }.
    • GET /runs/compare?a=&b=: 400 for invalid ids, 404 when a run has no checkpoint.
    • /runs/:id uses the same path-traversal id guard as /agents/:id, and the SPA fallback leaves /runs alone.
  • Wire types in shared/wireTypes.ts: RunHistoryStep, RunHistoryPayload, ForkRunRequest, ForkRunResponse, RunComparisonPayload.
  • Fix the build forced:
    • typecheck:server was failing on main since LOU-V11, because ChatMessage.content is a string and Message.content can now hold parts. chatReconcile.ts now flattens content with textOf().
    • The server tsconfig gets the DOM lib, as the SDK's own tsconfig has. Server tests now type-check SDK sources through the shared contract.
    • .fallowrc.json lists history as a used CheckpointStore member. It is only called through the interface.
  • Docs: a "Time travel" section in docs/agent-forge.md (store, routes, curl flow), CHANGELOG (Added), and regenerated llms files.

Why

Third differentiator in AUDIT.md section 7 (Agent Forge time-travel). D43 (history) and D44 (fork, compareTrajectories) gave the SDK the primitives. Forge's file store kept no history, so a Forge run could not be forked, and the server had no API for it.

Acceptance criteria

  • Forge's FileCheckpointStore implements history() with the same bounded ring semantics as LocalStorageCheckpointStore, checked by the shared SDK contract suite.
  • GET /runs/:id/history returns steps with status, savedAt, tokens and cost per step when known, plus finish reason and tool calls.
  • POST /runs/:id/fork takes body { fromStep, patch?: { toolResult?, appendInput?, businessState? } }, validates it, returns the new run id and starts the fork through the registry's run lifecycle and WS events.
  • GET /runs/compare?a=&b= returns compareTrajectories, with validated input.
  • Server tests cover the three endpoints (server/__tests__/timeTravel.test.ts: appendInput fork, toolResult fork of a run paused for approval, validation and error mapping), and the store contract is reused (server/__tests__/checkpointStore.test.ts).
  • Docs: "Time travel" section in docs/agent-forge.md, llms regenerated, CHANGELOG (Added).
  • Scope: apps/agent-forge/** and docs. No SDK source change. Outside that, only the test fixture under src/execution/__fixtures__, the contract test and .fallowrc.json changed.
  • Deferred to LOU-D45.2 (lou-d45.2-forge-history-panel): the client History panel, edit-and-replay, the side-by-side view and its client test.
  • Playwright e2e skipped: .github/workflows/e2e-studio.yml installs Chromium (npx playwright install --with-deps chromium), so the suite does not run here without a browser install.

How verified

npm run typecheck --workspace apps/agent-forge          -> pass
npm run typecheck:server --workspace apps/agent-forge   -> pass (failed on main before the chatReconcile fix)
npm run test --workspace apps/agent-forge -- --run      -> Test Files 12 passed, Tests 108 passed
npm run test:server --workspace apps/agent-forge        -> Test Files 14 passed, Tests 121 passed
npm run build:studio --workspace apps/agent-forge       -> pass
npx tsc --noEmit                                        -> pass
npm run lint                                            -> 0 errors, 404 warnings (unchanged; touched SDK test files have none)
npm run test:types                                      -> pass
npx vitest run                                          -> Test Files 157 passed, Tests 2280 passed | 3 skipped
npm run build && npm run build --workspace=packages/create-loushy-agent -> pass
npm run docs:verify-snippets -- --skip-build            -> all 120 snippet(s) type-check against src/; 6 also run cleanly
npm run docs:llms:check                                 -> pass
npm run test:coverage                                   -> Test Files 157 passed, Tests 2280 passed | 3 skipped
npm run fallow                                          -> pass (one existing duplication warning in Anthropic/OpenAI providers, not from this change)

🤖 Generated with Claude Code

https://claude.ai/code/session_016bY29ibQgv3MDdjAbUT4YR


Generated by Claude Code

claude added 2 commits October 1, 2026 16:49
…pare routes (LOU-D45.1)

- FileCheckpointStore keeps the bounded per-session history() ring with
  the same semantics as LocalStorageCheckpointStore (historyLimit,
  delete({ keepHistory })); the SDK's history contract suite moves to a
  shared fixture and runs against it too.
- RunManager.history/fork/compare and the routes GET /runs/:id/history,
  POST /runs/:id/fork (validated { fromStep, patch? }, started through the
  registry's run lifecycle and WS stream) and GET /runs/compare?a=&b=.
- Fix: chatReconcile compiles against multimodal Message.content (LOU-V11)
  by flattening it with textOf(); server tsconfig adds the DOM lib like the
  SDK's own, since server tests now type-check SDK sources.
- docs/agent-forge.md "Time travel", CHANGELOG, llms files.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bY29ibQgv3MDdjAbUT4YR
…ws CheckpointStore.history (LOU-D45.1)

parseForkRequest() checks each patch field from a Map of validators, which
keeps it under fallow's complexity threshold, and CheckpointStore.history
joins save/load/delete as an interface member fallow treats as used
(FileCheckpointStore.history is only called through the interface).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bY29ibQgv3MDdjAbUT4YR
@LinuxDevil
LinuxDevil merged commit 357b096 into main Oct 1, 2026
2 checks passed
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