Part I - Foundations
Agent boundary - model, policy, state, environment
Lesson 00 argued that a raw model call is not yet a system, and that the rest of the track is a catalogue of named ways to harden the loop around it. Before we can place any of those patterns, we need a map of the loop itself. This lesson draws the four boundaries every later pattern will modify — model, policy, state, and environment — and gives you a diagnostic that says which boundary broke when an agent misbehaves.
New capability: the four nouns that make every later pattern locatable — model, policy, state, environment — plus a test that attributes any failure to exactly one of them.
1 · From the book's five-step loop to four components
The book opens with a deliberately plain definition: an agent is a system that perceives its environment and takes actions to achieve a goal, evolved from a language model by adding planning, tool use, and environment interaction. It then describes how such a system behaves as a five-step loop, using a calendar assistant as the running picture:
This is a good story, but a story is not an architecture. Notice that the five steps quietly require four different kinds of machinery, and that conflating them is the source of most agent bugs. Step 1 needs somewhere to hold the goal. Steps 2 and 4 reach outside the program — into a mailbox, a calendar API. Step 3 is the only step that is "thinking," and even there we must separate the thought from the decision to act on it. Step 5 only makes sense if the loop has memory that survives a turn.
So we redraw the loop with the components named. This is the picture the entire track will keep returning to:
Four boundaries, one loop. The model only ever sees the slice of state you packed into its context, and only ever emits a proposal; the policy is the code that decides whether the proposal is allowed to touch the environment; the environment returns an observation; the observation is written back into state. Every pattern in this book is an intervention on exactly one of those arrows.
2 · The four boundaries, defined precisely
These four words are used loosely in blog posts and even in framework docs, so we fix sharp definitions now and hold them for 24 lessons.
The mental model that keeps these straight: the model is a brilliant consultant locked in a room with no phone. It can read whatever you slide under the door and write a brilliant recommendation back — "you should run the test suite, then email the customer." But it cannot run anything or send anything. The policy is the assistant standing at the door who reads each recommendation, checks it against the rules ("we never email customers without a human signing off"), and either carries out the safe ones or refuses. The environment is the building outside the room. The state is the case file that the assistant keeps updating and re-sliding under the door so the consultant always sees the current situation.
Two consequences fall straight out of this, and they are the spine of safe agent design:
- The model never has authority. When the book says the model "uses a tool," what physically happens is the model emits text that names a tool and arguments; the policy decides whether to honor it. A model cannot delete a file; it can only produce the string
delete_file("/etc/passwd"), which your policy is free to reject. - Hard invariants live in code, not in prose. "Please don't spend more than $5" in a system prompt is a request the model may forget or be talked out of. A budget check in the policy is a guarantee. Put dangerous limits in schemas, permission checks, and tool wrappers — never only in the prompt.
3 · The capability ladder is the boundary, switched on one piece at a time
The book frames agent sophistication as four levels, from a bare reasoning engine to collaborating teams. The crucial insight for this track — and the reason we learn the boundary first — is that the ladder is not four different things. It is the same four-component diagram with more of the arrows enabled. Each rung lights up another boundary.
| Level | Book name | What is switched on | Book's example | Covered in |
|---|---|---|---|---|
L0 | Core reasoning engine | Model only. No state, no tools, no environment. Answers from pretrained knowledge. | Explains a known concept well, but cannot name the 2025 Oscar Best Picture if it post-dates training. | lesson 02 (prompt/context) |
L1 | Connected problem-solver | Model + environment via tools. The policy can now authorize a search or an API call and feed the result back. | Looks up a new TV show with web search; fetches the live AAPL stock price from a finance API. | lessons 07 (tool use), 16 (RAG) |
L2 | Strategic problem-solver | Adds a real policy and state loop: multi-step planning, context engineering (curating what goes into each model call), and self-improvement from feedback. | "Find a café between two addresses": call a map tool, trim its output to a street list, pass that to a local-search tool — managing context so the model is not overloaded. In software: read a bug report and the repo, distill them into focused context, then write and test a patch. | lessons 03–08, 10–11 |
L3 | Collaborative multi-agent | Many bounded agents, each its own model+policy+state, coordinated through a communication protocol. | A product launch run by a "project manager" agent that delegates to "market research," "product design," and "marketing" agents. | lessons 09 (multi-agent), 17 (A2A) |
Read down that table and the design discipline is obvious: do not climb a rung you do not need. Every level you add lights up a boundary that can now fail. L0 can only be wrong; it cannot do damage. The moment you reach L1 you have an environment that can be slow, can error, can return adversarial content, and can cost money per call. L2 adds state that can go stale and a planner that can loop forever. L3 multiplies all of that by the number of agents. The book's own warning is that multi-agent systems are bottlenecked by the reasoning quality of the underlying model and that inter-agent learning is still immature — i.e. the highest rung is the least reliable. The boundary diagram is your budget: switch on the least machinery that solves the task.
4 · A worked turn through the boundary — with the numbers
The boundary is not an abstraction; it determines the token bill and the blast radius of a mistake. Let's run one real turn of the coding/research assistant — the running example for the whole track — and watch the four components in action with concrete numbers.
Scenario. A user files: "Tests are failing on main after my last commit — fix it." The agent is at L2: it has a read_file tool, a run_tests tool, and an edit_file tool, with a per-task budget of $0.50 and a hard cap of 8 model calls.
| Step | Boundary | What happens |
|---|---|---|
| 1 | State | Goal recorded as a contract: {intent: "fix failing tests", repo: "...", budget: $0.50, max_calls: 8, status: "running"}. Plan and observations are empty. |
| 2 | Policy → context | The policy builds the model context. It does not dump the whole repo — it packs the goal, the failing test names, and the last diff. Say that is ~3,000 input tokens. |
| 3 | Model | The model proposes: run_tests(path="tests/", timeout=60) and reasons that it needs to see the actual failure. Output: ~400 tokens. |
| 4 | Policy | Validates the proposal: is run_tests an allowed tool? Yes. Is timeout=60 within limits? Yes. Authorized. |
| 5 | Environment | The run_tests tool runs in a sandbox, captures stdout, truncates it to 2,000 tokens, and returns a structured observation: {exit: 1, failed: ["test_parse"], tail: "..."}. |
| 6 | State | The observation is appended; calls_used → 1; cost so far is debited. Loop back to step 2 with the failure now in context. |
The token and dollar math. Suppose the model is priced at $3 per million input tokens and $15 per million output tokens (a representative mid-tier rate; in this track the assumed provider is Claude — confirm exact model IDs and current pricing against the official Anthropic pricing page rather than from memory). One model call here costs roughly:
At ~$0.015/call the $0.50 budget buys about 33 calls — but the policy's hard cap of 8 calls binds first. Now watch what the boundary buys you. Because the environment truncates tool output to 2,000 tokens, a runaway test that prints 500,000 lines cannot blow up the next context to 500k tokens (which would cost 500{,}000 × \$3/10^6 = \$1.50 in a single call and bust the budget 3× over). Because the cap lives in the policy as a counter and not as "please stop after 8 tries" in the prompt, a model that keeps proposing one-more-try is physically stopped at call 8 with status: "blocked". The boundary is what converts "the model is well-behaved" (a hope) into "the task cannot exceed 8 calls or 2k-token observations" (a guarantee).
TaskState beside the transcript; render part of it into context per turn. This is exactly the L2 "context engineering" the book praises in the café example — curate what the model sees, don't fire-hose it.
5 · The implementation sketch, boundary by boundary
Here is the loop with each boundary labelled. Notice that model_policy only ever returns a proposed action, and a separate validate step gates it — that separation is the whole point.
class TaskState: # ── STATE: the typed task record, outlives any one call
goal: GoalContract
messages: list[Message] # the transcript is PART of state, not all of it
plan: list[Step]
observations: list[Observation]
artifacts: dict[str, ArtifactRef]
budget: Budget # dollars + max_calls live HERE, in code
permissions: PermissionSet
status: Literal["running", "blocked", "done", "failed"]
def agent_step(state: TaskState) -> TaskState:
context = build_prompt_context(state) # POLICY: choose what the model sees
proposed = model_policy(context) # MODEL: pure fn, returns a PROPOSAL only
action = validate_policy_action(proposed, state) # POLICY: authorize or reject
if action is None: # rejected (budget? permission? bad schema?)
return state.block(reason="policy refused action")
observation = environment.execute(action) # ENVIRONMENT: the only place the world changes
return state.apply(action, observation) # STATE: write back, advance status, debit budget
def run(state): # POLICY: the terminal condition lives in code
while state.status == "running" and state.budget.has_room():
state = agent_step(state)
return state
The book lets the same diagram describe every framework you will meet. When it discusses LangChain/LangGraph, the graph nodes are the policy and the edges are control flow; when it discusses Google's ADK or CrewAI, "agents" and "tools" map onto the model and environment boundaries; the model context protocol (lesson 12) is just a standard contract for the environment door. Frameworks differ in which boundary they make ergonomic, not in the boundaries themselves. The course invariant: a good pattern should clarify the boundary, never hide it behind framework magic.
6 · The boundary-attribution test
This is the deliverable of the lesson — the diagnostic you will reuse in every debugging session for the rest of the track. When an agent does the wrong thing, do not ask "is the model dumb?" Ask which boundary failed, in this order:
| Symptom | Boundary at fault | Fix lives in |
|---|---|---|
| Right context, still reasoned wrong | Model capability | better model, or reasoning pattern (lesson 19) |
| Model never saw a fact it needed | State / context | context construction (lesson 02), memory (lesson 10), RAG (lesson 16) |
| Chose a bad next step / looped forever | Policy | routing (lesson 04), planning (lesson 08), goals & stop (lesson 13) |
| Acted on stale or wrong facts | State freshness | state write-back, memory policy (lesson 10) |
| Did something it should not be allowed to | Policy / action gate | guardrails (lesson 20), human-in-the-loop (lesson 15) |
| Tool returned garbage / timed out / lied | Environment | tool design (lesson 07), recovery (lesson 14) |
Run the test on a real failure. The coding assistant deletes a file it should not have. A naive postmortem blames "the model." The boundary test asks: did the model merely propose delete_file? Almost certainly yes — and that is fine, models propose all sorts of things. The real failure is that the policy authorized a destructive action without a permission check. The fix is a guardrail at the action gate (lesson 20), not a better model. Locating the failure at the right boundary is what turns "the agent is flaky" into a specific, fixable bug.
Failure modes
- Using the conversation transcript as the only state store — budget, permissions, and plan get lost in prose and the context cost grows every turn.
- Enforcing dangerous permissions in the prompt ("please don't delete files") instead of in the policy/tool layer.
- Letting the model's proposal execute directly, with no validation gate between proposal and environment.
- Building the loop with no terminal condition — no budget, no call cap, no
donecheck — so it runs until something else crashes. - Climbing the capability ladder (multi-agent) before the single-loop version works, multiplying the failure surface.
Implementation checklist
- What exactly is stored in
TaskState, and what is merely transcript? - What is the closed set of actions the policy may authorize?
- Who validates each proposed action, and against which limits (budget, permission, schema)?
- What structured observation is written back after each action — and is tool output truncated?
- What exact condition (status, budget, call cap) stops the loop?
- Which capability level (L0–L3) does the task actually need — and have you justified going higher?
Where this points next
We now have the map: model, policy, state, environment, and a test that says which one broke. The very first boundary every other pattern leans on is the one between state and model — the context you slide under the consultant's door. Lesson 00 left the model as a black box; this lesson said the model only sees the slice of state the policy packs for it. Lesson 02, "Prompt and context contracts," makes that slice an engineering artifact: typed inputs, scoped context, validated structured outputs, so that "prompting" stops being prose and becomes an interface with a contract you can test. It is also where the book's L2 "context engineering" — the café and bug-report examples — gets built for real.
Interview prompts
- What is the difference between the model and the policy in an agent? (§2 — the model is a pure function that only proposes text/structured output with no authority; the policy is the code that builds context, authorizes or rejects the proposal, and decides when to stop. Authority lives in the policy.)
- Why should you not store a budget or permission rule in the system prompt? (§2, §4 — prompt text is a request the model can forget or be argued out of; a counter or permission check in the policy is a guarantee. Hard invariants belong in code, schemas, and tool wrappers.)
- An agent deleted a file it should not have. Where is the bug? (§6 — almost never the model, which merely proposed the action; the failure is the policy/action gate authorizing a destructive action without a permission check. Fix with a guardrail, not a bigger model.)
- What is wrong with using the chat transcript as your only state? (§4 — it conflates state with model context: budget/plan/permissions live in editable prose, context cost grows every turn, and you cannot reliably query task facts. Keep a typed TaskState and render part of it per turn.)
- Explain the book's Level 0→3 ladder in terms of the boundary diagram. (§3 — L0 = model only; L1 = model + environment via tools; L2 = a real policy/state loop with planning and context engineering; L3 = many bounded agents over a protocol. Same four components, more arrows enabled — and more failure surface per rung.)
- How would you decide whether a task needs a multi-agent (L3) design? (§3 — default to the lowest rung that solves it; reach for L3 only when specialization, isolation, or independent judgment beats one loop, since each extra agent multiplies the failure surface and token cost. Criteria are made precise in lesson 09.)