Colony Configuration Reference
Colony Configuration Reference
Section titled “Colony Configuration Reference”This document is the Colony configuration reference. It covers global infrastructure sections, agent configuration, and the config resolution chain. For multi-repo and multi-tenant configuration (Colony Cloud), see the Multi-Repo (Colony Cloud) section at the bottom.
Which tier do I need?
Section titled “Which tier do I need?”| Scenario | Read | Skip |
|---|---|---|
| First-time single repo | Tier 1 + 2 | Tier 3 |
| Production single repo | Tier 1 + 2 + relevant Tier 3 sections | Multi-tenant |
| Multi-repo / Colony Cloud | All tiers | — |
Config File Format
Section titled “Config File Format”Colony uses YAML configuration files. The config is loaded from the first location found in this order:
- Path passed via the
--configCLI flag ./colony.config.yaml(current working directory)~/.colony/config.yaml(user home directory)
If no config file is found, Colony exits with a configuration error. Once a config file is found and loaded, omitted fields use built-in defaults where available.
Config Schema & Editor Integration
Section titled “Config Schema & Editor Integration”Colony ships a machine-readable JSON Schema for colony.config.yaml at schema/colony.config.schema.json in the repository root. The schema uses the Draft 2020-12 dialect and is identified by:
$id: https://runcolony.com/schema/colony.config.schema.jsonThe $id URL is the schema’s canonical version identifier — there is no separate version field. When Colony releases a new schema version, the $id changes.
Note: The schema is generated with
additionalProperties: trueat the root level, which means unknown top-level keys are not flagged as errors. This keeps the schema forward-compatible with config files written against older Colony versions. Typos in key names will not produce schema validation errors — usecolony check --stage configfor semantic validation.
Editor autocomplete and inline validation
Section titled “Editor autocomplete and inline validation”Most YAML editors (VS Code with the YAML extension, JetBrains IDEs, Neovim with yaml-language-server) honour the yaml-language-server modeline. The canonical header uses the hosted schema URL and works from any config file location (~/.colony/config.yaml, a target repo, or anywhere else):
# yaml-language-server: $schema=https://runcolony.com/schema/colony.config.schema.json
github: owner: your-org repo: your-repo # ...This header is already included as the first line of colony.config.example.yaml and in configs generated by colony init --generate, so if you started from either of those you do not need to add it manually.
In-repo alternative: if your config is committed alongside the Colony repository’s schema/ directory, you can use a relative path instead:
# yaml-language-server: $schema=./schema/colony.config.schema.jsonThis relative form will not resolve for configs at ~/.colony/config.yaml or in a separate target repository — use the URL form for those.
Config validation
Section titled “Config validation”Use colony check --stage config to run semantic validation on your config file. This performs the same checks that Colony runs at startup — credential resolution, required-field presence, and value-range enforcement — and reports actionable errors with field paths.
Required fields vs. Colony-defaulted fields
Section titled “Required fields vs. Colony-defaulted fields”The schema marks some fields as required that Colony’s runtime fills in automatically from built-in defaults when the parent block is omitted entirely. The schema reflects what is structurally required if you provide the parent block, not what you must write in every config file.
Worked example: claude.scaling.*.developer_max_turns
In ClaudeScalingEntry, developer_max_turns is the only required field — model, effort, planning_max_turns, no_progress_window, and backstop_max_turns are all optional:
# OK — claude.scaling is omitted entirely; Colony substitutes DEFAULT_SCALING for all three tiers# (no claude.scaling block needed)
# OK — partial entry; only developer_max_turns is required per tierclaude: scaling: small: developer_max_turns: 100 medium: developer_max_turns: 200 large: developer_max_turns: 300However, ClaudeScalingConfig itself requires all three tier keys (small, medium, large) once you provide the claude.scaling block at all. Providing only small is a schema error:
# ERROR — claude.scaling is present but missing medium and largeclaude: scaling: small: developer_max_turns: 100 # medium and large are required once claude.scaling is setThe general rule: omit a block entirely to get Colony’s defaults for the whole block; provide it to take full ownership of that block’s required fields. See ClaudeScalingEntry for the full field table and built-in defaults per tier.
Regenerating the schema (contributors)
Section titled “Regenerating the schema (contributors)”After changing config types in packages/core/src/config-types.ts, regenerate the schema artifact by running:
npm run generate:schemaThis invokes scripts/generate-schema.mjs and overwrites schema/colony.config.schema.json. Commit the updated schema alongside the type change so the artifact stays in sync with the types.
Environment Variable Resolution
Section titled “Environment Variable Resolution”Call resolveConfig() before any GitHub operations. This function validates the config and resolves credentials from environment variables — reading GITHUB_TOKEN (or the value of token_env) into github.token, reading GitHub App private keys from disk, and resolving per-repo secrets.
Never use the raw parsed config object directly for GitHub operations; always go through resolveConfig().
Tier 1: Essential Configuration
Section titled “Tier 1: Essential Configuration”Fields you must set to get Colony running on a single repository. Copy the snippet below, fill in the placeholders, then move to Tier 2 to tune cost and model settings.
Minimum Viable Config
Section titled “Minimum Viable Config”github: owner: your-org # GitHub org or user that owns the repo repo: your-repo # repository name token_env: GITHUB_TOKEN # default; set GITHUB_TOKEN in your environment
workspace: repo_dir: /path/to/your/repo # absolute path to your local git clone
review: checks: test: npm test # CHANGE to your actual test command lint: npm run lint # CHANGE to your actual lint commandEnvironment Variables
Section titled “Environment Variables”| Variable | Required | Description |
|---|---|---|
GITHUB_TOKEN | required | Personal Access Token. See required scopes. Used by Colony to create branches, open PRs, and post comments |
ANTHROPIC_API_KEY | required | Anthropic API key for Claude Code invocations by worker agents |
DATABASE_URL | required | Postgres connection string (e.g. postgresql://user:pass@localhost:5432/colony) |
OPENAI_API_KEY | required when any agent uses engine: codex | OpenAI API key for Codex invocations. The key name is configurable via codex.api_key_env (default OPENAI_API_KEY). See Engine Selection |
github (essential fields)
Section titled “github (essential fields)”Authentication and identity settings for the target repository.
| Field | Type | Default | Description |
|---|---|---|---|
owner | string | required | GitHub organization or user that owns the target repository |
repo | string | required | Target repository name |
token_env | string | 'GITHUB_TOKEN' | Name of the environment variable containing the Personal Access Token used for GitHub API calls |
For GitHub App auth, bot identity, and ops identity settings, see the full github reference in Tier 3.
workspace (essential fields)
Section titled “workspace (essential fields)”| Field | Type | Default | Description |
|---|---|---|---|
repo_dir | string | '.' | Path to the local git clone of the target repository. Colony creates worktrees under this directory. Not required for Docker Compose deployments — workers clone the repo automatically inside their container |
For all workspace settings (base directory, setup command, branch, cleanup, etc.), see the full workspace reference in Tier 3.
review.checks (required — do not leave empty)
Section titled “review.checks (required — do not leave empty)”Warning: Leaving
review.checksempty means Colony uses LLM judgement alone — no build, test, or lint validation. The LLM reviewer may approve PRs that break your build or fail your test suite. Always configure at least one check.
| Field | Type | Default | Description |
|---|---|---|---|
checks | Record<string, string> | {} | Named shell commands to run as deterministic review checks (e.g. test: 'npm test', lint: 'npm run lint'). Keys are check names; values are shell commands |
Example:
review: checks: test: npm test lint: npm run lint build: npm run buildFor all review settings (CI gating, auto-merge, LLM review rounds, etc.), see the full review reference in Tier 3.
Tier 2: Recommended Configuration
Section titled “Tier 2: Recommended Configuration”Settings most users want to tune after their first issue. The essentials already got Colony running — these fields control cost, quality, and workflow preferences.
logging
Section titled “logging”Controls log output from all Colony processes.
| Field | Type | Default | Description |
|---|---|---|---|
level | string | 'info' | Pino log level. Valid values: 'trace', 'debug', 'info', 'warn', 'error', 'fatal' |
format | 'json' | 'pretty' | 'pretty' | Log output format. Use 'json' in production/containerized deployments for structured log ingestion; use 'pretty' for local development |
max_daily_usd
Section titled “max_daily_usd”A top-level scalar field that caps aggregate spend across all issues and repos for the current UTC day.
Soft cap:
max_daily_usdis an approximate limit, not an exact one. With concurrent workers, each worker checks the DB cost independently before claiming a task — up to(pool_size − 1)in-flight tasks may complete past the cap. Usebudget_headroom_pct(default 10%) to lower the effective ceiling and absorb this overage.
| Field | Type | Default | Description |
|---|---|---|---|
max_daily_usd | number | 50 | Maximum USD to spend in a single UTC day across all repos. When the ceiling is hit, Colony logs a warning and skips repo polling for the rest of the day — issues already running are not interrupted, but no new work is started. Resets at midnight UTC. The default config enables this ceiling; do not set this field to null. |
budget_headroom_pct | number | 10 | Percentage of max_daily_usd reserved as headroom to absorb concurrent-worker overage (0–100). The effective ceiling is max_daily_usd × (1 − budget_headroom_pct / 100). Set to 0 to disable headroom and restore the exact max_daily_usd value as the ceiling. Increase for high pool_size deployments where more tasks may be in flight simultaneously. |
Example — raise the ceiling to $200 with 5% headroom (effective cap $190):
max_daily_usd: 200budget_headroom_pct: 5Example — disable headroom (exact cap, prior behavior):
max_daily_usd: 200budget_headroom_pct: 0Note:
max_daily_usdandbudget_headroom_pctare top-level fields, not nested underclaude:oragents:. When the ceiling is hit you will see a log line likeGlobal daily cost ceiling exceeded — skipping repo until next UTC day. Issues will remain in whatever state they are in and resume processing automatically once daily spend resets at midnight UTC — no label changes or manual intervention are required. Runtime checks are skipped only when the resolved value isundefined; the standard loaded config defaults this field to50.
Cost Controls (claude.max_cost_per_issue)
Section titled “Cost Controls (claude.max_cost_per_issue)”| Field | Type | Default | Description |
|---|---|---|---|
max_cost_per_issue | number | — | USD cost cap per issue. When the accumulated cost for an issue reaches this limit, the Claude invocation is aborted, the issue is moved to colony:blocked, and Colony posts a comment with the cost breakdown. To unblock: raise this value in config, then comment /colony:retry on the GitHub issue to re-trigger processing. Omit for no limit. |
Example:
claude: max_cost_per_issue: 10For all claude settings (timeout, model overrides, Foundry, scaling), see the full claude reference in Tier 3.
Model Selection (claude.models) {#model-selection}
Section titled “Model Selection (claude.models) {#model-selection}”Override which Claude model each agent uses. The defaults balance capability against cost; use this section to tune the trade-off.
| Field | Type | Default | Description |
|---|---|---|---|
developer | string | 'claude-opus-5' | Model used for the developer agent |
reviewer | string | 'claude-opus-5' | Model used for the reviewer agent |
analyzer | string | 'claude-sonnet-5' | Model used for the analyzer agent |
planner | string | 'claude-opus-5' | Model used for the planner agent |
merger | string | 'claude-opus-5' | Model used for the merger agent |
Model Cost Guidance
Section titled “Model Cost Guidance”Colony agents make different demands on the model. The table below shows approximate pricing and recommended use cases to help you balance cost against quality.
| Model | Approx. pricing (input / output per M tokens) | Recommended use |
|---|---|---|
claude-opus-5 | ~$5 / $25 (confirmed; training cutoff May 2026) | Developer, Reviewer, Planner, and Merger — frontier model with lowest fake-validation rate. Reference default for developer, reviewer, planner, and merger. |
claude-opus-4-8 | ~$5 / $25 | Previous Opus generation — same pricing as Opus 5; a valid fallback for users running existing workflows. |
claude-opus-4-7 | ~$5 / $25 | Older Opus generation — same pricing; useful for users already running 4-7 in existing workflows. |
claude-opus-4-6 | ~$5 / $25 | Older Opus generation — still capable for complex tasks. |
claude-sonnet-5 | ~$3 / $15 (placeholder — official rate TBD) | Analyzer — good balance of speed and quality for code triage. Reference default for analyzer. Also preferred over Opus for small dev tasks (better cost-for-quality). |
claude-sonnet-4-6 | ~$3 / $15 | Previous Sonnet generation — still a valid choice, no longer the shipped default. |
claude-haiku-4-5-20251001 | ~$1 / $5 | Not suitable for any agent in the default pipeline — too small for reliable code triage or implementation. May be used for non-pipeline tooling (e.g. credential validation) but should not be set for analyzer, developer, reviewer, planner, or merger. |
claude-fable-5 | ~$10 / $50 | Optional premium model for the Planner and/or Analyzer only — NOT a default; opt in explicitly. Not recommended for Developer or Reviewer due to cost at scale. |
Pricing figures are approximate and may change. Use hedged budget estimates when planning spend.
Why Opus 5 for Reviewer and Developer?
Section titled “Why Opus 5 for Reviewer and Developer?”Opus 5 is Anthropic’s current frontier model and makes significantly fewer fake validations than earlier generations. Older Opus versions would sometimes tweak acceptance criteria or game tests so that everything appeared green while still shipping bugs. This failure mode is particularly costly in two roles:
- Reviewer: rubber-stamping spec gaps, approving PRs that satisfy the letter of the criteria but miss the intent
- Developer: writing code that games the test rather than satisfying the spec
Opus 5 reduces this behaviour, making it the right choice for correctness-critical roles even though it is slower at high thinking effort (high/max).
Effort-mode economics
Section titled “Effort-mode economics”Colony uses thinking effort hints (effort field in claude.scaling) to control how deeply the model reasons per turn. The cost-quality tradeoffs:
max(extended thinking): worth it only for multi-file / cross-cutting work. ~4x token spend; negligible quality gain on single-file tasks. Default forlargecomplexity developer tasks.high: the right default for non-trivial work. Used bymediumdev tasks and by the reviewer and planner.medium: suboptimal — if you want balance plus cheap tokens, use Sonnet onhighinstead of Opus onmedium.low: very fast but poor at non-trivial tasks. Only suitable for mechanical renames or tiny edits.
Override any agent’s model via claude.models.<agent>:
claude: models: developer: claude-opus-5 # highest capability, lowest fake-validation rate reviewer: claude-opus-5 # read-only loop; slowness tolerable; Opus 5 matters here analyzer: claude-sonnet-4-6 # high volume; sonnet-high is the sweet spot planner: claude-opus-5 # lowest volume, highest blast radius merger: claude-sonnet-4-6 # conflict resolution is bounded; sonnet sufficientNote: The annotated example config (
colony.config.example.yaml) sets all agents toclaude-sonnet-4-6as a cost-conscious starting point for evaluation, and also ships with aclaude.scalingblock that pins all three complexity tiers (small/medium/large) to Sonnet. This makes the example internally consistent: it will not trigger thecolony checkmodel-scaling-conflict warning, and all developer spend stays at Sonnet rates regardless of issue complexity. To re-enable auto-escalation to Opus 5 for medium and large issues, delete theclaude.scalingblock. This differs from the reference defaults above (Opus 5 fordeveloper,reviewer,planner, andmerger) — adjust those once you are comfortable with Colony’s output.Note on
opusplanalias: Theopusplanalias in pricing configuration is frozen atclaude-opus-4-6for backward compatibility with existing user configs. Users who want the new default should useclaude-opus-5explicitly.
For all claude settings, see the full claude reference in Tier 3.
Engine Selection (agents.<agent>.engine and codex:) {#engine-selection}
Section titled “Engine Selection (agents.<agent>.engine and codex:) {#engine-selection}”Each executor agent (analyzer, developer, reviewer, merger, planner) can run on either Claude Code (the default) or Codex. Set agents.<agent>.engine to switch the engine for a specific agent. Agents without an explicit engine field continue to use Claude Code — existing behavior is unchanged.
Redeploy required: The
enginefield is not hot-reloadable. Changing an agent’s engine requires a container restart or redeploy (see Config Hot-Reload Matrix).
| Field | Type | Default | Description |
|---|---|---|---|
engine | 'claude-code' | 'codex' | 'claude-code' | LLM engine backend for this agent. Applies to the five executor agents: analyzer, developer, reviewer, merger, planner. Omitted means Claude Code. |
agents: developer: engine: codex # run the developer on Codex; all other agents stay on Claudecodex: block
Section titled “codex: block”When any agent uses engine: codex, add a top-level codex: block to configure the Codex CLI. All fields have built-in defaults — the block is optional for Claude-only deployments.
| Field | Type | Default | Description |
|---|---|---|---|
timeout | number | 600 | Overall Codex CLI invocation timeout in seconds |
max_retries | number | 1 | Number of times to retry a failed Codex invocation |
inactivity_timeout | number | — | Seconds without output before the process is killed. Optional — omit for no inactivity cap |
binary_path | string | codex (from PATH) | Path to the Codex binary. Override when codex is not on PATH |
api_key_env | string | 'OPENAI_API_KEY' | Name of the environment variable containing the OpenAI API key for Codex invocations |
models | object | (see table below) | Per-agent model overrides for Codex engine invocations |
codex.models
Section titled “codex.models”Per-agent model overrides when using the Codex engine. Mirrors claude.models but uses OpenAI/Codex model identifiers. Omit the whole block to use the built-in Codex defaults.
| Field | Type | Default | Description |
|---|---|---|---|
developer | string | 'codex-1' | Model used when the developer agent runs on Codex |
reviewer | string | 'o4-mini' | Model used when the reviewer agent runs on Codex |
analyzer | string | 'o4-mini' | Model used when the analyzer agent runs on Codex |
planner | string | 'o4-mini' | Model used when the planner agent runs on Codex |
merger | string | 'o4-mini' | Model used when the merger agent runs on Codex |
Fallback keys:
mergerfalls back tocodex.models.reviewerwhencodex.models.mergeris unset;plannerfalls back tocodex.models.analyzer. This mirrors the fallback behavior inclaude.models.
Compatibility guard
Section titled “Compatibility guard”Colony validates the (engine, model) pair for each executor agent at config load time (colony check / startup). Incompatible pairs are rejected with a clear error — not a runtime failure.
Invalid claude.models entry (Claude-family required):
claude.models.<key> '<model>' is not a Claude model — claude.models entries must be Claude model ids (e.g. claude-opus-5)Invalid codex.models entry (Codex/GPT-family required):
codex.models.<key> '<model>' is not a Codex/GPT model — codex.models entries must be OpenAI/Codex model ids (e.g. codex-1, o4-mini, gpt-4o)Per-agent engine/model mismatch:
agents.<agent>.engine: <engine> but resolved model '<model>' is not a <Claude|Codex/GPT> model — set <claude|codex>.models.<agent> to a … modelMissing OPENAI_API_KEY: When any agent is configured with engine: codex and the environment variable named by codex.api_key_env (default OPENAI_API_KEY) is absent, Colony emits a warning at config load and colony check — not a fatal error:
agents.<agent>[, ...] use engine: codex but OPENAI_API_KEY is not set — set this environment variable for Codex invocationsWorked example: developer on Codex, all other agents on Claude
Section titled “Worked example: developer on Codex, all other agents on Claude”# Claude model selection for non-Codex agents (unchanged from default)claude: models: analyzer: claude-sonnet-4-6 reviewer: claude-opus-5 planner: claude-opus-5 merger: claude-sonnet-4-6
# Switch only the developer to Codexagents: developer: engine: codex
# Codex settings — only the developer reads these in this configcodex: api_key_env: OPENAI_API_KEY models: developer: codex-1 # built-in default; shown here for clarityWith this config:
- The developer agent uses Codex CLI with
codex-1and readsOPENAI_API_KEYfrom the environment. - All other agents (analyzer, reviewer, planner, merger) use Claude Code with their configured Claude models — unchanged from the default.
colony checkvalidates thatcodex-1is a valid Codex model and warns ifOPENAI_API_KEYis absent.
Cost model selection {#cost-model-selection}
Section titled “Cost model selection {#cost-model-selection}”Four overlapping knobs control what model is used and how much you spend. This section explains how they interact so you can predict spend before you hit a ceiling.
Developer model precedence
Section titled “Developer model precedence”The developer agent picks its model per-issue based on the issue’s assessed complexity:
| Complexity | Effective model |
|---|---|
| small | claude.scaling.small.model → fallback: claude.models.developer |
| medium | claude.scaling.medium.model → fallback: claude.models.developer |
| large | claude.scaling.large.model → fallback: claude.models.developer |
claude.scaling[complexity].model always wins over claude.models.developer for the developer agent. The claude.models.developer value is only used when the matching scaling entry has no model field.
All other agents (analyzer, reviewer, merger, planner) read directly from claude.models.<agent> — they are not affected by claude.scaling.
Built-in scaling defaults (used when claude.scaling is not set):
| Tier | Model | Max turns |
|---|---|---|
| small | claude-sonnet-5 | 80 |
| medium | claude-opus-5 | 150 |
| large | claude-opus-5 | 250 |
Common gotcha: If you set claude.models.developer: claude-sonnet-4-6 to cap developer spend, medium and large issues will still use claude-opus-5 via the built-in scaling defaults. To force all tiers to Sonnet, you must also set claude.scaling.medium.model and claude.scaling.large.model. The shipped colony.config.example.yaml already implements this pattern — if you copied from that file, no additional changes are needed:
claude: models: developer: claude-sonnet-4-6 # only affects tiers without a scaling.model override scaling: small: developer_max_turns: 80 model: claude-sonnet-4-6 medium: developer_max_turns: 150 model: claude-sonnet-4-6 # explicit — otherwise DEFAULT_SCALING.medium.model wins large: developer_max_turns: 250 model: claude-sonnet-4-6 # explicit — otherwise DEFAULT_SCALING.large.model winsRun colony check to detect this conflict — it emits a warning when claude.models.developer is set to a model that differs from the effective scaling models for any tier. Run colony estimate to see which model each complexity tier actually uses in the cost projection.
Model routing (model_routing) {#model-routing}
Section titled “Model routing (model_routing) {#model-routing}”Opt-in feature:
model_routingis disabled by default (enabled: false). Enable it only after reviewing the worked example and confirming the routing table matches your cost/quality goals.
Model routing lets Colony automatically select a model per task based on the task type and the issue’s assessed complexity tier. This is the primary lever for cutting cost on simple issues without sacrificing quality on complex ones.
Block shape
Section titled “Block shape”model_routing: enabled: true # master switch; false (default) → fall through to claude.models.* override: '' # optional global force-model; applies regardless of enabled/routes quota_fallback: true # optional; set false to decline quota-model fallback substitution entirely routes: analyze: small: claude-sonnet-4-6 medium: claude-sonnet-4-6 large: claude-sonnet-4-6 develop: small: claude-sonnet-4-6 medium: claude-opus-4-8 large: claude-opus-4-8 review: small: claude-sonnet-4-6 medium: claude-opus-4-8 large: claude-opus-4-8 merge: small: claude-sonnet-4-6 medium: claude-sonnet-4-6 large: claude-sonnet-4-6 plan: small: claude-opus-4-8 medium: claude-opus-4-8 large: claude-opus-4-8| Field | Type | Default | Description |
|---|---|---|---|
enabled | boolean | false | Master switch. When false, routes are ignored and resolution falls back to claude.models.* |
override | string | — | Global force-model. When set, forces this model for every task regardless of task type, tier, or enabled. Useful as a kill-switch |
routes | object | — | Per-task-type, per-tier model map. Task keys: analyze, develop, review, merge, plan. Tier keys: small, medium, large |
quota_fallback | boolean | true | When false, a model-specific quota 429 (quota_model_specific) never substitutes a replacement model — the task waits for the quota window instead of running on an unrequested model |
Task-type mapping: The sweep task type maps to the analyze route key; review-external maps to review.
Precedence chain
Section titled “Precedence chain”Model resolution follows this order (highest priority first):
override— when set, applies unconditionally regardless ofenabledorroutesroutes[taskType][tier]— whenenabled !== falseand a matching route entry existsclaude.models.<agent>— fallback when routing is absent, disabled, or has no matching route- Foundry
pin_models— backstop whenclaude.provider: foundryis set; maps model family names (opus/sonnet/haiku) to deployment identifiers
Important: override is checked before the enabled flag. Setting enabled: false disables route lookups but does not disable override. If you want to temporarily stop routing without clearing your routes table, set enabled: false and leave override unset.
Quota-model fallback
Section titled “Quota-model fallback”When a model-specific 429 is reported (the quota_model_specific failure class — one Claude model family is rate-limited, not all of them), Colony retries once against a substitute model. That substitute is resolved through the same precedence chain described above, not a fixed lookup table:
- If
model_routing.overrideis set, ormodel_routing.quota_fallback: falseis set, the retry is declined — the task is left to wait for the quota window instead of running on an unrequested model. This guaranteesoverridecan never be silently defeated by a quota fallback. - Otherwise, a one-step substitute family is chosen (sonnet → opus, haiku → sonnet, opus → sonnet), then resolved to a concrete model via
model_routing.routes[taskType][tier](if its family matches the substitute) and, for the developer agent only,claude.scaling[tier].model(if its family matches) — falling back to the bare family name (e.g.'opus') when neither matches.
The substitution is traced as a second model_resolved phase with source: 'quota_fallback', so the task trace records the model that actually ran, not only the model originally resolved. Under claude.provider: foundry or claude.provider: gateway, a bare family-name substitute is still resolved via the pin_models backstop (see below) exactly as the primary dispatch model is.
This precedence-aware resolution applies to worker-dispatched tasks (analyze, develop, review, merge, plan), which is where model_routing is evaluated. A handful of ClaudeCodeCLI invocations outside the worker’s task dispatch — merge conflict resolution’s LLM assist, semantic-conflict assessment, the sprint-master, and the strategize/scan-conventions/colonize CLI commands — do not consult model_routing at all (they run outside a routed task) and always fall back to the bare one-step family ladder (sonnet → opus, haiku → sonnet, opus → sonnet) on a quota_model_specific 429, regardless of model_routing.override or quota_fallback.
Complexity-tier scoring
Section titled “Complexity-tier scoring”Colony scores each task to a complexity tier before resolving its model. The scoring uses signals available at claim time:
Base tier (first match wins):
analyzerComplexityfrompipeline_issues(set by the analyzer) — used directly when presentplannedFileCountfrom task inputs:< 2→small;> 6→large; elsemediumissueBodyLengthin characters:< 500→small; elsemedium- Default:
medium
One-tier escalation — the base tier is escalated by one step (capped at large) when any of the following signals is present:
decompositionStrategyis set and not'single_unit'(i.e. the planner decomposed the issue)reviewCycle ≥ 1(at least one review-cycle has already occurred)- A directive of kind
changes_requested,ci_failure, ormust_decomposeis attached to the task
Interaction with claude.scaling
Section titled “Interaction with claude.scaling”For the developer agent only, claude.scaling[complexity].model (see Developer model precedence) is a separate override that applies after routing resolves the model for the develop route. In practice:
- If
model_routingis disabled, the developer agent usesclaude.scaling[tier].model→claude.models.developer(existing behaviour). - If
model_routingis enabled,routes.develop[tier]is resolved first. When no matching route entry exists, resolution falls back toclaude.scaling[tier].model, then toclaude.models.developer— i.e.routes.develop[tier]→claude.scaling[tier].model→claude.models.developer. Setroutes.develop.*for every tier where you want routing to take precedence overclaude.scaling.
Capability escalation on retry
Section titled “Capability escalation on retry”When an issue is retried via /colony:retry after being blocked for a capability-attributable reason, Colony automatically escalates the complexity tier by one step before enqueueing the next task:
| Block reason | Escalates? |
|---|---|
review_cycle_limit | Yes |
build_failure | Yes |
ci_hard_failure | Yes |
| All others | No |
The escalated tier is stored in task_inputs.model_tier and used by the worker when resolving the model for the retried task. This ensures that cost savings never trap an issue in a failure loop — if the cheaper model cannot solve the problem, the next attempt uses the next tier up.
The worker traces the resolution as model_resolved (with model, tier, and source fields) and, when a tier override is in effect, as model_escalated (with from_tier, to_tier, and reason).
Engine-scoped model fallback
Section titled “Engine-scoped model fallback”override and routes return the literal model string from the configuration table — they are not engine-aware and apply regardless of an agent’s configured engine. If an agent uses engine: codex and a routing entry resolves a model for it, that literal model string is passed to the Codex CLI exactly as written.
Engine-scoped behavior operates at the fallback layer only: when routing is disabled or no route matches a given task type and tier, resolveModelForTaskType falls back to config.<engine>.models.<agent> — codex.models.* for Codex agents and claude.models.* for Claude agents. In a mixed-engine deployment, ensure that any override or route model string is valid for the target agent’s engine.
V1 scope: Per-complexity and dynamic engine routing (choosing the engine per task type or complexity tier) are out of scope for V1. Per-agent
engineis a static configuration setting — all tasks for a given agent type use the same engine.
Worked example {#model-routing-example}
Section titled “Worked example {#model-routing-example}”Route all tasks to a cost-conscious model by default, but use the capable model for large development and planning tasks:
# Force all agents to Sonnet at the claude.models level (the routing fallback)claude: models: developer: claude-sonnet-4-6 reviewer: claude-sonnet-4-6 analyzer: claude-sonnet-4-6 planner: claude-sonnet-4-6 merger: claude-sonnet-4-6
# Enable routing to pin large dev/plan tasks to Opusmodel_routing: enabled: true # no override — allow per-route resolution routes: develop: large: claude-opus-4-8 # complex multi-file work gets the capable model plan: large: claude-opus-4-8 # epic decomposition for large issues uses OpusWith this config:
analyze(all tiers),review(all tiers),merge(all tiers), anddevelop/planforsmall/medium→claude-sonnet-5(fromclaude.models.*fallback)develop.largeandplan.large→claude-opus-4-8(fromroutes)- Any retry after
review_cycle_limit,build_failure, orci_hard_failure→ the scored tier is escalated one step, potentially promoting amediumissue tolargerouting on retry
Cost ceilings
Section titled “Cost ceilings”| Field | Scope | What happens when hit |
|---|---|---|
claude.max_cost_per_issue | Per-issue | Claude invocation is aborted; issue moves to colony:blocked; comment posted with cost breakdown. Resume with /colony:retry after raising the limit. |
max_daily_usd | Global/daily | No new work is started for the rest of the UTC day. Issues already running are not interrupted. Resets at midnight UTC automatically — no manual action needed. |
budget_headroom_pct | Modifier | Lowers the effective daily ceiling to max_daily_usd × (1 − pct/100) to absorb concurrent-worker overage. Default 10%. Set to 0 to use the exact max_daily_usd value. |
The two ceilings are independent and additive. An issue can be blocked by max_cost_per_issue while the daily ceiling has not yet been reached, or vice versa.
Intake Mode (agents.sprint_master.intake_mode)
Section titled “Intake Mode (agents.sprint_master.intake_mode)”Controls whether the sprint master picks up all new issues or only those tagged with colony:enqueue.
| Field | Type | Default | Description |
|---|---|---|---|
intake_mode | 'all' | 'tagged' | 'tagged' | Whether to pick up all new issues or only colony-tagged ones |
Example:
agents: sprint_master: intake_mode: all # pick up every new issue automaticallyFor all sprint master settings, see the full agents.sprint_master reference in Tier 3.
Default Branch (workspace.branch)
Section titled “Default Branch (workspace.branch)”Override when your repository’s default branch is not main.
| Field | Type | Default | Description |
|---|---|---|---|
branch | string | 'main' | Default branch name for the repository. Override when the repo’s default branch is not main (e.g. 'master', 'develop') |
Example:
workspace: branch: masterFor all workspace settings, see the full workspace reference in Tier 3.
Auto-Merge (review.auto_merge_on_approval)
Section titled “Auto-Merge (review.auto_merge_on_approval)”| Field | Type | Default | Description |
|---|---|---|---|
auto_merge_on_approval | boolean | false | Automatically merge the PR after the reviewer approves it |
Example:
review: auto_merge_on_approval: trueFor all review settings, see the full review reference in Tier 3.
Monitor Dashboard (agents.monitor.enabled)
Section titled “Monitor Dashboard (agents.monitor.enabled)”The monitor agent is disabled by default. Enable it to access the pipeline dashboard and alerting.
| Field | Type | Default | Description |
|---|---|---|---|
enabled | boolean | false | Enable the monitor agent. Exposes a dashboard and Prometheus metrics endpoint |
Example:
agents: monitor: enabled: trueFor all monitor settings (alerting, self-healing, cost thresholds, etc.), see the full agents.monitor reference in Tier 3.
Attribution (EU AI Act transparency)
Section titled “Attribution (EU AI Act transparency)”Colony appends a short disclosure footer to every agent-authored GitHub artifact — issue comments, PR descriptions, review bodies, and created issue bodies. This satisfies the EU AI Act Article 50(1) obligation, which requires AI systems that interact directly with natural persons to disclose that interaction. The deadline is 2 August 2026 and was not deferred by the May 2026 Digital Omnibus agreement.
attribution: enabled: true # default true text: 'Orchestrated by [Colony](https://runcolony.com) — autonomous software development pipeline.'| Field | Type | Default | Description |
|---|---|---|---|
enabled | boolean | true | When false, no disclosure footer is appended. Disabling this weakens the operator’s own Article 50(1) compliance posture — only disable after legal review. |
text | string | See above | The disclosure text appended after a horizontal rule (---). Self-hosted operators may substitute their own deployment name. |
The footer format is always:
---<text>Comment Policy
Section titled “Comment Policy”Controls whether operator-internal comments are suppressed on customer-facing issue threads. When customer_facing is true, comments tagged with audience: 'operator' are redirected to operator logs instead of being posted to the public issue thread. Untagged comments and audience: 'customer' / audience: 'both' comments always dispatch regardless of this setting.
comment_policy: customer_facing: false # default false — no suppression| Field | Type | Default | Description |
|---|---|---|---|
customer_facing | boolean | false | When true, audience: 'operator' comments are suppressed from issue threads (redirected to logs) |
Config Hot-Reload Matrix
Section titled “Config Hot-Reload Matrix”Colony’s config-watcher polls your colony.config.yaml every 2 seconds and applies changes live for the blocks below — no container restart needed. Blocks listed as Needs redeploy require a docker compose restart (or equivalent) to take effect.
| Block | Status | Singleton(s) consumed by |
|---|---|---|
claude | Reloads live | worker, sprint-master, monitor |
codex | Reloads live — Codex engine settings; per-agent engine field is NOT reloadable (requires redeploy) | worker |
review | Reloads live | worker |
max_daily_usd | Reloads live | worker, sprint-master |
budget_headroom_pct | Reloads live | worker, sprint-master |
agents | Partial — selected workers, sprint_master, and monitor sub-keys reload live; agent identity/startup settings need redeploy | worker, sprint-master, monitor |
llm | Reloads live (deprecated — prefer claude.scaling) | worker |
attribution | Reloads live | worker, sprint-master |
comment_policy | Reloads live (worker, sprint-master); monitor requires restart (caches write-services) | worker, sprint-master |
intelligence | Reloads live | worker |
model_routing | Reloads live | worker |
intake_rules | Reloads live | sprint-master |
default_workflow | Reloads live | sprint-master |
strategy | Reloads live — cadence sub-key only; timer updates take effect on next restart | sprint-master |
merge | Reloads live | worker |
max_cost_per_issue | Reloads live (top-level alias for claude.max_cost_per_issue) | worker |
logging | Partial — level reloads live; format needs redeploy | worker, sprint-master, monitor |
workspace | Partial — safe sub-keys reload live; identity/path sub-keys need redeploy | worker |
repos | Partial — per-repo review.* and safe workspace.* sub-keys reload live; adding/removing repos or changing identity fields needs redeploy | worker, sprint-master |
github | Needs redeploy | worker, sprint-master, monitor, webhook-receiver |
ado | Needs redeploy | worker, sprint-master |
database | Needs redeploy | worker, sprint-master, monitor |
tenants | Needs redeploy | worker, sprint-master |
labels | Needs redeploy | worker, sprint-master |
commands | Needs redeploy | sprint-master, webhook-receiver |
webhook | Needs redeploy | webhook-receiver |
deployment | Needs redeploy | all |
event_log | Needs redeploy | worker, sprint-master, monitor |
plugins | Needs redeploy | worker |
self_improvement | Needs redeploy | sprint-master |
executors | Needs redeploy | worker |
calibration | Needs redeploy | sprint-master |
epic | Needs redeploy | worker |
Authoritative source: The classifications above reflect
RELOADABLE_KEYS,PARTIAL_RELOADABLE_KEYS,REPO_REVIEW_RELOADABLE_KEYS, andREPO_WORKSPACE_RELOADABLE_KEYSinpackages/core/src/config-watcher.ts. Keep this matrix in sync when adding new reloadable keys.
logging sub-key detail
Section titled “logging sub-key detail”| Sub-key | Status |
|---|---|
level | Reloads live |
format | Needs redeploy |
workspace sub-key detail
Section titled “workspace sub-key detail”| Sub-key | Status |
|---|---|
setup_command | Reloads live |
setup_timeout | Reloads live |
prebuild_command | Reloads live |
skip_pre_push_hook | Reloads live |
repo_dir | Needs redeploy |
base_dir | Needs redeploy |
cleanup_after_merge | Needs redeploy |
branch | Needs redeploy |
review_workspace_base | Needs redeploy |
prune_blocked_after_days | Needs redeploy |
agents sub-key detail
Section titled “agents sub-key detail”Only the following agent sub-keys reload live. Other agents.* settings require restart or redeploy.
| Sub-key path | Status |
|---|---|
workers.* | Reloads live |
sprint_master.poll_interval | Reloads live |
sprint_master.code_map_scan_interval_hours | Reloads live |
sprint_master.heartbeat_timeout_minutes | Reloads live |
sprint_master.sweep_cooldown_minutes | Reloads live |
sprint_master.label_sync_limit | Reloads live |
sprint_master.projection_drain_limit | Reloads live |
sprint_master.auto_unblock_transient | Reloads live |
sprint_master.max_auto_unblocks_per_cycle | Reloads live |
sprint_master.auto_unblock_cooldown_minutes | Reloads live |
sprint_master.max_auto_unblocks_per_issue | Reloads live |
sprint_master.queue_starvation_threshold_hours | Reloads live |
sprint_master.orphan_recovery_cooldown_minutes | Reloads live |
sprint_master.work_task_retention_days | Reloads live |
sprint_master.utilization_rollup_enabled | Reloads live |
sprint_master.auto_repair_stale_blocked | Reloads live |
sprint_master.auto_repair_stale_subtask_edges | Reloads live |
monitor.poll_interval | Reloads live |
monitor.cost_alert_threshold | Reloads live |
monitor.self_healing | Reloads live |
monitor.alert_channels | Reloads live |
monitor.regression_guard | Reloads live |
monitor.agent_down_timeout | Reloads live |
monitor.pipeline_stall_timeout_minutes | Reloads live |
monitor.error_rate_threshold | Reloads live |
monitor.error_rate_window | Reloads live |
monitor.max_task_duration | Reloads live |
monitor.alert_cooldown | Reloads live |
monitor.metrics_refresh_minutes | Reloads live |
monitor.long_lived_state_ceiling_hours | Reloads live |
monitor.daily_digest | Reloads live |
Agent enabled, health_port, auth, identity, and non-listed agent fields | Needs redeploy |
repos[] sub-key detail
Section titled “repos[] sub-key detail”Each entry in repos[] is identified by its owner/repo slug. Adding or removing a repo entry always requires a redeploy. For existing repos:
| Sub-key path | Status |
|---|---|
review.checks | Reloads live |
review.timeout_per_check | Reloads live |
review.rebase_before_check | Reloads live |
review.require_ci_pass | Reloads live |
review.ci_check_timeout | Reloads live |
review.ci_required_checks | Reloads live |
review.format_command | Reloads live |
review.clean_verification | Reloads live |
review.auto_merge_on_approval | Reloads live |
review.external_prs | Reloads live |
review.ci_repair | Reloads live |
workspace.setup_command | Reloads live |
workspace.setup_timeout | Reloads live |
workspace.prebuild_command | Reloads live |
workspace.skip_pre_push_hook | Reloads live |
owner / repo / token_env / app / ops_app / ops_token_env | Needs redeploy |
workspace.repo_dir / workspace.base_dir / workspace.branch / workspace.review_workspace_base | Needs redeploy |
workers / dependabot / sla / self_improvement / intake_mode / pattern_memory / code_map | Needs redeploy |
Tier 3: Advanced & Multi-Repo Configuration
Section titled “Tier 3: Advanced & Multi-Repo Configuration”Complete reference for all configuration fields. New users can skip this section until they encounter a scenario that requires it.
tenant_id
Section titled “tenant_id”Top-level tenant identifier used when Colony synthesizes the default tenant in single-repo mode.
| Field | Type | Default | Description |
|---|---|---|---|
tenant_id | string | 'default' | Identifier assigned to the implicit tenant when tenants[] is not configured. Useful for event, cost, and DB records. |
deployment
Section titled “deployment”Controls deployment-mode-specific path handling. This section is optional.
| Field | Type | Default | Description |
|---|---|---|---|
mode | 'docker-compose' | 'native' | 'apple-container' | (inferred) | Deployment mode. If omitted, Colony infers docker-compose inside containers or when docker-compose.yml exists, otherwise native. apple-container must be set explicitly. |
Azure DevOps single-repo configuration. When top-level repos[] is omitted and ado is present, Colony synthesizes an ADO repo entry from this block and the top-level workspace settings.
| Field | Type | Default | Description |
|---|---|---|---|
organization | string | required | Azure DevOps organization name |
project | string | required | Azure DevOps project name |
repo | string | required | Azure DevOps repository name |
token_env | string | required | Environment variable containing the ADO Personal Access Token for the coder identity |
ops_token_env | string | — | Environment variable containing a separate ADO PAT for ops actions |
closed_state | string | 'Closed' | Work item state that represents a closed/done issue |
active_state | string | 'Active' | Work item state that represents an active issue |
work_item_type_map | ADOWorkItemTypeMap | — | Work item type names to use for Colony-created items |
webhook_username | string | — | Username expected by the ADO webhook receiver when basic auth is configured |
webhook_password_env | string | — | Environment variable containing the ADO webhook basic-auth password |
ADOWorkItemTypeMap
Section titled “ADOWorkItemTypeMap”Used by ado.work_item_type_map.
| Field | Type | Default | Description |
|---|---|---|---|
default | string | 'User Story' | Work item type for general Colony-created items |
self_improvement | string | — | Work item type for self-improvement items |
epic | string | — | Work item type for epic parent items |
subtask | string | — | Work item type for decomposed subtask items |
bug | string | — | Work item type for bug or defect items |
github
Section titled “github”Authentication and identity settings for the target repository.
| Field | Type | Default | Description |
|---|---|---|---|
owner | string | required | GitHub organization or user that owns the target repository |
repo | string | required | Target repository name |
token_env | string | 'GITHUB_TOKEN' | Name of the environment variable containing the Personal Access Token used for GitHub API calls |
app | GitHubAppConfig | — | GitHub App credentials for the coder identity (Analyzer, Developer). Use instead of token_env for App-based auth |
ops_app | GitHubAppConfig | — | Separate GitHub App credentials for the ops identity (Sprint Master, Reviewer, Merger). Allows two distinct bot identities |
ops_token_env | string | — | Name of the environment variable containing the PAT for the ops identity. Alternative to ops_app |
bot_username | string | — | Display name shown for bot-authored comments and labels |
GitHubAppConfig
Section titled “GitHubAppConfig”Used by github.app and github.ops_app.
| Field | Type | Default | Description |
|---|---|---|---|
app_id | number | required | GitHub App ID (found in the App settings page) |
private_key_path | string | required | Path to the .pem private key file for this App |
installation_id | number | required | Installation ID for this App on the target organization or repo |
labels
Section titled “labels”Controls the prefix used for all pipeline state labels created by Colony on GitHub issues.
| Field | Type | Default | Description |
|---|---|---|---|
prefix | string | 'colony' | Prefix for pipeline state labels (e.g. colony:analyzing, colony:ready-for-dev). Change this if you need to run multiple Colony instances against the same repo with distinct label namespaces |
workspace
Section titled “workspace”Controls how Colony creates and manages git worktrees for each issue.
| Field | Type | Default | Description |
|---|---|---|---|
repo_dir | string | '.' | Path to the local git clone of the target repository. Colony creates worktrees under this directory. Not required for Docker Compose deployments — workers clone the repo automatically inside their container |
base_dir | string | '~/.colony/workspaces/{owner}/{repo}' | Base directory for worktrees. Supports {owner} and {repo} template tokens which are substituted at runtime |
cleanup_after_merge | boolean | true | Remove the worktree after a PR is merged. Set to false to retain worktrees for post-merge inspection |
setup_command | string | — | Command to run after creating a worktree, instead of the default npm install. Use for non-TypeScript repos (e.g. 'bundle install' for Ruby/Rails). Commands run in a non-login shell and inherit the worker’s PATH (including mise shims); if you need a multi-step command, prefer bash -c '...' over bash -lc '...' to avoid login-shell PATH reset on Debian |
setup_timeout | number | 300 | Seconds to allow the setup command to run before timing out |
prebuild_command | string | — | Command to run after setup_command to pre-build workspace packages (e.g. 'npm run build'). Use for monorepos with internal packages that must be built before tests can run |
prune_blocked_after_days | number | 7 | Days before worktrees for blocked issues are automatically pruned |
review_workspace_base | string | — | Container-local path for ephemeral reviewer worktrees. Required when the reviewer runs in an isolated container with a different filesystem layout |
skip_pre_push_hook | boolean | — | When true, passes --no-verify to git push. Colony runs its own validation steps, so this is safe in containerized deployments where the local pre-push hook is not meaningful |
branch | string | 'main' | Default branch name for the repository. Override when the repo’s default branch is not main (e.g. 'master', 'develop') |
database
Section titled “database”Connection settings for the Postgres database used by the pipeline store. This section is optional for configuration — if omitted, Colony reads DATABASE_URL from the environment using built-in defaults. However, a Postgres connection is always required at runtime — colony check will always validate database connectivity regardless of whether this section is present.
| Field | Type | Default | Description |
|---|---|---|---|
url_env | string | 'DATABASE_URL' | Name of the environment variable containing the Postgres connection string |
listen_url_env | string | url_env | Environment variable for a direct Postgres URL used by LISTEN/NOTIFY. Set this separately when url_env points at PgBouncer transaction pooling |
max_connections | number | 10 | Maximum number of connections in the pg connection pool. Increase for high-throughput deployments with many concurrent workers |
idle_timeout | number | 10000 | idleTimeoutMillis for the pg connection pool (milliseconds). Lower this for connection-constrained environments |
connection_timeout | number | 10000 | connectionTimeoutMillis for establishing a pg client connection |
statement_timeout | number | 30000 | Server-side Postgres statement timeout in milliseconds |
query_timeout | number | 30000 | Client-side node-postgres query timeout in milliseconds |
keep_alive | boolean | true | Enable TCP keepalive for Postgres sockets |
ssl | boolean | false | Enable SSL for Postgres connections. Required for most cloud-hosted Postgres instances (e.g. RDS, Cloud SQL) |
Database migrations: Colony runs Postgres migrations automatically at agent startup — both the sprint-master and worker call
PipelineStore.initialize()which applies all pending migrations before the agent begins processing. No manual migration step is needed. If startup fails with a migration error, verifyDATABASE_URLconnectivity and that the Postgres user hasCREATE TABLEprivileges.
event_log
Section titled “event_log”Controls the local event log written by Colony agents. This section is optional.
| Field | Type | Default | Description |
|---|---|---|---|
enabled | boolean | true | Enable or disable event logging for agents that use the event logger |
dir | string | '~/.colony/events' | Directory where event log files are written |
retention_days | number | 30 | Number of days to retain local event log files before pruning |
agent_message_retention_days | number | 14 | Number of days to retain agent_messages rows before monitor retention sweeps prune partitions |
webhook
Section titled “webhook”Configures the webhook receiver that listens for incoming GitHub webhook events. This section is optional — omit it if you are not running the webhook receiver.
| Field | Type | Default | Description |
|---|---|---|---|
enabled | boolean | required | Enable the webhook receiver |
port | number (1–65535) | required | Port for the webhook receiver HTTP server to listen on |
secret_env | string | — | Name of the environment variable containing the HMAC secret used to verify webhook payloads from GitHub |
claude
Section titled “claude”Controls Claude CLI invocation behaviour and model selection for all agents.
| Field | Type | Default | Description |
|---|---|---|---|
timeout | number | 1800 | Overall Claude CLI invocation timeout in seconds |
max_retries | number | 1 | Number of times to retry a failed Claude invocation |
inactivity_timeout | number | 300 | Seconds without stdout/stderr output before the process is killed with SIGKILL |
max_cost_per_issue | number | — | USD cost cap per issue. When the accumulated cost for an issue reaches this limit, the Claude invocation is aborted, the issue is moved to colony:blocked, and Colony posts a comment with the cost breakdown. To unblock: raise this value in config, then comment /colony:retry on the GitHub issue to re-trigger processing. Omit for no limit. |
binary_path | string | 'claude' | Path to the Claude CLI binary. Override when the binary is not on PATH or you need a specific version |
provider | 'anthropic' | 'foundry' | 'gateway' | 'anthropic' | API provider for Claude Code CLI. Use 'foundry' to route through Azure Foundry, or 'gateway' for any Anthropic-compatible AI gateway (e.g. TrueFoundry) |
foundry | ClaudeFoundryConfig | — | Azure Foundry configuration. Only used when provider: 'foundry' |
gateway | ClaudeGatewayConfig | — | Anthropic-compatible gateway configuration. Only used when provider: 'gateway' |
auth_mode | 'api-key' | 'oauth-token' | 'api-key' | Claude Code authentication mode. api-key uses Anthropic/Foundry API keys; oauth-token uses a Claude subscription OAuth token |
api_key_env | string | 'ANTHROPIC_API_KEY' | Informational name for the Anthropic API key environment variable. Current Anthropic API-key runtime checks use ANTHROPIC_API_KEY |
oauth_token_env | string | 'CLAUDE_CODE_OAUTH_TOKEN' | Environment variable containing the OAuth token when auth_mode: oauth-token |
oauth_expires_at | string | — | ISO timestamp used for pre-invocation OAuth expiry checks. Refreshing this value can resume workers paused for token expiry |
models | ClaudeModelsConfig | — | Per-agent model overrides |
scaling | ClaudeScalingConfig | — | Per-complexity turn limits and model overrides. When omitted, built-in defaults are used (see below) |
Claude Auth
Section titled “Claude Auth”When auth_mode is omitted or set to api-key, Colony invokes Claude Code with API-key authentication. For the default Anthropic provider, ANTHROPIC_API_KEY must be present in the process environment; for provider: foundry, set claude.foundry.api_key_env (default ANTHROPIC_FOUNDRY_API_KEY).
When auth_mode: oauth-token, Colony injects only the OAuth token environment variable named by oauth_token_env (default CLAUDE_CODE_OAUTH_TOKEN) and intentionally does not inject ANTHROPIC_API_KEY. oauth_expires_at is optional metadata used by workers and sprint-master to detect expired tokens before invoking Claude Code.
ClaudeFoundryConfig
Section titled “ClaudeFoundryConfig”Used by claude.foundry. Only applies when claude.provider is 'foundry'.
| Field | Type | Default | Description |
|---|---|---|---|
resource | string | — | Azure resource name. URL constructed as https://{resource}.services.ai.azure.com/anthropic. Mutually exclusive with base_url |
base_url | string | — | Full Foundry endpoint root. Colony appends /v1/messages when probing credentials (a trailing /v1 on the base URL is normalised away). Custom ports, query strings, and http:// schemes are honoured. Mutually exclusive with resource |
api_key_env | string | 'ANTHROPIC_FOUNDRY_API_KEY' | Environment variable holding the Foundry API key |
pin_models.opus | string | — | Pin the Opus deployment name (sets ANTHROPIC_DEFAULT_OPUS_MODEL) |
pin_models.sonnet | string | — | Pin the Sonnet deployment name (sets ANTHROPIC_DEFAULT_SONNET_MODEL) |
pin_models.haiku | string | — | Pin the Haiku deployment name (sets ANTHROPIC_DEFAULT_HAIKU_MODEL) |
Example:
claude: provider: foundry foundry: resource: my-azure-resource api_key_env: ANTHROPIC_FOUNDRY_API_KEY pin_models: opus: claude-opus-4-6 sonnet: claude-sonnet-4-6 haiku: claude-haiku-4-5Authentication: set the environment variable named by claude.foundry.api_key_env (default ANTHROPIC_FOUNDRY_API_KEY). See the Claude Code Foundry docs for provider-specific details.
ClaudeGatewayConfig
Section titled “ClaudeGatewayConfig”Used by claude.gateway. Only applies when claude.provider is 'gateway'.
| Field | Type | Default | Description |
|---|---|---|---|
base_url | string | required | Gateway base URL (sets ANTHROPIC_BASE_URL) |
auth_token_env | string | 'ANTHROPIC_AUTH_TOKEN' | Environment variable holding the gateway auth token (sets ANTHROPIC_AUTH_TOKEN) |
custom_headers | string | — | Custom headers forwarded to the gateway (sets ANTHROPIC_CUSTOM_HEADERS) |
disable_experimental_betas | boolean | — | When true, sets CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS=1 |
pin_models.opus | string | — | Pin the Opus model identifier (sets ANTHROPIC_DEFAULT_OPUS_MODEL) |
pin_models.sonnet | string | — | Pin the Sonnet model identifier (sets ANTHROPIC_DEFAULT_SONNET_MODEL) |
pin_models.haiku | string | — | Pin the Haiku model identifier (sets ANTHROPIC_DEFAULT_HAIKU_MODEL) |
Example (TrueFoundry):
claude: provider: gateway gateway: base_url: https://llm-gateway.truefoundry.com/api/llm auth_token_env: TRUEFOUNDRY_API_KEY custom_headers: 'x-tfy-anthropic-beta: context-management-2025-06-27' disable_experimental_betas: true pin_models: opus: claude-code/claude-opus sonnet: claude-code/claude-sonnet haiku: claude-code/claude-haikuAuthentication: set the environment variable named by claude.gateway.auth_token_env (default ANTHROPIC_AUTH_TOKEN) to the gateway’s API key or bearer token. Gateway traffic does not use ANTHROPIC_API_KEY — the gateway branch in Colony’s provider env builder intentionally omits it. That token is read from the process environment, so it must also reach the container — see Gateway / LLM Proxy below.
Model naming rules for cost attribution
Section titled “Model naming rules for cost attribution”Colony and Claude Code both derive per-invocation cost by string-matching the model id against a known Anthropic pricing table. When traffic is routed through a gateway, model ids are typically namespaced (e.g. claude-code/claude-sonnet-4-6). Colony can still attribute cost in this case, but the namespaced id must contain a recognisable Anthropic tier token — one of opus, sonnet, fable, or haiku (case-insensitive).
Examples:
| Model id in config | Resolved pricing tier | Cost attribution |
|---|---|---|
acme/claude-sonnet-4-6 | Sonnet ($3/$15 per M) | ✓ |
anthropic-main/claude-opus-4-8 | Opus ($5/$25 per M) | ✓ |
claude-code/claude-haiku-4-5 | Haiku ($1/$5 per M) | ✓ |
gateway/mystery-model-v99 | — | ✗ (unknown tier) |
Fallback when identification fails. If Colony cannot identify the tier, it operates in a “no price caps” mode:
claude.max_cost_per_issueis inoperative for that model — agents are not stopped on cost grounds.- Failed runs with zero reported cost are not classified as quota-exhausted, avoiding false retries.
- Colony warns at worker startup and in
colony checkwhich model ids cannot be priced.
These warnings are best-effort — they confirm Colony’s tier matching works, but they cannot guarantee Claude Code itself resolves the same id to a price on the gateway. Verify against the gateway’s documentation if you need both Colony and Claude Code cost tracking to be accurate.
Gateway / LLM Proxy
Section titled “Gateway / LLM Proxy”Colony also supports routing Claude Code traffic through an LLM gateway or proxy (e.g. TrueFoundry, LiteLLM, a self-hosted reverse proxy) by setting standard Claude Code environment variables in the process environment of the sprint-master and worker containers. Use this when you want gateway routing without a claude.gateway config block, or alongside it to supply the gateway auth token.
| Variable | Purpose |
|---|---|
ANTHROPIC_BASE_URL | Override the Anthropic API endpoint root. Claude Code appends /v1/messages for requests. |
ANTHROPIC_AUTH_TOKEN | Bearer token forwarded to the gateway endpoint. |
ANTHROPIC_CUSTOM_HEADERS | Additional HTTP headers sent with every Claude Code request, one Name: Value pair per line (use \n between pairs for multiple headers). For gateway routing or tenant headers. Credentials belong in ANTHROPIC_AUTH_TOKEN or ANTHROPIC_API_KEY, not here. |
CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS | Set to true to disable Claude Code experimental beta features. Useful for gateways that do not support beta API surfaces. |
These variables are passed through the Compose environment: blocks for the sprint-master and worker services. Export them in your shell before running docker compose up, or add them (uncommented) to your .env file.
Custom-named gateway token variables. If your gateway requires a custom-named API key variable (e.g. TRUEFOUNDRY_API_KEY) rather than ANTHROPIC_AUTH_TOKEN, the fixed Compose allowlist cannot cover it automatically. You must manually add that variable name to the environment: block of each affected service (sprint-master, worker) in docker-compose.yml and docker-compose.canary.yml:
worker: environment: # ... existing vars ... - TRUEFOUNDRY_API_KEYThen export the variable in your shell or add it to .env.
Gateway Troubleshooting
Section titled “Gateway Troubleshooting”Two failure modes account for almost all gateway routing problems. The error messages differ enough to tell them apart immediately.
Namespaced-model rejection
Section titled “Namespaced-model rejection”Symptoms: Claude Code exits at startup with a message like Unknown model or Model not available. Colony blocks the issue and posts a comment noting a model-compatibility error.
Root cause: The model id set in pin_models (or claude.models.*) does not contain a recognisable Anthropic tier token. Claude Code string-matches the model id to decide which API surface to use; a purely opaque id like gateway/mystery-model-v99 fails this check.
Fix: Update pin_models so each value contains opus, sonnet, haiku, or fable somewhere in the id — for example claude-code/claude-sonnet-4-6 or acme/claude-opus-4-8. The prefix is arbitrary; the tier token is what matters.
claude: provider: gateway gateway: pin_models: opus: claude-code/claude-opus-4-8 # ✓ contains "opus" sonnet: claude-code/claude-sonnet-4-6 # ✓ contains "sonnet" haiku: claude-code/claude-haiku-4-5 # ✓ contains "haiku"Run colony check after updating — it validates all model ids against the same tier-matching logic Colony uses at runtime.
Auth / token rejection
Section titled “Auth / token rejection”Symptoms: Claude Code stderr contains an HTTP 401 or invalid_api_key / Authentication failed error. Colony blocks the issue with an auth-failure note.
Root cause: One of two things:
- Wrong
auth_token_envname. The value ofclaude.gateway.auth_token_env(defaultANTHROPIC_AUTH_TOKEN) doesn’t match the environment variable you actually exported. For example, if your gateway usesTRUEFOUNDRY_API_KEYbutauth_token_envis still at its default, the token is never injected. - Token not reaching the container. The variable is exported in your shell but is not in the Compose
environment:allowlist, so the worker container starts without it.
Fix for wrong name: Set claude.gateway.auth_token_env to the variable name you export:
claude: provider: gateway gateway: auth_token_env: TRUEFOUNDRY_API_KEYFix for missing Compose allowlist entry: Add the variable to the environment: block of sprint-master and worker in docker-compose.yml (and docker-compose.canary.yml if used):
worker: environment: - TRUEFOUNDRY_API_KEYThen re-export the variable in your shell (or add it to .env) and restart the containers.
Telling the two failures apart
Section titled “Telling the two failures apart”| Signal | Model rejection | Auth rejection |
|---|---|---|
| Error origin | Claude Code startup, before any API call | First API request to the gateway |
| HTTP status | None (local check) | 401 Unauthorized |
| Colony block reason | model-compatibility error | auth-failure |
| Fix target | pin_models / claude.models.* | auth_token_env + Compose allowlist |
ClaudeModelsConfig
Section titled “ClaudeModelsConfig”Used by claude.models.
| Field | Type | Default | Description |
|---|---|---|---|
developer | string | 'claude-opus-5' | Model used for the developer agent |
reviewer | string | 'claude-opus-5' | Model used for the reviewer agent |
analyzer | string | 'claude-sonnet-5' | Model used for the analyzer agent |
planner | string | 'claude-opus-5' | Model used for the planner agent |
merger | string | 'claude-opus-5' | Model used for the merger agent |
Model Cost Guidance
Section titled “Model Cost Guidance”Colony agents make different demands on the model. The table below shows approximate pricing and recommended use cases to help you balance cost against quality.
| Model | Approx. pricing (input / output per M tokens) | Recommended use |
|---|---|---|
claude-opus-5 | ~$5 / $25 (confirmed; training cutoff May 2026) | Developer, Reviewer, Planner, and Merger — frontier model with lowest fake-validation rate. Reference default for developer, reviewer, planner, and merger. |
claude-opus-4-8 | ~$5 / $25 | Previous Opus generation — same pricing as Opus 5; a valid fallback for users running existing workflows. |
claude-opus-4-7 | ~$5 / $25 | Older Opus generation — same pricing; useful for users already running 4-7 in existing workflows. |
claude-opus-4-6 | ~$5 / $25 | Older Opus generation — still capable for complex tasks. |
claude-sonnet-5 | ~$3 / $15 (placeholder — official rate TBD) | Analyzer — good balance of speed and quality for code triage. Reference default for analyzer. Also preferred over Opus for small dev tasks (better cost-for-quality). |
claude-sonnet-4-6 | ~$3 / $15 | Previous Sonnet generation — still a valid choice, no longer the shipped default. |
claude-haiku-4-5-20251001 | ~$1 / $5 | Not suitable for any agent in the default pipeline — too small for reliable code triage or implementation. May be used for non-pipeline tooling (e.g. credential validation) but should not be set for analyzer, developer, reviewer, planner, or merger. |
claude-fable-5 | ~$10 / $50 | Optional premium model for the Planner and/or Analyzer only — NOT a default; opt in explicitly. Not recommended for Developer or Reviewer due to cost at scale. |
Pricing figures are approximate and may change. Use hedged budget estimates when planning spend.
Override any agent’s model via claude.models.<agent>:
claude: models: developer: claude-opus-5 # highest capability, lowest fake-validation rate reviewer: claude-opus-5 # read-only loop; slowness tolerable; Opus 5 matters here analyzer: claude-sonnet-4-6 # high volume; sonnet-high is the sweet spot planner: claude-opus-5 # lowest volume, highest blast radius merger: claude-sonnet-4-6 # conflict resolution is bounded; sonnet sufficientNote: The annotated example config (
colony.config.example.yaml) sets all agents toclaude-sonnet-4-6as a cost-conscious starting point for evaluation. This differs from the reference defaults above (Opus 5 fordeveloper,reviewer,planner, andmerger). If you start from the example config, you will get cheaper but potentially less capable behaviour for complex issues — adjustdeveloper,reviewer,planner, andmergerto Opus 5 once you are comfortable with Colony’s output.Note on
opusplanalias: Theopusplanalias is frozen atclaude-opus-4-6for backward compatibility. Users who want the new default should useclaude-opus-5explicitly.
ClaudeScalingConfig
Section titled “ClaudeScalingConfig”Used by claude.scaling. The config is a map with keys small, medium, and large (matching issue complexity levels). Each entry is a ClaudeScalingEntry. For reliable runtime behavior, define all three tiers when setting claude.scaling; hot reload can merge partial tier updates into an already initialized scaling map.
Example:
claude: scaling: small: developer_max_turns: 80 medium: developer_max_turns: 150 large: developer_max_turns: 250 no_progress_window: 75ClaudeScalingEntry
Section titled “ClaudeScalingEntry”| Field | Type | Default (built-in) | Description |
|---|---|---|---|
developer_max_turns | number | 80 / 150 / 250 (small / medium / large) | Maximum Claude turns for a developer invocation at this complexity |
model | string | claude-sonnet-5 (small) / claude-opus-5 (medium/large) | Override the model for this complexity level, taking precedence over claude.models.developer |
effort | 'low' | 'medium' | 'high' | 'max' | high (small/medium) / max (large) | Effort level hint passed to the Claude CLI for this complexity. Use max only for large/multi-file work; see effort-mode economics above |
planning_max_turns | number | 500 | Maximum turns for planning sub-tasks at this complexity |
no_progress_window | number | — (75 for large only) | Number of turns without measurable progress before aborting |
backstop_max_turns | number | 500 | Hard turn ceiling regardless of other limits |
Validation requires developer_max_turns to be greater than 0 for any configured tier and planning_max_turns to be 0 or greater when set. Set planning_max_turns: 0 to skip planning for that tier.
Built-in default scaling (used when claude.scaling is not set):
| Tier | model | effort | developer_max_turns | Rationale |
|---|---|---|---|---|
| small | claude-sonnet-5 | high | 80 | Sonnet-high beats Opus-medium on cost-for-quality for 1–3 file mechanical work |
| medium | claude-opus-5 | high | 150 | Opus-high for 3–7 file work; max not justified at this scope |
| large | claude-opus-5 | max | 250 | The one tier where extended thinking’s 4x cost pays back on multi-file refactors |
Tuning for first use: The defaults work well for evaluation. If you find issues timing out or review checks failing on slow test suites, these are the first fields to adjust:
Field Default Recommended starting point claude.timeout1800Increase to 3600for large repos with long build timesreview.timeout_per_check120Raise to 300for slow test suitesagents.sprint_master.poll_interval3030is sufficient for evaluation; lower only if you need sub-30s latency and have webhooks configured
review
Section titled “review”Controls PR review behaviour: deterministic check commands, LLM review rounds, CI gating, and merge policy.
| Field | Type | Default | Description |
|---|---|---|---|
checks | Record<string, string> | {} | Named shell commands to run as deterministic review checks (e.g. test: 'npm test', lint: 'npm run lint'). Keys are check names; values are shell commands. Commands run in a non-login shell inheriting the worker’s PATH; prefer bash -c '...' over bash -lc '...' if a shell wrapper is needed |
timeout_per_check | number | 120 | Seconds allowed for each check command before it is killed |
max_review_cycles | number | 5 | Maximum number of LLM review rounds before giving up |
max_reimplement_cycles | number | — | Maximum number of times the developer re-implements after a review rejection. Unlimited if unset |
max_diff_lines | number | 3000 | Truncate the PR diff at this many lines when sending it to the LLM reviewer |
rebase_before_check | boolean | true | Rebase the PR branch onto the base branch before running check commands |
auto_merge_on_approval | boolean | false | Automatically merge the PR after the reviewer approves it |
require_ci_pass | boolean | true | Wait for all required CI checks to pass before merging |
ci_check_timeout | number | 300 | Seconds to wait for CI checks to complete before timing out |
ci_required_checks | string[] | [] | Names of specific CI checks that must pass. When empty, all checks must pass |
format_command | string | — | Formatting command to run before review checks (e.g. 'npm run format:fix'). Optional |
clean_verification | string | — | Optional raw shell command run as a final cache-defeating pre-handoff verification step (e.g. 'npm ci && npm run build:fresh && npm test' for TypeScript/React; 'dotnet clean && dotnet restore && dotnet build && dotnet test' for .NET). When absent, the clean step is skipped (backward compatible). The repo must actually provide the referenced scripts — e.g. build:fresh is not a standard npm script and must be declared in package.json. Can be overridden per-repo via repos[].review.clean_verification |
empty_verdict_auto_approve | boolean | true | Auto-approve when the LLM returns an empty or unparseable verdict and all deterministic checks passed |
scope_drift | object | — | Scope-drift detection settings. When absent or enabled is not true, scope-drift detection is disabled (default). Informational only — never auto-blocks. Global-only field; cannot be overridden per-repo in the first iteration |
scope_drift.enabled | boolean | false | Enable plan-vs-PR scope-drift detection in the reviewer. When true, the reviewer computes and surfaces a diff between the analyzer’s planned file set and the PR’s actual changed files |
external_prs | ExternalPrReviewConfig | mode: off | External PR review settings — controls whether Colony reviews human-authored PRs. See ExternalPrReviewConfig below |
ci_repair | CiRepairConfig | — | Controls which failed CI checks can trigger automatic CI repair tasks and how many repair cycles are allowed |
ExternalPrReviewConfig
Section titled “ExternalPrReviewConfig”Used by review.external_prs. Controls how Colony handles PRs that are not part of its own pipeline (i.e., PRs not on colony/issue-N branches). The feature is disabled by default (mode: off). See External PR Review for a usage guide covering all three modes.
| Field | Type | Default | Description |
|---|---|---|---|
mode | 'off' | 'on_request' | 'on_ready' | 'auto' | 'off' | When to review external PRs. off — disabled. on_request — only when a human comments /colony:review on the PR. on_ready — automatically when a PR is opened or transitions from draft to ready. auto — same triggers as on_ready, with the auto_approve flag available |
author_filter | string[] | ['*'] | Allowlist of GitHub usernames to review. ['*'] means any author. Set to a specific list to limit reviews to those users |
label_filter | string[] | [] | Require all listed labels to be present on the PR before reviewing. Empty list means no label requirement. Multiple labels use AND semantics — all must be present |
exclude_bots | boolean | true | Skip PRs authored by bot accounts (GitHub user type 'Bot') |
exclude_drafts | boolean | true | Skip draft PRs |
auto_approve | boolean | false | Recorded as task metadata and validated — Colony logs a startup warning if this is set to true without mode: auto. Note: Colony posts a formal GitHub Approve review event whenever the LLM verdict is APPROVE, regardless of this flag. Reserved for future use |
Example:
review: external_prs: mode: on_request # safest starting modeCiRepairConfig
Section titled “CiRepairConfig”Used by review.ci_repair.
| Field | Type | Default | Description |
|---|---|---|---|
max_cycles | number | 2 | Maximum CI repair cycles before escalating. executors.developer.ci_repair_max_cycles takes precedence when set |
deny_checks | string[] | [] | Check names that should never trigger automatic CI repair |
allow_checks | string[] | — | If set, only matching check names can trigger automatic CI repair |
agents
Section titled “agents”Configures the Colony agent processes. Each agent type extends a common set of fields. Agents run as standalone processes (singletons) or as part of the per-repo worker pool.
Common AgentConfig Fields
Section titled “Common AgentConfig Fields”All agent types share these base fields:
| Field | Type | Default | Description |
|---|---|---|---|
enabled | boolean | true | Whether the agent runs |
poll_interval | number | 30 | Seconds between poll cycles (minimum 5) |
health_port | number | (per agent) | HTTP health check port |
effort | 'low' | 'medium' | 'high' | 'max' | — | Claude effort level for this agent |
agents.sprint_master
Section titled “agents.sprint_master”Issue intake and pipeline monitoring. Enqueues tasks to the Postgres work queue when issues transition states.
| Field | Type | Default | Description |
|---|---|---|---|
| Common fields | health_port: 9100 | See Common AgentConfig Fields | |
intake_mode | 'all' | 'tagged' | 'tagged' | Whether to pick up all new issues or only colony-tagged ones |
heartbeat_timeout_minutes | number | 5 | Minutes before reclaiming stale tasks from workers that stopped heartbeating |
sweep_cooldown_minutes | number | 10 | Minutes to suppress re-enqueueing a sweep task after completion (0 to disable) |
label_sync_limit | number | 25 | Max issues to reconcile labels for per poll cycle |
projection_drain_limit | number | 50 | Max queued VCS write projections to drain per poll cycle |
auto_unblock_transient | boolean | true | Automatically unblock issues blocked by transient infrastructure failures |
auto_unblock_quota | boolean | true | Automatically re-queue quota-blocked issues after their reset time has passed |
max_auto_unblocks_per_cycle | number | 3 | Max issues to auto-unblock during one sprint-master cycle |
auto_unblock_cooldown_minutes | number | 10 | Per-issue cooldown before auto-unblocking the same issue again |
max_auto_unblocks_per_issue | number | 3 | Lifetime automatic unblock limit per issue before escalation |
full_sync_interval | number | 10 | Every Nth poll cycle, perform a full provider sync instead of cache-only reads |
full_sync_interval_active | number | 50 | Full-sync interval after webhooks have been active for enough cycles |
full_sync_active_threshold | number | 5 | Consecutive webhook-active cycles before switching to full_sync_interval_active |
webhook_inactivity_minutes | number | 15 | Minutes without webhook events before falling back toward full polling behavior |
queue_starvation_threshold_hours | number | 24 | Hours a pending task may wait before a starvation warning is logged (0 disables) |
orphan_recovery_cooldown_minutes | number | 5 | Per-issue cooldown before re-enqueuing orphan recovery work (0 disables) |
code_map_scan_interval_hours | number | 24 | Hours between periodic code-map scans for repos with code_map.enabled |
work_task_retention_days | number | 31 | Days to retain completed work_tasks rows before pruning |
utilization_rollup_enabled | boolean | true | Enable once-per-UTC-day rollup of worker utilization into worker_utilization_daily |
auto_repair_stale_blocked | boolean | false | Automatically clear stale is_blocked flags with no active dependency edge. Reuses the max_auto_unblocks_per_issue cap; a flapping issue escalates to needs-human. |
auto_repair_stale_subtask_edges | boolean | false | Automatically resolve stale subtask dependency edges on completed epics. Reuses the max_auto_unblocks_per_issue cap; a flapping issue escalates to needs-human. |
agents.analyzer
Section titled “agents.analyzer”Issue analysis and triage. Uses bare AgentConfig with no additional fields.
| Field | Type | Default | Description |
|---|---|---|---|
| Common fields | health_port: 9101 | See Common AgentConfig Fields |
agents.reviewer
Section titled “agents.reviewer”PR review: deterministic checks plus LLM review. Uses bare AgentConfig with no additional fields.
| Field | Type | Default | Description |
|---|---|---|---|
| Common fields | health_port: 9103 | See Common AgentConfig Fields |
agents.developer
Section titled “agents.developer”Issue implementation via Claude Code.
| Field | Type | Default | Description |
|---|---|---|---|
| Common fields | health_port: 9102 | See Common AgentConfig Fields | |
repo_context | RepoContextConfig | — | Controls repository context injection into prompts |
pr_overlap_threshold | number (0–1) | — | Fraction of plan files overlapping with open PRs to trigger block |
max_tooling_retries | number | 2 | Poll-level retries for transient push failures |
auto_decompose_on_exhaustion | boolean | true | Route oversized issues to planner after developer turn-limit exhaustion |
forbidden_paths | string[] | — | Glob patterns the agent may not read or write. See Protected Paths. |
read_only_paths | string[] | — | Glob patterns the agent may read but not write. See Protected Paths. |
self_validation_max_cycles | number (1–10) | 3 | Maximum self-validation repair cycles before escalating |
ci_repair_max_cycles | number (1–10) | 2 | Maximum CI repair cycles before escalating |
RepoContextConfig
Section titled “RepoContextConfig”Used by agents.developer.repo_context.
| Field | Type | Default | Description |
|---|---|---|---|
enabled | boolean | — | Enable repository context injection |
max_tokens | number | — | Maximum tokens for the context payload |
tree_depth | number | — | Depth of the directory tree to include |
agents.planner
Section titled “agents.planner”Epic decomposition — breaks large issues into sub-tasks.
| Field | Type | Default | Description |
|---|---|---|---|
| Common fields | health_port: 9105 | See Common AgentConfig Fields | |
max_turns | number | 200 | Max Claude turns for planning |
model | string | — | Override model for planner |
agents.merger
Section titled “agents.merger”Merge orchestration — handles PR merging and conflict resolution.
| Field | Type | Default | Description |
|---|---|---|---|
| Common fields | health_port: 9104 | See Common AgentConfig Fields | |
conflict_resolution | ConflictResolutionConfig | — | Automated merge conflict resolution settings |
ConflictResolutionConfig
Section titled “ConflictResolutionConfig”Used by agents.merger.conflict_resolution.
| Field | Type | Default | Description |
|---|---|---|---|
enabled | boolean | — | Enable automated conflict resolution |
max_conflict_files | number | 50 | Runaway backstop: skip LLM resolution when conflict files exceed this count. The primary gate is semantic complexity (LLM-judged), not this cap. |
max_conflict_regions | number | 150 | Runaway backstop: skip LLM resolution when conflict regions exceed this count. The primary gate is semantic complexity (LLM-judged), not this cap. |
timeout | number | — | Timeout in seconds for conflict resolution |
model | string | — | Override model for conflict resolution |
auto_merge_high_confidence_resolutions | boolean | true | Automatically merge when the LLM judges the conflict semantically simple, all files resolve at high confidence, and independently re-run review.checks pass. Set to false to always hand back. |
agents.monitor
Section titled “agents.monitor”Pipeline observability, self-healing, and Prometheus metrics. Enabled by default.
| Field | Type | Default | Description |
|---|---|---|---|
| Common fields | enabled: true, health_port: 9106 | See Common AgentConfig Fields | |
agent_down_timeout | number | 120 | Seconds before alerting that an agent is down |
pipeline_stall_timeout_minutes | number | 60 | Minutes before alerting a pipeline stall |
error_rate_threshold | number (0–1) | 0.5 | Alert if error rate exceeds this fraction |
error_rate_window | number | 30 | Minutes, rolling window for error rate calculation |
max_task_duration | number | 45 | Minutes before alerting on a long-running task |
cost_alert_threshold | CostAlertThreshold | — | Cost alerting thresholds |
alert_cooldown | number | 30 | Minutes, deduplication window for alerts |
alert_channels | MonitoringAlertChannel[] | [] | Alert delivery channels |
metrics_refresh_minutes | number | 10 | How often to refresh pipeline metrics |
sweep_cooldown_minutes | number | 10 | Minutes to suppress re-enqueueing a sweep task after completion (0 to disable) |
agent_health_host | string | 'localhost' | Host for polling agent health endpoints |
long_lived_state_ceiling_hours | number | 24 | Hours before stall-exempt issues trigger a long-lived-state ceiling alert |
daily_digest | DailyDigestConfig | enabled: false, time: '09:00' | Scheduled daily pipeline digest delivery |
weekly_digest | WeeklyDigestConfig | enabled: false | Scheduled weekly pipeline digest delivery |
auth | MonitorAuth | — | Credential for the monitor’s in-page login form and Authorization: Basic header (programmatic clients) |
self_healing | SelfHealingConfig | (see below) | Self-healing automation settings |
regression_guard | RegressionGuardConfig | (see below) | Pipeline health regression guard settings |
CostAlertThreshold
Section titled “CostAlertThreshold”Used by agents.monitor.cost_alert_threshold.
| Field | Type | Default | Description |
|---|---|---|---|
daily_usd | number | — | Alert when daily cost exceeds this USD amount |
monthly_usd | number | — | Alert when monthly cost exceeds this USD amount |
MonitorAuth
Section titled “MonitorAuth”Used by agents.monitor.auth.
| Field | Type | Default | Description |
|---|---|---|---|
username | string | required | Username for the monitor credential (used for in-page login and Authorization: Basic) |
password_env | string | required | Name of the environment variable containing the password |
DailyDigestConfig
Section titled “DailyDigestConfig”Used by agents.monitor.daily_digest.
| Field | Type | Default | Description |
|---|---|---|---|
enabled | boolean | false | Enable scheduled digest delivery |
time | string | '09:00' | Scheduled delivery time in HH:MM UTC format |
channels | DailyDigestChannelConfig[] | [] | Delivery channels. At least one channel is required when enabled: true |
DailyDigestChannelConfig
Section titled “DailyDigestChannelConfig”Used by agents.monitor.daily_digest.channels[].
| Field | Type | Default | Description |
|---|---|---|---|
type | 'slack' | 'webhook' | 'github_issue' | required | Digest delivery channel type |
url_env | string | — | Environment variable containing the Slack or webhook endpoint URL |
url | string | — | Literal Slack or webhook endpoint URL |
repo | string | — | Target owner/repo for github_issue digest delivery |
WeeklyDigestConfig
Section titled “WeeklyDigestConfig”Used by agents.monitor.weekly_digest.
| Field | Type | Default | Description |
|---|---|---|---|
enabled | boolean | false | Enable scheduled weekly digest delivery |
time | string | '09:00' | Scheduled delivery time in HH:MM UTC format |
day_of_week | number | 1 | Day of week for delivery (0 = Sunday, 1 = Monday … 6 = Saturday, UTC) |
channels | DailyDigestChannelConfig[] | [] | Delivery channels. At least one channel is required when enabled: true |
MonitoringAlertChannel
Section titled “MonitoringAlertChannel”Used by agents.monitor.alert_channels.
| Field | Type | Default | Description |
|---|---|---|---|
type | 'github_issue' | 'webhook' | 'slack' | 'pagerduty' | 'item_comment' | required | Alert channel type |
url | string | — | Endpoint URL for webhook/slack/pagerduty |
url_env | string | — | Name of the environment variable containing the endpoint URL |
routing_key | string | — | PagerDuty integration routing key (literal value, not an env var name) |
Example: Alerting Configuration
Section titled “Example: Alerting Configuration”agents: monitor: alert_cooldown: 30 cost_alert_threshold: daily_usd: 50 monthly_usd: 500 alert_channels: - type: slack url_env: COLONY_SLACK_WEBHOOK_URL - type: pagerduty routing_key: YOUR_PAGERDUTY_ROUTING_KEY - type: webhook url_env: COLONY_ALERT_WEBHOOK_URL - type: github_issueslack— Posts Block Kit messages to a Slack incoming webhook. Seturl_envto the name of the environment variable containing the webhook URL.pagerduty— Sends Events API v2 triggers. Setrouting_keyto the PagerDuty integration routing key (literal value, not an env var).webhook— POSTs a JSON payload to any HTTP endpoint. Seturl_envto the name of the environment variable containing the URL.github_issue— Creates a GitHub issue labeledcolony:alertin the first configured repo. No additional fields required.item_comment— Posts a comment on the referenced work item for item-scoped alerts (dead-letter digests, failed-projection audits, SLA warnings). This channel is the default rendering for sprint-master item-addressed alerts and is always active — no configuration is required to enable it. Comments carry thecolony:operationalmarker so auto-unblock paths skip them correctly.
The alert_cooldown (minutes) controls deduplication — each unique alert ID is only dispatched once per window. The cost_alert_threshold fires a warning when aggregate daily or monthly spend exceeds the configured USD amounts.
SelfHealingConfig
Section titled “SelfHealingConfig”Used by agents.monitor.self_healing.
| Field | Type | Default | Description |
|---|---|---|---|
worktree_cleanup_interval_hours | number | 6 | Hours between worktree cleanup sweeps |
max_auto_unblocks_per_issue | number | — | Maximum automatic unblocks per issue before requiring manual intervention (used by auto_repair_stale_blocked) |
auto_restart | boolean | false | Enable automatic agent restart on failure |
restart_strategy | 'pid' | 'systemd' | 'pm2' | 'pid' | Process restart mechanism |
restart_cooldown | number | 300 | Seconds between restart attempts |
max_restart_attempts | number | 3 | Maximum restart attempts before giving up |
restart_dry_run | boolean | false | Log restart actions without executing them |
work_task_retention_days | number | 31 | Deprecated. Retention sweeps are now driven by the sprint-master; configure agents.sprint_master.work_task_retention_days instead. Values at this location are still read at config load and migrated to the new key (with a warning), so an existing deployment keeps working. |
utilization_rollup_enabled | boolean | true | Deprecated. The utilization rollup is now driven by the sprint-master; configure agents.sprint_master.utilization_rollup_enabled instead. Values at this location are still read at config load and migrated to the new key (with a warning), so an existing deployment keeps working. |
auto_repair_stale_blocked | boolean | false | Deprecated. Consistency auto-repair is now driven by the sprint-master; configure agents.sprint_master.auto_repair_stale_blocked instead. Values at this location are still read at config load and migrated to the new key (with a warning), so an existing deployment keeps working. |
auto_repair_stale_subtask_edges | boolean | false | Deprecated. Consistency auto-repair is now driven by the sprint-master; configure agents.sprint_master.auto_repair_stale_subtask_edges instead. Values at this location are still read at config load and migrated to the new key (with a warning), so an existing deployment keeps working. |
Note: Transient auto-unblock (formerly
auto_unblock_transienton this config) is now exclusively the sprint-master’s responsibility. Configure it viaagents.sprint_master.auto_unblock_transient.
RegressionGuardConfig
Section titled “RegressionGuardConfig”Used by agents.monitor.regression_guard. Controls the pipeline-health regression guard that detects KPI degradation over rolling windows.
| Field | Type | Default | Description |
|---|---|---|---|
enabled | boolean | false | Enable the regression guard |
current_window_hours | number | 24 | Hours in the recent (current) measurement window |
baseline_window_days | number | 7 | Days in the baseline measurement window |
relative_threshold | number (0–1) | 0.25 | Fractional degradation that counts as a regression |
min_samples | number | 20 | Minimum sample floor — regressions are not reported below this count |
kpis | Record<RegressionKpi, boolean> | {} | Per-KPI enable flags; omitted keys are disabled |
KPI regression directions — a regression is flagged when the metric moves in the bad direction beyond relative_threshold:
| KPI | Regresses on |
|---|---|
block_rate | rise |
failure_blocked_rate | rise |
review_pass_rate | fall |
mean_cost_per_issue | rise |
mean_turns_per_issue | rise |
reimplement_loop_rate | rise |
dead_letter_rate | rise |
The REGRESSION_DIRECTION const exported from @colony/core encodes this mapping as a runtime value — downstream compute code should import it rather than re-deriving the direction.
This block is hot-reloadable via POST /api/config/reload. A partial reload merges only the specified keys and preserves all unspecified fields (including nested kpis entries).
Example
Section titled “Example”agents: monitor: regression_guard: enabled: true current_window_hours: 24 baseline_window_days: 7 relative_threshold: 0.25 min_samples: 20 kpis: block_rate: true review_pass_rate: true mean_cost_per_issue: trueagents.workers
Section titled “agents.workers”Per-repo worker pool configuration. Workers claim tasks from the Postgres work_tasks queue and dispatch to executor libraries. This section is optional — omit it when using standalone agent processes.
| Field | Type | Default | Description |
|---|---|---|---|
| Common fields | — | Inherits enabled, poll_interval, health_port from Common AgentConfig Fields; defaults depend on deployment configuration | |
heartbeat_interval | number | — | Seconds between task heartbeats. Workers send heartbeats to the Postgres queue to signal liveness — if a worker stops heartbeating for longer than sprint_master.heartbeat_timeout_minutes, the task is reclaimed |
max_task_retries | number | 3 | Max failures for a repo+issue+taskType combination within 60 minutes before dropping re-enqueue |
max_task_duration | number | 45 | Minutes before a task execution is considered timed out and aborted |
stale_task_threshold | number | 3 | Minutes before a claimed task without a heartbeat is reclaimed by another worker. Increase this if long-running pre-LLM setup steps (e.g. merging a branch far behind main) regularly exceed the default |
speculative_workspace_prep | boolean | false | Pre-create worktrees for pending develop tasks while the worker is idle |
Resolution Chain
Section titled “Resolution Chain”Note: The resolution chain applies to multi-repo deployments using
repos[]. For single-repo setups (the default), all settings are configured at the top level — no per-repo overrides are needed. See Multi-Repo (Colony Cloud) for details.
When a field can be set both globally and per-repo, Colony resolves the value using this fallback chain:
repoConfig.<field> ?? config.<field> ?? defaultThe per-repo value takes precedence when set; it falls back to the global value, then to the built-in default. The following helper functions in packages/core/src/config.ts implement this pattern for specific fields:
resolveIntakeMode(repoConfig, config)— resolvesintake_moderesolveAutoMerge(repoConfig, config)— resolves auto-merge behaviorresolveSelfImprovement(repoConfig, config)— resolvesself_improvementsettingsresolveDependabotConfig(repoConfig)— resolvesdependabotsettings
self_improvement
Section titled “self_improvement”Controls Colony’s self-improvement feature, which allows Colony to file issues against itself.
| Field | Type | Default | Description |
|---|---|---|---|
enabled | boolean | false | Enable the self-improvement feature |
label | string | 'colony:self-improvement' | GitHub label used to tag self-improvement issues |
cooldown_minutes | number (≥1) | 30 | Minimum minutes between self-improvement issue filings |
max_open_issues | number (≥1) | 5 | Maximum concurrently-open SI issues across all tracks for this repo; the fair-share scheduler seeds tracks in weight-proportion until this cap is reached |
tracks | SelfImprovementTrack[] | — | Per-track configuration for self-improvement. See deprecated fields |
calibration
Section titled “calibration”Controls the complexity calibration feature used to tune issue complexity estimates.
| Field | Type | Default | Description |
|---|---|---|---|
lookback_days | number | 7 | Number of days of historical issues to consider when calibrating complexity estimates |
Controls epic decomposition and completion behavior.
| Field | Type | Default | Description |
|---|---|---|---|
use_feature_branches | boolean | false | Create and use an epic feature branch for grouped subtask work |
rebase_strategy | 'on-complete' | 'periodic' | 'never' | 'on-complete' | When subtask branches should be rebased while an epic is in progress |
auto_merge_subtasks | boolean | true | Automatically merge approved subtask PRs when epic automation allows it |
review | EpicReviewConfig | (see below) | Final epic review remediation and human-review controls |
EpicReviewConfig
Section titled “EpicReviewConfig”Used by epic.review.
| Field | Type | Default | Description |
|---|---|---|---|
auto_fix_threshold | 'minor' | 'none' | 'minor' | Highest review finding severity that can trigger automatic remediation. Also controls whether a partial acceptance-criteria gap is treated as minor: with 'none', a partial gap has no in-session auto-fix path to catch it, so it stays major and blocking (routes to REMEDIATE/ESCALATE) instead of silently falling below blocking_severity. |
max_remediation_cycles | number | 3 | Maximum automated remediation cycles before escalation |
require_human_final_review | boolean | true | Require a human final review after automated epic review/remediation completes |
coverage_floor | number | 0 | Fraction (0-1) of the epic’s changed files the criteria pass must have seen. Below this floor the reviewer escalates to a human instead of approving, since it cannot vouch for a verdict it lacked the evidence to reach. 0 disables the check. |
blocking_severity | 'minor' | 'major' | 'major' | Severity at or above which a finding classified real forces a REMEDIATE/ESCALATE route. A minor finding below this threshold is not blocking, but remains eligible for the AUTO_FIX route independently of this setting when auto_fix_threshold is 'minor'. See Classify-then-route synthesis. |
adversarial | boolean | false | Enables the second (adversarial/hostile-second-opinion) LLM review pass alongside the criteria pass. When false, only the criteria pass runs — its claude.execute call, cost, and events — and the adversarial execution-failure/parse-failure escalations and verdict combination are all skipped. The deterministic coverage and dead-code guards run either way and can still force REQUEST_CHANGES. The posted review comment records whether the adversarial pass ran. |
max_diff_lines | number | 6000 | Truncate the epic diff at this many lines when sending it to the LLM reviewer. Epic review uses this value instead of review.max_diff_lines; because the default (6000) is always merged in, epic review does not inherit an explicitly raised review.max_diff_lines — set this field to change the epic budget. Kept higher than the regular-PR default because epic diffs routinely exceed it once tests are included, and epic review cost is not a meaningful constraint at that scale. |
Note:
coverage_flooris opt-in (0) rather than defaulting to some non-zero floor because large epics legitimately truncate the criteria pass’s file coverage — a non-zero default would escalate those epics on every cycle regardless of finding quality. Enable it deliberately once you’ve observed what coverage ratio your epics normally achieve.
Classify-then-route synthesis
Section titled “Classify-then-route synthesis”Epic review runs its criteria pass (and the adversarial pass, when adversarial is enabled), normalizes their output into findings, then synthesizes a verdict in two stages:
-
Classify (LLM). Each normalized finding is labeled
real,non-actionable,stale, oralready-satisfied(the cited code is still present but is no longer defective, as distinct fromstale, where the cited code is gone). This stage only assigns labels — it never decides what happens next, and it never decides whether a finding is a recurrence of a prior cycle’s finding (see the deterministic recurrence check below). -
Route (deterministic code).
routeClassifications()(packages/reviewer/src/epic-router.ts) maps the classified findings, the criteria pass’s coverage ratio, and a deterministic recurrence lookup (computed by the caller, not the classifier) to exactly one of four routes:Route Meaning APPROVENo actionable realfinding — nothing at or aboveblocking_severity, and no minor finding eligible for auto-fix; the epic PR is approvedAUTO_FIXEvery actionable finding is minor and auto_fix_thresholdis'minor'; the reviewer fixes them in-session and pushes to the epic branch instead of spawning a remediation subtask. This still consumes one ofmax_remediation_cycles. Minors are eligible for AUTO_FIX independently ofblocking_severity, so this route is reachable at the default config.REMEDIATEAny genuinely new blocking finding (major, or a coverage-/dead-code-guard finding at any label) spawns a remediation subtask — a finding that maps to a criterion renders as a - **Gap**:bullet, an unmapped one as a- **Integration**:bullet, in the same subtask. Auto-fix-eligible minors are never bundled in here — they’re belowblocking_severityand are already persisted separately as non-blocking polish findingsESCALATECoverage is below coverage_floor; or astale/non-actionable/already-satisfied-labeled adversarial finding or criteria-pass integration issue at or aboveblocking_severitywas the only thing this cycle would otherwise have raised (unverified_dismissal— see below); or every otherwise-blocking finding already has an active prior-cycle record at or aboveblocking_severity(a deterministic fingerprint recurrence, not a classifier label — already tracked from a prior cycle and still unresolved); a human reviews
The model can mislabel a finding, but it cannot choose the route directly — a hallucinated classification can at worst point routing at the wrong (deterministic) rule, never bypass the rules themselves, for every finding source. Coverage-guard and dead-code-guard findings are always blocking regardless of their LLM label, and criteria-pass gap findings (a missing/partial acceptance criterion) carry the same protection — all three are collected before the label-based settle step, so a stale/non-actionable/already-satisfied mislabel can never demote one of them out of routing (a criteria gap still falls into the blocking or auto-fix-eligible bucket purely by its own severity, never dropped from routing entirely). Adversarial findings and criteria-pass integration issues (no criterion, unlike a gap) aren’t re-collected the same way — a stale/non-actionable/already-satisfied label on one of them drops it from routing like any other dismissal, and if something else this cycle is still actionable that surviving work routes normally. But if such a dismissal at or above blocking_severity is the only thing this cycle would otherwise have raised, the route would silently become APPROVE with no deterministic backstop; instead it forces ESCALATE (unverified_dismissal) for human corroboration, so a single third-pass label can never turn a major adversarial finding into a silent approval. stale and already-satisfied findings below that floor are dropped before routing and never spawn a remediation subtask, since the cited code is either gone or already correct.
Recurrence is handled the same way regardless of finding source, including guard findings and criteria gaps (which the label-based settle step can never touch): epic-review.ts looks up every classified finding’s fingerprint against prior-cycle agent_findings records once per cycle and passes the resulting set into the router as recurringFingerprints. A finding whose fingerprint already has an active (non-dismissed) prior-cycle record never itself spawns a new remediation subtask; if it’s the only thing an otherwise-clean pass would have raised and it was itself at or above blocking_severity, the router escalates (repeated_blocking_finding) instead of approving an epic with a known, still-open blocking finding, or re-remediating a finding that already has an active subtask — a recurring minor finding below the threshold was never blocking and still routes to APPROVE (or AUTO_FIX, if otherwise eligible). A genuinely new blocking finding alongside a recurring one still spawns a remediation subtask for the new item alone.
plugins
Section titled “plugins”Registers executor plugins available to the worker.
| Field | Type | Default | Description |
|---|---|---|---|
registered | PluginRegistrationEntry[] | [] | Plugin modules to load at startup |
PluginRegistrationEntry
Section titled “PluginRegistrationEntry”Used by plugins.registered[].
| Field | Type | Default | Description |
|---|---|---|---|
name | string | required | Plugin name. Must match the manifest name returned by the loaded module |
module | string | required | Module path or bare package specifier. Relative paths resolve from colony.config.yaml |
tenants | string[] | ['*'] | Tenant IDs allowed to use the plugin’s executors. '*' allows any tenant |
intelligence
Section titled “intelligence”For a conceptual overview of how findings promote into repo intelligence and how retrospectives feed lessons back into the knowledge base, see Findings, Intelligence & Retrospectives.
Controls learned routing and promotion behavior.
| Field | Type | Default | Description |
|---|---|---|---|
auto_promote | IntelligenceAutoPromoteConfig | (see below) | Confidence-gated promotion of observations |
embedding | EmbeddingConfig | (see below) | Semantic-vector embedding provider used to compute intelligence scores |
reconcile | IntelligenceReconcileConfig | (see below) | Embedding-based deduplication on write — reinforce near-duplicates instead of inserting |
retrieval | IntelligenceRetrievalConfig | (see below) | Multi-source repo-intelligence retrieval service (shadow-mode by default) |
IntelligenceAutoPromoteConfig
Section titled “IntelligenceAutoPromoteConfig”Used by intelligence.auto_promote.
| Field | Type | Default | Description |
|---|---|---|---|
enabled | boolean | false | Enable automatic promotion of high-confidence observations |
confidence_threshold | number | 0.8 | Minimum confidence required before an observation can be promoted |
min_observation_count | number | 2 | Minimum number of supporting observations required for promotion |
IntelligenceReconcileConfig
Section titled “IntelligenceReconcileConfig”Used by intelligence.reconcile. Controls embedding-based deduplication: when a candidate intelligence item has a cosine similarity above reinforce_threshold to an existing item of the same kind, the existing item’s observation count is incremented rather than inserting a duplicate. Falls back to plain insert when embeddings are unavailable (null vector).
| Field | Type | Default | Description |
|---|---|---|---|
reinforce_threshold | number | 0.9 | Cosine-similarity threshold for semantic deduplication (0–1; higher = stricter matching) |
IntelligenceRetrievalConfig
Section titled “IntelligenceRetrievalConfig”Used by intelligence.retrieval. Gates the multi-source repo-intelligence retrieval service that selects, ranks, and budgets ranked snippets from findings, intelligence items, retrospectives, and the code map for injection into agent prompts. Disabled by default — enable enabled: true first to measure coverage before setting inject: true.
| Field | Type | Default | Description |
|---|---|---|---|
enabled | boolean | false | Master gate for the retrieval service |
inject | boolean | false | Inject retrieved snippets into agent prompts (set after shadow-measuring coverage) |
agents | object | (inherit enabled) | Per-agent opt-in/out overrides — keys: analyzer, planner, developer, reviewer, retrospector |
token_budget | number | 1200 | Maximum token budget for retrieved snippets in a single prompt (must be ≥ 1) |
max_snippets_per_source | number | 5 | Maximum snippets returned per source before cross-source ranking (must be ≥ 1) |
similarity_threshold | number | 0.3 | Cosine similarity floor for the embedding-based recall signal (0–1) |
re_enrichment_enabled | boolean | false | Enqueue re-retrospect tasks for detected legacy low-fidelity intelligence items (detection always runs; this flag gates the task enqueue) |
EmbeddingConfig
Section titled “EmbeddingConfig”Used by intelligence.embedding. Configures the embedding provider that computes semantic vectors for intelligence items. Only openai is supported today (uses text-embedding-3-small); reuses OPENAI_API_KEY already documented for the codex block.
| Field | Type | Default | Description |
|---|---|---|---|
enabled | boolean | false | Enable embedding computation (safe to leave false until backfill runs) |
provider | 'openai' | 'openai' | Embedding provider (currently only openai is supported) |
model | string | 'text-embedding-3-small' | Model name for embedding computation |
dimensions | number | 1536 | Vector dimensions — must match the chosen model |
api_key_env | string | 'OPENAI_API_KEY' | Environment variable name holding the API key |
Distinct from
agents.merger/executors.merger— this block gates analysis passes that run before the merge step (pre-merge checks and semantic-conflict detection). It does not configure the merger agent itself.
This block is hot-reloadable via POST /api/config/reload. Workers pick up changes live without a redeploy.
| Field | Type | Default | Description |
|---|---|---|---|
semantic_conflict_check | SemanticConflictCheckConfig | (see below) | Opt-in semantic-conflict analysis pass. Default off. |
SemanticConflictCheckConfig
Section titled “SemanticConflictCheckConfig”Used by merge.semantic_conflict_check. Gates the semantic-conflict analysis pass that detects concurrent changes to different files that are semantically coupled (e.g. one issue renames a config key while another adds a consumer of the old key). Default off — nothing changes for existing deployments until enabled: true is set.
| Field | Type | Default | Description |
|---|---|---|---|
enabled | boolean | false | Enable the semantic-conflict analysis pass |
min_confidence | number | 0.75 | LLM confidence threshold (0–1) at/above which the later PR is deferred for re-review |
max_candidate_branches | number | 10 | Maximum concurrent branches to compare against — runaway backstop |
model | string | — | Optional model override for the focused semantic-conflict assessment |
lookback_hours | number | 24 | How far back (hours) to consider recently-merged branches when scanning for semantic conflicts |
Example:
merge: semantic_conflict_check: enabled: true min_confidence: 0.8 max_candidate_branches: 15 lookback_hours: 48intake_rules
Section titled “intake_rules”Declarative per-issue workflow routing. When present, Colony evaluates rules in order at intake time and routes each issue to the first matching rule’s workflow. If no rule matches, the default workflow is used.
Note: The routing resolution logic (
resolveWorkflowForIssue) is wired in a follow-up sub-issue. At this stage, the config block is defined and validated but not yet consumed by the sprint-master intake path.
This block is hot-reloadable via POST /api/config/reload. The sprint-master picks up changes live without a redeploy.
intake_rules: default: colony-default # required — workflow id used when no rule matches rules: # optional — evaluated in order, first match wins - workflow: colony-content match: labels: [content] # matches if the issue carries ANY of these labels (OR) title_pattern: '^\[docs\]' # optional — regex tested against issue title - workflow: colony-docs match: issue_type: documentation # optional — matches GitHub issue type / node id body_pattern: 'RFC-\d+' # optional — regex tested against issue bodySemantics
Section titled “Semantics”defaultis required whenintake_rulesis present; it is a workflow id string.rulesis an ordered array; each rule has aworkflowid and amatchobject.- Within a single rule, all provided predicates must hold (AND across predicates).
labelsmatches if the issue carries any of the listed labels (OR within the list).title_patternandbody_patternare JavaScript-compatible regular expressions validated at config load time.- When
intake_rulesis absent, every issue resolves tocolony-default— today’s behavior.
IntakeRulesConfig fields
Section titled “IntakeRulesConfig fields”| Field | Type | Required | Description |
|---|---|---|---|
default | string | Yes | Workflow id used when no rule matches (or rules is absent) |
rules | IntakeRule[] | No | Ordered list of routing rules; first match wins |
IntakeRule
Section titled “IntakeRule”| Field | Type | Required | Description |
|---|---|---|---|
workflow | string | Yes | Workflow id for matching issues |
match | IntakeRuleMatch | Yes | Predicate set (at least one key) |
IntakeRuleMatch
Section titled “IntakeRuleMatch”At least one predicate must be specified per rule.
| Field | Type | Description |
|---|---|---|
labels | string[] | Matches if the issue carries ANY of the listed labels (OR within the list) |
issue_type | string | Matches if the issue type equals this value (GitHub issue type or node id) |
title_pattern | string | JavaScript-compatible regex tested against the issue title |
body_pattern | string | JavaScript-compatible regex tested against the issue body |
default_workflow
Section titled “default_workflow”Selects a builtin workflow as the repo-level default. When set, resolveWorkflowForRepo materializes the named builtin at the config tier — above a repo-committed .colony/workflow.yaml — so a cloud-side selection reliably overrides a repo-local file.
default_workflow: colony-content # or colony-default (the default when absent)Valid values: colony-default, colony-content.
Hot-reloadable via POST /api/config/reload — sprint-master picks up changes live without a redeploy.
Note: This is a repo-default selector, not a per-issue routing mechanism. Use
intake_rulesfor per-issue label-based routing.
strategy (StrategyConfig)
Section titled “strategy (StrategyConfig)”Controls when the automated strategize cycle fires. Default is a weekly interval; set a custom cadence with kind: interval (milliseconds) or kind: cron (5-field cron expression, evaluated in UTC).
On startup, Colony reads the last-persisted next_due_at from the database and fires a catch-up cycle immediately if the time has passed — so restarts never silently delay a weekly cycle.
strategy: cadence: kind: interval interval_ms: 604800000 # 7 days (default)
# OR use a cron expression (UTC):strategy: cadence: kind: cron expr: '0 0 * * 0' # Every Sunday at midnight UTCStrategyConfig fields:
| Field | Type | Default | Description |
|---|---|---|---|
cadence.kind | 'interval' | 'cron' | — | Cadence kind — required when cadence is present |
cadence.interval_ms | number | 604800000 | Milliseconds between cycles; used when kind is 'interval' |
cadence.expr | string | — | 5-field cron expression (UTC); required when kind is 'cron' |
Hot-reloadable (informational only — the timer reflects the cadence configured at last startup; restart to activate a changed cadence).
See strategy.md for the full strategize cycle documentation.
Credential File Convention
Section titled “Credential File Convention”Colony workers support an optional operator-mounted credential file at /colony/keys/credentials.yaml. When present, the container entrypoint runs scripts/render-credentials.sh before starting any agent process. The rendered config files authenticate package manager clients against private feeds (Azure DevOps Artifacts npm registries and NuGet feeds).
The /colony/keys volume already exists in the default container setup (it also holds the GitHub App private key).
Schema
Section titled “Schema”schema: 1credentials: azure_devops: pat: <personal-access-token> # required; Azure DevOps PAT with Packaging read scope organization: <org-name> # informational; not used by the renderer feeds: - url: https://<org>.pkgs.visualstudio.com/_packaging/<feed-name>/npm/registry/ kind: npm scope: '@my-scope' # optional; derived from feed name if omitted - url: https://<org>.pkgs.visualstudio.com/_packaging/<feed-name>/nuget/v3/index.json kind: nuget| Field | Type | Required | Description |
|---|---|---|---|
schema | number | required | Schema version. Must be 1. Any other value causes entrypoint to exit non-zero |
credentials.azure_devops.pat | string | required | Azure DevOps Personal Access Token. Missing or null value causes entrypoint to exit non-zero |
credentials.azure_devops.organization | string | — | Azure DevOps organization name. Informational only; not used by the renderer |
credentials.azure_devops.feeds | array | — | List of feed entries. Empty array or missing section is not an error — entrypoint exits 0 silently |
feeds[].kind | 'npm' | 'nuget' | required | Package manager for this feed |
feeds[].url | string | required | Full registry/feed URL |
feeds[].scope | string | — | npm scope to bind (e.g. "@acme"). For kind: npm only. Derived from the feed name in the URL if omitted |
Example
Section titled “Example”schema: 1credentials: azure_devops: pat: ghp_xxxxxxxxxxxxxxxxxxxx organization: myorg feeds: - url: https://myorg.pkgs.visualstudio.com/_packaging/my-npm-feed/npm/registry/ kind: npm scope: '@myorg' - url: https://myorg.pkgs.visualstudio.com/_packaging/my-nuget-feed/nuget/v3/index.json kind: nugetWhat gets rendered
Section titled “What gets rendered”| Kind | Output file | Format |
|---|---|---|
npm | ~/.npmrc | scope→registry mapping + base64-encoded _authToken |
nuget | ~/.config/NuGet/NuGet.Config | XML <packageSources> + <packageSourceCredentials> |
All rendered files are written with mode 0600.
Reserved kinds (not yet implemented)
Section titled “Reserved kinds (not yet implemented)”The following kind values are reserved for future use and are not processed in v1:
github— reserved; the existingscripts/git-credential-colony.mjshelper handles GitHub auth and is not superseded by this conventionpypi— future:~/.config/pip/pip.confmaven— future:~/.m2/settings.xml
Behavior
Section titled “Behavior”- Missing file — If
/colony/keys/credentials.yamldoes not exist,render-credentials.shexits 0 silently. No warning is logged, and all agents start normally. - Schema mismatch — If
schemais not1, the entrypoint exits non-zero with a message naming the offending file. No agent process starts. Fix the YAML and restart the worker. - Missing PAT — If
credentials.azure_devops.patis absent or null, the entrypoint exits non-zero with a clear error. Fix and restart. - Empty feeds — Zero feed entries is not an error; entrypoint exits 0.
- Credential values — The renderer never logs token values. Log output only reports the count of entries written (e.g.,
render-credentials: wrote 2 npm credential entries to ~/.npmrc).
Rotating credentials
Section titled “Rotating credentials”Edit /colony/keys/credentials.yaml on the host and restart the worker container. The entrypoint re-renders all credential files from scratch on each start — stale entries from a previous run are replaced.
Security
Section titled “Security”Credentials are never baked into image layers. The /colony/keys directory is operator-mounted at container run time. Rendered config files (~/.npmrc, ~/.config/NuGet/NuGet.Config) are written inside the running container’s filesystem with mode 0600 and do not persist across restarts.
Enterprise Networks
Section titled “Enterprise Networks”On networks that perform TLS interception (corporate proxies, Zscaler, Palo Alto, and similar), every outbound HTTPS connection is re-signed by an internal CA that the system trust store does not recognize. Without trust propagation, every npm install, dotnet restore, az login, and curl call fails at the TLS handshake — before authentication is even attempted.
Root CA mount
Section titled “Root CA mount”Mount your PEM-format root CA certificate(s) under /colony/keys/ca-certs/:
services: worker: volumes: - /path/to/ca-certs/:/colony/keys/ca-certs/:roOr mount individual files:
volumes: - ./corp-root-ca.crt:/colony/keys/ca-certs/corp-root-ca.crt:roAny *.crt file in that directory is treated as a PEM-encoded root CA. The directory is operator-managed and never baked into image layers.
Trust propagation
Section titled “Trust propagation”The entrypoint runs scripts/install-trusted-cas.sh before any other initialization. It propagates trust to:
| Runtime / tool | Mechanism |
|---|---|
curl, wget, apt, Azure CLI | System trust store via update-ca-certificates |
dotnet restore, dotnet tool install | System trust store (honored automatically) |
Node.js, npm, npx, bun | NODE_EXTRA_CA_CERTS env var pointing at a combined CA bundle |
Format requirement
Section titled “Format requirement”Certificates must be PEM-encoded with a .crt extension. If your CA certificate is in DER or PFX format, convert it before mounting:
# DER → PEMopenssl x509 -inform DER -in corp-ca.der -out corp-ca.crt
# PFX → PEM (extract CA cert only)openssl pkcs12 -in corp-ca.pfx -nokeys -out corp-ca.crtBehavior
Section titled “Behavior”- Empty or absent directory — exits 0 silently; trust stores are unchanged; behavior is identical to a standard deployment.
- Malformed cert — entrypoint exits non-zero with a message naming the offending file. No agent process starts. Fix the certificate and restart.
- Multiple certs — all
.crtfiles in the directory are installed. - Cert rotation — replace files on the host and restart the worker. Trust stores are rebuilt from scratch on each start.
- Cert contents — never logged. Log output only reports the count of certificates installed (e.g.,
install-trusted-cas: installed 1 CA certificate(s)).
executors (ExecutorsConfig)
Section titled “executors (ExecutorsConfig)”Executor-specific settings that belong to the execution logic of each agent type (distinct from agent process infrastructure such as poll_interval and health_port).
If executors is not set, values are auto-populated from the corresponding agents.* fields for backward compatibility via populateExecutorsFromAgents() in config.ts.
Workflow YAML and task inputs: Executors can receive typed inputs forwarded from a preceding stage via the
outputsblock in.colony/workflow.yaml— this is separate fromcolony.config.yaml. For theoutputsblock syntax, per-task-type input schemas, and merge semantics, see the Executor Contract Reference.
executors.analyzer
Section titled “executors.analyzer”| Field | Type | Default | Description |
|---|---|---|---|
effort | 'low' | 'medium' | 'high' | 'max' | — | Claude effort level for the analyzer. Maps to claude --effort |
executors.developer
Section titled “executors.developer”| Field | Type | Default | Description |
|---|---|---|---|
repo_context | RepoContextConfig | — | Controls how the developer builds repository context for Claude Code |
pr_overlap_threshold | number | — | Fraction from 0 to 1 of changed files that may overlap another open PR before blocking development |
max_tooling_retries | number | — | Maximum retries for transient tooling errors during development |
max_plan_files | number | — | Override the complexity gate threshold for planned files. Runtime default is floor(maxTurns / 10); set 0 to disable |
auto_decompose_on_exhaustion | boolean | true | Route oversized issues to planner after developer turn-limit exhaustion |
effort | 'low' | 'medium' | 'high' | 'max' | — | Claude effort level for the developer |
forbidden_paths | string[] | — | Glob patterns the agent may not read or write. See Protected Paths. |
read_only_paths | string[] | — | Glob patterns the agent may read but not write. See Protected Paths. |
self_validation_max_cycles | number (1–10) | 3 | Maximum self-validation repair cycles before escalating to human review. |
ci_repair_max_cycles | number (1–10) | 2 | Maximum CI repair cycles before escalating to human review. |
Protected Paths
Section titled “Protected Paths”forbidden_paths and read_only_paths let operators restrict which files the developer agent may access or modify. The enforcement happens at the tool call level — the agent receives a hard error rather than a prompt-level suggestion.
forbidden_paths — the agent cannot read or write these paths. Use for secrets, credentials, and other files the agent should never see (e.g. .env*, secrets/**).
read_only_paths — the agent can read these paths for context but cannot edit them. Use for IaC definitions, generated migration files, and CI workflows (e.g. infrastructure/**, .github/workflows/**, **/Migrations/**).
Example:
executors: developer: forbidden_paths: - 'secrets/**' - '.env*' read_only_paths: - 'infrastructure/**' - '**/*.bicep' - '**/*.bicepparam' - '.github/workflows/**' - '**/Migrations/**'Paths are repo-relative and use minimatch glob syntax with { dot: true } (so .env* matches .env). Empty or unset lists mean no enforcement — behavior is identical to deployments without these keys.
Cascade: values set under agents.developer are cascaded into executors.developer by resolveConfig(). At runtime, executors.developer is the authoritative source.
Hook glob engine limitation: the PreToolUse hook script that enforces the lists at tool-call time uses a custom glob-to-regex engine that does not support brace expansion ({a,b}). Use separate list entries instead — e.g. '**/*.bicep' and '**/*.bicepparam' rather than '**/*.{bicep,bicepparam}'. The reviewer diff check uses minimatch directly and does support brace expansion.
Bash detection limitation: the hook pattern-matches common shell write idioms (>, >>, tee, sed -i, cp, mv, cat <<HEREDOC >) and read idioms (cat, less, head, tail, grep, source), but is best-effort. It misses variable expansion ($DEST), subshell paths ($(get_path)/file), and quoted paths with spaces. The reviewer diff check (which scans the actual PR diff) is the reliable safety net.
executors.reviewer
Section titled “executors.reviewer”| Field | Type | Default | Description |
|---|---|---|---|
effort | 'low' | 'medium' | 'high' | 'max' | — | Claude effort level for the reviewer |
executors.planner
Section titled “executors.planner”| Field | Type | Default | Description |
|---|---|---|---|
max_turns | number | — | Maximum number of turns for the planner’s Claude session |
effort | 'low' | 'medium' | 'high' | 'max' | — | Claude effort level for the planner |
model | string | — | Override the Claude model used by the planner |
executors.merger
Section titled “executors.merger”| Field | Type | Default | Description |
|---|---|---|---|
conflict_resolution | ConflictResolutionConfig | — | LLM-assisted conflict resolution settings. See the agents.merger section for field details |
commands (White-Label Override) {#commands}
Section titled “commands (White-Label Override) {#commands}”Advanced: This section is for operators who need to white-label Colony’s slash-command surface. Most users do not need to configure this block. Skip it on your first read.
Controls the root keyword used in all slash commands that Colony posts to GitHub issue comments and PRs. By default every command uses the colony prefix (e.g. /colony:retry, /colony:help). Setting commands.root replaces that prefix across all agent-posted comments and the webhook receiver’s command parser.
| Field | Type | Default | Description |
|---|---|---|---|
root | string | 'colony' | Slash-command root keyword (e.g. 'pipeline' makes /pipeline:retry work). Must match ^[a-z][a-z0-9-]*$, max 32 characters. |
Semantics
Section titled “Semantics”Override, not alias. Setting commands.root: pipeline means /pipeline:retry works and /colony:retry stops working. The original root is not preserved as a secondary alias — Colony accepts only the configured root.
What changes. All slash commands rendered in agent comments (analyzer, developer, reviewer, merger, planner, sprint-master) and the webhook receiver’s command parser use the configured root. Operators and humans must use the new root when interacting with issues.
What does NOT change. GitHub labels are unaffected. State labels (e.g. colony:analyzing, colony:blocked) and inbound label commands (colony:enqueue, colony:paused) remain colony:-prefixed regardless of commands.root. To rename labels, use the separate labels.prefix option.
Not hot-reloadable. commands.root is read at process startup. Changing it requires restarting both webhook-receiver and sprint-master to take effect. Live reloading via POST /api/config/reload does not apply to this field.
Validation. The value is validated at startup by colony check. The accepted pattern is ^[a-z][a-z0-9-]*$ with a maximum length of 32 characters: lowercase letters and digits only, starting with a letter, hyphens allowed. Invalid values abort startup with a clear error.
Example
Section titled “Example”commands: root: pipeline # /pipeline:retry, /pipeline:help, etc.Forward-looking note:
commands.rootis preliminary work toward per-workflow trigger words. When Colony adds support for multiple workflows in a single environment, each workflow will be able to use a distinct slash-command root to route interactions independently.
Multi-Repo (Colony Cloud)
Section titled “Multi-Repo (Colony Cloud)”Colony Cloud supports managing multiple repositories from a single Colony instance using repos[] or tenants[] configuration. For single-repo deployments (the default open-source path), these sections are not needed — the flat github + workspace top-level config is all you need.
repos[] (RepoConfig)
Section titled “repos[] (RepoConfig)”Multi-repo deployments list each repository under repos. Each entry configures authentication, workspace, and pipeline behavior for one repository. Config cannot have both top-level repos and tenants.
| Field | Type | Default | Description |
|---|---|---|---|
owner | string | required | GitHub organization or user that owns the repository |
repo | string | required | Repository name |
token_env | string | — | Name of the environment variable containing the PAT for this repo. Either token_env or app is required |
app | GitHubAppConfig | — | Per-repo GitHub App credentials for the coder identity. Either app or token_env is required |
ops_app | GitHubAppConfig | — | Per-repo GitHub App credentials for the ops identity |
ops_token_env | string | — | Name of the environment variable containing the PAT for the ops identity. Alternative to ops_app |
bot_username | string | — | Display name shown for bot-authored comments and labels for this repo |
workspace | RepoWorkspaceConfig | required | Per-repo workspace settings. Overrides the global workspace section |
review | RepoReviewConfig | — | Per-repo review overrides. Only the fields in the explicit pick list can be overridden |
self_improvement | SelfImprovementConfig | — | Per-repo override for self-improvement settings |
pattern_memory | PatternMemoryConfig | — | Per-repo pattern memory settings. See fields below |
code_map | CodeMapConfig | — | Per-repo code-map (symbol index) settings. See fields below |
sla | SlaConfig | — | Per-repo SLA thresholds. See fields below |
intake_mode | 'all' | 'tagged' | — | Overrides the global agents.sprint_master.intake_mode for this repo. When set, only this repo’s intake behavior changes |
workers | WorkerPoolConfig | — | Per-repo worker pool settings. See fields below |
dependabot | DependabotConfig | — | Per-repo Dependabot integration settings. See fields below |
pattern_memory
Section titled “pattern_memory”| Field | Type | Default | Description |
|---|---|---|---|
enabled | boolean | required | Enable pattern memory for this repo |
max_results | number (≥1) | required | Maximum number of pattern results to include in prompts |
lookback_days | number (≥1) | required | Number of days of history to search for patterns |
code_map (CodeMapConfig)
Section titled “code_map (CodeMapConfig)”Per-repo code-map (symbol index) settings. Controls whether Colony builds and injects a symbol index for this repo. See also agents.sprint_master.code_map_scan_interval_hours.
| Field | Type | Default | Description |
|---|---|---|---|
enabled | boolean | false | Enable the code-map scanner for this repo |
inject | boolean | false | Inject code-map context into developer and reviewer prompts |
token_budget | number | — | Maximum tokens to use for the code-map context payload |
min_confidence | 'low' | 'heuristic' | 'high' | 'heuristic' | Minimum symbol-confidence threshold for symbols included in the code-map |
| Field | Type | Default | Description |
|---|---|---|---|
warn_after_minutes | Record<string, number> | — | Map of pipeline state name (e.g. 'ready-for-dev') to the number of minutes before a warning is emitted. Only states that need a threshold need to be listed |
workers
Section titled “workers”| Field | Type | Default | Description |
|---|---|---|---|
pool_size | number (integer ≥1) | — | Number of worker processes to run for this repo |
memory | string | — | Container memory limit for worker processes (e.g. '4g', '6G') |
health_port_start | number | — | Starting port for sequential health check endpoint allocation. Port ranges must not overlap across repos |
dependabot
Section titled “dependabot”| Field | Type | Default | Description |
|---|---|---|---|
auto_review | boolean | — | Automatically trigger a review cycle for Dependabot PRs |
auto_merge_patch | boolean | false | Auto-merge patch updates after deterministic checks pass |
auto_merge_minor | boolean | false | Require LLM review for minor updates before merging |
auto_migrate_breaking | boolean | false | Automatically create migration issues for breaking major updates (opt-in) |
bot_usernames | string[] | ['dependabot[bot]'] | GitHub usernames to treat as Dependabot for triggering this integration |
Each open Dependabot PR is expected to have an open tracking issue while auto_review is enabled.
If an operator closes a tracking issue while its Dependabot PR is still open, Colony records a
decline (metadata.declined = true on the dependabot-pr link row — the system of record) instead
of re-filing a new tracking issue, and posts a comment on the PR for visibility. Re-filing is
suppressed until the PR is closed — Dependabot will reseed a fresh PR, which gets a fresh tracking
issue and is reviewed normally. Reopening the declined tracking issue does not resume automated
review of that PR: it re-enters normal issue intake (analyze/develop on its own branch) like any
other reopened issue, and the declined flag is not cleared by the reopen. Tracking issues that
predate the dependabot-pr link (identified only by a body marker) have no link row to carry a
decline, so closing one of those simply re-files a fresh tracking issue on the next sweep, restoring
tracking.
Per-Repo Workspace Overrides (RepoWorkspaceConfig)
Section titled “Per-Repo Workspace Overrides (RepoWorkspaceConfig)”Each entry in repos[] requires a workspace block. Fields here override the corresponding global workspace values for that repo using the fallback chain repoConfig.workspace.<field> ?? config.workspace.<field> ?? default.
| Field | Type | Default | Scope | Description |
|---|---|---|---|---|
repo_dir | string | required | overrides global | Path to the local git clone for this repo |
base_dir | string | required | overrides global | Base directory for this repo’s worktrees. Supports {owner} and {repo} template tokens |
setup_command | string | — | overrides global | Command to run after creating a worktree (e.g. 'bundle install'). Overrides the global workspace.setup_command. Prefer bash -c '...' over bash -lc '...' if a shell wrapper is needed |
setup_timeout | number | 300 | overrides global | Seconds to allow the setup command to run. Overrides the global workspace.setup_timeout |
prebuild_command | string | — | overrides global | Command to run after setup_command to pre-build workspace packages. Overrides the global workspace.prebuild_command |
review_workspace_base | string | — | overrides global | Container-local path for ephemeral reviewer worktrees |
skip_pre_push_hook | boolean | — | overrides global | Pass --no-verify to git push for this repo |
branch | string | 'main' | overrides global | Default branch name for this repo |
secret_env_vars | string[] | — | per-repo only | Names of environment variables to resolve and inject into worker processes for this repo |
timeouts | TimeoutsConfig | — | per-repo only | Fine-grained clone and tooling timeouts. See fields below |
timeouts (TimeoutsConfig)
Section titled “timeouts (TimeoutsConfig)”| Field | Type | Default | Description |
|---|---|---|---|
clone | number | 600 (recommended) | Seconds before git clone is timed out |
mise_install | number | 600 | Seconds before mise install is timed out |
mise_reshim | number | 30 | Seconds before mise reshim is timed out |
submodule_init | number | 120 | Seconds before git submodule update --init is timed out |
Per-Repo Review Overrides (RepoReviewConfig)
Section titled “Per-Repo Review Overrides (RepoReviewConfig)”The review block inside a repos[] entry accepts an explicit subset of the global ReviewConfig fields. Only fields in the following pick list can be overridden per-repo:
| Field | Type | Description |
|---|---|---|
checks | Record<string, string> | Override the review check commands for this repo |
timeout_per_check | number | Seconds to allow each check to run |
rebase_before_check | boolean | Rebase the PR branch onto the base branch before running checks |
require_ci_pass | boolean | Require all CI checks to pass before merging |
ci_check_timeout | number | Seconds to wait for CI checks to complete |
ci_required_checks | string[] | Names of CI checks that must pass |
format_command | string | Command to auto-format code before review |
clean_verification | string | Cache-defeating pre-handoff verification command (see global review.clean_verification for examples). When set, overrides the global value for this repo |
auto_merge_on_approval | boolean | Auto-merge the PR when the reviewer approves |
external_prs | ExternalPrReviewConfig | External PR review settings (mode, filters, auto_approve). Per-repo value overrides the corresponding global review.external_prs fields (field-by-field fallback — unset fields inherit from global) |
ci_repair | CiRepairConfig | Override CI repair settings for this repo |
The following ReviewConfig fields are global-only and cannot be overridden per-repo: max_review_cycles, max_reimplement_cycles, max_diff_lines, empty_verdict_auto_approve, scope_drift.
Note: New fields added to
ReviewConfigare not automatically per-repo-overridable. The pick list inRepoReviewConfigmust be updated explicitly to expose them.
tenants[] (TenantConfig)
Section titled “tenants[] (TenantConfig)”Multi-tenant deployments group repositories by tenant under tenants. Each tenant has its own GitHub App credentials and optional cost budget. Config cannot have both top-level tenants and repos.
| Field | Type | Default | Description |
|---|---|---|---|
id | string | required | Unique identifier for this tenant |
name | string | — | Human-readable tenant name |
app | GitHubAppConfig | required | Tenant-level GitHub App credentials for the coder identity |
ops_app | GitHubAppConfig | — | Tenant-level GitHub App credentials for the ops identity |
anthropic_api_key_env | string | — | Name of the environment variable containing the Anthropic API key for this tenant. Overrides the global ANTHROPIC_API_KEY |
provider | 'anthropic' | 'foundry' | 'gateway' | — | Overrides claude.provider for this tenant. Omit to inherit the global claude.provider (built-in default: 'anthropic') |
foundry | object | — | Per-tenant ClaudeFoundryConfig; overrides claude.foundry. Only used when the tenant’s effective provider is 'foundry' |
gateway | object | — | Per-tenant ClaudeGatewayConfig; overrides claude.gateway. Only used when the tenant’s effective provider is 'gateway' |
cost_budget | object | — | Cost budget settings for this tenant. See fields below |
defaults | object | — | Default settings cascaded to repos in this tenant that do not override them. Currently supports timeouts (TimeoutsConfig) |
workers | NomadPoolConfig | — | When set, enables nomad mode for this tenant: workers roam across all tenant repos. See fields below |
repos | RepoConfig[] | required | List of repositories belonging to this tenant |
Provider precedence: tenant provider override > global claude.provider > built-in 'anthropic'. Credentials resolve per provider — when provider: 'foundry', the tenant’s foundry.api_key_env is used; when provider: 'anthropic', the tenant’s anthropic_api_key_env is used.
Each tenant must define app. Each repo inside tenants[].repos must also define its own repo auth (token_env or app) unless it is configured as an ADO repo.
Example — two tenants on different providers:
tenants: - id: acme app: app_id: 12345 private_key_path: /run/secrets/acme-app.pem installation_id: 67890 anthropic_api_key_env: ACME_ANTHROPIC_KEY repos: - owner: acme repo: app token_env: ACME_GITHUB_TOKEN
- id: contoso app: app_id: 23456 private_key_path: /run/secrets/contoso-app.pem installation_id: 78901 provider: foundry foundry: resource: contoso-azure-resource api_key_env: CONTOSO_FOUNDRY_KEY repos: - owner: contoso repo: platform token_env: CONTOSO_GITHUB_TOKENNote: Per-tenant provider configuration is resolved at startup and is not hot-reloadable. The
tenantsblock is excluded fromReloadableConfig— a full process restart is required to pick up provider changes.
cost_budget
Section titled “cost_budget”| Field | Type | Default | Description |
|---|---|---|---|
monthly_usd | number | — | Monthly spending cap in USD for this tenant |
When the tenant’s monthly_usd cap is exhausted, intake halts for every repo in the tenant. A signed
comment is intended to be posted on the first in-flight issue per repo (per-tenant-per-month cooldown to
avoid repeated comments), but in the current Sprint Master deployment inFlightIssues — the tracker the
comment-posting helper scans to pick a target issue — is never populated, so this remains log-only in
practice today: watch for the Tenant monthly budget exhausted, skipping repo warning rather than a
GitHub comment.
Repository-scoped budget cap (budget_cap_usd)
Section titled “Repository-scoped budget cap (budget_cap_usd)”Individual repos can carry their own monthly cap, nested inside the tenant cap above. This field has no
YAML key, no CLI command, and — in this OSS distribution — no monitor/dashboard write path either; it is a
DB-only column (repo_configs.budget_cap_usd) surfaced to the runtime as ResolvedRepoConfig.budgetCapUsd
/ RepoContext.budgetCapUsd. Only a positive value acts as a gate — there is no separate enable flag,
the value’s positivity is the enable flag. A non-positive value (0 or negative) is treated the same as
NULL (the column default): no cap, no gating. This lets 0 be used as a safe “unset” encoding by any
external writer of this column without accidentally halting intake. Migration 130 is a read-only
report, not a data change: it identifies any pre-existing non-positive budget_cap_usd rows and emits a
row-count RAISE NOTICE naming the affected repo_ids, but deliberately does not rewrite them to NULL.
Earlier revisions of this migration did rewrite those rows, which turned out to be self-defeating —
a deployment that had used 0 as a permanent kill-switch would silently resume spending on the first poll
after upgrade, with the row already NULL and no record of which repos had been gated. Leaving the stored
value intact keeps it recoverable and keeps it visible to the runtime read-seam warnings described below,
which fire every process start for as long as the row stays non-positive — a NULLed row could never trip
them again. The migration’s RAISE NOTICE is forwarded to Colony’s own pino log at warn level by a
notice listener attached in Migrator.migrate() (packages/pipeline-store/src/migrator.ts), so it is
visible in Colony’s log stream even though Postgres’s default log_min_messages setting would otherwise
discard a plain NOTICE from the server log. Because the migration makes no data change, its message is
purely informational; the authoritative, ongoing signal is the runtime warn logged at each read seam
(adapter, cost store, worker config resolution) every time a non-positive cap is read and dropped — this
fires on every process start for a legacy row, not just once at migration time.
If the repo already has a repo_configs row (i.e. any DB-driven config feature — intake mode, review
checks, model selection, auto-merge — has ever been set for it), set the cap with a column-preserving
UPDATE:
UPDATE repo_configs SET budget_cap_usd = $2 WHERE repo_id = $1;If the repo has no repo_configs row yet, be aware that migration 031_tenant_config.sql gives every
other column a NOT NULL DEFAULT: a fresh INSERT materialises intake_mode = 'tagged' and
review_checks = '[]' (plus auto_merge = false) even though you only intended to set a budget cap. In
DB-driven-config deployments (where repoConfigRowToResolved reads this row into ResolvedRepoConfig),
those materialised defaults are propagated as real overrides — intake_mode = 'tagged' silently switches
the repo to label-gated intake (requiring colony:enqueue on every issue) and review_checks = '[]' zeroes
out review checks, since both values win over the global config wherever a resolver falls back to
repoConfig?.field ?? config.field. OSS YAML deployments read budget_cap_usd directly via
getRepoBudgetCap and never invoke the adapter, so these defaults are inert there. If you are on a
DB-driven-config deployment and inserting a fresh row, set intake_mode and review_checks explicitly to
your intended values in the same statement — review_checks is NOT NULL and has no value meaning “fall
back to the global config”; whatever the row holds wins, so '[]' means “run no review checks at all.”
Supply the repo’s actual checks as a JSON array of {"name": ..., "command": ...} objects instead of
copying an empty array, e.g.:
-- review_checks is NOT NULL: whatever this row holds becomes the repo's checks in-- DB-driven-config deployments. '[]' means "run no review checks at all" — enumerate-- the repo's real checks instead of copying an empty array.INSERT INTO repo_configs (repo_id, budget_cap_usd, intake_mode, review_checks)VALUES ( $1, $2, 'all', '[{"name":"test","command":"npm test"},{"name":"lint","command":"npm run lint"}]')ON CONFLICT (repo_id) DO UPDATE SET budget_cap_usd = EXCLUDED.budget_cap_usd;Managed/cloud deployments may layer their own dashboard or API on top of this column.
At each poll, the tenant cap (cost_budget.monthly_usd) is checked first and still binds across the whole
tenant as described above. If the tenant cap has room, a second, narrower check runs against the
individual repo’s budget_cap_usd: when a repo’s own monthly spend reaches its cap, new-issue intake
and the New→Analyzing advance halt for that repository only — sibling repos in the same tenant keep
polling normally, and every other lifecycle step for the capped repo (merged/closed PR detection, label
projection from Postgres, stale-task reclamation, dependency unblocking, worktree pruning, and the rest)
continues to run so in-flight issues keep being serviced. The repo-cap check is logged with a
Repository monthly budget exhausted, skipping repo warning (structured with repo, spentUsd,
limitUsd) every time it trips. Unlike the tenant scope, the repo scope is user-visible: a
<!-- colony:repo-budget-exhausted:YYYY-MM -->-marked comment is posted on each skipped issue —
distinct skipped issues each receive their own comment. Both the poll path (posting on the
lowest-numbered new-state issue for the repo) and the webhook intake path (posting on the specific
issue whose intake it just gated) call the same shared helper, which serialises on a single
Postgres-backed bot_dedup_keys atomic claim keyed (repoId, issueNumber, 'repo-budget-exhausted', 'YYYY-MM') — issueNumber is the real gated issue, so the gate is one-per-issue-per-month, closing the
race only when both paths target the same issue (e.g. a poll and a webhook racing on the same
newly-opened issue). Whichever producer’s claim wins the INSERT ... ON CONFLICT DO NOTHING posts the
comment; another producer racing on that same issue/month sees created: false and skips posting. If the
comment post itself fails after the claim is won, a transient GitHub error (5xx, connection reset or
timeout, HTTP 429, or a GitHub 403 secondary-rate-limit/abuse-detection response — the classes
isRetryableError recognises) is normally absorbed by the write service’s own projection retry queue — it
returns without throwing, the claim is intentionally retained, and the queued projection re-posts later —
so no explicit release is needed for that path. The claim is released (best-effort DELETE) only when a
transient error is actually thrown (e.g. a write service constructed without a projection store), so the
next poll or webhook targeting that issue retries the post. A permanent (non-retryable) failure — auth,
validation, not-found, or a generic 403 — keeps the claim so the doomed write is attempted at most once per
(repo, issue, month) rather than being retried every poll cycle. A generic 403 stays non-retryable
deliberately: only the narrower secondary-rate-limit/abuse-detection message shape is treated as transient,
so a genuinely revoked or invalid token does not retry forever.
A claim being held (created: false) does not always mean a comment was actually posted — a crash or
SIGTERM landing between winning the claim and completing the write leaves a durable claim with no
comment. Both producers pass a getComments callback into postRepoBudgetExhaustedComment, which uses it
to self-heal this case: on created: false, it reads the claim’s posted_at, and when that timestamp is
more than 5 minutes old (a live producer posts within seconds, never minutes) it attempts a self-heal.
The self-heal itself is gated by a second, distinct bot_dedup_keys claim — kind
repo-budget-exhausted-selfheal, same (repoId, issueNumber, 'YYYY-MM') key shape — won via the same
atomic INSERT ... ON CONFLICT DO NOTHING as the original claim, and only the winner fetches comments and
checks the marker before proceeding to post. The original repo-budget-exhausted claim is never deleted,
so there is no delete/re-insert window for two concurrent recoverers to double-post. Because the self-heal
claim can only be won once per (repo, issue, month), this bounds the whole flow to at most one post plus
at most one self-heal re-post per (repo, issue, month) — including when the post itself is silently
suppressed (e.g. a customer-facing repo) or fails permanently, which leaves the marker absent forever but
does not turn into a per-poll retry loop: the second and later stale calls lose the self-heal claim and
short-circuit before fetching comments, so the getComments GitHub call also runs at most once per (repo,
issue, month). Neither path retains its own in-memory cooldown for this scope any more; the marker check
here is narrowly scoped to the self-heal path, not a general substitute for the PG claim.
The comment is best-effort, not a guarantee of delivery: the poll path only fires if a new-state issue
is already present in Postgres when the cap trips. In a poll-only (webhook-degraded) deployment, newly
opened GitHub issues are gated by intakeIssues before ever being written to Postgres, so they never
populate that candidate list. If no new-state issue happens to already be stranded in PG, a tripped cap
in that deployment mode produces no comment from the poll path — only the Repository monthly budget exhausted, skipping repo warning log — though the webhook intake path does not have this gap, since it
always has a concrete issueNumber to comment on the moment intake for that issue is attempted. Because
the dedup claim is now per-issue rather than per-repo, if both paths happen to target the same issue
concurrently, whichever wins the claim posts and the other skips; if they target different issues, each
still gets its own comment.
Both getRepoBudgetCap and getRepoMonthlyCost match owner/name case-insensitively and
deterministically select a single repos row (ORDER BY r.id LIMIT 1) — the same row for both queries —
so the cap and the spend total are always drawn from the same repo and never summed across multiple
tenants’ rows that happen to share an owner/name (repos only enforces UNIQUE(tenant_id, owner, name),
not global uniqueness). getRepoBudgetCap additionally returns a discriminated result distinguishing a
repos-row lookup miss (no row matched at all — e.g. an owner/name casing mismatch against the stored row,
or the repo hasn’t been seeded into repos yet) from a deliberately cleared cap (a row was found with no
cap set). A lookup miss is non-authoritative: it never clears an already-known cap or its fail-closed
protection (see below) — only a confirmed cap-cleared result does.
Per-repo spend is authoritative from the database: Sprint Master re-reads the repo’s total monthly cost
(getRepoMonthlyCost) and re-seeds the tracker every poll cycle, rather than deriving it from a
process-global cost delta. The re-seed overwrites the tracked spend with the DB total, including
downward movement — an issue transferred out of the repo or a corrected/deleted cost event lowers the
repo’s monthly total, and the tracker mirrors that drop on the very next poll. A repo that was gated
therefore un-gates within the same month once its DB total falls back below the cap, keeping the poll-path
gate consistent with the webhook path (which reads live DB spend directly on every intake event). The cap value itself
(budget_cap_usd) is likewise re-read from the database every poll, so a direct-SQL cap change takes
effect without a restart. The same repo_spend value is also exposed on Sprint Master’s /health endpoint
(alongside the existing tenant_spend) for observability.
The cap and spend reads are assigned atomically — both-or-neither — so a failure partway through this
hydration (e.g. the cap read succeeds but the spend read throws) can never leave a live cap paired with a
stale or unseeded spend value. Sprint Master also tracks, in memory, the last cap value actually observed
for each repo during its current process lifetime, seeded from two sources: every successful poll-cycle
hydration, and — re-checked before each poll’s hydration attempt, though it only has an effect the first
time (before either source has populated the record for that repo) — any cap already present on the repo’s
context (RepoContext.budgetCapUsd), which in DB-driven-config deployments is itself populated at startup
from ResolvedRepoConfig.budgetCapUsd. A hydration failure is
treated as gated (fail-closed) only for a repo that has previously been observed to carry a cap (via
either source): for that repo, intakeIssues and the New→Analyzing advance are skipped for that one poll,
and no repo-budget-exhausted comment is posted, since the spend/cap figures are stale and not authoritative
that cycle. Consequently, a repo whose startup-resolved config already carries a cap gates immediately on
its very first post-restart poll if that poll’s hydration fails — it does not need a prior successful
poll-cycle read to be treated as capped. A repos-row lookup miss (repoFound: false) is handled the same
way as a hydration failure with respect to the existing cap: it is not treated as “cap cleared”, so the
poll leaves ctx.budgetCapUsd and the last-observed-cap record exactly as they were (the spend read is
simply skipped for that poll, since there is no confirmed cap to pair it with) — this is what keeps a
config-seeded cap and its fail-closed protection from being silently discarded by a transient lookup miss.
A hydration failure for a repo that has never been observed to
carry a cap through either source — a fresh process’s first poll for a repo whose resolved config has no
cap, or a repo that has simply never set one — does not gate; the repo falls through fail-open, since
gating every repo in the deployment (including ones that never set a cap) off a single read failure would
silently halt all new-issue intake behind nothing but a log line. Both outcomes emit a pipeline error
event so the failure is observable regardless of which way it resolves. Every other lifecycle step (merged/
closed PR detection, label projection, stale-task reclamation, and the rest) continues servicing in-flight
issues regardless of which branch is taken. Hydration is retried on the next poll; note that the in-memory
last-observed-cap record resets on process restart, so a capped repo whose cap is not reflected in its
startup-resolved config (e.g. an OSS YAML deployment with no ResolvedRepoConfig.budgetCapUsd path, where
the cap is only ever known via the DB read) will fail open for its very first post-restart poll if that
poll’s hydration fails, rather than gate.
The webhook intake path (handleIntake, the primary route that creates paid analyze/plan work) applies
this identical fail-closed-for-observed-capped / fail-open-for-never-capped rule off the same in-memory
last-observed-cap record — Sprint Master hands the webhook path the same Map instance the poll path
writes to, so an observation made by one path (a successful hydration, or a config-seeded cap) is visible
to the other. A getRepoBudgetCap/getRepoMonthlyCost failure on the webhook path for a repo previously
observed as capped therefore also fails closed: the event is treated as handled without upserting the issue
or enqueueing work, leaving it for the poll-cycle fallback to pick up once the DB recovers. A failure for a
repo never observed as capped fails open, same as the poll path, and both branches emit the same pipeline
error event. This closes the gap where a DB degradation could previously disable the cap only on the
route that actually creates new paid work.
tenants[].workers (NomadPoolConfig)
Section titled “tenants[].workers (NomadPoolConfig)”When present, enables nomad mode for the tenant: a fixed pool of roaming workers claims tasks from any repo in the tenant rather than being pinned to one. Absent = per-repo behavior (default, unchanged).
In nomad mode,
pool_sizeis a desired replica count (an orchestration input), not an in-process fork count. Combiningtenants[].workerswithrepos[].workers.pool_sizeon any member repo is rejected at config load time.
| Field | Type | Default | Description |
|---|---|---|---|
pool_size | number (integer ≥1) | required | Desired replica count for nomad workers in this tenant |
memory | string | — | Container memory limit for worker processes (e.g. '4g', '6G') |
health_port_start | number | — | Starting port for sequential health-check endpoint allocation |
max_cached_repos | number (integer ≥1) | 4 | LRU cap on warm RepoContexts per nomad worker. Limits idle worktree memory usage per worker |
For a docker-compose snippet that stands up a nomad worker pool (including deploy.replicas, unique COLONY_INTERNAL_WORKER_ID injection, and the v1 scope note), see Nomad worker pool (multi-tenant mode) in the deployment guide.
Per-agent-type keys alongside repos[].workers
Section titled “Per-agent-type keys alongside repos[].workers”When repos[].workers (the worker pool model) is configured, setting per-agent-type keys under agents (developer, analyzer, reviewer, merger, planner) for executor-specific settings is deprecated. Use the executors section instead.
As of this release, these keys are explicitly dropped and not cascaded to executors when workers is configured. A warning is emitted at config load time. Run colony config migrate to strip them automatically.
Removal timeline: These deprecated per-agent keys are scheduled for removal in the next major release. Run
colony config migratenow to clean up your config.
# Before (deprecated — when repos[].workers is set)agents: developer: effort: high
# Afterexecutors: developer: effort: highDeprecated Configuration
Section titled “Deprecated Configuration”The following fields are still accepted by the config loader but will be removed in a future release. Migrate away from them as soon as possible.
llm.* (LlmConfig)
Section titled “llm.* (LlmConfig)”Fields: llm.analyzer_max_turns, llm.reviewer_max_turns, llm.developer_max_turns
Migration: Use claude.scaling (ClaudeScalingConfig) instead. Per-agent max_turns are now configured via claude.scaling entries keyed by complexity (small, medium, large).
# Before (deprecated)llm: developer_max_turns: 150
# Afterclaude: scaling: medium: developer_max_turns: 150monitoring.* (legacy top-level key)
Section titled “monitoring.* (legacy top-level key)”Migration: Move all fields under agents.monitor. The loader still accepts monitoring: as a top-level key and merges it into agents.monitor for backward compatibility (see config.ts:179–194), but this bridge will be removed in a future release.
# Before (deprecated)monitoring: enabled: true port: 9100
# Afteragents: monitor: enabled: true port: 9100self_improvement.tracks (SelfImprovementTrack[])
Section titled “self_improvement.tracks (SelfImprovementTrack[])”Migration: Manage tracks via the dashboard UI or the monitor REST API (POST /api/si/tracks). YAML track definitions in the config file are still accepted by the config loader and emit a deprecation warning at load time, but at runtime they are ignored — when a TrackStore (Postgres) is available, the seeding loop reads exclusively from the store. Run colony config migrate to remove stale YAML track entries.
Removal timeline:
self_improvement.tracksin YAML is scheduled for removal in the next major release. Runcolony config migratenow to clean up your config.
Each entry in self_improvement.tracks is a SelfImprovementTrack object with the following fields:
| Field | Type | Default | Description |
|---|---|---|---|
name | string | required | Unique identifier for this track |
label | string | required | GitHub label applied to self-improvement issues filed on this track |
cooldown_minutes | number | required | Minimum minutes between filings on this track |
seed_title | string | — | Title template for self-improvement issues filed on this track |
seed_body | string | — | Body template for self-improvement issues filed on this track |
instructions | string | — | Additional instructions injected into the developer prompt for this track |
cadence | object (kind, expr) | — | Firing schedule. kind: 'cooldown' uses cooldown_minutes; kind: 'cron' uses a cron expr. Internal cadence.expr is managed via the dashboard API |
proposal | object (ProposalSlate) | — | Slate of issue sizes to propose. Internal proposal.slate structure is managed via the dashboard API |
weight | number | — | Fair-share seeding weight — tracks are seeded proportional to weight (0 treated as 1 for scheduling); higher weight means more seeds under the max_open_issues cap |