---
title: Config-as-Code Pipelines
description: "Define durable multi-step agent workflows declaratively in .caged.yaml."
---

# Config-as-Code Pipelines

Define multi-agent workflows in `.caged.yaml` — with DAG-based execution, crash recovery, cost gates, and human approval checkpoints.

## Why Agent Pipelines?

Unlike traditional CI/CD (shell commands running builds), agent pipelines let **AI agents do the work**:

| Traditional CI | Agent Pipeline |
|---------------|----------------|
| `npm run build` | Agent writes the code |
| `npm test` | Agent writes + runs tests |
| Human creates PR | Agent creates the PR |
| Human reviews | Agent reviews, human approves |
| Fixed commands | Dynamic prompts with context |

Define these workflows alongside your code — version controlled, reproducible, portable.

## Agent Stage Type

The `type: agent` stage is the core building block. It spawns a sandbox, runs an AI agent inside, and captures the session:

```yaml
stages:
  - name: implement-feature
    type: agent
    agent: claude-code  # or: aider, codex, custom
    prompt: |
      Implement the following feature:
      $FEATURE_SPEC
      
      Requirements:
      - Write production-quality code
      - Add unit tests with >80% coverage
      - Commit changes with conventional commits
    template: node-20
    timeout: 30m
    budget: 15.00
```

The agent runs autonomously until it completes, hits the budget, or times out.

## Real-World Example: Feature Implementation

An AI agent implements a feature, another agent reviews it, human approves, then it deploys:

```yaml
# .caged.yaml — Agent-driven feature implementation
template: node-20
budget: 50.00
network_mode: allowlist
allowed_hosts:
  - api.anthropic.com
  - api.openai.com
  - github.com
  - registry.npmjs.org
secrets:
  - ANTHROPIC_API_KEY
  - GITHUB_TOKEN

pipelines:
  - name: agent-feature
    description: AI agents implement features with human oversight
    
    stages:
      # 1. Agent implements the feature
      - name: implement
        type: agent
        agent: claude-code
        prompt: |
          Implement the following feature:
          $FEATURE_SPEC
          
          Requirements:
          - Write production-quality code
          - Add unit tests with >80% coverage
          - Update documentation
          - Commit changes with conventional commits
        template: node-20
        timeout: 30m
        budget: 15.00

      # 2. Different agent reviews the implementation
      - name: code-review
        type: agent
        agent: aider
        prompt: |
          Review the changes made in the previous step:
          - Check for bugs, security issues, performance problems
          - Verify tests are comprehensive
          - Suggest improvements
          
          Output a structured review with PASS/FAIL and findings.
        depends_on: [implement]
        timeout: 10m
        budget: 5.00

      # 3. Run automated tests
      - name: test
        type: command
        command: npm test && npm run lint
        depends_on: [code-review]

      # 4. Gates before human review
      - name: quality-gate
        type: gate
        depends_on: [test]
        gate:
          trust_above: 70
          cost_below: 25.00

      # 5. Human reviews agent's work
      - name: human-approval
        type: await_approval
        depends_on: [quality-gate]
        approval:
          message: |
            Agent implemented: $FEATURE_SPEC
            
            Review the diff and agent's work before merging.
          channels: [slack, dashboard]
          sla_timeout: 24h

      # 6. Agent addresses feedback if any
      - name: address-feedback
        type: agent
        agent: claude-code
        prompt: |
          Address the reviewer's feedback:
          $APPROVAL_COMMENTS
        depends_on: [human-approval]
        condition:
          if: approval.has_comments
        timeout: 15m

      # 7. Merge and deploy
      - name: merge
        type: command
        command: git push origin HEAD:main
        depends_on: [address-feedback]
```

This pipeline:
1. **Agent implements** — Claude Code writes code, tests, docs
2. **Agent reviews** — Different agent (Aider) provides independent review
3. **Tests run** — Automated validation
4. **Gates check** — Trust score and cost limits
5. **Human approves** — Review the agent's work
6. **Agent fixes** — Address any feedback
7. **Merge** — Ship it

## More Real-World Pipelines

