# 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 ```yaml 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: 1. **Fully qualified**: `owner/name` - looks up in registry or installed tools 2. **Local tool**: Just `name` - checks local `~/.cmdforge//config.yaml` first 3. **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: 1. Resolve the tool reference to find the tool definition 2. Substitute variables in `input` and `args` values 3. Execute the tool with the resolved input and arguments 4. 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: ```yaml name: my-meta-tool dependencies: - official/summarize - official/translate@^1.0.0 # With version constraint ``` ### CLI Integration ```bash # 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: ```python 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 1. **Sandboxing**: Called tools run in the same process context as the parent 2. **Input validation**: All inputs are treated as untrusted strings 3. **No shell execution**: Tool references cannot contain shell commands 4. **Depth limit**: Maximum nesting depth (default: 10) prevents runaway recursion ## Example: Multi-Step Analysis Pipeline ```yaml 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 ```python @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 ```python 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