The Two-Halves Agent Architecture
Prerequisite: Lesson 27 — The wrong mental model, where we established that a framework's structural guarantees (ticket ✓, manifest ✓) say nothing about whether the LLM's output was true. After this lesson, you can: decide, for any agent decision with real consequences, whether it belongs in a prompt or in deterministic code — and explain in one sentence why that split is a design feature, not a workaround for a model that isn't smart enough yet.
Here's the question this lesson answers: if you ask an LLM nicely not to book a table for more than eight people, and it complies 95% of the time, is that a reliability problem you fix with a better prompt — or a different kind of problem entirely?
It's a different kind of problem. And the 5% is not a rounding error you engineer away. It's your runway, your customer relationship, or your regulatory exposure, depending on what the agent is doing when it fails to comply.
Two kinds of problem, two kinds of tool
sovereign-agent splits every scenario into two halves, and the split isn't cosmetic.
The loop half is good at open-ended problems — "figure out how to do this thing." It's an LLM in a ReAct-style loop with tools, free-form reasoning, the kind of work that's genuinely hard to specify in advance: research, analysis, creative synthesis.
The structured half is good at high-stakes constraints — "if deposit is over £300, escalate; otherwise confirm." It's deterministic Python. Rules. No model gets to decide whether the constraint applies. It just does, every time, the same way.
Most real problems need both halves, because most real problems have an open-ended part (find the right pub) and a part where being wrong has consequences (don't overbook past capacity). Here's the split in the actual code:
# Loop half — LLM in a ReAct loop
loop = LoopHalf(
planner=DefaultPlanner(model="Qwen3-Next-80B-Thinking", ...),
executor=DefaultExecutor(model="Qwen3-235B-Instruct", tools=...),
)
result = await loop.run(session, {"task": task})
# Structured half — deterministic rules
structured = StructuredHalf(rules=[
Rule(name="oversize_party",
condition=lambda d: d["party"] > PARTY_CAP,
action=lambda d: {"escalated": True, "reason": "over cap"},
escalate_if=lambda d: True),
Rule(name="confirm_under_cap",
condition=lambda d: d["party"] > 0 and d.get("pub_id"),
action=commit_booking),
])The loop decides what to try. The structured half decides what's allowed. Neither one can be talked into doing the other's job — the loop half has no PARTY_CAP check to argue with, and the structured half has no language model to persuade.
The pub-booking example, end to end
This is a real scenario sovereign-agent ships and runs against a real LLM, not a diagram-only illustration. Someone asks: "Find me a pub near Haymarket, open now, for 4 people at 19:30."
- Loop half. The LLM calls
pub_search, thenpub_availability, and identifies The Haymarket Tap as the candidate. - Handoff. The loop half writes
ipc/handoff_to_structured.jsonand stops. It has done its job — found a candidate — and now hands off. - Structured half. It takes the candidate booking and checks it against a rule, not against the LLM's opinion of whether four people is reasonable.
# party=4, cap=8
# Rule "confirm_under_cap" fires
d = {"party": 4, "pub_id": "haymarket_tap"}
commit_booking(d)
# → writes workspace/booking.md
# → outcome: committedSame scenario. Same code path. Two different outcomes, and the thing that decided between them was a deterministic condition — d["party"] > PARTY_CAP — not the LLM's read of the situation on that particular run.
Why not just prompt for the rule?
Because prompts are probabilistic and rules are not, and the gap between those two isn't small once you look at what it costs.
Asking the model nicely — "please don't book parties over 8" — gets you compliance somewhere around 95% of the time. Treat that as illustrative, not a measured figure from a specific eval: the exact number will move with model, prompt wording, and how hard the constraint is to misread, and this lesson isn't claiming to have benchmarked it. The point it's standing in for doesn't depend on the decimal — any compliance rate short of 100% means the failure mode below is real, whether the true number for your model and prompt is 90% or 99%. The other few percent, whatever it turns out to be, is not evenly distributed noise; it's the run where the LLM decided a slight overage was fine, or misread the number, or just didn't apply the constraint that time. Writing if party > 8: escalate gets you 100.000% compliance, every time, because it isn't asking — it's checking.
In a teaching system, that difference barely registers. In production, the 5% is everything: your runway if the booking commits your business to a liability you didn't sign up for, your relationship with the customer if the wrong thing gets confirmed, your regulatory exposure if the constraint you skipped was one you were legally required to enforce.
Structure is a feature, not a limitation. Reaching for a deterministic second half isn't an admission that the model isn't good enough — it's a recognition that some decisions should never have been the model's to make in the first place.
Deciding which half a decision belongs in
Once you accept the split exists, the next question is practical: given some new piece of logic in a scenario you're building, which half does it belong in? The test isn't "is this hard" or "is this simple" — plenty of open-ended work is genuinely hard, and plenty of rules are one line. The test is what happens if the model gets it wrong.
If getting it wrong means a slightly worse answer — a research summary that missed a paper, a pub recommendation that wasn't quite the best fit — that's loop-half territory. The cost of being wrong is a redo, and a redo is cheap. If getting it wrong means a real-world commitment you can't take back — a booking made against a constraint you needed enforced, a refund issued past a limit, an email sent to the wrong address — that's structured-half territory, because the cost of being wrong isn't a redo, it's a consequence.
Put differently: the loop half optimizes for good answers most of the time. The structured half exists because "most of the time" isn't a phrase you can put in front of a compliance requirement, a financial limit, or anything else where the exception is the whole reason the rule exists.
What to carry forward
The pub-booking rule you just saw is one deterministic decision inside one scenario. It's one instance of a pattern that runs through sovereign-agent's whole architecture: eight decisions, each one removing a class of bug rather than prompting the model to avoid it.
The eight architectural decisions behind sovereign-agent, each with the pain point that forced it, the naive approach that failed, and the tradeoff it costs.
Reply here and it goes straight to Rod. Same as replying to one of his emails.