90 minutes · 4 sub-sections: Architecture · Writing Tools · Dispatch · Security
Where most security vulnerabilities live.
Module 1 gave you the loop's executeTool extension point. This module goes inside it. By the end: the five-part contract, the dispatch sequence, the three injection vectors, and the defenses Module 11 harvests.
| Part | Purpose | What goes wrong if missing |
|---|---|---|
| Name | what the model invokes | ambiguous → wrong-tool calls |
| Description | a PROMPT — model reads to decide when | vague → never called, or called wrong |
| Input schema | types, bounds, defaults | parse failures, untyped garbage, injection |
| Implementation | what the harness executes | bugs, side effects, security holes |
| Error return | what goes back on failure | thrown exceptions → model hallucinates success |
Every tool in every production harness has all five. Missing any degrades reliability or security.
"Reads a file.""Reads a UTF-8 text file from the workspace. Returns full contents under 10k lines; else first 500 + line count. Use for inspecting file contents. NOT for binary — use read_binary. Paths relative to workspace root."
| Pi (4 tools) | Claude Code (40+) | |
|---|---|---|
| Decision noise | minimal | high (mitigated by dynamic registration) |
| Capability | personal assistant | enterprise multi-channel |
| Correct for | thin use cases | thick use cases |
The question isn't "how many tools?" It's: what's the minimum tool set that maximizes capability without decision noise? Most teams err on too many.
| Pattern | Tradeoff |
|---|---|
| Static (Pi: 4 fixed) | fewer choices, lower injection surface; less extensibility |
| Dynamic (Claude Code) | right tools at right time; harder to audit |
| MCP (external servers) | decoupled, swappable; NEW attack surface (M11, S12) |
| Schema-first (Pydantic/Zod) | eliminates parse failures; boilerplate |
| Free-text dispatch (old Aider) | simple; brittle — model drift breaks parsing |
Module 0.3's anchor: tool outputs are 67.6% of context. Output formatting is the highest-leverage decision in tool design.
truncated: true + guidance.[{path, line, preview}], not raw grep dumps. More token-efficient, more model-legible."truncated: showing 50 of 312. Narrow your pattern." Prompt engineering inside the tool result.| Tool | Idempotent? |
|---|---|
read_file | ✅ yes (reading twice is harmless) |
write_file | ⚠️ if same content; problematic if model retries with different content |
bash | ❌ almost never (git commit twice = 2 commits) |
send_email | ❌ never (2 emails sent) |
For non-idempotent tools: deduplicate via request ID, content-hash check, or "already sent" guard. Idempotency is a tool-design responsibility.
// WRONG — model gets generic "tool failed"
throw new Error("search failed");
// RIGHT — model knows what failed and whether to retry
return {
ok: false,
error: "search failed: timeout after 5000ms",
retryable: true // → model retries with backoff
};
Four categories (Module 7): transient (retry) · LLM-recoverable (self-correct) · user-fixable (interrupt) · fatal (halt). The tool provides the info; the loop categorizes.
Model emits tool_use
│
1. Parse (structured JSON or regex)
│ └ parse failure → precise error to model
2. Validate name against registry
│ └ unknown → error
3. Schema-validate inputs
│ └ invalid → precise error ("pattern required, string; got number")
4. Permission check (Module 6)
│ └ denied → permission error
5. Execute implementation
6. Format result (truncate + structure)
7. Return to model as tool result in context
Each step is a failure point AND an injection point. Structured output (JSON tool_use) eliminates step-1 parse failures.
MCP = ASI04 (Agentic Supply Chain) vulnerability if trusted blindly. Module 11.2 + Course 2 S12 cover tool-definition poisoning, evil-twin attacks, signed manifests.
| Vector | Mechanism | Defense |
|---|---|---|
| 1. Tool OUTPUT | attacker controls file/page/API response; contains instructions | untrusted-content tagging |
| 2. Tool DEFINITION (MCP) | malicious server's description has hidden instructions | signed manifests + runtime verify |
| 3. Dispatch-time | crafted output with fake closing tag corrupts parsing | structured output (JSON), not text parsing |
Vector 1 is the most common and most dangerous. Module 11.3 implements the defense; the lab verifies it defeats a crafted payload.
Defense in depth: tools declare required capabilities; harness checks before executing.
const tools = [
{ name: "read_file", capabilities: ["fs:read"], ... },
{ name: "write_file", capabilities: ["fs:write"], ... },
{ name: "bash", capabilities: ["shell"], ... },
{ name: "search_web", capabilities: ["network"], ... }
];
function executeTool(tc, agentPerms) {
const tool = registry[tc.name];
const allowed = tool.capabilities.every(c => agentPerms.has(c));
if (!allowed) return { ok: false, error: "permission denied" };
return tool.execute(tc.input);
}
A subagent with {fs:read} cannot call bash — regardless of model requests. Permission at the dispatch boundary, not the prompt. Module 0.2's governance principle, at the tool layer.
{ok, error, retryable}.Next: Module 3 — Context Management. The hardest engineering problem in production harnesses.