Cross-Repo Architecture
Multi-repo intelligence for local development and pipeline automation -- a central KAI-HUB router resolves which repos a work item touches and fans one dual-mode worker pipeline out per repo, while local skills detect cross-repo scope, tag plan steps by repo, and check sibling impact.
The Multi-Repo Problem
ADO pipelines run with checkout: self -- each pipeline only has access to the repo where its YAML file lives. But work items frequently span multiple repos.
Bug Reports
A bug report’s root cause may be in the backend repo, but the comment that triggers the agent lives on a work item that could touch any repo.
User Stories
A story may need changes in both frontend and backend repos. Implementation must happen in the repo that owns the affected code.
DoD Checks
DoD checks can span all repos that contribute to a story. Fixing a DoD gap may require changes in a different repo than where the check runs.
Each Repo Has Its Own
CLAUDE.md, .claude/rules/, .ai/config.yaml (project conventions),
and installed plugins (same dx plugins but configured per-project). The worker pipeline clones the
target repo dynamically so it picks up that repo’s conventions at runtime.
The KAI-HUB Model
One central router parses the @kai-<agent> tag, resolves the touched repos, and fans a single dual-mode worker pipeline out — one run per repo. No peer-to-peer pipeline delegation.
Central Router
The KAI-HUB router pipeline (ado-cli-hub.yml) is the single webhook entry point.
It parses the @kai-<agent> tag, dedups the event, runs
/dx-discover-repos, then queues the agent’s worker once per resolved repo
(cross-project via each repo’s ADO project; Basic PAT auth).
Dual-Mode Workers
Each agent has one worker pipeline. It keeps its own
resources.webhooks for single-repo direct triggers (operates on checkout: self)
and ALSO accepts a targetRepo param. When set, it clones that repo dynamically from the
registry into $(Pipeline.Workspace)/target.
Two Registries
repos.json (alias → repoId / adoProject / cloneUrl / defaultBranch / platform / brand / role)
and agents.json (tag → workerPipelineId / event / writes) live in the AI/automation repo
and are the single source of truth for which repos exist and which agents have a worker.
One Worker Pipeline Per Agent — Not One Per Repo
Under the old model each agent existed as a separate pipeline in every repo, and a code-writing
pipeline that discovered work elsewhere would write a delegate.json and queue the
matching pipeline in the target repo (peer-to-peer). That is gone. Now there is a single worker
per agent; the hub fans it out per resolved repo, and the worker clones whichever repo it was
handed. delegate.json, CROSS_REPO_PIPELINE_MAP, SOURCE_REPO_NAME,
and the standalone simple-router pipeline no longer exist.
KAI-HUB Router Fan-Out
A human comments @kai-<agent> on a work item. The hub parses the tag, resolves the touched repos, and queues the agent's one worker once per repo.
From an @kai-<agent> comment to one worker run per resolved repo -- the central fan-out.
The Registries
repos.json — keyed by repo alias
{
"brand-one": {
"repoId": "<ado-repo-guid>",
"adoProject": "Example Project",
"cloneUrl": "https://dev.azure.com/example-org/Example%20Project/_git/brand-one-frontend",
"defaultBranch": "development",
"platform": "global",
"brand": "brand-one",
"role": "frontend"
}
} agents.json — keyed by agent tag
{
"simple": { "workerPipelineId": "<id>", "event": "workitem.commented", "writes": true },
"dor": { "workerPipelineId": "<id>", "event": "workitem.commented", "writes": false }
} | Field | Registry | Description |
|---|---|---|
cloneUrl / defaultBranch | repos.json | Worker clone step — the URL to clone and the branch to base work on (PAT injected at clone time) |
adoProject | repos.json | Put in the per-repo queue URL so cross-project fan-out works |
platform / brand / role | repos.json | Routing metadata — mirror the repos: block route-targets.sh reads |
workerPipelineId | agents.json | ADO pipeline id of this agent’s dynamic-checkout worker — one per agent |
writes | agents.json | true = pushes code / authors content (needs a writable clone); false = read-only |
Repo Discovery — dx-discover-repos
3-tier cascade (first non-empty tier wins)
The dx-discover-repos skill is the multi-repo brain of the hub. It resolves the set of
repos a work item touches and emits a JSON array constrained to known registry aliases — it never
invents repos. Single-repo projects never call it.
| Tier | Source | How |
|---|---|---|
| 0 — Explicit directive | repos: a, b in the trigger comment | Deterministic — maps each alias to its registry entry |
| 1 — Simple-block routing | ```simple block in the story | Routes by declared platform / brand / scope via route-targets.sh |
| 2 — Cross-Repo Scope table | ## Cross-Repo Scope in triage/research | Extracts repo aliases from the markdown table |
| 3 — LLM fallback | Work item title + description | Picks the touched subset, constrained to registry aliases |
On ambiguity, the hub asks
If tier 1 can’t resolve a platform/brand (exit 3) or no candidate matches (exit 8), the skill emits a structured error instead of an array. The hub turns that into a clarification comment on the work item and queues nothing — no guessing.
Lambda-Level Routing (PR Answer)
A different pattern where the target repo is known before Claude starts -- from the webhook payload itself.
The PR-Router Lambda picks the right pipeline based on which repo the PR belongs to. No Claude involvement for routing.
Key Difference from Hub Fan-Out
For PR Answer the target repo is in the webhook payload, so the Lambda routes directly using
ADO_PR_ANSWER_PIPELINE_MAP — a JSON object mapping repo names to PR Answer pipeline IDs.
The hub router exists precisely because work-item agents do NOT know their target repo up front —
it has to be discovered after reading the ticket.
Design Decisions
Why a central router, why two registries, and why workers clone the target dynamically.
Why a Central Router?
Peer-to-peer delegation needed every code-writing pipeline to carry a map of every other repo’s pipeline IDs and re-queue itself across repos. One router with one registry replaces that web of cross-references — adding a repo is a registry edit, not a map update on every pipeline.
Why Two Registries?
repos.json and agents.json are the single source of truth. The router
reads them to know which repos exist and which pipeline to queue, so the routing logic lives in
one place instead of being scattered across pipeline variables.
Why Clone Dynamically?
A worker handed targetRepo clones that repo from repos.json at runtime, so
it always has the correct codebase, conventions, and branch. With no targetRepo,
TARGET_DIR defaults to $(Build.SourcesDirectory) — single-repo behavior is
unchanged.
Single-Repo Projects Don't Touch the Hub
The registries are only read in hub mode. A single-repo project keeps firing its worker directly via
the worker’s own resources.webhooks (the worker is dual-mode), so it never needs
repos.json or agents.json.
Local Cross-Repo Intelligence
Skills detect, tag, and verify cross-repo impact during local development -- no pipelines required.
Prerequisite
Local cross-repo intelligence requires the repos: section in .ai/config.yaml with
at least name, role, and path for each sibling repo.
Skills with Cross-Repo Awareness
/dx-req — Scope Detection
During research (Phase 5c), detects when a story spans sibling repos by checking for cross-repo
signals: dialog field dependencies, data model changes, template references, exporter modifications.
Appends a ## Cross-Repo Scope section to research.md listing affected repos,
field dependencies, and deployment order. The hub’s dx-discover-repos reads this same
table as its tier-2 source.
/dx-plan — Step Tagging
When research.md contains a Cross-Repo Scope section, tags each plan step with its target
repo: [My-Backend-Repo] Add dialog field. Groups steps by repo with deployment
dependency notes (typically backend before frontend).
/dx-pr-review — Field Impact
When reviewing a PR that modifies _cq_dialog XML files, greps sibling FE repos
(via repos[].path) for usage of renamed or removed field names. Reports breaking
changes and flags new fields available for frontend consumption.
How cross-repo intelligence flows through the local development workflow.
Local Developer Flow
Cross-repo detection still runs locally; handoff to another repo is manual.
What Happens Locally
When a developer runs /dx-bug-all 12345 locally:
DX_PIPELINE_MODEis not set- Cross-repo detection still runs (triage/research)
- Cross-repo scope appears in the summary
- No automatic fan-out — the developer decides
Manual Handoff
The summary tells the developer: “Also affects My-Backend-Repo — run
/dx-bug-all 12345 there.” The developer manually switches to the other repo
and runs the command. The hub router only fans work out in automation (pipeline) mode.
Configuration Required
Registries for hub fan-out, the targetRepo worker param, and the PR Router map.
Hub + Registries (AI/automation repo)
| File / Param | Where | Purpose |
|---|---|---|
.ai/automation/registries/repos.json | AI/automation repo | Alias → repoId / adoProject / cloneUrl / defaultBranch / platform / brand / role |
.ai/automation/registries/agents.json | AI/automation repo | Tag → workerPipelineId / event / writes |
targetRepo param | Each dual-mode worker | Empty → direct mode (checkout: self); set → clone repos.json[targetRepo] |
DX_PIPELINE_MODE | true | Marks a pipeline run (enables pipeline-only behaviors) |
Per PR Router Lambda
| Variable | Value | Purpose |
|---|---|---|
ADO_PR_ANSWER_PIPELINE_MAP | {"Frontend":"123","Backend":"456"} | Routes PR comments to correct repo’s pipeline |
ADO_ORG_URL | org URL | For ADO REST API calls |
Adding a New Repo
Bring a new repository into the multi-repo automation with a registry edit.
1. Install Plugins
Run /dx-init and /aem-init in the new repo. This sets up
.ai/config.yaml, rules, and project knowledge.
2. Import the Worker
Import the dual-mode worker pipelines into ADO. Set pipeline variables (same as existing repos).
The worker keeps its own resources.webhooks for single-repo direct triggers.
3. Register
Add the new repo as an alias in repos.json (repoId, adoProject, cloneUrl,
defaultBranch, platform, brand, role). No per-pipeline map edits — the hub reads the registry.
Error Handling
- Registry alias missing:
dx-discover-reposemits ano-matcherror → hub posts a clarification comment - Ambiguous platform/brand:
dx-discover-reposemits anambiguouserror → hub asks the human to add aplatform:hint - ADO queue call fails: hub step fails — visible in ADO logs
- Target pipeline paused/disabled: ADO queues the run but it will not execute
DX_PIPELINE_MODEnot set: local mode — detection runs, fan-out does not