← Back to Blog
[Building an Enterprise Agent Harness from Scratch] 1/10: What Breaks When You Hand-Build an Agent?

Building an Enterprise Agent Harness from Scratch1/10: What Breaks When You Hand-Build an Agent?

The first time an agent runs, it is easy to think that the hardest part is already done.

You give the model a task and a list of tools. The model either selects a tool or returns a final answer. The tool result goes back into the conversation, and the loop continues. A few dozen lines of code can already query orders, read documents, update a customer relationship management system (CRM), and even run code.

Then a new message appears in the support window: “I was charged twice yesterday. Please take care of it.”

The agent finds the order, calls the refund API, and quickly replies, “Your refund is complete.” The conversation ends cleanly. In the payment system, the refund may still be processing. The notification email may still be a draft. After a tool call timed out, the same refund may have been executed twice.

This is where we begin learning about the Agent Harness. The model decides what to do next. The Harness makes the overall task controllable, recoverable, and verifiable. The Harness is the system around the whole task. The program that actually advances the loop, calls tools, and saves progress can be called the runtime. Once we separate the model, runtime, and Harness, we can decide which layer should fix a failure.

First, See What the Minimal Agent Is Missing

A minimal agent performs four actions: read the task, make a decision, call a tool, and return the result to the model. In code, it looks roughly like this:

def run_agent(task, model, tools, max_turns=20):
    messages = [{"role": "user", "content": task}]

    for turn in range(max_turns):
        decision = model.next(messages, tools=tools.schemas())

        if decision.type == "final":
            return {"status": "MODEL_FINISHED", "answer": decision.text}

        if decision.type != "tool_call":
            return {"status": "FAILED", "error": "UNKNOWN_DECISION"}

        tool = tools.get(decision.name)
        result = tool.execute(decision.arguments)
        messages.append(decision.as_message())
        messages.append(result.as_tool_message())

    return {"status": "STOPPED", "error": "TURN_BUDGET_EXCEEDED"}

The first line puts the user’s task into the message list. On every turn, the model sees the current messages and each tool’s name, purpose, and input format. It then makes one decision. If it returns a final answer, the program immediately returns MODEL_FINISHED. If it asks to call a tool, the program finds that tool by name, runs it with the model’s arguments, and adds both the model’s request and the tool’s result to the message list. If the loop has not finished after 20 turns, the program stops.

This loop already gives the agent a minimum ability to act. It also exposes the missing pieces. Before tool.execute runs, the program does not check whether this user is allowed to perform the action. After it runs, there is no independent record of whether the real system changed. The message list carries all state. As soon as final appears, the program treats “the model is done talking” as “the business task is complete.” The turn limit can stop an infinite loop, but it cannot tell us how far the task progressed or hand the evidence collected so far to a person.

The Harness does not decide whether the customer deserves a refund. It saves progress, checks permissions, limits budgets, records calls, and verifies results around the loop. MODEL_FINISHED therefore means only that the model has stopped talking. The task becomes BUSINESS_COMPLETED only after the payment, CRM, and email systems all provide matching evidence. In this chapter, we are locating those empty spaces. The following chapters will fill them one by one.

Agent loop: The cycle in which a model decides, calls a tool, reads the result, and decides again.

Agent Harness: The system around a model that provides the loop, context, tool execution, state, permissions, verification, and runtime control.

How Far Is “The Model Stopped” from “The Business Task Is Complete”?

When the model stops requesting tools, the minimal loop returns MODEL_FINISHED. That means only that the model is ready to end the conversation. To know whether the money was refunded, we must query the payment system. To know whether the customer ticket contains the refund ID, we must query the CRM. To know whether the email was delivered, we must query the email system. A sentence from the model cannot replace facts in those three systems.

This gives the refund task its first missing piece: define the finish line before execution, save progress outside the conversation, and require evidence from the payment, CRM, and email systems at the end. In the next chapter, we will turn these into a task contract, a progress ledger, and an independent verifier.

As the task grows longer, a second gap appears. Order records, refund policies, and tool results keep filling the context. The customer’s original statement—“I was charged twice yesterday”—becomes easier to overlook. We must preserve the full history while showing the current turn only the material it needs. Chapter 3 will separate events, workflow state, current context, long-term memory, and business truth.

