{"record":{"id":"c4bddd686571ee87","repo":"shareAI-lab/learn-claude-code","slug":"token-budget-exceeded","errorCode":null,"errorMessage":"token budget exceeded","messagePattern":"token budget exceeded","errorType":"validation","errorClass":"WorkflowInputError","httpStatus":null,"severity":"error","filePath":"s16_workflow_runtime/code.py","lineNumber":461,"sourceCode":"    def phase(self, title):\n        \"\"\"Start a phase; subsequent agent()s group under it. Upsert: emitting the\n        same phase again (e.g. from each pipeline item) does not re-announce it.\"\"\"\n        self._phase = title\n        if title not in self._phases_seen:\n            self._phases_seen.add(title)\n            self.task.progress_event(\"workflow_phase\", title=title)\n\n    def log(self, message):\n        \"\"\"Emit a workflow_log progress line.\"\"\"\n        self.task.progress_event(\"workflow_log\", message=message)\n\n    async def agent(self, prompt, schema=None, label=None, phase=None):\n        \"\"\"Spawn one subagent. With a schema, force StructuredOutput + validate\n        (retry once). On resume, a cached key short-circuits the run.\"\"\"\n        label = label or (prompt[:24] + \"...\")\n        self._limits.claim_agent()\n        if self.budget.remaining() <= 0:\n            raise WorkflowInputError(\"token budget exceeded\")\n\n        key = self.journal.key(\"agent\", label, prompt, schema)\n        cached = self.journal.cached(key)\n        if cached is not MISS:\n            if schema is not None:\n                ok, err = SimpleJsonSchema(schema).validate(cached)\n                if not ok:\n                    raise WorkflowInputError(\n                        f\"cached agent output failed schema validation: {err}\"\n                    )\n            self.task.progress_event(\"workflow_agent\", label=label,\n                                     phase=phase or self._phase, status=\"cached\")\n            return cached\n\n        async with self._limits.semaphore:\n            run = await asyncio.to_thread(\n                self.runner.run, prompt, schema, label\n            )","sourceCodeStart":443,"sourceCodeEnd":479,"githubUrl":"https://github.com/shareAI-lab/learn-claude-code/blob/985456f4adea6f4df8fbad4112245dbd97444eae/s16_workflow_runtime/code.py#L443-L479","documentation":"ExecutionState.agent() checks Budget.remaining() before spawning each subagent and raises WorkflowInputError('token budget exceeded') when the run-wide token budget is already exhausted. The budget is shared across the whole workflow, including nested child workflows and parallel/pipeline agents, and only successful runs add their tokens via budget.add(). This is a hard stop so a workflow cannot silently overspend.","triggerScenarios":"Calling ctx.agent() (directly or inside pipeline/parallel stages) after prior agent runs have consumed >= the budget total configured for the workflow run. Note the check runs BEFORE the journal cache lookup, so even a resume that would only replay cached agents fails if the fresh Budget object is created with an already-exhausted total.","commonSituations":"Budget total in workflow meta set too low for the number of agent calls; a pipeline fan-out over many items multiplying token usage; resuming a large run with the same tight budget; a verbose schema causing big outputs.","solutions":["Raise the token budget total configured for the workflow (e.g. meta's budget field) so remaining() stays positive for all planned agent() calls.","Reduce agent count: batch items, shrink pipeline dimensions, or reuse one agent call for multiple inputs.","Shrink prompts/outputs: tighten the JSON schema (fewer/lower max lengths) and trim the text embedded into prompts.","If mid-run: fix the budget and resume from the last runId — cached agents replay without new token spend, but the budget total must be > 0 at each call site."],"exampleFix":"// before\nmeta = {\"name\": \"review\", \"tokenBudget\": 5000}\n// 20 pipeline agents x ~400 tokens each exhausts it\n\n// after\nmeta = {\"name\": \"review\", \"tokenBudget\": 50000}\n// or reduce fan-out:\nfor dimension in [\"security\", \"correctness\"]:  # was 6 dimensions\n    ...","handlingStrategy":"validation","validationCode":"async def agent_guarded(ctx, prompt, schema=None, label=None):\n    if ctx.budget.remaining() <= 0:\n        raise InsufficientBudget(f\"remaining={ctx.budget.remaining()}\")\n    return await ctx.agent(prompt, schema=schema, label=label)","typeGuard":"def has_budget(ctx) -> bool:\n    return ctx.budget.remaining() > 0","tryCatchPattern":"from s16_workflow_runtime import WorkflowInputError\ntry:\n    out = await ctx.agent(prompt, schema=S)\nexcept WorkflowInputError as e:\n    if \"budget\" in str(e):\n        checkpoint_and_report_budget(ctx)  # surface spent/remaining, stop cleanly\n    raise","preventionTips":["Size the token budget from a dry-run: estimate tokens per agent x number of pipeline items x dimensions, then add 30% headroom.","Log ctx.budget.spent()/remaining() in each phase so exhaustion is visible before it throws.","Keep fan-out (pipeline breadth x dimensions) proportional to the budget; cap items per run."],"tags":["workflow","budget","tokens","agents"],"backgroundTag":null,"analyzedSha":"985456f4adea6f4df8fbad4112245dbd97444eae","analyzedAt":"2026-08-14T22:02:26.028Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}