- document the CLI workflow for registering tools, wrapping validations, and querying registry metadata - index the new example in docs/showcase/examples.json - re-enable coverage threshold regression checks by removing obsolete tdd_expected_fail tags ISSUES CLOSED: #4565 Refs: #4305 Refs: #4227
26 KiB
Managing Tools and Validations in CleverAgents
Overview
CleverAgents provides a unified tool registry where you can register, inspect, and manage two kinds of callable operations: tools (general-purpose functions) and validations (pass/fail quality gates). This example walks through the complete lifecycle — registering tools from YAML config files, listing them with filters, inspecting details, and removing them — all from the CLI.
Prerequisites
- CleverAgents installed (
pip install cleveragents) - Python 3.13 or higher
What You'll Learn
- How to register a custom tool from a YAML configuration file
- How to register validations — both standalone (custom code) and wrapped (delegating to an existing tool)
- How to list all tools and validations with optional type/namespace filters
- How to inspect a specific tool to see its capability flags, resource slots, and validation mode
- How to remove a tool from the registry
- How to use
--format jsonfor machine-readable output in scripts
Step-by-Step Walkthrough
Step 1: Register a custom tool from YAML
Create a YAML file describing your tool (see examples/tools/custom-tool.yaml
for the full schema):
# examples/tools/custom-tool.yaml
name: local/line-counter
description: Count lines in a file
source: custom
code: |
import pathlib
path = pathlib.Path(inputs["file_path"])
count = len(path.read_text().splitlines())
return {"line_count": count}
input_schema:
type: object
required:
- file_path
properties:
file_path:
type: string
description: Path to the file to count lines in
output_schema:
type: object
properties:
line_count:
type: integer
capability:
read_only: true
writes: false
idempotent: true
resource_slots:
- name: target_file
resource_type: fs-mount
access: read_only
description: File system containing the target file
binding: contextual
timeout: 60
Register it with:
$ agents tool add --config examples/tools/custom-tool.yaml
Expected Output:
╭─────────── Tool Registered ───────────╮
│ Name: local/line-counter │
│ Namespace: local │
│ Short Name: line-counter │
│ Description: Count lines in a file │
│ Source: custom │
│ Type: tool │
│ Timeout: 60s │
│ Capability: │
│ read_only: True │
│ writes: False │
│ checkpointable: False │
│ idempotent: True │
│ unsafe: False │
│ human_approval_required: False │
│ Resource Slots: │
│ - target_file (fs-mount, read_only) │
│ Lifecycle: │
│ (none) │
╰───────────────────────────────────────╯
What's Happening:
The tool add command reads the YAML file, validates the schema, and persists
the tool definition to the local tool registry (SQLite database). The
capability block is especially important — it tells the plan executor whether
the tool is safe to run without human approval, whether it can be checkpointed,
and whether it has side effects.
The namespaced name local/line-counter uses the local namespace, which is
the conventional namespace for project-specific tools. Built-in tools use
provider namespaces like devops/ or qa/.
Step 2: Register a required validation (custom code)
Validations are pass/fail quality gates. A required validation must pass
before a plan step can proceed. Register one from YAML:
# examples/validations/required-validation.yaml
name: qa/coverage-check
description: Verify test coverage meets the required threshold
source: custom
mode: required
code: |
import json
report = json.loads(inputs["coverage_json"])
total = report.get("totals", {}).get("percent_covered", 0)
passed = total >= inputs.get("threshold", 80)
return {
"passed": passed,
"data": {"coverage_percent": total},
"message": f"Coverage {total}% {'meets' if passed else 'below'} threshold"
}
$ agents validation add --config examples/validations/required-validation.yaml
Expected Output:
╭──────────────────── Validation Registered ─────────────────────╮
│ Name: qa/coverage-check │
│ Description: Verify test coverage meets the required threshold │
│ Source: custom │
│ Mode: required │
╰────────────────────────────────────────────────────────────────╯
What's Happening:
The validation is stored in the same tool registry as regular tools, but with
tool_type = validation. The mode: required field means this validation
blocks plan execution if it fails. The inline code block is a Python snippet
that receives inputs and must return a dict with a passed boolean.
Step 3: Register a wrapped validation
A wrapped validation delegates execution to an existing tool and transforms its output into a pass/fail result:
# examples/validations/wrapped-validation.yaml
name: qa/lint-check
description: Validate that linting passes by wrapping the lint tool
wraps: devops/run-linter
mode: required
transform: |
def transform(result):
errors = result.get("errors", [])
passed = len(errors) == 0
return {
"passed": passed,
"data": {"error_count": len(errors), "errors": errors[:5]},
"message": f"{len(errors)} lint errors found" if errors else "No lint errors"
}
$ agents validation add --config examples/validations/wrapped-validation.yaml
Expected Output:
╭─────────────────────────── Validation Registered ────────────────────────────╮
│ Name: qa/lint-check │
│ Description: Validate that linting passes by wrapping the lint tool │
│ Source: wrapped │
│ Mode: required │
│ Wraps: devops/run-linter │
│ Transform: def transform(result): │
│ errors = result.get("errors", []) │
│ passed = len(errors) == 0 │
│ return { │
│ "passed": passed, │
│ "data": {"error_count": len(errors), "errors": errors[:5]}, │
│ "message": f"{len(errors)} lint errors found" if errors else "No lint │
│ errors" │
│ } │
╰──────────────────────────────────────────────────────────────────────────────╯
What's Happening:
The wraps field points to an existing tool (devops/run-linter). When the
validation runs, CleverAgents calls that tool and passes the result through the
transform function. This lets you reuse existing tools as quality gates
without duplicating logic.
Step 4: List all tools and validations
$ agents tool list
Expected Output:
Tools (3 total)
┏━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━┓
┃ Name ┃ Type ┃ Source ┃ Description ┃ Timeout ┃
┡━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━┩
│ local/line-counter│ tool │ custom │ Count lines in a file │ 60 │
│ qa/coverage-check │ validation │ custom │ Verify test coverage │ 120 │
│ │ │ │ meets the requir... │ │
│ qa/lint-check │ validation │ wrapped │ Validate that linting │ 180 │
│ │ │ │ passes by wrapp... │ │
└───────────────────┴────────────┴─────────┴─────────────────────────┴─────────┘
What's Happening:
tool list shows both tools and validations in a single unified view. The
Type column distinguishes them. The Source column shows how each was
defined: custom (inline Python code), wrapped (delegates to another tool),
or mcp (delegates to an MCP server tool).
Step 5: Filter by type
Show only validations:
$ agents tool list --type validation
Expected Output:
Tools (2 total)
┏━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━┓
┃ Name ┃ Type ┃ Source ┃ Description ┃ Timeout ┃
┡━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━┩
│ qa/coverage-check │ validation │ custom │ Verify test coverage │ 120 │
│ │ │ │ meets the requir... │ │
│ qa/lint-check │ validation │ wrapped │ Validate that linting │ 180 │
│ │ │ │ passes by wrapp... │ │
└───────────────────┴────────────┴─────────┴─────────────────────────┴─────────┘
Show only tools (not validations):
$ agents tool list --type tool
Expected Output:
Tools (1 total)
┏━━━━━━━━━━━━━━━━━┳━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━┓
┃ Name ┃ Type ┃ Source ┃ Description ┃ Timeout ┃
┡━━━━━━━━━━━━━━━━━╇━━━━━━╇━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━┩
│ local/line-counter│ tool │ custom │ Count lines in a file │ 60 │
└─────────────────┴──────┴────────┴─────────────────────────────┴─────────┘
Filter by namespace:
$ agents tool list --namespace qa
Expected Output:
Tools (2 total)
┏━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━┓
┃ Name ┃ Type ┃ Source ┃ Description ┃ Timeout ┃
┡━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━┩
│ qa/coverage-check │ validation │ custom │ Verify test coverage │ 120 │
│ qa/lint-check │ validation │ wrapped │ Validate that linting │ 180 │
└───────────────────┴────────────┴─────────┴─────────────────────────┴─────────┘
What's Happening:
The --type flag accepts tool or validation. The --namespace flag
filters by the prefix before the / in the tool name. You can also pass a
regex pattern as a positional argument: agents tool list "line-.*".
Step 6: Inspect a specific tool
$ agents tool show local/line-counter
Expected Output:
╭─────────── Tool Details ───────────╮
│ Name: local/line-counter │
│ Namespace: local │
│ Short Name: line-counter │
│ Description: Count lines in a file │
│ Source: custom │
│ Type: tool │
│ Timeout: 60s │
│ Capability: │
│ (default) │
│ Resource Slots: │
│ (none) │
│ Lifecycle: │
│ (none) │
╰────────────────────────────────────╯
Inspect a validation to see its mode:
$ agents tool show qa/coverage-check
Expected Output:
╭───────────────────────── Tool Details ─────────────────────────╮
│ Name: qa/coverage-check │
│ Namespace: qa │
│ Short Name: coverage-check │
│ Description: Verify test coverage meets the required threshold │
│ Source: custom │
│ Type: validation │
│ Timeout: 120s │
│ Capability: │
│ (default) │
│ Resource Slots: │
│ (none) │
│ Lifecycle: │
│ (none) │
│ Validation Mode: required │
╰────────────────────────────────────────────────────────────────╯
What's Happening:
tool show works for both tools and validations — use the full namespaced name
(namespace/short-name). For validations, the panel includes an extra
Validation Mode field showing required or informational.
Why do capabilities show as
(default)? The detail view collapses the default capability flags and omits resource slot bindings when they match the defaults stored in the registry. The values you set during registration are still persisted and enforced when the tool runs, even though the summary view shows(default)/(none).
Step 7: Get machine-readable JSON output
$ agents tool list --format json
Expected Output:
{
"command": "",
"status": "ok",
"exit_code": 0,
"data": [
{
"name": "local/line-counter",
"description": "Count lines in a file",
"source": "custom",
"tool_type": "tool",
"namespace": "local",
"short_name": "line-counter",
"capability": {},
"timeout": 60
},
{
"name": "qa/coverage-check",
"description": "Verify test coverage meets the required threshold",
"source": "custom",
"tool_type": "validation",
"namespace": "qa",
"short_name": "coverage-check",
"capability": {},
"timeout": 120
},
{
"name": "qa/lint-check",
"description": "Validate that linting passes by wrapping the lint tool",
"source": "wrapped",
"tool_type": "validation",
"namespace": "qa",
"short_name": "lint-check",
"capability": {},
"timeout": 180
}
],
"timing": {
"duration_ms": 0
},
"messages": [
{
"level": "ok",
"text": "ok"
}
]
}
Show a single tool in JSON:
$ agents tool show local/line-counter --format json
Expected Output:
{
"command": "",
"status": "ok",
"exit_code": 0,
"data": {
"name": "local/line-counter",
"description": "Count lines in a file",
"source": "custom",
"tool_type": "tool",
"namespace": "local",
"short_name": "line-counter",
"capability": {},
"timeout": 60
},
"timing": {
"duration_ms": 0
},
"messages": [
{
"level": "ok",
"text": "ok"
}
]
}
What's Happening:
All tool commands support --format json (or -f json). The response uses the
standard CleverAgents JSON envelope: data holds the payload, status is
"ok" on success, and timing.duration_ms shows elapsed time. For list
commands, data is an array; for show, data is a single object.
Step 8: Update an existing tool or validation
If you modify the YAML and want to re-register without removing first:
$ agents validation add --config examples/validations/required-validation.yaml --update
Expected Output:
╭──────────────────── Validation Updated ────────────────────────╮
│ Name: qa/coverage-check │
│ Description: Verify test coverage meets the required threshold │
│ Source: custom │
│ Mode: required │
╰────────────────────────────────────────────────────────────────╯
What's Happening:
Without --update, re-registering an existing name raises an error. The
--update flag performs an upsert — it updates the existing record in place.
The same flag is available on tool add --update.
Step 9: Remove a tool
$ agents tool remove --yes local/line-counter
Expected Output:
Removed tool: local/line-counter
What's Happening:
tool remove soft-deletes the tool from the registry. The --yes flag skips
the interactive confirmation prompt, which is useful in scripts. Without
--yes, the CLI will ask you to confirm before deleting.
Scripting with JSON Output
Because all commands support --format json, you can integrate tool management
into CI/CD pipelines:
# Check if a specific tool is registered
$ agents tool list --format json | jq -r '.data[] | select(.name == "qa/coverage-check") | .name'
qa/coverage-check
# List all validation names
$ agents tool list --type validation --format json | jq -r '.data[].name'
qa/coverage-check
qa/lint-check
# Check the timeout for a tool
$ agents tool show local/line-counter --format json | jq '.data.timeout'
60
# Count registered tools
$ agents tool list --format json | jq '.data | length'
3
# List all tools in the qa namespace
$ agents tool list --namespace qa --format json | jq -r '.data[].name'
qa/coverage-check
qa/lint-check
Tool vs Validation: When to Use Each
| Aspect | Tool | Validation |
|---|---|---|
| Purpose | General-purpose callable operation | Pass/fail quality gate |
| Return value | Any structured data | Must include passed: bool |
| Modes | N/A | required (blocks) or informational (advisory) |
| Source types | custom, mcp |
custom, wrapped |
| Used in plans | As action steps | As pre/post conditions |
| Namespace convention | local/, devops/ |
qa/ |
Complete Interaction Log
Click to see the full verified command sequence
# 1. Register a custom tool
$ agents tool add --config examples/tools/custom-tool.yaml
╭─────────── Tool Registered ───────────╮
│ Name: local/line-counter │
│ Source: custom │
│ Type: tool │
│ Timeout: 60s │
│ Capability: read_only: True, ... │
╰───────────────────────────────────────╯
# 2. Register a required validation (custom code)
$ agents validation add --config examples/validations/required-validation.yaml
╭──────────────────── Validation Registered ─────────────────────╮
│ Name: qa/coverage-check │
│ Mode: required │
╰────────────────────────────────────────────────────────────────╯
# 3. Register a wrapped validation
$ agents validation add --config examples/validations/wrapped-validation.yaml
╭─────────────────────────── Validation Registered ────────────────────────────╮
│ Name: qa/lint-check │
│ Source: wrapped │
│ Wraps: devops/run-linter │
╰──────────────────────────────────────────────────────────────────────────────╯
# 4. List all (tools + validations unified)
$ agents tool list
Tools (3 total)
┃ Name ┃ Type ┃ Source ┃ Description ┃ Timeout ┃
│ local/line-counter│ tool │ custom │ Count lines in a file │ 60 │
│ qa/coverage-check │ validation │ custom │ Verify test coverage... │ 120 │
│ qa/lint-check │ validation │ wrapped │ Validate that linting...│ 180 │
# 5. Filter by type
$ agents tool list --type validation
# → shows only qa/coverage-check and qa/lint-check
$ agents tool list --type tool
# → shows only local/line-counter
# 6. Filter by namespace
$ agents tool list --namespace qa
# → shows qa/coverage-check and qa/lint-check
# 7. Inspect a tool
$ agents tool show local/line-counter
# → shows capability flags, resource slots, lifecycle
# 8. Inspect a validation (shows Validation Mode)
$ agents tool show qa/coverage-check
# → shows Validation Mode: required
# 9. JSON output for scripting
$ agents tool list --format json
# → {"status": "ok", "data": [...], "timing": {...}}
$ agents tool show local/line-counter --format json
# → {"status": "ok", "data": {...}, "timing": {...}}
# 10. Update without removing
$ agents validation add --config examples/validations/required-validation.yaml --update
# → Validation Updated panel
# 11. Remove a tool
$ agents tool remove --yes local/line-counter
# → Removed tool: local/line-counter
Key Takeaways
- Tools and validations share a unified registry —
tool listshows both; use--type toolor--type validationto filter. - Namespaced names (
namespace/short-name) uniquely identify every tool. Uselocal/for project-specific tools andqa/for quality gates. - Validations have a
mode—requiredblocks plan execution on failure;informationalis advisory only. - Wrapped validations reuse existing tools as quality gates via a
transformfunction — no code duplication needed. --updateflag enables safe re-registration when you modify a YAML config without needing to remove and re-add.--format jsonworks on all tool commands — use it to integrate tool management into CI/CD scripts withjq.
Try It Yourself
Now that you've seen the full tool and validation lifecycle, try these variations:
- Register the MCP tool example:
agents tool add --config examples/tools/mcp-tool.yaml - Filter by source:
agents tool list --source wrapped - Regex filter:
agents tool list "qa/.*" - CI check:
agents tool list --format json | jq '.data | map(select(.tool_type == "validation")) | length' - Override mode at registration:
agents validation add --config examples/validations/required-validation.yaml --informational
Related Examples
- See
cli-tools/output-format-flags.mdfor a deep dive into all six output formats (json,yaml,plain,table,rich,color) - See
docs/showcase/cli-tools/for more CLI tool examples
This example was automatically generated and verified by the CleverAgents UAT system. Feature area: Tool and validation management | Test cycle: 1 | Generated: 2026-04-07