# How it works

> Committed, judged, merged — or appealed. Every verdict cites its evidence and every move goes on the record.

```
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`.

```
// 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.

Example checks for #4399 lane-billing → predev:

| Check | Input key | Result | Backend | Time |
|---|---|---|---|---|
| build | k:7e1c…a9 | reused | stored | 0s |
| clippy | k:51b0…3d | reused | stored | 0s |
| test (3 crates affected) | k:c40f…12 | red | warm-1 | 4m 21s |
| gates | k:9d22…e0 | green | warm-1 | 1m 02s |
| oracle | k:0a7e…5b | reused | stored | 0s |

Router, deadline 20m, cheapest that finishes in time:

| Backend | Free slots | Finishes in | Price | Chosen |
|---|---|---|---|---|
| warm-1 (your server, hot build dir) | 1 | 6m | $0.000 | yes |
| laptop-ana (your laptop) | 1 | 14m | $0.000 | slower, cold |
| cloud-c7 (spot, 16 vCPU) | 8 | 5m | $0.012/min | not 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.

```
● 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. Example, wi-0412, attempt 1 of 2, prompt fix, to mbp-3 (claude):

- 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.

1. Rung 1 (now): 2 × fix. The failing check, its log and the diff.
2. Rung 2: 1 × stalled. Root cause, the history, a different approach.
3. Rung 3: notify owner. Email, Slack or a webhook. A person takes over.
4. Policy: `.ci/policy.yaml`, or 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.

| Host | Runtime | State | Item |
|---|---|---|---|
| mbp-3 | claude | busy | wi-0412 |
| studio-1 | codex | limited until 14:00, resumes itself | returned to the docket |
| laptop-ana | claude | ready | none |

Example question q-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.

- observe: the facts now: forge, store, backends, agents.
- desire: what must be true: branch file, policy, stack, stamps.
- diff: the actions that close the gap.
- act: idempotent, 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.