### Bug Fix Pipeline

Agent investigates and fixes bugs from issue tracker:

```yaml
pipelines:
  - name: bug-fix
    description: Agent investigates and fixes reported bugs
    
    stages:
      - name: investigate
        type: agent
        agent: claude-code
        prompt: |
          Investigate this bug report:
          $BUG_REPORT
          
          1. Reproduce the issue
          2. Find the root cause
          3. Document your findings
        timeout: 15m
        budget: 5.00

      - name: fix
        type: agent
        agent: claude-code
        prompt: |
          Fix the bug you investigated.
          - Write a failing test first
          - Implement the fix
          - Verify all tests pass
          - Commit with "fix: <description>"
        depends_on: [investigate]
        timeout: 20m
        budget: 10.00

      - name: verify
        type: command
        command: npm test
        depends_on: [fix]

      - name: approve
        type: await_approval
        depends_on: [verify]
        approval:
          message: "Bug fix ready for review. See diff."
          sla_timeout: 4h
```

### Dependency Upgrade Pipeline

Agent upgrades dependencies and fixes breaking changes:

```yaml
pipelines:
  - name: dep-upgrade
    description: Agent upgrades dependencies safely
    
    stages:
      - name: audit
        type: command
        command: npm audit --json > audit.json
        
      - name: upgrade
        type: agent
        agent: claude-code
        prompt: |
          Upgrade all dependencies with known vulnerabilities.
          Read audit.json for the list.
          
          For each upgrade:
          1. Update package.json
          2. Run npm install
          3. Fix any breaking changes
          4. Ensure tests pass
          5. Commit each upgrade separately
        depends_on: [audit]
        timeout: 45m
        budget: 20.00

      - name: test
        type: command
        command: npm test
        depends_on: [upgrade]

      - name: approve
        type: await_approval
        depends_on: [test]
        approval:
          message: "Dependency upgrades complete. Review changes."
```

### Code Migration Pipeline

Agent migrates codebase (e.g., JS → TypeScript):

```yaml
pipelines:
  - name: migrate-to-typescript
    description: Agent converts JavaScript to TypeScript
    
    stages:
      - name: setup
        type: command
        command: |
          npm install typescript @types/node --save-dev
          npx tsc --init
          
      - name: migrate-batch
        type: agent
        agent: claude-code
        prompt: |
          Convert the next batch of JavaScript files to TypeScript.
          
          Rules:
          - Convert 5-10 files per batch
          - Add proper type annotations
          - Fix any type errors
          - Ensure tests still pass
          - Track progress in MIGRATION.md
        depends_on: [setup]
        timeout: 30m
        budget: 15.00
        
      - name: typecheck
        type: command
        command: npx tsc --noEmit
        depends_on: [migrate-batch]

      - name: review-batch
        type: await_approval
        depends_on: [typecheck]
        approval:
          message: "Migration batch complete. Review before continuing."
          sla_timeout: 2h

      - name: continue-or-done
        type: gate
        depends_on: [review-batch]
        gate:
          # Custom expression: continue if JS files remain
          if: "count(glob('**/*.js')) > 0"
```

## Parallel Agent Execution

Run multiple agents in parallel when they don't depend on each other:

```yaml
pipelines:
  - name: multi-agent-review
    stages:
      - name: implement
        type: agent
        agent: claude-code
        prompt: "Implement feature: $FEATURE_SPEC"
        timeout: 30m

      # These three run in parallel after implement
      - name: security-review
        type: agent
        agent: claude-code
        prompt: "Security review: Check for vulnerabilities, injection risks, auth issues."
        depends_on: [implement]
        timeout: 10m
        budget: 3.00

      - name: perf-review
        type: agent
        agent: aider
        prompt: "Performance review: Check for N+1 queries, memory leaks, bottlenecks."
        depends_on: [implement]
        timeout: 10m
        budget: 3.00

      - name: test-coverage
        type: agent
        agent: codex
        prompt: "Add missing test cases to achieve 90% coverage."
        depends_on: [implement]
        timeout: 15m
        budget: 5.00

      # Waits for all three reviews
      - name: human-review
        type: await_approval
        depends_on: [security-review, perf-review, test-coverage]
        approval:
          message: "Three agents reviewed. Check their findings."
```

