12 KiB
adr_number, title, status_history, tier, authors, superseded_by, related_adrs, acceptance
| adr_number | title | status_history | tier | authors | superseded_by | related_adrs | acceptance | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 11 | Tool System |
|
2 |
|
null |
|
|
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
bindfield. - Parameter: Passed as argument at invocation time via
from_paramfield.
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: trueactions can only use tools whereread_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_onlyactions reject tools withwrites: true, and thathuman_approval_requiredtools 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.