Critters is a TypeScript daemon that polls issue trackers (Linear and Jira) for issues labeled "Critter", spawns coding CLI instances (Claude Code and/or Codex) to plan and implement the work, and opens draft pull requests for human review. It runs on Bun and orchestrates everything through tmux panes.
Inspired by Stripe's Minions.
curl -fsSL https://raw.githubusercontent.com/ack-ventures/critters/main/install.sh | bashThis downloads the latest binary, installs it to your PATH, and walks you through initial setup.
critters— start the daemoncritters version— show versioncritters update— check for and apply updatescritters stop— stop the daemon gracefullycritters init— (re-)configure~/.critters/critters status— show daemon status (active/queued critters, today's stats)critters retry <ID>— retry a failed critter (reset to Todo)critters logs <ID>— show logs for a critter runcritters kickoff— trigger an immediate poll (instead of waiting for the next interval)critters list-types— show configured critter typescritters init-repo— scaffold.critters.yamlin the current repocritters prompt-help— launch Claude to help design critter types and promptscritters clean— clean up stale work directories (--all,--dry-run,--branches,--panes)critters release-notes— show release notes for recent versionscritters validate— validate config file without starting daemoncritters help— show usage
--dry-run— poll once, show what would happen, and exit--no-tmux— daemonize to background without tmux (logs to~/.critters/critters.log, writes PID to~/.critters/critters.pid)--no-watch— disable config file watching (no hot-reload)--skip-update— skip auto-update check on startup--config PATH— use a custom config file--type NAME— filter dry-run to a specific critter type--json-logs— output structured JSON logs (one object per line)
See docs/self-hosting.md for the full deployment guide.
Quick start (binary):
curl -fsSL https://raw.githubusercontent.com/ack-ventures/critters/main/install.sh | bash
critters init
tmux new -s critters
crittersPrerequisites: Bun, Claude Code CLI (authenticated) and/or Codex, gh CLI (authenticated), tmux, jq
# Clone and install
git clone https://github.com/ack-ventures/critters && cd critters
bun install
# Configure
cp .env.example .env
# Edit .env and set LINEAR_API_KEY (for Linear) and/or JIRA_HOST, JIRA_EMAIL, JIRA_API_TOKEN (for Jira)
# Optionally set SLACK_WEBHOOK_URL for notifications, or SLACK_BOT_TOKEN + SLACK_CHANNEL for threaded notifications
# Optionally tweak critters.config.yaml
# Run
bun start
# or: bun run src/index.ts# Clone and configure
git clone https://github.com/ack-ventures/critters && cd critters
cp .env.example .env
# Edit .env — set ANTHROPIC_API_KEY and LINEAR_API_KEY (and/or JIRA_* vars)
# Start
docker compose up -d
# View logs
docker compose logs -f
# Dashboard at http://localhost:3847The Docker image includes all runtime dependencies (Claude Code CLI, gh, git, jq).
Auth requirements:
CLAUDE_CODE_OAUTH_TOKEN— Claude Max/Team subscription token (runclaude setup-tokenon the host, then add to.envor export in your shell). Alternatively, setANTHROPIC_API_KEYfor direct API access.~/.ssh— SSH keys for git clone (mounted read-only)GITHUB_TOKEN— GitHub CLI auth (rungh auth tokenon the host, then add to.envor export in your shell)
For the pre-built image, replace build: . with image: ghcr.io/ack-ventures/critters:latest in docker-compose.yaml.
To use the latest released binary instead of building from source, set the build target to prod:
# docker-compose.yaml
services:
critters:
build:
context: .
target: prod # downloads released binary instead of building from sourceOr build directly: docker build --target prod . (append --build-arg CRITTERS_VERSION=1.6.1 to pin a version).
- Watcher polls your issue tracker (Linear and/or Jira) every 120 seconds for issues with the "Critter" label in "Todo" status.
- Spawner shallow-clones the target repo into a temp directory and creates a feature branch.
- Phase 1 (Planning): A configured coding CLI instance explores the codebase and writes an implementation plan.
- Phase 2 (Execution): A configured coding CLI instance implements the plan, commits, pushes, and opens a draft PR.
- Status updates flow back to the issue tracker: Todo → In Progress → In Review (on PR) or Critter Failed (on error).
The built-in critter types form a pipeline:
Todo → In Progress → In Review → review ──┬──► Done (merged)
(create) │
┌──────────────────┘
▼
Human Review
│
└──► fix-review-comments ──► In Review (loops back to review)
- create picks up "Todo" issues, plans and implements the work, opens a draft PR, and moves the issue to "In Review".
- review picks up "In Review" issues, reviews the PR diff, and either merges it (→ "Done") or requests changes (→ "Human Review").
- fix-review-comments (optional, user-configured) picks up "Human Review" issues, reads the review feedback from the PR, addresses it, and moves the issue back to "In Review" — sending it through review again.
Without fix-review-comments, issues that need changes stop at "Human Review" for manual intervention (or critters retry --force).
To enable the loop, add a fix-review-comments type to your config:
critterTypes:
# ... create and review types ...
fix-review-comments:
trigger: { label: "Critter Review", status: "Human Review" }
repo: { clone: true, branch: true }
phases:
- name: fix
prompt: ~/.critters/prompts/fix-review.md
model: opus
maxTurns: 50
tools: default
outcomes:
success: { status: "In Review" }
failure: { status: "Critter Failed" }
concurrency: 2
timeoutMinutes: 20
enrichment: extractPrUrl
claimStatus: "In Progress"The prompt file should instruct Claude to read the PR review feedback (for example with gh pr view --comments plus gh api repos/:owner/:repo/pulls/:number/comments for inline review comments), make the requested fixes, commit, and push. Use builtin:execution prompts as a starting point. claimStatus prevents duplicate dispatch if two poll cycles trigger before the fix completes.
Settings live in critters.config.yaml:
| Field | Default | Description |
|---|---|---|
provider |
"linear" | Default issue tracker: "linear" or "jira" |
pollIntervalSeconds |
120 | How often to poll for issues |
concurrency |
2 | Max parallel critters |
timeoutMinutes |
30 | Total timeout per task (both phases) |
workDir |
/tmp/critters-work | Temp clone directory |
triggerLabel |
"Critter" | Label that triggers pickup |
maxPlanningTurns |
50 | Max turns for planning phase |
maxExecutionTurns |
75 | Max turns for execution phase |
defaultAllowedTools |
see file | Tools critters can use |
repos |
{} | Project ID → repo URL + extra tools |
teamRepos |
{} | Team ID → fallback repo URL |
defaultRepo |
— | Final fallback repo URL when not in description, project config, or team config |
tmuxSession |
"critters" | Name of the tmux session to use |
branchPrefix |
"critter" | Prefix for feature branch names (<prefix>/<ID>-<slug>) |
planningModel |
"opus" | Model for planning phase |
executionModel |
"opus" | Model for execution phase |
healthPort |
3847 | HTTP server port for dashboard and health checks (0 to disable) |
dashboardToken |
— | Shared secret for dashboard POST endpoints (also reads DASHBOARD_TOKEN env var) |
maxLogSizeMb |
10 | Max log file size in MB before rotation (with --no-tmux) |
cleanupIntervalMinutes |
60 | How often periodic work directory cleanup runs (minutes) |
cleanupStaleMinutes |
timeout + 30 | Age threshold (minutes) for considering work directories stale |
minDiskSpaceMb |
1024 | Minimum free disk space (MB) required before cloning a repo |
jiraStatusMap |
{} | Map critter status names to Jira workflow status names |
hooks |
{} | Shell commands run on lifecycle events (see below) |
costAlertThreshold |
— | Cost (USD) per task that triggers a Slack alert |
costBudget |
— | Cost (USD) per task that triggers a kill |
reviewTriggerLabel |
"Critter Review" | Label that triggers review pickup |
reviewModel |
"opus" | Model for reviews |
reviewConcurrency |
2 | Max parallel review critters |
reviewTimeoutMinutes |
15 | Timeout per review |
maxReviewTurns |
30 | Max turns per review |
cli |
"claude" | Default CLI adapter: "claude" or "codex" |
phases[].sandbox |
— | Optional phase-level sandbox override for adapters that support it, e.g. Codex read-only, workspace-write, or danger-full-access |
autoRetry |
— | Auto-retry config: { maxRetries, baseDelaySeconds, maxDelaySeconds } |
circuitBreaker |
— | Circuit breaker config: { failureThreshold, maxBackoffMinutes } |
mcpConfig |
— | Path(s) to MCP config JSON file(s), applied to all critters |
strictMcpConfig |
false | When true, passes --strict-mcp-config to prevent inheriting operator's MCP servers |
metricsRetentionDays |
90 | Days to retain metrics data before pruning |
autoUpdate |
— | Auto-update config: { enabled (default: true), intervalMinutes (default: 1440) } |
tunnel |
— | Tunnel config: { enabled, auth, domain } for ngrok remote access |
linearWebhookSecret |
— | Linear webhook signing secret (env: LINEAR_WEBHOOK_SECRET) |
jiraWebhookSecret |
— | Jira webhook secret (env: JIRA_WEBHOOK_SECRET) |
Per-repo tool overrides merge with the defaults:
repos:
"project-uuid":
url: "git@github.com:org/repo.git"
extraAllowedTools:
- "Bash(python:*)"
- "Bash(pip:*)"You can choose the CLI globally, per critter type, or per phase:
cli: claude
critterTypes:
create:
phases:
- name: planning
prompt: builtin:planning
cli: codex
model: gpt-5.2
maxTurns: 50
tools: readonly
- name: execution
prompt: builtin:execution
cli: claude
model: opus
maxTurns: 75
tools: defaultCritters preserves the existing tools config for both adapters. Claude enforces the allowlist directly; Codex uses sandboxing plus prompt-level restrictions, so enforcement is capability-based rather than identical.
For Codex phases that need outbound GitHub access through gh, set a phase sandbox explicitly. In practice, PR review and review-fixing phases often need danger-full-access, because Codex workspace-write can block the GitHub API calls that gh relies on.
Shell commands that run on lifecycle events. Environment variables available: CRITTER_ISSUE_ID, CRITTER_IDENTIFIER, CRITTER_TITLE, CRITTER_REPO_URL, CRITTER_BRANCH, CRITTER_PR_URL (when applicable).
hooks:
onTaskStarted: "echo 'Task started'"
onPrCreated: "curl -X POST https://example.com/notify"
onMerged: "./scripts/post-merge.sh"
onTaskFailed: ""
onReviewStarted: ""
onNeedsChanges: ""The daemon runs an HTTP server on port 3847 (configurable via healthPort, set to 0 to disable).
| Route | Method | Description |
|---|---|---|
/ or /dashboard |
GET | Live dashboard with task stats, charts, and recent activity. Includes a "New Critter" button for creating tickets |
/healthz |
GET | JSON health check (uptime, version, per-type active/queued counts, active critter details, metrics summary) |
/metrics |
GET | JSON array of recent metric events |
/poll |
POST | Trigger an immediate poll cycle |
/review-poll |
POST | Trigger an immediate review poll cycle |
/restart |
POST | Restart the daemon process |
/stop |
POST | Stop the daemon gracefully |
/api/v1/metadata |
GET | Provider teams, critter types, and repo info |
/api/v1/issues |
POST | Create a critter issue ({ provider, teamId, title, description, critterType }) |
/api/logs/<id> |
GET | Processed log tail for a critter run |
/webhook/linear |
POST | Linear webhook endpoint (requires linearWebhookSecret) |
/webhook/jira |
POST | Jira webhook endpoint (requires jiraWebhookSecret) |
When dashboardToken is configured, POST endpoints require a bearer token. critters status queries the health endpoint to display a quick summary in the terminal.
Webhook endpoints provide near-instant issue pickup instead of waiting for the next poll. Webhooks are additive — polling continues as the fallback. Set LINEAR_WEBHOOK_SECRET and/or JIRA_WEBHOOK_SECRET to enable. The daemon must be reachable from the internet — use the tunnel config for ngrok or a reverse proxy.
Works with both Linear and Jira. For a critter to pick up an issue, it needs:
- Label: "Critter" (exact match, configurable)
- Status: "Todo" (Linear) or the mapped status via
jiraStatusMap(Jira) - Description: must include
repo: git@github.com:org/repo.giton its own line (unless a project or team mapping exists in the config)
Optionally, assign the issue to the relevant project and include implementation guidance in the description -- the critter reads it as its task spec.
Beyond the built-in create and review flows, you can define custom critter types in critters.config.yaml. Each type gets its own trigger label, phase pipeline, model, tools, and outcome statuses.
critterTypes:
code-audit:
trigger: { label: "Code Audit", status: "Todo", statusType: "unstarted" }
repo: { clone: true }
phases:
- name: audit
prompt: ~/.critters/prompts/code-audit.md
model: sonnet
maxTurns: 20
tools: [Read, Glob, Grep, "Bash(git:*)", "Bash(ls:*)"]
outcomes:
success: { status: "Done", comment: true }
failure: { status: "Critter Failed", comment: true }
concurrency: 3
timeoutMinutes: 10Custom types automatically:
- Prompt Claude to write a
.critter-report.mdfile - Upload the report as a
.mdattachment on the issue - Post the report as an inline comment
Prompt files support {{identifier}}, {{title}}, {{description}}, and other variables. See CLAUDE.md for the full reference.
Assignee filtering: Add trigger.assignee to only pick up issues assigned to a specific user. Use an email address (e.g., "alice@company.com") or "me" for the authenticated user. Useful in shared projects where you don't want critters picking up every labeled issue.
Quiet mode: Add quietComments: true to suppress status/progress comments (e.g., "picking up task...", "phase completed in..."). Report content and error comments are still posted.
Claim status: Critter types with trigger.statusType: "unstarted" automatically transition issues to "In Progress" when picked up, preventing duplicate dispatch. Override with claimStatus: "Reviewing" (or any status), or set claimStatus: null to disable. Types without statusType: "unstarted" (e.g., review types) don't claim by default.
Model guidance: Use sonnet or opus for custom types. Haiku often ignores tool-use instructions and produces shallow output.
If critterTypes is omitted from config, the daemon synthesizes the default create and review types from the flat config fields — fully backward compatible.
Critters is a general-purpose agent orchestrator — the built-in create and review types are just the start. Any workflow matching "watch for trigger → run Claude with a prompt → produce an outcome" can be a critter type:
- Issue triage bot — automatically labels, prioritizes, and summarizes new issues
- Documentation writer — reads code and generates or updates markdown docs, opens a PR
- Security auditor — scans for OWASP top 10 vulnerabilities, dependency issues, and hardcoded secrets
- Test generator — reads implementation code and writes missing test cases, opens a PR
- Multi-step workflows — chain critter types using Linear/Jira blocking relationships (e.g., a "plan" critter spawns sub-tickets, then "implement" critters pick up each one)
See CLAUDE.md for complete config examples.
A single daemon can poll both Linear and Jira. Set the default provider at the top level, then use provider on each critter type to override:
provider: linear # default
jiraStatusMap:
"Todo": "To Do"
"In Progress": "In Progress"
"In Review": "In Review"
"Done": "Done"
"Critter Failed": "Failed"
critterTypes:
create:
provider: [linear, jira] # polls both trackers with the same config
trigger: { label: "Critter", status: "Todo", statusType: "unstarted" }
repo: { clone: true, branch: true }
phases:
- name: planning
prompt: builtin:planning
model: opus
maxTurns: 50
tools: readonly
- name: execution
prompt: builtin:execution
model: opus
maxTurns: 75
tools: default
outcomes:
success: { status: "In Review" }
failure: { status: "Critter Failed" }
concurrency: 2
timeoutMinutes: 30provider accepts a single value ("linear", "jira") or an array ([linear, jira]). When an array is used, the type is expanded internally so each provider is polled independently — no need to duplicate config.
Only the env vars for providers you actually use are required. A Linear-only config doesn't need JIRA_* vars.
See CLAUDE.md for full multi-provider docs, Jira differences, and more config examples.
- Self-Hosting Guide — deployment, systemd, remote access, monitoring
- Configuration Reference — all config fields, environment variables, critter types, hooks
See CLAUDE.md for detailed developer docs, architecture diagrams, and contributor conventions.