06 medium command composition
Part I: Output & Parsing | Challenge §6
6. Command Composition & Piping
Severity: Medium | Frequency: Common | Detectability: Easy | Token Spend: Medium | Time: Low | Context: Low
Impact
- Agent must write fragile JSON extraction logic for every inter-command data transfer
- No stdin support forces use of temporary files or shell variable extraction, both error-prone
- Commands that don't accept
-for stdin cannot participate in streaming pipelines
The Problem
Agents often need to chain commands: get an ID from one command, pass it to another. Poor composition support forces the agent to do text extraction and reformatting.
Output not suitable for piping:
$ tool create-user --name Alice | tool send-welcome-email
# Doesn't work: create-user outputs JSON blob, send-welcome-email expects a user ID
Required format transformation between commands:
$ ID=$(tool get-user --name Alice --output json | python -c "import sys,json; print(json.load(sys.stdin)['id'])")
$ tool delete-user --id $ID
# Agent has to know the JSON structure and write extraction logic
Commands that don't read from stdin:
$ tool get-user-id --name Alice | tool send-email
# send-email ignores stdin, requires --user-id argument
Solutions
--output id mode (extract single value):
$ tool get-user --name Alice --output id
42
# Just the primary identifier, no JSON, pipeable
Stdin acceptance for IDs:
$ tool get-user --name Alice --output id | tool send-welcome-email --user-id -
# --user-id - means "read from stdin"
Batch input from file/stdin:
$ tool list-users --output jsonl | tool send-welcome-email --users-jsonl -
--from flag for reading prior command output:
$ tool get-user --name Alice --output json > /tmp/user.json
$ tool send-welcome-email --from-file /tmp/user.json
For framework design:
- Every command that takes an ID also accepts - to read from stdin
- Provide --output id as a standard extraction mode
- Define a pipe protocol: each framework command can declare what it emits and what it accepts
Evaluation
| Score | Condition |
|---|---|
| 0 | No --output id mode; commands don't accept stdin; inter-command data transfer requires manual JSON extraction |
| 1 | --output json exists but single-value extraction requires jq or inline Python; no stdin acceptance |
| 2 | --output id mode available on read commands; some commands accept - to read ID from stdin |
| 3 | All ID-taking commands accept - for stdin; --output id standard across the tool; --from-file accepted for structured input |
Check: Chain two commands using shell pipe — tool get-X --output id | tool use-X --id - — and verify it works without intermediate variables or subshell parsing.
Agent Workaround
Extract IDs explicitly with jq or inline Python rather than shell pipes:
# Step 1: get the primary ID
result = subprocess.run(
["tool", "get-user", "--name", "Alice", "--output", "json"],
capture_output=True, text=True,
)
user_id = json.loads(result.stdout)["data"]["id"]
# Step 2: pass it to the next command
result2 = subprocess.run(
["tool", "send-welcome-email", "--user-id", str(user_id)],
capture_output=True, text=True,
)
Use temp files for complex intermediate state:
import tempfile, json, os
with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
json.dump(parsed_result["data"], f)
tmppath = f.name
try:
result = subprocess.run(
["tool", "process", "--from-file", tmppath],
capture_output=True, text=True,
)
finally:
os.unlink(tmppath)
Limitation: If the tool suite has no consistent ID field name (some use id, others uuid, key, name), the agent must know each command's output schema to extract the right value — check the tool manifest for primary_key metadata if available, otherwise read the output schema