All lessons › Function A — Getting Started — Building Agentic AI › Build the Agent — From a Loop to a Running Platform
A1.7

The human gate, and the budget that stops the loop

A1
▶
Lightboard recording

Not recorded yet. The lesson below is complete and runnable today — the video is added later without touching this page.

Video placeholderpublishes automatically from recordings/A1.7.mp4
Risk

An unbounded loop against an impossible task spends until somebody notices, and a gate nobody can approve fast enough is a gate that gets removed.

Control

High-risk actions gated on a named human; budgets that bind and are recorded when they bind.

The lab

Approve one action, refuse another, then exhaust the budget on purpose.

◉Use case relevance

The agent is asked to reconcile a booking that cannot be reconciled. It tries, rephrases, tries again. Six hours later it has spent four hundred thousand tokens and exhausted a vendor's rate limit for everybody else on that integration. Nothing failed. There was simply no number at which it was supposed to stop.

At CyberTravels. Cancelling a booking and issuing a refund are CyberTravels' two irreversible actions, and both are gated in cybertravels/config.py::TOOL_POLICY. The budgets are two lines above them.

◴What this lesson is — Day 0, Day 1, Day 2

The two ceilings on an agent loop — a human gate on irreversible actions, and budgets on steps and tool calls — including what a run returns when it hits one, and why a present-but-saturated gate is not a control.

Day 0 — why

An unbounded loop against an impossible task spends until somebody notices, and a gate nobody can approve fast enough gets removed.

Day 1 — how

Gate the irreversible actions on a named human, and bound both steps and tool calls.

Day 2 — measure

Approvals per reviewer per hour — the number that says whether the gate is still a control — and the share of runs that hit a ceiling.

Measure this

what to measuredenominator or target
approvals per reviewer per hourrate against what one reviewer can actually consider
runs that hit a ceilingshare of runs

▦The framework, and how it works

        TWO CEILINGS, OPPOSITE FAILURES

   human gate    present but saturated  ─► coverage 100%, review 0%
   budget        absent                 ─► spend until somebody notices

   at the ceiling: return INCOMPLETE, and say so

Two ceilings on the same loop, and they fail in opposite directions.

The human gate

Some actions are worth pausing for. In CyberTravels those are cancel_booking and issue_refund — the ones that are hard to reverse. The runtime pauses, names the action and the scope it is about to request, and waits for a person.

The failure mode is not that the gate is missing. It is that the gate stops working while still being present: at ten approvals a day a person reads each one, at four hundred they approve the queue. Coverage stays at 100% and actual review collapses, which is why the control is measured by approvals per reviewer per hour rather than by whether it exists. B3.9 is the lesson that models it.

The budget

A loop against an impossible task does not stop. It retries, rephrases, and spends — tokens, money, and somebody else's rate limit. CyberTravels bounds two things:

  MAX_STEPS       8    model turns
  MAX_TOOL_CALLS  12   tool invocations

The important property is what happens at the ceiling: the run returns an incomplete result that says so, not a confident summary of what it managed. A budget that silently truncates is worse than none, because the output looks finished.

And hitting a ceiling is recorded as a span. A loop that quietly hit its limit on 40% of runs last week is a thing you want to know.

2 · Approve one, refuse one, then exhaust the budget

Do this — the gate, in your browser

1. Start the application:

./cybertravels/run.sh        # then open http://127.0.0.1:8000

2. Sign in as Dana and ask it to cancel booking CT-4417.

3. Watch it stop. It names the action it wants to take and the permission it is about to ask for, and then it waits for you. Only two tools do this — cancel_booking and issue_refund — because they are the two that are hard to undo.

4. Approve it, and watch the rest of the run continue.

5. Now ask again, and refuse this time. Both of your answers get written down. That is the part that matters: a gate that only records the times somebody said yes cannot answer the question an investigation asks first, which is what did it try to do.

Do this — the budget, without the browser

Two limits, and you can watch one of them bite in four lines. Start Python and type:

from cybertravels import runtime, config
print(config.MAX_STEPS, config.MAX_TOOL_CALLS)   # 8 12

b, n = runtime.Budget(), 0
while b.call():
    n += 1
print(n, b.exhausted())        # 12 tool_calls

It counts to twelve and stops, then prints the word tool_calls. That word is the point. The run does not just end — it ends saying which limit it hit, so what comes back is an unfinished answer that admits it is unfinished, rather than a confident summary of however far it got.

A note on the skill below, because the numbers will not match and the mismatch is the point rather than a mistake. CyberTravels bounds two things — model turns and tool calls. The skill audits four kinds of ceiling, including per-target and wall-clock, and on its fixture the per-target ceiling fires first at six calls. Your loop does not have a per-target ceiling. That is the finding the skill is for: an agent that may make twelve calls total can still make all twelve against one traveller's booking.

The skill

▶Real time execution as skill

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, Glob

The ceiling that binds first is the only one that matters

A 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.

When to use this

Any agentic loop, particularly one that retries, and any agent that runs on a schedule with nobody watching.

Procedure

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.

Example

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.

Output contract

{
  "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}}
}

Failure modes

  • Listing budgets without ordering them. Only the first one exists.
  • Returning partial work as an answer. The flag is the control; the number is the trigger.
  • A ceiling the agent can reset by spawning. Count the whole tree.

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.

1In your AI agent — you are taught

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 A1.7

Then restart your agent and open it in that folder. On Windows, if python3 says Python was not found, use python instead.

Then pick a1-7-the-human-gate:

agentinstall just for this agentstart it
Claude Codepython3 scripts/install_skills.py --tool claude --lessons A1.7type /a1-7-the-human-gate
GitHub Copilot (VS Code)python3 scripts/install_skills.py --tool copilot --lessons A1.7type /a1-7-the-human-gate in Chat
Cursorpython3 scripts/install_skills.py --tool cursor --lessons A1.7type / in Agent chat and search for it
Codexpython3 scripts/install_skills.py --tool agents --lessons A1.7type $a1-7-the-human-gate, or run /skills

2As one Python command — you just run it

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 A1.7

3Read the code, and the difference — nothing runs

Neither 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 A1.7 --out work/cybertravels
python3 scripts/checkpoint.py --at A1.7 --diff

On 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.

✓What you just proved

A high-risk action pausing and naming its scope, an approval and a refusal both recorded as audit rows, twelve tool calls before the ceiling binds with exhausted() naming tool_calls, and a budget ceiling returning an incomplete result rather than a summary. From the skill: a per-target ceiling reported as missing, which it is.

✎Your turn

Raise MAX_TOOL_CALLS in cybertravels/config.py to 500 and give the agent a task it cannot finish. Watch the cost, and then answer the question the skill raised: twelve calls is a ceiling on the run, and nothing yet stops all twelve landing on one traveller's booking. Write down the number you think that should be. B3.4 builds exactly this ceiling, and it is worth having guessed first — the number in the code is four.

→Where this leaves you

What you can do now. A running agentic platform you built yourself: a reasoning loop with an independent verifier, two MCP resource servers behind a process boundary, a workload identity per agent, per-action delegation that a resource server actually enforces, memory that records where its contents came from, signed agent-to-agent envelopes, a human gate and a budget that binds.

What you still cannot do. It runs, and you cannot yet tell anybody what it did. There is no trace, the audit rows cannot say what motivated an action, and the only evidence it works is that you watched it work once.

A2.0 — why a demo is not a system, and the three things that separate them.

Tools used

OPA

Skill source: skills/runtime/budget-and-stop-condition-audit

← PreviousA1.6 · Agent to agent — handing work over without laundering authority