Skip to content

Give the agenda planner a token budget that survives thinking - #243

Merged
EllAchE merged 1 commit into
mainfrom
agenda-token-budget-20260817-025400
Aug 17, 2026
Merged

Give the agenda planner a token budget that survives thinking#243
EllAchE merged 1 commit into
mainfrom
agenda-token-budget-20260817-025400

Conversation

@EllAchE

@EllAchE EllAchE commented Aug 17, 2026

Copy link
Copy Markdown
Owner

Finishes what #221 started. The AI reviewer and the agenda planner had the same defect; #221 fixed the reviewer, this fixes the planner.

The bug

max_tokens is a single ceiling over reasoning and visible text. claude-sonnet-5 reasons by default when the request omits thinking. lib/ai/agenda.ts passed max_tokens: 4096 — sized for the placements alone — so a long thinking pass could spend the budget and leave the JSON cut off.

This one is more exposed than the reviewer was. A programme is exactly the kind of answer that earns a long think, and the response body grows with the queue, so both halves are large.

It failed more quietly than the reviewer did

The reviewer at least said something was wrong. Here, extractJson returns no placements from a fragment — exactly as it does from prose — and the caller then returns status: 'ok' with an empty proposal. The organizer sees a planner that apparently had nothing to suggest, when in fact it had plenty and ran out of room saying it. No error, no log line, nothing to investigate.

The fix

  • Ceiling raised to 16,000, matching the reviewer. Still under the size where a non-streaming request risks an HTTP timeout, and an unused ceiling is free — billing counts tokens generated, not tokens allowed.
  • proposalText reads stop_reason and withholds a cut-off draft, so the caller returns status: 'error' naming the real cause rather than an empty board. The message suggests a shorter queue, which is the half of the budget the organizer actually controls.

The two alternatives stay rejected for the same reasons as in #221: budget_tokens is rejected outright by this model, and turning thinking off is documented to let reasoning prose leak into the text block — which is the string extractJson reads.

Nothing about the trust boundary changed. Every placement still goes through validateProposal and the same detectConflicts the board uses.

Tests

New lib/ai/agenda.test.ts (6 cases) — the file had none. Covers text blocks joined, non-text blocks ignored, stop_reason: "max_tokens" withholding the draft, and the point of the guard: that truncation is not detectable from the text, since a budget can run out on the token after a syntactically complete object.

bun run test (1758 passed / 163 files), bun run lint, bun run typecheck all clean.

Note for #173

#173 also touches lib/ai/agenda.ts. This change is confined to the constant, one new exported function, and the block that reads the response — but whichever merges second should check MAX_OUTPUT_TOKENS survived, and lib/ai/agenda-optimizer.ts deserves the same look before it ships.

🤖 Generated with Claude Code

The same defect fixed in `lib/ai/review.ts` in #221. `max_tokens` is one ceiling
over reasoning and visible text together, and `claude-sonnet-5` reasons by
default when the request omits `thinking`. The call passed 4096 — sized for the
placements alone — so a long think could spend the budget and leave the JSON cut
off. A programme is exactly the answer that earns a long think, and the body
grows with the queue, so both halves are large here.

Raise it to 16,000, matching the reviewer.

Downstream this failed more quietly than the reviewer did. `extractJson` returns
no placements from a fragment, exactly as it does from prose, and the organizer
was shown an empty proposal with `status: "ok"` — the planner appearing to have
nothing to suggest, when it had plenty and ran out of room saying it.

Add `proposalText`, which reads `stop_reason` and withholds a cut-off draft so
the caller can name the real cause. Suggest a shorter queue, since that is the
half of the budget the organizer controls.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@EllAchE
EllAchE merged commit 8e535e3 into main Aug 17, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant