You've earned the agent, the task cleared all three gates. Now build it so it holds. Tonight: the anatomy, tools as contracts and the ones MCP hands you, planning, memory, durability, single vs multi-agent, and the exact failure mode hiding in each choice.
Model, tools, memory, and a stopping condition. The model reasons, picks a tool, observes the result, and repeats, until it decides it's done or you cut it off. Everything else is detail.
flowchart LR
G["Goal"] --> M{"Model
reason + choose"}
M -- "call" --> T["Tool"]
T --> O["Observation"]
O --> MEM[("Memory")]
MEM --> M
M -- "stop when done
or capped" --> DONE["Result"]
classDef m fill:#EEE6FF,stroke:#1F2937,color:#0E1726;
classDef ok fill:#D6F5E3,stroke:#1F2937,color:#0E1726;
class M m;
class DONE ok;
The stopping condition is not optional. An agent without a hard cap is an unbounded bill and an infinite loop waiting to happen.
Every tool is a door into your systems that a probabilistic caller can open. Design it like a public API called by a stranger: tight inputs, a clear description, validated outputs, and the least privilege that does the job.
run_sql(query) · http_get(url) · shell(cmd) — unbounded power, the model decides how far it reachesPrefer refund_order(order_id) with server-side rules over run_sql(...). The tool, not the prompt, is where you enforce what's allowed.
# GOOD: narrow, typed, server enforces the limits
{ "name": "refund_order",
"description": "Refund one order the current user owns.",
"input": { "order_id": "string",
"reason": "enum[damaged, late, wrong_item]" } }
# server re-checks: user owns order_id, amount <= cap, not already refunded
# DANGEROUS: unbounded power, the model decides how far it reaches
{ "name": "run_sql", "input": { "query": "string" } } # one injection from a breach
Same capability, two risk profiles. refund_order can only do the one thing, checked server-side. run_sql can do anything, and the model chooses.
Everything on the last three slides still holds. MCP standardizes how you hand the contract over: one protocol, so a tool written once is callable by any agent. The architectural shift is quieter and bigger, most tools you ship, you didn't write.
flowchart LR
AG{"Your agent"} --> C["MCP client"]
C --> GW["Gateway
auth + allow-list"]
GW --> S1["Your server
refund_order"]
GW --> S2["Third-party server
you don't run this one"]
classDef ok fill:#D6F5E3,stroke:#1F2937,color:#0E1726;
classDef un fill:#FEE4E2,stroke:#1F2937,color:#0E1726;
class GW,S1 ok;
class S2 un;
The gateway is the one place to decide which tools this agent may call. Without it, "just add an MCP server" grants privileges nobody reviewed. Thursday, we attack exactly this.
Reason-then-act (observe, decide, repeat) lets an agent handle tasks whose steps aren't knowable up front. But planning multiplies calls and compounds error, one wrong early step and the rest builds on sand. Plan when you must; script when you can.
flowchart LR T["Thought
what next?"] --> A["Action
call a tool"] A --> O["Observation
tool result"] O --> T T -. "goal met or capped" .-> D["Done"] classDef t fill:#EEE6FF,stroke:#1F2937,color:#0E1726; classDef ok fill:#D6F5E3,stroke:#1F2937,color:#0E1726; class T t; class D ok;
Powerful, and where cost hides: every trip round the loop is another model call. The dashed exit, a goal check or a hard step cap, is the whole difference between an agent and a runaway bill.
flowchart TB
subgraph SHORT["Short-term · the context window"]
A["Recent steps + observations"]
end
subgraph LONG["Long-term · a store"]
B["Facts, decisions, past runs"]
end
B -. "retrieve on need" .-> A
A -. "persist what matters" .-> B
classDef s fill:#DCEBFE,stroke:#1F2937,color:#0E1726;
classDef l fill:#D6F5E3,stroke:#1F2937,color:#0E1726;
class A s;
class B l;
Short-term rots (the S3 U-curve, contradictions). Long-term drifts (stale or wrong facts retrieved as truth). Curate both, and never let memory become an unaudited source of authority.
A 40-step run outlives your deploy window. It will be killed halfway: a timeout, a rate limit, a crash, a Tuesday release. With no durable state you restart from zero, pay for every step twice, and re-fire every side effect you already fired.
This is why the loop and its state are two different things. If the state only lives in the process, the run is only as durable as the process.
Multi-agent (an orchestrator delegating to specialists) buys parallelism and separation of concerns. It charges you coordination overhead, multiplied cost, and new ways to fail. Default to one agent with good tools; split only on a real seam.
flowchart TB
O{"Orchestrator"}
O --> A["Researcher
own tools + context"]
O --> B["Writer
own tools + context"]
O --> C["Checker
own tools + context"]
A --> S["Synthesize"]
B --> S
C --> S
classDef o fill:#EEE6FF,stroke:#1F2937,color:#0E1726;
class A,B,C o;
Worth it when subtasks are genuinely independent and each needs its own context. Not worth it to make an org chart out of prompts.
| Pattern | Buys you | Breaks as | Guardrail |
|---|---|---|---|
| Tool use | reach into systems | misuse / over-reach | tight contract, least privilege |
| MCP / imported tools | reach without glue | privileges nobody reviewed | gateway, allow-list servers |
| Planning loop | handles the unknown | error compounding | checkpoints, step caps |
| Memory | continuity | rot / stale drift | curate + expire |
| Long runs | multi-step work | restart from zero, double side effects | checkpoint + idempotency keys |
| Multi-agent | parallel specialists | coordination + cost | split only on real seams |
If your system earned an agent, draw its four parts. If it didn't, draw the workflow you chose instead, that's a valid answer too. Post your stopping condition in the chat.
Tools are contracts, including the ones MCP hands you. Plan only when the path is unknown. Curate memory. Checkpoint anything long. Stay single until a real seam forces multi. And every pattern ships with a failure mode, name it before it names you.