Pipelines¶
Every Developer command runs a pipeline. A pipeline is a plain Python function in src/developer/pipelines/<name>.py: it calls the agent for each stage, looks at what came back, and decides what to do next with ordinary if, match and loops.
Stages¶
A stage is a single agent invocation, described by a Stage(name, template, profile):
| Field | What it does |
|---|---|
name |
The stage's name, also how claude.stages in developer.yaml configures its model, effort, budget and turn limit |
template |
Jinja2 template for the prompt |
profile |
The ToolProfile that decides which tools exist for the agent |
A pipeline runs a stage through its StageRunner (pipelines/runner.py):
ask(stage, Model, resume=..., **context)for a stage that answers with structured output. It returns the validated model and the session that produced it.work(stage, resume=..., **context)for a stage that changes files. It returns the session.
The runner renders the template from the context it's given, runs the agent, and records the stage's metrics. Everything around the agent, such as diffs, commits, the quality tools, pushes and comments, is ordinary code in the pipeline function.
Sessions¶
Stages share conversational context through sessions. A stage that should continue another's conversation is passed that stage's session with resume=. Otherwise it starts fresh. This means craft_pr can describe what the implementation saw and changed, without re-reading the entire codebase.
A stage that returns no session stops the pipeline with an error. A later stage that expects its context would otherwise run without it and quietly produce worse output.
The safety boundary¶
This is the part that matters. The agent can read code, write files, and run shell commands. But it cannot:
- Run
git add,git commit,git push, or any destructive git command - Create pull requests or issues
- Post comments on GitHub
All of that happens in the pipeline functions' own Python code. The agent returns structured data (a PR title, a commit message, a list of findings). Python takes that data and does the actual operations.
The boundary is enforced in layers, all applied by the Claude provider for every stage:
- Tool profiles. Each stage gets a profile (
none,read,read_run,edit,edit_run) that decides which built-in tools exist for its agent. A stage that returns structured data never gets Write, Edit or Bash. - No prompts, no bypass. The agent runs in
dontAskmode, so anything outside its profile is refused rather than auto-approved, and in Claude Code's restricted mode, which keeps file tools inside the worktree and ignores the target repo's own Claude settings. - A guard before every tool call. Bash commands may use only read-only git subcommands (
status,diff,log, ...), and file tools are refused outside the worktree. - A sandbox around Bash. Commands can only write inside the worktree, the temp directory and paths the repo's
sandboxconfig allows, can only reach the repo's allowed domains, and can't read credential files or credential environment variables. If the sandbox can't start, commands fail instead of running unsandboxed. - No secrets in the environment. GitHub App credentials are read from
.envwithout being exported, and blanked in the agent's environment.
Quality gates¶
The resolve and address pipelines run the repo's quality tools after implementation, up to three times. Between runs, the agent fixes what the tools reported, continuing its implementation session. If the tools still fail on the last run, resolve opens a draft PR that names the remaining problems, and address pushes what it has. A tool that runs past its timeout counts as failing.
Metrics¶
Every stage records timing, turn count, tool calls, token usage and cost. These are appended to PR bodies and comments as a collapsible <details> block, and saved with the run's transcript in runs/. Good for understanding cost and spotting stages that take longer than expected.