Part IV · Applied concepts · Chapter 33
Harness engineering
The harness is everything around a model that turns it into a working system: the loop that calls it, the context it is given, the tools and permissions it has, the checks on what it produces, and the records of what it did. Two products built on the same model can differ enormously in quality, and the difference is almost all harness. This chapter is about engineering that layer on purpose.
A model upgrade is something you receive; the harness is something you build. Most failures in a deployed AI system (a loop that never stops, a check that never ran, a tool that did too much) are harness failures, and they are fixed with ordinary engineering.
The parts of a harness
| Part | Question it answers | Chapter |
|---|---|---|
| Control loop | When is the model called, and when does it stop? | 29 |
| Context assembly | What goes into each call, and what is left out? | 22 |
| Tools and permissions | What can the model look at and change, and who approves? | 28 |
| Output contracts | What shape must the answer have, and is it checked? | 28 |
| Verification | How do we know the output is right before anyone relies on it? | this chapter |
| Budgets | How much time and money may one task spend? | 42 |
| Observability | What happened, step by step, and what did it cost? | 45 |
| Evaluation | Is this version of the whole system better than the last? | 44 |
One gateway for every model call
The most useful structural rule is that every model call goes through one function. That function is where you scrub personal data, attach the output schema, cap the tool loop, record the cost and send the trace. If calls can bypass it, every protection becomes "usually". Enforce the rule with a check that fails the build, not with a comment asking people to remember: a short script that reads the code with Python's ast module and fails if the provider's SDK is called anywhere outside the gateway is enough.
Two more habits belong in the gateway. Pass user text to the model as data parts, never by pasting it into a prompt template, so a customer who types template syntax cannot change the prompt. And scrub personal data from every message at this point, even if an earlier step already did: the gateway is the one place that cannot be skipped (chapter 34).
Verification: trust the check, not the claim
A model's statement that it finished, or that its answer is right, is not evidence. The harness should verify with something that does not depend on the model's opinion:
- Deterministic checks first. Schemas, tests, linters, reference checks, arithmetic redone in code. They are cheap, exact and repeatable.
- Model-based checks second, for what code cannot judge (tone, suitability), with a rubric and known agreement with people.
- Fail closed. If a check cannot run (the checker errored, the service is down), treat the output as failed, not passed.
- Prove a check can fail. A new check should be shown to catch a deliberately broken input before you trust its "all clear". A check that silently ran on nothing prints nothing, and nothing looks exactly like a pass.
- Guard the checks themselves. An agent told to make the tests pass can delete the failing test. Changes to tests, thresholds and checkers deserve a separate review.
Retries and the maths of verification
When a reliable check exists, a retry turns a modest success rate into a high one. Worked: if one attempt succeeds with probability 0.8 and the check catches every failure, the chance that at least one of three attempts passes is 1 − 0.23 = 0.992. Without the check you cannot tell which attempt to keep, and the chance that all three are right is only 0.83 = 0.512.
Two cautions. Attempts are not independent: a model that misunderstood the task usually misunderstands it again, so feed the reason for the failure into the retry, or move to a stronger model. And every retry costs time and money, so cap them and decide in advance what happens when the cap is reached: a safe fallback, not one more try.
Permissions and side effects
- Separate reading from writing. Most steps need only reads.
- Route irreversible actions (payments, messages, deletions, deployments) through a person or a rule outside the model.
- Run code the model writes in a sandbox with no secrets and limited network.
- Prefer dry runs that show what would happen; make the real run a separate, explicit step.
Guards inside the tool loop
A tool loop is where a harness most often runs away, so give it a few mechanical guards:
- Cap the rounds. Allow a fixed number of tool rounds (8 is a sensible default for simple tasks; a research-style task may justify more). A model that uses them all without answering gets one more call with no tools, so it must answer from the exchange so far.
- Return bad calls as errors. If a tool call's arguments fail the tool's schema, send the model an error result it can correct, instead of crashing the run.
- Notice repeats. If the model makes the same call with the same arguments twice in one run, hand back the earlier result with a note to answer now, instead of running the tool again.
Two further rules apply to what the loop produces. Run the deterministic checks first and the model-based check second, and if the model-based check errors, fail closed to a fixed safe reply. Retry a blocked answer at most once, with the reasons for the block, perhaps on a stronger model, and then fall back to a safe reply. If failures cluster, a circuit breaker (chapter 42) stops retries piling up.
In Lab 5 you build the tool loop itself, and in Lab 9 you record every call with its tokens and its cost, so you can see what a loop did.
State that survives the context
A long task outlives any one context window. Keep the plan, progress and decisions in files or tables the harness owns, so a run can resume after a crash, a person can see where it stands, and a fresh context can pick up the work. Write down what was tried and failed, not only what worked; otherwise the next attempt repeats the same mistakes.
Budgets and records
- Budgets that refuse. A spend limit should refuse to start the path it guards when no limit is set, and a path that calls a provider directly should book the worst-case cost before each call (chapter 42).
- Tag every answer. Record the prompt version, the model ids, the code version and the data source with each answer, and send a trace with personal data scrubbed (chapter 45).
Common harness failures
| Failure | What it looks like | Fix |
|---|---|---|
| Silent pass | A check reports success because it never ran | Make every check print what it examined; test it on a broken input |
| Runaway loop | Cost climbs while the agent repeats a step | Turn, tool and money caps with clear messages |
| Context rot | Quality falls as the conversation grows | Compact or restart with a written summary (chapter 22) |
| Gamed check | Tests pass because they were weakened | Review changes to checks separately |
| Hidden gap | A known shortcut lives only in someone's memory and is forgotten | Keep a written list of known gaps and scan the code for markers that must match it |
| Unrecorded change | Nobody can say which prompt version produced an answer | Version prompts and tag every answer with them |
A step succeeds 70% of the time per attempt. You allow up to two attempts and the check catches 90% of failures (a failure it misses is passed through). What fraction of outputs that reach the user are failures?
Answer
First attempt: 0.7 good; 0.3 × 0.1 = 0.03 bad and missed; 0.3 × 0.9 = 0.27 caught and retried. Second attempt: 0.27 × 0.7 = 0.189 good, 0.27 × 0.3 × 0.1 = 0.0081 bad and missed, and 0.27 × 0.3 × 0.9 = 0.0729 caught again, which go to the fallback. Outputs reaching the user: 0.7 + 0.03 + 0.189 + 0.0081 = 0.9271, of which 0.0381 are failures: about 4.1%. The check's misses, not the model, now set the error rate.
Self-check
Why route every model call through one function?
So scrubbing, limits, cost records, tracing and validation apply to every call without exception, and one check can enforce it.
What does "fail closed" mean for a check?
If the check cannot run, the output is treated as failed, not as passed.
One attempt succeeds 80% of the time and the check is perfect. What do three attempts give?
1 − 0.23 = 0.992, if the attempts fail independently.
How do you know a new check works?
Run it on an input you broke on purpose and see it fail, then on a correct input and see it pass.
Further reading
- Building effective AI agents article beginner
Baseline patterns for agent systems and a case for keeping them simple. - Effective context engineering for AI agents article intermediate
Treats context as a scarce budget and covers compaction, note-taking and sub-agents. - Writing effective tools for AI agents article intermediate
How tool design and descriptions shape agent behaviour, and how to evaluate tools. - Effective harnesses for long-running agents article advanced
Techniques for agents that work across many context windows, such as progress files and incremental steps.
Free, checked on 2026-10-21. All resources for this chapter and 4 more.