Pipelines
Chain commands into named, reusable workflows.
A pipeline is a named sequence of marmot invocations chained via stdin/stdout. Define once, invoke with the same @<name> sigil that routes presets:
marmot pipeline create web-summary \
--step 'search ${input}' \
--step 'run "summarize the top results in three paragraphs"'
marmot @web-summary "AI safety in 2026"
# Equivalent to:
# marmot search "AI safety in 2026" \
# | marmot run "summarize the top results in three paragraphs"Pipelines are stored under a top-level pipelines key in ~/.marmot/config.json alongside presets.
Why pipelines vs shell aliases?
A shell function can do the same thing — web-summary() { marmot search "$1" | marmot run "summarize…"; }. The advantages of a marmot pipeline:
- Discoverability —
marmot pipeline listshows what's available. - Portability — lives in your config, sharable across machines / agents / users.
- Composition — a step can reference an existing preset (
@<your-preset>) so workflows build on tuned defaults. - Sigil routing —
marmot @web-summary "..."works the same asmarmot @<preset> "...". One mental model.
If you're solo and live in one shell, the shell function is fine. The pipeline shines when you want the workflow to follow you.
Create
marmot pipeline create <name> --step '...' [--step '...']...Names follow the slug regex ^[a-z0-9]+([-_][a-z0-9]+)*$ — same shape as preset names. Name collisions with presets are rejected at create time so the @<name> sigil's resolution stays deterministic (pipeline beats preset, but the only way both exist with the same name is hand-editing ~/.marmot/config.json).
Each --step '...' flag carries one step in textual form. Two forms accepted:
| Form | Meaning | Example |
|---|---|---|
<verb> [args] | Inline marmot verb invocation. | search ${input} · run "summarize this" |
@<preset> | Reference an existing preset. The preset must exist at run time. | @my-podcast · @digest ${input} |
Quoted args ("…", '…') are preserved as a single token. Backslash-escapes inside step strings are NOT supported. Compose pipelines together at the shell — marmot @a | marmot @b — pipelines themselves do not nest.
Substitution
Step strings can interpolate the runtime positional arguments using:
| Token | Resolves to |
|---|---|
${input} | All positional args joined with a single space. Required by default. |
${input?} | Same but resolves to empty string when no positional was passed. |
${1}, ${2}, … | The Nth positional (1-indexed). Required by default. |
${1?}, ${2?}, … | Optional variants — empty string when not supplied. |
Required substitution that has no matching positional surfaces a clear error before any subprocess is spawned: Pipeline "web-summary" step references ${input} but no input argument was provided. Pass one: marmot @web-summary <input>.
Update
marmot pipeline update <name> --step '...' [--step '...']...Passing --step replaces the full step list. Omitting --step keeps the existing steps; you can update just the --label.
Run
marmot pipeline run <name> [input...]
marmot @<name> [input...] # sigil shortcutEach step is executed as its own marmot subprocess. The first step's stdin is inherited from the parent (so cat foo.txt | marmot @<name> chains stdin into step 1). Step N's stdout pipes into step N+1's stdin. The final step's stdout is the user's stdout. Errors surface a Pipeline "<name>" failed at step N (<verb>) with exit code <code> message and a non-zero exit.
# Multi-arg substitution
marmot pipeline create two-pass \
--step 'search ${1}' \
--step 'run "answer the user question: ${2}"'
marmot @two-pass "AI safety" "what are the most-cited concerns?"List and get
marmot pipeline list # human table on TTY, JSON when piped
marmot pipeline list --json
marmot pipeline list --markdown
marmot pipeline get <name>
marmot pipeline get <name> --json
marmot pipeline get <name> --markdownlist reports name, step count, and label. get prints identity (name, pipeline_id, label) and the numbered step list.
Output mode is TTY-aware just like the other list/get commands — interactive terminals get the human layout; piped output falls back to JSON.
Rename and delete
marmot pipeline rename <old> <new>
marmot pipeline delete <name>Rename keeps pipeline_id stable. Delete returns {removed: false} when the name didn't exist (not an error).
Config keys
{
"pipelines": {
"web-summary": {
"pipeline_id": "<uuid>",
"label": "Search then summarize",
"steps": [
{ "verb": "search", "args": "${input}" },
{ "verb": "run", "prompt": "summarize the top results in three paragraphs" }
]
}
}
}Examples
# Search the web, then summarize the results
marmot pipeline create web-summary \
--step 'search ${input}' \
--step 'run "summarize the top results in three paragraphs"'
marmot @web-summary "AI safety in 2026"
# Scrape a page, then extract data with the model
marmot pipeline create extract-emails \
--step 'scrape ${input}' \
--step 'run "list every email address you find. one per line."'
marmot @extract-emails https://example.com/team
# Two-arg pipeline: search topic, then ask a follow-up
marmot pipeline create research \
--step 'search ${1}' \
--step 'run "answer the user question against these results: ${2}"'
marmot @research "openrouter pricing 2026" "which models offer the best $/token?"