What lives on disk
The data repo
my-data-repo/ pipelines.yaml # channels: {}, and optionally defaults: .env # secrets, gitignored .params.local.json # local params, gitignored (written by `install`) agents/ <agent-name>.md # YAML front matter + Markdown prompt pipelines/ <pipeline-name>/ pipeline.yaml steps/ <step>.md # prompts for `agent` steps, by convention scripts/ <notify-channel>.sh # scripts invoked from `channels.<x>.run` .runs/ # gitignored, generated by the engine .gitignore # must include: .env, .params.local.json, .runs/pipelines.yaml
It lives at the root of the data repo, not the engine's. It can be empty (channels: {}), but it must exist: it's the marker the engine uses to find the repo root by walking up directories.
channels: email: type: shell env: [EMAIL_API_KEY] run: scripts/notify-email.shThe channel fields and what a channel script receives are in Notifications.
pipelines.yaml also accepts a repo-level defaults: block (on_error, retry, timeout, notify), with the same shape as a pipeline's defaults:. It is currently not applied to pipelines automatically: each pipeline.yaml still declares its own.
Secrets
Never in plaintext in any YAML. A secret is declared by name in requires.env (a pipeline) or channels.<x>.env (a notification channel), and its value is resolved at runtime:
- From
.envat the root of the data repo (KEY=valuelines,#comments, optional quotes). It takes precedence. - If it isn't there, from the process's own environment.
ANTHROPIC_API_KEY is the one value you don't declare: the engine reads it from .env (or the environment) by itself and passes it only to agent steps.
A name in requires.env can depend on params (QA_{{params.app}}_USER); see Secret names that depend on params.
.env and .params.local.json are in the data repo's expected .gitignore; they are never committed.
.runs/
A gitignored directory at the root of the data repo, with one subdirectory per pipeline and per run:
.runs/ .locks/ <pipeline>.lock <pipeline>/ <run-id>/ # ISO 8601 with ':' and '.' replaced by '-' run.json <step-1>.log <step-2>.log ...Each <step>.log holds the step's transcript, with any secret value already replaced by [redacted] before it touches disk.
run.json
The run record:
| Field | Notes |
|---|---|
id, pipeline, pipelineVersion | |
status | running, success, failed or skipped |
startedAt, finishedAt | ISO 8601 |
params | The resolved params this run ran with |
steps | Map of step id → step record |
totalCostUsd | Sum of costUsd across all agent steps |
skipReason | Only if status is skipped |
And per step:
| Field | Notes |
|---|---|
status, startedAt, durationMs, attempts | |
outputs | Already validated against the step's outputs: contract |
costUsd, inputTokens, outputTokens, toolsUsed | Only in agent steps |
error | Failure message, if any |
skipReason | If the step was skipped by its step-level when: |
denials | Tools denied by the sandbox hook; see Agent steps and the sandbox |
Where everything else lives
| What | Where | Notes |
|---|---|---|
| Run history | .runs/ in the data repo | Gitignored. One subdirectory per pipeline and per run. |
| Locks | .runs/.locks/<pipeline>.lock | If a run died holding the lock, run exits with 3 until it's cleared. |
| Installed params | .params.local.json in the data repo | Gitignored. Written by install --set. |
| Secrets | .env in the data repo | Gitignored. Only names go in the YAML, never values. |
| Workspaces | By convention, ~/Library/Application Support/pipelines/workspaces/<pipeline>/ on macOS and ~/.local/share/pipelines/workspaces/<pipeline>/ on Linux, passed in through a param | Each pipeline's own clones. Pipelines shouldn't work in your checkouts: to see what they produce, git fetch in yours. |
| Scheduled jobs | macOS: ~/Library/LaunchAgents/cat.start.pipelines.<name>.plist. Linux: ~/.config/systemd/user/cat.start.pipelines.<name>.{service,timer} | Derived from triggers.cron. No param values inside. |
| Job logs | macOS: the path in the plist (StandardOutPath / StandardErrorPath), by default ~/Library/Logs/pipelines-<name>.log. Linux: ~/.local/state/pipelines/logs/pipelines-<name>.log | What happened before the engine got as far as writing to .runs/. |