Once the Agent Can Act, Errors Have Consequences

When an agent produces only text, most errors remain inside the answer. Once it can cancel an order, change a bill, or create a refund, one similar function name can change real data.

A tool therefore cannot be just a function. It needs a clear purpose, parameters, side effects, permissions, approval rules, and result states. The model first proposes an action. The runtime checks it. The service that owns the order performs the final authorization. Chapter 4 follows an accidentally created purchase request through this entire tool pipeline.

Tool calls also introduce time. A refund API spins for ten seconds and returns only a timeout. If the agent tries again, the payment system may already have received the first request. Chapter 5 follows an email through this failure. We will save the identity of the action, query the real outcome, and retry only after confirming that the old request cannot continue and produced no effect.

A Task Can Fail Without a Red Error Message

An agent repeatedly searches the same sources. It rewrites an email, then throws it away and starts again. New text keeps appearing in the window, but the task is not moving toward its finish line. Model input, elapsed time, and tool costs continue to rise.

The runtime must therefore manage turns, time, model input, tool calls, and cost. When the budget is exhausted, it should stop and hand the evidence collected so far to a person. It should not hide a runaway loop behind “still thinking.”

Permissions follow the same logic. A full-access service account makes a demo easy to build, but it can also let an agent working on one order modify another customer’s data. Chapter 6 connects the identities of the user, agent, runtime instance, and backend service so that each action receives only the short-lived permission required for the current order.

After Failure, We Need to Trace the Path Back

When an ordinary application fails, I inspect the request, logs, and database changes. An agent raises more questions: Which materials did this turn receive? Which tool did it choose? Where did the arguments come from? Which approval authorized the action? Which retry changed the state? We need to save decisions and their evidence in a form that can be inspected.

This information belongs in one execution record from the first day. Model calls, tools, policies, approvals, state, cost, and final outcomes all share the same run ID. Sensitive content is redacted according to policy, or represented by a digest that can confirm whether the content changed. In Chapter 7, these runtime records become repeatable evaluations and deployment gates.

Trace: A structured execution record that connects model calls, tools, state, errors, cost, and outcomes for one run.

Adding more agents does not automatically fix a weak agent. It adds model calls, handoffs, state copies, and trust boundaries. In Chapter 8, we will run the same task set first, prove that splitting the work provides a benefit, and only then add worker agents.

2026 Progress: The Harness Is Becoming Its Own Product Layer

The small loop above exists inside one process. If the process stops, messages, progress, and the execution environment may disappear together. A long task forces us to answer three questions: Where are the records stored? Who controls the next step? Where does model-generated code run safely? In 2026, managed products began handling these concerns as separate responsibilities.

In its Managed Agents architecture published on April 8, 2026, Anthropic uses a session to store an append-only conversation record, a harness to read that record and decide the next step, and a sandbox to isolate code and file operations. In plain language, records, orchestration, and code execution each have their own place. After a long run or a controller restart, a new harness can read the original session instead of asking the model to redo the entire task.

Amazon Bedrock AgentCore Harness became generally available on June 17, 2026. It offers managed agent loops, tool connections, context, cross-turn state, failure recovery, and isolated environments for each session. In the refund task, it can keep the agent running and isolate the session. The company’s own payment system must still verify that a refund ID was actually created.

OpenAI Agents API entered public beta on September 10, 2026. It presents three levels of control: call the model directly, use the Agents SDK inside your application, or let the platform host the complete Harness. When I compare these products, I ask the same questions every time: Who saves progress? Who waits and resumes? Who isolates execution? Who verifies the business outcome?

Take the Problem Map into the Next Chapter

Look again at the refund from the beginning. We can now see eight separate problems: completion, context, tools, retries, budgets, permissions, observability, and collaboration. “Agents are unreliable” is no longer one vague complaint. It has become eight system capabilities that we can design, test, and accept separately.

We will begin with the problem closest to the finish line. After the agent says “the refund is complete,” how can the program produce enough evidence to close the task for real?

New ideas, straight to your inbox.

AI, engineering, and experiments. One or two useful emails a month.

No spam. Unsubscribe anytime.