feat(handoff): scale transcript budget to the destination context window - #686
feat(handoff): scale transcript budget to the destination context window#686playtix-brain wants to merge 1 commit into
Conversation
Handoff transcripts are truncated from the front, so a 50k-char budget (~12k tokens) drops the start of the conversation — goal, constraints, decisions — and hands the next agent a transcript that begins mid-word. Size the budget from the destination model's context id instead, claiming 35% of it and never going below the previous-generation default of 400k chars. A 1M-context handoff now carries ~350k tokens instead of ~12k. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
SDSLeon
left a comment
There was a problem hiding this comment.
Deep review (findings-only, PR tests verified 11/11 locally). Direction looks right — scaling the ceiling to the destination window instead of a fixed 50k is the correct fix for front-truncation eating the goal/constraints. Details inline.
Scope question to confirm: since gui→gui handoffs now take the thread-transcript route (own thread on switch, thread mention on fork — nothing copied), this PR only affects context-file handoffs: anything involving a terminal, mirrored threads, read_thread disabled, and the mobile path. Is that the intended readership? Related: 35% of the destination window rides the new session's first message for the rest of its run — deliberate trade vs. a smaller share?
| * of its session, so it is filled by priority rather than recency alone. | ||
| */ | ||
| export const MAX_TRANSCRIPT_CONTEXT_CHARS = 50_000; | ||
| export /** |
There was a problem hiding this comment.
The /** Whole-file budget, roughly 12-15k tokens… */ block above (lines 5–9) documented the deleted MAX_TRANSCRIPT_CONTEXT_CHARS and now contradicts the new comment below it (“Small next to any current context window”). It was orphaned when the constant was removed — suggest deleting lines 5–9.
| * of its session, so it is filled by priority rather than recency alone. | ||
| */ | ||
| export const MAX_TRANSCRIPT_CONTEXT_CHARS = 50_000; | ||
| export /** |
There was a problem hiding this comment.
Nit: the leading export on this line now exports HANDOFF_CONTEXT_SHARE — leftover from replacing export const MAX_TRANSCRIPT_CONTEXT_CHARS. Only handoffTranscriptBudget needs to be public; suggest dropping this export.
| const DEFAULT_MAX_TRANSCRIPT_CONTEXT_CHARS = 400_000; | ||
|
|
||
| /** `"1m"` / `"200k"` / `"272000"` -> token count, or undefined when unparsable. */ | ||
| function tokensFromContextSize(contextSize: string | undefined): number | undefined { |
There was a problem hiding this comment.
Nit: this reimplements parseContextWindowInput() (src/shared/agents/codexContextWindows.ts) for the same contextSize id format, minus its comma handling and 1k–10M clamps — e.g. "20m" is rejected there but yields a ~28M-char budget here. Suggest reusing the shared parser and keeping the 400k floor on top.
| export function buildTranscriptContext( | ||
| thread: Thread, | ||
| sourceLabel: string, | ||
| maxChars: number = DEFAULT_MAX_TRANSCRIPT_CONTEXT_CHARS, |
There was a problem hiding this comment.
Coupling note: raising this default 50k → 400k silently 8×'s the other caller, src/mobile/useRemoteDesktop.ts:1184, which passes no budget — and mobile inlines the transcript into the prompt with no attachment-file fallback (the buildHandoffLaunchInput inline path), so it is the path most exposed to prompt-size limits. input.targetConfig.contextSize is in scope there; suggest threading handoffTranscriptBudget(...) through it too, or calling out mobile as intentionally default-only.
| }); | ||
| }); | ||
|
|
||
| describe("handoffTranscriptBudget", () => { |
There was a problem hiding this comment.
Suggestion: nothing tests the PR's core claim — that the dialog forwards the destination's contextSize — and there are no cases for realistic ids ("1M", "272k", "default", "1.05M"). A wiring test plus parametrized ids would lock this (and the parser-parity point on tokensFromContextSize) in.
Handoff transcripts are truncated from the front, so the budget decides which end of the conversation the receiving agent loses. At 50k characters (~12k tokens) it loses the beginning — the goal, the constraints, the decisions — and starts mid-sentence:
That is a real handoff from a 1M-context model handing off to another 1M-context model: 12k tokens kept, everything before it dropped, first word cut in half.
Change
The budget now scales with the destination model's context window and claims a share of it (35%), instead of a fixed constant that predates today's windows:
handoffTranscriptBudget(contextSize)parses the provider's own context id ("1m","200k", plain token counts) and never returns below the 400k default, so an unparsable or tiny value cannot make the handoff worse than it is today. The remaining 65% of the window stays free for the work the receiving agent was handed.Selection behaviour is unchanged — first user message pinned, conversation before tool activity, gap markers — only the ceiling moved, and
selectRowsnow takes it as a parameter instead of reading a module constant.Tests: 11 passing. The three budget-filling tests now pass an explicit test budget so they assert selection behaviour rather than tracking whatever the default happens to be; three new cases cover scaling, the fallback, and the floor.
🤖 Generated with Claude Code