Raza Rizwan

Loop and harness: stops, permissions and checks around the agent

The basic outer harness for the agent, from The AI Agent Factory: what a loop is made of, the three stops every loop needs, the five verbs of a harness, allow-ask-deny permissions sorted by blast radius, hooks, typed output and a progress file.

Status
stable
Trust
human-reviewedHuman: Raza Rizwan ·
7 min read
Owner
Human: Raza Rizwan
Approved
Human: Raza Rizwan ·

The agent from the previous page answers one request. A worker runs on its own, beat after beat, with nobody watching. Two of the book's courses cover what that takes: loop engineering says what starts a run and what stops it; harness engineering says what the agent may do inside a run and how its work is proven. This page takes the basic parts of both.

What a loop is made of

A loop that really runs on its own has five working parts and one saved-memory layer, the spine: 1

  1. Heartbeat: a schedule or an event that starts the loop. Each single firing is a beat.
  2. Worktree: isolation, so two agents working at once do not overwrite each other's files.
  3. Skill: project knowledge written down once, so no run starts from nothing.
  4. Subagents: the maker-checker split. The agent that writes is not the agent that grades.
  5. Connector: so the loop can act in real tools, not only suggest.
  6. State, the spine: a file on disk that records what is done and what comes next. The model forgets everything between runs. No spine, no loop.

The heartbeat menu does not depend on the domain: "do this now, until it is done" is a conditional loop; "do this every night" is a schedule; "react when something arrives" is an event. 1 For the basic worker here, the heartbeat is the HTTP request that reaches it, and the FastAPI page gives it that door.

Run until done: three stops

A fixed-timer loop does not know when the work is complete; a conditional loop stops because the work is complete, and a separate command or checker decides. The agent that performed the work should not approve its own result. 1

Every loop needs three stops, and each prevents a specific failure: 1

StopWhat it isLeave it out, and
Success conditionHow the loop knows the task is doneNothing defines done, so the loop cannot stop on purpose or be graded
LimitA ceiling: max tries, max minutes, or max spendA goal it cannot reach uses up the whole token budget
No-progress checkCatch the agent repeating the same action with the same argumentsIt spends the whole limit repeating one mistake

Write conditions a command can prove: "tests pass and lint is clean", not "the code is good". This is where the spec pays off, because its acceptance criteria are already conditions a command can prove. 1 On the SDK side, max_turns from the previous page is the per-run limit, and the book's rule is to set both the ceiling on beats and the ceiling on turns inside a beat. 1

Long runs get worse over time: old tool output and dead ends pile up in the context, and the model's decisions degrade. Compact long runs into a short summary, move big outputs to files and keep a pointer, and hand messy subtasks to a subagent. Treat context as a budget, not a bucket. 1

The spine: a progress file

Keep the state outside the model, on disk, in two layers: a short rules file the loop reads on every run, and a progress file that records what was tried, what passed and what is still open. Every run reads the progress file at the start and updates it at the end. When the loop keeps making the same mistake, the fix is not a cleverer prompt; it is to write the lesson into the rules file. 1

<!-- progress.md, the loop's memory between runs -->

## Done

- 2026-06-22: fixed flaky test in test/auth (retry on token refresh)

## In progress

- Dependency audit: 3 of 7 advisories patched; lodash bump blocked by an API change

## Open / needs a human

- CVE-2026-xxxx in image lib: the fix changes the output format, escalating to a maintainer

Because it is plain text in the repo, the progress file is also the record of what the loop did while you were away: at the human gate you read the spine, not every transcript. 1

The five verbs of a harness

Every harness surface does one of five jobs: 2

  1. Constrain: limit what the agent can do. Permission rules, deny lists, sandboxes.
  2. Inform: give the agent what it needs. The rules file, skills, connectors, tool design.
  3. Verify: prove the work before it counts. Hooks, tests, linters, typed output.
  4. Correct: recover the run, then change the harness so the mistake never repeats.
  5. Escalate: when the harness cannot decide, send it to a person, visibly.

One rule binds all five, the most important sentence in that course: a guardrail lives in the harness, never in the prompt. "Please do not touch the .env file" is a request the model can ignore, misread or lose; a deny rule on that file is enforced by the tool layer. Whenever you catch yourself writing "please never" into a prompt, move that sentence into the layer that words cannot change. 2

