For months I had the same problem with coding agents: the session drops a pile of changes, and by review time I no longer know what it touched or why.
The agent writes, the agent validates, the agent congratulates itself. The human signs blind.
And when the agent did write tests, it got worse — green tests that proved nothing. Mocks of the very code under test, assertions on private fields, snapshots of incidental structure.
Suite green, confidence zero.
So I built agents-concerto: a multi-agent orchestrator on top of Claude Code. The name is literal: there's a conductor and there are players. The conductor never touches an instrument. And the merge still belongs to the human.
The repo isn't an application. It's an orchestration brain: one CLAUDE.md, four agents in Markdown, four Bash scripts. Its product is open PRs ready for human review.
Seven rules that don't bend
-
Config-driven, not conditional. The number of target repos is just the length of a list in
config.md. One repo or fifteen take the same code path. -
Agents by pipeline function, not by technology. One generic implementer serves any stack. Never
backend-dev/frontend-dev. - Two-party authority. No agent that writes code may approve or merge.
- Worktree isolation. Every agent that touches code runs in its own git worktree, never your checkout.
- Model by complexity, not by role. One implementer; the model is picked per invocation from a complexity tier.
- Stop at "PR ready". No auto-merge. Merge permissions are never even requested.
- Cycle cap with an escape hatch. A finite cap on review→fix cycles, and a human exit once it's hit.
Rule 3 is the one you feel. When the author isn't the approver, the verdict means something.
The acceptance criterion is the unit of work
This is the part that changed the system most: a criterion isn't a note for humans, it's a test spec.
Shaping (/shape, or inline in /run) refuses to emit a wishlist. Criteria come out in Given-When-Then — given <context>, when <action>, then <observable result> — inside a tiered contract where ## Task and ## Acceptance criteria are mandatory and ## Scope / ## Non-goals are optional, included only when they add signal.
A Given-When-Then criterion already names a context, a trigger, and an observable result. That's a test. And because the "then" must be observable, the criterion is hard to satisfy by asserting on internals.
So the criterion travels the pipeline: written at shaping, turned into a test at implementation, verified as a criterion→test mapping at review.
The four players
-
orchestrator(opus) — decomposes the task, writes the plan, dispatches, runs the fix loop. Never writes application code. Not one line, not a "quick fix". -
classifier(sonnet, read-only) — returns one tier and nothing else.complex→ opus for gnarly logic, concurrency, or sensitive paths;trivial|standard→ sonnet. When torn, pick higher. -
implementer— has nomodel:in its frontmatter; it's chosen per invocation from the tier. One agent, different muscle per piece. Works only in its worktree, TDD where tests exist, and Tidy First: a structural commit (refactors, no behavior change) then a behavioral one, never mixed. -
reviewer(opus) — toolset isRead, Grep, Glob, Bash: noWrite, noEdit. It cannot change the code it reviews. It describes fixes; it never applies them.
Outside-in tests
Every criterion becomes at least one test asserting observable behavior — outputs, rendered UI, persisted state, HTTP responses, emitted events. Never internals: no private fields, no mocks of internal helpers, no asserting a function was called. Tests are driven from the public entry point, named from the user's view (it("shows an error when the email already exists")), and only genuinely external dependencies get mocked — network, clock, third parties. Never the code under test.
It all compresses into one rule:
If a behavior-preserving refactor breaks the test, the test is wrong.
That's why Tidy First and BDD belong together. The structural commit, by definition, doesn't change behavior — so if a test breaks there, either the "refactor" wasn't one, or the test was coupled to internals. Every PR proves both at once.
The reviewer's gates
Correctness against the criteria, tests green, Tidy First discipline (a mixed commit is NEEDS_FIXES, named), and the BDD gate: a criterion with no covering test, or a test asserting on internals, blocks the PR.
That last gate has a qualifier I think is the most important decision in it: it applies only to tests added or modified in the diff. Pre-existing tests the PR leaves alone aren't judged. Without that, a three-line change gets blocked demanding a suite rewrite — or the implementer tries to fix the old tests and scope creep eats the run. The standard applies going forward; old debt gets paid when you actually touch it.
The verdict is a consolidated comment citing path:line plus structured output. And CLEAN means "ready for a human to review", not "approved". The reviewer never formally approves. It can't.
The flow
Task ──► /run ──► Step 0: shape it inline if it's vague
▼
Step 1 load config + open run log (RUN_ID)
Step 2 read the task (per task_source)
Step 3 scope: which repos? (filter over repos)
Step 4 plan.md: sub-tasks + Blocked by
▼
Step 5 dependency waves (sub-tasks in parallel):
5a classifier ──► tier → model (sonnet|opus)
5b worktree-create.sh (branch off base_branch)
5c implementer (tier's model) ─ TDD + Tidy First
▼ opens PR
5d reviewer ──► CLEAN | NEEDS_FIXES (path:line)
5e fix loop ≤ max_fix_cycles
CLEAN ──────────────► ready for human review
cap reached ────────► ready-for-human (escalate)
5f worktree-cleanup.sh (branch and PR untouched)
▼
Step 6 run-log summary + notify + STOP
The human reviews the PRs and merges.
Sub-tasks don't run in plan order — a wave is everything currently unblocked, and a wave runs in parallel, each sub-task in its own worktree.
Because nothing auto-merges, there's no conflict worker and none is needed: two branches touching the same file only collide at merge time, and the human picks the order.
Two config fields stay deliberately orthogonal here: task_source is where a task is read from (none, github, gitlab, linear, jira), while each repo's own host is where its PR gets opened. So a single run can read from Linear and open PRs on GitHub and GitLab at the same time.
Boundaries, enforced
The invariants don't live only in prompts. .claude/settings.json denies them at the tool level:
gh pr merge ← never merge
gh pr review ← it drags --approve along
git merge ← never merge locally
git push --force ← never rewrite pushed history
A denied command is a deliberate boundary, not an obstacle to route around. An agent that hits one escalates the sub-task as ready-for-human and the run continues.
Worth knowing: an agent team costs roughly 4–6× a single Claude Code session. Every run writes a summary with that reminder plus PR, cycle, and escalation counts.
Trying it
claude plugin marketplace add moruno21/agents-concerto
claude plugin install agents-concerto@moruno-plugins
/agents-concerto:setup
/agents-concerto:run <task description>
What building it taught me
Most of the value isn't in the agent that writes code — it's in the boundaries around it. The worktree. The reviewer with no write access. The cycle cap. The deny rules.
And with tests, the boundary isn't a permission, it's an entry point. An agent told "write tests" writes tests coupled to what it just wrote, because that's what's in front of it.
An agent told "exercise this through its public interface and assert what a user observes" can't. The fix wasn't banning mocks — it was moving where you stand to look.
The agents play. The human holds the baton.
Top comments (0)