# Multiple coding agents across repositories

> Keep multiple coding agents consistent across services with shared contracts, explicit ownership, integration gates, and a controlled merge order.

Multiple coding agents can change separate repositories safely only when they work from one versioned change plan and converge through executable checks. Prompting every agent with the same feature request is not coordination. Each agent will fill gaps differently, and those reasonable local decisions can produce a globally broken release.

I treat a cross-repository change as a small distributed-systems project. The shared contract defines what must agree, ownership defines who may decide, integration tests prove that the pieces meet, and merge sequencing controls when each assumption becomes true. Parallel work starts only after those four controls exist.

## Coordination begins with one change plan

A single change plan must describe the target state, the intermediate states, and the proof required to move between them. Without it, each repository becomes its own source of truth. An agent editing the API may assume clients upgrade first, while an agent editing the client may assume the server accepts both formats. Both patches can look correct in isolation.

Keep the plan in a repository that all participating jobs can read at a pinned commit. A dedicated coordination repository works, but a small directory in the repository that owns the public interface is often enough. The location matters less than the rule: agents receive a file and commit identifier, not a paraphrase copied into several prompts.

I use a manifest like this for any change that crosses a service boundary:

```yaml
change_id: checkout-address-v2
contract_revision: 7c92e41
coordinator: platform-payments
repositories:
  - name: checkout-api
    owns: [openapi, compatibility-handler]
    base: 4db31c8
  - name: web-checkout
    owns: [client, browser-flow]
    base: 81f772a
  - name: order-worker
    owns: [event-consumer, replay-test]
    base: c602ab4
sequence:
  - checkout-api-compatible
  - order-worker-compatible
  - web-checkout-producer
remove_after: all-consumers-on-v2
proof:
  - provider-contract
  - consumer-contract
  - staging-address-flow
```

This file prevents several quiet errors. Every agent knows the exact contract revision, the base commit it may build on, and the portion it owns. The sequence records compatibility states rather than repository names, so the release intent survives if one repository needs two merge requests. The removal condition stops an eager cleanup agent from deleting the old field as soon as the new one appears.

The plan should also name invariants in plain language. For example: old clients may send `address_line`; new clients send `address.lines`; the API accepts both during migration; the event contains both until the worker deployment is confirmed; no agent may change money rounding. These statements expose assumptions that a schema alone cannot carry.

Freeze the plan for a work wave. If an agent discovers that the contract is wrong, it should stop and propose a plan revision. Letting it silently improve the contract creates two generations of work in the same batch.

## Shared contracts must be executable

A contract is useful when CI can reject a disagreement. A design note helps humans understand intent, but it cannot tell whether one agent renamed a nullable field, changed an enum, or interpreted an omitted value as zero. Put machine-readable interfaces beside the prose: OpenAPI for HTTP, Protocol Buffers or another schema for messages, SQL migrations for storage, and explicit fixtures for behavior that the type system cannot express.

The OpenAPI Specification describes an API without requiring access to source code or network traffic. That separation is exactly what multi-repository work needs. Pin the document by commit or immutable artifact digest and generate or validate every participating side from that same revision. Do not tell two agents to create matching schemas independently.

A compact compatibility fragment might look like this:

```yaml
AddressInput:
  type: object
  properties:
    address_line:
      type: string
      deprecated: true
    address:
      type: object
      required: [lines]
      properties:
        lines:
          type: array
          minItems: 1
          items:
            type: string
  anyOf:
    - required: [address_line]
    - required: [address]
```

The fragment does more than announce a new field. It defines the period in which either representation is valid. Provider tests must prove both branches work, and consumer tests must prove which branch each client emits. Later, a new contract revision can remove the deprecated branch after deployment evidence satisfies `remove_after`.

Teams often call generated types a contract. That blurs syntax with behavior. Types can prove that `lines` is an array of strings; they cannot prove trimming rules, ordering, idempotency, authorization, retry behavior, or the meaning of an empty array. Add named examples and assertions for those semantics. If an agent can make two plausible implementations that both satisfy the type, the contract is incomplete.

Consumer-driven contract tests are useful here, with one warning. Pact documents the flow clearly: the consumer records its assumptions, and the provider replays those interactions against a running provider. Pact also stresses that publishing the contract is only half the loop; teams need provider verification results to know whether a version can deploy. I agree, but I do not let a consumer dictate undocumented domain behavior. The interface owner reviews new expectations before they become release gates.

## Ownership boundaries must include decisions

