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

The loop — model, tools, and the step that turns text into an action

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.1.mp4
Risk

A loop with no verifier accepts whatever the model says it did, and a demo that 'works' has never been checked.

Control

Plan, act, verify — with the verifier independent of the thing it checks.

The lab

Build the loop and add the independent verifier that decides what it may accept.

◉Use case relevance

The difference between a demo and a system is one line. In the demo, the model decides to call a tool and the tool is called. In the system, the model proposes and separate code decides. Every control in the rest of this commons lives in the gap between those two sentences, and a loop written without the gap has nowhere to put any of them.

At CyberTravels. The loop is cybertravels/runtime.py. execute_tool is the line where the model stops proposing and CyberTravels' own code starts deciding — every control in Function B attaches to it.

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

The reasoning loop in three stages: the model proposes, your code disposes, and something independent of the model verifies. Plus the exit condition, which cannot be the model agreeing that it is done.

Day 0 — why

A loop that accepts whatever the model says it did has no controls in it, because there is nowhere to put one.

Day 1 — how

Write the loop in three stages — plan, act, verify — with the verifier independent of the model.

Day 2 — measure

Runs the verifier rejected. Zero means either the loop is perfect or the verifier is not independent, and it is the second one.

Measure this

what to measuredenominator or target
runs the verifier rejectedcount per batch; zero means the verifier is not independent, not that the loop is perfect

▦The framework, and how it works

        PLAN, ACT, VERIFY

   model proposes ──► YOUR CODE DECIDES ──► tool
        ▲                    │
        │                    ▼
        └──── verifier ◄── result
              (not the model)

  a loop with two stages is a demo · the third makes it a harness

The loop is four lines long and everything else in this commons is a consequence of it.

while not done:
    step = model(context)          # the model proposes
    if step.wants_a_tool:
        result = you_decide(step)  # YOUR code disposes
        context += result

The third line is the whole subject. The model proposes and your code disposes — and a system where those two are the same line has no controls in it, because there is nowhere to put one.

Plan, act, verify

A loop with two stages plans and acts. A loop with three stages also checks, and the check is what separates a harness from a demo:

  • Plan — the model chooses a tool and arguments.
  • Act — your code applies policy, then calls the tool.
  • Verify — something other than the model decides whether what came back is acceptable.

The independence in the third stage is not a detail. A model asked to grade its own output will grade it generously, and a loop that terminates on the model saying "done" terminates on an opinion. C2.1 measures exactly this and finds a 1.5B model with an independent verifier beating a much larger one without.

The exit condition

Every loop needs one that does not depend on the model agreeing. Yours will have three: the task completed, the step budget ran out, or a control refused. The second and third are the ones that matter, and A1.7 builds them.

2 · The loop, and the verifier that is not the model

The file is cybertravels/runtime.py. Three things live in it, and they are the three stages from the section above:

stagethe codewhat it does
planrun() — the while budget.step() loopasks the model what to do next
actexecute_tool()checks the rules, gets a token, makes the call
verify_verify_result(), at the end of execute_tooldecides whether the answer that came back is acceptable

Do this

1. Open cybertravels/runtime.py and find _verify_result. It is about twenty lines long.

2. Read what goes into it. Three things: the name of the tool, the arguments it was called with, and the result that came back.

3. Notice what is missing from that list: the model. The function is never given one, so it cannot ask one anything. That is the whole idea — the thing that checks the work is not the thing that did the work.

4. Now make it reject something. You do not need a server running for this. Start Python in your checkout and type:

from cybertravels import runtime

# we asked for a refund of 140 and the server says it paid 1400
print(runtime._verify_result("issue_refund", {"amount": 140},
                            {"amount": 1400, "ok": True}))

It prints the reason it refused:

asked to refund 140, resource server refunded 1400

5. Now give it a correct result and watch it stay quiet:

print(runtime._verify_result("issue_refund", {"amount": 140},
                            {"amount": 140, "ok": True}))

This prints None. In this function, None means nothing wrong here — there is no reason to give, so it gives none.

What you have just seen. A refund that came back ten times too large was caught by nine lines of arithmetic, with no AI involved. When that happens inside a real run, the rejection is recorded with the label at="verifier", which is how A2.1 tells it apart from the other three places a request can be refused.

The skill below is the one C2.1 uses to review a harness. Run it against the loop you are writing, not after it ships.

The skill

▶Real time execution as skill