SurfaceGuides behaviourMechanically enforced
Prompt or rules fileYesNo
Tool descriptionYesNo
Permission deny ruleYesYes, at the tool layer
Sandbox or network fenceYesYes, at the OS layer
Hook after an actionYesOnly forward: it cannot undo what ran
Required CI check + branch protectionYesYes, at merge

Constrain: allow, ask, deny

Every mature harness expresses constraint as a list of rules, each matching a kind of action, each with one of three answers. Allow is a green light, ask is a doorbell, deny is a wall. Sort by blast radius, not by frequency: reading a normal source file, allow; reading secrets or anything outside the project, deny; running the test suite, allow; pushing to a branch, ask, or allow only for named branches; deleting files outside the worktree, touching secrets, force-pushing, changing the harness's own config, deny. When in doubt, start one bucket stricter than feels convenient. 2

Money belongs in this bucket too: a per-run spend cap, a step cap and a rule about which model each job may use are permission rules that guard the budget instead of the files. 2

The book's example, for a coding agent's settings file: 2

{
  "$schema": "https://json.schemastore.org/claude-code-settings.json",
  "permissions": {
    "allow": [
      "Read",
      "Bash(npm test *)",
      "Bash(git diff *)",
      "Bash(git push origin claude/*)"
    ],
    "ask": ["WebFetch"],
    "deny": [
      "Read(./.env)",
      "Read(./secrets/**)",
      "Bash(rm -rf *)",
      "Bash(git push --force *)"
    ]
  }
}

Deny beats ask, and ask beats allow. Deny patterns match command text, not meaning, so treat them as tripwires and let a sandbox be the wall. 2 In the agent you are building, the same three answers are the SDK's own primitives: allow is a plain tool, ask is needs_approval, deny is a tool you never give the agent at all.

Verify: hooks and typed output

A hook is code the harness runs automatically at fixed moments, no matter what the model wants: after every edit, run the linter; before the session ends, run the tests and refuse to end if they fail. A hook that stands before an action can block it; a hook that runs after cannot undo it, but pushes the failure straight into the agent's next turn so the mistake gets fixed instead of buried. "Done" stops being a claim the model makes and becomes a state the harness has proven. 2

The universal layer is a git hook or CI: a pre-commit that runs the linter and tests binds any agent and any human, and a required CI check plus branch protection is the last line, because work that fails it cannot merge. 2

# .git/hooks/pre-commit, the tool-agnostic verify gate
#!/bin/sh
npm run lint --silent && npm test --silent || {
  echo "pre-commit: lint or tests failed, commit blocked"; exit 1;
}

Verification breaks quietly when the thing being checked is free text. A checker whose verdict cannot be parsed is a checker you do not have. Demand a fixed, machine-checkable shape and validate every field against its allowed values before any later step trusts it; a malformed verdict is not retried forever and not guessed at, it escalates to a person. 2

Reply with ONLY a JSON object, no other text. A passing review
looks exactly like this:
{
"verdict": "PASS",
"reasons": [],
"risk": "low"
}
Allowed values: verdict is PASS or FAIL; risk is low or high; reasons
holds one short string per reason, and is empty only on a clean PASS.

A well-formed lie still fails the contract: {"verdict": "MAYBE"} is flawless JSON and must be rejected, because presence is not permission. 2 In the SDK, output_type with a Pydantic model is this same idea built in; the next page uses it to grade the agent.

Your basic harness, in one list

For the worker this room builds, the harness is small and written down:

  1. A rules file (the constitution from write the spec) with the gates as rules.
  2. max_turns on every run, plus a spend cap on the API key.
  3. Tools sorted allow / ask / deny: plain tool, needs_approval, or not given.
  4. A progress file the run reads first and updates last.
  5. Tests and a pre-commit hook that refuse a broken commit.
  6. Typed output wherever a verdict is parsed.

Continue to evals before shipping.

Footnotes

  1. Loop Engineering: A Crash Course, Concepts 2, 5 and 12. The AI Agent Factory, Panaversity, 2026. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9

  2. Harness Engineering: A Crash Course, Concepts 3, 4, 8 and 9. The AI Agent Factory, Panaversity, 2026. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10

On this page