File ownership does not prevent inconsistent changes because the dangerous conflicts happen between files. One agent can own the API repository and another the worker repository while both independently decide what `null`, retry, or duplicate delivery means. Define ownership around decisions and invariants, then map those decisions to files.

For each boundary, name one owner for the contract, one owner for each implementation, and one coordinator for the cross-repository outcome. The contract owner decides field meaning and compatibility policy. Implementation owners decide internal structure only. The coordinator can reject a locally elegant change that breaks the global sequence.

An ownership record can stay short:

- The checkout API team owns address validation semantics and the OpenAPI document.
- The web team owns form behavior but cannot add server expectations without contract review.
- The order team owns event processing and replay safety, not event field definitions.
- The coordinator owns the compatibility window and merge queue.

This is not bureaucracy for its own sake. Coding agents need a narrow decision surface. A prompt that says "update the worker for address v2" invites it to repair nearby schemas, rename shared concepts, and normalize data according to its local conventions. A task that says "consume contract revision 7c92e41, preserve v1 replay, edit only these paths, and report any missing semantic rule" gives it room to implement without granting it architecture authority.

Use repository policy to enforce the same boundary. Code-owner review should cover contract files, database migrations, shared workflow definitions, and compatibility adapters. Protect generated outputs from hand edits. If an agent touches a path outside its declared scope, CI should flag the change for coordinator review even when the tests pass.

Avoid shared ownership by committee. When two agents are both allowed to change the canonical contract, you have created a race. One may add a field to satisfy its tests while the other narrows an enum to satisfy another consumer. Give one task the contract edit, merge or pin that revision, then let implementation agents consume it.

## Agents need isolated work and pinned inputs

Each agent should start from a known base commit in its own branch or worktree and should never inherit another agent's uncommitted state. Isolation prevents accidental file interference, but pinning prevents the subtler problem: two clean workspaces built from different assumptions.

Record the base and contract revisions before starting the wave. The agent should print them in its result, along with changed paths and test commands. A coordinator can then reject a patch built against a stale base without reading the entire diff.

A useful task envelope includes the repository, base commit, contract commit, allowed paths, forbidden decisions, acceptance commands, and expected handoff. It also says what the agent must do when information is missing: stop with a specific question, not invent a default. That rule feels slower until you compare it with debugging three coherent but incompatible patches.

Long-running agents need drift checks. Before handing off, an agent should fetch the target branch, find the common ancestor, and show the diff from that point. Git's `merge-base` command finds the best common ancestor for a three-way merge, while `git diff origin/main...HEAD` shows the work introduced since that shared point. These are better inputs for review than an agent's memory of what it changed.

```sh
git fetch origin main
git merge-base origin/main HEAD
git diff origin/main...HEAD
```

The expected output is a commit hash followed by the patch. The coordinator compares the hash with the manifest base and checks the changed paths against the declared ownership. If the target moved, rebase or rebuild the branch and rerun its acceptance tests before it enters the integration queue.

Do not solve drift by giving every agent permission to pull and merge continuously. That approach mixes coordination into implementation, makes results depend on timing, and lets agents absorb half-finished changes. Synchronize at explicit barriers, then start a fresh wave from recorded commits.

## Integration tests belong at service seams

Cross-repository integration tests must assert externally visible behavior at each changed seam. A giant end-to-end suite that starts the whole company stack is too slow and too vague. Unit tests inside each repository are too narrow. The useful middle layer starts the smallest real provider and exercises it with the real consumer contract or a thin protocol client.

Build three kinds of evidence when the change warrants them. Provider compatibility tests send old and new payloads to the new server. Consumer contract tests capture exactly what the new client needs. A focused journey test proves the business path through the deployed components. These tests answer different questions, so one green category cannot substitute for another.

Place each test where failure has a clear owner. The API repository should validate its OpenAPI document and run provider behavior. The consumer repository should verify request construction and response handling. A coordination pipeline can assemble immutable images from candidate commits and run the journey test. It should consume commit hashes from the manifest, never whatever happens to be latest on each default branch.

Test negative behavior, not merely the happy payload. For the address migration, send both fields with conflicting values and define which wins, omit both, send an empty `lines` array, retry the event, and replay an old stored event. This is where agents tend to diverge because each library and local style suggests a different default.

Mocks need a hard boundary. Mock internal dependencies to keep provider verification deterministic, as Pact recommends, but do not mock the interface you are trying to prove. A test where the consumer mock and provider stub were both generated from the same schema can show syntactic agreement while neither side matches production behavior. At least one gate must run the real serialization, routing, validation, and error mapping on both sides of the seam.

