AH.Atelier
☀ Noon
The Native EraPart 3 of 6

Who Stuffs the Text, Who Draws the Path

Anh HoangSeptember 26, 202629 min read10 photos
In this part · 8 sections
  1. What is a harness?
  2. I. LLM-as-router and the closed action set
  3. II. From supreme router to worker node
  4. III. The finite graph: the constraint of topology
  5. IV. State, reducers and the end of the unsigned tape
  6. V. The runtime keeps time, failure and evidence
  7. VI. A mirror from the model's core
  8. The door to the basement
Hand-drawn sketch of a graph: a model box with reasoning coiled inside it, a shortcut arrow from the model to the world's door crossed out, the only path to the door running through a highlighted human-review node and a padlock, and an audit strip running along the bottom

The history of LLM applications over the past few years fits into two verbs: stuffing text and drawing paths.

In the early phase, nearly all the effort went into stuffing the right text. Engineers polished prompts, inserted examples, prescribed formats; RAG pipelines worked hard to retrieve documents and push them into the context. Part 01 covered that whole stretch: from prompt engineering and Chain-of-Thought to RAG, the core question stayed the same, namely which tokens to lay in front of the model so that its next prediction is more useful. The LLM was a passive reading machine: it took a prepared context, produced an answer, and stopped.

At the end of 2022, ReAct pulled the LLM out of that comfort zone. The output was no longer just a final answer. The model was allowed to emit an Action, receive an Observation, and then choose its next step from a history that had just grown longer. For the first time, the machine did more than read what others had stuffed into it; it picked up a pen and started drawing the path for the very process it was running in.

But as Part 02 showed, that pen still wrote prose. An action had to be written out as a string, cut out with a regex, matched against a tool name, and only then executed. One misplaced colon, one mistyped argument, or one polite apology squeezed into the middle, and the protocol broke. The tool set might be finite, but the door between the model and the runtime opened onto the whole space of strings.

On 13 June 2023, OpenAI released function calling.1 Developers could declare functions and get back the function name and its arguments in a JSON object, instead of hunting for the word Action: inside a paragraph. For the first time, a proposed tool call had its own channel.

A dedicated channel made structured output more reliable, but it did not guarantee that every generation matched the schema. The two decisive pieces arrived in 2024. On the architecture side, LangGraph (January 2024) made cycles, state and checkpoints first-class citizens of the agent runtime.2 On the model side, Structured Outputs (August 2024) with the strict: true flag pushed schema adherence all the way down into decoding.3

From then on, three things moved house. Control flow left the conversation scroll and became source code. State left the text string and became typed data. Tool calls left prose and went through a channel of their own.

A timeline from 2020 to 2025 split into two phases, stuffing text and drawing paths, marking few-shot and RAG, ReAct, function calling, LangGraph, Structured Outputs and Workflow/Agent; below it three rows show control flow, state and tool calls moving from a strip of prose to a gridded block
Figure 1. Three moves: control flow, state and tool calls each leave prose for a structure held by code.

This is the turn that matters most. The model still does the reasoning, and the system has not retreated to rigid if-else pipelines. What changed is where the decision sits. The model may still propose the next step, but it has lost, for good, the implicit right to turn every string it produces into a real action.

And correct syntax does not guarantee correct logic. A JSON object with every bracket in place that calls delete_database when the task needs query_database does not make the system any smarter. It only lets the mistake slip through the parser more smoothly.

So a new boundary has to be built, and it has to live in code:

The model proposes. The runtime authorizes. The tool executes. The state records. The verifier checks. The audit trail keeps the evidence.

That line closed Part 02. This part picks up from there and names the layer of code that holds that boundary: the harness. Harness engineering does not take the model's reasoning away. It simply refuses to let the model run across the map and, at the same time, draw new roads that nobody controls.

What is a harness?

Function calling opens a structured channel for the model to propose tool calls. But a message channel is not yet a control system. Who decides what the model gets to see? Who frames the actions it may propose? Who checks a proposal before it fires? Who keeps the process alive through a crash, and who keeps the evidence when something goes wrong?

The layer of code that answers these questions goes by a plain old software word: harness.

