Dual AI Coding Assistant Migration: One Set of Project Assets, Thin Adapter Layers
When a team runs two AI coding assistants against the same project, the common shortcut — copying one assistant's entire configuration directory — quietly creates rule, memory, and workflow forks that grow more expensive over time. This article explains how to restructure the setup around one shared set of project assets and two thin adapter layers: how to inventory and classify an existing assistant directory, turn entry files into short launchers, keep task memory and workflows single-sourced, stop the adapter layer from absorbing business logic, and add CI checks and governance so configuration drift does not come back. The result is that adding, replacing, or dropping an assistant costs a small, ongoing adapter effort instead of a large one-time copy.
1. The Core Problem: Copying a Config Means Manufacturing a Fork
Storing an assistant-facing instruction file in a version control repository — describing what the project is, which rules apply, and how far tasks have progressed — has become routine for many teams. A project that has used a single assistant for months or longer often ends up with tens of megabytes of assets in the assistant directory at the repo root: glossaries, architecture decision records, troubleshooting playbooks, task memory, and a full set of scripts.
When the tool ecosystem shifts, or a single vendor’s service availability, cost, or compliance requirements change, teams start considering bringing a second assistant on board. For many, the instinctive first move is to copy the whole directory and rename the file.
Copying takes only a few seconds; the real problem comes afterward.
- Rule forks: Each end maintains its own constraints. One end has switched to “database migration scripts must be idempotent,” while the other still generates non-idempotent scripts under the old standard.
- Memory forks: One end recorded “Plan A was confirmed abandoned last week”; the other lacks that entry, so it re-litigates the same plan — sometimes even re-implements it.
- Workflow forks: The same build script ends up maintained in two copies; a bug gets fixed in one, while the other still carries it.
- Maintenance cost compounds over time: Every change has to be remembered as “change it in two places.” Miss one, and the two ends’ understanding of the same project begins to diverge.
The goal is not “two usable configs” but “one set of project assets plus two very thin assistant adapter layers.” To get there, start with an asset inventory.
2. Asset Inventory Before Migration: Fully Separate Two Categories
A mature assistant directory usually mixes four types of content, each with completely different ownership:
| Asset type | Typical content | Ownership | Handling |
|---|---|---|---|
| Business knowledge and constraints | domain terms, architecture decision records, interface contracts, coding standards, lessons learned | project | share as-is |
| Task memory | current progress, pending decisions, rejected approaches | project | share as-is |
| Executable automation | build, validation, code generation, and batch-processing scripts | project | share as-is |
| Assistant runtime adaptation | entry file location, skill discovery, external tool integration, hook protocol, permission model | assistant | one per assistant, kept as thin as possible |
The first three look like they “live inside the assistant’s directory,” but in reality they belong to no particular assistant. The fourth describes “how a specific assistant is invoked, what it can do, and where it needs human confirmation,” which is inherently tied to a specific tool.
There is a simple, reliable test: If you swapped the assistant, would this file’s content still hold?
- If it still holds, it is a project asset — keep only one copy.
- If it no longer holds, it is adapter configuration — write one per assistant, keep it to mapping only, and put no business content in it.
The most common and most expensive mistake during migration is to copy the entire directory over and sort it out later. Copying takes seconds, but every subsequent change then costs double the maintenance, and the two ends drift further apart the longer you wait. So the recommended target structure is “split first, then migrate”: entry files sit where each assistant can auto-discover them, and all real assets are consolidated into a single unified context directory.
repo/├── entry.md # Assistant A's entry convention (thin launcher)├── .aux/entry.md # Assistant B's entry convention (location set by B)├── docs/ai/│ ├── README.md # Context index: what each file does and when to load it│ ├── glossary.md # Domain terms and naming conventions│ ├── architecture.md # Architecture decision records, module boundaries│ ├── coding-standard.md # Coding standards and technology-choice constraints│ ├── task-state.md # Task memory: current state, pending decisions, rejected approaches│ └── playbooks/ # Per-scenario lessons learned and operations manuals└── scripts/└── ci/verify.sh # Validation script shared by both ends
A suggested migration order:
- Inventory and classify: Mark ownership for each file against the table above. When unsure, treat it as a project asset and have the adapter layer reference it rather than copying it.
- Migrate assets: Move all of the first three categories into
docs/ai/, and clean up stale records and duplicate entries while you are at it. This step is the best moment to align both ends’ understanding. - Rebuild entries: Write a thin launcher for each assistant whose only job is “load by index,” carrying no concrete content itself.
- Fill in the adapter layer: Land skill discovery, external tool integration, hooks, and the permission model one item at a time.
- Add validation: Use CI to automatically check structure, dead links, and write points, so the configuration cannot fork again.
3. Step One: Let the Second Assistant Know Where to Start Reading
Different assistants have different conventions for entry files. Some use a fixed filename, some accept several candidate names, and some search down the directory hierarchy level by level. What they share is this: before starting a task, the assistant automatically reads the entry file, reading from the repo root all the way down to the current working directory, and rules closer to the current directory take higher priority. This is consistent with how linters and static-analysis tools handle layered configuration.
This mechanism creates two practical constraints:
- An entry file should not be a “giant protocol”: Mainstream assistants impose a default length limit on the instruction chain loaded automatically at startup. Stuff dozens of pages of specification into the entry file and, at best, it gets truncated; at worst, it crowds out the working context you actually need.
- Proximity override is a feature, not a defect: You can drop a very short entry file into a subdirectory to add just that module’s special rules. But keep to “upper layers define the general, lower layers define the exceptions,” and define any given rule at only one level — otherwise you end up with contradictory instructions.
The recommended approach is to make the root entry a “launcher”: it does one thing — tell the assistant to read the index and assets in order.
# Project Context EntryThis project uses unified context assets located in `docs/ai/`.## Must read at the start of every task1. `docs/ai/README.md`: context index — what each file does and when to load it2. `docs/ai/glossary.md`: domain terms and naming conventions3. `docs/ai/task-state.md`: current task state and pending decisions## Read on demand by task type- Changes touching architecture or module boundaries: read `docs/ai/architecture.md` first- Writing business code: read `docs/ai/coding-standard.md` first- Troubleshooting production or environment issues: read the relevant playbook under `docs/ai/playbooks/`## Hard constraints- Only modify files directly related to the current task- For changes to database schema, external interfaces, or the permission model, propose a plan first and execute only after human confirmation- Before finishing a task, write new conclusions and lessons learned back to `docs/ai/`, following the memory-write protocol defined there
This launcher is usually only a few dozen lines, yet it covers most loading needs. If a subdirectory needs to add module-specific rules, it can carry just a few lines of “proximity notes,” again pointing to the unified assets:
# payment module — additional rules- General standards: see the repo-root entry and `docs/ai/`- All monetary calculations in this module must use integer cents; floating point is forbidden- Database changes in this module must pass plan review first
This forms a clear priority chain — “global rules → general standards → module exceptions” — so the assistant can load the appropriate context from any directory without getting lost in a massive entry file.
4. Step Two: Memory and Workflows Must Exist Only Once
Once the assets are consolidated, the thing most likely to fork again is long-term memory.
Memory inherently carries a “write” action: one end finishes a task and updates its progress, then the other end goes and updates its own copy, and a conflict is nearly inevitable. A workable solution is to model memory as a “single-writer, multi-reader” file and constrain both ends’ behavior with a protocol.
# Task Memory- Last updated: 2025-06-01- Maintenance rule: both sides may append new entries; before modifying or closing an existing entry, check its status first — never overwrite it directly## In Progress- [in-progress] T-128 payment callback idempotency rework: 10% canary rolled out, awaiting regression results## Pending Decisions- [pending] T-131 whether the reconciliation job's scheduling frequency needs to increase## Confirmed Conclusions- [confirmed] Drop introducing an extra message broker: at current volume, ops cost outweighs the benefit## Rejected Approaches- [rejected] Polling payment status inside the callback handler: causes long thread occupation; switched to event-driven
The accompanying rules can go into the unified standards:
- New tasks are appended under “In Progress”; when finished, change them to “Done” with a short conclusion rather than deleting them. Historical conclusions are valuable to the new assistant too.
- Before modifying any entry, read the latest content first. When both ends write at the same time, appended entries win, and conflicting entries are handed to a human to decide.
- All important lessons learned go into
docs/ai/playbooks/, using a three-part “symptom, root cause, resolution” structure.
Workflows, meanwhile, should be “scripted” as much as possible.
One of the values of an AI coding assistant is that it can assemble a series of operations on the fly from natural language. But if those operations exist only inside one end’s configuration, they are lost when you switch to the other end, or you have to describe them all over again. A more robust approach is to distill repeatedly executed actions into in-repo scripts, so both ends complete the task by “running the script.”
#!/usr/bin/env bash# scripts/ci/verify.sh — shared validation entry point for both endsset -euo pipefailROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"cd "$ROOT"fail=0# 1. Entry file size check: over the threshold means business content is being stuffed in againfor f in entry.md .aux/entry.md; doif [ -f "$f" ] && [ "$(wc -l < "$f")" -gt 60 ]; thenecho "[WARN] $f is $(wc -l < "$f") lines, over the recommended limit of 60"fail=1fidone# 2. Subdirectory entries may only add exceptions, not redefine global standardsif grep -RIn --include='entry.md' -E '^#{1,2} .*[Gg]eneral' . \| grep -vE '^\./(entry\.md|\.aux/entry\.md):'; thenecho "[WARN] subdirectory entry should not redefine global standards"fail=1fi# 3. Memory write-point check: status markers may only appear in the unified task memory fileif grep -RIl --include='*.md' -E '\[(in-progress|pending|confirmed|rejected)\]' docs .aux 2>/dev/null \| grep -v 'docs/ai/task-state.md'; thenecho "[WARN] scattered task-memory write points found; merge them into docs/ai/task-state.md"fail=1fi# 4. Asset dead-link checkgrep -RhoE 'docs/ai/[A-Za-z0-9_./-]+\.md' entry.md .aux docs \| sort -u \| while read -r p; do[ -f "$p" ] || echo "[ERROR] reference to a nonexistent file: $p"doneexit "$fail"
Scripting brings three direct benefits:
- Consistent behavior: Both ends run the same code and no longer depend on their own interpretation of natural language.
- Testability: A script can have unit tests and can be wired into CI — a prompt cannot.
- Auditability: Who ran what and when is recorded in the script output and pipeline logs, rather than being scattered across chat transcripts.
5. Step Three: Keep the Adapter Layer Thin — Point the Way, Do Not Do the Work
After splitting, each assistant keeps only a thin adapter layer, mapped item by item as follows:
| Adaptation item | Question to confirm | Recommended approach |
|---|---|---|
| Entry | Where does the assistant read project instructions from? Does it support layered lookup? Is there a length limit on the instruction chain? | A launcher of a few dozen lines at the root, a few exceptions in subdirectories, with the body pointing to the unified assets |
| Skill/command discovery | From which directories are custom capabilities discovered? What are the naming rules? | Each end uses its own agreed directory; internally only a thin wrapper, with the actual logic calling shared scripts |
| External tool integration | Through what protocol does the assistant connect to external tool services? Which transports does it support? | Centralize the tool-service address in configuration; both ends reuse the same manifest to avoid capability mismatch |
| Hook mechanism | Which events does it support (e.g., session start, before/after file write, before/after command execution)? How does the output protocol signal block vs. allow? | Hooks do just two things: call the shared script, and report the result per that end’s output convention |
| Permission model | Which operations run automatically, which need confirmation, and which are forbidden outright? | Use the same-granularity manifest for both ends, and explicitly document any differences |
| Credential management | Where are keys and tokens stored? | Not in the repo — inject them via environment variables or a credential-hosting service, using the same injection method for both ends |
Two easy-to-overlook details:
First, the skill directories differ across the two ends, but the implementation must be shared. Writing a separate implementation of the same capability for each end is another fork in disguise. The recommended pattern is “thin wrapper + shared implementation”:
# Assistant A side (placed per its convention)skills/review-api/└── SKILL.md # only describes when to use it and its input/output; the body points to docs/ai/coding-standard.md# Assistant B side (placed per its convention)commands/review-api.md # likewise only description and delegation# Shared implementationscripts/review/api-check.sh
The capability logic exists only once; the difference between the two ends shows up only in “how it is discovered and how it is invoked.”
Second, permission-model differences should be recorded explicitly, not silently tolerated. If one end auto-allows file reads while the other asks for confirmation every time, that directly produces a gap in interaction experience and efficiency between the two ends. You can spell this out in the unified standards:
## Execution permission convention (both ends follow this)- Auto-allowed: read-only operations, in-repo text edits, running validation scripts under scripts/- Requires confirmation: installing dependencies, modifying build config, deleting files, running database changes- Forbidden: pushing directly to the trunk branch, reading or using plaintext credentials, calling production write endpoints
If an end cannot support the same granularity of control, make up the gap with a constraint in the launcher and record the difference in docs/ai/README.md, so every user knows the capability boundary of both ends.
Watch out especially for adapter-layer bloat. If one side’s adapter directory starts accumulating concrete judgments about “how a given class of request should be handled in business terms,” the adapter layer is absorbing business logic, and the fork will simply reappear somewhere else. The adapter layer answers only “where to read, what to read, and whether an action is allowed” — it does not answer “how the business is done.”
If the team’s infrastructure runs on a cloud platform, this governance can also be handled with general-purpose capabilities, for example:
- Keys and tokens: Stored centrally in a secret-management service and granted least-privilege access.
- Session and execution logs: Aggregated into centralized logging, making it easier to investigate “why did this run behave differently from the last one.”
- Environments that need isolation: Run through a container platform or function-compute service, so an unrestricted script cannot affect a developer machine or production.
6. Leaving Forks Nowhere to Hide: CI Validation and Governance
Splitting is only the starting point; over time the configuration tends to blend back together. You need a few automatic defenses.
First line: structural validation. The scripts/ci/verify.sh above already covers entry-file size, subdirectory redefinition, scattered memory write points, and asset dead links. It can be wired straight into the pipeline and run on every commit.
Second line: change review. Files under docs/ai/ are the shared source of truth for both ends and should be treated as “critical configuration”:
- Any change goes through the normal code-review process, with at least one colleague familiar with the module confirming it.
- Key conclusions such as architecture decisions and rejected approaches are kept as append-only records rather than overwritten.
- The memory file allows multi-party appends, but state changes must state their basis.
Third line: periodic reconciliation. After both ends run in parallel for a while, an information gap of “one end knows, the other doesn’t” easily appears. You can run a lightweight reconciliation on a fixed cadence:
- Have each end output a “summary of its current understanding of the project” (core rules, in-progress tasks, known risks).
- Compare the differences and judge which are missing assets and which are adapter-layer gaps.
- Add the missing items into
docs/ai/, and fix adapter problems in the corresponding launcher.
This reconciliation is itself a check on “whether it is truly single-sourced.” If the differences are small every time, the split is working; if they keep growing, a second copy of some asset has appeared somewhere.
7. Common Pitfalls
| Pitfall | Symptom | How to avoid it |
|---|---|---|
| Copying the whole directory | No visible problem short term; weeks later the two ends reach contradictory conclusions | Classify before migrating; keep only one copy of project assets |
| Writing the entry file as a long protocol | Truncated by the instruction-chain limit, or crowding out working context | Keep only the index and hard constraints in the entry; move the body into unified assets |
| Ignoring proximity priority | Subdirectory rules conflict with root rules, making assistant behavior unpredictable | Upper layers define the general, lower layers the exceptions; define each rule once |
| Writing a separate skill implementation per end | Behavior diverges for the same capability | Thin wrapper + shared script |
| Dual-writing memory | State overwrites itself and conclusions get lost | Single-writer protocol + CI write-point checks |
| Committing credentials to the repo | Security risk that spreads to both ends in sync | Centralized credential hosting; inject references only |
| Letting the permission model drift | One end has high misoperation risk, the other is too slow | A unified permission manifest, with differences explicitly recorded |
| Pushing business logic into the adapter layer | The fork reappears in a new location | The adapter layer only points the way; it does not make decisions |
8. Acceptance Checklist
Once migration is complete, use these questions to self-check:
- Are there duplicate entries or contradictory descriptions under
docs/ai/? - Is each assistant’s entry file only a few dozen lines, containing only the index and hard constraints?
- Do subdirectory entries only add exceptions, without redefining global standards?
- Is there only one task-memory file and one write protocol?
- Are all repeatedly executed operations scripted, with both ends calling the same script?
- Are skill/command implementations shared, with each end keeping only a thin wrapper?
- Are the permission manifests identical across both ends, with any inconsistencies documented in writing?
- Are all credentials injected through a hosting service, with no plaintext anywhere in the repo?
- Is the structural-validation script wired into the pipeline and able to catch newly introduced forks?
- Is a periodic reconciliation mechanism in place to keep the information gap continuously converging?
Finally, the essence of this work is not “moving the configuration into another tool,” but fully separating project assets from assistant adaptation. Assets are oriented toward the project and share its lifecycle; adaptation is oriented toward the tool and adjusts as the tool evolves. This way, whether you later add a third assistant, replace one of the two ends, or go back to a single end, the accumulated standards, memory, and workflows do not need to be rebuilt — you only write another launcher of a few dozen lines. Migration cost shifts from “a one-time large-scale copy” to “a continuously controllable, small adapter effort,” which is exactly the outcome that true dual-end compatibility should deliver.
