# Loop and harness: stops, permissions and checks around the agent (/docs/agent/loop-and-harness)

---
type: Procedure
title: "Loop and harness: stops, permissions and checks around the agent"
description: "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
order: 11
generated: { by: "process:claude-code", at: 2026-09-03T08:15:00Z }
verified:
  - { by: "human:rrizwan98", at: 2026-09-03T08:20:00Z }
sources:
  - id: af-21
    title: "Loop Engineering: A Crash Course. The AI Agent Factory, Panaversity, 2026"
    resource: https://agentfactory.panaversity.org/docs/loop-engineering-crash-course
  - id: af-22
    title: "Harness Engineering: A Crash Course. The AI Agent Factory, Panaversity, 2026"
    resource: https://agentfactory.panaversity.org/docs/harness-engineering-crash-course
ksor:
  audience: [public]
  owner: human:rrizwan98
  approval: { by: "human:rrizwan98", at: 2026-09-03T08:20:00Z }
trust_tier: human-reviewed
build_id: sha256:4d4bff09bd0a1dcff0a0a50f20f7ef514fc471e2b1f9e81e0e7b6150b93c0793
dirty: true
---

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: [^af-21]

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. [^af-21] 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. [^af-21]

Every loop needs three stops, and each prevents a specific failure: [^af-21]

| Stop | What it is | Leave it out, and |
| --- | --- | --- |
| Success condition | How the loop knows the task is done | Nothing defines done, so the loop cannot stop on purpose or be graded |
| Limit | A ceiling: max tries, max minutes, or max spend | A goal it cannot reach uses up the whole token budget |
| No-progress check | Catch the agent repeating the same action with the same arguments | It 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. [^af-21] 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. [^af-21]

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. [^af-21]

## 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. [^af-21]

```markdown
<!-- 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. [^af-21]

## The five verbs of a harness

Every harness surface does one of five jobs: [^af-22]

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. [^af-22]

| Surface | Guides behaviour | Mechanically enforced |
| --- | --- | --- |
| Prompt or rules file | Yes | No |
| Tool description | Yes | No |
| Permission deny rule | Yes | Yes, at the tool layer |
| Sandbox or network fence | Yes | Yes, at the OS layer |
| Hook after an action | Yes | Only forward: it cannot undo what ran |
| Required CI check + branch protection | Yes | Yes, 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. [^af-22]

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. [^af-22]

The book's example, for a coding agent's settings file: [^af-22]

```json
{
  "$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. [^af-22]
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. [^af-22]

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. [^af-22]

```sh
# .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. [^af-22]

```text
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.
[^af-22] 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](write-the-spec.md))
   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](evals-before-shipping.md).

[^af-21]: Loop Engineering: A Crash Course, Concepts 2, 5 and 12. The AI Agent Factory, Panaversity, 2026.
[^af-22]: Harness Engineering: A Crash Course, Concepts 3, 4, 8 and 9. The AI Agent Factory, Panaversity, 2026.