In the physical world, a harness is the belt a climber wears. It does not generate any pull. It only transmits force in a chosen direction and holds the climber when they slip. In software the idea has been around for decades as the test harness: the code that prepares input, calls the module under test, collects output, measures time, isolates the environment and decides pass or fail.

In agent architecture, a harness does not just wrap one API call. It holds the whole lifecycle of the process the LLM takes part in. The exact boundary shifts from framework to framework, but a real harness carries at least five responsibilities:

  • Context management: decide which part of the state is rendered into the context on each call.
  • Framing the action space: declare the tools or branches the model is allowed to propose.
  • Validation: check a proposal before it is allowed to touch a real tool.
  • Runtime orchestration: own control flow, state, timeouts, retries, checkpoints and interrupts.
  • Auditability: record enough evidence to observe, investigate and recover.
The harness drawn as a dashed frame around a loop: θ proposes, the verifier validates, the runtime authorizes and orchestrates, the tool executes, the state records, render builds context and feeds back to θ; a rejected proposal goes to an Error branch; underneath runs an audit trail strip collecting traces from each stage
Figure 2. The model is one gear in a loop the harness holds. The five responsibilities (orange labels) sit at different stages, and every stage leaves a mark on the audit trail.

The model supplies the reasoning power. The harness decides where that power goes, where it is stopped, and what trace it leaves. The LLM, in the end, is one gear inside the harness, never the whole agent.

A harness is not the name of a library either. LangGraph, LlamaIndex Workflows or Temporal are only ingredients. A hand-written while loop with a closed tool set, strict schemas, a step limit, clear timeouts and a permission check is still a good harness. Importing a heavyweight framework, on the other hand, does not grant any of those properties automatically. What shapes the architecture is not the library's logo, but which boundaries the code actually enforces.

The five responsibilities finally come down to one question: once the model has emitted its text, what is actually allowed to happen? The six sections below answer it from the inside out: the choice set (I), the model's role (II), the paths (III), memory (IV), time and evidence (V), and finally a mirror held up by the model's own core (VI).

I. LLM-as-router and the closed action set

A router is the component that stands at a fork and picks the next branch. Using an LLM for this is a good idea. A messy complaint email might need to go to accounting, legal or technical support; a language model reads intent better than dozens of keyword-matching if-else blocks.

The naivety of early agents was not in handing the choice to the model. It was in the space the model was allowed to choose from.

Formally, a router maps an input to a finite set of branches:

r:X→N.r: X \to N.

The set NN has to be declared up front and closed. On paper, the original ReAct also had a finite action set: for HotpotQA it was just search, lookup and finish.4 But at execution time the model proposed its actions in prose. The tool set on paper had three elements; the string space the model swam in was all of Σ∗\Sigma^*.

Three tools, infinitely many strings. So the runtime had to solve two problems: does the string just generated contain a command at all, and if so, is that command in the allowed set?

Let Π\Pi be the parser that turns an output τ\tau into an action, or returns ⊥\bot when it fails. A failed routing turn falls into exactly one of three events:

Eε={Π(τ)=⊥}broken syntax,Eω={Π(τ)≠⊥, Π(τ)∉N}well-formed but outside the set,Eη={Π(τ)∈N, Π(τ) wrong for the task}inside the set but wrong.\begin{aligned} E_\varepsilon &= \{\Pi(\tau)=\bot\} &&\text{broken syntax,}\\ E_\omega &= \{\Pi(\tau)\neq\bot,\ \Pi(\tau)\notin N\} &&\text{well-formed but outside the set,}\\ E_\eta &= \{\Pi(\tau)\in N,\ \Pi(\tau)\ \text{wrong for the task}\} &&\text{inside the set but wrong.} \end{aligned}

The three events are disjoint, so their probabilities add up:

P(failure)=ε+ω+η.P(\text{failure}) = \varepsilon + \omega + \eta.

This sum measures only the routing decision. Permission errors, network errors and system crashes belong to other layers, and are not to be hidden inside this equation.

