v0.30.2 feat: dream synthesize stops dropping fat transcripts#754
Merged
v0.30.2 feat: dream synthesize stops dropping fat transcripts#754
Conversation
The subagent handler now detects 400 "prompt is too long" responses
from the Anthropic SDK and rethrows as UnrecoverableError. The worker
already routes UnrecoverableError straight to `dead`, so doomed jobs
fail terminally on first attempt instead of stalling 3x with the same
oversized prompt.
isPromptTooLongError matches the production message verbatim
("prompt is too long: N tokens > N maximum"), case-insensitive, on
both the outer message and inner error.message paths. Defensive
secondary match for status=400 + invalid_request_error/request_too_large
with the words "too long"/"exceed"/"maximum".
9 unit cases pin the detection: production wording, case folding,
nested SDK shape, defensive 400 paths, unrelated 400s, transient
errors, null/empty inputs.
The synthesize phase now chunks oversized transcripts at paragraph boundaries instead of submitting one giant prompt that 400s on Anthropic. Closes the v0.30 dream-cycle queue clog where 1.7M-token transcripts dead-lettered after 3 stalls and re-discovered every cycle. D1: per-chunk budget = floor(model_context_tokens × 0.9 × 3.5). MODEL_CONTEXT_TOKENS keys on resolved Anthropic ids (Opus 4.7 = 1M, Sonnet 4.6 = 200K, Haiku = 200K). Non-Anthropic models fall back to 180K-token safe default with a once-per-process stderr warning. dream.synthesize.max_prompt_tokens overrides the model lookup (token-shaped, name from PR #748, floor 100K). D5: on max_chunks_per_transcript cap hit, log + skip; do NOT write to dream_verdicts. Closes the cache-poisoning class — next cycle re-attempts under whatever budget is then current. D6: orchestrator-side deterministic slug rewrite, zero Sonnet trust. collectChildPutPageSlugs raw-fetches every (job_id, slug) pair (no SELECT DISTINCT — that erased the collision evidence the audit claimed to detect) and rewrites bare-hash6 slugs to <hash6>-c<idx> for chunked children. D8: pre-fan-out lookup of completed legacy `dream:synth:<filePath>: <hash16>` jobs. Transcripts already synthesized under the single-chunk shape skip submission with `already_synthesized_legacy_ single_chunk` instead of resubmitting under chunked keys. D9: hash-deterministic chunk boundaries. The 3-tier ladder lifted from PR #748 (## Topic: > --- > nearest \\n) is fed a back-half search-window offset derived from contentHash. Same content always chunks identically across runs; chunk N of a previously-failed transcript produces byte-identical content on retry. D10: 24-chunk default cap, operator-configurable via dream.synthesize.max_chunks_per_transcript. 18 unit cases pin the chunker (boundary ladder, hash determinism, hard fallback, slug rewrite all 7 shapes). 4 PGLite E2E cases pin fan-out shape (single-chunk legacy key parity, multi-chunk chunked key shape) + skip paths (D5 cap hit no verdict-cache write, D8 legacy-key skip). Credits PR #748 (Wintermute) for the boundary ladder, config key naming, and 3.5 chars/token estimator. This branch supersedes #748 with the structural safeguards (model-aware budget, terminal-error classify, slug rewrite, hash-determinism, doctor surfacing).
queue_health gains a 4th subcheck counting dead `subagent` jobs in the last 24h whose error_text starts with `prompt_too_long:`. When present, prints a fix hint pointing at `gbrain dream --phase synthesize --dry-run --json` to identify the fat transcripts and naming the two operator escape hatches (`dream.synthesize.max_prompt_tokens` for budget tuning, larger-context model for capacity). Operators now see the chunking failure mode without grepping minion_jobs by hand.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- README dream help: 8-phase → 9-phase, mention v0.30.2 chunking + config keys - CLAUDE.md synthesize.ts: chunker + per-chunk idempotency + D6 slug rewrite + D7 scope + D8 legacy-key - CLAUDE.md subagent.ts: prompt_too_long terminal classification - CLAUDE.md doctor.ts: queue_health subcheck 4 (dead-lettered prompt_too_long) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The docs/ pass extended three Key Files entries in CLAUDE.md (synthesize.ts, subagent.ts, doctor.ts). The auto-derived llms-full.txt bundle picks up those CLAUDE.md changes via build-llms; the build-llms test caught the drift in CI. Generated by: bun run build:llms
This was referenced May 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.
Summary
Dream synthesize stops dropping fat transcripts. Subagents that overflow Anthropic's context die once, not three times. The queue stops clogging.
The v0.30 dream cycle has been stalled for one user since May 2 — daily aggregated transcripts at 2.7-4.5MB each generate 1.7M-token Anthropic prompts, hit the 1M-token hard limit, and 400. The subagent handler treated those failures as renewable, so doomed transcripts stalled three times before dead-lettering and every new cycle re-discovered the same fat transcripts. Six days of synth backlog, queue full of doomed work.
This PR ships:
Chunking + terminal classify —
src/core/cycle/synthesize.ts+src/core/minions/handlers/subagent.tssplitTranscriptByBudget(content, contentHash, maxChars)with 3-tier boundary ladder (## Topic:→---→\n), seeded with a deterministic offset fromcontentHashso the same content always chunks identically (D9 stable chunk identity).MODEL_CONTEXT_TOKENSmap (Opus 4.7 = 1M, Sonnet 4.6 = 200K, Haiku = 200K) drives afloor(context × 0.9 × 3.5 chars/token)per-chunk budget. Non-Anthropic ids fall back to 180K-token safe default with stderr warning.dream.synthesize.max_prompt_tokensconfig override (token-shaped, name from PR Fix dream/synthesize pipeline for large transcripts #748, floor 100K) beats the model lookup.dream.synthesize.max_chunks_per_transcript(default 24, operator-configurable). On cap hit, log + skip; do NOT write todream_verdicts(D5 closes the cache-poisoning class).UnrecoverableErrorso the worker routes them straight todeadon first attempt — no more 3-stall retry pile.Orchestrator slug rewrite (D6) — zero Sonnet trust
collectChildPutPageSlugsno longer doesSELECT DISTINCT(which erased the collision evidence the audit claimed to detect). Raw-fetches every (job_id, slug) pair, then for chunked children rewrites bare-hash6 slugs to<hash6>-c<idx>in-memory.Migration safety (D8)
dream:synth:<filePath>:<hash16>jobs. Transcripts already synthesized under the single-chunk shape skip submission withalready_synthesized_legacy_single_chunkinstead of resubmitting under chunked keys.Doctor visibility —
src/commands/doctor.tsqueue_healthgains subcheck 4 counting deadsubagentjobs in the last 24h whoseerror_textstarts withprompt_too_long:. Fix hint points atgbrain dream --phase synthesize --dry-run --json.Credits PR #748 (Wintermute) for the boundary ladder, config key naming, and 3.5 chars/token estimator. This branch supersedes #748 with the structural safeguards (model-aware budget, terminal-error classify, deterministic chunk identity, slug rewrite, doctor surfacing).
Test Coverage
Tests: 27 unit + 4 PGLite E2E = 31 new cases. All green.
Pre-Landing Review
The plan went through
/plan-eng-review(4 issues found, 0 critical gaps, all resolved as D1-D4) plus two rounds of/codexoutside-voice review:AskUserQuestionand resolved as D5–D10 in the plan.Implementation matches the resolved plan.
bun run verify(8 shell pre-checks + typecheck) clean. No new pre-landing review issues found.Eval Results
No prompt-related files changed — evals skipped.
Plan Completion
10/10 decisions implemented:
MODEL_CONTEXT_TOKENS × 0.9 × 3.5 chars/token)queue_healthextensiongbrain jobs prune --status deaddocumented in CHANGELOGVerification Results
No dev server in this repo — plan verification skipped (this is a CLI / library, not a web app).
TODOS
No items in TODOS.md were completed by this branch. The two named follow-ups (per-turn token-budget guard in subagent.ts, tighter token estimator for dense content) are documented in the CHANGELOG's
### Out of scope (deferred to v0.30.3+)section.Documentation
8-phaseto9-phase(the cycle has been 9-phase since v0.29) and extended with a one-liner about the v0.30.2 fat-transcript chunker plus the two operator config keys (dream.synthesize.max_prompt_tokens,dream.synthesize.max_chunks_per_transcript).src/core/cycle/synthesize.ts: notessplitTranscriptByBudget(content, contentHash, maxChars)(deterministic-offset chunker on the## Topic:→---→\nladder), theMODEL_CONTEXT_TOKENS × 0.9 × 3.5 chars/tokenbudget formula with 180K-token fallback for non-Anthropic ids, per-chunk idempotency keys (dream:synth:<filePath>:<hash16>:c<i>of<n>), the legacy single-chunk key preservation (D8) so existing brains skip withalready_synthesized_legacy_single_chunk, the orchestrator-side<hash6>-c<idx>slug rewrite incollectChildPutPageSlugs(D6, zero Sonnet trust), and the explicit D7 scope (initial prompt size only; turn-N caught by the newsubagent.tsterminal classifier).src/core/minions/handlers/subagent.ts: notes that Anthropic 400prompt is too longresponses now classify asUnrecoverableErrorso the job goes straight todeadon first attempt instead of stalling three times.src/commands/doctor.ts: notesqueue_healthgains a fourth subcheck — dead-lettered subagent jobs whoseerror_textstarts withprompt_too_long:within the last 24h, with fix hints pointing atgbrain dream --phase synthesize --dry-run --json(identify the offender) andgbrain jobs prune --status dead --queue default(clean up).To take advantage of v0.30.2block.0.30.2by the metadata commit.package.jsonsynced.Commit:
6943f421(docs: update README + CLAUDE.md for v0.30.2).Test plan
test/cycle-synthesize-chunker.test.ts+test/subagent-prompt-too-long.test.tstest/e2e/dream-synthesize-chunking.test.tstest/cycle-synthesize.test.ts+test/e2e/dream-synthesize-pglite.test.ts+test/e2e/dream-allow-list-pglite.test.tsbun run typecheckcleanbun run verifyclean (privacy, jsonb-pattern, progress-stdout, test-isolation, wasm-embedded, admin-build, admin-scope-drift, cli-executable, typecheck)gbrain dream --phase synthesize --dry-run --json— confirm transcripts that previously generated 1.7M-token prompts now reportchunks: Ninstead of submitting (run after merge)gbrain jobs prune --status dead --queue defaultto clear pre-v0.30.2 doomed jobs🤖 Generated with Claude Code
Need help on this PR? Tag
@codesmithwith what you need.