DAG visualization:

```
           implement
          /    |    \
   security  perf  coverage
          \    |    /
         human-review
```

## Gate Stages

Add guardrails before expensive operations:

```yaml
stages:
  - name: implement
    type: agent
    agent: claude-code
    prompt: "Build the feature"
    budget: 20.00

  - name: cost-gate
    type: gate
    depends_on: [implement]
    gate:
      cost_below: 15.00  # Fail if agent spent too much

  - name: trust-gate
    type: gate
    depends_on: [cost-gate]
    gate:
      trust_above: 75  # Fail if trust score dropped

  - name: deploy
    type: command
    command: npm run deploy
    depends_on: [trust-gate]
```

Gates check instantly:
- `cost_below` — fails if run cost exceeds threshold (USD)
- `trust_above` — fails if agent trust score is below threshold (0-100)

## Pipeline Defaults

Set defaults that apply to all stages:

```yaml
pipelines:
  - name: agent-pipeline
    defaults:
      template: node-20
      timeout: 20m
      budget: 10.00
      retry:
        max_attempts: 2
        backoff: 30s

    stages:
      - name: task-a
        type: agent
        agent: claude-code
        prompt: "Do task A"
        # Inherits: template, timeout, budget, retry

      - name: task-b
        type: agent
        agent: claude-code
        prompt: "Do task B"
        budget: 25.00  # Override default
        depends_on: [task-a]
```

## Conditional Execution

Run stages only when conditions are met:

```yaml
stages:
  - name: implement
    type: agent
    agent: claude-code
    prompt: "Implement the feature"

  - name: notify-success
    type: command
    command: |
      curl -X POST $SLACK_WEBHOOK \
        -d '{"text":"✅ Agent completed task successfully!"}'
    depends_on: [implement]
    condition:
      on_success: true

  - name: escalate-failure
    type: command
    command: |
      curl -X POST $PAGERDUTY_WEBHOOK \
        -d '{"message":"Agent failed. Human intervention needed."}'
    depends_on: [implement]
    condition:
      on_failure: true
```

## Triggering Pipelines

Once defined in `.caged.yaml`, trigger pipelines via CLI or API:

```bash
# List pipelines
caged pipeline list

# Start a feature implementation
caged pipeline run agent-feature \
  --env FEATURE_SPEC="Add dark mode toggle to settings page"

# Start a bug fix
caged pipeline run bug-fix \
  --env BUG_REPORT="Users report 500 error on /api/users endpoint"

# Check run status
caged pipeline runs agent-feature
```

Or via API:

```bash
curl -X POST https://api.caged.dev/v1/pipelines/agent-feature/runs \
  -H "Authorization: Bearer caged_sk_..." \
  -d '{
    "env": {
      "FEATURE_SPEC": "Add dark mode toggle to settings page"
    }
  }'
```

## Failure Handling

Control what happens when an agent stage fails:

```yaml
stages:
  - name: primary-agent
    type: agent
    agent: claude-code
    prompt: "Implement the feature"
    on_failure: continue  # Don't fail pipeline

  - name: fallback-agent
    type: agent
    agent: aider  # Try different agent
    prompt: "Complete the task the previous agent couldn't finish"
    depends_on: [primary-agent]
    condition:
      on_failure: true  # Only run if primary failed
```

## Multiple Pipelines

Define different pipelines for different workflows:

```yaml
pipelines:
  - name: feature
    description: Agent implements new features
    stages:
      - name: implement
        type: agent
        agent: claude-code
        prompt: "$FEATURE_SPEC"
      # ...

  - name: bugfix
    description: Agent investigates and fixes bugs
    stages:
      - name: investigate
        type: agent
        agent: claude-code
        prompt: "$BUG_REPORT"
      # ...

  - name: refactor
    description: Agent refactors code
    stages:
      - name: analyze
        type: agent
        agent: claude-code
        prompt: "Analyze $TARGET_PATH for refactoring opportunities"
      # ...
```

