Capabilities and safeguards for AI agents
AI agents with ordered toolsets and limits fixed in code, so what they can do doesn't rest on a prompt.
By: Benjamin Clark
Intent
This pattern builds capabilities and safeguards into an AI agent using pydantic-ai: toolsets bundled with their own instructions, an order those tools must be used in, and limits on what they can reach that are set in code for each request. This enables three outcomes:
- A new agent is a prompt, an output type and a choice of capabilities, rather than a new build.
- A manipulated prompt can't widen what the agent reaches, or make it skip a step.
- Context budgets, errors and logging are handled once, however many tools an agent has.
Motivation
An AI agent usually starts as a system prompt and a few tools, and the rules on how to use them are written into that prompt. As the tools grow, this fails in three ways:
- Safety rests on prose. The model can talk past its instructions, and a prompt injection can change what it does.
- The prompt and the context fill up. Guidance for every tool competes for space, and one large tool response can overflow the model's input.
- Nothing is consistent. Each agent handles errors and logging in its own way, so nobody can say what it did or why.
This pattern moves those rules out of the prompt and into code. Each capability carries its own tools and instructions, and can load only when a question needs it. Hooks enforce the order of tool calls, and send a call made out of order back to the model to re-plan. What the tools can reach is fixed for each request, outside the model, where an injected instruction can't change it. The prompt only has to describe the job. The two variants below differ in where these pieces live: in each agent, or in a library every agent shares.
Applicability
Use this pattern when:
- An AI agent needs tools to answer, such as reading data, calling a service or reading a file.
- Its tools must be used in a set order, such as reading an item's state before drilling into it.
- What the tools can reach must be enforced in code, not left to the prompt.
- Several agents should handle context, errors and logging the same way.
Do not use this pattern when:
- The task is single-shot text with no tools, such as generating a label, where one model call with a structured output is enough.
Structure
Variant A: Self-contained agents
When a business runs one or two agents, or its agents belong to separate teams that release on their own schedules, this variant works best.
- Each agent's service holds its own agent, capabilities and hooks, beside the code that runs it.
- Each agent handles context, errors and logging itself, following the same design as the others.
- Changing an agent's prompt or capabilities means releasing only that agent's service.
Variant B: A shared agent library
When several services run agents, or one team owns how every agent behaves, this variant works best.
- One generic agent lives in a shared library, with the hooks that enforce the order of tool calls and log every one, so every agent behaves the same way.
- Each agent is a persona (a system prompt, an output type and a set of capabilities) built on that generic agent, so a new agent is configuration rather than new code.
- Capabilities that several agents need live in the library beside it, so those agents share one copy.
- Agent support is an optional part of the library, so services without an agent never install the agent framework.
Participants
| Participant | Role |
|---|---|
| Caller | A user or an event that asks the agent for something. |
| Handler | Builds the per-request dependencies, runs the agent and saves each exchange. |
| Agent | A persona: a system prompt, an output type and a set of capabilities. |
| Model | Chooses which capability to load and which tool to call, and returns a typed result. |
| Hooks | Enforce the order of tool calls, and log every one. |
| Capability | A toolset with its own instructions, available upfront or loaded on demand. |
| Target | What the tools reach, such as an API, a file or a service, within the scope set for the request. |
Constraints and trade-offs
- Safeguards limit what an agent can do, not whether it's right. Language models are non-deterministic, so the same question can get a different answer, and an agent can make something up within its limits. Anything that matters still needs checking, by enforced validation or by a person.
- Every tool call is a round trip back into the model's context, which adds latency and uses more tokens, so an agent with tools is slower and dearer than one model call.
- Prompts and capabilities live in code, so changing one takes a release rather than a live edit.
Variant A: Self-contained agents trade-offs
- The core benefit is independence: each agent changes, and upgrades its framework, on its own schedule, without affecting any other.
- The core cost is consistency: each agent keeps its own copy of the shared behaviour, so the copies drift apart, and a fix made in one has to be repeated in the others.
Variant B: A shared agent library trade-offs
- The core benefit is consistency: context budgets, errors, logging and the order of tool calls are handled once, and every agent gets each fix with its next upgrade.
- The core cost is coupling: changing a prompt means releasing the library and upgrading each service that uses it, and a framework upgrade reaches every agent at once.
Part of how I deliver
An agent on your own data starts here. It's one of many patterns I bring through contract delivery, and I combine it with whichever others suit your systems, your data and the way your team already ships.
AI in your systems, safely
I put chatbots and MCP servers into the systems businesses already run, reaching only what they should. If you want one on yours, book a call and we'll talk it through.