Treat test artifacts as release evidence. Store the provider version, consumer version, contract revision, environment, and result together. "CI was green" is not enough when five pipelines ran against different commits. The coordinator needs to answer which exact combination passed.

## A locally green change can still break the release

Consider a checkout API, a browser client, and an order worker in separate repositories. Three agents receive the same request: replace `address_line` with structured address lines. Each produces a sensible patch and passes local tests.

The API agent makes `address` required and stops emitting `address_line`. Its tests use new fixtures. The browser agent sends `address` and accepts the API's new response. The worker agent reads `address.lines[0]`, but its tests construct events directly rather than consuming a recorded v1 event. Every repository turns green.

The browser merges first. Production still runs the old API, so checkout requests fail validation or silently discard the new object, depending on the old handler. If the API merges first, old browser clients fail because the server now requires the new field. If the API and browser deploy together, queued v1 events still crash the new worker. The defect does not live in any single patch. It lives in the unplanned intermediate states.

The repair is an expand, migrate, contract sequence. First, the API accepts both request shapes and emits both event shapes. The worker deploys a reader that handles both and proves it can replay a stored v1 fixture. The browser then starts producing v2. After deployment records show no active v1 producer and the queue retention window has passed, a separate cleanup change removes v1.

Notice what did not fix this failure. Better prompts would not force the agents to choose the same deployment assumption. A larger shared context window would give them more information but no authority model. Running all repository unit tests in one job would repeat the same blind spots. The missing control was a compatibility sequence backed by seam tests.

Database changes create the same pattern. Renaming a column in one migration while another service still queries it is not a repository conflict, and Git cannot detect it. Add the new column, write both representations where necessary, backfill with observable progress, switch readers, then remove the old column in a later wave. Let the coordinator decide when evidence permits each transition.

## Compatibility needs an explicit budget

Backward compatibility should have a defined scope and lifetime, or agents will either remove it too early or preserve it forever. State which versions must coexist, how the system detects remaining old traffic, and what evidence permits deletion. A vague instruction to "keep this backward compatible" leaves every implementation owner to choose a different boundary.

Budget compatibility across four dimensions: accepted inputs, emitted outputs, stored data, and operational procedure. An API may accept both request shapes while emitting only the new response, but that is unsafe if an old consumer reads the response. A worker may understand both event versions while a replay tool still writes only v1. A database may contain both columns while a rollback procedure restores code that knows only the old one. The plan must cover every path that remains live during the migration.

Time alone is weak removal evidence. Waiting seven days does not prove that a rarely used client, a delayed queue, or a disaster recovery job has advanced. Prefer observable conditions: no v1 producer releases remain in supported environments, telemetry has recorded no old shape across a complete business cycle, queue age is below the migration point, and restore drills use a compatible version. Use a calendar deadline as a prompt to investigate, not as proof that deletion is safe.

Define failure behavior for the compatibility layer. If both old and new fields arrive, choose precedence or reject the request. If transformation loses information, record that fact and decide whether the operation can continue. If an old consumer cannot represent a new enum value, choose a stable fallback rather than letting each client agent invent one. These rules belong in fixtures because prose interpretations drift.

Rollback deserves its own contract state. Suppose the new producer activates, then its release must roll back after it has written v2 events. The restored producer may emit v1 again, while consumers still need to read the v2 events already queued. That means consumer compatibility often has to outlive producer rollback and the normal observation window. Cleanup can begin only when both forward deployment and rollback exposure have passed.

Avoid bidirectional translation unless the business case requires it. Teams often propose a universal adapter because it sounds flexible. In practice, converting v2 to v1 and back can erase structure, defaults, or provenance, and agents will implement the lossy corners differently. Prefer one canonical internal representation, translate each accepted legacy input into it once, and emit legacy output only at boundaries with known old consumers.

Make the budget visible in code. Give compatibility adapters names tied to the change identifier, add counters for each legacy branch, and attach the removal condition to their tests. A generic helper named `normalizeAddress` can survive for years because nobody knows whether its odd behavior is still required. A helper named `acceptCheckoutAddressV1DuringV2Migration` tells the cleanup owner what to search for and why it exists.

When the budget expands, revise the plan before more agents start. Supporting one extra consumer can change schemas, rollout order, test combinations, and cleanup timing. Treat that as a design change, not a minor prompt amendment. The coordinator should invalidate only the evidence affected by the new compatibility state, but it must be able to explain why the rest remains valid.

## Merge sequencing is part of the design

The correct merge order follows runtime dependencies and reversible compatibility states, not which agent finishes first. A pull request is ready only when its prerequisites exist and its addition is safe against the currently deployed versions.

