Module 6: The Deterministic Shell
Module introduction: the deterministic shell
Why this module exists here
You've spent five modules surrounding the AI component with deterministic code. In module 1 you gave the shape a name —a probabilistic core inside a deterministic shell—; in module 2 you gave it a latency and cost budget; in module 3 you gave it an eval gate; in module 4 you guarded its trust boundary with guardrails; in module 5 you made it resilient to its failures. All that work protects the system from a model that is slow, expensive, unreliable in its content, and fallible. But there's a question that none of those modules fully answered, and it's the most uncomfortable of all: what happens when the model not only says something, but wants to do something? When the LLM's output is no longer a text you display, but an action —refund money, change a price, cancel an order, deactivate an account—, non-determinism stops being a quality problem and becomes a problem of power. A bad text can be filtered. A bad action already happened.
This module installs the thesis that governs that boundary, and it's the most important sentence in the entire guide: the model proposes, the system disposes. The LLM never executes a refund, a charge, or a state change directly. Instead, it proposes an action in a validatable format —for example {action: "refund", amount: 50, order_id: "A-1001"}— and a deterministic layer of business rules approves or rejects it before it touches anything. The model's intelligence enters the system as a suggestion, not as an order. That one-word change —from "the LLM does" to "the LLM proposes and the code disposes"— is what makes it safe to put a non-deterministic component next to money.
From that thesis come three principles that the lessons will develop and measure. The structured action: the model returns a named command with typed fields, not free code nor text for someone to interpret —so there's something concrete to validate and dispatch—. The bounded capabilities: the model can only propose from a closed menu of allowed actions, so that a hallucination can't reach an operation that isn't on the menu. And the small core: the less surface the LLM has over irreversible actions, the smaller the blast radius; the AI-native discipline is to take out of the core everything that can be a rule, and leave it a single responsibility —to propose—.
It's worth marking this module's hard boundary before continuing, because it's twofold. Toward the domain guides: the business logic itself —what Mercado's refund policy should be, what amount is reasonable, how the return window is calculated— is domain content, not content for this guide. Here we treat the containment pattern (that a deterministic layer validates before executing), not the concrete rules of any business. And toward module 4: the output guardrail validated the format and content of what the model says (is it valid JSON?, does it have a forbidden claim?, is it an injection?). Here we validate something different —the proposed action against the business rules before executing it (is this refund within policy?, does this order exist?, has it already been refunded?)—. A guardrail says "this output is valid"; the deterministic shell says "this action can be executed". They are two distinct gates, and this module builds the second.
The case, as throughout the guide, is Mercado, and the protagonist is the support agent: the agent that "wants" to refund. A customer writes "I want my money back", the model proposes a refund action, and the shell validates it against the policy —amount within the limit, order exists, within the window, not already refunded— and executes only if it passes. An enthusiastic model that wants to please the customer by refunding too much, or that hallucinates an order that doesn't exist, or that proposes an absurd amount, always hits the same deterministic wall before touching a cent.
And the promise as always: nothing is asserted from memory, everything is executed. Every simulation runs in Python, with the LLM simulated by a stub that proposes good and bad actions —out of policy, hallucinated, absurd amounts—, never a real API, without keys or network, with fixed data, so the output you see in each "What to expect" block is the literal output of running the code. You can copy it and reproduce it identically.
Three analogies: the teller, the new employee, and the autopilot
The teller who can recommend approving your loan, but it's the bank's system that approves it. You go to a branch to ask for a loan. The teller helps you, sees your case, and can recommend that they approve it —"it looks to me like you qualify, your history looks good"—. But the teller doesn't approve the loan. Even if they want to, even if they like you, even if they're convinced. They press a button that sends your application to the bank's system, and that system, with its hard rules —your score, your current debt, your profile's limit—, decides. The teller proposes; the system disposes. And that separation isn't useless bureaucracy: it's what prevents a too-kind, or too-pressured, or simply mistaken teller from compromising the bank's money. The teller contributes the human judgment and the reading of the case; the system contributes the guarantee that no approval violates the rules. An LLM is exactly that teller: it can read the customer's case and recommend a refund, but it shouldn't approve it; the system does that with its rules.
The new employee who proposes a discount and the manager authorizes it according to the policy. A newly hired employee at a store wants to close a sale and tells the customer "let me see if I can give you a discount". They don't apply the discount on their own: they propose it to the manager, who authorizes or denies it according to the store's policy —up to 10% without special permission, nothing below cost, not combinable with other promotions—. The new employee doesn't yet know all the limits, would sometimes propose a discount that sinks the margin, and precisely for that reason their proposal goes through an authorization that does know the limits. Over time the employee learns, but the authorization doesn't disappear: it's what guarantees that no discount violates the policy, whoever it comes from. An LLM is the permanent new employee: however much it "learns" from the prompt, its proposal always goes through the deterministic authorization that knows the policy.
The autopilot that suggests a maneuver but there are hard limits it can't cross. The autopilot of a modern airplane does a great deal: it holds the heading, adjusts the altitude, suggests corrections. But above it there's a flight envelope protection system: hard limits the airplane doesn't cross, no matter what the autopilot (or the human pilot) orders. You can't bank the airplane beyond a certain angle, you can't exceed a certain speed, you can't force a climb that would cause a stall. The control system can propose an aggressive maneuver; the envelope clips it to what's safe. The control's intelligence flies the airplane; the hard limits guarantee that no order —however good it seems in the moment— takes it out of the safe range. An LLM near money needs its envelope: it proposes whatever it wants, but there are hard limits —the maximum amount, the window, the order's status— that aren't crossed.
The point that unites the three is the same, and it's the whole module in one idea: the intelligent part proposes; the part with hard rules disposes, and the hard rules are what give the guarantee. The teller recommends, the bank approves; the employee proposes, the manager authorizes; the control suggests, the envelope clips. In all three, the "smart" part never has the last word over the irreversible: there's always a deterministic layer, with rules it knows, that decides whether the suggestion is executed. Your Mercado support agent is the teller, the employee, the control: it proposes refunds; the deterministic shell is the bank, the manager, the envelope. And no out-of-policy refund goes out, however much the model proposes it with total conviction.
Worked example: the agent proposes, the shell disposes
We're not going to say that the shell contains the dangerous actions: we're going to execute it and count. We model Mercado's support agent with the complete pattern in miniature. The probabilistic core is a stub that simulates the LLM: given the customer's message, it proposes a structured action for a refund. On purpose, it sometimes proposes something legitimate and sometimes something out of policy, hallucinated, or absurd, because the point is to see what the shell does with the bad. The deterministic shell validates each proposal against the refund policy —the order exists, wasn't already refunded, is within the return window, the amount doesn't exceed the order total or the agent's limit— and executes only if it passes.
# Module 6, Lesson 1: the deterministic shell in miniature.
# The LLM (SIMULATED by a stub) PROPOSES a structured action; the
# deterministic_shell VALIDATES it against the refund policy and only
# EXECUTES if it passes. No network, no API, no keys. Fixed data.
# --- Deterministic source of truth: Mercado's orders. ---
ORDERS = {
"A-1001": {"total": 50.00, "days_since_delivery": 3, "refunded": False},
"A-1002": {"total": 120.00, "days_since_delivery": 45, "refunded": False},
"A-1003": {"total": 30.00, "days_since_delivery": 5, "refunded": True},
"A-1005": {"total": 75.00, "days_since_delivery": 8, "refunded": False},
}
# --- Refund policy (business rules, deterministic). ---
MAX_REFUND = 100.00 # maximum amount the agent can refund on its own
RETURN_WINDOW_DAYS = 30 # return window in days
# --- Probabilistic core: SIMULATES the support agent's LLM. ---
# Given the customer's message, PROPOSES a structured action. Sometimes it
# proposes something legitimate; sometimes something out of policy, hallucinated or absurd.
def llm_propose(message):
proposals = {
"I want my money for order A-1001":
{"action": "refund", "order_id": "A-1001", "amount": 50.00},
"refund A-1002 in full":
{"action": "refund", "order_id": "A-1002", "amount": 120.00},
"A-1003 arrived broken, I want it back":
{"action": "refund", "order_id": "A-1003", "amount": 30.00},
"give me a refund for order A-9999":
{"action": "refund", "order_id": "A-9999", "amount": 40.00},
"refund me 5000 dollars for A-1005":
{"action": "refund", "order_id": "A-1005", "amount": 5000.00},
"give me my money back for A-1005":
{"action": "refund", "order_id": "A-1005", "amount": 75.00},
}
return proposals[message]
# --- Deterministic shell: validates the proposal BEFORE touching money. ---
def deterministic_shell(proposal):
order_id = proposal["order_id"]
amount = proposal["amount"]
# Rule 1: the order must exist in the source of truth.
if order_id not in ORDERS:
return (False, f"order {order_id} does not exist")
order = ORDERS[order_id]
# Rule 2: not already refunded.
if order["refunded"]:
return (False, f"order {order_id} was already refunded")
# Rule 3: within the return window.
if order["days_since_delivery"] > RETURN_WINDOW_DAYS:
return (False, f"outside the window ({order['days_since_delivery']} > {RETURN_WINDOW_DAYS} days)")
# Rule 4: the amount cannot exceed the order total.
if amount > order["total"]:
return (False, f"amount {amount:.2f} > order total {order['total']:.2f}")
# Rule 5: the amount cannot exceed the agent's limit.
if amount > MAX_REFUND:
return (False, f"amount {amount:.2f} > agent limit {MAX_REFUND:.2f}")
return (True, "approved")
messages = [
"I want my money for order A-1001",
"refund A-1002 in full",
"A-1003 arrived broken, I want it back",
"give me a refund for order A-9999",
"refund me 5000 dollars for A-1005",
"give me my money back for A-1005",
]
executed = blocked = 0
money_paid = money_blocked = 0.0
print(f"{'order':<8}{'amount':>9} {'result':<10}reason")
print("-" * 64)
for msg in messages:
proposal = llm_propose(msg) # the LLM PROPOSES
ok, reason = deterministic_shell(proposal) # the shell DISPOSES
tag = "EXECUTE" if ok else "BLOCK"
if ok:
executed += 1
money_paid += proposal["amount"]
else:
blocked += 1
money_blocked += proposal["amount"]
print(f"{proposal['order_id']:<8}{proposal['amount']:>9.2f} {tag:<10}{reason}")
print()
print(f"LLM proposals : {len(messages)}")
print(f"Executed (passed) : {executed}")
print(f"Blocked (contained) : {blocked}")
print(f"Money refunded : {money_paid:.2f}")
print(f"Money contained : {money_blocked:.2f} (never touched the customer's account)")
What to expect. When you run the file, the output is exactly this:
order amount result reason
----------------------------------------------------------------
A-1001 50.00 EXECUTE approved
A-1002 120.00 BLOCK outside the window (45 > 30 days)
A-1003 30.00 BLOCK order A-1003 was already refunded
A-9999 40.00 BLOCK order A-9999 does not exist
A-1005 5000.00 BLOCK amount 5000.00 > order total 75.00
A-1005 75.00 EXECUTE approved
LLM proposals : 6
Executed (passed) : 2
Blocked (contained) : 4
Money refunded : 125.00
Money contained : 5190.00 (never touched the customer's account)
Read the output calmly, because there's the whole module in miniature.
The model proposed six actions; the shell executed two. The two it executed are legitimate refunds: A-1001 for $50 (exists, within the window, not refunded, reasonable amount) and A-1005 for $75 (the same). The other four were blocked, and each one for a different reason worth looking at, because they're the four ways an LLM near money goes off the rails. The A-1002 proposed refunding an order delivered 45 days ago —outside the 30-day window—: the model didn't "know" (nor did it have any way to know) the policy, and proposed too much. The A-1003 proposed refunding an order that was already refunded: without the shell, it would be a double refund, money paid twice for the same thing. The A-9999 proposed refunding an order that doesn't exist: a pure hallucination —the model invented an order identifier—. And the A-1005 for $5000 proposed refunding seventy times the order's value: an absurd amount, perhaps because the model misinterpreted the customer's message.
The number that matters is at the bottom: $5190 contained. The model proposed refunding, in total, $5315. The shell executed $125 —the legitimate refunds— and contained $5190 that never touched any customer's account. Those $5190 aren't a theoretical saving: they're a nonexistent order paid into the void, a double refund, an out-of-policy refund and, above all, an absurd $5000 amount that a single model error would have disbursed. The shell didn't make the model smarter —the model kept proposing exactly the same six actions—; what it did was interpose itself between the proposal and the execution, and let through only what meets the rules. That's the complete pattern: the model proposes the six, the system disposes two.
Notice the asymmetry of responsibilities, because it's the shape of the module. The probabilistic core does one thing: propose a structured action from the message. The deterministic shell does everything else: verify against the source of truth, check each policy rule, decide, execute or block, and give a reason. The core is a drop of uncertainty; the shell is the ocean of certain code that contains it. And the guarantee "no out-of-policy refund goes out" isn't given by the model (it can't, it's probabilistic): it's given by the shell, because if amount > MAX_REFUND is a hard condition that always holds.
The ideas this module installs, and where each one lives
That example touched, without fully developing them, the module's ideas. It's worth seeing them explicit, because they're the backbone of the lessons that follow.
1. The model proposes, the system disposes (lesson 2). The root principle. The LLM never executes an action on money or state; it proposes, and a deterministic layer disposes. Lesson 2 executes the contrast between the naive design (the LLM executes) and the shell design (the LLM proposes) and measures the difference in money.
2. The structured action (lesson 3). The model returns a named command with typed fields —{action: "refund", ...}—, not free code nor interpreted text. So there's something concrete a deterministic dispatcher can validate and dispatch. Lesson 3 executes a dispatcher that dispatches the valid commands and rejects free text and invented actions at the channel.
3. The bounded capabilities (lesson 4). The model can only propose from a closed menu of actions. Everything not on the menu is rejected by definition. Lesson 4 measures how the blast radius grows with the menu's size: a small menu leaves 0 dangerous operations within reach; a broad one, several.
4. Validate the proposal against the business rules (lesson 5). The heart: run the proposed action against each policy rule before executing. Lesson 5 executes a matrix rule by rule and shows which one fails in each blocked proposal.
5. The small core (lesson 6). The less surface the LLM has over irreversible actions, the smaller the blast radius. Move to deterministic rules every decision that can be one. Lesson 6 measures the reduction of the probabilistic surface when going from a fat core to a thin core.
6. The complete pipeline (lesson 7). Assemble structure → capability → policy → execution, with an audit log. Lesson 7 executes the pipeline over a batch and shows at which stage each blocked proposal stopped.
Keep this map; it's the module's route:
Idea Lesson Key concept
─────────────────────────────────────── ──────── ──────────────────────────────
The model proposes, the system disposes L2 propose vs execute;
the root antipattern
The structured action L3 named command with fields;
deterministic dispatcher
The bounded capabilities L4 closed menu; least privilege;
blast radius
Validate against business rules L5 rule by rule; execute only
if all pass
The small probabilistic core L6 move decisions to rules;
less surface that hallucinates
The proposal -> execution pipeline L7 structure+capability+policy;
audit log
─────────────────────────────────────── ──────── ──────────────────────────────
Build the agent's shell L8 the mini-project, executed
The map: where this module sits in the guide and in the ecosystem
This module is the sixth piece of the deterministic shell that surrounds the AI component, and it's the one that deals with its actions. This is how it connects with the rest of the guide:
flowchart TD
M1["M1 · Place the component<br/>(contract, boundary, core/shell)"]
M2["M2 · Latency and cost as architecture"]
M3["M3 · The eval as a fitness function"]
M4["M4 · Guardrails and the trust boundary"]
M5["M5 · Failure modes and resilience for AI"]
M6["M6 · The deterministic shell"]
M7["M7 · The data and feedback loop"]
M8["M8 · Project: architect an AI feature"]
M1 --> M2 --> M3 --> M4 --> M5 --> M6 --> M7 --> M8
Read it this way: in M1 you gave the core/shell shape; in M4 you put guardrails that validate the content of the output; here (M6) you build the part of the shell that validates the actions: when the model's output stops being a text you display and becomes an action that touches money or state, the validation changes —from "is it valid content?" to "is it an action that can be executed according to the rules?"—. In M7 you'll close the data loop, and you'll see that the audit log from lesson 7 (what was proposed, what was approved, what was blocked) is also the first raw material of that loop.
And this module's two hard boundaries, which must be respected. The first, with the domain guides: we don't teach what Mercado's refund policy should be —what amount, what window, what exceptions—; that's a business decision, content of the domain guides. We teach the pattern: that there exists a deterministic layer that validates the proposed action before executing it, whatever the policy is. The second, with module 4: the M4 guardrail validated the format and content of the output ("is it valid JSON?", "does it have a forbidden claim?"); the M6 shell validates the action against the business rules ("is this refund within policy?"). A guardrail can approve a perfectly well-formed output —{action: "refund", amount: 5000, order_id: "A-1005"} is impeccable JSON— that the shell must reject because the action violates the policy. They are two gates in series: first "is it a valid output?" (M4), then "is it an executable action?" (M6). And the third boundary, the usual one with AI Engineering: how to make the model propose better actions —better prompt, tool use, function calling— is AI Eng; here we treat how to contain the actions it proposes, be they good or bad.
Common mistakes
Letting the LLM execute the action directly. What happens: the team connects the model's output directly to the payment system —"the agent detects that a refund is needed and calls the refund API"—, without any layer that disputes it. The day the model hallucinates an order, proposes an absurd amount, or refunds out of policy, the money has already gone out: there was nowhere to stop it. Why it happens: it's the shortest path —connect the output to the action— and in the tests the model almost always proposed well, so the failure was never seen. How to detect it: trace what separates the model's output from the real effect on money or state; if the answer is "nothing, the output triggers the action", you don't have a shell. How to fix it: interpose a deterministic layer that receives the action as a proposal, validates it against the rules, and executes only if it passes. Lesson 2 measures it: the naive design paid $5315; with a shell, $125.
Giving it broad capabilities "so it's useful". What happens: so the agent "can solve anything", it's given access to a huge menu of actions —refund, give credit, change prices, cancel orders, deactivate accounts—. Each extra capability is one more irreversible operation within reach of a hallucination. The day the model, confused, proposes changing a price to $0.01 or deactivating an account, that action was on the menu, so nothing stops it by definition. Why it happens: "useful" is confused with "powerful", and it's feared that a small menu will limit the agent. How to detect it: list the actions the agent can propose and ask yourself how many are irreversible and dangerous; if there are many, your blast radius is large. How to fix it: apply least privilege —give it the minimum menu it needs for its job, and nothing more—. Lesson 4 measures it: a bounded menu leaves 0 dangerous operations within reach; a broad one, 3.
Validating the text but not the action. What happens: the team put an M4 guardrail that validates the output being well-formed JSON with no forbidden content, and believes that protects them. But {action: "refund", amount: 5000, order_id: "A-9999"} passes the guardrail without a problem —it's perfect JSON, with no forbidden words— and is still an action that refunds an absurd amount of an order that doesn't exist. The guardrail validated the content; no one validated the action. Why it happens: "valid output" is confused with "executable action", which are distinct things. How to detect it: ask yourself whether a perfectly well-formed output could still execute something that violates the policy; if the answer is yes, you're missing the M6 shell. How to fix it: after the format guardrail, add the validation of the action against the business rules. Lesson 5 executes it rule by rule.
Trusting that the prompt "told it not to over-refund". What happens: instead of a deterministic shell, the team puts the policy in the prompt —"never refund more than $100, never refund outside the 30-day window"— and trusts that the model obeys. Most of the time it obeys; but the prompt is a suggestion to a probabilistic component, not a guarantee, and sooner or later the model proposes $5000 all the same, because a customer insisted, because it misinterpreted, or simply because it's non-deterministic. Why it happens: putting the rule in the prompt is easy and seems to work in the tests. How to detect it: if your only defense against an out-of-policy refund is an instruction in the prompt, you don't have a guarantee —you have a probability—. How to fix it: the policy lives in deterministic code, not in the prompt. The prompt can ask the model to propose within policy (it helps it propose well), but the guarantee is given by the shell's if, which holds 100% of the time no matter what the model proposes.
Exercises
Exercise 1 — The teller, the employee, and the pilot. For each of the module's three analogies, identify (a) who proposes, (b) who disposes, and (c) what guarantee the disposing part gives that the proposing part couldn't. Then say, for Mercado's support agent, who proposes and who disposes.
See solution
- The teller: (a) proposes = the teller (recommends approving the loan); (b) disposes = the bank's system (approves or denies according to score, debt, limit); (c) the guarantee = no approval violates the bank's rules, however much the teller wants. The teller contributes the judgment of the case; the system contributes the certainty of the policy.
- The new employee: (a) proposes = the employee (suggests a discount); (b) disposes = the manager (authorizes according to the store's policy); (c) the guarantee = no discount sinks the margin or violates the policy, whoever it comes from. The employee doesn't know all the limits; the authorization does.
- The autopilot: (a) proposes = the control system (suggests a maneuver); (b) disposes = the envelope protection (clips to what's safe); (c) the guarantee = the airplane doesn't leave the safe range, no matter what order it receives. The control flies; the hard limits guarantee safety.
- Mercado's support agent: proposes = the LLM (suggests a refund action from the customer's message); disposes = the
deterministic_shell(approves or blocks according to the policy: order exists, within the window, not refunded, amount within the limit). The guarantee: no out-of-policy refund goes out, however much the model proposes it with total conviction.
The pattern in all four: the intelligent part proposes, the part with hard rules disposes, and the guarantee is always given by the second.
Exercise 2 — The containment number. In the worked example, the model proposed refunding $5315 in total and the shell executed $125, containing $5190. Of those $5190, which proposal was the most dangerous and why? Then answer: if instead of a deterministic shell the team had put the policy in the model's prompt, what guarantee would it have over those $5190?
See solution
The most dangerous proposal was the A-1005 for $5000. The other three blocked are bounded errors: the A-1002 ($120) would refund a bit too much and outside the window, the A-1003 ($30) would be a double refund, the A-9999 ($40) would pay a nonexistent order. But the A-1005 for $5000 is an absurd amount —seventy times the order's real value ($75)—: a single error of this kind, executed, is an embezzlement. It's the case that best illustrates why the shell matters: it protects not only against small and frequent errors, but against the large and rare error that, executed just once, does enormous damage.
If the policy lived in the prompt instead of in the shell, the guarantee over those $5190 would be none —only a probability—. The prompt asks the model not to over-refund, but the model is probabilistic: most of the time it will obey, but every so often it will propose $5000 all the same, and if there's no deterministic shell to stop it, that $5000 is executed. The difference is categorical: the shell with if amount > MAX_REFUND gives a hard guarantee (0 refunds over the limit, always); the prompt gives a high probability (almost never, but not never). Near money, "almost never" isn't enough.
Exercise 3 — Guardrail or shell. Module 4 validated the model's output; module 6 validates the action. For each of these checks, say whether it belongs to the output guardrail (M4) or the action shell (M6): (a) the model's output is well-formed JSON; (b) the order the action wants to refund exists in the database; (c) the output doesn't contain offensive language; (d) the refund amount doesn't exceed the agent's limit.
See solution
- (a) The output is well-formed JSON → output guardrail (M4). It's a format validation: does the output have the expected structure? It doesn't look at whether the action is executable, only whether the text is well-formed. Without this, you can't even parse the proposal.
- (b) The order exists in the database → action shell (M6). It's a validation of the action against the source of truth and the business rules: it's not enough for the JSON to be valid; the order it wants to refund has to actually exist. A hallucinated
order_idproduces perfect JSON that the shell must reject. - (c) The output doesn't contain offensive language → output guardrail (M4). It's a content validation: it looks at what the text says, not what the action does. It's moderation, a module 4 topic.
- (d) The amount doesn't exceed the limit → action shell (M6). It's a business rule about the action:
amount <= MAX_REFUND. A $5000 amount can come in an impeccable and non-offensive output; only the shell, with the policy's rule, stops it.
The general lesson: (a) and (c) ask "is it a valid output?" and are M4 guardrails; (b) and (d) ask "is it an executable action according to the rules?" and are the M6 shell. The two gates go in series: first the guardrail lets through a well-formed output, then the shell decides whether the action is executed.
Summary and next step
In this lesson you installed the thesis that sustains the module and crowns the guide: the model proposes, the system disposes. The LLM never executes an action on money or state directly; it proposes a structured action, and a deterministic layer of business rules approves or rejects it before it touches anything. You saw it with three analogies —the teller who recommends but the bank approves, the employee who proposes but the manager authorizes, the control that suggests but the envelope clips— and you measured it: the support agent proposed six refund actions, the shell executed two legitimate ones and contained four dangerous ones ($5190 that never touched any customer's account), without making the model smarter —only interposing itself between the proposal and the execution—. And you marked the two hard boundaries: the business logic itself belongs to the domain guides; the validation of the output's content was M4, and here we validate the action against the rules.
Before moving on you should be able to: state and defend "the model proposes, the system disposes"; explain why the LLM should never execute an irreversible action directly; distinguish the output guardrail (M4) from the action shell (M6); and argue why a policy rule belongs to the deterministic code and not to the prompt.
Lesson 2 takes the first idea and develops it in depth: the model proposes, the system disposes, as the root principle of the entire module. You'll see, executed and measured, the contrast between two designs with exactly the same model proposals: the naive design, where the LLM executes directly and the money moves without asking, and the shell design, where the LLM proposes and a deterministic layer disposes. The difference between paying $5315 and paying $125 isn't a detail: it's the distance between a system that trusts the model and one that contains it.
Resources
- Anthropic, "Building Effective Agents" (2024) — anthropic.com/engineering/building-effective-agents. The discussion of tool use and structured actions —the model proposes tool calls with typed arguments, and the system decides what to do with them— is the technical basis of "the model proposes, the system disposes" from this lesson. In English.
- Claude documentation — docs.anthropic.com. The tool use pages describe how a model returns a structured action (a tool call with parameters) that the system executes; read them for the structured-action mechanism, without focusing on a specific model version. In English.
- Martin Fowler and Bharani Subramaniam, "Emerging Patterns in Building GenAI Apps" — martinfowler.com/articles/gen-ai-patterns. The guardrail and containment patterns place this action shell in the complete architectural map of a GenAI app. In English.
- Chip Huyen, AI Engineering (O'Reilly, 2024). Its treatment of agents and of the control over the actions a model can take is the extended version of this lesson's containment principle. We keep the containment; building the agent is the boundary with AI Eng. In English.
- The least privilege principle, from classic security: give each component the minimum set of permissions it needs, and nothing more. It's exactly the "bounded capabilities" idea that lesson 4 applies to the LLM's action menu. Any introductory security reference covers it.