Function calling pushed ε\varepsilon down sharply. Structured Outputs went one decisive step further: if the next branch is declared as an enum, constrained decoding steps in at sampling time and removes every token that cannot lead to a valid value. Production no longer has to hope the model spells the function name right; the choice set is locked at sampling. With the decoder as guarantor, ε≈0\varepsilon \approx 0 and ω≈0\omega \approx 0.

The "approximately" is not false modesty. The Structured Outputs announcement itself names two ways out: the model may return a refusal instead of an object matching the schema, and the output may be cut off if it hits the token limit.3 The harness still needs a branch for both. The difference is that these are now named cases, not a stretch of unpredictable prose.

Still, η\eta is untouched. A schema can force the next field to be review or search, but the schema has no idea whether this email really needs review or search. A perfectly typed function call can still carry a wrong SQL query, a wrong amount, a wrong customer ID.

A cloud labelled Σ* holding every string the model can write passes through the parser Π and splits three ways: ⊥ for the broken-syntax error ε, delete_all for the outside-the-set error ω, and the set N containing search, review and reply where only one choice is right for the task; below, two stacked bars show that prose plus regex has all of ε, ω and η, while a typed channel with an enum shrinks ε and ω to almost nothing and leaves η unchanged
Figure 3. Three disjoint ways a routing turn fails. A typed channel with an enum squeezes ε and ω to nearly zero; η stays put. Bar lengths are illustrative.

So the harness has two separate jobs. First, turn every out-of-set output into a system error code, never letting it drift into a hidden action. Second, keep the right to validate a structurally valid action before it touches a real tool.

The model still gets to choose. What it loses is not its ability to navigate, but the power to invent an option that can be executed.

II. From supreme router to worker node

Closing the choice set only locks the list of routes. The harder question remains: who holds the key that turns a proposal into a real effect?

In a disciplined harness, the LLM is demoted exactly one level: from the driver of the process to a worker node inside it. It receives part of the state, works on it, and emits a structured proposal. That proposal does not execute itself. The runtime judges whether to accept it, which node wakes up next, and where the system stops.

Let ss be the global state. At each node vv:

  • πv(s)\pi_v(s) is a projection that keeps only the fields node vv is allowed to read;
  • renderv\mathrm{render}_v builds the context from that filtered data;
  • parsev\mathrm{parse}_v forces the output into a structured object.

A model call can then be written as:

τv∼Pθ(⋅∣renderv(πv(s))),yv=parsev(τv).\tau_v \sim P_\theta\big(\cdot \mid \mathrm{render}_v(\pi_v(s))\big), \qquad y_v = \mathrm{parse}_v(\tau_v).

This formula also settles a debt from Part 01. Chain-of-Thought, as analysed there, is not state but context: the model writing more into its own conditions. In a harness, that reasoning chain may run as long as it likes, as long as it stays inside τv\tau_v, inside the node. The only thing that leaves the node is yvy_v, a typed object. Reasoning is the node's private business; the graph's edge set is not a place for it to write on.

The state s drawn as a table of fields user_query, documents, draft, approval, retries and errors with only the first two highlighted; the projection π_v carries them into node v, made of render, θ and parse; a dashed line of reasoning coils inside the node; y_v leaves and meets a runtime with a padlock, which only on accept writes to the edge set E, while a direct line from the node to the edge set is crossed out
Figure 4. A worker node reads through a projection, thinks as long as it likes inside, but emits only y_v. Only the runtime moves along the edge set.

At the application layer, the LLM is treated as a function that keeps no state between calls: data goes in, a distribution over outputs comes out. The API provider may optimize with a KV cache or threads, but the state the system needs for recovery and audit must not cling to that hidden memory. It has to live in ss, stored in a database, under the runtime's authority.

In Building effective agents (December 2024), Anthropic named a very clean boundary.5 Workflows are systems where LLMs and tools are orchestrated through predefined code paths. Agents are systems where LLMs dynamically direct their own processes and tool usage.

That is not a black-and-white line but two ends of a control spectrum. Turn the knob toward workflow and you get a network of small nodes branching on static logic: easy to test, rigid. Turn it toward agent and you get a star: the LLM at the core, tools as satellites. But even at the most flexible end, the model does not glide freely over Σ∗\Sigma^*. It may pick tools many times, but the tool set is licensed by code. A dispatcher checks the function name, the schema, the permissions and the maximum step count before anything runs.

