Not recorded yet. The lesson below is complete and runnable today — the video is added later without touching this page.
recordings/B3.4.mp4Without a ceiling the loop runs until an external system stops it, and the failure mode is denial of service against yourself.
Ceilings bound to the loop, with the run terminating rather than degrading when one is hit.
Run a looping agent against each ceiling and record which one fires first.
A budget is what makes "autonomous" a bounded word. Without one, the honest description of the worst case is "until someone notices", and nobody signs off on that when it is written down.
Budget.target() and Budget.tokens() to cybertravels/runtime.py and two ceilings to config.py, because twelve calls all landing on Northwind Rail is, from Northwind's side, an attack. exhausted() names which ceiling bound — "the run stopped" and "the run stopped hammering one vendor" are different incidents.Ceilings that bound what the loop does to any one place, not only how long it runs: per-target call limits and a token budget, with the exhausted ceiling named so the incident is actionable rather than just a stopped run.
Without a ceiling the loop runs until an external system stops it, and the failure is denial of service to everything sharing the quota.
Ceilings bound to the loop, terminating the run rather than letting it degrade.
Runs terminated by their own ceiling, and spend per run at the 95th percentile.
Measure this
| what to measure | denominator or target |
|---|---|
| runs terminated by their own ceiling | share of runs |
| spend per run | currency at the 95th percentile |
bounded run
+-------------------------------------------+
| tokens <= 60k wall clock <= 5 min |
| steps <= 25 spend <= $2.00 |
| actions <= 3 writes, 0 deletes |
+-------------------------------------------+
|
hit any ceiling -> stop, report, hand back
"autonomous" now has a worst case you can write on a pageMitigates: T4 Resource Overload · T10 Overwhelming Human-in-the-Loop.
A budget is what makes "autonomous" a bounded word.
B1.13's loop had no exit condition, so it ran until something outside it intervened. A ceiling turns that into a defined worst case — and a worst case is the thing you can actually put in a design document, an incident plan or a risk register.
Four ceilings, because they bound different failures:
Tokens or cost. The visible one. Bounds the bill.
Wall-clock time. Bounds a workflow step that never returns.
Actions. Bounds consequence, and it is the one that matters for security. Twenty tool calls is a very different blast radius from two thousand, whatever either costs.
Downstream calls per target. Bounds harm to other people. B1.13's damage was not the token spend, it was the capacity taken from everybody else.
Two design rules:
Terminate, do not degrade. A loop that hits a ceiling and keeps going with a smaller model or a shorter context has not been bounded, it has been redirected.
Make the ceiling visible in the output. stopped_by: action_budget is a signal to a human that this run is incomplete. Silent truncation is how a partial result becomes a reported success, which is B1.16 arriving through a different door.
What this control closes. Turns an unbounded loop into a defined worst case, and bounds the harm to other people's capacity, not just your bill.
A loop usually carries several budgets, and in practice only ever one of them fires.
The skill runs B1.13's impossible task against all of them and reports which budget binds first. Then it checks something people forget: what the loop actually returns when it stops.
If partial work comes back looking like a finished answer, the budget has not protected you. It has turned a cost problem into a quality problem.
The skill — skills/runtime/budget-and-stop-condition-audit/SKILL.md
name: budget-and-stop-condition-audit
description: >-
Check that an agent loop has ceilings that actually bind — per-target, token
and action — and that hitting one returns an incomplete result rather than a
summary of what it managed. Use when reviewing loop termination, retries, or
an agent that runs unattended.
allowed-tools: Read, Grep, GlobA loop usually has several budgets and only one of them ever fires. Which one fires, and how early, is the design; the rest are decoration. The second half matters more: what the loop returns when it stops. A run that halts and reports its partial work as an answer has converted a budget into a quality problem.
Any agentic loop, particularly one that retries, and any agent that runs on a schedule with nobody watching.
1 — Enumerate every ceiling. Steps, tokens, wall clock, cost, actions, per-target attempts. For each, where it is checked and what it does on breach.
2 — Order them by when they bind. Run a task that consumes all resources and record which fires first. A per-target ceiling usually binds long before a token budget, which means the token budget was never the control.
3 — Test the breach path. The result must carry an explicit incomplete flag. Check what a caller does with it: a loop that returns complete: False into a pipeline that ignores the field has the same outcome as no budget.
4 — Check the ceiling is not resettable by the agent. A budget the loop can extend on its own behalf — by starting a sub-task, spawning a child, or retrying at a new target — is advisory.
5 — Derive the numbers from observation. State the p95 of a legitimate run and set each ceiling above it. A round number either strangles real work or never fires.
Input — the fixture committed at the top of scripts/budget_and_stop_condition_audit.py. Edit it and re-run: the buckets, counts and verdicts below are derived from it, not hard-coded.
Output — the opening lines of a real run:
steps taken : 6
stopped by : per_target (reports-db)
complete : False <- visible in the output, not silent
ceiling limit used
tokens 50000 10800
seconds 60 2.4
actions 20 6
The run continues past this. test_skills.py executes the script on every build, with no model configured — so what CI proves is that it runs and refuses legibly, not that it prints these lines. The output above is a recorded run against a real model, and nothing re-diffs it: a model's answer is not reproducible, and this repository does not claim otherwise anywhere it can be checked.
{
"ceilings": [{"name": "str", "value": 0, "checked_at": "str", "binds_at_step": 0}],
"first_to_bind": "str",
"breach": {"returns_incomplete": true, "caller_respects_flag": false},
"agent_resettable": ["str"],
"basis": {"p95_legitimate_run": {"steps": 0, "tokens": 0}}
}
Three ways to do this lesson. Pick one. They run the same procedure against the same code — what changes is who drives it, and how much you see.
The agent reads the lesson, explains the idea, fetches the code, runs the check and answers your questions as you go. This is the one to take if you are here to learn.
Install it, once. From the folder you cloned. --all does every agent it finds on your machine:
python3 scripts/install_skills.py --all --lessons B3.4Then restart your agent and open it in that folder. On Windows, if python3 says Python was not found, use python instead.
Then pick b3-4-budgets-and-stop-conditions:
| agent | install just for this agent | start it |
|---|---|---|
| Claude Code | python3 scripts/install_skills.py --tool claude --lessons B3.4 | type /b3-4-budgets-and-stop-conditions |
| GitHub Copilot (VS Code) | python3 scripts/install_skills.py --tool copilot --lessons B3.4 | type /b3-4-budgets-and-stop-conditions in Chat |
| Cursor | python3 scripts/install_skills.py --tool cursor --lessons B3.4 | type / in Agent chat and search for it |
| Codex | python3 scripts/install_skills.py --tool agents --lessons B3.4 | type $b3-4-budgets-and-stop-conditions, or run /skills |
No agent, no conversation. The same lesson executes and prints the same readback. Take this one if you have no assistant, if you are reviewing rather than learning, or if you want the result without the teaching around it.
python3 scripts/lesson.py B3.4Neither of the above, and no model needed. The first command writes CyberTravels exactly as it stood at the end of this lesson; the second prints the diff — the lines this lesson added or changed, and nothing from any later one.
python3 scripts/checkpoint.py --at B3.4 --out work/cybertravels
python3 scripts/checkpoint.py --at B3.4 --diffOn Windows, type python wherever these say python3 — and use \ instead of / in the paths. Everything else is identical. A0.0 step 2 explains why.
Ways 1 and 2 need a model; way 3 does not. A signed-in Claude Code CLI needs no API key, and any OpenAI-compatible endpoint works too. With neither, the audit exits 2 and says so rather than inventing an answer. A0.0 sets this up.
Ways 1 and 2 both end with the same readback: What I did, What changed, The number and Read this next. A step that was skipped or refused is reported as exactly that, never as a pass.
If you did the last lesson’s challenge, expect this to stop. Most challenges ask you to edit the code, and ways 1 and 2 will not overwrite your edits: you get stopped, not a failure, with two options — --out for a fresh folder that keeps your work, or --force to discard it and start this lesson clean.
The impossible task from B1.13 now stops after six steps, halted by the per-target ceiling — before the token or action budgets are anywhere near exhausted — and the result carries complete: False rather than reporting what it managed.
Check whether your agent's budget bounds calls per downstream target. If it only bounds tokens, your cost is protected and the service your agent hammers is not.
Tools used
Skill source: skills/runtime/budget-and-stop-condition-audit