Build a dependency graph with changes as nodes. An edge means one change requires another contract revision, generated artifact, or deployed behavior. Merge the smallest compatibility-providing changes first, then consumers, then producers, and cleanup last. Cycles signal that the proposed patches cannot deploy independently; break the cycle by adding an adapter or splitting a patch.

Merge order and deployment order are related but not identical. A dormant consumer change can merge behind a feature flag before its provider deploys. A schema migration may need to deploy before application code can merge if tests query a shared environment. Record both sequences when they differ, including the condition that enables the dormant path.

A merge queue protects the target branch from changes that pass separately but fail together. GitLab's merge train documentation explains the failure precisely: two merge requests can each pass a merged-results pipeline and still break the target when combined. A train tests each request with all requests ahead of it. That model is useful inside one repository, but a multi-repository train needs an external coordinator that advances a set of pinned commits across repositories.

Do not hold every merge until the entire feature is complete. That creates oversized branches and painful rebases. Merge backward-compatible groundwork early, keep new behavior inactive, and use short-lived flags or configuration to control activation. Every flag needs an owner and removal condition in the change plan, or temporary compatibility becomes permanent confusion.

If a prerequisite fails after a dependent patch is ready, invalidate and retest the dependent candidate. Passing against contract revision 7 does not prove compatibility with a repaired revision 8. Reuse source changes when appropriate, but never reuse evidence across a changed dependency.

## One coordinator must own global state

A coordinator should schedule agents, freeze contract revisions, collect evidence, and advance the dependency graph. It can be a person, a workflow, or a dedicated agent with narrow permissions. It should not rewrite implementation patches while also judging them; mixing author and gatekeeper roles hides changes from the owners who must support them.

The coordinator maintains a small state machine for each repository: planned, implementing, locally verified, integration verified, mergeable, merged, deployed, and observed. Transitions require artifacts, not prose claims. For example, integration verified requires a result tied to the candidate commit and contract revision. Deployed requires an immutable release identifier from the actual environment.

Keep global state outside agent conversation histories. Conversations are hard to diff, easy to truncate, and poor inputs for automation. Write decisions and candidate hashes into the manifest, and make every agent read it at task start. If the coordinator changes a decision, it creates a new revision and marks affected evidence stale.

Permissions should follow ownership. Implementation agents can push only to their branches. The contract owner can approve interface changes. The coordinator can enqueue approved candidates but cannot bypass required tests. Cleanup agents cannot run until deployment evidence satisfies the removal condition. These limits reduce the cost of a confused or overenthusiastic agent.

Human review belongs at high-consequence decisions, not every generated line. Review changes to public contracts, destructive migrations, authentication rules, money calculations, and the compatibility sequence. Let automated gates handle formatting, generated-code drift, scoped path checks, and repeatable test results. This division keeps review attention for decisions the system cannot infer safely.

For companies trying to move from ad hoc agent use to a controlled delivery system, a Team & AI Audit from oleg.is can map repositories, ownership gaps, and the first multi-agent pipeline before tools and prompts multiply the existing ambiguity.

## Parallelism should follow conflict radius

The number of agents you can run safely depends on how much shared meaning their tasks touch. Ten agents editing independent adapters may be safer than two agents changing one public schema and its migration. Count semantic collisions, not repositories.

Start parallel work after the contract and sequence are fixed. Good parallel candidates consume the same settled interface and own disjoint implementations: a web client, a mobile client, documentation fixtures, and an observer that measures migration. Poor candidates make coupled decisions about the same schema, database state, or rollout flag.

Limit each wave to work the coordinator can integrate and verify before its inputs drift. If reviews, environments, or contract tests form a queue, more implementation agents only produce stale branches faster. Reduce concurrency until evidence moves through the gate without piling up.

Track a few operational signals: how often tasks stop for missing contract rules, how many patches touch undeclared paths, how often integration invalidates locally green work, and how long candidates wait for prerequisites. These counts diagnose coordination. A high stop rate early may be healthy because agents expose ambiguity before code lands; repeated late integration failures mean the plan or seam tests are weak.

The safe stopping point is also explicit. Work is complete when every candidate is merged from the approved base, the pinned combination passes seam tests, deployments reach the required versions, and removal conditions are either satisfied or scheduled as owned work. Agent completion messages do not establish any of those facts.

Shared contracts, decision ownership, seam tests, and ordered compatibility states turn parallel agents into an engineering system. Remove one control and local speed becomes integration debt. Keep all four, and adding an agent is a capacity decision rather than a bet that its assumptions match everyone else's.