## Shared State Store

Pipelines include a built-in key/value store for sharing data between stages. Instead of passing artifacts via git branches or external storage, stages can read and write to a shared state store scoped to the run.

### Writing State from Stages

Agent stages can write to state using the `caged state` command available in the sandbox:

```yaml
stages:
  - name: analyze
    type: agent
    agent: claude-code
    prompt: |
      Analyze the codebase and identify files that need refactoring.
      
      When done, save your findings:
      caged state set analysis_results '{"files": [...], "priority": "high"}'

  - name: refactor
    type: agent
    agent: claude-code
    prompt: |
      Read the analysis results and refactor the identified files.
      
      caged state get analysis_results
    depends_on: [analyze]
```

### State Types

State entries support different types for structured data:

| Type | Description | Example Use |
|------|-------------|-------------|
| `string` | Plain text | Status messages, IDs |
| `json` | JSON object | Analysis results, configs |
| `file` | File reference | Generated artifacts |
| `patch` | Git diff | Code changes for review |
| `artifact` | Binary data | Build outputs |

```yaml
# Saving different types via CLI in stage commands
stages:
  - name: generate
    type: command
    command: |
      # String
      caged state set status "completed"
      
      # JSON
      caged state set config '{"debug": true}' --type json
      
      # File reference
      caged state set build_artifact './dist/app.js' --type file
      
      # Git patch
      git diff HEAD~1 | caged state set changes - --type patch
```

### State TTL and Limits

State entries have automatic expiration and size limits:

- **Max key length:** 256 characters
- **Max value size:** 1 MB per entry
- **Max entries per run:** 100
- **Max total size per run:** 10 MB
- **Default TTL:** 7 days
- **Max TTL:** 30 days

Set custom TTL when writing:

```bash
# Expires in 1 hour
caged state set temp_data '{"key": "value"}' --ttl 3600
```

### Accessing State via CLI

```bash
# List all state for a run
caged pipeline state list <pipeline-id> <run-id>

# Get a specific entry
caged pipeline state get <pipeline-id> <run-id> analysis_results

# Set a value
caged pipeline state set <pipeline-id> <run-id> review_status '"approved"'

# Set from file
caged pipeline state set <pipeline-id> <run-id> config -f config.json

# Delete an entry
caged pipeline state delete <pipeline-id> <run-id> temp_data
```

### Accessing State via API

```bash
# List state
curl https://api.caged.dev/v1/pipelines/{id}/runs/{runId}/state \
  -H "Authorization: Bearer caged_sk_..."

# Get single entry
curl https://api.caged.dev/v1/pipelines/{id}/runs/{runId}/state/analysis_results \
  -H "Authorization: Bearer caged_sk_..."

# Set entry
curl -X PUT https://api.caged.dev/v1/pipelines/{id}/runs/{runId}/state/my_key \
  -H "Authorization: Bearer caged_sk_..." \
  -d '{
    "value": {"result": "success"},
    "type": "json",
    "ttl_seconds": 86400,
    "created_by": "api"
  }'

# Delete entry
curl -X DELETE https://api.caged.dev/v1/pipelines/{id}/runs/{runId}/state/temp_key \
  -H "Authorization: Bearer caged_sk_..."
```

### Multi-Agent Handoff with State

State is essential for multi-agent pipelines where one agent's output becomes another's input:

```yaml
pipelines:
  - name: ai-code-review
    stages:
      - name: implement
        type: agent
        agent: claude-code
        prompt: |
          Implement: $FEATURE_SPEC
          
          When complete, save a summary:
          caged state set implementation '{"files_changed": [...], "approach": "..."}'
        budget: 15.00

      - name: security-review
        type: agent
        agent: claude-code
        prompt: |
          Review for security issues.
          
          First, get implementation context:
          caged state get implementation
          
          Save your findings:
          caged state set security_review '{"issues": [...], "severity": "..."}'
        depends_on: [implement]
        budget: 5.00

      - name: compile-report
        type: command
        command: |
          # Combine all reviews into final report
          impl=$(caged state get implementation)
          security=$(caged state get security_review)
          echo "{\"implementation\": $impl, \"security\": $security}" > report.json
          caged state set final_report -f report.json
        depends_on: [security-review]

      - name: human-approval
        type: await_approval
        depends_on: [compile-report]
        approval:
          message: "Review complete. See state entry: final_report"
```

