Multi-Repo Development
Run a single loop across multiple sibling repositories and produce plans that cross repo boundaries.
Many teams ship features that span more than one repository: a backend API and a frontend app, a shared library and its consumers, a platform service and an SDK. ClosedLoop.ai's multi-repo mode lets a single loop plan and implement across those peers without context-switching.
Enabling multi-repo
Pass --add-dir for every peer repo when you launch a loop:
./run-loop.sh .closedloop-ai/work --prd requirements.md \
--add-dir ../peer-backend \
--add-dir ../peer-mobilesetup-closedloop.sh resolves each flag to a canonical absolute path and builds a repo map that every subagent can read.
The three env vars
Multi-repo state is carried entirely by environment variables, set in $CLOSEDLOOP_WORKDIR/.closedloop-ai/config.env:
| Variable | Format | Meaning |
|---|---|---|
CLOSEDLOOP_ADD_DIRS | pipe-separated absolute paths | All resolved --add-dir values. |
CLOSEDLOOP_ADD_DIR_NAMES | pipe-separated short names | User-friendly names, disambiguated when collisions occur. |
CLOSEDLOOP_REPO_MAP | pipe-separated name=path | Canonical name-to-path map used by agents. |
How peer names are chosen
Each peer repo is expected to have a .closedloop-ai/.repo-identity.json with at least:
{
"name": "peer-backend",
"type": "service",
"discoverable": true
}When names collide, make_unique_repo_name walks path segments to build names like peer-backend-acme. Ancestors of the workdir are silently skipped.
Discovery tiers
discover-repos.sh combines three sources:
- Explicit paths from
CLOSEDLOOP_ADD_DIRS(tagged"local": true). - Env var from
CLAUDE_WORKSPACE_REPOS(formatname1:path1,name2:path2). - Sibling scan – any sibling directory containing a
.repo-identity.jsonwithdiscoverable: true.
Results are deduplicated across tiers and emitted as JSON with currentRepo, discoveryMethod, peers[], and a monorepo flag detected via .repo-identity.json type=monorepo or the presence of apps//packages/.
Which agents are multi-repo aware
pre-explorerwrites acode-map-{name}.jsonper repo.plan-draft-writeremits plans with a## Repositoriessection and per-task@{repo}:pathprefixes for cross-repo tasks.cross-repo-coordinatordecides which peers are needed and emits aCAPABILITIES_LIST.generic-discoveryprobes each peer for capabilities and caches results to.discovery-cache/{peer}.json.cross-repo-prd-writerproducescross-repo-prd-{peer}.mdand tags plan tasks with[CROSS-REPO: {peer}].api-spec-writercaptures the inter-repo contract asapi-requirements.md.
No orchestrator-level branching is required. Agents inspect the env vars themselves and act accordingly.
Plan schema addition
plan.json has a repositories map keyed by short name:
{
"repositories": {
"peer-backend": { "path": "/Users/me/code/peer-backend", "isPrimary": false },
"peer-mobile": { "path": "/Users/me/code/peer-mobile", "isPrimary": false },
"primary": { "path": "/Users/me/code/primary", "isPrimary": true }
}
}Tasks tagged [CROSS-REPO: peer-backend] are planned to land in that peer's working copy.
Running from the desktop app
The desktop app auto-clones repositories that are referenced in a cloud command but missing locally (via gh repo clone, 120-second timeout). For multi-repo loops, ensure every peer repo either exists in your sandbox or is accessible via gh before dispatch. You can pre-populate ~/.closedloop-ai/config/repos.json with the set you expect to use frequently.
Authorization at loop create
additionalRepos is now authorized and verified the moment a loop is created — not only when a PLAN runs. The control plane validates each peer against the calling org's GitHub installation and confirms the requested branch exists. Failures surface as structured errors:
| Error | Status | Meaning |
|---|---|---|
UnauthorizedRepoError | 403 | The peer repo is not installed for this org. Install or re-authorize it in the GitHub app. |
BranchNotFoundError | 400 | The base branch does not exist on the peer. Push the branch or correct the name. |
| Duplicate-repo validation error | 400 | The same repo appears more than once in additionalRepos. |
POST /loops/{id}/github-token also accepts an optional additionalRepos body so a desktop harness can scope the token request to the actual peer set. When the body is omitted (or invalid), the route falls back to the loop record on file.
Multi-repo execution results (v2)
When a multi-repo loop completes, the runtime publishes a v2 completion event whose results[] field is a discriminated union per peer:
{
"schemaVersion": 2,
"results": [
{ "status": "success", "repo": "primary", "branch": "feat/payments", "prUrl": "https://github.com/acme/primary/pull/142" },
{ "status": "skipped", "repo": "peer-mobile", "reason": "no changes required" },
{ "status": "failed", "repo": "peer-backend", "error": "build-validator failed" }
]
}Single-repo loops continue to publish the v1 event shape. The platform normalizes both into a unified result so judges, the activity feed, and downstream automation see one consistent structure.