fix(transcript): project only a fork's own turns into its transcript - #3679
fix(transcript): project only a fork's own turns into its transcript#3679liukx0205 wants to merge 3 commits into
Conversation
Forked agents (e.g. /btw children) persist the inherited source context in their wire log. The cold transcript projection rebuilt those inherited messages as the fork's own turns, so fork transcripts contained the main conversation, ordinals collided with the live projection, and prompts paired with the wrong answers. Mark fork-seeded context messages as inherited and exclude them from the transcript snapshot projection. Fixes #3672
🦋 Changeset detectedLatest commit: 0ff83d0 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 |
commit: |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ceb122c21f
ℹ️ 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".
Pass the optional inherited field directly instead of using a conditional spread, per the repo's optional-property rule. Side chats forked before the inherited marker existed keep their seeded main-conversation messages unmarked, so the marker filter alone still rebuilt them as the fork's own turns when reopening an old /btw chat. Detect that legacy shape in the cold projection: when the agent meta records a forkedFrom parent and the wire log carries no inherited marker, drop context.append_message records preceding the fork's first turn.prompt before reducing the transcript.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e2f960ee3a
ℹ️ 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 fork that compacts summarizes its inherited parent history into the compaction summary, but reduceContextTranscript synthesizes that summary as an unmarked compaction_summary message, so the own-content projection kept it and groupMessagesIntoSnapshot exposed its full text as a compaction marker. When the agent meta records a forkedFrom parent, drop compaction_summary messages from the cold projection alongside the inherited ones; the main agent still projects its own summaries.
Related Issue
Fixes #3672
Problem
Every
/btwfork's transcript came out misaligned: the fork's own turn sat at ordinal 0, followed by the inherited main-conversation turns, followed by the fork's own turn again as a duplicate (empty frames). By agent-12 the turn's prompt was paired with the main conversation's answer, and the real answer was missing from the transcript. Any consumer that rebuilds from the transcript (e.g. the desktop side-chat panel resync) received the wrong content.Root cause:
AgentLifecycleService.forkseeds the child agent's context memory with the source agent's messages, and those inherited messages are persisted in the child's wire log as ordinarycontext.append_messagerecords, indistinguishable from the child's own messages. The cold transcript projection (TranscriptService.readColdSnapshot→reduceContextTranscript→groupMessagesIntoSnapshot) rebuilt them as the fork's own turns: inherited turns took ordinals 0..N-1 and the fork's own turn ordinal N, while the live projection numbers the fork's own turns from 0 — so backfill merged byturnIdpaired the fork's prompt with the main conversation's answer and appended the fork's real turn as a duplicate.What changed
agent-core-v2:ContextMessagegains aninheritedflag;AgentLifecycleService.forkmarks the messages it seeds into the child asinherited: true(LLM context is unchanged — the child still sees the inherited conversation).reduceContextTranscriptpreserves the flag.kap-server:readColdSnapshotexcludes inherited messages from the transcript snapshot projection, so a fork's transcript contains only its own turns, ordinals stay contiguous from 0 and match the live projection, and prompt/response pairing heals correctly on backfill.Note: wire logs written before this fix carry no
inheritedmarker, so transcripts of forks created by older versions are not repaired retroactively.Checklist
/approve).gen-changesetsskill, or this PR needs no changeset.gen-docsskill, or this PR needs no doc update.