State keeps agents loosely coupled — they communicate through shared state rather than direct dependencies.

---

## Full Example

A complete `.caged.yaml` with sandbox config and pipelines:

```yaml
# .caged.yaml
template: node-20
resources:
  cpu: 4
  memory: 4096
  disk: 20
timeout: 3600
budget: 25.00
network_mode: allowlist
allowed_hosts:
  - api.openai.com
  - registry.npmjs.org
  - github.com
secrets:
  - OPENAI_API_KEY
  - GITHUB_TOKEN

policy:
  template: soc2
  require_approval:
    - "rm -rf *"
    - "drop table"

pipelines:
  - name: agent-ci-cd
    description: AI agent builds, tests, and deploys with human oversight
    defaults:
      template: node-20
      timeout: 15m
      retry:
        max_attempts: 2
        backoff: 30s

    stages:
      - name: clone
        type: command
        command: git clone $REPO_URL repo && cd repo

      - name: install
        type: command
        command: cd repo && npm ci
        depends_on: [clone]
        timeout: 10m

      - name: build
        type: command
        command: cd repo && npm run build
        depends_on: [install]

      - name: test
        type: command
        command: cd repo && npm test
        depends_on: [build]
        retry:
          max_attempts: 3
          backoff: 10s

      - name: lint
        type: command
        command: cd repo && npm run lint
        depends_on: [build]
        on_failure: continue

      - name: security-scan
        type: command
        command: cd repo && npm audit --audit-level=high
        depends_on: [build]
        on_failure: continue

      - name: cost-gate
        type: gate
        depends_on: [test, lint, security-scan]
        gate:
          cost_below: 10.00

      - name: trust-gate
        type: gate
        depends_on: [cost-gate]
        gate:
          trust_above: 75

      - name: approve-deploy
        type: await_approval
        depends_on: [trust-gate]
        approval:
          message: "All checks passed. Deploy to production?"
          channels: [slack, dashboard]
          sla_timeout: 4h
          auto_decision: reject

      - name: deploy
        type: command
        command: cd repo && npm run deploy:prod
        depends_on: [approve-deploy]
        env:
          DEPLOY_ENV: production

      - name: notify-success
        type: command
        command: |
          curl -X POST $SLACK_WEBHOOK \
            -d '{"text":"✅ Deployment successful!"}'
        depends_on: [deploy]
        condition:
          on_success: true

      - name: notify-failure
        type: command
        command: |
          curl -X POST $SLACK_WEBHOOK \
            -d '{"text":"❌ Pipeline failed. Check dashboard."}'
        depends_on: [deploy]
        condition:
          on_failure: true
```

## Triggering Pipelines

Once defined in `.caged.yaml`, pipelines are synced to Caged when you create a sandbox:

```bash
# Create sandbox from repo with pipelines
caged create --repo https://github.com/myorg/myapp

# List available pipelines
caged pipeline list

# Start a pipeline run
caged pipeline run agent-ci-cd
```

Or via API:

```bash
# Pipelines are auto-synced on sandbox creation
# Start a run
curl -X POST https://api.caged.dev/v1/pipelines/agent-ci-cd/runs \
  -H "Authorization: Bearer caged_sk_..." \
  -d '{"env": {"REPO_URL": "https://github.com/myorg/myapp"}}'
```

## Next Steps

- [Pipeline State Store Guide](/guides/state-store) — detailed patterns for shared state
- [Pipelines Guide](/guides/pipelines) — core concepts and best practices
- [Multi-Agent Collaboration](/recipes/multi-agent) — multiple agents on the same codebase
- [Pipelines API Reference](/api-reference/pipelines) — full endpoint documentation
- [Approvals Guide](/guides/approvals) — configuring HITL approval channels
