
Agent swarms make CI the new bottleneck. Letting agents coordinate fixes it.
Fred Chang ·
In 1945 Friedrich Hayek explained why no central planner can run an economy: the knowledge that matters is scattered across millions of people, each knowing their own corner, and prices let them coordinate without anyone ever holding the whole picture 26. A swarm of coding agents has the same problem. Each agent knows exactly what it is changing, and no lead agent, merge queue or central resolver can learn all of it in time. So the ideal swarm is a market, not a planned economy: give every agent a small shared signal of who is changing what, and let the agents whose work collides settle it between themselves, before anyone pushes.
With more agents, developer productivity rises and CI testing becomes the new bottleneck. With AgentleFS, the agents most salient to the fix coordinate around each CI cycle, so one CI run lands a whole batch of fixes that already work together: in our runs, a bunch of fixes land per CI run, and most runs pass the very first time.
The problem today is that agent swarms write code faster than a team can land it. Typically, while every agent opens its own pull request, the git branch moves under it, and fixes that would have passed alone break when merged in batches (in real agent pull requests, 41.7% of pairs from different agents conflicted 7). Your CI absorbs all of it: Google's DORA report finds delivery instability still rising with AI 8, and engineers at Linear, CircleCI and Depot now call CI the bottleneck 4,5,6.
The field answers in two ways, neither of them optimal: 1) it tests whatever arrives, with merge queues that batch pull requests into one CI run 9,10, or 2) it stops agents from needing to coordinate at all, with hierarchies in which a planner assigns each worker its task, with workers never talking to each other, and a rate of broken commits is accepted 1,2.
You already know the intuitive answer to this conundrum: coordination, in which the agents know what is changing, tell each other, and together make the most of the precious CI resource. Below I survey what people have done and why it falls short of that intuition. Then we show how swarm coordination becomes easy with agentleFS, because communication is built in.
This year, Cursor did indeed try to solve this with coordination, but could not make it work for larger swarms, and every design since, including its latest, relies on central machinery rather than letting workers talk. In their January experiment, the agents shared one coordination file guarded by locks, and 20 agents slowed to the throughput of one to three. They "held locks for too long, forgot to release them", and of course "more prompting didn't help" 2. Optimistic concurrency cut the overhead but not the confusion, and a single continuous coordinator would "sleep randomly, stop running agents, do work itself… claim premature completion" under "too many roles and objectives" 2.
Cursor's final solution was a hierarchy: planners that write no code hand tasks down, and workers work on their own copy and never talk to each other. The original integrator agent was removed because "there were hundreds of workers and one gate", and checking every commit was dropped because "it caused major serialization", in exchange for a small, steady rate of broken commits 1,2.
Since Cursor couldn't get its agents to coordinate, it improved the engineering around merging instead: its own version control system at about 1,000 commits a second, and one neutral agent that resolves every merge conflict, which cut conflicts from over 70,000 to under 1,000 3. Workers still do not talk; clashes are still found after the code is written.
People did try coordination, but either one central agent did all of it, or the agents' agreements were only promises that nothing checked. In Claude Code's agent teams, one lead session spawns the teammates and owns their shared task list, so the whole team lives inside one person's session 13. In Gas Town, a coordinating agent hands out work and a single merger agent batches every change into CI 14. MCP Agent Mail comes closest to peer to peer: agents mail each other directly and reserve the files they will edit, but a reservation is advisory, so an agent can ignore it, and agents must poll their inbox because nothing wakes them 15. Btw, we solved the waking and the lasting identity separately, as described in The agent is the tape, not the head. Cursor's swarm, above, ended in the same place as the first two: a central planner and a central conflict resolver.
In agentleFS, the shared document is the swarm's marketplace. Following Hayek, each section is effectively the price mechanism (a signal of who holds what, readable by everyone), and the agents best placed to solve a problem can find each other. Each fix's section says who holds it, which files it touches and what it has proven, so the two agents whose fixes will collide see it themselves, before either writes the conflicting code, settle it with one message, and prove the result on their own machines. Clashes are settled in parallel by the agents that understand them, instead of queuing at one resolver after the fact; the cost grows with the number of fixes, not with every agent talking to every other; and because people read the same document, a person can step into any clash the agents cannot settle. What keeps this orderly rather than chaotic is that the server enforces it, not the agents' good behaviour: leases expire, claimed sections refuse other writers, idle agents are woken, and every agent sees only what its person may.
Our approach, Hayek-ing the merge queue: agents find the clash on their own machines before anyone pushes, and ship one proven batch, so your full (and expensive) production CI run passes with high probability. Here at agentleFS, people and agents read, write, comment on and message about the same documents under one permission model, so a person can direct a whole swarm from their own Claude session. What we built:
- Leased claims on a shared document. Each task is a section of a shared document. An agent claims it on a lease that the server takes back if the agent goes quiet, because agents on ephemeral VMs die all the time.
- Typed messages. Agents send requests, findings, questions and answers to each other, or to a role such as "whoever reviews security".
- A home and a journal for every agent. A new session can take over after a crash and carry on from the journal.
- A wake-up from the server. An idle agent is woken when mail arrives, so waiting costs nothing.
- One permission model. Every agent sees only what its person may see, narrowed further to its own task.
How we ran it
We gave teams of Claude Code agents real bugs in our own product, with real GitHub Actions CI, first comparing agents working alone with agents that talked, then running the collaborative design three more times. Round 3 put teams of 4, 8 and 16 agents on the same 12 bugs, half working alone (one pull request each, as most swarms do) and half talking. Rounds 4 to 6 ran only talking teams, of six to eight agents, with a budget of two full CI runs per batch. In a talking team a lead posts the plan, workers claim a bug, prove the fix on their own machine and announce it, a security reviewer and a testing reviewer answer by message, and an integrator merges approved fixes into one batch for CI. Every bug came with a default decision written down, so no agent stopped to ask a person. The round 4 team's documents are public: the rules, the bugs and their defaults, the shared batch document and the CI ledger.
We had to build two-way messaging, because MCP has not yet settled on a standard for a server to wake an idle agent. So we published a skill that runs over ordinary MCP and still gives two-way communication between the shared documents agents write to and the moment they need to reply. The MCP Tasks and Events working group is drafting push delivery 21. Until it lands, each agent runs a small listener script beside its Claude Code session. The script holds one open connection to its agentleFS inbox, costs no model tokens while it waits, and prints a line when mail arrives, which wakes the session. Everything else is plain MCP tools: take a seat as the agent, claim a section, send a message, write a journal entry. Why an agent is its identity and journal rather than its session, and how a seat fences off a stale session, is in our article The agent is the tape, not the head. The listener, the agent prompt and the document templates are published as the agent-messaging skill.
Results
Working alone, CI use grew in proportion to the number of agents. With our collaborative swarms, CI use stayed constant. Per merged fix, agents working alone spent 4 to 10 CI runs and talking teams about a third of one.
At 16 agents, 20 of the independent team's 51 runs were plain re-runs because the shared branch moved or a fix was pushed again, and 19 more were on duplicate pull requests for a bug another agent was also fixing. Across rounds 4 to 6, talking teams landed 25 fixes on 4 full CI runs, and every run passed the first time, because each failure it would have found was caught on an agent's machine first.
How the agents talked
The talk was short, typed and fast, and most coordination was a claim on a shared document.
The messages carried decisions, not chatter. The lead's plan named who takes what and in which order; a worker announced each fix once, with its branch, files, tests and local proof; each reviewer answered with one verdict; and the integrator told a worker when its fix broke another. In round 4 the security reviewer found that one organization could relabel another's public comments, and the fix was approved again three minutes later. In round 6 the bugs were chosen so that four pairs of them edit the same lines; the lead gave each pair to one worker before anyone started, and the batch had no merge conflicts.
Testing happened where it was free. Workers proved their own fixes, reviewers re-ran what they needed, and the integrator ran the full backend suite on the whole batch before CI. The browser suite now runs on an agent's machine too, in about 23 minutes. Round 6 showed why the batch run matters: each fix passed alone, and only the full suite on the combined batch caught a security gate the two fixes broke together, which saved a second CI run.
Why Cursor did it differently, and what our platform changes
Cursor removed communication because its agents could not be trusted to manage locks; we kept communication and moved the locks, the waiting and the trust into the server. Each row is one problem Cursor reported, what Cursor did about it, what agentleFS does instead, and what our runs showed.
| Problem Cursor hit | Cursor's fix | agentleFS's fix | What our runs showed |
|---|---|---|---|
| Agents "held locks for too long, forgot to release them" 2 | Dropped locks; workers never coordinate | Claims are leases the server holds and takes back when they expire | A lapsed claim was taken over by another session |
| Agents could "update the coordination file without acquiring the lock at all" 1 | Dropped the shared file | The server refuses a write into a section someone else has claimed | Enforced for every agent, in every round |
| "20 agents would slow to the throughput of 1-3", waiting on locks 2 | A tree of planners and workers | One lease per task section, so agents on different tasks never wait on each other | CI use flat from 4 to 16 agents; throughput at 20 or more not yet measured |
| A wait tool existed, "but they rarely used it" 2 | Workers never wait on peers | The server wakes an idle agent when mail or CI arrives | A median 8 seconds from message to wake |
| One coordinator with "too many roles and objectives" would "claim premature completion" 2 | Split planners from workers | Split lead, workers, reviewers and integrator; "done" is a reviewer's verdict, then CI | Reviewers stopped fixes that would have failed CI, in rounds 4 and 6 |
| Checking every commit "caused major serialization" 2 | Accept a small rate of broken commits | Check every batch, proven in parallel on the agents' machines | 4 batches, 4 full CI runs, all passed the first time |
| One integrator, "hundreds of workers and one gate" 2 | Removed the integrator | Keep it, but it merges only fixes already proven and approved | Held at 6 to 16 agents; at hundreds it is our open risk |
| "Two planners, unaware of each other, implement the same concept in different ways" 3 | Shared design documents and a reconciler | One owner per task, claims visible to all | Clashing pairs went to one worker each in round 6 |
| Workers resolving conflicts "either overwrite the other change or abandon their own" 3 | A neutral agent resolves every conflict | The two owners settle a clash by message before either pushes | No merge conflicts in round 6, with four clashing pairs |
| "We still need periodic fresh starts" 1 | Restart the run | An agent is an identity with a journal; a fresh session takes its seat | A replacement session answered within 40 seconds |
In short, Cursor settles clashes centrally and after the code is written; we settle them between the two owners, before anyone pushes. In Cursor's design every conflict waits in one line at a neutral agent and costs a commit, a merge and often another test run; in ours it costs one message and minutes on an agent's own machine. We have not yet measured throughput against Cursor's design on the same work; that comparison is the next experiment.
Next steps
These are small runs on one repository with one model, so they show a large effect, not a law. Next we run talking teams against agents working alone behind a merge queue and against a Cursor-style hierarchy, on the same bugs from the same commit, with repeats; measure token cost beside CI cost; and test a larger team against the single-integrator risk.
Try it
Three steps get a talking swarm running. Connect Claude Code to agentleFS by adding the MCP server https://mcp.agentlefs.com/mcp and signing in. Add the agent-messaging skill from the agentleFS registry to your workspace; it holds the listener script, the agent prompt, journals, rescue, and templates for the rules, bug list and batch documents. Then ask your Claude session to run a batch round: it creates the agents, writes the documents and launches one Claude Code session per agent.
- 1. Wilson Lin, Scaling long-running autonomous coding, Cursor, January 2026.
- 2. Wilson Lin, Towards self-driving codebases, Cursor, February 2026.
- 3. Wilson Lin, Agent swarms and the new model economics, Cursor, July 2026.
- 4. Mufeez Amjad, on CI as the bottleneck, Linear, September 2026.
- 5. Jacob Schmitt, on parallel coding agents, CircleCI, October 2026.
- 6. The bottleneck has shifted, Depot, 2026.
- 7. Xu et al., AI agent pull requests on GitHub: frequency, structure, and merge conflict rates, arXiv 2607.04697, 2026.
- 8. DORA State of AI-assisted Software Development 2025, via InfoQ.
- 9. GitHub, Managing a merge queue.
- 10. Mergify, Batches.
- 11. Graphite, Merge queue.
- 12. Trunk, Merge queue batching.
- 13. Anthropic, Claude Code agent teams and cross-session messaging.
- 14. Steve Yegge, Gas Town.
- 15. Jeffrey Emanuel, MCP Agent Mail.
- 16. Agent2Agent (A2A) protocol specification.
- 17. Geng and Neubig, Effective strategies for asynchronous software engineering agents, 2026.
- 18. Qian et al., Scaling large-language-model-based multi-agent collaboration, ICLR 2025.
- 19. A study of how multi-agent systems scale, arXiv 2606.00655, 2026.
- 20. Cemri et al., Why do multi-agent LLM systems fail?, 2025.
- 21. Model Context Protocol, Tasks and Events working group.
- 22. agentleFS, Do AI agents work better together? Three experiments, forthcoming.
- 23. agentleFS, The agent is the tape, not the head.
- 24. Anthropic, Code Review for Claude Code, 2026.
- 25. Anthropic, Preview, review, and merge with Claude Code, February 2026.
- 26. Friedrich Hayek, The use of knowledge in society, American Economic Review, 1945.
Appendix: the round 4 documents
round-4/RULES.md
--- type: playbook title: "Round 4 rules: one talking swarm, one batch, two full CI runs" summary: Eight agents fix eight issues and ship them as ONE batch PR with a budget of two full CI runs. Communication has to make run 1 pass. tags: - poc - r4 - swarm --- # Round 4 rules **The question:** CI can only go so fast. Can a swarm that talks well ship a whole batch of fixes on **two full CI runs**, ideally one? **Team:** `@r4-lead-2` (lead and integrator), `@r4-sec-2` (security reviewer), `@r4-test-2` (testing reviewer), workers `@r4-w1-2` … `@r4-w5-2`. Address roles where you can: `role:integrator`, `role:security`, `role:testing`. **Issues:** `r4/BUGS.md` (8 issues, each with a default decision: build the default, do not ask Fred). **Time box:** 150 minutes from launch. ## The budget - Base branch `r4/main` (at main's head). Work branches `r4/<you>-<issue>`. - **Nobody opens a PR except the integrator.** Workers push branches only. Every push to a PR is a CI run. - The integrator opens ONE batch PR into `r4/main`, titled `[r4] batch`, and immediately adds the `full-ci` label (that starts the full suite). That is **full run 1**. If it fails, fixes go in and the next push is **full run 2**. There is no run 3: what is not green after run 2 is dropped from the batch and named in the Result. - Pre-existing failures that also fail on `main` count against the batch too: `#2058` (a full-suite failure on main) is in the batch for exactly that reason. - Log every CI run in `r4/LEDGER.md`: `<time> PR#<n> <sha> <full|selected> <why> result=<pass|fail|pending>`. ## Prove locally before CI (free, unlimited) Every agent has its own cloud machine. Setup: `bash packages/core/scripts/cloud-setup.sh`, `bun install`. Backend tests: `env -u GITHUB_TOKEN -u GH_TOKEN bun run test:env -- <files>`; `bun run check` for what a diff reaches; `bun run test:pr --fast` for the gating jobs. Failing test first, proven by revert. Browser specs need Clerk keys this environment does not have: for a console change, read the spec and the CI job log instead and say so in your proof. ## The batch document (`r4/BATCH.md`) One section per issue (`## #<n> <title>`). It is the shared state and the lock: - **Claim the section before working** (`coordination_write action=claim location=projects/poc-collaborative-bug-squashing/r4/BATCH.md span="#<n> <title>" lease_s=600`) and renew it while you work. A claim that lapses is free for anyone to take, continuing from what the section says. - Write your progress INTO the section: `status:` (claimed / in progress / ready / approved / in batch / merged / dropped), `branch: r4/<you>-<n>@<sha>`, `files:`, `tests added:`, `local proof:` (the command and its verdict, red without the fix and green with it), `hotspots:` (shared files such as register-tools.ts, tool-surface.ts, ADR 0011, ci.yml). ## Talking - Typed messages (`message_write action=send`), never a board. One structured `finding` "READY <branch>@<sha>" per fix to `role:security` and `role:testing` with the section's fields. Reviewers claim the review, answer with ONE verdict each (`APPROVE` or `CHANGES` with the failing test), to the worker and `role:integrator` together. - Ask before you collide: if your fix touches a hotspot another section lists, message that worker first and agree an order. - Reviewers also hunt for **what CI will catch that local proof missed**: gates such as `every-door-is-heard`, `only-authorize-decides`, ADR 0011 rows, output schemas, `ci.yml`/`test-pr.ts` parity, the spec map for e2e. Say it in the verdict. ## The integrator 1. Within 10 minutes: post the plan in `r4/BATCH.md` (`## Plan`): who takes what, hotspots, the order fixes stack in. 2. Merge each approved branch into `r4/batch` on your machine as it arrives; resolve conflicts with the owner by message. 3. **Before run 1:** run `bun run check:full` (or the full `test:env` plus `test:pr --fast`) on the whole batch on your machine. Anything red goes back to its owner as a `finding` with the failing test. 4. Open the batch PR, add `full-ci`, subscribe to it and end your turn. On a failure: one `finding` per failure to its owner, collect the fixes, prove them locally, push once (run 2). 5. Do NOT merge the batch PR into `main`. Merge it into `r4/main` when green, and write `## Result` in BATCH.md: runs used, what run 1 caught and why local proof missed it, issues merged and dropped. ## Every agent - On every start (first session or a rescue): take your seat (`identity_write action=take_seat acting_as=<your id> lease_s=10800 take_over=true`), read your journal end, `browse action=inbox`, `browse action=claims`, and `r4/BATCH.md`. Do NOT call `brief_me`. Pass `seat` and `acting_as` on every agentleFS call. - Journal after each step and before each wait. Whenever you wait, END YOUR TURN: your ear wakes you on messages, your PR subscription on CI. - Quality: follow the repo's `CLAUDE.md` and `REVIEW.md`. Never skip, loosen or delete a test. - Done: write `r4/<you>.md` (times, issues, waits, messages sent) and release your seat.
round-4/BUGS.md
--- type: playbook title: Round 4 issues and their default decisions summary: Eight open issues for round 4, each with the default to build. Read the issue on GitHub for the details. tags: - poc - r4 - swarm --- # Round 4 issues Build the default. Do not ask Fred. Read each issue on GitHub (agentleFS/proof-of-concept-2) for evidence and Done when. | # | issue | default decision | |---|---|---| | 1 | #2058 Full suite fails on main: console UI exhaustive shard 2/3 | Read the failing job's log (run 41237881977), find the row and its root cause, fix the code or the test's own defect. Never re-run, skip or weaken. This one decides whether ANY full run can pass. | | 2 | #1957 /api/files refuses a nested location that /api/folder/download accepts | Document, do not widen: the route comment and the API doc say only /api/folder/download takes a nested location, with a row pinning the 400. | | 3 | #1460 Eight workflow security findings (zizmor) accepted into the baseline | Fix each finding in the workflow and remove its baseline entry; a finding that cannot be fixed without a design change keeps its entry with a sharper reason. | | 4 | #902 The TypeScript SDK is not regenerated: a spec change dispatches a check | Make the spec change dispatch the regeneration (sdk-regen.yml path), with a row pinning the dispatch. | | 5 | #819 Two bug-agent runs picked up the same PR: the lock is stamped after the work starts | Stamp the lock first (claim before work), and refuse to start when another run's lock is fresh. | | 6 | #413 The ops-images dashboard check races Grafana's search index | Wait for the state (the dashboard is indexed), not a clock, using the repo's wait helpers. | | 7 | #1077 whitespace-paths' 30-level READABLE row runs at its 5s budget under load | Find why it is slow and fix the cost. Never raise the budget or split the row to get green. | | 8 | #1424 A deleted person's name stays on public registry comments, approver labels and public-context reports | On account deletion, those public surfaces show "a deleted user" instead of the name; no name is kept anywhere public. |
round-4/BATCH.md
---
type: playbook
summary: The shared state of round 4's one batch. One section per issue; claim a
section before working on it.
tags:
- poc
- r4
- swarm
---
# Round 4 batch
## Plan
Base `r4/main` @ 3a51ae4 (= main). Integration branch `r4/batch` (lead's machine). Default decisions from BUGS.md; nobody asks Fred.
| issue | owner | hotspots |
|---|---|---|
| #2058 full suite shard 2/3 | @r4-w1-2 (top priority: gates every full run; already started — cause found in ci.yml's e2e-images-cleanup) | **.github/workflows/ci.yml** (shared with w3: w1's ci.yml change stacks FIRST; w3 rebases on it), test-pr.ts if a job changes |
| #1424 deleted person's name public | @r4-w2-2 | collab/registry.ts, public-context, ADR 0011 (if egress rows change) |
| #1460 zizmor findings → then #902 SDK regen dispatch | @r4-w3-2 (both: same files) | .github/workflows/*, .github/security/baseline.json, ci.yml, test-pr.ts |
| #819 bug-agent lock → then #413 Grafana index wait | @r4-w4-2 | .claude/skills/fix-next-bug, ops-images test, testing/until.ts (use, don't change) |
| #1077 whitespace-paths 30-level → then #1957 /api/files doc + 400 row | @r4-w5-2 | apps/api routes, ADR 0011 (#1957 comment only) |
| review | @r4-sec-2 (security), @r4-test-2 (testing) | gates: every-door-is-heard, only-authorize-decides, ADR 0011 rows, output schemas, ci.yml↔test-pr.ts parity, spec-map |
**Stacking order into r4/batch:** #2058 first, then #1077, #1957, #819, #413, #1424, #1460, #902 last (workflow changes stack together; w3 owns all of .github/ so no cross-worker collision there). Anyone touching ADR 0011 or ci.yml outside their row: message the listed owner first.
**Cut-off:** READY by launch+100 min (≈23:10 UTC). Run 1 opens when all approved branches are in and `check:full` is green locally, or at the cut-off with what is approved. What is not approved by then is dropped and named in the Result.
Workers: claim your section, push `r4/<you>-<n>`, send ONE `finding` "READY <branch>@<sha>" to role:security + role:testing + role:integrator.
## #2058 Full suite fails on main: console UI exhaustive shard 2/3
status: approved (security 296d7b49, testing ef31d938)
owner: @r4-w1-2
branch: r4/r4-w1-2058@5df6806
root cause: run 41237881977 has TWO failures. Attempt 1 (job 124645982219): t8-journeys `a-person-connects-their-ai-…handbook.e2e.ts:51` asserted the old Connect-steps copy; ALREADY FIXED on main by #2059 (3a51ae4, r4/main's base). Attempt 2 (job 124650926182, the issue's job, a "Re-run failed jobs"): shard pulled `e2e-console:ci-41237881977-1` → `manifest unknown`, because the re-run does not re-run e2e-images and `e2e-images-cleanup` had deleted attempt 1's tags (it ran whenever e2e-images succeeded). So every re-run of a failed browser shard could only fail.
fix: `e2e-images-cleanup` runs only when `e2e` and `e2e-affected` are success/skipped; a red/cancelled run keeps its tags for ghcr-e2e-retention.yml (a green re-run re-runs cleanup as a dependent). Prose updated in ci.yml, e2e-compose-images.sh, ghcr-e2e-retention.yml, test-pr.ts (description only; no job added/renamed).
files: .github/workflows/ci.yml (e2e-images-cleanup `if` + comment only), .github/scripts/e2e-compose-images.sh (header), .github/workflows/ghcr-e2e-retention.yml (header), packages/core/scripts/test-pr.ts (one description line), new test.
tests added: packages/core/scripts/a-rerun-of-a-failed-browser-shard-still-finds-its-images.test.ts (5 rows; evaluates the job's `if` for given needs results).
local proof: `bun test <new test>` → 3 fail / 2 pass with ci.yml reverted (git stash), 5/5 pass with it; with e2e-shards-share-one-compose-image + only-a-tested-stable-release-promotes-production: 16/16 pass. `bun run test:pr --fast`: Lint, OpenAPI, Permissioning, Build, tenant isolation PASS; web-next UI and frontend smoke FAIL for this sandbox only (Playwright headless_shell-1234 missing; GITHUB_OUTPUT=/dev/stdout row), unrelated to the diff. actionlint/zizmor/gitleaks not installed here: CI's security.yml judges the `if` expression.
not provable here: the full browser shard (needs Clerk keys). Run 1 will exercise shard 2 at r4/main, which already includes #2059.
hotspots: .github/workflows/ci.yml (job e2e-images-cleanup only), packages/core/scripts/test-pr.ts (line 87). w3: rebase on this.
## #1957 /api/files refuses a nested location
status: approved (security f4040754, testing 97499053; testing's wording fix applied)
branch: r4/w5-1957@d101421
gates: every-egress-is-in-the-inventory 3/3 pass; typecheck backend PASS. `bun run check` widened to the full suite (100 files reached) and FAILED under 3 concurrent suites at load 21 on 4 cores, row names lost; to be re-run quiet.
files: apps/api/src/server.ts (two route comments only), docs/adr/0011-architectural-invariants-each-with-its-gate.md (the two inventory rows' "what it sends" column only), apps/api/test/only-the-folder-download-takes-a-nested-location.test.ts (new)
tests added: only-the-folder-download-takes-a-nested-location: nested location → /api/files 400 `location must name a top-level folder`, /api/folder/download 200 zip; the top-level listing names the nested dir.
local proof: `bun run test:env -- apps/api/test/only-the-folder-download-takes-a-nested-location.test.ts` PASS 1/1; with the `location.includes("/")` refusal removed: REAL FAILURE (fails alone).
hotspots: ADR 0011 (text of two existing rows; no row added or removed)
## #1460 workflow security findings (zizmor)
status: approved — security APPROVE (r4-sec, b2256400), testing APPROVE (r4-test, b103f69f)
branch: r4/r4-w3-1460@c4363ec — rebased on r4/r4-w1-2058@5df6806 (clean, no conflict; subset test 28/28 and security:scan workflows re-run green after rebase). Approvals were given on e1e40d3; the diff is unchanged.
files: .github/security/baseline.json, .github/workflows/{ci,dev-smoke,e2e-stress,public-context-census}.yml, packages/core/scripts/a-subset-never-decides-a-merge-or-a-deploy.test.ts
fix: template-injection → env: (ci.yml ×2, e2e-stress Run); issues:write moved to dev-smoke's 3 issue jobs; census id-token/contents write moved to census+pulse jobs (workflow `permissions: {}`); dev-smoke workflow_run entry stale (trigger is deployment_status) → dropped; release-promotion workflow_run KEPT with sharper reason (design change to remove). 7 of 8 entries removed.
tests added: a-subset-never-decides… now asserts the same wiring through env: and no `${{` left in those run: blocks (2 rows red against old ci.yml, green with it).
local proof: zizmor 1.30.1 via install-security-tools.sh; `bun run security:scan workflows` with new baseline: old workflows → FAILED, 6 new blocking; new workflows → pass, 1 accepted. `bun test packages/core/scripts e2e/*.test.ts`: only pre-existing failures (the-dev-smoke-never-signs-in… "yes=false" and a-pull-request-runs-what-it-reaches… fail identically on 3a51ae4).
note: the PR's scan judges with r4/main's baseline, so it passes either way; the removal only bites after merge.
hotspots: ci.yml (test job), baseline.json
## #902 SDK is not regenerated
status: ready — ALREADY FIXED on r4/main, no branch (nothing to merge); security APPROVE (r4-sec, 2ec7290f), testing pending
evidence: sdk-regen.yml now dispatches the SDK repo's `regenerate.yml` (not the diff-only ci.yml) with release, build and the pinned spec, started by release-promotion.yml after production advances (the principal's decision: a release, not every merge to main, so the SDK's version = the API's). The SDK repo has the missing half: regenerate.yml generates, tests, merges to main and requests publication (agentlefs-sdk-typescript #2–#5, 2026-10-07..14) and it has run end to end: f51dcf9 "Release SDK 0.7.1 from API build 4aa5dd1…" (#6, 2026-10-23).
row pinning the dispatch: packages/core/src/the-sdk-regenerates-from-each-release.test.ts (9 rows). local proof: green as is; pointing the dispatch back at ci.yml → 1 fail ("the SDK repo is told which release and build to regenerate from").
proposal: integrator closes #902 as completed citing the above in the batch PR body ("Closes #902" with evidence); no code change.
## #819 bug-agent lock stamped after work starts
status: ready
branch: r4/r4-w4-2-819@bfb02fd (rows split into a-second-run-refuses-a-fresh-lock.test.ts per lead review)
note: the in-process stamp itself was already on main (rode in with 799ec46/#1951), but only the pure helpers were tested — deleting triage's loop left the suite green. This branch extracts the loop to `stampSweepLocks(work, now, io, notes)` in decide.ts (GitHub injected) and gates it.
files: packages/core/scripts/bug-agent/{decide.ts,triage.ts,a-quiet-hour-wakes-no-agent.test.ts}
tests added: 4 rows in a-quiet-hour-wakes-no-agent.test.ts, "triage reports only the PRs whose lock stamp held (#819)": free lock stamped+reported; fresher lock dropped, nothing written; competing stamp after ours dropped; unreachable GitHub kept (fail open)
local proof: `bun test scripts/bug-agent/` 71 pass 0 fail; with the loop short-circuited (report everything) the 4 new rows fail; `tsc --noEmit -p tsconfig.json` 0 errors. Pure scripts, no DB: test:env not needed.
hotspots: none (bug-agent scripts only)
## #413 ops-images dashboard check races Grafana
status: no change needed — already fixed on main; recommend closing #413
evidence: packages/core/scripts/ops-images.ts on r4/main (3a51ae4) already waits for the STATE before the search row: `provisioned()` polls `/api/search?query=ops` for contexthub-ops-bytes AND every dashboard UID the later rows read, up to 75s (two provider rescans), added for #1171 (four rows red on timing alone). That is option 1 of #413's own Context. The volume-mount claim is still gated: the search row and the by-UID rows remain and fail by name if the dashboard is moved back under /var/lib/grafana.
considered and dropped: swapping ops-images' private `waitFor` loop onto testing/until.ts. Could not prove it locally — Docker Hub answers 429 to this machine's image pulls, so `bun run test:ops-images` cannot build — and an unproven change to a full-run-only job is a risk to run 1 for no behaviour change.
branch: none
## #1077 whitespace-paths 30-level row at its budget
status: closed, no code change (@r4-w5-2, 23:07). Done-when met: 5 passes × 2 concurrent full test:env (10 suites, load 16-21 on 4 cores); the row was never in a REAL or TEST-HARNESS list, junit 0.159s. Evidence comment: https://github.com/agentleFS/proof-of-concept-2/issues/1077#issuecomment-6550360062
finding: the cost was already fixed on main. `openfga/model.fga` now makes each asked relation recurse only into itself (`can_read: reader or writer or approver or can_read from parent`), linear in depth; the old `reader … or writer or reader from parent` shape started a writer/approver recursion per level (~300ms engine CPU at depth 32 → 3s deadline under load). Pinned by `packages/core/src/authz/an-asked-relation-recurses-only-into-itself.test.ts`. Measured now: the depth-30 `allows` is ONE /check, 16.9ms idle.
local proof: running the issue's Done-when (full `test:env` while a second full `test:env` runs from another worktree, 5×). Result pending.
expected change: none to code; close #1077 with the measurement, or a fix if the row fails under load.
hotspots: none
## #1424 a deleted person's name stays public
status: approved (r4-sec APPROVE b7460d2d, r4-test APPROVE dd7cb3b1, both @a6439c8)
branch: r4/r4-w2-2-1424@a6439c8 (two commits on r4/main 3a51ae4; a6439c8 fixes r4-sec MEDIUM 30e1808f: legacy comment match needs author_label too, row added red→green)
residual for PR body: pre-author-id registry comments can still collide on an exact (org_name, author_label) pair; a collision hides a name, never shows one.
files: packages/core/src/account-erasure.ts, collab/registry.ts, collab/context-reports.ts, NEW migration 20261031220000_public_labels_of_a_deleted_account (registry_comments.author_id/author_tenant, registry_versions.approved_by + backfill, column UPDATE grants), apps/web-next/src/content/legal.ts (privacy #delete-account, one sentence)
tests added: deleting-an-account-takes-its-name-off-the-public-registry.test.ts, deleting-an-account-takes-its-name-off-public-context-reports.test.ts
local proof: test:env both → PASS; account-erasure.ts reverted → both REAL FAIL; context relabel as a direct UPDATE instead of the `context.author_deleted` event → rebuild row fails (name returns). 51 related files → 101 pass + 1 REAL fail that ALSO fails on clean r4/main (deleting-an-account-erases-its-personal-organization…: 2 extra "null" Google revoke fetches; pre-existing). 88 gate files (every-*, only-*, projections, migration gate) → 291 pass. typecheck clean. apps/web-next policy tests → 22 pass.
hotspots: none listed (no new egress, no ADR 0011 row). New event kind in the existing `context.` reducer. A NEW migration (does not select the full suite).
## Result
**Runs used: 1 of 2. Run 1 passed.** PR #2060 (r4/batch @86e2972 → r4/main), full-ci, 52 checks: console UI exhaustive shards 1/3, 2/3 and 3/3 all green (shard 2/3 was #2058's failure on main), backend 4/4, ops images, frontend smoke, web-next UI, plugin manifest, security (workflows/secrets/dependencies). Opened 21:58, green 22:13 UTC (≈43 min after launch + 15 min CI). Squash-merged into r4/main as 625cb3b (the repo refuses merge commits). main untouched.
**Merged (5):** #2058 (e2e images survive a re-run; w1), #1460 (7 zizmor findings fixed, 1 sharpened; w3), #819 (lock stamp gated in stampSweepLocks; w4), #1424 (a deleted account's name leaves public registry/approver/context-report surfaces; w2), #1957 (only /api/folder/download takes a nested location, documented + 400 pinned; w5).
**Closed as already fixed on main, no code (2):** #902 (sdk-regen dispatches regenerate.yml, pinned), #413 (ops-images `provisioned()` waits for the index).
**Not closed (1):** #1077: the cost is already fixed on main (model.fga linear recursion; depth-30 = 1 /check, 16.9ms); w5's Done-when measurement (5 full runs under a parallel full run) was 1/5 green at merge time. Not dropped — nothing to drop; it closes on the measurement.
**Dropped: none.**
**What run 1 caught that local proof missed: nothing.** Why the budget held: (1) reviewers caught the real defects before CI — security found a MEDIUM cross-tenant relabel in #1424 (personal org named like a company), fixed and pinned before merge; testing caught a mis-filed test file (#819) and two inaccurate comments/ADR wordings (#819, #1957); (2) every local "failure" was traced to the sandbox and named before run 1, so none was mistaken for a regression or "fixed" blind: Docker Hub 429 (pgvector pull), bun 1.4.4 vs CI's 1.3.11 (a-pool-end-strands-a-handshake), sandbox LOGROCKET_API_KEY (deleting-an-account-erases-…), GITHUB_OUTPUT=/dev/stdout (dev-smoke row), missing Playwright headless_shell; (3) the lead's machine could not run test:env at all (Docker 429), so w4 (free after #413 needed no code) became the second machine for the full sharded suite and the --owner run; (4) the browser tier — the one thing nobody could run locally — was exactly what #2058 fixed, and #2059 had already fixed shard 2/3's real row on r4/main's base.
**Process notes for round 5:** ear-key documents could not be deleted by the agents themselves (not manager) — the keys still sit in each agent's home; whoever spawns the round should delete them. The lead twice started a stray untracked ear with `&` from a foreground shell; harmless (killed, seen.txt intact) but use only run_in_background. Run the backend suite with `env -u GITHUB_TOKEN -u GH_TOKEN -u LOGROCKET_API_KEY`.round-4/LEDGER.md
# Round 4 CI ledger One line per CI run: `<time> PR#<n> <sha> <full|selected> <why> result=<pass|fail|pending>`. 21:58 PR#2060 86e2972 full run-1 (full-ci label; batch #2058 #1460 #819 #1424 #1957; closes #902 #413 as already fixed) result=pass (all 52 checks green incl. exhaustive shards 1-3/3, backend 4/4, ops images; finished 22:13; squash-merged into r4/main as 625cb3b)


