Repository navigation
fix(transcript): fill cold snapshot turn/step times and prompts from wire records - #4060
liruifengv wants to merge 8 commits into
Conversation
…wire records readColdSnapshot dropped turn startedAt, step times, and the prompts list for sessions read from the wire, so reopened or forked sessions showed wrong message times and lost prompt metadata. foldWireRecordFacts now folds them from durable records: turn startedAt from turn.prompt (falling back to agent.turn.started), step times from step.begin/step.end loop events, and prompt entries from turn.prompt/turn.steer updated by prompt.completed/aborted/steered, keeping only prompts whose turn survives in the snapshot.
🦋 Changeset detectedLatest commit: bbc9661 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: dd97086e20
ℹ️ 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 live projector Address review on the cold-snapshot facts fold: - project prompt content to the public message shape and strip bundled skill/hook blocks, matching projectPromptContentParts - restrict turn.prompt drafts to user-origin prompts; non-user tracked prompts still get terminal facts from completions - assign steer input only for single-child steers; synthesize steered children from prompt.steered alone and terminal prompts from completions without a turn draft - derive the fallback prompt status from the turn.ended reason - map start-only turns recorded via agent.turn.started - fill begin times on synthesized interrupted steps - associate legacy steer records without a turnId with the active turn so context undo hides their prompt facts - realign step times with visible steps after vacuous failed attempts compact the cold tree
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2cf8a5ce09
ℹ️ 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".
…s in cold facts - persist the submission createdAt on turn.prompt and per-child createdAts on prompt.steered (additive wire fields), and prefer them over record times when reconstructing prompts so queued prompts no longer report the turn launch time after a restart - remap turn.step.interrupted through the kept wire-step mapping so an interrupted retry lands on the visible step instead of synthesizing a stray empty step - match the context fold's vacuity predicate: a hidden empty thinking part is vacuous
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f08afbce26
ℹ️ 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 strip them at broadcast - add an optional createdAt to prompt.completed/prompt.aborted so queued prompts rejected or cancelled before launch keep their submission time in cold snapshots - strip the durable-only createdAts/createdAt fields from prompt.steered/prompt.completed/prompt.aborted in the session event broadcaster so the public websocket contract stays unchanged - associate completion-only prompts with their owning raw turn via turnPromptIds so a context undo hides terminal non-user prompts together with the command turn
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2f7892cf56
ℹ️ 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 identity - restrict turn.steer draft content and client metadata to user-origin steers, matching the live projector which only has submitted content for user prompts; other origins keep only the terminal steer fact - stop copying the synthetic merged message id into every steered child: single-child steers keep the record messageId while merged steers fall back to each child's own prompt id, like the live projector which preserves each submitted userMessageId
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 521724e374
ℹ️ 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".
A tracked non-user active prompt (skill/plugin command) creates no turn.prompt draft, so a restart during the turn lost the prompt entirely and a later completion rebuilt it without steeredAt. Upsert the active prompt when applying prompt.steered, the same way the live projector does: steered content as the content fallback, steeredAt as the createdAt fallback, running status until a completion lands, and the owning raw turn resolved through turnPromptIds so undo visibility still applies. Children are applied before the active upsert so the list order matches the live store, where child submissions precede the steer events.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: bbc96617a9
ℹ️ 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".
| ) { | ||
| promptDrafts.set(payload.promptId, { | ||
| promptId: payload.promptId, | ||
| content: projectPromptContent(payload.input, payload.origin), |
There was a problem hiding this comment.
Persist the pre-gate prompt content
For user prompts containing a malformed, unsupported, or non-canonical image, this reconstructs content from turn.prompt.input, but that durable input has already passed through gateImageFormatParts in runPromptGate, which can replace the submitted image with an internal text notice or rewrite its data URL. The live prompt.submitted event is emitted before that gate, so desktop/web clients see the original payload while live and a different payload after restart. Fresh evidence beyond the earlier projection fix is this irreversible pre-persistence gate rewrite; persist the pre-gate content or reconstruct it from a durable pre-gate record.
AGENTS.md reference: AGENTS.md:L118-L120
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Acknowledged — the durable turn.prompt.input is indeed post-gate content while prompt.submitted is pre-gate, so gate-rewritten images diverge after a restart. After a scope review we are keeping this PR to its core fix (restoring missing turn/step times and the prompts list on cold snapshots) and stopping the Codex-driven expansion here, since this and the remaining findings either require more durable-log format changes or address narrow edge cases. Leaving this as a known follow-up (pre-gate content needs to be persisted, e.g. via a durable submission record) rather than growing this PR further.
| content: | ||
| steer.promptIds.length === 1 | ||
| ? projectPromptContent(steer.content, undefined) | ||
| : undefined, |
There was a problem hiding this comment.
Keep steer-only non-user children content-free
If a skill/plugin-origin or untracked delivery is accepted for steering and the process exits after durable prompt.steered but before the nudge emits turn.steer, this fallback has no origin and unconditionally exposes the single child's expanded/internal content. The live projector creates such a child with no content because it never received prompt.submitted. Fresh evidence after the earlier origin-gating fix is that the steer-only crash path bypasses that gate entirely; persist enough origin information to keep non-user children content-free.
AGENTS.md reference: AGENTS.md:L118-L120
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Acknowledged — the steer-only crash path can surface content for non-user children because prompt.steered carries no per-child origin. Valid, but fixing it requires persisting per-child origin on the durable steer record, and after a scope review we are keeping this PR to its core fix and deferring this (along with the other durable-format follow-ups) rather than expanding further. Noted as a known limitation of the steer-only synthesis.
| interruptedSteps.has(turnId)), | ||
| interruptedSteps.has(turnId) || | ||
| stepTimeTurns.has(turnId) || | ||
| agentStartedTurns.has(turnId)), |
There was a problem hiding this comment.
Exclude undone start-only turns from fallback mapping
In a legacy history containing agent.turn.started but no turn.prompt, these newly admitted candidates never advance nextTurnId; consequently, context.append_message anchors the turn at zero and a later context.undo hides no raw IDs because its loop ends at that unchanged clock. If the user then submits a replacement turn, raw turn 0 remains the first fallback candidate and its old start record is assigned to the replacement base turn. Fresh evidence beyond the earlier start-only mapping fix is that those records were added to candidate mapping without being added to undo/turn-clock accounting.
AGENTS.md reference: AGENTS.md:L118-L120
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Acknowledged — start-only agent.turn.started records were admitted to candidate mapping without joining the undo/turn-clock accounting, so a phantom turn can survive an undo and lend its start record to a replacement turn. This is a genuine edge in legacy (pre-turn.prompt) histories, but the correct fix touches the undo anchor/clock machinery, which carries more regression risk than we want to take in this PR. Deferring as a follow-up; the PR stays scoped to its core fix.
| content: projectPromptContent(steer.content, undefined), | ||
| createdAt: steer.steeredAt, | ||
| rawTurnId: rawTurnIdByPromptId.get(steer.activePromptId), | ||
| status: 'running', |
There was a problem hiding this comment.
Derive synthesized active prompt status from its ended turn
For a tracked user-slash skill or plugin turn whose turn.prompt intentionally created no draft, steering synthesizes the active prompt here. If turn.ended is persisted and the process exits before the separately dispatched prompt.completed, the explicit running status bypasses the turn-outcome fallback below, so reopening reports a running prompt even though its turn completed, failed, or was cancelled. Fresh evidence beyond the earlier fallback-status fix is that the newly added active-prompt synthesis branch pre-populates the status instead of allowing that fallback to run.
AGENTS.md reference: AGENTS.md:L118-L120
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Acknowledged — the synthesized active prompt pre-populates running instead of letting the turn-outcome fallback apply when turn.ended landed but prompt.completed did not. Small and valid, but we are freezing this PR at its core scope after a review of the Codex-driven expansion; bundled with the other follow-ups for a subsequent PR.
|
@codex Thanks for the thorough review rounds — the findings so far have all been addressed and their threads resolved. A note on scope: this PR is staying scoped to its core fix — restoring missing turn/step times and the prompts list in cold snapshots (reopened or forked sessions). We are not expanding it further. The remaining open threads are acknowledged as valid but deferred to follow-up work, since they either require more durable-log format evolution (e.g. making
The deferred items are replied to individually in their threads with the rationale. |
|
To use Codex here, create a Codex account and connect to github. |
|
Codex Review: Didn't find any major issues. 🎉 Reviewed commit: ℹ️ About Codex in GitHubCodex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
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
Sessions read from disk — reopened after a server restart, or freshly forked — lose turn
startedAt, step times, and the entirepromptslist in their transcripts, so message times render wrong (the first turn falls back to the session'screatedAt, e.g. the fork moment) and prompt metadata is missing.Bug Reproduction Steps
GET /api/v1/sessions/{id}/transcript?agent_id=mainreturns turns withstartedAt = nullandprompts = [](onlyendedAtis present).createdAt— for a fork, the fork moment — instead of the real message time.Root Cause
Cold snapshots are built from a lossy projection:
readColdSnapshotreduces wire records to context messages and discards record times;groupMessagesIntoSnapshothas no time fields and always returnsprompts: []; andfoldWireRecordFactsonly backfilledendedAt. Live sessions mask the gap because times and prompts arrive via live ops. This is a fundamental fix, not a workaround: the missing facts are folded from durable wire records at the layer already designated for record-level facts (foldWireRecordFacts, which already backfillsendedAt, interrupted steps, tasks, interactions, todos, and markers the same way).Code Changes
All behavioral changes are inside
foldWireRecordFacts(packages/transcript/src/history/foldFacts.ts);groupTurns.tsonly exports two existing helpers for reuse.prompt.completed/abortedwin; otherwise the owning turn'sturn.endedreason maps to the prompt status (completed→completed,cancelled→aborted,failed/blocked→ same), elserunning.createdAtis the original submission time:turn.promptpersistscreatedAt,prompt.steeredpersists per-childcreatedAts, andprompt.completed/prompt.abortedpersistcreatedAt(all additive optional wire fields, regeneratedwire-manifest.d.ts), so queued prompts — including ones rejected or cancelled before launch — no longer report the turn launch or rejection time after a restart; records written by older versions fall back to the record time.prompt.steered/prompt.completed/prompt.abortedso the public websocket contract is unchanged, and loop snapshots asserting wire logs were updated accordingly.turnPromptIds, so undoing a user-slash skill/plugin turn also drops its terminal prompt entry.Impact Scope
layers.test.ts+10 cases;transcript.test.tscold-snapshot e2e case. transcript 135/135, kap-server suite green, root lint + typecheck green. Also verified against real session data: a forked session's turns recover their original times (not the fork moment), and legacy sessions from theagent.turn.startedera map correctly.Checklist
/approve).gen-changesetsskill, or this PR needs no changeset.gen-docsskill, or this PR needs no doc update.