Pipelines
Pipelines enable complex, multi-step agent workflows that survive server restarts, support human approval gates, and provide full observability into execution progress.
Why Pipelines?
Running an AI agent to "clone a repo and run tests" is simple. But real-world workflows often require:
- Sequential steps — build before test before deploy
- Parallel execution — run linting and tests simultaneously
- Human approval — require sign-off before production deploy
- Retry logic — automatically retry flaky network calls
- Crash recovery — resume interrupted workflows after server restart
- Shared state — pass artifacts between stages
Pipelines provide all of this in a single, declarative API.
Core Concepts
Pipeline
A named, versioned workflow definition. Immutable once created — changes create a new version.
Stage
A single unit of work in a pipeline. Stages can be:
- command — run a shell command in a sandbox
- await_approval — pause for human approval
- gate — check a condition (trust score, cost threshold)
- eval — run a Cage Eval scenario
Run
A single execution of a pipeline with specific inputs. Runs are durable — they survive server restarts and can be paused indefinitely for approval.
State
Key/value store scoped to a run. Stages can write artifacts (file paths, computed values) that subsequent stages read.
Quick Start
1. Create a Pipeline
curl -X POST https://api.caged.dev/v1/pipelines \
-H "Authorization: Bearer caged_sk_..." \
-H "Content-Type: application/json" \
-d '{
"name": "ci-pipeline",
"stages": [
{"name": "build", "type": "command", "command": "npm run build"},
{"name": "test", "type": "command", "command": "npm test", "depends_on": ["build"]},
{"name": "lint", "type": "command", "command": "npm run lint", "depends_on": ["build"]},
{"name": "deploy", "type": "command", "command": "npm run deploy", "depends_on": ["test", "lint"]}
],
"defaults": {"template": "node-20"}
}'
This creates a diamond DAG:
build
/ \
test lint
\ /
deploy
test and lint run in parallel after build. deploy waits for both.
2. Start a Run
curl -X POST https://api.caged.dev/v1/pipelines/pipe-abc123/runs \
-H "Authorization: Bearer caged_sk_..." \
-d '{"repo": "https://github.com/myorg/myapp", "branch": "main"}'
3. Monitor Progress
curl https://api.caged.dev/v1/pipelines/pipe-abc123/runs/run-xyz789/stages \
-H "Authorization: Bearer caged_sk_..."
Adding Approval Gates
Require human sign-off before sensitive operations:
{
"name": "approve-prod",
"type": "await_approval",
"depends_on": ["test"],
"config": {
"message": "Tests passed. Deploy to production?",
"channels": ["slack", "dashboard"],
"sla_timeout": "1h"
}
}
When this stage is reached:
- Run status changes to
paused - Notifications sent to configured channels
- Approvers see the request in dashboard / Slack
- On approval, run resumes automatically
- On rejection, run is canceled
Retry Policies
Handle transient failures automatically:
{
"name": "upload",
"type": "command",
"command": "aws s3 cp dist.zip s3://bucket/",
"retry": {
"max_attempts": 3,
"backoff": "5s",
"max_backoff": "30s"
}
}
Failed stages retry with exponential backoff until max_attempts is exhausted.
Conditional Execution
Run stages only when conditions are met:
{
"name": "notify-failure",
"type": "command",
"command": "slack-notify 'Build failed!'",
"depends_on": ["build"],
"condition": {"on_failure": true}
}
Condition options:
on_success— run only if all dependencies succeededon_failure— run only if any dependency failedif— custom expression (coming soon)
On Failure Behavior
Control what happens when a stage fails:
{
"defaults": {
"on_failure": "continue"
}
}
stop(default) — fail the entire runcontinue— mark stage failed but continue other branches
Environment Variables
Pass environment to all stages:
curl -X POST https://api.caged.dev/v1/pipelines/pipe-abc123/runs \
-H "Authorization: Bearer caged_sk_..." \
-d '{
"env": {
"NODE_ENV": "production",
"AWS_REGION": "us-east-1"
}
}'
Per-stage environment overrides pipeline-level:
{
"name": "deploy",
"type": "command",
"command": "deploy.sh",
"env": {"DEPLOY_TARGET": "us-west-2"}
}
Crash Recovery
Pipelines automatically recover from server restarts:
- Server crashes mid-run
- On restart,
RecoverRuns()is called - Incomplete runs resume from last completed stage
- Paused runs (awaiting approval) remain paused
No manual intervention required.
Events and Observability
Pipeline lifecycle events are emitted to the event pipeline:
pipeline.run.createdpipeline.run.startedpipeline.run.completedpipeline.run.failedpipeline.stage.startedpipeline.stage.completedpipeline.stage.failed
These integrate with:
- Session replay — see full stage execution
- Cost tracking — aggregate LLM + compute costs
- Trust scoring — score agent behavior per stage
Best Practices
1. Keep Stages Atomic
Each stage should do one thing. Don't put "build and test" in a single command.
2. Use Dependencies Explicitly
Even if stages naturally run in order, declare depends_on for clarity and parallelization.
3. Set Appropriate Timeouts
Don't let runaway commands block pipelines. Default timeout is 10 minutes.
{"timeout": "30m"}
4. Add Approval Gates for Production
Never auto-deploy to production. Add an await_approval stage.
5. Use Gate Stages for Guardrails
Check trust scores or cost limits before expensive operations:
{
"name": "cost-check",
"type": "gate",
"config": {"cost_below": 10.00}
}
SDK Usage
TypeScript
import { Caged } from '@caged-dev/sdk';
const caged = new Caged({ apiKey: 'caged_sk_...' });
// Create pipeline
const pipeline = await caged.pipelines.create({
name: 'my-pipeline',
stages: [
{ name: 'build', type: 'command', command: 'npm run build' },
{ name: 'test', type: 'command', command: 'npm test', dependsOn: ['build'] }
]
});
// Start run
const run = await caged.pipelines.startRun(pipeline.id, {
repo: 'https://github.com/myorg/myapp',
branch: 'main'
});
// Poll status
let status = run.status;
while (status === 'pending' || status === 'running') {
await new Promise(r => setTimeout(r, 5000));
const updated = await caged.pipelines.getRun(pipeline.id, run.id);
status = updated.status;
}
console.log(`Run completed with status: ${status}`);
Python
from caged import Caged
caged = Caged(api_key="caged_sk_...")
# Create pipeline
pipeline = caged.pipelines.create(
name="my-pipeline",
stages=[
{"name": "build", "type": "command", "command": "npm run build"},
{"name": "test", "type": "command", "command": "npm test", "depends_on": ["build"]}
]
)
# Start run
run = caged.pipelines.start_run(pipeline.id, repo="https://github.com/myorg/myapp")
# Wait for completion
run.wait()
print(f"Run completed with status: {run.status}")
Next Steps
- Pipelines API Reference — full endpoint documentation
- Approvals Guide — HITL approval configuration
- Eval Guide — integrate Cage Eval into pipelines
- CI Pipeline Recipe — full CI/CD example