Autonomous issue-to-PR pipeline. Conductor watches a GitHub repo for labeled issues, implements them with Claude Code via TDD, validates the result, and opens a pull request — so you review working code, not promises.
GitHub Issue (conductor:todo) → Claude Code (TDD) → Lint/Build → QA → PR with Closes #N → You review
Conductor runs as a local CLI on the developer's machine. It polls GitHub for issues labeled conductor:todo + conductor:afk, picks them up one at a time, and drives each through a fixed pipeline:
- Pickup — selects the oldest eligible issue, clones the repo, creates a feature branch (
conductor/{{number}}-{{slug}}). - Implement — spawns
claude --printwith a structured prompt containing acceptance criteria, user stories, and parent PRD context. Claude implements via TDD. - Validate — runs configured checks (lint, build, type-check). Stops on first failure.
- QA — stubbed for v1, returns skipped. Architecture supports future agent-browser testing.
- PR — pushes the branch, creates a pull request with
Closes #Nin the body. Transitions labels toconductor:review. - Your review — you open the PR, read the diff, and either approve or request changes. That's your only touchpoint.
- Rework (if needed) — when you label an issue
conductor:rework, Conductor fetches review comments, re-runs the agent, and force-pushes (--force-with-lease) to the same branch. - Merge — on approval, Conductor transitions labels to
conductor:done.
In "wait-for-merge" mode (default), Conductor polls until the current PR merges before picking up the next issue.
All behavior is controlled by a single file in your repository: CONDUCTOR.md. YAML front matter for settings, Markdown body for the agent prompt.
---
github:
owner: your-org
repo: your-repo
token: $GITHUB_TOKEN
labels:
todo: "conductor:todo"
in_progress: "conductor:in-progress"
review: "conductor:review"
rework: "conductor:rework"
done: "conductor:done"
afk: "conductor:afk"
branch:
pattern: "conductor/{{number}}-{{slug}}"
workspace:
root: ./workspaces
after_clone:
- "pnpm install"
agent:
command: claude
max_turns: 10
retry_budget: 3
allowed_tools: "Edit,Write,Bash(*)"
timeout_minutes: 30
max_cost_per_issue: 5.0
validate:
commands:
- "pnpm run lint"
- "pnpm run build"
qa:
enabled: true
pr:
draft: false
base_branch: "main"
labels: ["conductor"]
polling:
interval_ms: 10000
backoff_max_ms: 60000
sequencing:
wait_for_merge: true
---
You are implementing issue #{{ issue.number }}: {{ issue.title }}.
{{ issue.body }}
## Parent PRD context
{{ prd.body }}
## Acceptance criteria
{{ acceptance_criteria }}
## User stories
{{ user_stories }}Conductor reads this file at startup. Change the prompt, adjust retry budgets, swap validation commands — it's all here, versioned with your code.
Conductor uses labels as the contract boundary between itself and Doit (or any external system). Create these labels in your GitHub repo:
| Label | Purpose |
|---|---|
conductor:todo |
Issues waiting to be picked up |
conductor:in-progress |
Agent is working |
conductor:review |
PR created, waiting for your review |
conductor:rework |
You requested changes |
conductor:done |
Merged and closed |
conductor:afk |
Set by Doit on issues it creates |
Doit sets conductor:todo + conductor:afk on issues it creates. Conductor picks up issues with both labels.
Conductor works best with issues that are specific and verifiable. Each issue should have:
A clear title — what to build, not what's wrong.
Acceptance criteria — checklist of behaviors that must be true.
User stories — who benefits and how.
A "Blocked by" reference (optional) — Blocked by #N. Conductor parses this and skips issues whose blockers aren't closed.
A "Parent PRD" reference (optional) — #N linking to a broader PRD issue. Conductor fetches that issue's body for additional context.
| Tool | Purpose | Install |
|---|---|---|
| Claude Code | AI coding agent | npm install -g @anthropic-ai/claude-code |
| GitHub CLI | PR creation and management | brew install gh && gh auth login |
| Node.js | Runtime (≥22) | brew install node |
| Git | Version control | Pre-installed on most systems |
You also need:
- A GitHub personal access token with repo scope.
- An Anthropic API key for Claude Code (or a Claude Code subscription).
# Install globally from GitHub
npm install -g github:auditmos/conductor
# Verify
conductor --help
# Uninstall
npm uninstall -g conductor# Start Conductor (reads CONDUCTOR.md from current directory)
conductor
# Start with a specific config file
conductor --config /path/to/CONDUCTOR.mdConductor runs in the foreground and logs to stdout. Use a process manager (pm2, systemd, or just tmux) to keep it running. Run one instance per repo.
A single Node.js process with .conductor-state.json for crash recovery.
src/
├── index.ts # CLI entry, startup, signal handlers
├── lib/
│ ├── config.ts # CONDUCTOR.md parser (gray-matter + Zod)
│ ├── template.ts # {{ variable }} template renderer
│ ├── state.ts # .conductor-state.json persistence
│ ├── github.ts # Octokit wrapper (issues, labels, PRs)
│ ├── workspace.ts # Clone, branch, cleanup
│ ├── agent.ts # claude --print invocation
│ ├── validate.ts # Sequential command runner
│ ├── qa.ts # QA stub (future agent-browser)
│ ├── pr.ts # Push + PR creation
│ └── orchestrator.ts # State machine, poll loop
No framework, no ORM, no queue system. If the process dies, restart it — it picks up where it left off from .conductor-state.json and GitHub label state.
GitHub-native. No Doit API dependency. GitHub labels are the sole contract boundary. Any system that can read/write GitHub labels can integrate.
Sequential dispatch. One issue at a time. No parallelism, no merge conflicts, no race conditions. Wait-for-merge mode polls until the current PR merges before picking the next issue.
Local state + GitHub as source of truth. .conductor-state.json enables crash recovery. GitHub labels are the canonical state — Conductor reconstructs from them on restart.
In-repo configuration. CONDUCTOR.md lives in your repository. Change the prompt, adjust timeouts, add validation commands — it's a code review, not a config migration.
Env var resolution. Secrets like $GITHUB_TOKEN are resolved from process.env at startup, keeping them out of the config file.
Apache 2.0