June 18, 2026
Controlled Autonomy: Build Your Agent a Leash, Not a Cage
There are two ways to build an agent, and both of them fail.
The first is the cage. You write the whole thing as branching logic — a long switch statement, a thicket of if-else — that scripts every move in advance. It's predictable, which feels safe. It's also unmaintainable the moment the problem gets real: every new capability means surgery on a monolith nobody can fully read, and the agent can only ever do exactly what you anticipated. Anything you didn't foresee, it can't handle.
The second is total freedom. You hand the model a goal and a pile of tools and say "figure it out." Sometimes it's magic. Often it wanders — chases a dead end, re-derives something it already knew, makes a confident wrong turn three steps deep — and because nothing constrains it, a single bad decision can take down the entire run. Impressive in a demo, terrifying in production.
The pattern that actually ships lives between them. We call it controlled autonomy, and the metaphor we keep coming back to is a dog on a leash. The dog can sniff wherever it likes — it has real freedom in the moment. But it cannot bolt into the road, because the leash defines the space. You get exploration without catastrophe.
A fixed pipeline, with room to think inside it
Concretely, controlled autonomy means two things held in tension.
The rails are fixed. The overall shape of the process is decided by you, in advance, and the agent cannot deviate from it. A database agent, for example, always moves through the same backbone: understand the request, find the relevant data objects, assemble their context, generate a query, execute it, and present the result. That order is not the agent's to renegotiate. You know what the process looks like, so you encode it — and the agent doesn't waste time (or risk the whole task) reinventing a workflow you already understand.
The pockets are autonomous. Inside each stage, the agent makes real decisions. How many tables are actually relevant here? Is this result suspicious enough to retry? Do I have enough to answer, or should I probe for one more sample? Should I back up a step because the evidence says I went wrong? These are genuine judgment calls, and this is where you want the model's intelligence spent — not on inventing the pipeline, but on navigating it.
The art is choosing the boundary well. Too many rails and you're back in the cage. Too few and you're back in the forest. Get it right and you have a system that's fast (it doesn't rethink its own structure every run), safe (a bad call is contained to one stage), and flexible (it adapts where adaptation actually matters).
The clean way to build the rails
The cage fails partly because of how it's usually coded: one giant control structure that grows a new branch every time the agent learns a trick. The fix is a small, old idea — a finite state machine — and it's worth knowing because it's what makes controlled autonomy maintainable.
Instead of one sprawling switch, you define a set of named states — SplitInput, ResolveObjects, GenerateQuery, Execute, Respond — and give each one a handler: a small, self-contained function that does its work and returns the next state. A dictionary maps each state to its handler. The engine driving the whole thing is almost embarrassingly simple:
Start in the first state. Look up its handler, run it, take the next state it hands back. Repeat until you land on a state marked terminal. That's the entire control loop.
The payoff is everything the cage lacked:
- It's testable. Each handler is a function with a clear input and a clear output. You can unit-test "given this context, ResolveObjects returns GenerateQuery" in isolation, with no need to spin up the whole agent.
- It's extensible. A new capability is a new state: add it to the enum, write one handler, add one dictionary entry. You never touch the main loop. The thing that made the cage terrifying to change becomes a one-file addition.
- It's legible. Anyone can read the list of states and the transitions between them and understand what the agent does. The logic isn't smeared across a thousand lines — it's a map.
This is also where the autonomy lives without making a mess. A handler is free to call the model, spawn a sub-task, retry, or decide to send the flow backward — Execute can return GenerateQuery when a result looks wrong, creating a self-correcting loop. The freedom is real, but it's expressed as transitions between well-defined states, not as unconstrained wandering.
When the line becomes a graph
A simple state machine is a line: one state at a time, marching forward (with the occasional loop back). Real work isn't always a line. A question like "compare headcount in Prague and Brno" naturally splits into two independent sub-questions that have no reason to wait for each other.
So the model generalizes. The moment your agent runs several states concurrently — forking into parallel sub-tasks and later joining their results — it has, strictly speaking, stopped being a simple state machine and become something richer: a graph of tasks with branches that run in parallel and synchronization points where they reconverge. Independent branches execute at once; a join waits for all of them before the flow continues.
The theory has precise names for this — concurrent state machines are formally Petri nets, the structure is a directed acyclic graph — but here's a piece of practical advice worth more than the vocabulary: when you explain this to a client, call it "agentic orchestration." People have an intuition for an agent that delegates and parallelizes. Almost nobody wants a lecture on Petri nets. Use the precise terms in your design docs; use the legible ones in the room.
Why the fixed part is the valuable part
It's easy to think the autonomy is the clever bit and the pipeline is just scaffolding. It's the other way around. The fixed pipeline is where your hard-won process knowledge is stored — every "we learned to always check the lookup before generating the query" is baked into the order of the states. That's an asset. It's the difference between an agent that benefits from everything your team has figured out and one that has to rediscover it, expensively and unreliably, on every single request.
Autonomy is what lets the agent handle the cases you didn't script. The pipeline is what stops that autonomy from becoming chaos. Build the rails deliberately, place the autonomous pockets where judgment genuinely pays off, and keep the whole thing as a clean state machine you can test and extend. That's not a compromise between control and intelligence. It's how you get both.
Wrestling with an agent that's either too rigid to be useful or too loose to trust? Mapping out the right pipeline — and the right pockets of autonomy inside it — is the first thing we do on a build. Let's scope yours.
