feat(agent-adapter): per-turn provider checkpoints and the forkAfterTurn capability - #511
feat(agent-adapter): per-turn provider checkpoints and the forkAfterTurn capability#511Zerlight wants to merge 13 commits into
Conversation
|
Your Claude subscription has hit its usage limit. It resets at 11:30am (UTC). Re-trigger Pullfrog after the reset, or add an Add repo secret → · Model settings → · Setup docs → · Ask in Discord →
|
Greptile SummaryThis PR introduces provider-specific per-turn checkpoints and uses them to fork conversation lineages safely.
Confidence Score: 4/5The PR appears safe to merge, with a non-blocking durable provenance inconsistency in parent checkpoint bindings. The checkpoint, attribution, launch, and dispatch-state paths are conservatively guarded; the only accepted issue records the successor run on a checkpoint attached to its parent, which does not currently affect fork selection but leaves inconsistent persisted metadata. Files Needing Attention: packages/host/engine/src/conversation/turn-service.ts
|
| Filename | Overview |
|---|---|
| packages/host/engine/src/conversation/turn-service.ts | Adds dispatch-aware turn tracking and live checkpoint persistence; preceding bindings record the successor run instead of the owning parent run. |
| packages/host/engine/src/conversation/checkpoint-service.ts | Resolves live or verified replay cuts and prioritizes a parent's original provider history with a current-history fallback. |
| packages/host/engine/src/conversation/lineage-attribution.ts | Implements conservative positional attribution, hidden-prefix tail alignment, and ambiguity rejection. |
| packages/host/engine/src/session/lifecycle-service.ts | Launches fresh, resumed, or checkpoint-forked runs according to graph position and provider capability. |
| packages/host/engine/src/session/history-service.ts | Maps invalid provider checkpoints to typed failures and retries an explicitly supplied fallback cut. |
| packages/host/agent-adapter/src/native/claude-code.ts | Captures successful-turn assistant-row checkpoints and validates transcript membership before forking. |
| packages/host/agent-adapter/src/native/codex/adapter.ts | Captures completed turn IDs, rejects unforkable paginated histories, and types JSON-RPC fork refusals. |
| packages/host/agent-adapter/src/native/opencode/adapter.ts | Captures one preceding-message checkpoint per turn while keeping the unverified turn-level capability disabled. |
| packages/host/agent-adapter/src/native/pi/adapter.ts | Captures settled session leaves and validates entries before creating branched sessions. |
| apps/daemon/src/conversation-store.ts | Makes the first live binding immutable while allowing replay bindings to be refreshed or replaced by live captures. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[Turn dispatch] --> B[Provider adapter]
B -->|ending checkpoint| C[Current turn binding]
B -->|preceding checkpoint| D[Parent turn binding]
C --> E[Conversation checkpoint service]
D --> E
F[Verified provider-history read] -->|replay binding| E
E --> G{Fork requested}
G -->|valid persisted cut| H[Provider branchHistory]
H --> I[New run and sibling turn]
G -->|stale cut| J[Typed unsupported response]
H -->|primary cut invalid| K{Current-history fallback}
K -->|valid| I
K -->|invalid or absent| J
Reviews (1): Last reviewed commit: "fix(agent-adapter): opencode mints a pre..." | Re-trigger Greptile
dcc2f04 to
ce3e1d4
Compare
There was a problem hiding this comment.
Greptile has paused reviews on this repository — it used its 100 free open-source review credits for this billing period. Reviews resume automatically on September 23. To continue before then, an organization admin can keep reviews running past the free credits — those bill as normal usage.
There was a problem hiding this comment.
Greptile has paused reviews on this repository — it used its 100 free open-source review credits for this billing period. Reviews resume automatically on September 23. To continue before then, an organization admin can keep reviews running past the free credits — those bill as normal usage.
|
Your Claude subscription has hit its usage limit. It resets at 11:30am (UTC). Re-trigger Pullfrog after the reset, or add an Add repo secret → · Model settings → · Setup docs → · Ask in Discord →
|
There was a problem hiding this comment.
🔵 Needs a closer look
It changes core turn lifecycle/fork semantics across engine + multiple adapters, so despite strong tests it warrants final human review for correctness and edge-case safety.
Pull request overview
This PR implements Phase 1 of the turn-graph/checkpoint work by introducing per-turn provider checkpoints (persisted as turn bindings) and using them to enable safe “fork after turn” behavior across adapters and the engine session lifecycle, including legacy rewrite (history.branch) flows.
Changes:
- Added a checkpoint pipeline from adapters (
onCheckpoint) → engine (bindLiveCheckpoint) → store bindings, with “first live capture wins” semantics. - Reworked forking/resume/replace logic in the engine to fork from persisted per-turn cuts (with verified cold-read backfill), and to refuse typed when a cut is missing or invalid.
- Updated adapters (claude-code/codex/pi/opencode) to mint/validate checkpoints and to surface invalid cuts via
HistoryCheckpointInvalidError, with engine-side mapping to fixedunsupportedmessages.
File summaries
| File | Description |
|---|---|
| packages/host/engine/src/session/session-input-dispatcher.ts | Associates live prompt echoes with persisted turn IDs for checkpoint-based rewrite/fork. |
| packages/host/engine/src/session/session-event-processor.ts | Promotes dispatching turns on running so turn stamping works for whole-turn send() adapters. |
| packages/host/engine/src/session/orchestrator.ts | Wires adapter checkpoint stream into turn binding persistence and exposes static history capabilities. |
| packages/host/engine/src/session/live-session.ts | Encodes live branch cursors by (historyId, turnId) and adds checkpoint subscription plumbing. |
| packages/host/engine/src/session/lifecycle-service.ts | Implements fork-after-turn admission/launch paths, rewrite behavior, and typed refusal rules. |
| packages/host/engine/src/session/history-service.ts | Adds branch cut fallback handling and maps invalid checkpoints to fixed typed unsupported. |
| packages/host/engine/src/engine.ts | Introduces and wires ConversationCheckpointService into lifecycle + projection. |
| packages/host/engine/src/conversation/turn-service.ts | Adds dispatching/running tracking split and persists live/replay turn bindings. |
| packages/host/engine/src/conversation/projection-service.ts | Routes attribution/backfill through checkpoint service; reuses host-user content helpers. |
| packages/host/engine/src/conversation/lineage-attribution.ts | Extracts lineage attribution + hidden-prefix alignment logic into a shared module. |
| packages/host/engine/src/conversation/conversation-store.ts | Enforces “live binding is immutable” on (turnId, historyId) in the in-memory store. |
| packages/host/engine/src/conversation/checkpoint-service.ts | New service for resolving fork cuts and replay-backfilling bindings from verified corpora. |
| packages/host/engine/src/tests/* | Adds/updates tests for checkpoint capture, fork semantics, invalid-cut mapping, and attribution/backfill. |
| packages/host/agent-adapter/src/native/pi/adapter.ts | Enables forkAfterTurn and emits/validates checkpoints with typed invalid-cut errors. |
| packages/host/agent-adapter/src/native/opencode/adapter.ts | Keeps forkAfterTurn dark; emits preceding checkpoints safely; validates fork targets before forking. |
| packages/host/agent-adapter/src/native/codex/history.ts | Propagates history_mode and disables cursors for paginated rollouts. |
| packages/host/agent-adapter/src/native/codex/app-server.ts | Preserves JSON-RPC error codes on rejections to distinguish refusal vs disconnect. |
| packages/host/agent-adapter/src/native/codex/adapter.ts | Pre-checks forkability, maps JSON-RPC refusals to typed invalid-cut errors, and emits checkpoints. |
| packages/host/agent-adapter/src/native/claude-code.ts | Enables forkAfterTurn, emits checkpoints, and verifies checkpoint existence in raw transcript. |
| packages/host/agent-adapter/src/history-branch.ts | Introduces HistoryCheckpoint and HistoryCheckpointInvalidError contracts. |
| packages/host/agent-adapter/src/base.ts | Adds checkpoint listener support and a shared emitCheckpoint() implementation. |
| packages/host/agent-adapter/src/adapter.ts | Extends adapter interface with optional onCheckpoint and documents invalid-cut behavior. |
| packages/host/agent-adapter/AGENTS.md | Documents the checkpoint contract and per-provider cut semantics. |
| packages/foundation/schema/src/model/history.ts | Adds forkAfterTurn capability and clarifies the legacy branch capability. |
| apps/daemon/src/conversation-store.ts | Applies “first live capture wins” semantics at the DB upsert layer. |
| apps/daemon/src/tests/conversation-store.test.ts | Updates store tests to assert replay/live overwrite ordering and immutability of live cuts. |
Review details
- Files reviewed: 43/43 changed files
- Comments generated: 0
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
…esume a checkpoint-less tip blind
…of starting fresh
…claude stop-hook cut divergence
… the adapter's running status
…o its cut on the current history
…user message first seen inside the turn
ce3e1d4 to
57cc0be
Compare
|
Your Claude subscription has hit its usage limit. It resets at 11:30am (UTC). Re-trigger Pullfrog after the reset, or add an Add repo secret → · Model settings → · Setup docs → · Ask in Discord →
|
|
Review round on this PR (Greptile, one inline thread): fixed on the top of the stack in 4b692dd — a |

Summary
Phase 1 of CODE-627 — Conversation turn graph & immutable attachment store. Linear: https://linear.app/arcbox/issue/CODE-632/featagent-adapter-per-turn-provider-checkpoints-and-the-forkafterturn
Stack: #510 ← this PR (
ruocheng/code-632, baseruocheng/code-631) ← #512. Merge bottom-up; this PR's diff is only its own commits.Adapters mint a durable provider checkpoint per turn and
branchbecomesforkAfterTurn. The engine forks at persisted checkpoints (the parent's own binding first, its cut on the current history as fallback), refuses invalid checkpoints typed, commits a turn only when its dispatch resolves, aligns pre-graph lineages to the provider corpus tail only when the alignment verifies uniquely, and never resumes a checkpoint-less tip blind.Commits
Verification
Every commit passed
pnpm check:ciandpnpm testat its own tip; the stack tip (1d942a62; the same tree as the originally gated73fc5ff2plus the two review fixes below on 628/629) is atpnpm check:ci0 errors,pnpm test3408 passed / 1 skipped. Adversarial reviewers (one per axis, isolated read-only worktrees) reviewed the branch; each P1/P2 was reproduced with a failing test or a probe step before its fix — the round-by-round record is in the Linear issue's comments. Not verified here: the live fork surfaces of the installed opencode build and the claude stop-hook cut divergence recorded in the agent-adapter docs (CODE-633's spike tracks them).Checklist
pnpm check:ciandpnpm testboth pass (no Rust changes)AGENTS.mdand module docs in this branch)