Autonomous software engineering agent powered by Claude AI. Monitors your GitHub repositories, picks up issues labeled agent-ready, implements fixes, opens PRs, waits for CI, merges, and cuts releases — without human intervention.
curl -fsSL https://raw.githubusercontent.com/programmism/junior-swarm/main/install.sh | bashOr pull the Docker image directly:
docker pull ghcr.io/programmism/junior-swarm:latestChoose the Claude backend that fits your setup:
| Mode | When to use | Billing |
|---|---|---|
API key (backend: api) |
You have an Anthropic API key | Per token |
CLI (backend: cli) |
You have a Claude.ai subscription (Pro/Team/Max) | Flat monthly |
1. Create a GitHub token
Go to GitHub → Settings → Developer settings → Personal access tokens → Fine-grained tokens and create a token with:
| Permission | Access |
|---|---|
| Contents | Read and write |
| Issues | Read and write |
| Pull requests | Read and write |
| Workflows | Read and write |
| Metadata | Read-only (auto-selected) |
2. Create config.yaml
global:
backend: api # use ANTHROPIC_API_KEY
projects:
- name: "my-app"
repo: "myorg/my-app"
github_token_env: "GITHUB_TOKEN"
workdir: "/workspace/my-app"3. Create .env
ANTHROPIC_API_KEY=sk-ant-...
GITHUB_TOKEN=ghp_...4. Start
docker run -d \
--name junior-swarm \
--env-file .env \
-v $(pwd)/config.yaml:/config/config.yaml:ro \
-v junior-swarm-workspace:/workspace \
-p 8080:8080 \
ghcr.io/programmism/junior-swarm:latestCLI mode runs Claude through the claude CLI subprocess, which uses the credentials stored by claude login. No API key needed — billing goes through your Claude.ai subscription.
1. Install the claude CLI and log in
npm install -g @anthropic-ai/claude-code
claude loginVerify it works:
claude -p "say hello" --output-format json2. Create a GitHub token (same as Option A above)
3. Create config.yaml
global:
backend: cli # use claude CLI subprocess
projects:
- name: "my-app"
repo: "myorg/my-app"
github_token_env: "GITHUB_TOKEN"
workdir: "/workspace/my-app"4. Create .env
GITHUB_TOKEN=ghp_...
# No ANTHROPIC_API_KEY needed5. Start (mount the claude credentials from your home directory)
docker run -d \
--name junior-swarm \
--env-file .env \
-v $(pwd)/config.yaml:/config/config.yaml:ro \
-v junior-swarm-workspace:/workspace \
-v $HOME/.claude:/root/.claude:ro \
-p 8080:8080 \
ghcr.io/programmism/junior-swarm:latestNote: The
-v $HOME/.claude:/root/.claude:romount passes your claude CLI credentials into the container. Runclaude loginonce on the host before starting the container.
Add the label agent-ready to any issue in your repo. The agent picks it up on the next poll (default: every 5 minutes) and runs the full development cycle automatically.
Check status: http://localhost:8080/status
Each project runs its own loop. On every cycle the agent goes through these stages in order:
triage → planning → implementation → pull_request → release → validation
| Stage | What the agent does |
|---|---|
| triage | Searches for open issues labeled agent-ready. If none found, checks the configured backlog. If still nothing, goes idle until next poll. |
| planning | Reads the codebase, understands the issue, designs the approach. Breaks large issues into sub-issues if needed. |
| implementation | Creates a feature branch, writes code, adds tests, runs go test / linter, commits. Fixes all failures before moving on. |
| pull_request | Pushes the branch, opens a PR, polls CI checks every 60 s. If checks fail — reads logs, fixes, pushes again. When all checks pass, merges. |
| release | Reads existing tags, determines next semantic version, pushes a tag, creates a GitHub Release. |
| validation | Watches deployment pipelines. If something breaks, files a new issue and restarts the cycle to fix it. On success, marks the cycle complete. |
You can replace the default cycle with your own stages in config.yaml. Each stage has:
stage— name (used in logs)hint_prompt— instructions injected into the conversation at the start of this stageskip_if— condition to skip this stage (see below)max_iter— max Claude API calls for this stage (overrides globalmax_cycle_iterations)
Minimal 2-stage example — just implement and open a PR, skip release and validation:
projects:
- name: "my-app"
repo: "myorg/my-app"
github_token_env: "GITHUB_TOKEN"
workdir: "/workspace/my-app"
cycle:
- stage: "triage"
hint_prompt: |
Find an open issue labeled 'agent-ready'.
Call CycleSetIssue with the issue number, then CycleAdvance.
If nothing to do, call CycleIdle.
- stage: "implement-and-pr"
hint_prompt: |
Create a branch, implement the fix, run tests, push, open a PR.
Wait for CI. When merged, call CycleComplete.Skip stages conditionally:
cycle:
- stage: "triage"
hint_prompt: "..."
- stage: "implementation"
hint_prompt: "..."
- stage: "release"
hint_prompt: "..."
skip_if: "no_pr" # skip release if no PR was merged this cycle
max_iter: 10 # release needs fewer iterationsskip_if supported values:
| Value | Skips when |
|---|---|
no_issue |
No work item was selected (triage went idle) |
no_branch |
No branch was created yet |
no_pr |
No PR was opened yet |
Full config with all options:
global:
# Claude backend — "api" (default) or "cli"
backend: "api"
# Path to the claude CLI binary (only used when backend is "cli").
# Defaults to "claude" (looked up from PATH).
claude_path: "claude"
claude_model: "claude-opus-4-6" # Claude model (api mode only)
max_tokens: 8192 # Max tokens per response (api mode only)
max_cycle_iterations: 40 # Max tool calls per stage
cycle_interval_seconds: 300 # Seconds between cycles when idle
log_level: "info" # debug | info | warn | error
log_format: "json" # json | text
max_retries: 3 # GitHub API retry attempts on transient errors
retry_backoff_seconds: 2 # Base backoff (doubles: 2s, 4s, 8s…)
requests_per_minute: 0 # Claude API rate limit (0 = unlimited, api mode only)
projects:
- name: "my-app"
repo: "owner/repo" # required
github_token_env: "GITHUB_TOKEN" # required — name of env var holding the PAT
workdir: "/workspace/my-app" # required — clone directory inside container
# --- Optional ---
# Per-project backend override (overrides global.backend for this project only)
backend: "cli"
claude_path: "/usr/local/bin/claude"
# Static tasks when no GitHub issues exist.
# Completed items are remembered across restarts.
backlog:
- title: "Add health check endpoint"
body: "Implement GET /health returning 200 OK with JSON {status: ok}"
# Extra tools installed once at startup (tracked by .toolchain-installed marker)
toolchain:
- name: "golangci-lint"
install: |
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh \
| sh -s -- -b /usr/local/bin v1.62.0
# Env vars injected into every shell command for this project
env:
GOPROXY: "https://proxy.golang.org"
# Per-project overrides of global defaults
claude_model: "claude-opus-4-6"
max_tokens: 16384
max_cycle_iterations: 60
cycle_interval_seconds: 600
max_retries: 5
requests_per_minute: 10
# Custom cycle (omit to use built-in 6-stage default)
cycle:
- stage: "triage"
hint_prompt: "..."
- stage: "implementation"
hint_prompt: "..."
max_iter: 80
- stage: "release"
hint_prompt: "..."
skip_if: "no_pr"projects:
- name: "backend"
repo: "myorg/backend"
github_token_env: "GITHUB_TOKEN" # same var name
workdir: "/workspace/backend"
- name: "frontend"
repo: "myorg/frontend"
github_token_env: "GITHUB_TOKEN" # same var name
workdir: "/workspace/frontend"# .env
GITHUB_TOKEN=ghp_one_token_for_bothprojects:
- name: "backend"
repo: "myorg/backend"
github_token_env: "GITHUB_TOKEN_BACKEND"
- name: "client-project"
repo: "client-org/their-repo"
github_token_env: "GITHUB_TOKEN_CLIENT"# .env
GITHUB_TOKEN_BACKEND=ghp_...
GITHUB_TOKEN_CLIENT=ghp_...The value of github_token_env is just the name of an environment variable. You pick the name, then define that variable in .env (or your secrets manager). The agent reads the token at runtime using os.Getenv(github_token_env).
Prerequisites: Go 1.23+, git, gh CLI, and either an Anthropic API key or the claude CLI.
git clone https://github.com/programmism/junior-swarm.git
cd junior-swarm
go build -o junior-swarm ./cmd/junior-swarm
# API mode:
export ANTHROPIC_API_KEY=sk-ant-...
export GITHUB_TOKEN=ghp_...
./junior-swarm --config config/config.yaml
# CLI mode (after `claude login`):
export GITHUB_TOKEN=ghp_...
./junior-swarm --config config/config.yaml # config.yaml has backend: cliservices:
junior-swarm:
image: ghcr.io/programmism/junior-swarm:latest
env_file: .env
volumes:
- ./config.yaml:/config/config.yaml:ro
- workspace:/workspace
# For CLI mode: mount claude credentials from host
# - $HOME/.claude:/root/.claude:ro
ports:
- "8080:8080"
restart: unless-stopped
volumes:
workspace:docker compose up -d| Endpoint | Description |
|---|---|
GET /health |
{"status":"ok"} when running |
GET /status |
JSON with each agent's current stage and last error |
Token usage is logged at INFO level after each stage (API mode only):
{"level":"INFO","msg":"stage tokens","input":1240,"output":380,"cache_read":0,"cache_write":0}
cmd/junior-swarm/ entry point
internal/
agent/ cycle orchestration, rate limiter
backend/ Claude execution backends (api, cli)
config/ YAML config loading and validation
cyclestate/ per-cycle mutable state, backlog persistence
logging/ structured logging setup
supervisor/ multi-agent management, health server
tools/ 20 agent tools (bash, git, GitHub API, file system, cycle control)
workspace/ repo cloning, sandboxed command execution
channels/ inbox channels (Telegram, Slack, MCP)
inbox/ user↔agent messaging between stages
config/
example.yaml full configuration reference
docker/
Dockerfile multi-stage build
entrypoint.sh container startup
docker-compose.yml
install.sh one-liner installer