Topic 59

Giving an Agent a Computer

Code Execution

Every Monday morning the finance team reconciles marketplace refunds against seller balances. Last week that meant 6,412 refund lines from the payment provider's export against 2,000 seller ledger rows, looking for the sellers where the two do not agree. There is no tool for that job, and there cannot be one, because nobody can write down in advance which of the eleven refund reason codes are chargeable to a seller, how a partial refund on a two-item order splits, or what happens when finance changes the fee schedule in October.

Code execution is the capability for work nobody could enumerate. You give the agent a runtime and the data, it writes a short program, your code runs that program somewhere controlled and hands back its output and whatever files it produced. Twenty tools for filtering, joining, summing and formatting collapse into one, and in exchange you have handed a probabilistic component a general-purpose machine. Both halves of that sentence are true, and this chapter exists because of the second one.

Why It Collapses the Surface

Chapter 3 established that a tool surface has a size cost: every tool spends schema tokens on every turn and adds one more thing to choose wrongly, and selection accuracy falls as the list grows. Now try to serve reconciliation from tools. You need something to filter by reason code, something to group by seller, something to join two datasets on a key, something to apply the fee schedule, something to diff two columns and something to write a spreadsheet. That is six tools before the first real question, and the first real question — which of these discrepancies predate the fee change? — needs a seventh that nobody thought of.

A runtime answers all of them with the same capability. The agent reads the column names, writes fifteen lines, runs them, and looks at what came back. When finance asks the follow-up, it writes fifteen different lines. The tool-count problem does not get better for this class of work; it disappears, because the surface stops being a list of verbs and becomes one verb plus a language.

The fifteen lines that replaced a tool surface nobody could have specified
import pandas as pd

CHARGEABLE = {"damaged", "not_as_described", "missing_parts", "late"}

refunds = pd.read_csv("/data/refunds_2026-08-10.csv")        # 6,412 rows
ledger  = pd.read_csv("/data/seller_ledger_2026-08-10.csv")  # 2,000 rows

billed = refunds[refunds.reason_code.isin(CHARGEABLE)]
by_seller = billed.groupby("seller_id").amount_cents.sum()

j = ledger.join(by_seller, on="seller_id", how="left").fillna(0)
j["delta_cents"] = j.refund_debits_cents - j.amount_cents

out = j[j.delta_cents != 0]
out.to_csv("/out/discrepancies.csv", index=False)
print(len(out), out.delta_cents.abs().sum())

In words: read both files, keep only the refunds whose reason code makes them chargeable to the seller, total those per seller, line the totals up against what the ledger actually debited each seller, and keep the rows where the two numbers differ. Write those rows to a file and print two things — how many sellers disagree and by how much in total. Last Monday that printed 23 and 411862, which is 23 sellers and $4,118.62. Nothing else about the run entered the context window, and that is the second half of the argument, made properly in Topic 61.

The same capability, reviewed twice — and only one of the two reviews has a printable answer
Twenty toolsenumerable · individually authorizable · individually testable
Filtering, grouping, joining, applying the fee schedule, diffing two columns, writing a spreadsheet — six before the first real question, and a seventh nobody thought of for which of these predate the fee change? Each carries a schema on every turn, one more thing to choose wrongly, its own scoped credential, its own ceiling in the dispatcher and its own line in the audit log. The review is a list a security reviewer reads line by line.
One runtimeanything expressible in the language, against anything the box can reach
Fifteen lines replace all six, and next October's question needs fifteen different lines rather than a deploy. The tool-count problem does not improve; it disappears. The review moves — one mount, one network policy, one credential set, one set of resource limits — and nothing in the run has a signature you can test in advance, because the program does not exist until the run.

The Trade

You gain generality and lose the ability to reason about what the agent can do. With nine tools, "what can this agent do" is a list you can print, hand to a security reviewer, and check line by line: each tool has its own scoped credential, its own validation, its own ceiling. With a runtime, the answer is "anything expressible in that language, against anything the box can reach". The capability review from Chapter 12 does not get harder so much as it moves: it stops being a review of nine tool definitions and becomes a review of one mount, one network policy, one credential set and one set of resource limits. Every guarantee you had now comes from the sandbox boundary, and the sandbox is the next topic.

There is a second loss that gets less attention. A tool has a signature and a unit test; the program the model writes has neither, and will be different next Monday for the same job. You cannot test the code, because the code does not exist until the run. What you can test is the boundary it runs inside and the verification applied to what comes out, and if you are not testing both of those you are testing nothing at all.

Verification Beats Trust

The one advantage code execution has over the model's prose is that it produces a checkable artefact. A summary cannot be verified without redoing the work; a CSV can be checked against invariants in milliseconds. So the rule is the Chapter 9 rule applied at a new surface: run it, check the output against something that must be true, and reject rather than believe. A plausible reconciliation is worse than none, because finance will act on it.

Every artefact passes this before anyone sees it
def accept(artefact, refunds, ledger):
    rows = read_csv(artefact)
    billed = refunds[refunds.reason_code.isin(CHARGEABLE)]

    assert rows.seller_id.is_unique                          # no seller twice
    assert set(rows.seller_id) <= set(ledger.seller_id)      # every row is a seller the ledger knows
    assert rows.delta_cents.sum() == (ledger.refund_debits_cents.sum()
                                      - billed.amount_cents.sum())
    assert (rows.currency == "USD").all()

    return rows                        # anything else raises and fails the run

