| # ADR-0012: Capability-Based Tool Contract |
|
|
| ## Status |
|
|
| Accepted |
|
|
| ## Context |
|
|
| The manifest carried a `tools` list, but nothing stood behind it — the fourth |
| stable contract (tools) was named, not implemented. Agents need to call tools |
| (image generation, retrieval, oracles), and "the Artist gets image-gen, the |
| Critic does not" must be enforced by the runtime, not by convention. |
|
|
| ## Decision |
|
|
| Add `ToolRegistry` (`src/tools/registry.py`) as a capability-checked broker. |
| Agents never hold a tool; they ask the registry, which checks the calling agent's |
| `manifest.tools` grant before dispatching, raising `CapabilityViolation` on a |
| denied call. Tools are `(name, description, run)` triples; `run(**params)` |
| returns a JSON-serialisable dict the agent folds into its event. |
|
|
| `ManifestAgent` exposes `call_tool()` (capability-checked) and injects granted |
| tool descriptions into the prompt. A built-in deterministic `oracle` tool plus a |
| `fortune-teller` handler agent exercise the whole path end-to-end (scenario |
| `oracle-grove`), with its result recorded on the ledger. |
|
|
| Live MCP servers are deliberately **deferred**: the same `(name, description, |
| run)` interface fronts an in-process callable today and an MCP-server-backed tool |
| later, so swapping one for the other is invisible to agents. |
|
|
| ## Consequences |
|
|
| - Least-privilege tool access is enforced at runtime and is testable. |
| - Tool output is first-class ledger data, not a side channel. |
| - The contract is stable across in-process tools and future MCP servers. |
| - A tool-using agent and a tool-less agent can sit in the same cast, scoped |
| independently — demonstrated in `oracle-grove`. |
|
|