Repository navigation
feat: fork a session from an earlier prompt - #4017
liruifengv wants to merge 7 commits into
Conversation
The session fork REST action now accepts turn_index to fork from an earlier user-visible turn, and a live session no longer rejects fork while a turn is running or queued: when quiescence cannot be acquired the fork is capped at the last completed turn (explicit indices beyond it fail request.invalid; only a session with no completed turn yet still fails session.fork_active_turn).
🦋 Changeset detectedLatest commit: 8bc5e8e The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
commit: |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b594e07378
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
… holds, and fork outcome - clamp busy forks to the last completed user-visible turn index instead of the completed-turn count, so a steer-split turn keeps the steered input and its answer, and explicit indexes into it are accepted - reject forking while quiescence is held by a non-turn operation such as manual compaction, undo, or another fork, instead of treating it as turn activity and copying a wire in an inconsistent state - derive the fork's lastTurnReason from the retained wire's terminal turn.ended rather than copying the source metadata, which an active turn may have cleared
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f72cd67205
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
The REST :fork body now takes prompt_id (a durable id persisted in the wire as turn.prompt/turn.steer records) and the engine resolves it to the user-visible turn index against the full wire history. Addressing by a client-computed index drifted whenever the client could not see the whole conversation — paginated transcripts silently forked at a turn near the session start, and turns the client does not render (e.g. shell-command turns) shifted every later index. - resolve promptId to a visible turn index via the append message id; promptId and turnIndex are mutually exclusive, an unknown id or one past the last completed turn fails request.invalid - kap-server fork schema, action boundary schema, docs, and the node-sdk fork input speak prompt_id; the engine and SDK keep the turnIndex option for full-history consumers (e.g. the VSCode bridge)
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5f9d80d99f
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
…and contention - count a shell command as a completed turn once its output append lands, so busy forks stop clamping past (or rejecting on) a finished shell command - cap busy-fork slices at the start of the currently active engine turn, so a running non-visible turn's records never leak into the fork - keep the loop non-quiescent while a turn is finalizing (endTurn covers persistence of turn.ended), so a fork can never copy a wire whose terminal record is still in flight; settled() waits for the same point - classify quiescence contention per agent: any agent holding the guard without turn activity (compaction, undo, another fork) rejects the fork instead of being masked by another agent's active turn - resolve fork prompt ids through turn.steer promptIds (merged steer) and reject ids matching more than one visible turn (duplicated after a restart) instead of silently picking the first
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 24770b152a
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
- cap busy-fork slices at the first unterminated turn instead of the first turn after the last completed visible one, so completed continuation turns before the active turn stay in the fork - stop the fork outcome scan at the last turn's start boundary, so a slice cut before its terminal record no longer inherits an earlier turn's last_turn_reason - gate quiescence on the turn-close window (pendingTurnCloses, released once turn.ended is published) while settled() keeps waiting for the turn result to resolve (pendingFinalizations), so undo, compaction, and fork called from turn-completion continuations no longer fail with a spurious busy error
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 950b501236
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
- fork now requires quiescence from every agent of the source session and rejects with session.fork_active_turn while any operation is in progress; the busy-fork slicing path (cap at the last completed turn) is removed and will return in a follow-up built on loop-reported fork boundaries - keep the prompt_id addressing (merged-steer resolution, ambiguous rejection), the turn-sliced fork, and the turn-close quiescence fix that lets undo, compaction, and fork run right after turn.ended
|
Scope update: this PR is now limited to forking from an earlier prompt (idle-only). The busy-fork path (forking while a turn is running, capped at the last completed turn) has been removed and will return in a follow-up PR built on loop-reported fork boundaries instead of record-stream inference. The three latest review threads (shell interleaving, subagent active turns, gate-blocked completions) are addressed by that removal and resolved accordingly. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e5e316d61b
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| if (reason === 'failed' || reason === 'blocked') return 'failed'; | ||
| return undefined; | ||
| } | ||
| if (isEngineTurnStartRecord(record) || isUserVisibleTurnRecord(record)) return undefined; |
There was a problem hiding this comment.
Preserve outcomes across gate-blocked prompt slices
When a completed turn is followed by a prompt rejected by onBeforeSubmitPrompt, the wire contains a visible user message and prompt.completed with reason blocked, but no turn.ended. Forking at that prompt therefore reaches this early return and clears lastTurnReason, even though the source still reports the preceding completed outcome. Recognize terminal prompt.completed records before treating the visible message as an unterminated turn so the fork does not silently lose its session outcome.
Useful? React with 👍 / 👎.
Its only production caller was the removed busy-fork contention classification; the interface method stays out until the busy-fork follow-up needs it again
There was a problem hiding this comment.
💡 Codex Review
When an earlier fork point falls in the middle of a background subagent turn, slicing solely by the main agent's timestamp retains the subagent's prompt and partial output while discarding its later turn.ended. This can happen even after the source becomes fully idle—for example, a subagent launched during prompt 1 finishes after prompt 2 begins, and the user later forks at prompt 1—so quiescence does not prevent the fork from containing an unterminated subagent wire. Fresh evidence after the busy-fork removal is that completed subagent turns can still straddle a historical cutoff; truncate each subagent at its last terminal boundary before the cutoff, or omit a turn that overlaps it.
When a concurrent prompt submission reaches loop.submit() after the fork has acquired quiescence, the paused engine accepts and durably journals it as agent.input.submitted; the REST prompt and fork routes are not serialized. If prompt_id selects the last visible prompt, this fallback extends the slice to records.length, so readSourceWireRecords() flushes and copies that newly queued engine input even though its visible prompt has not started. Resuming the fork can then execute a prompt submitted concurrently to the source, violating the requested historical boundary; either prevent admissions while the fork lease is held or establish a fixed wire cutoff before reading the slice.
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
Requirement or Bug
Support forking a session from an earlier prompt: the
:forkendpoint accepts aprompt_id, and the copy keeps only the conversation through that prompt's turn segment. Forking is idle-only — it is rejected withsession.fork_active_turnwhile any operation is in progress. (Forking while a turn is running was split out to a follow-up PR built on loop-reported fork boundaries.)Bug Reproduction Steps
N/A
Root Cause
N/A
Code Changes
REST (
POST /sessions/{id}:fork)prompt_id(the persistent id of a user prompt) to fork from an earlier point instead of the latest state. Validated at both schema layers: the route-boundarysessionActionRequestSchemaand the action bodysessionForkSchema, then passed through toISessionManager.fork.Engine (
SessionLifecycleService.fork)resolveForkPromptIndexmaps a prompt id to its user-visible turn: merged steers are resolved throughturn.steer.promptIds, and an id matching more than one visible turn is rejected as ambiguous instead of silently picking the first.last_turn_reasonis derived from the slice: a slice cut before its terminal record (a steer-split turn) reports no outcome instead of inheriting an earlier turn's reason.Turn-close quiescence fix (
AgentLoopService)turn.endedevent is published (pendingTurnCloses), whilesettled()keeps waiting for the turn result to resolve (pendingFinalizations). Undo, manual compaction, and fork called from turn-completion continuations no longer fail with a spurious busy error.Docs:
server-api.md(en/zh) documentsprompt_idand the idle-only fork semantics.Impact Scope
agent-core-v2: sessionLifecycle fork, forkTurnSlice, loop quiescence.kap-server::forkroute schema and contract tests.node-sdk:ForkSessionInput.promptIdpass-through; the engine keepsturnIndexfor the vscode consumer.Checklist
/approve).gen-changesetsskill, or this PR needs no changeset.gen-docsskill, or this PR needs no doc update.