Four invariants, none of which requires understanding the reconciliation: no seller appears twice, every seller named is one the ledger knows, the per-seller differences add up to the difference between the two grand totals, and the currency column is what it should be. Note what is not on that list — whether every chargeable refund line went into the join exactly once — because a 23-row discrepancy file cannot prove it; that check belongs inside the job, on the full join, before the slice is written. Across Sundry's first eleven weekly runs these rejected three artefacts. Twice a join had silently dropped rows because a handful of seller ids carried a trailing space; once an amount column was parsed as text and summed to zero, producing a file that looked immaculate and claimed every seller was in perfect balance. All three runs failed loudly instead of shipping, which is the entire point.

Where It Fits at Sundry

Two jobs, both offline. The weekly reconciliation is one. The other is bulk analysis over Sundry's own operational data: the failure-class distribution across 400 failed tickets in Chapter 8 was produced this way, by an agent that read the run records, wrote a grouping script and returned a table. Both share a shape — computation over data the agent is already allowed to read, with a file at the end and a human who checks it.

The support agent does not get a runtime, and adding one is not on the roadmap. Money-moving actions have exactly the right shape as tools: issue_refund takes an amount, the dispatcher compares that amount against the $150 ceiling, and the approval design in Chapter 12 rests entirely on the action being a named call with a number in it. A program that computes a refund and then moves the money has no ceiling anyone can enforce, no argument anyone can validate, and no line in the audit log that a reviewer can read. Reserve tools for consequence; reserve code for computation.

Cost Profile

In tokens this is cheap, and the arithmetic is worth doing once. Pulling 6,412 refund lines into the context at roughly 40 tokens a row is about 256,000 tokens, which exceeds most budgets outright and, per Chapter 2, gets re-sent on every remaining turn. The sandbox version never puts a row in the context. It puts two file paths in, and reads back 23 lines and two totals. The whole 41-minute run bills around 140,000 input tokens across 34 turns, and the weekly job costs Sundry under two dollars.

In risk it is expensive, which makes this the reverse of nearly every tradeoff in this book. Elsewhere the cheap option is the constrained one: fewer tools, smaller context, tighter ceilings. Here the cheap option is the one that removes your ability to say what the agent can do, and the money you save on tokens is spent on the engineering that draws the box. Budget for the sandbox work at the same time you budget for the capability, because a code-executing agent without a boundary is not a cheaper agent, it is an unpriced one.

Code execution vs a large tool surface

Tools — enumerable, individually authorizable, individually testable. Each one has a schema you wrote, a credential scoped to it, a ceiling enforced in the dispatcher and a line in the audit log. What they cannot do is express anything nobody anticipated, and every new question costs a new tool, a new review and a permanent schema tax on every turn.

Code execution — expresses anything, needs one sandbox instead of twenty permissions, and answers next week's question without a deploy. What it costs is the enumeration: "what can this agent do" stops being a list and becomes a question about the boundary, and nothing in the run has a signature you can test in advance.

Use tools for actions with consequences and ceilings. Use code for computation over data the agent can already read. Most systems want both, and the line between them is not a matter of taste: if a wrong answer costs money or cannot be undone, it belongs in a tool where the ceiling lives.

Common Mistakes
  • Giving code execution to an agent that also holds production credentials — the sandbox is not a boundary if the secrets are inside it, and the first program that reads an environment variable has every permission the process has.
  • Trusting the output without verification — a reconciliation that dropped 400 rows on a bad join prints a clean total, reads as authoritative, and gets forwarded to finance under your team's name.
  • Using code execution for money-moving actions — ceilings, idempotency keys and approval gates live in tools for a reason, and a script that issues refunds has none of the three.
  • Installing packages at runtime because the program asked for one — you have added an unreviewed dependency inside your own boundary, chosen by a model, from a network you should have closed.
Best Practices
  • Pair every code-execution capability with automatic verification of the artefact it produces, and fail the run when an invariant breaks rather than annotating the output.
  • Keep credentials out of the execution environment entirely — the parent process fetches the inputs and performs any privileged action.
  • Reserve tools for consequential actions and code for computation, and write the split down so the next feature request does not cross it unnoticed.
  • Pin the runtime image and its package versions, and rebuild it through review like any other deployed artefact.
Comparable toolsVendor code interpreters the hosted version of this capabilityE2B sandbox runtimes built for agent codeModal ephemeral compute with a Python APIDaytona disposable development environmentsNotebook backends the same execute-and-inspect loop, with a human

Knowledge Check

What does giving an agent a runtime actually replace?

  • The half-dozen computation tools you would otherwise write, plus the ones next week's question needs
  • The action tools that move money, since a program can call the same payment API directly
  • The retrieval layer, because a program can read the policy library from disk far more cheaply
  • The verification step, since a program that runs without error has demonstrated its own correctness

Sundry adds code execution to its reconciliation agent. What did the team give up?

  • The ability to enumerate what the agent can do, since every guarantee now rests on the sandbox boundary
  • The token savings of a tool surface, since generated programs cost far more context than tool schemas
  • Deterministic control flow, which a fixed tool list had preserved by constraining the agent's options
  • The audit trail, because a program's actions cannot be recorded in the run record the way a tool call is

Why must a generated artefact be verified rather than read and believed?

  • A wrong program usually completes without error and produces a file that looks entirely correct
  • Generated programs crash frequently, so the run needs a check to detect the ones that failed
  • A sandboxed program cannot be trusted because the container may corrupt the files it writes
  • The same job produces different code each week, so the output must be compared against the previous run

Why does Sundry's support agent have no code execution, only its nine tools?

  • Its consequential actions need ceilings and approval gates, which only exist on named tool calls
  • Running a program in a container adds seconds of latency, which the nine-second first-response target cannot absorb
  • The model is not reliable enough to write correct programs against Sundry's order schema
  • Code execution costs far more per ticket than tool calls, which the cost-per-resolution target rules out

You got correct