Repository navigation
fix(orchestration): byte-cap MCP outputs, tombstones, and transcript tail rings - #510
Merged
Merged
Conversation
…tail rings Orchestration MCP reads only capped message COUNT, never size. One child answer was mirrored 5x per orchestration_read_agent response (7x for closed agents in wait_agents), a single huge child message could blow the parent's context, closed-agent tombstones stored pre-close outputs verbatim in main for 24h, and the transcript reader's tail path buffered raw untruncated items in its ring before bounding. - orchestrationMcp: maxCharsPerMessage (default 4000) on every extracted text incl. the live turn; maxCharsPerAgent running budget (default 24000) over the message tail, oldest dropped first; per-message and per-output truncated/totalChars metadata; record-level latest/final mirrors become 400-char excerpts, keeping exactly one full capped copy at output.latestAssistantText (lifecycle hazard preserved: text is shortened, never dropped). - MCP tools: read_agent/read_run_outputs/wait_agents accept maxCharsPerMessage/maxCharsPerAgent; read_run_outputs and wait_agents add maxTotalChars (default 120000) with summary-first degradation — every agent keeps status/lifecycle/counts plus a short excerpt, leftover budget restores full outputs in order. - OrchestrationBridge: tombstones byte-clamped at storage time (MAX_CLOSED_AGENT_CHARS 256KiB + per-field clamp), limitOutputMessages honors caller char caps, and the new cap params joined the cachedReadRunOutputs cache key so differently-capped reads cannot collide in the 250ms polling cache. - AgentTranscriptReader: tail ring now stores per-item truncated clones (boundItems stays the final pass), and the final-projection re-add guards against the ring holding clones instead of raw references. Closes #373 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…or, total-budget enforcement, recovery hints Finding 1 (blocker): budget starvation dropped the NEWEST message. When maxCharsPerAgent was smaller than one per-message-capped text, both the renderer's visibleMessageSummary and the bridge's capMessagesByChars returned `messages: []` for an agent that plainly produced output. The newest message is now exempt from dropping: it is truncated down to the agent budget instead (minimal-excerpt floor), in both paths; older messages drop exactly as before. Finding 2 (blocker): boundOutputsToTotalChars never enforced maxTotalChars when the summary-only set already exceeded the budget — `used` started over budget, the upgrade loop never fired, and the whole summary set was returned anyway. Summaries now shrink their free-form text fields in progressive steps (200/100/48 chars) until the set fits; the floor keeps latestAssistantText present and non-empty (lifecycle presence invariant). Residual overflow is per-agent JSON skeleton only, which cannot be cut without dropping agents entirely. wait_agents' sibling `agents` array previously rode outside the budget; its JSON size is now charged against maxTotalChars as reservedChars so one budget governs the whole tool response. Finding 3 (nit): the three orchestration read/wait tool descriptions now tell callers how to recover truncated content — re-read with explicit larger caps, or agent_transcript_read_file for the disk-backed transcript. Verified with throwaway runtime smokes (not committed, per repo test policy): all three fail on the pre-fix code and pass on this commit; tsc node+web and the vitest suite pass (hotkeyBinding pre-existing failure ignored; SessionRecorderManager flake passes in isolation). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This was referenced Jul 9, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Orchestration MCP outputs were bounded by message COUNT only — nothing capped size (#373):
orchestration_read_agentresponse —output.messages[last].text,output.agent.latestAssistantText,output.agent.finalAssistantText,output.latestAssistantText,output.finalAssistantText.finalAssistantTextwas never independently computed; it was always a verbatim alias oflatestAssistantText. Closed agents inorchestration_wait_agentshit seven times (two more copies inagents[i]).visibleMessageSummarycapped count (maxMessages, default 20) butmessages[].textand the live-turn text were fullentryTextContent— one pasted diff or giant tool dump in a child message blew the parent's context.OrchestrationBridge.noteClosedkept the entire pre-close output in main's memory for up to 24h x 500 agents;limitOutputMessagescount-sliced only.AgentTranscriptReaderpushed RAW untruncated items into the tail ring (tailschema max 10,000) while streaming; per-item truncation only happened afterwards inboundItems.Fix
Renderer (
orchestrationMcp.ts) —maxCharsPerMessage(default 4000) on every extracted text including the live turn;maxCharsPerAgentrunning budget (default 24000) across the message tail, oldest dropped first (newest-first scan, mirroring the transcript reader's budget semantics); per-message and per-outputtruncatedflags +totalCharsoriginal-size estimates. Truncation marker matchestruncateItemText(…\n[truncated]). The record-levellatestAssistantText/finalAssistantTextmirrors become 400-char excerpts; exactly ONE full (per-message-capped) copy remains atoutput.latestAssistantText. Lifecycle hazard preserved:hasDurableOutputstill sees non-empty text — truncation shortens, never drops, so completed children cannot regress towaiting.MCP tools (
createBuiltInMcpServer.ts) —orchestration_read_agent,orchestration_read_run_outputs,orchestration_wait_agentsacceptmaxCharsPerMessage/maxCharsPerAgent(ranges match theagent_transcript_*tools); the two multi-agent tools addmaxTotalChars(default 120,000) enforced in the handler: over budget, outputs degrade summary-first — every agent keeps status/lifecycle/counts plus a short excerpt, then leftover budget restores full outputs in order — with top-leveltruncated: true. No agent is ever dropped entirely.Bridge (
OrchestrationBridge.ts) —noteClosedbyte-clamps stored tombstones (MAX_CLOSED_AGENT_CHARS= 262,144 message budget + 16,384 per mirror field, defense-in-depth against uncapped senders);limitOutputMessageshonors caller char caps for tombstone serving; the new cap params joined thecachedReadRunOutputscache key so differently-capped reads can't collide in the 250ms polling cache.Transcript reader (
AgentTranscriptReader.ts) — the tail ring now stores per-item truncated clones (idempotent;boundItemsstays the final pass), and the final-projection re-add check now guards against the ring holding clones instead of raw references (pre-existing duplicate-final edge case).Plumbing — request types in
orchestrationTypes.ts, renderer handler pass-through inworkspace/hook/index.ts.Verification
npx tsc -b tsconfig.node.json— cleannpx tsc --noEmit -p tsconfig.web.json— 0 errorsNODE_ENV=test npx vitest run— 467 passed; only the pre-existingagent-voice-dictation hotkeyBindingsuite failure (present on main)Closes #373
🤖 Generated with Claude Code