What Is an Agent Harness? How to Build One for Production
An agent harness is the runtime around an AI model. It holds the loop, tools, context, memory, permissions and checks. This guide explains each part, shows tested code, and lists what to ask a vendor.
By Muhammad Usman AliEditor responsible: Muhammad Usman Siddiqui
Guide · Intermediate · 44 min

On this page · 18 sections
- 01What an agent harness is
- 02Why the harness matters as much as the model
- 03The Six-Part Anatomy
- 04A minimal harness you can run
- 05Add permissions and hard limits
- 06Failure modes to design for
- 07Ready-made harnesses
- 08MCP and the 2026-07-28 spec
- 09Build, adopt or buy
- 10Cost control
- 11Where TypeSafe Jev fits
- 12What the evidence shows
- 13Evaluating agent runs
- 14When you do not need a custom harness
- 15Common mistakes
- 16Production checklist
- 17What to ask a vendor
- 18Glossary
Key takeaways
- An agent harness is the code around the model. A common formulation is agent = model + harness. It runs the loop, executes tools, manages context and memory, enforces permissions and records what happened.
- We group the harness into six parts, the Six-Part Anatomy. They are the loop controller, tool registry, context manager, memory store, permission gate, and trace and eval hooks (a record of every step, and a test set that scores runs). The grouping is ours, not a standard.
- Agents fail in repeating ways. Nine are covered here, each with a symptom, a guard and a test. Limits and permissions belong in code, not in the prompt.
- Most teams should adopt a ready-made harness such as the Claude Agent SDK or LangGraph first, and build their own only when they need control those tools do not give.
- When you evaluate a vendor, ask what stops the loop, what the agent may touch, how you replay a bad run, and what one task costs.
What we tested
For this guide we wrote three small Python programs and ran them offline with a stub model (a fake model that replays fixed steps), using Python 3.13.3 on macOS on 2026-10-27. Two are small harnesses. The third validates a JSON permission policy and applies it to sample tool calls. Their real output is shown below. A fourth sample uses the Claude Agent SDK. We did not run it, because it needs an API key. We only parsed it for syntax. We drew four diagrams by hand as SVG and rendered each one to check it. We opened and read the sources listed at the end on 2026-10-27, including the Agent SDK pages, three 2026 Anthropic Engineering posts (harness design, 2026-03-24; evals, 2026-01-09; the C compiler project, 2026-02-05), the LangChain harness post and v1 docs, the LangGraph, OpenAI Agents SDK and Managed Agents docs, the OWASP page and the MCP 2026-07-28 changelog. The OWASP entry cited is LLM01 from the 2026 edition of the Top 10 for LLM Applications, read in the OWASP GenAI-LLM-Top10 repository. We did not run LangGraph, Claude Code, the OpenAI Agents SDK, Managed Agents, or TypeSafe Jev. The one EdgeFirm project mentioned is described only as its published case study states it. We did not interview the team for this guide.
Most agent demos look the same. A model gets a task, calls a few tools and returns an answer. The demo works because someone watched it. A production agent has to work when nobody is watching, and that depends on the code around the model more than on the model itself. That code is called the agent harness, and this guide explains it for two readers: business leaders who need to judge it, and engineers who need to build or choose one.
This is our engineers' report. On 2026-10-27 we read the sources listed at the end, ran three small programs offline and did the arithmetic shown. You will find a named model for the parts (we call it the Six-Part Anatomy), the code and its real output, a failure table with a guard and a test for each, a comparison of ready-made options taken from their own documentation, and a production checklist. Where a sentence is our judgment and not a finding, we label it as ours.
What an agent harness is
An agent is a program that uses a language model to plan its own steps and call tools until a task is done. The model alone cannot do that. It only reads text and writes text. Something else has to run the tools, feed the results back, and decide when to stop.
That something is the agent harness. When we read Anthropic's Claude Code docs, they described Claude Code as the layer around the model that provides the tools and manages the context the model sees, and says this layer is what the term agentic harness refers to. The same page says the loop is powered by two parts: models that reason and tools that act.
When we compared definitions, LangChain's was the most compact: a formula, agent = model + harness. Their definition is that a harness is every piece of code, configuration and execution logic that is not the model itself, and they say the model holds the intelligence while the harness makes that intelligence useful. The Anthropic evals guide we read gives a matching definition: the harness, or scaffold, is the system that enables a model to act as an agent. It processes inputs, orchestrates tool calls and returns results.
We found no single official definition, and the word is young. In this guide we use the one the sources share: a harness is everything in an agent that is not the model weights.
For business readers, think of the model as a smart new hire and the harness as the job design. It covers what tools the person gets, what they may approve alone, what they write down, and when a manager steps in.
Why the harness matters as much as the model
A model without a harness answers questions. A model with a harness does work. The gap between the two is mostly code you control.
Our read is that the harness often decides whether an agent is useful, safe and affordable. It rests on the sources below, not on a benchmark of our own, so treat it as engineering judgment. Three findings point the same way.
- In Anthropic's harness design post (2026-03-24), a strong model still failed on long tasks without structure. Models lost coherence as the context filled, some began wrapping up early, and agents praised their own mediocre work. The fixes were in the harness: a planner, a separate evaluator that tests the running app, files for handoff between agents and git.
- The Agent SDK docs we read say that without limits the loop runs until the model finishes by itself. They call a budget a good default for production agents.
- When we listed where spend, safety and context are controlled, all three sat in harness settings: turn caps, budget caps, permission modes and compaction (shrinking old conversation so it fits the model's window).
A better model raises the ceiling. A better harness decides how often you reach it.
Where each layer lives
It helps to separate three layers before looking inside the harness. The model is the weights, run by a model provider. The harness is the runtime around it. The application is your product: the screens, the business rules, the data and the definition of done.
Three stacked layers: the application on top, the harness in the middle with six parts, the model at the bottom, with arrows for task and limits going down and results and cost coming up.
The split matters when something goes wrong. A wrong answer may be the model. A loop that never ends, a bill nobody saw, or a tool the agent should never have reached is the harness. A task that was never defined well is the application.
The Six-Part Anatomy
Across the harnesses we read, from Anthropic's SDK to LangChain's middleware, we found the same six jobs. The grouping into six parts is our own, not a standard. We call it the Six-Part Anatomy and keep the same numbers and names through the rest of this guide.
The Six-Part Anatomy: a loop controller bar above a row of context manager, model, permission gate and tool registry, with memory store and trace and eval hooks below, the user on the left and tools and data on the right.
Read the diagram left to right. A task comes in from the user or an app. The context manager builds the prompt. The model picks a next step. If the step is a tool request, the permission gate decides whether it may run. The tool registry runs it and the result goes back into the history. The loop controller starts every turn and decides when to stop. The memory store and the trace hooks sit underneath and serve every step.
1. The loop controller
The loop controller calls the model, and if the model asks for a tool, runs it and calls the model again. If the model answers, it stops. The Agent SDK docs call each full cycle a turn and say the loop ends when the model replies with no tool calls.
This part owns the limits. A turn cap and a spend cap live here. So does the stop rule, which is the answer to a question every business owner should ask: who decides that the agent is done? If the answer is only "the model", you have an agent that grades its own work.
2. The tool registry
Tools are functions the model may request, such as read a file, search a database or send an email. Each one has a name, a description and typed inputs. The registry holds them, runs them, applies a timeout and trims the output.
Tools cost context. The Agent SDK docs say every tool definition takes space, and that the core tools load on every request while tools from MCP servers are loaded on demand by default. So load only what the task needs. Fewer, clearer tools also give the model fewer ways to pick the wrong one.
3. The context manager
The context window is everything the model can see in one call. It does not reset between turns. The system prompt, tool definitions, history, tool inputs and tool outputs all accumulate there, and large tool output can use thousands of tokens in a single turn.
The context manager decides what goes in and what gets cut. When the window nears its limit, the Agent SDK compacts older history into a summary, and the docs warn that early instructions can be lost. Persistent rules belong in a file that is re-read on every request. LangChain's write-up names a related technique: keep the head and tail of a very large tool output, and move the full text to a file the model can open if it needs it.
Anthropic's harness post adds a warning that we would repeat. With one earlier model, compaction on its own was not enough for long tasks, so they used context resets: clear the window and give a fresh agent a structured note of state and next steps. With a newer model they dropped the resets. We would re-check this choice whenever the model changes. That is why the memory store exists.
4. The memory store
Memory is what survives between sessions. In Claude Code, each session starts with a fresh context window. Persistent knowledge lives in CLAUDE.md files and in auto memory, and the first 200 lines or 25KB of the MEMORY.md file, whichever comes first, load at the start of each session. Anthropic's long-running agent work passed state between agents through files and git for the same purpose, so that a fresh agent could see what the last one did.
Memory is also a risk. A note that was true last month can be wrong today. A good memory store records when a note was written and where it came from, lets you delete it, and makes the agent check before it acts on an old note.
5. The permission gate
The gate decides which tool calls run on their own, which need a human, and which are blocked. The Agent SDK has allow lists, deny lists and permission modes. A deny list blocks a tool regardless of other settings, and a hook can reject a call before it runs. When a call is denied, the model receives a rejection message as the tool result and usually tries another route or reports that it could not proceed.
We start every gate from default deny. A tool that nobody listed stays off. The sample later in this guide works that way.
6. Trace and eval hooks
A trace records every model call, tool call and result. An eval replays tasks and scores the outcome. Together they answer two questions: what did the agent do, and was it right?
The Agent SDK returns cost, usage, turn count and a session ID on every result, which is the raw material for a trace. Claude Code writes each message, tool use and result to a local JSONL file (a plain-text file with one record per line), which is what makes resuming and rewinding possible. Anthropic's evals guide says you will not know whether your graders work unless you read the transcripts and grades from many trials.
A minimal harness you can run
This is the smallest harness that still counts: a loop, a tool table and a stop rule. The model is a stub that replays a fixed plan, so it runs offline with no API key. The point is the structure, not the intelligence.
"""The smallest agent harness: loop, tool table, stop rule. Runs offline with a stub model.""" TOOLS = { "add": lambda a, b: a + b, "multiply": lambda a, b: a * b,} def stub_model(history): """Stands in for an LLM. Reads the history, returns a tool call or a final answer.""" results = [h["content"] for h in history if h["role"] == "tool"] if len(results) == 0: return {"type": "tool", "tool": "add", "args": [2, 3]} if len(results) == 1: return {"type": "tool", "tool": "multiply", "args": [results[0], 10]} return {"type": "answer", "text": f"The result is {results[-1]}."} def run(model, task, max_turns=5): history = [{"role": "user", "content": task}] for turn in range(1, max_turns + 1): action = model(history) if action["type"] == "answer": print(f"turn {turn}: answer") return action["text"] result = TOOLS[action["tool"]](*action["args"]) print(f"turn {turn}: {action['tool']}{tuple(action['args'])} -> {result}") history.append({"role": "tool", "content": result}) return "stopped: turn limit reached" if __name__ == "__main__": print(run(stub_model, "Add 2 and 3, then multiply by 10."))We ran it with Python 3.13.3 on 2026-10-27. This is the real output:
turn 1: add(2, 3) -> 5turn 2: multiply(5, 10) -> 50turn 3: answerThe result is 50.To use a real model, replace stub_model with a function that sends the history to a model API and parses a tool request or an answer. The rest stays the same.
Add permissions and hard limits
The minimal loop has two gaps. Nothing stops a bad tool call, and nothing stops a loop that never ends. The second sample closes both. It uses a permission policy with three outcomes (run, ask, deny), a turn limit and a spend limit.
The guarded loop
The diagram shows one turn of the guarded loop. The red markers show where each failure from the failure table appears. We come back to them in the next section.
The guarded loop: check caps, plan with a model call, permission gate, act, observe, then back to check caps. Stops sit above for a cap hit and a verified answer. Red markers F1 to F9 show where each failure appears.
"""A harness loop with a permission gate, a turn limit and a spend limit. Stub model, no API key.""" from dataclasses import dataclass, field # Permission policy: what runs, what needs a human, what is blocked.# Anything not listed in ALLOW or ASK is refused (default deny), so a new tool stays off until someone adds it.ALLOW = {"search_orders"}ASK = {"issue_refund"}DENY = {"delete_customer"} TOOLS = { "search_orders": lambda customer: {"customer": customer, "order": "A-1001", "total": 42.0}, "issue_refund": lambda order, amount: f"refunded {amount} on {order}", "delete_customer": lambda customer: "deleted",} # Made-up unit prices so the budget logic has something to count. Not real model prices.PRICE_PER_1K_TOKENS = 0.01 @dataclassclass Budget: max_turns: int = 6 max_cost: float = 0.05 turns: int = 0 cost: float = 0.0 def charge(self, tokens): self.turns += 1 self.cost += tokens / 1000 * PRICE_PER_1K_TOKENS def exhausted(self): if self.turns >= self.max_turns: return "error_max_turns" if self.cost >= self.max_cost: return "error_max_budget_usd" return None @dataclassclass Result: subtype: str text: str = "" log: list = field(default_factory=list) def approve(tool, args): """Human-in-the-loop hook. Here a rule stands in for a person: refunds up to 50 pass.""" return args[-1] <= 50 def run(model, task, budget): history, log = [task], [] while True: stop = budget.exhausted() if stop: return Result(stop, log=log) action, tokens = model(history) budget.charge(tokens) if action["type"] == "answer": return Result("success", action["text"], log) tool, args = action["tool"], action["args"] if tool in DENY or tool not in TOOLS or tool not in ALLOW | ASK: observation = f"blocked: {tool} is not permitted" elif tool in ASK and not approve(tool, args): observation = f"blocked: {tool}({', '.join(repr(a) for a in args)}) was not approved" else: observation = TOOLS[tool](*args) call = f"{tool}({', '.join(repr(a) for a in args)})" log.append(f"turn {budget.turns}: {call} -> {observation}") history.append(observation) def scripted(steps, tokens_per_call): """A stub model that replays a fixed list of actions.""" it = iter(steps) return lambda history: (next(it, {"type": "answer", "text": "done"}), tokens_per_call) def show(name, result, budget): print(f"== {name}: {result.subtype} (turns={budget.turns}, cost=${budget.cost:.3f})") for line in result.log: print(" ", line) if result.text: print(" ", result.text) if __name__ == "__main__": steps = [ {"type": "tool", "tool": "search_orders", "args": ["ada"]}, {"type": "tool", "tool": "delete_customer", "args": ["ada"]}, {"type": "tool", "tool": "issue_refund", "args": ["A-1001", 42.0]}, {"type": "tool", "tool": "issue_refund", "args": ["A-1001", 900.0]}, {"type": "answer", "text": "Refund handled."}, ] b = Budget() show("permissions", run(scripted(steps, 800), "Refund ada", b), b) loop_forever = [{"type": "tool", "tool": "search_orders", "args": ["ada"]}] * 50 b = Budget(max_turns=3) show("turn limit", run(scripted(loop_forever, 800), "Loop", b), b) b = Budget(max_turns=50, max_cost=0.05) show("spend limit", run(scripted(loop_forever, 2000), "Loop", b), b)Again the model is a stub, and the unit price is made up so the budget has something to count. It is not a real model price. We ran it on 2026-10-27. This is the real output:
== permissions: success (turns=5, cost=$0.040) turn 1: search_orders('ada') -> {'customer': 'ada', 'order': 'A-1001', 'total': 42.0} turn 2: delete_customer('ada') -> blocked: delete_customer is not permitted turn 3: issue_refund('A-1001', 42.0) -> refunded 42.0 on A-1001 turn 4: issue_refund('A-1001', 900.0) -> blocked: issue_refund('A-1001', 900.0) was not approved Refund handled.== turn limit: error_max_turns (turns=3, cost=$0.024) turn 1: search_orders('ada') -> {'customer': 'ada', 'order': 'A-1001', 'total': 42.0} turn 2: search_orders('ada') -> {'customer': 'ada', 'order': 'A-1001', 'total': 42.0} turn 3: search_orders('ada') -> {'customer': 'ada', 'order': 'A-1001', 'total': 42.0}== spend limit: error_max_budget_usd (turns=3, cost=$0.060) turn 1: search_orders('ada') -> {'customer': 'ada', 'order': 'A-1001', 'total': 42.0} turn 2: search_orders('ada') -> {'customer': 'ada', 'order': 'A-1001', 'total': 42.0} turn 3: search_orders('ada') -> {'customer': 'ada', 'order': 'A-1001', 'total': 42.0}Three things we read in that output.
- The delete call was blocked and the agent carried on. The model saw a rejection as the tool result and moved to the next step. The Agent SDK works the same way: a denied tool returns a rejection message and the model tries another route.
- The small refund passed and the large one did not. The approval rule here is a stand-in for a human.
- The spend limit stopped the run at $0.060 on a $0.050 cap. The check happens after a model response arrives, so the last call can push spend past the cap. The Agent SDK docs describe the same effect and advise leaving headroom.
A permission policy as a file
Hard-coded sets work for a demo. In a real system you want the policy in a file that a reviewer can read and a test can load. This JSON file holds the limits and a rule for each tool, with default deny. The refund rule asks for approval only above 50. That is a demo threshold, the same one as the sample above. Set your own.
{ "version": 1, "default": "deny", "limits": { "max_turns": 20, "max_cost_usd": 2.0, "tool_timeout_seconds": 30, "max_tool_output_chars": 4000 }, "tools": { "search_orders": { "decision": "allow" }, "read_policy_doc": { "decision": "allow" }, "issue_refund": { "decision": "ask", "approver": "support_lead", "when": { "arg": "amount", "above": 50 } }, "send_email": { "decision": "ask", "approver": "support_lead" }, "delete_customer": { "decision": "deny" } }}A policy file is code, so validate it before it ships. This script checks that the default is deny, that every limit is set, that every ask rule names an approver, and then applies the policy to sample calls.
"""Validate a permission policy file, then apply it to sample tool calls. Offline, standard library only."""import jsonimport sysfrom pathlib import Path DECISIONS = {"allow", "ask", "deny"}LIMIT_KEYS = {"max_turns", "max_cost_usd", "tool_timeout_seconds", "max_tool_output_chars"} def validate(policy): """Return a list of problems. An empty list means the policy is well formed.""" problems = [] if policy.get("default") != "deny": problems.append('default must be "deny" so a new tool stays off until someone lists it') missing = LIMIT_KEYS - set(policy.get("limits", {})) if missing: problems.append(f"limits missing: {sorted(missing)}") for name, rule in policy.get("tools", {}).items(): if rule.get("decision") not in DECISIONS: problems.append(f"{name}: decision must be one of {sorted(DECISIONS)}") if rule.get("decision") == "ask" and not rule.get("approver"): problems.append(f"{name}: an ask rule needs an approver") if "when" in rule and rule["decision"] != "ask": problems.append(f"{name}: a 'when' condition only makes sense on an ask rule") return problems def decide(policy, tool, args): """Return allow, ask or deny for one call. Unlisted tools get the default.""" rule = policy["tools"].get(tool) if rule is None: return policy["default"] if rule["decision"] == "ask" and "when" in rule: cond = rule["when"] # An ask rule with a threshold only asks above the threshold. Below it, the call runs. return "ask" if args.get(cond["arg"], 0) > cond["above"] else "allow" return rule["decision"] if __name__ == "__main__": policy = json.loads((Path(__file__).parent / "policy.json").read_text()) problems = validate(policy) print("policy valid" if not problems else "policy problems: " + "; ".join(problems)) if problems: sys.exit(1) calls = [ ("search_orders", {"customer": "ada"}), ("issue_refund", {"order": "A-1001", "amount": 42.0}), ("issue_refund", {"order": "A-1001", "amount": 900.0}), ("send_email", {"to": "ada@example.com"}), ("delete_customer", {"customer": "ada"}), ("export_all_data", {}), ] for tool, args in calls: print(f"{tool}({args}) -> {decide(policy, tool, args)}") bad = {"default": "allow", "limits": {}, "tools": {"x": {"decision": "ask"}, "y": {"decision": "maybe"}}} print("broken policy ->", "; ".join(validate(bad)))We ran it on 2026-10-27 with Python 3.13.3. This is the real output:
policy validsearch_orders({'customer': 'ada'}) -> allowissue_refund({'order': 'A-1001', 'amount': 42.0}) -> allowissue_refund({'order': 'A-1001', 'amount': 900.0}) -> asksend_email({'to': 'ada@example.com'}) -> askdelete_customer({'customer': 'ada'}) -> denyexport_all_data({}) -> denybroken policy -> default must be "deny" so a new tool stays off until someone lists it; limits missing: ['max_cost_usd', 'max_tool_output_chars', 'max_turns', 'tool_timeout_seconds']; x: an ask rule needs an approver; y: decision must be one of ['allow', 'ask', 'deny']The unlisted tool export_all_data was denied without anyone writing a rule, which is the point of default deny. The last line shows the validator catching a broken policy, so a bad edit fails in review, not in production. The format is ours, a pattern to copy and not a standard.
Failure modes to design for
Agents fail in a small number of repeating ways. The table lists nine. Each has a symptom you would see, a guard in the harness, and a test you can run before launch. The marker numbers match the guarded loop diagram. An idempotency key, mentioned under F7, is a label sent with an action so that repeating the action does not do it twice. The last column says where the row comes from. "Our reasoning" means we derived it from how the code works and no source states it.
| Marker | Failure | Symptom you see | Guard in the harness | How to test it | Basis |
|---|---|---|---|---|---|
| F1 | Runaway loop | The same call repeats, turns climb, no answer arrives | Turn cap in code, check for repeated identical calls, timeout per tool | Stub a model that repeats one call. Expect the run to end at the cap with a cap result | SDK docs: without limits the loop runs until the model finishes. Our sample ran. The repeated-call check is our reasoning |
| F2 | Context overflow | Early rules are forgotten, work repeats, quality drops late in a long run | Trim tool output, compaction with instructions, rules in a re-read file, one work item per session | Run a long task with large outputs. Check that a rule from the start is still obeyed at the end | Agent SDK docs, Anthropic harness design post |
| F3 | Wrong tool | The agent calls a tool that does not fit, or passes bad inputs | A short tool list per task, typed inputs, a rejection returned as the tool result | Script calls to unlisted tools and bad inputs. Expect a block and a loop that continues | SDK docs on denied calls. Typed inputs are our reasoning |
| F4 | Prompt injection through tool output | After reading a web page or file, the agent makes calls nobody asked for | Least privilege, human approval for high-risk actions, mark outside content as untrusted | Put a page with a hostile instruction in a test fixture. Expect the gate to stop the call | OWASP LLM01 |
| F5 | Silent cost | The first sign of spend is the invoice, and nobody knows the cost of one task | Spend cap, cost logged per run, alerts on drift | Set a cap below the expected cost. Expect a stop, a logged cost and a known overshoot | SDK docs on budget headroom. Anthropic: a full harness run cost over 20 times a solo run in one example. Alerts on drift are our reasoning |
| F6 | Stale memory | The agent acts on a saved note that is no longer true | Date and source on every note, expiry, check before acting, a way to delete | Seed an outdated note. Expect the agent to verify it or flag it | Our reasoning |
| F7 | Partial failure | A tool fails halfway. The agent retries and duplicates an action, or reports success | Retry rules, idempotency keys, checkpoints, resume from the last state, report a partial result | Make a stub tool fail on its second call. Expect no duplicate side effect and an honest report | Agent SDK docs on resuming sessions. Retry rules and idempotency keys are our reasoning |
| F8 | Premature done | The agent declares the job finished when it is not | A required-checks list that starts as failing, a separate evaluator or test before an answer is accepted | Give a task with a hidden failing check. Expect the harness to reject the answer | Anthropic harness design post |
| F9 | Over-wide permissions | The agent can reach tools it never needs | Default deny, an allow list per task, a deny list for destructive actions | Run the policy script with an unlisted tool. Expect deny | SDK docs. Our policy sample ran |
Three notes on the table.
First, F4 gets the most attention from us in any system that reads outside content. In the OWASP 2026 entry (LLM01) that we read, indirect prompt injection is described as the model ingesting content from an external source, such as a web page, document, email or tool response, that contains instructions the user never saw. Among its mitigations it lists least privilege per operation and explicit human confirmation before privileged, irreversible or externally visible actions. It also says no reliable prevention exists today, so defense has to be architectural. A harness cannot make the model immune. It can limit what an injected instruction is able to do, which is why F4 and F9 belong together.
Second, the clearest case for F8 is in Anthropic's long-running agent post. They report that agents praise their own work even when it is mediocre, and that Claude out of the box was a poor QA agent: it found real issues, then talked itself into approving the work. Their fix was a separate evaluator, tuned to be skeptical, that tests the running app against agreed criteria and fails a sprint if any criterion falls below its threshold. They note the evaluator is still a model and still missed some bugs.
Third, we checked the cost side of F1 in Anthropic's compiler post. In multi-agent systems cost multiplies, and that project ran 16 parallel agents across nearly 2,000 Claude Code sessions and reported a total cost just under $20,000. If you add parallel agents or subagents, add a cap on them too. The SDK budget cap counts subagent spend toward the total.
Ready-made harnesses
You do not have to write the loop yourself. Several tools ship one. We read each project's own documentation on 2026-10-27 and list what it says. Where the pages were silent we write "not stated in docs".
Claude Code and the Claude Agent SDK
Claude Code is a terminal agent. The Agent SDK is a library that gives you the same tools, agent loop and context management in Python and TypeScript. It includes built-in tools for files, search, shell commands and the web, plus hooks, subagents, MCP, permissions and sessions. Its permission modes range from asking for everything to running without prompts, and the docs advise reserving the no-prompt mode for isolated environments such as containers.
Managed Agents, the hosted option
Anthropic also offers Managed Agents, which its overview calls a pre-built, configurable agent harness that runs in managed infrastructure. Sessions run in an Anthropic-managed cloud sandbox (an isolated environment where the agent can run commands without touching your systems) or in a self-hosted sandbox on your own infrastructure. The built-in tools are Bash, file operations, web search and fetch, which can be limited to an allowlist or blocklist of domains, and MCP servers. Sessions are stateful, with a persistent filesystem and conversation history. Events stream back as server-sent events, and the full event history is stored on the server and can be fetched. The harness has built-in prompt caching and compaction, and scheduled deployments run an agent on a cron schedule.
Two limits matter for planning. It is in beta and needs the managed-agents-2026-04-01 beta header. And the docs say it is not currently eligible for Zero Data Retention or HIPAA Business Associate Agreement coverage, because sessions store history, sandbox state and outputs on the server. We found no permission policy on the overview page, so the matrix leaves that cell open.
LangChain and LangGraph
LangChain's docs call create_agent the standard way to build agents in LangChain 1.0 and say middleware is its defining feature. It runs the model and tools loop on the LangGraph runtime, and the docs say middleware is not a separate runtime. Middleware hooks (small pieces of code that run at set points in each step) let you customize each step, with built-in options for human approval, summarizing history near context limits, and redacting personal data before it reaches the model. LangGraph adds durable execution (a run that survives a failure and resumes where it stopped), persistence and human-in-the-loop pauses.
The LangGraph docs describe it as very low-level and focused on orchestration, with durable execution that resumes from where an agent left off, short-term and long-term memory, and debugging through LangSmith. Its docs also point to LangSmith Deployment, a purpose-built deployment platform for long running, stateful workflows. That makes LangGraph a toolkit for building a harness more than a finished one.
OpenAI Agents SDK
OpenAI's Agents SDK for Python describes a built-in loop that continues until the task is complete, function tools generated from Python functions, handoffs between agents, guardrails that run input checks alongside the agent, sessions as a memory layer, built-in tracing, and mechanisms for involving humans during runs. The docs also list sandbox agents, which run inside isolated workspaces, hosted tools, and websocket transport. By default it uses OpenAI's Responses API, and the docs list adapters for other model providers.
Comparison matrix
The first table covers what each option gives you for the six parts. The second covers license and hosting facts from primary sources. An empty claim is better than a guess, so gaps say "not stated in docs".
| Option | Loop | Tools | Permissions and approval | Memory | Tracing |
|---|---|---|---|---|---|
| Claude Agent SDK | Yes, the Claude Code loop as a library | Built-in tools, MCP, custom tools | Allow list, deny list, permission modes, hooks, approval callback | Sessions to resume or fork, CLAUDE.md and memory files | Cost, usage, turns and session ID on each result, hook events |
| Claude Code | Yes, in the terminal | Built-in tools, MCP, skills | Modes from manual approval to auto, rules in settings | CLAUDE.md, auto memory, local session files | Messages and tool results saved to local JSONL files |
LangChain create_agent with LangGraph | Yes, on the LangGraph runtime | Tools you define, called by the model | Middleware for human approval | Persistence, short-term and long-term memory | Debugging through LangSmith |
| OpenAI Agents SDK | Yes, built in | Function tools, MCP, handoffs | Guardrails for input checks, human-in-the-loop mechanisms | Sessions | Built-in tracing |
| Managed Agents | Yes, hosted | Bash, file operations, web search and fetch with optional domain lists, MCP servers | Not stated on the overview page. You can steer or interrupt a running session | Stateful sessions with a persistent filesystem and conversation history | Events streamed as server-sent events, full history stored and fetchable |
| Option | Licence | Where it runs |
|---|---|---|
| Claude Agent SDK | Docs: governed by Anthropic's Commercial Terms. The Python repository carries an MIT license file and the TypeScript README points to the Commercial Terms | A library in your own process |
| Claude Code | Not stated in docs we read | Local machine, Anthropic-managed cloud VMs or self-hosted environments, or remote control from a browser |
| LangChain and LangGraph | MIT license files in both repositories | Libraries you run yourself. The LangGraph docs also point to LangSmith Deployment, a deployment platform for long running, stateful workflows |
| OpenAI Agents SDK | MIT license file in the repository | A library you run yourself, with sandbox agents for isolated workspaces. Model calls go to OpenAI by default, other providers through adapters |
| Managed Agents | Not stated on the overview page | Hosted by Anthropic, with a managed cloud sandbox or a self-hosted sandbox. In beta. Not currently eligible for Zero Data Retention or HIPAA BAA coverage |
Licence facts come from each project's own repository and documentation. Every cell is taken from documentation, and features change often, so check the current pages before you decide.
The Agent SDK sample
Here is the third code sample. It is the Agent SDK with the limits from the earlier section.
"""Claude Agent SDK with limits. NOT executed in our test run: it needs an API key.Needs Python 3.10 or later (quickstart). The budget cap relies on a recent Claude Code version (see the agent loop docs).Names are copied from the Agent SDK docs checked on 2026-10-27 (quickstart and agent loop pages)."""import asyncio from claude_agent_sdk import ClaudeAgentOptions, ResultMessage, query async def main(): try: async for message in query( prompt="Find and fix the bug causing test failures in the auth module", options=ClaudeAgentOptions( allowed_tools=["Read", "Edit", "Glob", "Grep"], # auto-approved disallowed_tools=["Bash"], # blocked, whatever else is set permission_mode="default", # tools not listed above are denied here, because no approval callback is set max_turns=30, # cap on tool-use round trips max_budget_usd=1.00, # cap on estimated spend ), ): if isinstance(message, ResultMessage): print(message.subtype) # success, error_max_turns, error_max_budget_usd ... if message.total_cost_usd is not None: print(f"cost: ${message.total_cost_usd:.4f}") except Exception as error: # query() raises after yielding an error result print(f"Session ended with an error: {error}") asyncio.run(main())Per the docs, allowed_tools auto-approves the tools you list, disallowed_tools blocks them regardless of other settings, and max_turns and max_budget_usd cap the loop. Hitting a cap returns a result with the subtype error_max_turns or error_max_budget_usd. A single-shot query() call then raises an error after yielding that result, so wrap the loop in a try block. Install with pip install claude-agent-sdk.
MCP and the 2026-07-28 spec
The Model Context Protocol (MCP) is an open protocol that lets an agent connect to external tools and data. A harness acts as the MCP client. It decides which servers to trust and when to call them.
The 2026-07-28 revision changes how clients and servers talk. We read the official changelog on 2026-10-27. These are the points we think matter for a harness builder.
- No sessions. Protocol-level sessions and the
Mcp-Session-Idheader are removed. Servers that need state across calls use explicit handles passed as ordinary tool arguments. - Stateless requests. The
initializehandshake is removed. Each request carries its protocol version and client capabilities in_meta. - Discovery. Servers must implement
server/discoverto advertise their versions, capabilities and identity. - Tasks moved to an extension. Experimental tasks left the core protocol and became an official extension, with polling through
tasks/get. - Caching hints. Results from list calls carry
ttlMsandcacheScope. Servers should return tools in a stable order, which the changelog says helps prompt cache hit rates. - Deprecated. Roots, Sampling and Logging are deprecated, along with the HTTP+SSE transport and Dynamic Client Registration. They keep working during a deprecation window of at least twelve months.
In plain terms: MCP is how an agent plugs into tools, and this revision makes that connection simpler. If you only buy agents, ask your vendor which MCP version they support. The rest of this section is for engineers.
What this means in practice: if your harness opens an MCP session and waits for an initialize reply, it needs a change. Check each server's supported versions with server/discover and handle resultType: "input_required" results. This is our reading of the spec, so test it against your own servers.
An MCP server is also an entry point for F4. Tool output from a server you do not control is outside content. Treat it that way in the gate. For a business-level view of MCP, read our post What is MCP? The Model Context Protocol explained for business leaders. For the wider security picture, see AI agent security: how to secure AI agents.
Build, adopt or buy
Start from the cost of getting it wrong. A loop is easy to write. The hard parts are the edges: permissions, recovery, context, and traces you can replay.
| Option | You get | You own | Fits when |
|---|---|---|---|
| Adopt a coding-agent SDK | Tested loop, built-in tools, permissions, sessions | Your prompts, tool choices, limits | The task looks like work in files and shells |
| Adopt a graph framework | State, persistence, human pauses, many model providers | Graph design, tool code, hosting | You need multi-step flows with checkpoints |
| Use a hosted agent service | A loop and sandbox that run for you | Config, data policy, vendor lock-in | You want speed and accept a hosted runtime. Managed Agents is in beta |
| Build your own | Full control of every step | Everything, including bugs | You have strict data, approval or audit rules |
Our recommendation is to adopt first and build later. Write your own loop only when a specific need is unmet, such as an approval flow that tools cannot express or a rule that data must never leave your network. When you do build, copy the structure above and add the missing parts one at a time.
Scoring the options
The next table scores four paths against six criteria. The ratings are our engineering judgment from the documentation above. We left numbers off because we have no measurements to back them. Change the weights to fit your case: a regulated team will weight compliance above speed.
| Criterion | Claude Agent SDK | LangGraph | Hosted agent service | Build from scratch |
|---|---|---|---|---|
| Control over each step | Medium: hooks and permissions, but the loop is Anthropic's | High: you design the graph | Low to medium: set by the vendor | High |
| Time to first run | Short: loop and tools included | Medium: you design the graph and tools | Short | Long |
| Running and upkeep effort | Medium | Medium to high | Low | High |
| Lock-in | Medium: tied to one tool and its terms | Low to medium: open license you can run yourself, with an optional LangChain deployment platform | High, and Managed Agents is in beta | Low |
| Compliance fit (data stays with you) | Good: runs in your process, though model calls still leave your network | Good: you run it, you pick providers | Check each vendor. Managed Agents is not currently eligible for Zero Data Retention or HIPAA BAA coverage, though it offers a self-hosted sandbox | Best: you decide everything |
| Team skills needed | Python or TypeScript plus agent basics | Graph and state design | Configuration, vendor knowledge | Full agent engineering |
Read it by the rows that matter to you, not by totals. For the wider question of building in-house, buying or bringing in a partner, see our build, buy or partner decision matrix. A short first run favors an SDK or a hosted service. A strict data rule favors building, or running a library inside your network.
A decision tree
The same logic as a tree. It starts with compliance, because a hard rule there removes options before preference matters.
A decision tree with four questions on compliance, team, control and who runs the loop, ending in build, adopt a library you host, adopt a graph framework, buy hosted (Managed Agents is in beta), or adopt an SDK.
Cost control
Agents spend more than chat. For budget ranges on a full project, see how much it costs to build an AI agent. In Anthropic's harness design post, a solo run of a retro game maker took 20 minutes and cost $9, and the full three-agent harness took 6 hours and cost $200, more than 20 times as much. Our arithmetic: $200 divided by $9 is about 22. What we make of it: this is one team's run from March 2026, so we use it as an order of magnitude, not a forecast for your workload. The same post says an evaluator is worth its cost when a task sits beyond what the current model does reliably alone.
Levers a harness gives you, per the Agent SDK docs:
- Cap turns and spend, and leave headroom for the last response.
- Lower the
effortsetting for simple steps such as listing files. - Scope subagents (helper agents the main agent hands a task to) to the fewest tools they need. A subagent returns only its final reply, so the main context grows by a summary.
- Load tool definitions on demand. MCP tool schemas are deferred by default in the SDK, so only names take context until a tool is used.
- Keep prompts stable. Content that repeats across turns is prompt cached, which cuts cost and latency. Our post on reducing LLM costs without losing quality covers more techniques.
Where TypeSafe Jev fits
Not every step needs a large language model. Routing a request, scoring a risk, or answering yes or no are small decisions. In our judgment a small, fast model may be enough to make them before the large one runs. We put no figure on the saving.
TypeSafe's Jev is built for that shape of work. Its docs say Jev is a "System One" model that makes fast, structured decisions: you define the answers through Choice, Score or Noul types (a Noul is a yes or no question, answered with a probability), and it returns a typed result with probabilities instead of generated text. The docs also say it does not write replies, produce code or explain its reasoning, and that it takes text and JSON input. Images, audio and video are not supported yet.
In a harness, that suggests two places:
- Router. Decide which model, tool set or agent handles a request, before the loop starts.
- Guard. Score a proposed tool call as safe or risky before the permission gate lets it through.
Where it fits is our suggestion, drawn from its docs. The docs say calibration is measured across groups of predictions and does not guarantee that a single answer is correct. So we would use it as one signal, not as the only check on a high-risk action. The concepts page we read on 2026-10-27 gives no latency or price figures and does not state its access status, and prices sit on a separate page we did not read. We left speed and price figures out, because any you see elsewhere are vendor claims until someone measures them.
| Model | Vendor | Weights | Price in / out per 1M tokens | Strengths | Best for |
|---|---|---|---|---|---|
| Jev 1.13 | TypeSafe AI | Closed | Not listed | Non-generative (per TypeSafe). Returns typed answers, such as a choice, a score or a yes or no, with probabilities. Takes text and JSON input. | Fast structured decisions where a typed answer with probabilities is enough, per TypeSafe. Use in an agent loop is our suggestion, not TypeSafe's claim. |
What the evidence shows
Below is what we found when we read the primary sources and one EdgeFirm case study, with our reading of each.
Anthropic's long-running agents
Anthropic's engineers built a harness on the Claude Agent SDK that turns a prompt of one to four sentences into a full app. It has three agents. A planner expands the prompt into a spec. A generator builds features one sprint at a time. An evaluator clicks through the running app with Playwright and fails a sprint if any criterion falls below its threshold. Before coding, the generator and evaluator agree what done means, which the post calls a sprint contract, and the agents communicate through files. In the retro game maker example, the solo run took 20 minutes and cost $9, and its core feature did not work. The full harness took 6 hours and cost $200, and its game was playable, with physics issues. With a newer model the author removed the sprint construct and moved the evaluator to a single pass at the end. Their advice is to remove one component at a time to find which ones carry the load, and to re-check the harness each time a new model ships. They also report limits: the evaluator still missed small layout issues and bugs in deeply nested features, and the music app they built was "far from a professional music production program". What we take from it: the evaluator-for-cost trade is the part that carries over to other work, while the sprint construct was tied to one model. That is our judgment, not the post's.
Anthropic's parallel-agent compiler
Anthropic tasked 16 agents with writing a Rust-based C compiler. The run took nearly 2,000 Claude Code sessions over two weeks, 2 billion input tokens and 140 million output tokens, for a total cost just under $20,000. It produced a 100,000-line compiler that can build Linux 6.9 on x86, ARM and RISC-V. Coordination was simple. Each agent claimed a task by writing a lock file to a current_tasks/ folder, worked in its own clone and merged through git, with no orchestrator agent. The author's lessons are about the test harness. The verifier must be nearly perfect, because the agent works autonomously on whatever it is given. Print only a few lines and log detail to files so output does not pollute context. Give agents a fast option that runs a sample of the tests. Add continuous integration to catch regressions. The post also lists limits: the compiler could not generate 16-bit x86 boot code, it has no assembler or linker of its own, and its output is less efficient than GCC with all optimizations disabled. The author says they felt uneasy about deploying code that no human has verified. What we take from it: the verifier carried more weight than the orchestration, which was a lock file and git. That is our reading.
An EdgeFirm project
EdgeFirm built an agent-driven security analysis platform that ingests an enterprise code repository and produces validated vulnerability reports. Its case study says the platform uses the Claude Agent SDK with MCP-integrated tools, gives the agent controlled tool access within execution boundaries, runs analysis in isolated Docker environments, uses Temporal for durable workflow execution, and has a failure-handling loop that refines automated tests. It reports fewer edge-case failures in the client's own testing, and fewer false positives through runtime validation. The page also carries an unnamed testimonial, which we do not use.
We report only what the case study states. It names no run counts, costs or percentages, and we add none. Our reading is that it follows the pattern in this guide: an off-the-shelf agent loop, with controlled tool access, isolation and validation built around it. The page does not describe turn or spend limits.
Evaluating agent runs
A harness that records runs gives you data. An eval turns the data into a decision. Start small. The Anthropic evals guide we read says 20 to 50 simple tasks drawn from real failures is a great start. It prefers deterministic graders where possible, model graders where needed and human graders sparingly. For model judges it advises calibrating them against human experts, giving the judge a way out such as returning "Unknown", and grading each dimension with its own judge.
The rubric below is the template we would start from. The first five criteria are our adaptation of common agent-eval dimensions, and the last three come from the failure table. Adapt the wording to your task. Score each run, keep the scores with the run ID, and re-run the set whenever the prompt, tools, model or harness code changes.
| Criterion | What to check | Score 0 to 1 | Fail if |
|---|---|---|---|
| Factual accuracy | Do claims match the sources or the system of record? | 0 wrong, 0.5 partly right, 1 right | Any claim that would cause harm if wrong |
| Citation or evidence accuracy | Does each claim point to the thing that supports it? | 0 none, 0.5 some, 1 all | A claim with no support |
| Completeness | Did the run cover every part of the task? | 0 missed most, 0.5 missed some, 1 covered all | A required part is missing |
| Source quality | Did it use primary or authoritative sources? | 0 poor, 0.5 mixed, 1 good | It relied on low-quality pages when better ones existed |
| Tool efficiency | Right tools, a sensible number of calls? | 0 wasteful, 0.5 some waste, 1 lean | Repeated identical calls (F1) |
| Permission behavior | Did every call stay inside the policy? | 0 broke policy, 1 stayed inside | A blocked action retried in a way that gets around the policy, such as the same action through another tool or with changed arguments. Trying a different, allowed approach is fine |
| Cost | Is the cost within the cap and the expected range? | 0 over cap, 0.5 near cap, 1 well inside | Spend over the cap |
| Honest finish | Does the result say what was not done? | 0 hid a gap, 1 reported it | A claimed finish with a missing step (F7, F8) |
When you do not need a custom harness
Many projects should never write their own. You do not need one if any of these is true. If you are still deciding whether to prototype or ship, proof of concept or production AI explains which to build first.
- The task is one model call with no tools. A prompt, a schema check and a retry are enough. A loop adds risk without adding value.
- The steps are fixed and known in advance. A plain workflow in code, with the model filling in one step, is easier to test than an agent that chooses its own steps. Use an agent when the path cannot be written down ahead of time.
- A ready-made harness already covers your case. If the Claude Agent SDK, LangGraph or a hosted service handles your limits, permissions and traces, your effort is better spent on prompts, tools and evals.
- You cannot yet say what done looks like. A harness cannot fix an undefined task. Write ten examples of good and bad results first.
- Nobody will own it. A custom harness is a system with bugs and upgrades. If no one has time to maintain it, adopt one that someone else maintains.
Common mistakes
These are the mistakes we would watch for first, drawn from the failure table and the sources. They are engineering judgment, not survey results.
- Putting limits in the prompt. A request to stop after five steps is not a control. Keep every limit in code.
- Trusting the model's word that it is done. Anthropic's agents praised their own mediocre work. Check the result with something that is not the generating model, such as a test or a separate evaluator.
- Reading outside content with high-impact tools in the same run. An agent that reads a web page and can also send email or delete records has the risk of F4 and F9 together. Split the jobs or gate the dangerous tools.
- Skipping traces until something breaks. You cannot replay a run you did not record.
- Never testing the stop path. Teams test the happy path. The cap, the denial and the partial failure are the ones that cost money.
- Letting memory grow without dates. An undated note cannot be checked and will eventually be wrong.
- Adding subagents without a cap. Spend scales with the number you allow. The compiler run above, with 16 agents, is the large end of that.
Production checklist
Use this before an agent touches real data. Each row names its part in the Six-Part Anatomy and the failure it guards against.
| # | Part | Check before launch | Guards against |
|---|---|---|---|
| 1 | Loop controller | A hard cap on turns and spend, set in code, with headroom for the last response | F1, F5 |
| 2 | Loop controller | A defined result for each stop: cap hit, denial, error, verified answer | F7, F8 |
| 3 | Tool registry | A timeout on every tool and a limit on output size | F2, F7 |
| 4 | Tool registry | Only the tools the task needs, each with typed inputs | F3 |
| 5 | Permission gate | Default deny, an allow list per task, a deny list for destructive actions | F9 |
| 6 | Permission gate | Human approval, above a threshold you set, for anything that moves money, sends messages or deletes data | F4, F9 |
| 7 | Context manager | Rules that must survive in a file that is re-read each call, not in the first prompt | F2 |
| 8 | Context manager | Outside content marked as untrusted | F4 |
| 9 | Memory store | A rule for what is saved, with a date and source, who can read it, and a way to delete it | F6 |
| 10 | Trace and eval hooks | Every model call, tool call, result and cost stored with a run ID | F5 and all |
| 11 | Trace and eval hooks | A fixed set of real tasks with known good outcomes, run on every change | F8 |
| 12 | Application | What the user sees when the agent stops at a limit, is blocked or is wrong, and a route to a human | F7, F8 |
What to ask a vendor
If you buy an agent instead of building one, the harness is what you are buying. Five questions bring it into view.
- What stops the agent? Ask for the turn and spend limits and who sets them.
- What can the agent touch? Ask for the list of tools and the rule for each one.
- How do we replay a bad run? Ask to see a trace from start to finish.
- What does one finished task cost? Ask for the average and the worst case, from real runs.
- Where does our data go? Ask which models and services see it, and what is stored.
A vendor with clear answers has a harness. A vendor who talks only about the model may not.
EdgeFirm builds these systems for clients. If you want help scoping one, see our AI agent development service or our AI implementation services. To go further, read AI agent orchestration for multi-agent designs, LLM evaluation for building the eval set, and AI agents for business for use cases and costs. Our post The reality of AI agents in 2026 covers what goes wrong in practice.
Glossary
| Term | Meaning |
|---|---|
| Agent | A program that uses a language model to plan its own steps and call tools until a task is done. |
| Agent harness | The code, configuration and logic around a model that makes it an agent. Some people say agentic harness. |
| Agent loop | The cycle of calling the model, running the tool it asked for, and calling the model again. |
| Turn | One full cycle of the loop. In the Agent SDK, one round trip in which the model asks for tools and the results come back. |
| Tool | A function the model may ask the harness to run, such as read a file or search a database. |
| Context window | Everything the model can see in one call: prompt, history, tool definitions and tool output. |
| Compaction | Summarizing older history so a long run fits in the context window. Details from early on can be lost. |
| Memory | Information that survives between sessions, such as notes, a progress file or a rules file. |
| Permission gate | The check that decides whether a tool call runs, needs a human, or is blocked. |
| Default deny | A policy where any tool not listed is refused. |
| Guardrail | A check on inputs, outputs or actions that can stop a run. OpenAI's SDK uses this word for input checks that run alongside the agent. |
| Subagent | A helper agent that the main agent hands a task. It returns a summary, not its whole transcript. |
| Prompt injection | Text in the input, or in content the agent reads, that changes the model's behavior in ways nobody intended. |
| Trace | A record of every model call, tool call and result in one run. |
| Eval | A fixed set of tasks with known good outcomes, used to score a system when anything changes. |
| MCP | The Model Context Protocol, an open protocol for connecting an agent to external tools and data. |
| Idempotency key | A label sent with an action so that repeating it does not do the action twice. |
Frequently asked questions
An agent harness is the software runtime around an AI model. A common formulation is agent = model + harness. It runs the loop that calls the model, executes the tools the model asks for, manages the context window, stores memory, enforces permissions and records traces. The model decides what to do next. The harness decides what is allowed.
Yes. Anthropic's own docs describe Claude Code as the layer around the model that provides the tools and manages the context the model sees, and say this layer is what the term agentic harness refers to. The Claude Agent SDK exposes the same loop as a library.
Usually not at first. A ready-made harness gives you a tested loop, permissions and context handling on day one. Build your own when you need a custom approval flow, strict data boundaries, a model mix the tools do not support, or control over every step.
There is no settled line. A framework such as LangGraph gives you low-level parts to assemble. A harness is the assembled runtime. In practice the terms overlap, and one product can be both. Judge a tool by what it handles for you, such as limits, permissions and traces.
Set a hard cap on turns and on spend inside the harness, not in the prompt. Use a cheaper model or lower effort for simple steps, cache stable context, and keep tool output short. Log cost per task so you see drift early. Expect some overshoot, because spend is counted after each model response.
No. MCP is a protocol for connecting tools and data sources to an agent. The harness is what decides when to call them, checks permissions and handles the results. A harness can use MCP servers as one source of tools.
You cannot make the model immune, so limit what an injected instruction can do. Give the agent least privilege, require human approval for high-risk actions, and mark outside content as untrusted. Test with a hostile page in a fixture. OWASP lists these among its mitigations for prompt injection.
When the task is one model call, when the steps are fixed and known, when a ready-made harness already covers your limits and permissions, when you cannot yet define what done means, or when nobody will own and maintain the code.
Last updated Oct 27, 2026. Spotted something out of date? Tell us at hello@edgefirm.io.
Where we can help
Read next
Written by
Co-Founder & Director of Engineering
Usman brings 8+ years of experience building enterprise systems. He specializes in system architecture, DevOps, and data pipelines that power production AI.