Skip to content

Tools Within Tools

Here's a secret that changes everything: tools can call other tools. That summarize command you installed from the registry? You can use it as a building block inside your own creations. It's like having LEGO bricks that already do something cool.

What You'll Learn

  • The tool step type and when to use it
  • Passing data between tools
  • Building pipelines from existing tools
  • Real-world composition patterns

Why Compose Tools?

Let's say you want a tool that:

  1. Summarizes an article
  2. Translates the summary to Spanish
  3. Fixes any grammar issues

You could write three prompt steps from scratch. Or you could stand on the shoulders of giants and reuse tools that already exist:

name: spanish-summary
version: "1.0.0"
description: Summarize text and translate to Spanish

steps:
  - type: tool
    tool: summarize
    output_var: summary

  - type: tool
    tool: translate
    input: "{summary}"
    args:
      lang: Spanish
    output_var: translated

  - type: tool
    tool: fix-grammar
    input: "{translated}"
    output_var: final

output: "{final}"

Three lines per step. Each one leverages a battle-tested tool from the registry. The prompts, the edge cases, the provider configs—all handled.

The Power of Composition

When the summarize tool gets updated with better prompts, your tool automatically benefits. You're not copying code—you're building on it.

Anatomy of a Tool Step

A tool step has four parts:

- type: tool              # This is a tool step
  tool: owner/name        # Which tool to call (or just "name" for local tools)
  input: "{variable}"     # What to send as input (optional, defaults to {input})
  args:                   # Arguments to pass (optional)
    flag-name: value
  output_var: result      # Where to store the output
Field Required Description
type Yes Must be tool
tool Yes Tool name or owner/name for registry tools
input No Input template (defaults to {input})
args No Key-value pairs for tool arguments
output_var Yes Variable name to store the result

Passing Data Between Tools

The input field supports variable substitution, just like prompt templates:

steps:
  # Step 1: Use original input
  - type: tool
    tool: extract-keywords
    input: "{input}"           # The original stdin input
    output_var: keywords

  # Step 2: Use output from step 1
  - type: tool
    tool: expand-topic
    input: "{keywords}"        # Output from previous step
    output_var: expanded

  # Step 3: Combine multiple variables
  - type: tool
    tool: write-article
    input: |
      Topic: {keywords}

      Research:
      {expanded}
    output_var: article

Passing Arguments

Most tools accept arguments. Pass them through the args field:

- type: tool
  tool: translate
  input: "{input}"
  args:
    lang: French          # --lang French
    formal: "true"        # --formal true
  output_var: french_text

Variable Arguments

Arguments can use variables too! This is incredibly powerful:

args:
  lang: "{target_language}"    # From a previous step or tool argument

Local vs Registry Tools

You can call both your own tools and registry tools:

steps:
  # Call a local tool (in your ~/.cmdforge/)
  - type: tool
    tool: my-custom-extractor
    output_var: extracted

  # Call a registry tool
  - type: tool
    tool: official/summarize
    input: "{extracted}"
    output_var: summary

Real-World Patterns

Pattern 1: The Pipeline

Chain tools in sequence, each transforming the output of the last:

name: polish-writing
description: Clean up rough drafts

steps:
  - type: tool
    tool: fix-grammar
    output_var: fixed

  - type: tool
    tool: simplify
    input: "{fixed}"
    args:
      level: "high school"
    output_var: simple

  - type: tool
    tool: tone-shift
    input: "{simple}"
    args:
      tone: professional
    output_var: polished

output: "{polished}"

Pattern 2: The Fork

Send the same input to multiple tools, then combine results:

name: multi-perspective
description: Get analysis from different angles

steps:
  - type: tool
    tool: summarize
    output_var: summary

  - type: tool
    tool: extract-keywords
    input: "{input}"        # Same original input
    output_var: keywords

  - type: tool
    tool: sentiment-analysis
    input: "{input}"        # Same original input
    output_var: sentiment

  - type: prompt
    provider: claude
    prompt: |
      Combine these analyses into a report:

      Summary: {summary}
      Keywords: {keywords}
      Sentiment: {sentiment}
    output_var: report

output: "{report}"

Pattern 3: The Conditional

Use code steps to route data to different tools:

name: smart-translate
description: Only translate if not already in English

steps:
  - type: tool
    tool: detect-language
    output_var: detected_lang

  - type: code
    code: |
      needs_translation = detected_lang.strip().lower() != "english"
    output_vars: [needs_translation]

  - type: tool
    tool: translate
    input: "{input}"
    args:
      lang: English
    output_var: translated
    # Note: Tool runs but you can use code to choose output

  - type: code
    code: |
      if needs_translation:
          result = translated
      else:
          result = input
    output_vars: [result]

output: "{result}"

Provider Overrides

Need to use a different AI provider than what the tool specifies? Override it:

- type: tool
  tool: summarize
  provider: ollama          # Use local Ollama instead of cloud
  output_var: summary

Debugging Composed Tools

When things go wrong, test each tool step individually:

# Test the first tool manually
echo "your input" | summarize

# Check what's going into step 2
echo "your input" | summarize | cat

# Then test step 2
echo "output from step 1" | translate --lang Spanish

Or use the Testing Sandbox in the Visual Builder to step through each tool interactively.

Next Steps