CLI I/O Contract
This page defines the input/output contract for MTHDS methods when invoked as CLI commands. It specifies what a method writes to stdout, what it reads from stdin, and how errors propagate through pipe chains.
Output Modes
A method's CLI produces structured JSON on stdout. Two output modes are defined: compact (default) and full (opt-in via --with-memory).
Compact Output (Default)
The concept's rendered JSON is emitted directly — no envelope, no metadata:
{
"clauses": [
{ "title": "Non-Compete", "risk_level": "high" },
{ "title": "Termination", "risk_level": "medium" }
],
"overall_risk": "high"
}
This is the structured content of the method's main output concept. Standard JSON tools work directly:
mthds-agent pipelex run method extract-terms | jq '.clauses[] | select(.risk_level == "high")'
# Or using the installed CLI shim:
extract-terms | jq '.clauses[] | select(.risk_level == "high")'
When the method has no main output (empty working memory), an empty object {} is emitted.
Full Output (--with-memory)
When --with-memory is passed, the output includes the main stuff renderings and the full working memory:
{
"main_stuff": {
"json": "<concept as JSON string>",
"markdown": "<concept as Markdown string>",
"html": "<concept as HTML string>"
},
"working_memory": {
"root": {
"contract_text": {
"stuff_code": "...",
"stuff_name": "contract_text",
"concept": { "code": "Text" },
"content": { "text": "The parties agree..." }
},
"extracted_terms": {
"stuff_code": "...",
"stuff_name": "extracted_terms",
"concept": { "code": "ContractAnalysis" },
"content": { "clauses": ["..."], "overall_risk": "high" }
},
"main_stuff": {
"stuff_code": "...",
"stuff_name": null,
"concept": { "code": "ContractAnalysis" },
"content": { "clauses": ["..."], "overall_risk": "high" }
}
},
"aliases": {
"main_stuff": "extracted_terms"
}
}
}
The full output preserves all intermediate results and aliases from the pipeline's working memory. This is required when piping output to another method, because the downstream method may need intermediate stuffs for multi-input binding.
Side Effects
Regardless of output mode, the runtime may write side-effect files to disk:
- Output JSON (
live_run.json/dry_run.json) — the full execution result saved alongside the bundle. - Graph HTML (
live_run.html/dry_run.html) — execution graph visualizations (generated by default, disabled with--no-graph).
These files are not included in stdout output. Their paths appear in runtime logs on stderr.
Input Acceptance
A method accepts inputs through three sources, resolved in priority order:
1. --inputs Flag (Highest Priority)
The --inputs / -i flag accepts a file path or inline JSON string. If the value starts with {, it is parsed as inline JSON; otherwise it is treated as a file path.
# Inline JSON
mthds-agent pipelex run method my_method --inputs '{"text": {"concept": "native.Text", "content": {"text": "hello"}}}'
# File path
mthds-agent pipelex run method my_method --inputs data.json
When a method is installed as a CLI shim (see CLI Reference), the same commands are available as:
my_method --inputs '{"text": {"concept": "native.Text", "content": {"text": "hello"}}}'
my_method --inputs data.json
When --inputs is provided, stdin is ignored entirely. This allows overriding piped data for debugging.
2. stdin (Fallback)
When --inputs is not provided and stdin is not a TTY (i.e., data is being piped), JSON is read from stdin:
echo '{"text": {"concept": "native.Text", "content": {"text": "hello"}}}' | mthds-agent pipelex run method my_method
When stdin is a TTY (interactive terminal), no stdin reading occurs and the method runs without inputs.
3. Auto-detected inputs.json
In directory mode, inputs.json in the target directory is auto-detected and used as a fallback when no explicit inputs are provided.
Envelope Detection
When JSON arrives via stdin, the runtime distinguishes between two formats based on the presence of a working_memory key at the top level:
Flat Inputs
No working_memory key present. The JSON is treated as direct input bindings — the same format as --inputs:
{
"document": {
"concept": "native.Document",
"content": { "url": "/path/to/file.pdf" }
}
}
Full Envelope
A working_memory key is present. This indicates the JSON came from an upstream method's --with-memory output. The runtime extracts named stuffs from working_memory.root and converts them to input bindings.
The input resolution rules when receiving a full envelope:
-
Name matching: Each stuff name in the upstream working memory's
rootis matched against the downstream method's declared input names. Matching entries are bound automatically. -
Single-input shortcut: If the downstream method declares exactly one input, it auto-binds to the upstream's
main_stuffcontent. This is the common case for simple chains:extract-terms --with-memory | assess-risk -
Error on failure: If the downstream method's declared inputs cannot be satisfied from the upstream's working memory, a clear error is raised listing what was available upstream vs. what was expected downstream.
Error Propagation
Errors are emitted as structured JSON on stderr with a non-zero exit code:
{
"error": true,
"error_type": "PipelineExecutionError",
"message": "Pipe 'assess_risk' failed: missing required input 'analysis'",
"hint": "Check 'pipe_stack' to identify which pipe failed",
"error_domain": "runtime",
"retryable": false
}
In a Unix pipe chain, errors stop execution at the failing step. Use set -o pipefail in shell scripts to ensure mid-chain failures propagate:
set -o pipefail
extract-terms --with-memory \
| assess-risk --with-memory \
| generate-report
Examples
Simple Chain (Single Input)
# Extract terms, then assess risk, then generate report
extract-terms --inputs '{"document": {"concept": "native.Document", "content": {"url": "contract.pdf"}}}' --with-memory \
| assess-risk --with-memory \
| generate-report
Each intermediate step uses --with-memory to pass the full envelope. The final step omits it to produce compact output.
Compact Output with jq
# Extract just the high-risk clauses
extract-terms --inputs data.json \
| jq '.clauses[] | select(.risk_level == "high")'
Override Piped Input
# The --inputs flag overrides whatever comes from stdin
echo '{"old": "data"}' | mthds-agent pipelex run method my_method --inputs '{"new": "data"}'
The --inputs flag always wins — the stdin data is ignored.