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.

Problem

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.

Solution

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.

Pattern 1

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.

KAI-HUB Flow

From an @kai-<agent> comment to one worker run per resolved repo -- the central fan-out.

ADO Comment @kai-bugfix on work item
KAI-HUB Router parse tag + dedup
/dx-discover-repos resolve touched repos
Fan-Out queue worker per repo
Worker (clone) dynamic checkout of target repo

The Registries

repos.json — keyed by repo alias

repos.json
{
"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

agents.json
{
"simple": { "workerPipelineId": "<id>", "event": "workitem.commented", "writes": true },
"dor":    { "workerPipelineId": "<id>", "event": "workitem.commented", "writes": false }
}
FieldRegistryDescription
cloneUrl / defaultBranchrepos.jsonWorker clone step — the URL to clone and the branch to base work on (PAT injected at clone time)
adoProjectrepos.jsonPut in the per-repo queue URL so cross-project fan-out works
platform / brand / rolerepos.jsonRouting metadata — mirror the repos: block route-targets.sh reads
workerPipelineIdagents.jsonADO pipeline id of this agent’s dynamic-checkout worker — one per agent
writesagents.jsontrue = 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.

TierSourceHow
0 — Explicit directiverepos: a, b in the trigger commentDeterministic — maps each alias to its registry entry
1 — Simple-block routing```simple block in the storyRoutes by declared platform / brand / scope via route-targets.sh
2 — Cross-Repo Scope table## Cross-Repo Scope in triage/researchExtracts repo aliases from the markdown table
3 — LLM fallbackWork item title + descriptionPicks 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.

Pattern 2

Lambda-Level Routing (PR Answer)

A different pattern where the target repo is known before Claude starts -- from the webhook payload itself.

PR Answer Routing

The PR-Router Lambda picks the right pipeline based on which repo the PR belongs to. No Claude involvement for routing.

ADO PR Comment Repo-scoped hook fires
PR Router Extract repo from payload
Pipeline Map Look up repo pipeline ID
Queue Correct repo pipeline

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

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

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.

Local Cross-Repo Flow

How cross-repo intelligence flows through the local development workflow.

/dx-req Detects scope across repos
research.md Cross-Repo Scope section
/dx-plan Tags steps with [repo]
/dx-step Developer works per-repo steps
/dx-pr-review Checks sibling field impact
Local

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_MODE is 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.

Config

Configuration Required

Registries for hub fan-out, the targetRepo worker param, and the PR Router map.

Hub + Registries (AI/automation repo)

File / ParamWherePurpose
.ai/automation/registries/repos.jsonAI/automation repoAlias → repoId / adoProject / cloneUrl / defaultBranch / platform / brand / role
.ai/automation/registries/agents.jsonAI/automation repoTag → workerPipelineId / event / writes
targetRepo paramEach dual-mode workerEmpty → direct mode (checkout: self); set → clone repos.json[targetRepo]
DX_PIPELINE_MODEtrueMarks a pipeline run (enables pipeline-only behaviors)

Per PR Router Lambda

VariableValuePurpose
ADO_PR_ANSWER_PIPELINE_MAP{"Frontend":"123","Backend":"456"}Routes PR comments to correct repo’s pipeline
ADO_ORG_URLorg URLFor ADO REST API calls
New Repo

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-repos emits a no-match error → hub posts a clarification comment
  • Ambiguous platform/brand: dx-discover-repos emits an ambiguous error → hub asks the human to add a platform: 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_MODE not set: local mode — detection runs, fan-out does not
KAI by Dragan Filipovic