Most people hit the same ceiling the moment they run agents on more than one repo: the models are fine, but you are now the scheduler for ten terminals.
Most people hit the same ceiling the moment they run agents on more than one repo: the models are fine, but you are now the scheduler for ten terminals.
This tutorial shows how to use Maxxwell to run coding agents across many small repos and large monorepos, with concrete patterns for dependency mapping and cross-repo coordination.
If you want the bigger picture of what “agent-native development” looks like, see the pillar piece Agent-native development: a working definition; this one is the hands-on wiring guide.
You’ll get more out of this if you already:
Maxxwell basics you need to know:
claude, cursor-agent). Maxxwell doesn’t wrap or replace them.working, idle, waiting on you, blocked, needs sign-in, done, dead, not heard from, plus possibly stalled overlays.The rest of this tutorial is how to wire that across repos.
You need to decide where coordination lives.
Pattern: each repo has its own worker(s), and you run a control seat over them.
Works well when:
Typical stack:
Pattern: everything is in one repo, but you isolate work by worktree + path.
This mirrors Claude Code’s guidance for large codebases:
CLAUDE.md / config files nested per directory to keep agents scopedTypical stack:
Both patterns rely on the same primitives: isolated worktrees, a visible control plane, and a human being the last click before anything lands.
This section assumes:
~/dev/service-a~/dev/service-b~/dev/shared-libCreate a small manifest that the orchestrator and you both treat as ground truth.
~/dev/agent-fleet/repos.yml:
repos:
- name: service-a
path: ~/dev/service-a
kind: service
depends_on: [shared-lib]
- name: service-b
path: ~/dev/service-b
kind: service
depends_on: [shared-lib]
- name: shared-lib
path: ~/dev/shared-lib
kind: library
depends_on: []
This is intentionally simple. The orchestrator doesn’t parse it on its own, but you will:
From Maxxwell’s CLI (or the in-app terminal), start one worker per repo.
Example with a Claude Code CLI placeholder claude-code:
# service A worker
cd ~/dev/service-a
claude-code # or your actual agent command
# service B worker
cd ~/dev/service-b
claude-code
# shared lib worker
cd ~/dev/shared-lib
claude-code
Maxxwell lists three sessions, each with a state. Name them clearly in the UI (e.g. svc-a/feature-x, svc-b/bug-123, shared-lib/refactor).
In Maxxwell, start a new agent session and brief it as the orchestrator.
Example initial prompt (edit for your stack):
You are an orchestration agent managing several coding agents that each work in a different git repository.
Repos and dependencies:
- service-a (~/dev/service-a) depends on shared-lib
- service-b (~/dev/service-b) depends on shared-lib
- shared-lib (~/dev/shared-lib) has no dependencies
Your job:
- Plan work that may span multiple repos.
- Decide which worker should do each task.
- Draft clear, copy-pastable instructions TO A SPECIFIC WORKER.
- Track what landed (merged) vs what is waiting on reviews or my decisions.
Rules:
- Never run git or shell commands yourself; draft them for ME to run.
- When cross-repo changes are needed, plan them in dependency order.
- When unsure about repo state, ask me which branch/commit we are on.
Now you talk to this orchestrator instead of juggling three agent chats.
Say you need to change shared-lib and then update both services.
We need to change the Foo API in shared-lib and update both services to call the new API.
Assume feature branches per repo and that I will run all git commands.
Plan the sequence and draft instructions for each worker.shared-lib worker: “Refactor Foo API, add tests, expose a backward-compatible shim.”service-a worker: “Update callers, adjust tests, and note dependency on shared-lib@feature/foo-api.”working → waiting on you → done), you can ask the orchestrator:Summarize the status of the Foo API rollout across repos. What landed, what is waiting on me?The orchestrator replies with a return report instead of you skimming three terminals.
Maxxwell doesn’t magically know your dependency graph; you have to expose it.
A workable pattern:
repos.yml from above./what-depends-on shared-libCODEOWNERS per repoExample snippet you can paste into the orchestrator when you change the graph:
Dependency rules:
- shared-lib is a library; service-* repos depend on it.
- Changes to shared-lib that break the public API MUST:
- Update all dependent services in the same feature wave.
- Keep a deprecation shim in shared-lib when feasible.
- Land in shared-lib main LAST, after dependent changes are merged.
When planning changes, schedule cross-repo work in this order: library -> dependents -> final library cleanup.
You are building, in text, what GitHub’s MultiRepoOps docs describe as a side-repo control plane, but with a conversational interface.
Now the other side of the world: one big repo instead of many.
Google’s engineers have written about their monorepo holding billions of lines of code and absorbing a large volume of daily changes; the challenge is scope, not raw editing. Claude Code’s monorepo docs say the same: nested config, path-scoped rules, and graphs.
You want “many repos” semantics inside one repo.
From the monorepo root:
# feature for payments service
git worktree add ../monorepo-payments-feature -b feat/payments-new-flow
# infra refactor
git worktree add ../monorepo-infra-cleanup -b chore/infra-cleanup
Now you have two directories that behave like separate repos.
Start workers in each worktree:
cd ../monorepo-payments-feature
claude-code
cd ../monorepo-infra-cleanup
claude-code
Maxxwell shows two workers; sessions outlive the app, so you can quit and reattach without killing work.
Borrowing from Claude Code’s CLAUDE.md pattern, place guidance in directories.
Example services/payments/AGENT.md:
# Agent scope for payments service
- Only modify code under `services/payments/` and its tests.
- Do not change shared libraries in `libs/` without explicit instruction.
- Prefer adding new modules over editing core routing logic.
- Use existing test helpers from `services/payments/tests/helpers/`.
For a shared library:
libs/billing/AGENT.md:
# Agent scope for billing library
- Only modify code under `libs/billing/`.
- Any breaking API change must:
- Update all known callers listed in `libs/billing/CALLERS.md`.
- Add or update migration notes in `libs/billing/MIGRATIONS.md`.
Brief each worker to read and respect the nearest AGENT.md.
Agents need to understand the monorepo as a graph, not as one giant text blob.
If you use Bazel:
bazel query "allrdeps(//libs/billing:all)" > libs/billing/CALLERS.md
If you use Nx:
nx graph --file=project-graph.json
Then add to libs/billing/AGENT.md:
Known callers are documented in CALLERS.md, generated from the build graph.
Consult this list before changing public APIs.
Now brief the orchestrator:
This monorepo uses Bazel/Nx. Dependency information lives in:
- libs/billing/CALLERS.md (Bazel-generated reverse deps)
- project-graph.json (Nx project graph)
When planning API changes, always:
- Ask me to regenerate CALLERS.md if it may be stale.
- Use the list of callers to enumerate all affected packages.
You have effectively given an agent the same inputs that Nx and Bazel use for task graphs.
Past a few sessions, the main job is knowing where attention is needed.
In Maxxwell, every worker has a state derived from facts, not guesses - similar to Agent Orchestrator’s “display status from facts” design.
A practical pattern:
working: leave it alone.waiting on you: this is where to click next.blocked / needs sign-in: unblock or kill.possibly stalled: read the last few turns and decide whether to re-aim.For all active workers, summarize:
- What they are working on
- Whether they are waiting on me
- Whether any look stalled or off-targetThis respects the principle: the person presses Enter. Agents draft, you decide.
To make this concrete, here are two parallel workflows.
shared-lib with new API + shimservice-a and service-b to use new APIshared-lib worker implements the new APIservice-* workers update callers and testsbazel query "allrdeps(//libs/billing:all)" > libs/billing/CALLERS.md../monorepo-billing-api worker changes libs/billing and updates CALLERS.mdservices/paymentsThe underlying coordination problem is the same. The difference is whether your graph edges are repo-level or path-level.
Start with one worker per active branch that actually needs parallelism.
If you’re not running into context or queueing limits, adding more workers often just adds cognitive load. Use Maxxwell’s working/idle states as a sanity check before spinning up more.
Use separate worktrees and path-scoped config:
AGENT.md / CLAUDE.md files in subtreesThis is the same pattern Claude Code, Alera, and others recommend for large monorepos.
For polyrepos, keep it in a control repo or a small manifest file like repos.yml.
For monorepos, use your build system or task runner:
bazel query outputproject-graph.jsonMake the location and meaning of these files part of the orchestrator’s brief.
Tmux gives you panes; Maxxwell adds:
working, waiting on you, possibly stalled, etc.)You can still attach to any worker as a real terminal session; nothing is hidden.
No. Maxxwell conducts; it doesn’t fly on autopilot.
You get honest visibility (including “not heard from”) and a context-pressure readout with one-click compaction, but you re-aim sessions and decide what to stop or restart. That’s deliberate.