The skill — skills/appsec/agentic-harness-loop/SKILL.md

name: agentic-harness-loop
description: >-
  Build and check the plan-act-verify loop that turns a model into a harness —
  including the exit condition, the parser, and an independent verifier that
  decides what the loop is allowed to accept. Use when writing an agentic
  pipeline, or when a loop reports success on work nobody checked.
allowed-tools: Read, Grep, Glob

The loop is yours; the model is a component in it

A harness is four moves — plan, act, verify, stop — and the security engineer owns three of them. What the loop accepts is decided by the verifier, not by the model, and a loop with no verifier accepts whatever came back and reports success.

When to use this

Whenever you are about to put a model in a for-loop: triage, remediation, detection authoring, anything that iterates until it is satisfied.

Procedure

1 — Write the exit condition before the prompt. A loop whose exit is easier than the work will take the exit. Do not offer the model a way to declare itself done; decide that from the outside, on the artefact.

2 — Parse defensively, because parsing is the harness's job. Asked for one line, a small model returns the whole function and a larger one returns a fenced block. Both are reasonable readings. Take what you need from what arrives rather than requiring the model to do you a favour.

3 — Write the verifier as an independent check. Independent means it does not ask the model whether the answer is good. It examines the artefact: does the line use a placeholder, does the test pass, does the exploit stop working.

4 — Test the verifier against a correct answer you did not expect. This is the step that gets skipped. A verifier that accepts only one spelling of correct rejects real work and burns the budget doing it — check %s, :name and $1 before shipping a check for ?.

5 — Test it against a plausible wrong answer. Something that reads like a fix and is not — an escape function wrapped around concatenation. If the verifier accepts it, the loop ships it, reports success, and the trace looks clean.

Example

Input — the fixture committed at the top of scripts/agentic_harness_loop.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:

the loop, wired: ['plan', 'act', 'verify', 'stop']
backend  : replay
steps    : 1
accepted : return DB.execute("SELECT * FROM bookings WHERE ref=?", (ref,))
verified : None   <- nothing checked it

The loop stopped because the model produced something, which is not the
same as producing something correct. Whatever came back was accepted.

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

{
  "loop": {"moves": ["plan", "act", "verify", "stop"], "max_steps": 0, "exit_owned_by": "harness|model"},
  "parse": {"expected": "str", "shapes_seen": ["str"], "strategy": "str"},
  "verifier": {"independent": true, "accepts": ["str"], "rejects": ["str"]},
  "runs": [{"verifier": "none|present", "steps": 0, "accepted": "str", "verified": null}]
}

Report the unverified run too. The pair is the lesson: the same model and the same prompt, with and without something checking the answer.

Failure modes

  • Offering the model a DONE exit. It will take it on the first turn.
  • A verifier that accepts one spelling. It rejects correct work, repeatedly, and the budget goes on retries.
  • Asking a model to judge the answer. That is a second opinion, not a verification.

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

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-1-the-loop:

agentinstall just for this agentstart it
Claude Codepython3 scripts/install_skills.py --tool claude --lessons A1.1type /a1-1-the-loop
GitHub Copilot (VS Code)python3 scripts/install_skills.py --tool copilot --lessons A1.1type /a1-1-the-loop in Chat
Cursorpython3 scripts/install_skills.py --tool cursor --lessons A1.1type / in Agent chat and search for it
Codexpython3 scripts/install_skills.py --tool agents --lessons A1.1type $a1-1-the-loop, 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.1

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

The loop's three stages named, the exit condition stated explicitly, and the verifier identified as independent of the model or flagged as not being so.

✎Your turn

Delete the verifier — in cybertravels/runtime.py, replace the body of _verify_result with a single return None, which is the two-stage loop most systems actually ship. Then run the snippet above again: the 1400-for-140 refund now comes back None, meaning acceptable, and the run reports success.

That is the failure mode, and the point is that it does not look like one. Nothing errored. No span says anything is wrong. The trace is shorter and tidier than before, because there is no denied at="verifier" row in it. A reviewer reading that trace sees a clean run.

Then put it back — or keep going, and remember that scripts/lesson.py for A1.2 will tell you the tree has changed and offer you --force. That is the harness protecting your edit, not an error.

Tools used

Anthropic SDKMCP

Skill source: skills/appsec/agentic-harness-loop

← PreviousA1.0 · What an agent is, and what you are about to build