Files
cleveragents-core/docs/adr/ADR-011-tool-system.md
freemo c2db74ba81
CI / lint (push) Successful in 15s
CI / build (push) Successful in 16s
CI / quality (push) Successful in 20s
CI / security (push) Successful in 35s
CI / typecheck (push) Successful in 42s
CI / benchmark-regression (push) Has been skipped
CI / unit_tests (push) Successful in 2m38s
CI / integration_tests (push) Successful in 3m11s
CI / docker (push) Successful in 39s
CI / coverage (push) Successful in 4m58s
CI / benchmark-publish (push) Successful in 17m32s
Docs: Restyled ADR pages
2026-03-10 12:38:35 -04:00

196 lines
12 KiB
Markdown

---
adr_number: 11
title: Tool System
status_history:
- - '2026-02-16'
- Proposed
- Jeffrey Phillips Freeman
- - '2026-02-16'
- Accepted
- Jeffrey Phillips Freeman
tier: 2
authors:
- Jeffrey Phillips Freeman
superseded_by: null
related_adrs:
- number: 2
title: Namespace System
relationship: Tools use namespaced identifiers for discovery and conflict resolution
- number: 4
title: Data Validation
relationship: Tool input/output schemas are Pydantic models with JSON Schema generation
- number: 8
title: Resource System
relationship: Tools declare resource bindings that determine which resources they operate on
- number: 10
title: Actor and Agent Architecture
relationship: Tools serve as atomic execution nodes within actor graphs
- number: 12
title: Skill System
relationship: Skills compose tools into reusable collections for actor assignment
- number: 13
title: Validation Abstraction
relationship: Validations are a specialized Tool subtype with structured pass/fail semantics
- number: 15
title: Sandbox and Checkpoint
relationship: Tool invocations create checkpoints for rollback and recovery
- number: 28
title: Agent Skills Standard (AgentSkills.io)
relationship: Agent Skills are a tool source (`agent_skill`) integrated into the Tool Registry
- number: 29
title: Model Context Protocol (MCP) Adoption
relationship: MCP tools are registered as Tool records and follow the four-stage tool lifecycle
- number: 30
title: Skill Abstraction Definition
relationship: Tools are the atomic operations that skills collect and present to actors
- number: 37
title: Tool Reachability and Access Projection
relationship: Extends resource bindings with transitive reachability, access projection, and read/write routing
acceptance:
votes_for:
- voter: Jeffrey Phillips Freeman <Jeffrey.Freeman@CleverThis.com>
comment: The four-stage lifecycle with dual role as skill component and graph node is a clean unification
votes_against: []
abstentions: []
---
## Context
CleverAgents must integrate tools from multiple sources — built-in file/git operations, MCP server-hosted tools, Agent Skills (SKILL.md-based instructions), and custom user-defined tools. Each source has different discovery mechanisms, execution protocols, and capability characteristics. The system needs a uniform tool abstraction that normalizes these differences while preserving source-specific capabilities like checkpointing, resource binding, and sandbox awareness.
## Decision Drivers
- Must integrate tools from four distinct sources (built-in, MCP servers, Agent Skills, custom) with different discovery and execution protocols
- Tools must serve a dual role: as LLM tool-calling targets within skills and as deterministic graph nodes in actor workflows
- Resource bindings must decouple tools from specific resources, enabling portability across projects
- Need a uniform four-stage lifecycle (discover, activate, execute, deactivate) to normalize source-specific differences
- Change tracking must be driven by tool invocations, not by parsing LLM output, to ensure reliable auditable changesets
- Capability metadata (read-only, checkpointable, side effects) must be declared per tool to enable safety enforcement at runtime
## Decision
A **tool** is the atomic unit of execution — the smallest piece of functionality that can read, write, or transform resources. Tools are namespaced, independently registered, and follow a **four-stage lifecycle** (discover, activate, execute, deactivate). Every tool has a uniform interface regardless of source, and plays a **dual role**: as a component of a skill (available for LLM tool-calling) and as a tool node in an actor graph (deterministic, non-LLM execution step).
## Design
### Tool Identity and Registration
Tools follow the namespace system (`[[server:]namespace/]name`). They are defined in YAML, registered via `agents tool add --config <FILE>`, and stored in the persistent **Tool Registry**. The registry stores: name, description, source, config path, input/output schemas (JSON Schema), capability metadata, resource slots, and implementation code or server reference.
### Tool Sources
| Source | Discovery | Execution | Examples |
|--------|-----------|-----------|---------|
| **Built-in** | Hardcoded descriptors | Native Python calls | `read_file`, `write_file`, `edit_file`, `search_files` |
| **MCP** | MCP server `tools/list` | JSON-RPC `tools/call` | Any MCP-compliant server tool |
| **Agent Skill** | SKILL.md frontmatter parsing | Agent-mediated instruction following | Instruction-based tools from SKILL.md files |
| **Custom** | YAML registration | User-defined implementation | User-created tools |
### Four-Stage Lifecycle
**discover()**: Find and catalog the tool. Built-in: return hardcoded descriptors. MCP: spawn server, `initialize` handshake, `tools/list`. Agent Skill: parse SKILL.md frontmatter (~50-100 tokens). Custom: parse YAML definition.
**activate()**: Prepare the tool for use. Built-in: no-op. MCP: ensure server running, register for `notifications/tools/list_changed`. Agent Skill: load full SKILL.md body into agent context. Custom: load implementation.
**execute(params, ctx)**: Run the tool. Built-in: call native Python. MCP: translate to `tools/call` JSON-RPC, rewrite sandbox paths, validate params, record changes. Agent Skill: agent-mediated execution following instructions. Custom: user-defined execution.
**deactivate()**: Clean up. MCP: clean server shutdown. Agent Skill: remove instructions from context. Others: no-op.
### Dual Role
**As a skill component**: When an actor references a skill, all tools in that skill become available for LLM tool-calling. The LLM generates tool call requests, and the Tool Router validates and dispatches them.
**As an actor graph node**: `type: tool` nodes in actor graphs execute deterministically without LLM involvement. Can reference a named registered tool or define an anonymous inline tool.
### Tool Interface
```
Tool:
Identity: name, qualified_name, source (mcp | agent_skill | builtin | custom)
Schema: input_schema (JSONSchema), output_schema (JSONSchema)
CapabilityMetadata: read_only, writes, write_scope, idempotent,
checkpointable, checkpoint_scope, side_effects,
cost_profile, human_approval_required
ResourceBindings: slots (Map<String, ResourceSlot>)
Lifecycle: discover(), activate(), execute(params, ctx), deactivate()
```
### Tool Adapter Layer
Each source has an adapter that translates to the uniform interface:
- **MCPToolAdapter**: Manages MCP server lifecycle, JSON-RPC communication, sandbox path rewriting, and capability inference from tool names.
- **AgentSkillAdapter**: Three-tier progressive disclosure (metadata ~50-100 tokens → instructions < 5000 tokens → resources variable).
- **BuiltinToolAdapter**: Direct native Python execution with hardcoded capability metadata.
- **CustomToolAdapter**: User-defined implementation with manually declared capabilities.
### Resource Bindings
Tools declare **resource slots** — named slots specifying what resource type and access mode is needed. Three binding modes:
- **Contextual** (default): Resolved from the plan's project at activation time.
- **Static**: Hardcoded to a specific resource by name via `bind` field.
- **Parameter**: Passed as argument at invocation time via `from_param` field.
Built-in tool groups have implicit resource slots (e.g., `file_operations` implicitly binds a `directory` slot of type `fs-directory` or `git-checkout`).
### Capability Metadata
Every tool declares capabilities: `read_only`, `writes`, `write_scope` (file paths, resource slots, environment), `idempotent`, `checkpointable`, `checkpoint_scope` (file, transaction, commit, snapshot), `side_effects`, `cost_profile`, and `human_approval_required`.
Metadata origin varies by source: built-in (hardcoded, not overridable), MCP (inferred + overrides), Agent Skill (SKILL.md frontmatter + overrides), custom (manually declared).
### Metadata Overrides
When referencing a named tool in a skill or actor graph, `capability` and `description` fields can be overridden at the point of use. Overrides are shallow-merged, never persist back to the registry, and cannot override schema or built-in metadata.
### Anonymous Tools
Inline tool definitions in skill YAML or actor graph nodes. Same format as named tools but unregistered, unnamespaced, and scoped only to their defining context.
### Tool Execution Flow
Six-step pipeline: LLM generates call → Tool Router validates → Resource binding resolution and sandbox setup → Adapter-specific execution → Change recording → Result return.
### Change Tracking
The changeset is built from tool invocations, not by parsing LLM output. Each tool execution receives a `ToolExecutionContext` with sandbox, plan, resources, and a changes recorder. Write operations record their changes directly.
## Constraints
- Tools must not parse LLM output to extract code or changes. All resource modification happens through tool invocations.
- `read_only: true` actions can only use tools where `read_only: true`. Enforced at runtime.
- Tool input/output schemas are JSON Schema, generated from Pydantic models where applicable.
- Anonymous tools share the same format as named tools but are scoped only to their defining skill or actor graph.
- Maximum tool calls per actor step is controlled by `plan.tool.max-calls-per-step` (default: 25).
- Maximum retries for transient failures is controlled by `plan.tool.max-retries` (default: 3).
## Consequences
### Positive
- A single tool interface normalizes four different sources (built-in, MCP, Agent Skill, custom) behind the same lifecycle and capability model.
- The dual role (skill component + graph node) maximizes tool reuse across LLM-driven and deterministic execution.
- Resource bindings decouple tools from specific resources, enabling portability across projects.
- Change tracking from tool invocations ensures reliable, auditable changesets.
### Negative
- The adapter layer adds complexity — each source requires its own adapter implementation with source-specific behavior.
- Capability inference for MCP tools (from tool names) is heuristic and may be inaccurate.
- The four-stage lifecycle adds overhead for simple tools that need no activation or deactivation.
### Risks
- MCP server availability is an external dependency. Server crashes or network failures during execution require retry and recovery logic.
- Agent Skill tools depend on LLM instruction-following quality, which is less reliable than programmatic execution.
- The capability metadata for custom and MCP tools is self-declared and may be inaccurate.
## Alternatives Considered
None — specification-driven requirement. The tool system with its four-stage lifecycle, dual role, adapter layer, and resource binding model is prescribed by the specification.
## Compliance
- **Adapter conformance tests**: Each adapter (MCP, Agent Skill, Built-in, Custom) has tests verifying correct lifecycle behavior across all four stages.
- **Resource binding tests**: Tests verify all three binding modes with correct resolution and error handling for missing bindings.
- **Capability enforcement tests**: Tests verify that `read_only` actions reject tools with `writes: true`, and that `human_approval_required` tools trigger approval gates.
- **Change tracking tests**: Tests verify that the changeset accurately reflects all tool invocations and contains no changes from LLM output parsing.
- **Schema validation**: Tool registration validates input/output schemas against JSON Schema specification.