LLMs as translation layers, not autonomous agents
Draft- engineering
- llm
Every reliable thing I've shipped with a language model has the same shape, and it isn't the shape the industry is selling. There's a known input on one side, a known consumer on the other, and the model in the middle doing one bounded, lossy translation between them. Not deciding what to do. Not choosing its own steps. Translating.
The thing being sold is the opposite: the agent. Give a model a goal, a bag of tools, and a loop, and let it work out the rest. It's a genuinely seductive pitch—autonomy, generality, the system that figures things out on its own. I've come to think it's also, for most production work, the wrong default. Autonomy is where the reliability goes to die.
Agents promise autonomy and deliver debugging
Here's the problem with the agent framing, stated plainly. An agent's behavior is a trajectory—a sequence of decisions, each conditioned on the last, each capable of being slightly wrong. When something goes wrong at step seven, the cause might be at step seven, or it might be a small misreading at step two that only became load-bearing four steps later. You're not debugging a function; you're debugging a path through a space you can't enumerate. And the errors don't just occur, they compound: a step that's 90% reliable, run seven times in a row, is a coin flip.
You also can't test it, not the way you test software. What's the assertion? The output of an autonomous agent isn't a value you compare against an expected value; it's a story about how it got somewhere. The moment you try to pin the behavior down—"given this input, always do exactly these things"—you've admitted the autonomy was never the point, and you'd have been better off writing the steps yourself.
None of this makes agents useless. It makes autonomy a cost you pay, not a feature you get for free—and most problems don't need it.
Translation layers: bounded input, bounded output, testable
The alternative is to give the model a much smaller and much more honest job. Take a bounded input. Produce a bounded output. Hand it to something that already knows what to do with it.
This is the shape of everything I've actually shipped. A Cypress suite throws a stack trace—unstructured text, no two alike. A database wants rows with typed fields: error class, cause, the selectors involved. The model sits between them and does the one thing neither side can: read the prose and fill in the schema. Input bounded (one failure). Output bounded (a fixed set of fields, enforced by function-calling). The consumer—a database, a query, a human scanning a dashboard—already knew what it wanted; the model just made the messy thing fit.
Same shape, different domain. A go-to-market tool I built kept a pile of structured facts about each company—FDA inspections, citations, recalls, the CFR parts they'd been cited under—and the sales team needed one thing out of all of it: which of four product lines to pitch. The model sat between them and made the judgment neither a database query nor a rule table made cleanly: read the compliance profile, return a single product classification with its reasoning. Bounded input (one company's record), bounded output (one of four categories). The consumer—the sales workflow—already knew what to do with a category; the model just turned a heap of regulatory facts into one. It classified the company and stopped; it didn't decide what happened next.
The difference from an agent isn't subtle. In each case the model's entire contribution is a pure function: input in, structured output out, no side effects, no memory, no next step it gets to choose. And a pure function is the most testable thing in software. You can feed it a hundred real inputs and eyeball the outputs. You can regression-test it when you change the prompt. You can hang a schema validator off it and reject anything malformed before it touches the rest of the system. The blast radius of a wrong answer is exactly one record, flagged at the boundary—not an action already taken three steps ago and now load-bearing.
Keeping the blast radius small
If translation is the pattern, the discipline is all about the two boundaries. A few rules I hold to:
Bound the input. One failure, one document, one record at a time—never "here's the whole system, work out what matters." A narrow input is one you can reason about, and one the model can't wander away from.
Bound the output with a schema. Don't ask for prose you have to parse back out; ask for a filled-in structure, and enforce it. Function-calling turns "please respond in JSON" into a contract checked at the API boundary. The output has a shape you decided, not one the model improvised.
Let the model propose, never act. The output is data, and deterministic code decides what to do with it. The model's hand is never on the lever—it doesn't send the email, merge the PR, or delete the row. It hands a structured suggestion to a system, or a person, that owns the consequences. This is the load-bearing rule: keep the side effects on the deterministic side of the boundary.
Validate, and keep the source. Check the output against the schema, and store the raw input next to the structured result so any row can be traced back to what produced it. Structured output you can't audit is structured output you can't trust.
Treat the model like the flaky API it is. It's a rate-limited network service that fails transiently and occasionally makes things up. Retries, timeouts, a validator, a fallback—the same scaffolding you'd wrap around any unreliable dependency.
What these rules have in common is that every one of them shrinks the model's authority and widens the deterministic system's. The model gets to be brilliant at exactly one thing—reading messy input and producing clean structure—and is trusted with nothing else.
Where autonomy actually earns its place
I don't want to strawman the agent. There are problems where the loop is the point: open-ended exploration, where you genuinely don't know the steps in advance; a coding assistant, where the human is the consumer and reviews every diff; research and triage, where "wander around and report back" is the actual task. In those settings the autonomy buys something real.
The tell is the presence of a human. Autonomous loops work when a person is close enough to catch the compounding errors before they compound—an assistant, not an unattended pipeline. The moment you want the thing to run a thousand times overnight with nobody watching, you want a translation layer, because "nobody watching" plus "emergent trajectory" is how you wake up to a mess you can't reconstruct.
The boring conclusion
The most valuable thing a language model does in production is unglamorous: it turns input one system can't read into output another system can. That's translation, and translation is reliable precisely because it's bounded—a known input, a known consumer, and a pure function in between that you can test, validate, and trust with nothing more than the single job it's good at.
Autonomy is the better story. But the systems I'd put my name on don't ask the model to decide. They ask it to translate, and they keep both hands on the wheel.