6.6 KiB
6.6 KiB
Meta-Tools: Tools That Call Other Tools
Meta-tools are CmdForge tools that can invoke other tools as steps in their workflow. This enables powerful composition and reuse of existing tools.
Step Type: tool
A new step type tool allows calling another CmdForge tool from within a tool's workflow.
Manifest Format
name: summarize-and-translate
description: Summarize text then translate the summary
version: 1.0.0
category: Text
steps:
- type: tool
tool: official/summarize # Tool to call (owner/name or just name for local)
input: "{input}" # What to pass as input (supports variable substitution)
args: # Optional arguments to pass to the tool
max_words: "100"
output_var: summary # Variable to store the output
- type: tool
tool: official/translate
input: "{summary}" # Use output from previous step
args:
target_language: "{language}" # Can use tool's own arguments
output_var: translated
output: "{translated}"
arguments:
- flag: "--language"
variable: language
default: "Spanish"
description: "Target language for translation"
Resolution Order
When resolving a tool reference in a tool step:
- Fully qualified:
owner/name- looks up in registry or installed tools - Local tool: Just
name- checks local~/.cmdforge/<name>/config.yamlfirst - Registry fallback: If not found locally, checks installed registry tools
Tool Step Properties
| Property | Required | Description |
|---|---|---|
type |
Yes | Must be "tool" |
tool |
Yes | Tool reference (owner/name or name) |
input |
No | Input to pass to the tool (default: current {input}) |
args |
No | Dictionary of arguments to pass |
output_var |
Yes | Variable name to store the tool's output |
provider |
No | Override provider for the called tool |
Execution
When a tool step executes:
- Resolve the tool reference to find the tool definition
- Substitute variables in
inputandargsvalues - Execute the tool with the resolved input and arguments
- Capture the output in the specified
output_var
Error Handling
- If a called tool fails, the parent tool fails with an appropriate error
- Tool not found errors include helpful installation suggestions
- Circular dependencies are detected and prevented
Dependency Resolution
Declaring Dependencies
Tools can declare their dependencies in the config:
name: my-meta-tool
dependencies:
- official/summarize
- official/translate@^1.0.0 # With version constraint
CLI Integration
# Install a tool and its dependencies
cmdforge install official/summarize-and-translate
# Check if dependencies are satisfied
cmdforge check my-meta-tool
# Install missing dependencies
cmdforge install --deps my-meta-tool
Dependency Checking
Before running a meta-tool, the runner verifies all dependencies are installed:
def check_dependencies(tool: Tool) -> List[str]:
"""Returns list of missing dependencies."""
missing = []
for dep in tool.dependencies:
if not is_tool_installed(dep):
missing.append(dep)
return missing
Security Considerations
- Sandboxing: Called tools run in the same process context as the parent
- Input validation: All inputs are treated as untrusted strings
- No shell execution: Tool references cannot contain shell commands
- Depth limit: Maximum nesting depth (default: 10) prevents runaway recursion
Example: Multi-Step Analysis Pipeline
name: code-review-pipeline
description: Comprehensive code review using multiple specialized tools
version: 1.0.0
category: Developer
dependencies:
- official/analyze-complexity
- official/find-bugs
- official/suggest-improvements
steps:
# Step 1: Analyze code complexity
- type: tool
tool: official/analyze-complexity
input: "{input}"
output_var: complexity_report
# Step 2: Find potential bugs
- type: tool
tool: official/find-bugs
input: "{input}"
output_var: bug_report
# Step 3: Generate improvement suggestions based on findings
- type: tool
tool: official/suggest-improvements
input: |
## Code:
{input}
## Complexity Analysis:
{complexity_report}
## Bug Report:
{bug_report}
output_var: suggestions
output: |
# Code Review Results
## Complexity Analysis
{complexity_report}
## Potential Issues
{bug_report}
## Improvement Suggestions
{suggestions}
Implementation Notes
ToolStep Dataclass
@dataclass
class ToolStep:
"""A step that calls another tool."""
tool: str # Tool reference (owner/name or name)
output_var: str # Variable to store output
input_template: str = "{input}" # Input template
args: Dict[str, str] = field(default_factory=dict)
provider: Optional[str] = None # Provider override
def to_dict(self) -> dict:
d = {
"type": "tool",
"tool": self.tool,
"output_var": self.output_var,
}
if self.input_template != "{input}":
d["input"] = self.input_template
if self.args:
d["args"] = self.args
if self.provider:
d["provider"] = self.provider
return d
Execution Function
def execute_tool_step(
step: ToolStep,
variables: dict,
depth: int = 0,
max_depth: int = 10
) -> tuple[str, bool]:
"""Execute a tool step by calling another tool."""
if depth >= max_depth:
return "", False # Prevent infinite recursion
# Resolve the tool
try:
resolved = resolve_tool(step.tool)
except ToolNotFoundError:
print(f"Error: Tool '{step.tool}' not found", file=sys.stderr)
return "", False
# Prepare input
input_text = substitute_variables(step.input_template, variables)
# Prepare arguments
args = {}
for key, value in step.args.items():
args[key] = substitute_variables(value, variables)
# Run the tool
output, exit_code = run_tool(
tool=resolved.tool,
input_text=input_text,
custom_args=args,
provider_override=step.provider,
_depth=depth + 1 # Track nesting depth
)
return output, exit_code == 0
Future Enhancements
- Parallel tool execution: Run independent tool steps concurrently
- Conditional execution: Skip steps based on conditions
- Tool aliases: Define shorthand names for frequently used tools
- Caching: Cache tool outputs for identical inputs