A slider from workflow to agent with a knob in the middle; on the left a chain of small nodes joined by fixed arrows, some containing θ; on the right θ sits at the centre of a dashed dispatcher ring with five satellite tools outside it, each exit padlocked, and a proposal to send_email, which is not on the list, crossed out right at the ring
Figure 5. The control spectrum. At both ends the final right to execute belongs to code: concrete paths on the left, the dispatcher ring on the right.

So the historical difference is not that old agents used an open set and new ones use a closed set. ReAct in 2022 had a finite action list too. The evolution is in the enforcement mechanism. The early generation described actions in a prompt and trusted a parser's scissors to cut in the right place. A mature harness locks actions into a typed channel and makes the runtime stamp them before any function is called.

An AI's autonomy is a knob. But wherever you set it, the final right to execute has to belong to the code.

III. The finite graph: the constraint of topology

The open-ended while loop of older systems can be replaced by a state graph. But calling every such graph a DAG (directed acyclic graph) quietly erases the most important feature of an agent.

Agents exist to loop: try a step, observe the result, revise the plan, try again. The LangGraph announcement states the goal plainly: to make it easier to build cyclical graphs, which agent runtimes often need.2 A straight pipeline without loops is only a special case.

A harness does not forbid cycles. It only requires that a cycle has a name, a state, and a limit.

A basic topology has three kinds of parts:

  • Node: a unit of computation, which can be an LLM call, an API call or a piece of Python.
  • Edge: a route that is allowed from one node to another.
  • Conditional edge: a branching route decided by code, based on the state or on an LLM proposal that has already been validated.

At node vtv_t, the model may propose a destination yty_t. The runtime applies a strict transition rule:

