can1357/oh-my-pi · error · ToolError

agent() blocked: turn token budget exhausted (${turnBudget.s

Error message

agent() blocked: turn token budget exhausted (${turnBudget.spent}/${turnBudget.total} output tokens). Raise or drop the +Nk! ceiling to continue.

What it means

The eval session can impose a hard per-turn output-token ceiling (+Nk!). Before spawning a subagent, the bridge checks the turn budget and refuses agent() calls once spent tokens reach the total, so a runaway eval cannot exceed the cap. The message tells you to raise or remove the hard ceiling.

Source

Thrown at packages/coding-agent/src/eval/agent-bridge.ts:128

function buildSubagentFailureMessage(agentName: string, result: SingleResult): string {
	const abortReason = trimToUndefined(result.abortReason);
	if (result.aborted && abortReason) return abortReason;
	return (
		trimToUndefined(result.error) ??
		trimToUndefined(result.stderr) ??
		abortReason ??
		`agent() subagent '${agentName}' failed.`
	);
}

/**
 * Run a single subagent on behalf of an eval cell's `agent()` call.
 */
export async function runEvalAgent(args: unknown, options: EvalAgentBridgeOptions): Promise<EvalAgentResult> {
	const parsed = parseAgentArgs(args);
	const turnBudget = options.session.getTurnBudget?.();
	if (turnBudget?.hard && turnBudget.total !== null && turnBudget.spent >= turnBudget.total) {
		throw new ToolError(
			`agent() blocked: turn token budget exhausted (${turnBudget.spent}/${turnBudget.total} output tokens). Raise or drop the +Nk! ceiling to continue.`,
		);
	}
	const isolation =
		Object.hasOwn(parsed, "isolated") || Object.hasOwn(parsed, "apply") || Object.hasOwn(parsed, "merge")
			? {
					...(parsed.isolated !== undefined ? { requested: parsed.isolated } : {}),
					...(parsed.merge === false ? { merge: "patch" as const } : {}),
					...(parsed.apply !== undefined ? { apply: parsed.apply } : {}),
				}
			: undefined;

	try {
		const execution = await withBridgeTimeoutPause(
			options.emitStatus,
			() =>
				runStructuredSubagent({
					session: options.session,

View on GitHub (pinned to 9690622007)

Solutions

  1. Raise the hard ceiling, e.g. +50k! instead of +10k!
  2. Drop the ! to make the budget soft (advisory) if a hard cap isn't needed
  3. Restructure the eval to use fewer/smaller agent() calls
  4. Split the work across turns so each turn gets a fresh budget

Example fix

// before (tight hard ceiling drains before agent())
// eval header: budget +10k!
agent({ prompt: 'refactor module' })
// after: raise or soften
// eval header: budget +100k!  (or +100k for soft)
agent({ prompt: 'refactor module' })
Defensive patterns

Strategy: validation

Validate before calling

const tb = session.getTurnBudget?.();
if (tb?.hard && tb.total !== null && tb.spent >= tb.total) console.warn('budget exhausted; raise +Nk! before agent()');

Type guard

null

Try / catch

try { await agent(args) } catch (e) { if (/turn token budget exhausted/.test(e.message)) { raiseBudgetAndRetry(); } else throw e }

Prevention

When it happens

Trigger: Calling agent() in an eval when the session's hard turn budget is fully spent (spent >= total, total !== null) — e.g. after many prior tool calls or large outputs consumed the +Nk! allocation.

Common situations: Eval configured with a tight +10k! hard budget but the workflow needs subagents late in the turn; loops calling agent() repeatedly until the budget is drained; forgot the ! makes the cap hard.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/a01c3b88b6002d10. Report an issue: GitHub.