Skip to content

Multi-repo loading

You wrote a guardrail workflow — the one that won’t let your agent push on a red suite. It does what you need. You copy it into the next project. And the next. By project three you’ve forked it three different ways and you can’t remember which one had the bug fix.

That’s the copy-paste model. You stop reusing because reuse means re-coordinating across N forks every time something changes.

v0.2’s multi-repo loader replaces it with something you already do every day: you pull a curated set of guardrail workflows into your project the way you pull a dependency. One line per repo in your gateway config —

repos:
- path: ~/repos/cognitive-architectures

— and that repo’s workflows, capabilities, and skills land in your registry, namespace-prefixed so nothing collides with what you already have. Updates are a git pull away. You don’t fork; you consume. (The advanced multi-team case — where you publish a library for other people to load — falls out of the same machinery, but the everyday use is just “add a dependency.”)

The rest of this guide covers the manifest a consumable repo ships, how loaded ids get namespaced, and the validation rules (V19–V23) that turn the failure modes of pulling shared config — id collisions, dangling references, silent local edits — into named errors at startup instead of surprises mid-run.

Each resource repo ships a flowgate.repo.yaml at its root:

flowgate.repo.yaml
schema: flowgate.repo/v1
name: swe-core
namespace: swe
version: 0.6.0
description: |
Core SWE capabilities + lifecycle orchestrators.
layout:
capabilities: capabilities
orchestrators: orchestrators
skills: skills
scripts: scripts-library
connections: connections

The manifest declares:

  • schema — pinned to flowgate.repo/v1 for v0.2. Forward-incompatible schema bumps will use new constants so older gateways refuse to mis-parse newer repos.
  • name — the repo’s identifier, lowercase kebab. Used in diagnostics and flowgate.query({subject}) describe.
  • namespace — the prefix every loaded definitionId gets. Single segment, lowercase kebab. Two repos sharing a namespace fail at load (V20).
  • version — repo version, semver-shaped by convention. Surfaced via flowgate.query({subject}).
  • layout — per-tier directory locations. All fields optional; defaults match the field names. A repo that doesn’t ship one tier (a connections-only repo, say) leaves that field unset and the loader silently skips the missing directory.

You wire repos in via a top-level repos: block — one entry per library you want to consume:

gateway.yaml
version: "1.0.0"
repos:
- path: ~/repos/cognitive-architectures
- path: ~/repos/flowgate-meta
- path: /opt/internal-tools/governance-pack

Paths can be absolute, relative (resolved against the gateway config’s directory), or ~/-prefixed. The loader walks each repo’s manifest, globs every *.yaml in the declared layout directories, and merges the contents into the gateway’s registry — namespace-prefixed.

After loading, mcp-flowgate check shows you what landed:

config: gateway.yaml
workflows (28):
- cognitive/cap.coordinate.pr-open
- cognitive/cap.diagnose.localize
- cognitive/cap.plan.draft
...
- cognitive/flow.add-feature
- cognitive/flow.bugfix-from-error-log
- meta/cap.research.tool-inventory
- meta/flow.author-capability
...
validation: ok

Every id is prefixed <namespace>/<original-id>. cognitive/cap.plan.vet and meta/cap.plan.compose-implementation are distinct ids the registry holds separately.

Inside a repo-loaded workflow, kind: workflow references resolve against the workflow’s own namespace by default:

# In repo `cognitive`:
states:
vetting:
transitions:
vet:
executor:
kind: workflow
definitionId: cap.plan.vet # → resolves to cognitive/cap.plan.vet
use:
outputs: { "$.context.verdict": verdict }

When the orchestrator lives in the cognitive namespace, the bare cap.plan.vet reference auto-prefixes to cognitive/cap.plan.vet. To call a capability in a different namespace, fully qualify the reference:

definitionId: meta/cap.audit.mine-transitions

The loader rewrites bare references at load time; the runtime sees only fully-qualified ids. Unresolved references fail at load with V22 (UNRESOLVED_WORKFLOW_REF) — better than a “not found” error in the middle of a running workflow.

The validation cloud catches the most common multi-repo mistakes:

RuleTriggers whenError code
V19Repo manifest declares the wrong schema: constantflowgate.repo/v1 expected; got ...
V20Two declared repos share a namespace:DUPLICATE_REPO_NAMESPACE
V21A single repo defines the same id in two filesDUPLICATE_REPO_DEF
V22Workflow references an id that no loaded repo providesUNRESOLVED_WORKFLOW_REF
V23Host config shadows a repo-provided id without listing it in overrides:ANONYMOUS_OVERRIDE

V23 catches the most insidious one: silently editing a pulled-in guardrail. When you locally redefine a workflow that came from a repo, that edit has to be declared — otherwise you’ve quietly diverged from the upstream you’re tracking and a git pull won’t tell you. If you want to replace a pulled-in capability with your own version, you declare it explicitly:

version: "1.0.0"
repos:
- path: ~/repos/cognitive-architectures
overrides:
- cognitive/cap.coordinate.pr-open # explicit shadow declaration
workflows:
cognitive/cap.coordinate.pr-open:
# ...operator's customized version...

Defining cognitive/cap.coordinate.pr-open in the host config without listing it in overrides: fails at load. Listing it without an actual collision (the repo doesn’t provide it) also fails (STALE_OVERRIDE). Either flavour of accidental drift surfaces at startup.

Everything above is the single-developer case: you pull libraries the way you pull dependencies. The same machinery scales to a team without changing.

A repo you’ve curated — your house guardrails, the workflows everyone’s agent should run — is just a git repo with a flowgate.repo.yaml. Push it somewhere your team can clone, and every developer adds one repos: line to load it. Updates ship by git pull; nobody re-forks. The namespacing keeps each team’s library distinct, V20 stops two libraries from claiming the same namespace, and V23 means a developer can’t quietly override a shared guardrail without that override showing up in their own config diff. The thing you wrote for yourself becomes the thing the whole team consumes, with the divergence-detection already baked in.

Load order is deterministic:

  1. Each repo’s manifest loads in declaration order. V20 catches namespace collisions across repos.
  2. Inside each repo, every layout directory’s *.yaml files load. V21 catches in-repo duplicates.
  3. Repo definitions land in the registry namespace-prefixed.
  4. The host gateway config’s own workflows: / skills: / scripts: / connections: blocks load last, on top of the repo content. V23 catches anonymous shadows.

Host include: files (the v0.1 modular-config primitive) still load via the original include semantics — they layer on top of the repo content. Existing operators upgrading to v0.2 with include:-only configs don’t need to change anything; repos: is purely additive.

The fastest way to see multi-repo loading work is against the two libraries v0.2 ships with:

Terminal window
git clone https://github.com/matt-cochran/cognitive-architectures ~/repos/cognitive-architectures
git clone https://github.com/matt-cochran/flowgate-meta ~/repos/flowgate-meta
cat > gateway.yaml <<'EOF'
version: "1.0.0"
repos:
- path: ~/repos/cognitive-architectures
- path: ~/repos/flowgate-meta
EOF
mcp-flowgate check --config gateway.yaml

That lists every loaded id from both repos under their namespace prefixes. Pick one and mcp-flowgate serve --config gateway.yaml; the model now sees them all through search, none of them in its tool list.