Every month we publish the ledger. Not because the numbers always look good — sometimes they don’t — but because the receipts are the product. If Colony is going to claim it runs autonomous software development in production, “runs” needs to mean something verifiable. This is what May looked like.
All figures below are drawn from the full calendar month of May 2026 (May 1 – May 31).
Headline numbers
| Metric | Value |
|---|---|
| PRs merged | 904 |
| Median cost per PR | $2.73 |
| Median active dev time | 23 minutes |
| Repos active | 3 |
| Total spend | $4,909.63 |
904 PRs over 31 days works out to roughly 29 per day. Median active development time was 23 minutes per PR — analysis, implementation, code review, and merge. That number is the active agent time from issue pickup to merged PR; it includes the Inspector’s deterministic checks (build, tests, lint, types) plus LLM review, but excludes queue wait time.
29 per day is slower than April’s pace. The workload mix explains most of it. Three cross-cutting initiatives ran through the bulk of the month: the IssueState.Blocked state machine refactor, which required coordinated changes across core types, the pipeline store, executors, and the sprint-master; a systematic migration from GitHubClient to a VCS abstraction layer; and the org-scoped query refactor across cloud API routes. Wide-scope changes like these move slowly on an issues-per-day basis — the diffs are large, the dependency chains are long, and they tend to block other work while in flight. Epic review cycles, where the pipeline detects implementation gaps in a prior epic and files follow-up passes, accounted for roughly 60 issues. Self-improvement work held steady at 16% of completed issues.
Per-agent spend
Total spend for May: $4,909.63 across 904 merged PRs.
| Agent | Spend | Share |
|---|---|---|
| Developer | $3,188.16 | 64.9% |
| Analyzer | $945.68 | 19.3% |
| Reviewer | $612.93 | 12.5% |
| Planner | $162.86 | 3.3% |
Developer at 64.9% is expected. It’s the only agent running Claude Code with full read/write access to the workspace — multi-turn sessions, file operations, test execution. The cost is proportional to what it actually does.
Analyzer at 19.3% is the second-largest line. Every PR goes through static and semantic analysis: build, tests, lint, types, plus structural checks before handing off to the Reviewer. That’s 1,800+ Analyzer runs across the month.
Reviewer at 12.5% runs the LLM-driven code review pass — logic, correctness, API contract adherence. Reviewer and Analyzer together account for 31.8% of total spend, which is the cost of the quality gate that makes autonomous merge safe.
Planner at 3.3% decomposes epics into independently-mergeable sub-issues. May included several multi-pass decomposition cycles where the initial breakdown produced sub-issues that were too tightly coupled to merge in isolation. The July ledger will have a fuller breakdown of where that spend concentrated.
Two failure modes that surfaced in May
One resolved. One active.
Resolved: dependency metadata stored in HTML comments
For the first few months of operation, cross-issue dependencies were tracked by embedding metadata directly in GitHub issue bodies: <!-- colony:depends-on: #N -->. The sprint-master parsed these on every poll cycle to determine which blocked issues could be unblocked.
This worked at small scale. It broke in three ways as the pipeline grew:
- Text-parsing fragility. Issue body edits (agent comments, retrospective additions, human notes) could corrupt the dependency metadata. The parser was lenient by design, which made corruption silent.
- Race conditions. The planner writes dependency metadata and transitions labels as two separate operations. The sprint-master could read the label before the metadata write completed and pick up an issue that looked ready but wasn’t.
- Poll overhead. Parsing full issue bodies for every blocked issue on every poll cycle scaled linearly with the number of blocked issues.
The fix was replacing HTML comment metadata with an issue_dependencies Postgres table managed by a DependencyManager service. Cycle detection runs as a recursive CTE at insert time. Resolution returns both unblocked issues and completable epics in a single query. Legacy HTML comment parsing was removed after all callers migrated (#1064–#1071, #1085–#1088, #1111).
The dependency graph is now atomic and query-efficient. The metadata-before-state-transition invariant holds because both writes go to Postgres in the same transaction.
Active: circuit breakers at looping failure paths are still incomplete
The pipeline has circuit breakers on the most common loops: build failure cycles, review cycles, cost caps. May surfaced a gap.
When workspace preparation fails — the worker can’t create a worktree, clone state is corrupted, git references are stale — the issue re-enqueues every 30 seconds with no backoff and no incrementing failure counter. During the dependency graph implementation (issue #1067), this produced 84 failed task attempts in 3 minutes, all hitting the same workspace error. The sprint-master saw tasks completing (technically — they failed cleanly) and kept re-enqueueing.
The broader principle is documented in production-learnings: every looping failure path needs a circuit breaker with a manual-unblock escape hatch (#816, #1067). The build and review loops have this. Workspace preparation failures do not yet. The fix requires a failure counter on work_tasks with an exponential backoff policy and a human-review-ready escalation after a threshold. Work is in progress (#1892).
Until it ships, operators monitoring a workspace-failure loop will see it in the task queue as rapid re-enqueue events with identical error signatures. The current mitigation is a sprint-master alert rule that fires when a single issue accumulates more than 10 failed tasks in a 5-minute window.
Named refactor: atomic state transitions in the pipeline store
The most structurally significant change to cross the public ledger threshold in May was atomicTransition() in pipeline-store.ts (#1753).
Background: Colony’s pipeline state for each issue lives across three places — the state column in pipeline_issues, boolean flag columns (is_blocked, is_paused), and pending rows in the work_tasks queue. Historically these were updated by independent callers in sequence. The invariants — is_blocked must equal state === DependencyBlocked, is_paused must equal state === Paused, no pending tasks for a blocked issue — were enforced by convention, not by the database.
That convention held until it didn’t. The most frequent manual repair during the March 31–April 1 session was fixing is_blocked flag drift: issues that had been unblocked still showing is_blocked=true in Postgres, invisible to the sprint-master’s task queue. Dozens of issues across multiple repos required direct SQL updates (#1682). That same month surfaced task queue drift: deletePendingTasksForIssue() called as a separate operation after state transitions left a window where a worker could claim a stale task for an issue in an unexpected state.
atomicTransition() closes this class entirely. State, flags, audit trail, and task queue cleanup execute in a single Postgres transaction. The invariants are enforced by the transaction, not by callers. Ad-hoc setBlockedFlag() and deletePendingTasksForIssue() calls have been removed from executor packages (#1820, #1821, #1822) and the sprint-master transition hook (#1785).
This is the kind of refactor that doesn’t show up in a demo. It’s invisible unless you’ve watched the failure mode it prevents occur in production enough times to track it down to a root cause. The ledger records it because that’s what the ledger is for.
The June 15 post covers the Postgres-as-authority architecture in full — the consistency model that makes atomic transitions meaningful, and what the migration away from GitHub-label-as-truth looked like in practice.
If you’d like to see the pipeline running on your work, we should talk.