vt+1={yt,if yt∈succ(vt),Error(vt,yt),if yt∉succ(vt).v_{t+1} = \begin{cases} y_t, & \text{if } y_t \in \mathrm{succ}(v_t),\\[2pt] \mathrm{Error}(v_t, y_t), & \text{if } y_t \notin \mathrm{succ}(v_t). \end{cases}

Error\mathrm{Error} here is not a scolding sentence in English stuffed back into the context, like the apology loop of Part 02. It is a real branch defined by the programmer: log the incident, trigger a fallback, or cancel the run.

Looking only at control flow, the system resembles a finite-state machine (FSM). But the data inside the state can grow without bound, a list of documents or a JSON string, so the more accurate name is an extended finite-state machine (EFSM): finite control flow, extended data, and edges guarded by conditions on that data.

Cycles do not break the finiteness of the control set; they only let a run pass through the same node many times. An agent loop is then no longer a conversation that keeps extending itself. It becomes an observable trajectory: which node are we at, how many times have we looped, how has the state changed, which condition allows us to go back?

To stop infinite loops, the runtime caps the number of transitions; in LangGraph that is recursion_limit. But a step limit is not a time limit. A single node can still hang the whole run when the network stalls. For the harness to really hold the process, it also needs wall-clock timeouts, cancellation, and per-node resource limits.

The power of the dominator

The biggest benefit of topology is that it moves safety properties from instructions into structure. Suppose the node Drafting_Email has only two outgoing edges:

Drafting_Email -> Human_Review
Drafting_Email -> Search_Docs

Even if the LLM returns a flawless JSON object proposing Send_Email, the runtime throws it away at once, because that edge is not in succ(Drafting_Email)\mathrm{succ}(\texttt{Drafting\_Email}). There is no need to spend tokens telling the model "don't send before approval". The graph's structure makes the unapproved path simply not exist.

In graph-theory terms: Human_Review dominates Send_Email. Every path from the start to sending the email must pass through the human review point.6

But it needs saying in full: a dominator only guarantees that the run passes through Human_Review, not that the human agreed. For the second guarantee, the edge Human_Review -> Send_Email must be a conditional edge guarded by approval_status == "approved", and that field may be written only by the review node itself, a rule that section IV hands to a reducer. Topology takes care of the path; the edge condition takes care of the content; write permissions take care of who may change that content.

A graph of Classify, Drafting_Email, Search_Docs, Human_Review and Send_Email; Drafting_Email and Search_Docs link back and forth in a cycle; Human_Review is highlighted and has a rejected edge back to Drafting_Email; only the edge from Human_Review to Send_Email exists and it is guarded by approval == approved; a dashed proposal straight from Drafting_Email to Send_Email is crossed out, with a note that the JSON next Send_Email is not in succ and becomes an Error; a step counter in the corner reads 7 of 25
Figure 6. Human_Review dominates Send_Email. A proposal that jumps the fence, even as perfect JSON, has no edge to travel on; the only valid edge is also guarded by a condition on the state.

When a safety rule lives only in the prompt, we are flipping a coin on whether the model obeys. When it lives in the topology, we can check it on the graph before the system ever runs.

Of course, that proof holds only at the application layer. If code inside a node opens a socket and sends the email itself, the topology is useless. That is the sandbox's job, and the subject of Part 04.

IV. State, reducers and the end of the unsigned tape

The chaos of early agents was not only about where they went, but also about how they "remembered".

The user request, the thoughts, tool results, runtime errors, even the apologies, all got pushed into one messages array. That is the unsigned tape from Part 02: growing after every loop, every kind of data on one channel, every author writing on the same surface, and the update rules (who may change what) resting mostly on unspoken agreement.

Saying ReAct "had no state" is unfair. It had state, but a state flattened into one line of conversation history. The origin of each piece of data and the right to overwrite it were not protected by the runtime.

The harness retires that tape. State becomes a typed object:

class AgentState(TypedDict):
    user_query: str
    documents: Annotated[list[DocumentRef], merge_dedup]
    draft: str
    approval_status: Literal["pending", "approved", "rejected"]

When a node finishes, it does not return the whole state, only an update Δt\Delta_t. For each field kk, a reducer RkR_k, pure code, decides how to merge the new value:

st+1[k]=Rk(st[k], Δt[k]).s_{t+1}[k] = R_k\big(s_t[k],\ \Delta_t[k]\big).

A reducer can overwrite, append, deduplicate, increment a counter, or flatly raise an error if an update breaks an invariant, for example a node other than Human_Review trying to write approval_status.

So when the model is called at the Writer node, the projection πWriter(s)\pi_{\text{Writer}}(s) takes only user_query and documents. The three timeouts in earlier nodes never appear in its context.

On the left a messages array of cells user, thought, action, observation, error timeout, sorry, repeating in a jumble, labelled one channel, no signatures; an arrow leads right to AgentState with fields user_query, documents, draft, approval_status and retries, each with its own reducer such as overwrite, append and dedupe, only the review node writes, accumulate; an update Δ from a node enters through a reducer; the projection π_Writer takes the first two fields into a context, labelled a view, not a store, with no trace of the three timeouts
Figure 7. From the unsigned tape to typed state: every field has a name, a merge rule and a set of allowed writers. Context is just a view built for one node.

This is where context engineering reaches its mature form: from stuffing text by hand to stuffing text by architecture.

hv=renderv(πv(s)).h_v = \mathrm{render}_v\big(\pi_v(s)\big).

Put this formula next to Pθ(xt+1∣ht)P_\theta(x_{t+1}\mid h_t) from Part 01. The model still only sees hh; nothing has changed on its side. What changed is that hh is no longer produced by someone typing a prompt or by a conversation writing itself, but by a typed function built from the state. The context is no longer a dumping ground for everything the system remembers. It is a temporary view, built for one node at one moment.

Typed state is not a silver bullet either. Don't treat a TypeError as a firewall. A malicious URL is still a valid str and passes every type check. Reducers govern how state changes; validators govern the shape of data; policy governs permissions; the sandbox governs execution limits. The four layers work together, and none replaces another.

But at least the tape now has signatures: fields have names, updates have origins, merges have rules, and the context is only a view that serves reasoning.

V. The runtime keeps time, failure and evidence

Once control flow lives in the graph and durable data lives in the state, the runtime carries the hard part that remains: time, failure, human intervention, and recovery.

Interrupts and checkpoints

Human-in-the-loop cannot just be an input() call hanging a console. If the server goes down, the run goes with it. In a harness, a waiting point needs two mechanisms: an interrupt to stop at a defined boundary, and a checkpoint to save the state and the position in the graph to a database. The reviewer clicks approve three days later, and the system resumes from the checkpoint. LangGraph, for instance, saves a checkpoint at every super-step, that is, after each round of nodes finishes and hands over to the next.7

But resuming opens a familiar distributed-systems problem: did the node that crashed already cause an external effect? If the node that charges a card finished but crashed before the next checkpoint, will it charge again on resume? That is why APIs with side effects need idempotency: calling again with the same key has the same effect as calling once. Stripe does this with an idempotency key attached to each request.8 The harness cannot solve idempotency on its own; it can only pass the key down, and the guarantee has to be designed at the service level.

Goodbye, prompt retry

When a tool call fails because the network is congested, pasting the stack trace into the prompt and asking the LLM to "try again" is the worst option. It turns an infrastructure error into a reading-comprehension test for the model, exactly the apology loop of Part 02. A decent harness has a retry budget, exponential backoff, a split between retryable and non-retryable errors, and a circuit breaker. Whether to retry is a branch of the runtime, not of the model.

Checkpoint ≠ audit trail

The two are often lumped together, but they answer different questions. A checkpoint answers "where do we resume?": it keeps only the state at super-step boundaries. An audit trail answers "who proposed what, what did the runtime decide, what did the tool actually do?": it keeps every event, including those that fall between two checkpoints, like the crashed card charge above.

Above, a timeline with three checkpoints s1, s2 and s3 at super-step boundaries, then a charge_card node runs and crashes before s4 exists; an arrow resumes from s3 and runs charge_card again, asking charged twice and noting it needs an idempotency key; below, an audit trail table with timestamps, actors θ@Writer, runtime and tool, and rows proposes charge_card, policy allow, call 1 with a key, timeout 5.28 s then retry 2 of 3; a clock sits in the corner
Figure 8. A checkpoint tells you where to resume; the audit trail tells you what happened, including between two checkpoints. The clock belongs to the runtime.

The clock has to sit in the runtime. The model does not know on its own that a tool took five seconds, or that an API call is on its third attempt. Those facts must not be left for the model to guess from prose; the runtime has to write them into the audit log.

VI. A mirror from the model's core

At this point someone will object: the harness is just scaffolding for models that are still immature. Once models are strong enough, they will find their own way, with no need for graphs or external rules.

To answer, look inside the strongest models themselves, for example the Mixture of Experts (MoE) architecture of DeepSeek-V3. A caveat first: the harness did not come from MoE, and a gating network is not an agent. This is a similarity in design, not a causal argument.

Each MoE layer in DeepSeek-V3 has one shared expert and 256 routed experts.9 When a token comes in, it does not pass through all 256. The gating network computes an affinity score between the token and each expert:

si,t=σ(ut⊤ei),s_{i,t} = \sigma\big(\mathbf{u}_t^{\top}\mathbf{e}_i\big),

then applies a TopK rule to send the token to only 8 experts, with each token reaching at most 4 machine nodes. What stands out is how DeepSeek-V3 balances load: a bias bib_i is added to the score, but it is used only to choose which experts are called; the weight multiplied into each expert's output still comes from the original score si,ts_{i,t}.9 The score is the model's inclination; the power to decide who gets called belongs to a selection rule layered on top of it.

Two columns side by side. On the left, MoE gating: the formula s_i equals sigma of u transposed times e_i, a row of score bars for the experts with the eight tallest filled, passing through a selection-rule box of plus b_i, TopK equals 8, at most 4 nodes, out to a row of indices in 1 to 256 with eight cells filled. On the right, the harness: θ proposes, passing through a selection-rule box of succ(v), policy, max steps, out to an index in N made of draft, search and review with review chosen. At the bottom: neither side writes the target out as text and waits for a regex
Figure 9. The same pattern: scores, then a selection rule, then an index in a closed set. At the tensor level, nobody lets the model spell out an expert's name.

Place the gating network next to the harness and three similarities appear:

  • A closed target set. The MoE router picks an index in {1,…,256}\{1,\dots,256\}. It can route badly, but it cannot "hallucinate" a 257th expert.
  • Rules on top of scores. The affinity score expresses an inclination, while TopK, the node limit and the bias bib_i form a selection rule that stands above the raw score. The harness works the same way: the LLM scores or proposes, and the graph's code keeps the right to apply policy and pick the path.
  • No prose in between. Routing happens on tensors and returns an index. Nobody prints the expert's name to a screen and waits for a regex to cut it out. The event ε\varepsilon simply does not exist at this interface.

The lesson is a general pattern: a probabilistic component may score and propose within a space of options, but a finite interface and a deterministic set of rules must keep the right to turn scores into actions.

At the weight level, researchers never let the model write out an expert's name and hope a parser understands it. There is no reason that, at the application layer, where actions can send email, change a database or run code, we should accept a lower standard than that.

The door to the basement

The path of AI architecture is not a story of prompts, agents and workflows replacing one another. At each stage, we take a bit more responsibility away from the token string and hand it to the rigour of software engineering.

Prompt engineering organizes the input. ReAct lets the model help propose the next step. Function calling separates commands from prose. Typed state, graph runtimes and Structured Outputs turn loose conventions into enforceable structure.

The harness crystallizes that transformation:

  • The schema frames the proposal.
  • The dispatcher closes the action set.
  • The graph closes the set of paths.
  • The reducer closes the rules for updating state.
  • The runtime keeps time, checkpoints and retries.
  • The audit log keeps the evidence.

The harness does not make the model any less intelligent. It draws a clear line where intelligence is allowed to become action. The model can still be the brightest mind in the system, but it is no longer the one holding supreme power.

With a harness, ε\varepsilon and ω\omega are stopped by the runtime. But η\eta is still there.

A valid node can still run an SQL statement that deletes the wrong data. A piece of Python written by an LLM can still quietly open a network connection, read /etc/shadow or download malware. The topology may proudly declare "I have no edge to Send_Email", yet it is powerless if code running inside a node opens a socket and sends the email itself.

From here on, the architecture cannot defend only at the application layer. It has to go down to the operating system.

Part 04 goes down there: sandboxes, microVMs and the zero-trust principle, where AI-written code runs in its own virtual machine such as Firecracker, with its own kernel and with network and file system cut to the bare minimum, so that even if it wanted to, it could not reach the real host.

The model proposes the path. The harness decides which paths are allowed to exist. The runtime decides which step runs. And the sandbox decides how far that step can reach.

  1. OpenAI, Function calling and other API updates, 13 June 2023. ↩
  2. LangChain, LangGraph, 17 January 2024: LangGraph was built to "better enable creation of cyclical graphs, often needed for agent runtimes". ↩
  3. OpenAI, Introducing Structured Outputs in the API, 6 August 2024. The post singles out model refusals (the refusal field) and output cut off at the token limit. ↩
  4. Shunyu Yao et al., ReAct: Synergizing Reasoning and Acting in Language Models, ICLR 2023. The HotpotQA action space is in section 3.1. ↩
  5. Anthropic, Building effective agents, 19 December 2024: "Workflows are systems where LLMs and tools are orchestrated through predefined code paths. Agents, on the other hand, are systems where LLMs dynamically direct their own processes and tool usage". ↩
  6. On dominators and how to compute the dominator tree, see Keith D. Cooper, Timothy J. Harvey, Ken Kennedy, A Simple, Fast Dominance Algorithm, Rice University, 2001. ↩
  7. LangChain, LangGraph: Checkpointers. ↩
  8. Stripe, Idempotent requests. ↩
  9. DeepSeek-AI, DeepSeek-V3 Technical Report, 2024, section 2.1.2: "the bias term is only used for routing. The gating value, which will be multiplied with the FFN output, is still derived from the original affinity score". ↩

— espresso.hoagg

↑ Back to top