Most agent demos are about forty lines of code. Call the model, check whether it asked for a tool, run the tool, append the result, call the model again. It works on the first try and it is impressive. Then someone asks what happens when the crew-scheduling system times out for the fourth time, who approved the work order, whether a resident's phone number ended up in the logs, and how an operator stops the run at 02:00 without leaving half-written records behind. The forty lines have no answer, because none of those responsibilities were ever written down.
An agent is not the model plus a list of tools. The production system is the loop around the model: what decides when the model is called, which tool may run, what can change, when a person must approve, how work is cancelled or resumed, why execution stops and what evidence survives. If you do not own that loop, or explicitly govern the one your framework provides, you do not control the agent. You are watching it.
Part 1 covered which model should run each turn. This part covers the loop that model runs inside.
The model is the driver. The harness is the vehicle.
A clever driver without brakes, a fuel gauge, a tracker or road rules is not a transport system. The model is a capable driver. The harness is the vehicle, the brakes, the dashboard, the logbook and the control room.
CloudNala has written about harness engineering for coding agents, where the harness is the workshop of guides and sensors around an agent that changes software: see harness engineering has grown up and the workshop around the agent. This article is narrower and closer to runtime. It is about the loop that runs a business agent in production, one request at a time, and the controls that must exist in that loop before real traffic arrives. Swap the model and every one of these responsibilities remains.
A running example: a burst pipe at 02:00
Gamtoos Valley Municipality is a fictional Eastern Cape municipality. Its maintenance agent receives a report: a water main has burst on Kraal Street and the road is flooding. The agent may:
- classify the report;
- look up the asset (a 160mm main, asset WM-0411);
- check crew availability;
- draft a work order.
It may not:
- submit the work order without a supervisor's approval;
- approve procurement;
- read a resident's profile;
- send bulk SMS messages;
- keep retrying a failed integration indefinitely.
Everything in this example, including the asset, the crew and the systems, is fictional. We built it as a small public-safe lab (open source, MIT licence) so that every control below can be tested deterministically, including deliberate misbehaviour.
Write the budgets down
Start by listing every limit the forty-line loop does not have, and make each one explicit:
- Turns: how many model calls a run may make (eight in the lab).
- Tool calls: how many tools a run may execute (six).
- Cost: a ceiling on spend for one run (five illustrative credits).
- Retries per tool: how many times a failed tool may be retried (two in the lab; a third failure stops the run). The right number depends on the integration: a payment call may get none, a flaky read may get more.
- Wall-clock time: how long a run may take before it is stopped (30 seconds in the lab; minutes or hours for real long-running work).
A budget is not an optimisation. It is the difference between an agent that fails and an agent that fails expensively. CloudNala's article on preventing expensive and destructive tool calls covers the cloud-operations version of the same argument. In the lab, a policy that never finishes, calling the classifier over and over, is stopped by the tool-call budget. A policy that keeps retrying a crew system that is down is stopped after the third failure, not the thirtieth.
Give every run a typed stop reason
When a run ends, the system that called it needs to know what to do next. "Something went wrong" forces a person to read logs. A typed stop reason lets the caller route the outcome without guessing.
The distinctions matter. awaiting_approval is resumable. escalated_to_person is a handover. policy_denied means the agent asked for something outside its authority, which is worth reviewing even though nothing happened. budget_exhausted means the trace needs reading before anyone simply retries. The loop in The agent loop in plain English makes the same point at the level of concepts: an agent is only governable if every way it can end is designed.
Put policy in hooks, not in the prompt
A hook is code that runs at a defined point in the loop: before or after a model call, before or after a tool call, before or after a state change. Hooks are where the organisation's policy lives, because they run whatever the model decides. A hook can:
- deny a tool call outright;
- replace one tool with a safer one, such as a sandboxed or read-only variant;
- rewrite arguments, for example to clamp a quantity;
- redact a result before it reaches the model or the trace;
- retry a failed call within the retry budget;
- interrupt the run for approval.
In the lab, the before-tool hook denies approveProcurement, getResidentProfile and sendBulkSms, and interrupts before submitWorkOrder. The after-tool hook strips a resident phone number that the asset system includes in its response, so the number never reaches the model or the trace. The model was never told any of this. It cannot talk its way past a hook, because the hook does not read the prompt. Tool definitions still shape what the model attempts, as tool descriptions are part of agent behaviour explains, but enforcement belongs in code.
Cancellation and interruption are different
These two words are often used loosely. They need to mean different things.
- Cancellation ends the work safely. An operator or a calling system signals that the run must stop. The loop checks for the signal before every model call and every tool call, and it stops at the next check without starting new actions. In the lab, a run cancelled at the start makes zero tool calls.
- Interruption pauses the work for a decision and allows controlled resumption. The loop stops before a risky action and returns a checkpoint with the pending action. A supervisor approves or declines. On approval, the run resumes from the checkpoint, executes the action and records who approved it. A resumed action passes through the same hooks and budgets as any other. On decline, the case goes to a person.
Mixing the two causes real damage. If an interruption is implemented as a cancellation, the approved work has to start from scratch and may repeat side effects. If a cancellation is implemented as an interruption, an operator who meant "stop" finds the run waiting to continue. Human-in-the-loop is not a button covers what makes the approval itself meaningful.
Checkpoint only where you need to resume
Persisting the state of every run is not free. A checkpoint holds tool results, sometimes personal information, and the pending action. It needs an owner, an identity binding, a retention period and protection against two people resuming the same run. Persist checkpoints where resumability is genuinely required, typically at approval interrupts and long-running steps, and nowhere else. An agent without durable state cannot own a long-running process explains when durable state is essential. The other half of that judgement is not keeping state you do not need.
Trace what a reviewer will ask about
A trace is the record that lets someone reconstruct a run after the fact. For the burst-pipe agent it includes the request, each model decision, each tool with its arguments and result, each hook decision (allowed, denied, redacted, interrupted), each approval with the approver's identity, every state change and the final stop reason. In the lab, the resumed run's trace shows the supervisor who approved the work order alongside the tool call it unlocked.
Be clear about what a trace is not. It records what happened. It does not judge whether that was good, which is why a trace is not an evaluation. And it must respect the same data rules as the systems it records, which is what the redaction hook is for.
How this maps to a framework
Keep the requirements framework-neutral, then check how your framework expresses them. As a dated example, the Strands Agents documentation (checked on 5 November 2026; Python package 1.64.0 released 4 November 2026) describes:
- hook events including
BeforeToolCallEvent, which can replace the selected tool, cancel the call or raise an interrupt; AfterToolCallEvent, which can change the result or request a retry;AfterModelCallEvent, which can retry a model call;- interrupts that end a run with a stop reason of
interruptand resume with the person's response; agent.cancel(), which cancels the current invocation cooperatively at the next check and ends it with acancelledstop reason (TypeScript API reference), with an option in the TypeScript SDK to let the running batch of tools finish first.
We found it through the AI Engineering newsletter's walkthrough, Build an agent harness you control end to end (2 October 2026). Other frameworks name these things differently, and some leave parts to you. Whatever you use, pin the version, read how its loop actually stops, and write down which controls the framework provides and which you must build. Our lab includes no framework code, so the requirements stay readable.
Test the loop before production traffic does
The lab's 14 tests cover the harness, the workflow from Not Every Workflow Needs an Agent and the router from Part 1. For the harness, they check that:
- a risky action interrupts and resumes from the checkpoint with the approver recorded;
- a declined approval returns the case to a person;
- cancellation makes no tool calls;
- repeated tool failure stops the loop;
- a forbidden tool is denied by policy;
- an endless agent is stopped by its budget;
- resident data never reaches the trace.
Every stop reason has at least one test that produces it. Before real traffic, add failure injection against your real integrations, security tests for the hooks, and recovery tests that resume from a checkpoint after a restart.
Key points
- An agent is the loop around the model. Owning or explicitly governing that loop is what makes agent behaviour operable.
- Write the budgets down: turns, tool calls, cost, retries per tool and time.
- End every run with a typed stop reason the calling system can act on.
- Put policy in hooks that deny, replace, rewrite, redact, retry or interrupt, so enforcement does not depend on the model.
- Cancellation ends work. Interruption pauses it for a decision and resumes from a checkpoint. Persist checkpoints only where resumability is needed.
- Trace every decision, approval and stop reason, and keep personal data out of the trace.
Previous: Part 1, Stop sending every agent task to the most expensive model. All parts are on the series page. For the authority model behind these controls, see Agent capability is not agent authority.
How CloudNala can help
CloudNala reviews agent runtimes before they take production traffic. We take your existing loop, whether hand-built or framework-based, and write down every responsibility it currently hides: budgets, stop reasons, hooks, approval interrupts, cancellation, checkpoints and trace contents. Then we test each one with failure cases. You leave with an agent-loop readiness checklist completed against your system, the gaps ranked by risk, and tests that prove each control works.
Work with CloudNala
CloudNala helps organisations move from technology ambition to practical execution across cloud, AI, data, platform engineering and digital services.
Whether you are exploring AI, modernising your cloud environment, building a public-sector digital service, or turning an idea into a working MVP, we can help you shape the roadmap and deliver the next step.
Book an Agent Engineering Review or write to us at consult@cloudnala.co.za
