2026-06-26 · fundamentals

Give the agent a room, not a building

Week 10 of the fundamentals series: agents and tool use. Agency helps when the path can't be scripted in advance, and it stays safe when the action space is designed so the worst possible run is affordable. The tiered mutation layer and the growth agent that have been teaching me this in production.

Week 10 of the fundamentals series: agents. The word has absorbed so much marketing that I want to start from the mechanical definition, because the mechanics are what you actually ship: an agent is a model in a loop, choosing which tool to call next based on what the last call returned. That's it. The loop is what separates it from week 6's extraction call, and the loop is where both the value and the danger live.

What the sources say

The paper that fixed the pattern is ReAct (Yao et al., 2022): interleave reasoning traces with actions, feed each action's observation back in, and the model can pursue tasks neither pure reasoning nor pure acting handles, because intermediate results steer what happens next. Read alongside Toolformer from week 2, the shape of the field's answer is consistent: models are good at deciding what's needed next and bad at being the infrastructure, so give them tools and a loop.

The papers are about capability. The production question they leave open is the one this week is really about: the model's choices are probabilistic, so over enough iterations the loop will eventually pick a wrong action with full confidence. You can't prompt that away. What you can do is design the action space so a wrong action is affordable.

When agency is actually worth it

The test I use: can I write the control flow in advance? If the task decomposes into known steps, a pipeline beats an agent every time, it's cheaper and it fails legibly. Enacted is pure pipeline: detect, fetch, diff, summarize, gate. The steps never depend on what the model thinks. Making it agentic would add failure modes and remove nothing.

The case for agency is when the path genuinely varies with what's found along the way. My clearest production example is PermitCheck's growth agent, which runs daily at 8 a.m. by cron and does business-development legwork: work the lead-nurture queue, watch for relevant conversations, check what changed in analytics. What needs doing on Tuesday depends on what Monday produced, which is exactly the shape a scripted pipeline can't hold and a loop can.

The build: three tools, three tiers

The week's build is a three-tool agent whose action space is the design artifact. The tools aren't three arbitrary capabilities, they're three tiers of consequence, and the tiering is the part I'd walk through line by line the way I used to walk through Chrome extension manifests on Dev.to: here's each piece and why it's there.

1

Tier 1: read tools, unrestricted

Query the database, fetch a page, check analytics. Wrong reads cost tokens and nothing else, so the loop can explore freely. This is where agents earn their keep.

2

Tier 2: propose tools, dry-run by defaultgate

Anything that would write returns a preview of what would change, validated against a schema, with nothing written. The proposal is inspectable before it's real.

3

Tier 3: apply, confirmation requiredgate

Executing a proposal takes a second call carrying an explicit confirmed flag plus an idempotency key, so a retried or duplicated call can't apply twice.

4

Every call logged, either way

Tool name, input, outcome, success or error, one row per action. The log is how you find out what your agent actually does, as opposed to what you designed it to do.

An action space tiered by consequence

This isn't hypothetical structure, it's lifted from the mutation layer in my fitness app, where every model-initiated write already works this way: mutations are classified by risk tier, and the risky tiers get a dry-run on the first call and require confirmed: true on the second. Two more details from that system carry the real lessons. Every mutation captures a before-image of the row it's about to change, so any write can be reversed by hand with the prior state sitting right there. And every mutation carries a client-generated idempotency key with a unique constraint behind it, so the classic agent failure, a timeout followed by a retry of a call that actually succeeded, becomes a no-op instead of a double-write.

None of that machinery is AI. It's the same engineering you'd want around any untrusted caller, and treating the model as one is the reframe that makes agent safety tractable, because untrusted callers are a problem backend engineering solved a long time ago.

Bounding the blast radius in production

The growth agent shows the same philosophy applied to a scarier action, sending email, where a wrong action leaves the building and can't be rolled back.

So the action space is a room, not a building. The agent doesn't compose arbitrary email to arbitrary addresses. It works a nurture queue of people who submitted a permit check, each with a sequence step from 0 to 4, and each step maps to a template written by me. The agent's actual authority is narrow: decide, per lead per day, whether to advance the sequence. Recipients come from the queue, content comes from templates, and the daily cron caps the loop count structurally, an agent that runs once a day can only be so wrong before the log gets read. Every send lands in a growth log with its outcome, rolled up into a daily summary I can scan in thirty seconds.

Could a maximally creative failure still send a mistimed email to a real person? Yes, and that's the point of the design: the worst run is an awkward follow-up, not a mass send, because the action space makes the mass send inexpressible. I didn't constrain the agent because I distrust the model's intentions. Models don't have intentions. I constrained it because probability mass eventually visits every action you make expressible, so the safety property has to live in what's expressible.

That's the week 10 question to put to any agent design, your own or a vendor's: what's the most expensive action this loop can express, and what did it cost the last time it happened? If the answer to the second half is "it can't have happened yet," the design isn't done.

One useful essay a week. No noise.