| name | squid-pipeline |
| description | Create, modify, and debug agentic pipelines with Squid. Define multi-agent YAML workflows with spawn (OpenClaw, Claude Code, OpenCode), gates, parallel execution, loops, branching, restart loops, sub-pipelines, and structured approvals. Use when working with .yaml pipeline files or when the user mentions pipelines, workflows, agents, or Squid. |
Required Reading โ Reference Files
This skill includes reference docs and working examples. You MUST read the relevant files before generating pipelines or tests. SKILL.md alone is not sufficient โ the references contain critical syntax details.
BEFORE creating or modifying any pipeline, read:
references/step-types.md โ Full config for every step type (run, spawn, gate, parallel, loop, branch, transform, pipeline). Contains exact field names, types, and valid values.
references/patterns.md โ Validated workflow patterns with complete YAML examples. Use these as templates.
BEFORE creating or modifying any test file, read:
references/testing.md โ Full test file schema, mock syntax, assertion types, mode behaviors. Contains exact valid field names and values.
BEFORE writing a pipeline that matches an example pattern, read the matching example:
examples/simple-deploy.yaml + examples/simple-deploy.test.yaml โ Basic pipeline with tests
examples/multi-agent-dev.yaml โ Multi-agent with parallel branches
examples/iterative-refinement.yaml โ Restart loop pattern
examples/advanced-gates.yaml โ Structured gate input
examples/orchestrator.yaml โ Sub-pipeline composition
All paths are relative to this skill directory.
Install
If Squid is not installed, install it first:
git clone https://github.com/dominno/squid.git
cd squid
npm install
npm run build
Then use directly:
npx squid run pipeline.yaml
npx squid test
npx squid validate pipeline.yaml
npx squid viz pipeline.yaml
npx squid init --template basic --name my-pipeline
Or link globally:
npm link
squid run pipeline.yaml
Requires Node.js 20+.
Mandatory Rules
These rules are NON-NEGOTIABLE. Every pipeline you generate MUST follow all of them. Violations are bugs.
R1: Gate before side effects
Any step that modifies external state (git push, API POST, deploy, file write to shared storage, PR creation, sending messages) MUST be preceded by a type: gate step. The side-effect step MUST have when: $gate.approved in its condition.
R2: Every pipeline and sub-pipeline MUST set onError
Always set onError: fail (or skip/continue with justification). Never omit it โ the default behavior is implicit and error-prone.
R3: Every spawn step MUST specify output format and timeout
In the task field, always end with Output JSON: { ... } showing the exact shape. Always set timeout (seconds). No exceptions.
R4: Every run step that calls an external API or network MUST have retry
Use retry: { maxAttempts: 2, backoff: fixed } minimum. Network calls are flaky by nature.
R5: Every loop MUST have maxIterations
Prevents runaway execution. No exceptions.
R6: Downstream steps MUST guard on upstream success
If step B depends on step A's output, step B MUST have a when: condition that checks the relevant output field. Especially critical after restart: loops โ downstream steps must check the final approval/success state, not just whether the step ran.
R7: transform steps MUST use ${ref} interpolation in JSON templates
Inside JSON template strings, ALWAYS use ${stepId.json.field} (curly braces), NEVER bare $stepId.json.field.
- Correct:
{"score": ${reviewer.json.score}, "name": "${args.name}"}
- Wrong:
{"score": $reviewer.json.score, "name": "$args.name"}
- Bare
$ref only works as a standalone expression (e.g., transform: $step.json) or in when: conditions.
- Transforms do NOT support: ternary operators (
? :), JavaScript expressions, function calls, or arithmetic. Use a branch step instead.
R8: Every pipeline MUST have a .test.yaml covering at minimum
- Happy path โ all gates approved, all steps succeed
- Gate rejection โ verify side-effect steps are skipped
- Step failure โ verify error propagation
- Restart exhaustion (if
restart: is used) โ verify behavior when maxRestarts is reached without meeting the threshold
R9: spawn tasks that clone repos MUST use deterministic branch checkout
Never use shell inference (git log --format=%D | grep ...). Instead use:
- For PRs:
git fetch origin pull/{number}/head:pr-{number} && git checkout pr-{number}
- For branches: pass the branch name explicitly via pipeline args or prior step output
R10: No unused code or imports in scripts
Scripts MUST be clean โ no unused imports, no dead code, no placeholder comments.
Pipeline Structure
Build Squid pipelines in YAML. Every pipeline has name, steps, and required onError (see R2).
name: <pipeline-name>
description: <what it does>
agent: claude-code
onError: fail
args:
<key>:
default: <value>
description: <help text>
required: true
env:
KEY: value
steps:
- id: <unique-id>
type: <step-type>
description: <label>
Rules: unique id per step, sequential execution, outputs available as $stepId.json.
Step Types
| Type | Key Config | Purpose |
|---|
run | run: "cmd" | Shell command |
spawn | spawn: { task, agent, model } | AI sub-agent |
gate | gate: { prompt, input, requiredApprovers } | Human approval with structured input |
parallel | parallel: { branches, maxConcurrent } | Fan-out/fan-in |
loop | loop: { over, as, steps, maxConcurrent } | Iterate array |
branch | branch: { conditions, default } | Conditional routing |
transform | transform: "$ref" | Data shaping (R7: JSON templates only, no JS expressions) |
pipeline | pipeline: { file, args } | Sub-pipeline |
You MUST read references/step-types.md before using any step type โ it contains exact field names, valid values, and required options not shown above.
Spawn โ Agent Adapters
4 adapters available. Resolution order: step.agent โ pipeline.agent โ SQUID_AGENT env var โ openclaw (default fallback).
export SQUID_AGENT=claude-code
The agent: field in YAML is optional โ only needed to override the env/fallback default. Set it per-pipeline at root, or per-step in spawn config.
When to use which adapter
| Adapter | Best for | Requires |
|---|
claude-code | Code tasks (implement, fix, refactor) โ runs in a repo directory | claude CLI installed and authenticated |
openclaw | Non-code tasks (review, plan, research) โ runs as OpenClaw sub-agents | OpenClaw gateway or CLI |
openclaw + runtime: acp | Code tasks via OpenClaw's Agent Control Plane (Claude Code through OpenClaw) | OpenClaw with ACP configured |
opencode | Code tasks via OpenCode CLI | opencode CLI installed |
Claude Code (standalone, no OpenClaw)
agent: claude-code
steps:
- id: implement
type: spawn
spawn:
task: "Implement feature X. Output JSON: {\"files_changed\": <n>}"
agentId: code-reviewer
model: claude-sonnet-4-6
timeout: 300
cwd: /path/to/repo
Env: CLAUDE_MODEL (optional default model)
How it works: Runs claude --agent <agentId> -p "task" --output-format json as subprocess (omits --agent if no agentId).
Sub-agents: Define agents in .claude/agents/<name>.md files. Use agentId to target them โ each agent has its own system prompt, tool access, and permissions.
OpenClaw (sub-agents)
agent: openclaw
steps:
- id: review
type: spawn
spawn:
task: "Review code quality. Output JSON: {\"score\": <n>, \"issues\": [...]}"
agentId: code-reviewer
thinking: high
runtime: subagent
timeout: 300
Requires: openclaw CLI installed and authenticated (openclaw config). Squid invokes openclaw agent --agent <id> --message "..." as a subprocess. The CLI uses its own stored credentials (~/.openclaw/config.json) โ no env vars needed.
OpenClaw-only options: runtime, mode (run|session), sandbox (inherit|require), attachments
When does model: apply?
runtime: subagent โ model is ignored. The OpenClaw agent uses whatever model is in its own config.
runtime: acp โ model is passed to the ACP-spawned Claude Code instance.
OpenClaw + ACP (Claude Code through OpenClaw)
Use runtime: acp to run Claude Code agents managed by OpenClaw's Agent Control Plane:
- id: implement
type: spawn
spawn:
task: "Implement the feature. Output JSON: {\"implemented\": true}"
agent: openclaw
runtime: acp
timeout: 600
OpenCode (standalone, no OpenClaw)
agent: opencode
steps:
- id: fix
type: spawn
spawn:
task: "Fix the bug. Output JSON: {\"fixed\": true}"
model: gpt-4o
timeout: 300
cwd: /path/to/repo
Env: OPENCODE_MODEL (optional default model)
How it works: Runs opencode run --message "task" as subprocess.
agentId โ targeting named sub-agents
agentId works across adapters to target a specific named agent:
| Adapter | How agentId is used | Where agents are defined |
|---|
claude-code | Passed as --agent <name> to the CLI | .claude/agents/<name>.md files |
openclaw | Passed as --agent <name> to the CLI | OpenClaw agent config |
opencode | Not yet supported | โ |
- id: review
type: spawn
spawn:
task: "Review the code. Output JSON: {\"score\": <n>}"
agentId: code-reviewer
timeout: 120
Mixing adapters in one pipeline
agent: claude-code
steps:
- id: implement
type: spawn
spawn:
task: "Write the code"
timeout: 300
- id: review
type: spawn
spawn:
task: "Review the code"
agent: openclaw
agentId: reviewer-agent
timeout: 120
Full adapter reference: docs/adapters.md โ custom adapter interface, feature comparison table, and detailed examples.
Gate โ Structured Input + Identity
- id: deploy-config
type: gate
gate:
prompt: "Configure deployment"
input:
- name: env
type: select
options: ["staging", "prod"]
- name: replicas
type: number
default: 2
requiredApprovers: ["lead"]
allowSelfApproval: false
- Halts with 8-char short ID (chat-friendly) + full resume token
- Access input:
$gate.json.input.env, $gate.json.approvedBy
- Input validated: type, required, regex, select options
R1 reminder: Every step after a gate that performs side effects MUST check when: $gateId.approved.
Common Options
Apply to any step:
when: $approve.approved && $test.json.pass
retry: { maxAttempts: 3, backoff: exponential-jitter }
restart: { step: write, when: $review.json.score < 80, maxRestarts: 3 }
timeout: 300
env: { KEY: value }
description: "Human-readable label"
Data Flow
| Pattern | Value |
|---|
$stepId.json | Parsed JSON output |
$stepId.stdout | Raw stdout |
$stepId.approved | Gate boolean |
$stepId.json.input.field | Gate structured input |
$args.key | Pipeline argument |
$env.VAR | Environment variable |
$item / $index | Loop context |
Interpolation: ${args.key}, ${stepId.json.field} in strings.
Key Patterns
Plan โ Gate โ Execute (R1): Always gate before side effects. No spawn/run that pushes, deploys, or mutates external state without a preceding gate.
Parallel Agents โ Review: Fan out to specialists, then aggregate.
Iterative Refinement: restart: loops back until quality threshold met. Downstream steps MUST guard on the final result (R6) โ e.g., when: $review.json.approved.
Sub-Pipeline Composition: Break large workflows into type: pipeline stages. Each sub-pipeline MUST set its own onError (R2).
Error Handling: branch: on $step.status == "failed" with rollback.
Retry on network calls (R4): Any run step hitting an external API (GitHub, Slack, HTTP) MUST have retry.
Read references/patterns.md for complete YAML examples of each pattern before implementing.
Examples
Working pipeline examples in examples/:
| File | What it demonstrates |
|---|
examples/simple-deploy.yaml | Basic build โ test โ gate โ deploy |
examples/orchestrator.yaml | Sub-pipeline composition (calls sub-build, sub-test, sub-deploy) |
examples/multi-agent-dev.yaml | 8 specialized agents: architect, coders, tester, reviewer, docs |
examples/video-pipeline.yaml | Content creation with parallel asset generation loops |
examples/iterative-refinement.yaml | Restart loop: write โ review โ refine until quality met |
examples/advanced-gates.yaml | Structured input fields, requiredApprovers, short IDs |
examples/observability.yaml | Event hooks, OTel spans, audit trails, chat notifications |
examples/simple-deploy.test.yaml | YAML test file with sandbox + integration tests |
examples/sub-build.test.yaml | YAML test file for sub-pipeline |
Testing
Create pipeline.test.yaml alongside pipeline.yaml. R8: Every pipeline MUST have tests.
pipeline: ./pipeline.yaml
tests:
- name: "deploys when approved"
mode: sandbox
mocks:
run:
build: { output: { built: true } }
gates:
approve: true
assert:
status: completed
steps:
deploy: completed
Modes: sandbox (all mocked) | integration (run steps execute).
Run: squid test (auto-discovers) or squid test file.test.yaml.
Test YAML syntax rules โ MUST follow exactly
Supported mock types โ ONLY these two exist:
mocks:
run:
stepId:
output: { key: value }
stdout: "raw text"
status: completed
error: "message"
spawn:
stepId:
output: { key: value }
status: accepted
error: "message"
NEVER use these โ they do NOT exist and are silently ignored:
mocks.pipeline โ does NOT exist. Sub-pipeline steps run normally (their internal steps get sandbox defaults).
mocks.branch โ does NOT exist.
mocks.loop โ does NOT exist.
mocks.transform โ does NOT exist.
mocks.gate โ does NOT exist. Use gates: top-level key instead.
Spawn mock status values:
- Use
status: error to simulate spawn failure (NOT status: failed โ that is for run mocks only)
- Use
status: accepted for success (default if omitted)
Sandbox vs integration mode behavior:
sandbox: Run mocks work via onRun hook. Spawn mocks are IGNORED โ spawns go to the mock adapter which always returns {mocked: true}. Use integration mode if you need spawn mocks to work.
integration: Run steps execute for real (unless mocked). Spawn mocks work via onSpawn hook.
Gate behavior in tests:
gates: { stepId: true } โ gate approved, step status = "completed", $stepId.approved = true
gates: { stepId: false } โ gate rejected, step status = "skipped", $stepId.approved = false
- Unmocked gates are auto-approved
Sub-pipeline steps in tests:
- Cannot be mocked directly. The sub-pipeline file is loaded and its steps run in the current test mode.
- To control sub-pipeline behavior, mock its internal
run steps by their step IDs, or use sandbox mode where unmocked run steps return {sandbox: true}.
Required test coverage (R8):
- Happy path โ all approved, all succeed
- Gate rejection โ assert side-effect steps are
skipped
- No-data path โ assert conditional steps are
skipped when when: evaluates false
- Restart exhaustion โ if
restart: used, test behavior when threshold is never met
You MUST read references/testing.md before writing any test file โ it contains the full assertion schema and mode behavior details.
Events / Observability
Pipeline execution emits lifecycle events for monitoring, OTel, audit trails, and chat notifications.
import { createEventEmitter, runPipeline, parseFile } from "squid";
const events = createEventEmitter();
events.on("*", (e) => console.log(`[${e.type}] ${e.stepId}`));
events.on("gate:waiting", (e) => slack.send(`Approve: ${e.data?.prompt}`));
events.on("step:error", (e) => pagerduty.trigger(`${e.stepId}: ${e.data?.error}`));
await runPipeline(parseFile("pipeline.yaml"), { events });
13 event types: pipeline:start/complete/error, step:start/complete/error/skip/retry, gate:waiting/approved/rejected, spawn:start/complete.
OTel-compatible: every event has traceId, spanId, timestamp, duration.
Read references/step-types.md (Events section) and examples/observability.yaml for implementation details.
CLI
squid run <file> [--args-json '{}'] [--dry-run] [-v]
squid test [file.test.yaml]
squid resume <file> --token <token> --approve yes|no
squid validate <file>
squid viz <file>
squid init --template basic|agent|parallel|full --name <name>
Verbose mode (-v)
Add -v to any squid run command for step-by-step execution logs:
squid run pipeline.yaml -v
squid run pipeline.yaml -v --dry-run
squid run pipeline.yaml -v --test
Output shows timestamps, step types, outputs, gate activity, retries, and errors:
15:10:46.532 [pipeline] โถ my-pipeline mode=run args={"topic":"test"}
15:10:46.535 [spawn] โ [research] starting...
15:10:52.143 [spawn] โ [research] completed (5608ms)
15:10:52.143 [output] {"facts":["fact1","fact2"]}
15:10:52.144 [gate] โธ [approve] waiting for approval: Review results
15:10:55.000 [gate] โ [approve] approved
15:10:55.001 [run] โ [deploy] starting...
15:10:55.500 [run] โ [deploy] completed (499ms)
15:10:55.501 [step] โ [cleanup] skipped (condition_false)
15:10:55.502 [pipeline] โ my-pipeline completed (4970ms)
Icons: โถ start, โ running, โ done, โ error, โ skipped, โป retry, โธ gate waiting, โก spawning.
Pre-Delivery Checklist
Before delivering any pipeline to the user, verify ALL items. If any item fails, fix before delivering.
Documentation Links
Full docs on GitHub (for topics not covered in this skill or its references):