Loading lesson...
Loading lesson...
Stage 2 gave you the vocabulary of agent design. Stage 3 makes you write it. Across five modules you build a single agent from an empty directory, decide whether a second agent is justified, put the same loop behind a visual canvas, expose your own capabilities through a shared protocol, and then connect all of it to external services that are slow, rate limited, authenticated and occasionally wrong. The teaching is deliberately unglamorous, because almost everything that breaks an agent in production is scaffolding rather than reasoning.
One argument runs through all five modules. The model supplies the reasoning, and you supply the structure that turns reasoning into action you can repeat, test and stop. That structure is a loop with explicit stop conditions, tool functions that were unit tested before the loop ever ran, descriptions precise enough that the model can tell which tool to call and when not to call it, handoffs shaped so the receiving side cannot misread them, timeouts and backoff on every outbound call, and error objects that tell the agent something it can act on rather than a stack trace it cannot. Where a module gives you a knob, the stage teaches you that the knob is usually a symptom: raising a step limit, raising an iteration cap or retrying harder are what you do after you have found the description, the completion signal or the credential that was actually at fault.
The sections follow the stage's teaching order, so you can read straight through to rebuild the stage in your head, or jump to the concept you need. Each section links back to its module for the full treatment, including the runnable code, the interactive tools and the version-specific detail that this summary deliberately leaves with the source.
The first module has you build a research agent from an empty directory, and the first decision is layout rather than code. Tool implementations, tool schemas and the loop each get their own file, with tests beside them. The reason is diagnostic rather than aesthetic: when the agent calls the wrong tool or passes the wrong argument, you want to know immediately whether the fault is in a function, in the description the model read, or in the loop that drove the conversation. The loop file is kept free of business logic. It only moves messages between the model and the tools.
The loop itself has exactly three states and a production agent handles all three explicitly. The model returns a final answer and the loop returns it. The model asks for tool calls, so the loop appends the assistant message, runs each requested tool, wraps every result, including failures, as a tool result tied back to the call that produced it, appends those results and goes round again. Or the step counter reaches a hard maximum and the loop returns a plain admission that it could not finish. That maximum is set before you begin. Without it a confused agent will keep calling the model until somebody notices the bill.
The step limit is a safety belt, not a design goal. An agent that routinely reaches it has a problem in its descriptions or its system prompt, and raising the limit only makes the same problem more expensive. This is where structured logging earns its place: log the step number, the stop reason, the tool name and the keys the tool returned, and an opaque run becomes a readable decision trace. When a run does hit the limit, the module tells you to read the last few tool calls and look for one of three causes. The model has no completion condition it can satisfy. A tool is returning an error that the model reads as a reason to keep trying. Or two tools have overlapping descriptions and the model is alternating between them.
The first tool is read-only and the loop runs under a hard cap, so a wrong first agent costs a wasted run rather than a change you have to undo, and the test set built at the fourth step is the part of it that survives the rewrite.
A first agent is a small loop with one safe tool, a hard step cap, and tests for both happy and bad inputs. ReAct and the OpenAI Agents SDK quickstart name this contract.
A system prompt is read once at the top of a conversation. A tool description is read every time the model weighs up whether to call that tool, which makes it the more direct lever on what the agent actually does. The module is blunt about the consequence: when the agent selects the wrong tool, rewrite the description before you touch the prompt or the limit. Two patterns carry most of the weight. State what the tool is not for as well as what it is for, so that a search tool says plainly that it is not for calculations and a notification tool says plainly that it is not to be called during research. And put a sequencing hint on any tool that is meant to end the run, because a tool described only as compiling a report gives the model no signal that compiling the report is how the task finishes.
Tools themselves are ordinary functions, and the module has you build and test them completely before the loop exists. Unit test each one, then write the integration test for the whole loop. That ordering is what narrows the search space: if the tools are known good and the integration test fails, the fault is in the loop, the system prompt or the schemas, and you have already eliminated three quarters of the surface. The tests are cheap and they run in a second, whereas diagnosing the same fault from a mid-run agent transcript is slow and ambiguous.
One safety rule applies to every tool that evaluates an expression. Tool arguments are generated by the model, and the model can be steered by whatever text has reached its context, so passing a model-generated string to a general purpose evaluator is handing an untrusted party a way to run code in your process. The module's answer is a restricted evaluator that permits arithmetic and nothing else, blocking imports, attribute lookups and file access, or a purpose-built calculation interface with an explicit list of what it will accept. Wrapping the dangerous call in exception handling is not a fix, because the code has already run by the time the exception is caught.
A single agent holding twenty tools suffers tool confusion, because the model has too many similar choices and picks badly. Its context grows and grows expensive as the task history accumulates. And one failure stops everything. Splitting the work addresses all three: each agent carries a narrow tool set, each context stays focused on one job, and a failure is contained to the agent that had it. The module is equally clear about the bill. Every handoff costs tokens and adds latency, and a worker that is confidently wrong can pass bad data to the supervisor, which then presents it as a finding. Start with one agent, and move to several only when you have measured the specific failure that coordination fixes.
The supervisor pattern runs in three phases. A capable model plans, breaking the request into sub-tasks and assigning each to a specialist. Workers execute their assignments. The supervisor then synthesises the worker outputs into one result. Because planning and synthesis are where reasoning quality matters and worker tasks are usually bounded extraction or lookup, the module recommends matching model capability to the job rather than using the same model everywhere, and puts the resulting saving at roughly five to ten times with no meaningful quality loss on the worker tasks. Where worker tasks are genuinely independent, they should run concurrently rather than one after another; three ten second tasks in sequence take thirty seconds and in parallel take a little over ten.
The interface between agents matters more than the individual agents. An unstructured instruction such as a request to go and research the market leaves the receiver guessing at the output format, the background it should assume and what to do if it cannot finish. A structured handoff removes all three ambiguities by carrying the specific sub-task, the context the receiver would not otherwise have, the expected output format so the supervisor can parse rather than interpret, and a fallback instruction for failure. Worker replies need the same treatment: a sentence saying the task is complete gives the supervisor nothing to check, whereas a status field, a result field and an optional confidence let it handle partial failure programmatically. Two coordination failures are worth naming in advance. Conflicting worker findings should go to a judge step that is given both claims and the context and asked to reason about the disagreement, not resolved by arbitrary choice. And a worker that responds to difficulty by delegating back upward can loop indefinitely, which is why delegation depth is capped and workers are required to return a structured failure instead. Underneath all of it sits least privilege: give each agent only the permissions its own task needs, so that one compromised or hallucinating worker has a small blast radius.
Each topology buys one strength and creates one risk, so the six contract points beneath, owner, handoff schema, shared state, allowed tools, delegation depth and escalation, are settled before the second agent rather than after the first tangled failure.
More agents do more work and create more failure modes. Pick the topology before adding the second agent. OpenAI Agents SDK handoffs and OWASP Agentic AI name the six contract points.
n8n is a source-available workflow automation platform, licensed under its Sustainable Use Licence and described by the module as fair-code, with a visual node editor, where each node is one operation and nodes connect into a workflow with branching and looping. It accepts JavaScript inside a node when the visual route runs out, and it can be self-hosted or used as a hosted service. The module's decision rule is about ownership rather than capability: reach for the visual builder when you are connecting existing services and the people who will maintain the result are not engineers, and reach for code when you need deep branching, high throughput or fine control over retry behaviour. Two operational details belong to the self-hosted case. Set an explicit encryption key rather than letting one be derived from the machine, or stored credentials become unreadable the day the deployment moves. And treat administrator access as equivalent to holding the credentials themselves, because encryption at rest does not stop an administrator building a workflow that reads a stored key back out.
The AI Agent node is the same loop you wrote by hand, with the implementation hidden. You connect a chat model and at least one tool sub-node, and the agent decides which tools to call to complete the task; HTTP request, code, database and messaging nodes all serve as tool sub-nodes. Because it is the same loop, the same failure modes arrive with it: vague tool descriptions still cause wrong tool selection, and a missing completion condition still causes looping. The module treats the node's iteration cap as the visual equivalent of the step limit and keeps it deliberately low for production, on the reasoning that a high cap simply lets a runaway loop become expensive before anyone sees it. A workflow also needs a trigger, and the two that matter here are a webhook endpoint that external systems post to for event-driven work, and a scheduled trigger driven by a cron expression for time-based work.
Error handling is where visual workflows fail quietly. A workflow with none of it simply stops when an upstream service is rate limited or unavailable, the failure lands in an execution log nobody reads, and customers receive nothing at all. The module asks for three mechanisms in every production workflow. An error trigger wired to the main workflow that raises an alert naming the workflow, the failing node and the error. Retry on failure enabled for every node that calls an external service, with a small number of attempts and a wait between them. And a validation branch after the agent node that checks the output is non-empty, plausible and free of an error marker, routing anything doubtful to human review rather than to the customer. The reason for the third is the trap the module names directly: a run can complete without error at the platform level and still have sent an empty, wrong or misaddressed response, so log the content that went out and not only the send status.
The canvas removed the code, not the responsibility: the trigger, the credential scope, the retry policy, the approval gate that defaults to stop and the execution log still decide reliability, so a risky write with no approval and no log leaves nothing to investigate.
A workflow canvas hides code, not responsibility. Triggers, credentials, retries, approvals and logs decide reliability. n8n workflow concepts and the Google SRE workbook are the cited sources.
The Model Context Protocol exists because every provider had invented its own tool-calling format, so the same integration had to be written again for each one. It defines three roles. A host is the application that runs the model. Inside the host sits a client, the protocol layer that connects out. Servers are separate processes or services that expose capabilities and know nothing about which host or model is calling them. The consequence that matters for security is that the model never touches your database or your credentials. It chooses a tool and supplies arguments; the client relays the call; the server executes it with its own permissions. A server exposes three kinds of thing: tools, which are functions the model can execute and which may have side effects, resources, which are context and data to read, and prompts, which are templated workflows a user can invoke.
Transport is a deployment choice rather than a security ranking. The standard input and output transport runs the server as a subprocess of the host, which is right for local developer tooling, file access and command line integrations, and which is secured by process isolation with no network port exposed. A networked transport runs the server as an HTTP service that several clients can reach, which is what a team sharing one server needs, and it is only secure with transport encryption and authentication on every endpoint. Neither is inherently safer. Choosing by who has to reach the server, rather than by which sounds more secure, is the working habit. Building a minimal server is correspondingly small: declare the tools you expose with their input schemas, handle incoming tool calls, and run the transport. Connecting it to a desktop host is a configuration entry naming the command, its arguments and any environment it needs, followed by a restart, with a log file to read when the server fails to start.
The security model is the part people get wrong, and the misconception the module corrects is worth carrying: the server controls its own access, not the model. The model only decides which tool to call and with what arguments. If a server was configured with broad file system reach, that reach exists whatever the model intended. A server running under a local transport has the file system and network access of the user who launched the host, so a file-reading tool that accepts an arbitrary path is an exfiltration route the moment injected text reaches the model. The corrections are all about narrowing what the server will do rather than about instructing the model better. Expose only the tools that are needed. Replace a tool that takes an arbitrary query or path with named, scoped tools that take typed parameters and build safe queries internally. Resolve a path to its canonical form before checking that it sits inside the permitted directory, since blocking a prefix is defeated by traversal. Validate every input, keep internal detail out of error messages, and keep credentials in the environment rather than in source. The protocol's own security guidance points the same way, resting on explicit user consent for data access and tool invocation, and treating tool descriptions from an untrusted server as untrusted input.
A host-owned MCP client uses JSON-RPC over stdio or Streamable HTTP to initialise, discover, call and read, returning structured results or errors. Transport changes where credentials come from, not who approves the call, so the host can refuse a capability the server offers.
MCP standardises the host/client/transport/server exchange. JSON-RPC over stdio or Streamable HTTP, capability negotiation at init, OAuth-style authorisation for HTTP. Spec dated 2025-11-25.
The last module connects the agent to services you do not control, and the first rule is that every HTTP client in a tool function gets an explicit timeout. Without one, a slow service hangs the agent loop indefinitely and blocks whatever was queued behind it. The second rule is that a status code is not a result. A query language interface will commonly answer with a success status and report the failure in an errors field in the body, so a tool that only checks the status code will hand the agent an empty result dressed as a success. Validate the shape of the response, not just its code. The same caution applies to any interface that reports application errors inside a successful envelope.
Authentication has two shapes in this module. A key sent in a header is the simple case, and the rule is that it is loaded from the environment at run time and never written into the function or, worse, into the tool schema, because the schema is text the model reads and can repeat. Delegated authentication uses an authorisation server that issues an access token, which the specification describes as a credential representing an authorisation issued to the client and used to reach protected resources; the client credentials grant is the variant for the case where the scope is limited to resources the client itself controls. The implementation detail that separates working code from code that fails intermittently is refreshing the token before it expires, with a margin, rather than waiting for the first rejection and treating that as the signal.
Rate limiting is where naive retrying makes things worse. Retrying immediately after a too-many-requests response extends the block rather than shortening it, so the delay doubles with each attempt, and a small random jitter is added so that concurrent callers do not all wake at the same instant and trigger the limit again. Where the service sends a retry delay header, honour it in preference to your own calculation; that header is defined to tell the client how long to wait, and it carries either a number of seconds or a date. Retry policy is also a judgement about which failures are transient: rate limiting and a temporarily unavailable service may clear, whereas a rejected credential will be rejected identically on the next attempt, so retrying it wastes quota and hides the real fault. When a service fails persistently rather than transiently, a circuit breaker stops calling it at all for a recovery period, then lets a single probe through and reopens or closes on the result.
Finally, errors have to be shaped for their reader. A raw exception is useless to an agent, which cannot reason about a connection error string, and it is worse than useless if it carries internal structure that the agent may relay to a user. Catch at the tool boundary and return an object with a success flag the agent can test, a category, and a short description in plain language that says what the agent or the user might do next. Log the full detail server side for the developer. This is the same discipline as the module's opening case, in which an internet-facing endpoint required no authentication and an attacker walked the customer records simply by incrementing an identifier: the interface behaved exactly as built, and the fault was in what it was willing to answer. Object level authorisation, which means checking that this caller may access this specific record rather than merely that the endpoint is reachable, is the control that was missing, and an agent tool is the same attack surface in a new wrapper.
Authentication leads with a scoped short-lived token, validation returns an RFC 9457 error on an unknown field, and retry admits idempotent verbs only, backing off and honouring Retry-After. A call that is not safe to repeat falls straight through to the circuit breaker.
Reliability is designed before the first HTTP call: least-privilege auth, schema validation, safe retry, circuit breaker. OWASP ASVS and RFC 9457 are the cited contracts.
Raising the step limit, or the visual equivalent, because the agent keeps reaching it.
Instead: Reaching the limit is a symptom. Read the last few tool calls in the log and look for the actual cause: no completion condition the model can satisfy, a tool error the model reads as a reason to keep trying, or two tools whose descriptions overlap enough that the model alternates between them. Fix the description or the prompt, then leave the limit where it is. A high limit only makes a runaway loop expensive before anyone notices it.
Reaching for the system prompt when the agent calls the wrong tool.
Instead: The system prompt is read once; the tool description is read every time the model considers that tool, which makes it the more direct lever. Rewrite the description first, saying both what the tool is for and what it is not for, and add a sequencing hint to any tool that is meant to end the run. Treat the system prompt as the secondary control.
Passing a model-generated string to a general purpose evaluator, on the basis that it is wrapped in exception handling.
Instead: Exception handling catches the error after the code has already run, so it is not a security boundary and neither is trusting the model to generate only harmless expressions. Use a restricted evaluator that permits arithmetic and blocks imports, attribute access and file access, or a purpose-built interface with an explicit list of permitted operations.
Splitting a task across several agents because specialisation sounds more capable.
Instead: Coordination is not free. Every handoff costs tokens and latency, and a worker that is wrong with confidence can have its output synthesised into a finding. Start with one agent, measure the specific limit that is hurting you, meaning tool confusion, context growth or independent work that could run concurrently, and add a second agent only against that measurement.
Letting agents talk to each other in ordinary prose.
Instead: An unstructured instruction leaves the receiver guessing at the output format, the background to assume and what to do on failure, and an unstructured reply leaves the supervisor interpreting rather than parsing. Carry the sub-task, the context, the expected output format and a fallback instruction in the handoff, and require a status field, a result field and an optional confidence in the reply. Cap delegation depth so a stuck worker cannot delegate back upward for ever.
Treating a completed workflow run as proof that the right thing was sent.
Instead: A run can complete without error at the platform level and still have produced an empty, wrong or misaddressed response. Add a validation branch after the agent step that checks the output is non-empty, within expected bounds and free of an error marker, route anything doubtful to human review, and log the content that went out rather than only the send status. Wire an error trigger so that failures raise an alert naming the workflow, the node and the error instead of settling into a log nobody reads.
Assuming that because the model chooses which tool to call, the model bounds what the tool can reach.
Instead: The server decides its own access; the model only supplies a name and arguments. A server started under a local transport has the file system and network reach of the user who launched the host. Expose only the tools that are needed, replace arbitrary query or path arguments with named tools taking typed parameters, resolve paths to their canonical form before checking they sit inside the permitted directory, and keep credentials in the environment rather than in source or in a schema the model can read.
Retrying every failed request the same way, harder.
Instead: Retry policy is a judgement about which failures are transient. Back off exponentially with jitter on rate limiting and on a temporarily unavailable service, and honour a retry delay header in preference to your own calculation when one is sent. Never retry a rejected credential with the same credential, since the answer will be identical and the retries hide the real fault. Add a circuit breaker so a service that is failing persistently stops being called at all until a single probe succeeds.
Handing the raw exception back to the agent so it has more to work with.
Instead: An agent cannot reason about a stack trace, and internal detail in an error message can be relayed onward to a user. Catch at the tool boundary and return an object carrying a success flag the agent can test, a category and a short plain-language description of what to do next. Keep the full detail in the server-side log where the developer will look for it.
That is the Practical building stage in one place. A loop with three states, a hard limit that is a symptom rather than a setting, and a structured log that turns a run into a readable trace. Tool descriptions as the real control surface, and tools tested in isolation before the loop exists. A second agent justified by a measured limit rather than by ambition, with structured handoffs and parseable replies as the price of admission. A visual canvas that changes who can maintain the workflow while leaving every production obligation in place. A protocol that separates who provides a capability from who consumes it, and a security model in which the server, not the model, decides what can be reached. And outbound calls that carry a timeout, a retry policy that knows which failures are transient, credentials that live in the environment, and errors shaped for the agent rather than for the debugger. The scenario practice now puts that under pressure with realistic situations, so the confusions surface here rather than in Stage 4, where the threat landscape, secure implementation and responsible deployment all assume you can already tell a description fault from a loop fault, a coordination failure from a tool failure, and a transient error from a permanent one, without stopping to look it up.