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
toolstep 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:
- Summarizes an article
- Translates the summary to Spanish
- 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
- Test your tool steps - Debug before you deploy
- Mix with prompt and code steps - The full power of multi-step tools
- Publish your composed tools - Share your creations