CmdForge/docs/META_TOOLS.md

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:

  1. Fully qualified: owner/name - looks up in registry or installed tools
  2. Local tool: Just name - checks local ~/.cmdforge/<name>/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:

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

  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

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