modernci Agent-first CI.
How it works

Committed, judged, merged — or appealed.

committed→judged→merged
↘ appealed → judged again
appealed: sent back to an agent, which returns with a fix

An agent says it's done. ModernCI opens the pull request, judges it on the cheapest machine that finishes in time, and merges it on a green verdict. On red, the move is appealed: a work item goes to an agent with everything a fix needs. Every step goes on the record, metered.

01

Committed

The agent pushes its branch and calls done over MCP, naming a parent if the branch is stacked. ModernCI opens the pull request, or reuses the open one. Agents use four tools: done, claim, report and ask.

mcp · tools/call done agent host mbp-3 → your daemon
// request
{ "name": "done", "arguments": { "repo": "acme/monorepo", "branch": "lane-billing", "parent": null } }

// result
{ "move": "lane-billing → predev", "pr": 4399, "state": "judging", "checks": 7, "deadline": "20m" }
02

Judged

Each check's input key is looked up first: the content of every file, env var and toolchain the check read. A stored green costs nothing. The rest run on the cheapest backend that finishes before the branch file's deadline, using each check's measured duration. The result is a verdict, green or red, and it cites its evidence. Moves waiting for a backend sit on the docket.

Checks · #4399 lane-billing → predevExample rows
Each check, its input key, and whether it was reused or run
CheckInput keyResultBackendTime
buildk:7e1c…a9reusedstored0s
clippyk:51b0…3dreusedstored0s
test · 3 crates affectedk:c40f…12redwarm-14m 21s
gatesk:9d22…e0greenwarm-11m 02s
oraclek:0a7e…5breusedstored0s
Router · deadline 20m · cheapest that finishes in time
Backends the router considered
BackendFree slotsFinishes inPriceChosen
warm-1 · your server, hot build dir16m$0.000yes
laptop-ana · your laptop114m$0.000slower, cold
cloud-c7 · spot, 16 vCPU85m$0.012/minnot needed
03

Merged

The verdict lands as one ci status on the head commit, stamped with the engine and config that made it. On a green verdict, the target fast-forwards onto the judged commit. If the target moved and the moved commits touch the move's inputs, only the affected checks re-run first.

status · ci on commit 8f3a2c1, #4402
● ci  green   7 checks · 5 reused · 2 run on warm-2 · 3m 40s
evidence   1,418 tests judged (target 1,418) · logs · input keys
stamp      engine 0.4.2 · config predev.yaml@3f9c0e1
merged     predev fast-forwarded to 8f3a2c1 · done to merge 11m

A verdict made by an older engine or config is stale. ModernCI judges the move again on its own, and a new engine shadows the old one before it judges anything.

04

Red becomes a work item

Every red has an owner within one tick. The work item carries what a fix needs, and nothing the agent has to go and find.

redwork item wi-0412attempt 1 of 2 · prompt fix→ mbp-3 (claude)
#4399 lane-billing→predev: verdict red on test
check
test · cargo nextest run -p acme-billing -p acme-tax -p acme-ledger
tests
213 green · 1 red · 0 skipped · 214 judged (target 214)
failed
billing::invoice_rounds_half_up
log
assertion `left == right` failed
  left: 400
 right: 201
at crates/billing/src/invoice.rs:188
reproduce
modernci ci --from lane-billing --only test -- billing::invoice_rounds_half_up
diff
3 files, +41 −12 · crates/billing/src/invoice.rs, …/config.rs, …/tests/invoice.rs
second witness
not a flake: fails alone, and on a second machine, 3 of 3
attempts
none yet
precedent
r-12: "Invoice totals round half-up, never per customer."
sealed
**/tests/**, golden/** · only a human order unseals them; weakening goes to Questions
05

Appealed

The failure ladder sends the item to an available agent host, which pushes a fix. ModernCI judges the failing check first, on a warm host, and judges the full move again only if that comes back green. A bad fix costs one check, not a whole move.

Rung 1 · now2 × fixThe failing check, its log and the diff.
Rung 21 × stalledRoot cause, the history, a different approach.
Rung 3notify ownerEmail, Slack or a webhook. A person takes over.
Policy.ci/policy.yamlOr edit it in the UI. Prompts are named templates.
06

Limits and questions

A host that hits a usage limit reports limited with its reset time. It gets no work, its items go back on the docket without counting an attempt, and it resumes on its own. An agent that needs a decision calls ask, and its item stays under a stay, shown with its reason, until a person answers. The answer becomes a ruling, and later prompts follow it as precedent.

Agent hostsExample rows
Agent hosts and their state
HostRuntimeStateItem
mbp-3claudebusywi-0412
studio-1codexlimited until 14:00, resumes itselfreturned to the docket
laptop-anaclaudereadynone
questionq-88 · from mbp-3 on wi-0415 · stay: waiting on a person

The golden for invoice totals has the old half-even rounding. Update the golden, or change the code back to half-even?

  1. Update golden/billing/invoice.json to half-up. (agent recommends: matches ruling r-12)
  2. Revert the code to half-even.
07

What keeps agents honest

Built for agents means built for agents that cut corners. ModernCI takes no agent's word.

  • Per-test truth. Per-test results (JUnit, nextest and the like) are judged, not just the exit code. Zero tests, a duration collapse or a count drop against the target is red. Tests that disappear are listed on the move.
  • Evidence, not claims. A fix for a failing test must show red before and green after, measured by ModernCI.
  • Sealed paths. Deleting or loosening an assertion, skipping a test or editing a golden is sealed: only a human order unseals it. It goes to Questions, not to merge.
  • Flakes named by cause. A suspect gets a second witness: it is re-run alone, under load and in isolation on a second backend, and classed as load, shared state, clock, port race or infra. The agent fixes the cause instead of adding a retry.
  • Evidence on every verdict: logs, test counts, input keys and stamps.
08

The engine underneath

One desired-state loop runs everything. Webhooks, polls, agent reports and timers only mark a move dirty. A green, unmerged move stays in the gap until it merges, even if no event arrives. Silence was the most expensive failure we lived, so nothing stalls without an alarm.

observeThe facts now: forge, store, backends, agents.
desireWhat must be true: branch file, policy, stack, stamps.
diffThe actions that close the gap.
actIdempotent, each put on the record with the fact it used.

The loop runs at least once every 20 seconds. If the last full pass is older than three ticks, stall.loop fires.