# T-1100: Isolation Patterns RCA — Five Patterns Reconciled

**Task:** T-1100 (Inception: reconcile FIVE isolation patterns, G-031)
**Generated:** 2026-04-11
**Scope:** Read-only RCA. No source files modified.

---

## Executive Summary

The five "isolation patterns" are not five competing architectures. They collapse into:
- **One correct mechanism** (vendored dir via `do_vendor`) with two triggers (fw init / fw vendor)
- **One correct dispatch wrapper** (project-detecting shim) orthogonal to the above
- **Two anti-patterns** (manual cp -r, symlink) that produce broken or bloated state
- **One undiscovered sixth antipattern** (nested `.agentic-framework`) caused by running `fw init` on already-initialized projects

**Recommendation:** GO — adopt `fw vendor` (Pattern 2) as the single documented isolation mechanism, with the shim (Pattern 3) as the standard global dispatch. Document Patterns 4 and 5 as deprecated anti-patterns. Add init guard against Pattern 6.

---

## Phase 1 — Pattern Enumeration with Mechanism Details

### Pattern 1: "Vendored plain dir" (old fw init)

**Task description:** `<project>/.agentic-framework/` with framework files committed into project git, no `.git` inside. Supposedly visible in `proxmox-ring20-management`. Created by an earlier `fw init` before shim migration.

**Mechanism:** `lib/init.sh:do_init()` always calls `do_vendor --target "$target_dir"` (line 110) unless `.agentic-framework/` already exists. `do_vendor()` copies specific include dirs (bin, lib, agents, web, docs, .tasks/templates, FRAMEWORK.md, metrics.sh) via rsync with exclusions.

**On-disk artifact:** `<project>/.agentic-framework/` containing `bin/fw`, `lib/*.sh`, `agents/`, `web/`, `docs/`, `VERSION` file. No `.git` inside (excluded by rsync).

**.framework.yaml shape (from init):**
```yaml
project_name: <name>
version: <FW_VERSION>
provider: claude
initialized_at: <timestamp>
upstream_repo: <auto-detected from FRAMEWORK_ROOT git remote>
```
No `framework_path` field (removed at T-498).

**fw resolution:** `resolve_framework()` in bin/fw checks `$PROJECT_ROOT/.agentic-framework/FRAMEWORK.md` (T-498 path, line 91-94). If found, FRAMEWORK_ROOT = `$PROJECT_ROOT/.agentic-framework`. The vendored `bin/fw` is then the authoritative CLI.

**fw upgrade behavior:** Runs all 10 steps. Step 4b syncs hook scripts, lib/*.sh, agent scripts, bin/fw, VERSION into `.agentic-framework/`. Effectively keeps the vendored dir current. Step 4c migrates `~/.local/bin/fw` from symlink to shim if needed.

**What breaks:** Nothing, if the project ran `fw upgrade` after T-498. If it predates T-498 and still has a `framework_path:` field in `.framework.yaml`, the resolution falls through to the legacy path (line 97-105 in bin/fw). But `do_vendor` doesn't write `framework_path:`, so this is only a concern for very old projects that pre-date the vendor step entirely.

**Discovery:** `proxmox-ring20-management` not accessible in this environment. Based on source code, Pattern 1 and Pattern 2 produce **identical on-disk artifacts** (same `do_vendor()` function). The difference is whether `fw init` triggered it (Pattern 1) or an explicit `fw vendor` call (Pattern 2). **No structural distinction exists.**

---

### Pattern 2: "fw vendor" explicit subcommand

**Mechanism:** `do_vendor()` defined in `bin/fw:118-278`. Called explicitly as `fw vendor [--target DIR]`. Same rsync logic as what `fw init` calls internally.

**Include list (exact):**
```
bin, lib, agents, web, docs, .tasks/templates, FRAMEWORK.md, metrics.sh
```

**Exclude list (within copied dirs):**
```
__pycache__, *.pyc, .DS_Store, lib/ts/src, lib/ts/tsconfig.json,
lib/ts/package.json, lib/ts/package-lock.json, lib/ts/node_modules
```

Notably NOT excluded: `.agentic-framework` itself (relevant to Pattern 6 discovery below).

**On-disk result:** `<project>/.agentic-framework/` ~56MB. VERSION file written with current `$FW_VERSION`.

**Hook references:** `.agentic-framework/bin/fw hook <name>` (generated by `generate_claude_code_config()` when `fw_prefix = ".agentic-framework/bin/fw"`)

**fw resolution:** Same as Pattern 1.

**Evidence from disk survey:**

| Project | Vendored VERSION | YAML version | Size | Status |
|---------|-----------------|--------------|------|--------|
| /opt/052-KCP | 1.5.246 | 1.5.246 | 56M | OK |
| /opt/051-Vinix24 | 1.5.246 | 1.5.246 | 56M | OK |
| /opt/termlink | 0.9.585 | 0.9.585 | 58M | STALE — T-909 fixed symlink but didn't upgrade version |
| /opt/053-ntfy | 1.5.246 | 1.5.246 | 56M | OK |
| /opt/001-sprechloop | 1.5.246 | 1.5.246 | 7.4M | Older (smaller vendor at time of init) |
| /opt/025-WokrshopDesigner | 1.1.14 | 1.5.246 | 56M | VERSION DRIFT — yaml updated but vendored scripts not |
| /opt/050-email-archive | 1.5.246 | 1.5.246 | 165M | BLOATED — nested .agentic-framework inside |
| /opt/150-skills-manager | 1.5.246 | 1.5.246 | 9.9M | OK |

**Key finding: Patterns 1 and 2 are mechanically identical.** `fw init` calls `do_vendor` internally; `fw vendor` calls it explicitly. The "two patterns" are an artifact of documentation and history, not architecture. Both are Pattern 2.

---

### Pattern 3: "Project-detecting shim"

**Mechanism:** `bin/fw-shim` (installed by `fw upgrade` step 4c to `~/.local/bin/fw`) walks up from `$CWD` looking for:
1. `<dir>/bin/fw + FRAMEWORK.md` → framework repo itself
2. `<dir>/.agentic-framework/bin/fw` → consumer project with vendored framework

Then `exec`s the found binary. The shim does NOT provide isolation — it provides **dispatch**.

**When installed:** `fw upgrade` step 4c detects if `~/.local/bin/fw` is a symlink pointing to `*.agentic-framework/bin/fw` or `*/bin/fw` and replaces it with the shim. One-time migration.

**Current state:** `/root/.local/bin/fw` = shim (confirmed via `file` — bash script containing `find_fw`). The shim is installed.

**How it composes with Pattern 2:** Shim finds `<project>/.agentic-framework/bin/fw`, execs it. The vendored `bin/fw` then resolves FRAMEWORK_ROOT to `<project>/.agentic-framework`. These two mechanisms compose correctly and are designed to work together.

**What "ring20-dashboard with shim" actually means:** The shim was installed globally, but `/opt/ring20-dashboard` has no `.framework.yaml` and no `.agentic-framework`. So the shim would FAIL for ring20-dashboard (no framework found in directory tree). Ring20-dashboard was described in G-025/G-028 context as `/root/ring20-dashboard` — a different location not accessible here. The onboarding incident described a project that had `.framework.yaml` but no `.agentic-framework/`. Without the vendored dir, the shim cannot dispatch and the project has no isolation. Pattern 3 alone (without Pattern 2) is incomplete.

**What breaks without Pattern 2:** If `~/.local/bin/fw` is the shim but the project has no `.agentic-framework/`, `fw` fails with "No framework project detected." Pattern 3 requires Pattern 2 to function.

---

### Pattern 4: "Manual cp -r" (~349MB)

**Mechanism:** `cp -r /opt/999-Agentic-Engineering-Framework <somewhere>/.agentic-framework` with no exclusions.

**Evidence:** `/root/.agentic-framework` = **327MB** (confirmed). The framework repo itself is 349MB. The global install at `/root/.agentic-framework` is a near-full copy. It contains:
- `.git/` history from the framework repo (not present in `do_vendor` output)
- `.context/`, `.tasks/` — the framework's own operational state
- All tests, config, CI files

**VERSION:** 1.4.520 — the global install is STALE (framework is now 1.5.246 on the git branch, VERSION file says 1.5.246). This install last matched a version from around the 1.4.x era.

**fw resolution impact:** `resolve_framework()` checks `$PROJECT_ROOT/.agentic-framework/FRAMEWORK.md` first. If a project's `.agentic-framework` was created by manual cp, it will include FRAMEWORK.md and pass the check — resolution works, but the vendored dir is bloated and may carry contaminated `.git` state.

**fw upgrade behavior:** Step 4b syncs individual scripts into the vendored dir but doesn't clean up bloat. The bloated dir stays at 327MB+ as scripts are patched in. No `--delete` is used in step 4b (it copies file by file, not rsync). The dir never shrinks.

**What breaks:**
1. `git` commands inside the project may accidentally pick up the vendored `.git` if git walks up the directory tree
2. Framework's own `.context/`, `.tasks/` inside the vendored copy can confuse agents that check for `.framework.yaml` or `.tasks/`
3. Massive disk waste (327MB vs 56MB expected)

**Current global install status:** The `/root/.agentic-framework` is now UNUSED by the shim (shim dispatches to project-local `.agentic-framework/bin/fw`). Step 4c of `fw upgrade` still syncs hook scripts and lib into it for backward compatibility, but no active session uses it as the primary fw.

---

### Pattern 5: "Symlinked .agentic-framework"

**Mechanism:** `ln -s /opt/999-Agentic-Engineering-Framework /opt/termlink/.agentic-framework`

**Pre-T-909 state:** `/opt/termlink/.agentic-framework` → `/opt/999-Agentic-Engineering-Framework`. The symlink pointed to the FULL framework repo, not to a vendored copy.

**fw resolution failure mode:** When `fw` is invoked from `/opt/termlink` with a symlink:
1. `FW_REAL_PATH = readlink -f $0` resolves through the symlink to the framework's `bin/fw`
2. `FW_BIN_DIR = dirname(FW_REAL_PATH)` = `/opt/999-Agentic-Engineering-Framework/bin`
3. `resolve_framework()` candidate = `/opt/999-Agentic-Engineering-Framework`
4. PROJECT_ROOT = `/opt/termlink` (from `find_project_root()`)
5. FRAMEWORK_ROOT = `/opt/999-Agentic-Engineering-Framework` ← **wrong project's framework root**

The hooks would execute using the framework's own `.context/`, `.tasks/` — causing state pollution in the framework repo when termlink operations run.

**Post-T-909 state:** `/opt/termlink/.agentic-framework` = **real directory**, 58MB. Vendored copy. `file` confirms "directory" not "symbolic link". T-909 completed 2026-04-11T12:21:19Z.

**fw vendor self-reference detection (T-680):** `do_vendor()` has a guard: if source and target resolve to the same canonical path, it attempts to find the upstream framework via `$FW_BIN_DIR/..`. This prevents the symlink from creating infinite recursion. But the self-reference check is NOT the same as "this is a symlink" — the detection works at the canonical path level.

**Current status:** Pattern 5 is ELIMINATED in this environment. The symlink antipattern is documented as wrong but not blocked by any gate.

---

### Pattern 6 (Discovered): "Nested .agentic-framework"

**Not in the original five-pattern list.** Discovered during Phase 1 disk survey.

**Evidence:**
- `/opt/050-email-archive/.agentic-framework/.agentic-framework` exists (165MB total vs 56MB expected)
- `/opt/150-skills-manager/.agentic-framework/.agentic-framework` exists
- `/root/.agentic-framework/.agentic-framework` exists
- `/opt/999-Agentic-Engineering-Framework/.agentic-framework` exists (framework vendoring itself)

**Mechanism (hypothesized):** Running `fw init` inside a project that already has `.agentic-framework/` — or using `--force` — triggers `do_vendor --target <project>` again. If the SOURCE framework itself has an `.agentic-framework/` at its root (the framework repo does: `/opt/999-Agentic-Engineering-Framework/.agentic-framework`), and if the rsync copies the framework's root-level dot-directories... but wait: `do_vendor`'s include list is explicit (`bin`, `lib`, `agents`, etc.) — `.agentic-framework` is NOT in the include list, so rsync wouldn't copy it.

**Alternative mechanism:** Someone ran `fw init <project>/.agentic-framework` — initializing the vendored framework dir itself as a project. `fw init` would then call `do_vendor --target <project>/.agentic-framework`, creating `<project>/.agentic-framework/.agentic-framework`. This is the most plausible explanation.

**Impact:** Doubles disk usage. The nested `.agentic-framework` is dead code — nothing resolves to it. But `do_vendor` won't clean it up (no `--delete` for the outer `.agentic-framework`, only for its listed include dirs).

**Missing gate:** `fw init` has no guard that checks "is this target directory already inside a `.agentic-framework/`?" If such a guard existed, the nested copy would be prevented.

---

## Phase 2 — Migration Matrix

The 5×5=20 migration space collapses because Patterns 1 and 2 are identical. Realistic migrations:

### Migrations that actually occur

| From | To | Trigger | Steps | Breakage risk |
|------|----|---------|-------|---------------|
| 5 (symlink) | 2 (vendored) | T-909 complete | 1. Remove symlink<br>2. `fw vendor` | Low — T-909 succeeded |
| 4 (manual cp) | 2 (vendored) | Operator decision | 1. Backup old dir<br>2. `rm -rf .agentic-framework`<br>3. `fw vendor` | Medium — old dir may have been used as scratch |
| 3 (shim-only) | 2+3 (vendored+shim) | `fw vendor` | 1. `cd <project>`<br>2. `fw vendor` | Low — additive |
| global symlink | 3 (shim) | `fw upgrade` step 4c | Automatic — upgrade detects symlink, installs shim | Low — automatic |
| 6 (nested) | 2 (clean) | Manual cleanup | 1. Remove nested `.agentic-framework` inside outer one | None — nested is dead code |

### Migrations that DON'T occur in practice

| From | To | Why not |
|------|----|---------|
| 1 → 2 | — | Same artifact; no migration needed |
| 2 → 1 | — | No reason to downgrade to "old-style" |
| 2 → 4 | — | Would be deliberate regression |
| 2 → 5 | — | Would be deliberate regression |
| 5 → 4 | — | Replacing one antipattern with another |

### The real migration concern (G-028)

`fw upgrade` can be run from different contexts:
- If `FRAMEWORK_ROOT` resolves to `/opt/999-Agentic-Engineering-Framework` (the real framework) → step 4b syncs from the correct source
- If `fw` was invoked via a stale path (global install, old symlink) → step 4b syncs from the WRONG source

The `upstream_repo` field in `.framework.yaml` is NOT used by `fw upgrade` to select the source — `fw upgrade` uses `$FRAMEWORK_ROOT` (the currently active framework). If these diverge (G-028), upgrade silently syncs from the wrong source.

---

## Phase 3 — Canonical Recommendation

### The architecture in two sentences

**For project isolation:** `fw vendor` (explicit) or `fw init` (which calls it) produces `<project>/.agentic-framework/` — a self-contained vendored copy of the framework at a specific version. This is the ONLY correct isolation mechanism.

**For global dispatch:** The shim at `~/.local/bin/fw` finds the project-local `fw` binary from any directory, eliminating the global install dependency. This is ORTHOGONAL to isolation — it dispatches, not isolates.

### Why the other patterns fail

| Pattern | Fatal flaw | Recovery |
|---------|-----------|---------|
| 1 (old init) | None — same as Pattern 2 | None needed |
| 4 (manual cp) | .git + .context contamination, no exclusions, no VERSION, no upgrade path | Replace with `fw vendor` |
| 5 (symlink) | PROJECT_ROOT resolution failure, shared state pollution | Replace with `fw vendor` (T-909 model) |
| 6 (nested) | Dead code, disk waste, confuses any tool that walks `.agentic-framework/` | Remove nested copy |

### Why Pattern 2 (fw vendor) is canonical

1. **Correct exclusions:** Excludes `.git`, `__pycache__`, `lib/ts/node_modules` — avoids contamination
2. **VERSION tracking:** Writes VERSION file; `fw upgrade` step 4b syncs it; `fw doctor` can check drift
3. **Self-reference protection:** T-680 guard prevents recursive vendoring
4. **Hook paths are correct:** Generated `.claude/settings.json` uses `.agentic-framework/bin/fw hook <name>` — project-relative, not absolute
5. **Size is controlled:** ~56MB vs 327MB (manual cp) — 6× smaller
6. **Upgrade path is clear:** `fw upgrade` step 4b syncs all vendored scripts atomically
7. **Shim compatibility:** Shim's `find_fw()` looks for `.agentic-framework/bin/fw` — direct match

### Why Pattern 3 (shim) should co-exist, not replace

The shim REQUIRES a vendored dir to dispatch to. It's a usability wrapper, not an isolation mechanism. Its purpose: `fw` works from any directory without the user knowing which project's `fw` to call. Without Pattern 2 as the backing, Pattern 3 produces an error.

**Correct composition:** `fw init` → Pattern 2 (vendored dir). `fw upgrade` → Pattern 3 (shim migration if global symlink). Projects use both. Neither replaces the other.

### Deprecation path for dead patterns

| Pattern | Status | Action |
|---------|--------|--------|
| 4 (manual cp) | DEPRECATED | Document in CLAUDE.md as anti-pattern; `fw doctor` should detect oversized `.agentic-framework` (>100MB) |
| 5 (symlink) | DEPRECATED | Document; `fw doctor` already checks for symlink-as-`.agentic-framework` |
| 6 (nested) | UNDOCUMENTED BUG | Add guard in `fw init`: detect if target is inside `.agentic-framework/` and abort |

---

## Phase 4 — Recommendation

**Recommendation: GO — Pattern 2 (fw vendor) as canonical, Pattern 3 (shim) as standard dispatch**

### Rationale

1. **Pattern 1 = Pattern 2** — collapsing the two-trigger concern eliminates false complexity. Document: "`fw init` vendors the framework automatically; `fw vendor` does it explicitly for an existing project. Both produce identical results."

2. **Pattern 3 is orthogonal** — document the shim as "how `fw` works globally" separate from "how framework code is isolated per project."

3. **Pattern 4 is already diagnosed** — `/root/.agentic-framework` at 327MB is a live example. It's unused by the shim. A `fw doctor` check for oversized vendored dirs would catch future recurrence.

4. **Pattern 5 is eliminated** — T-909 fixed `/opt/termlink`. No symlink consumers remain. `fw vendor`'s T-680 self-reference guard prevents accidental re-creation. Document as anti-pattern.

5. **Pattern 6 is a new bug** — Not in the original five-pattern list. Requires a guard in `fw init` to prevent initializing inside `.agentic-framework/`. Severity: low (dead code), but disk waste is real (165MB vs 56MB for 050-email-archive).

### Migration plan summary

| Consumer | Current pattern | Migration | Command |
|----------|---------------|-----------|---------|
| /opt/termlink | 2 (OK — post T-909) | None needed | — |
| /opt/052-KCP | 2 (OK) | None needed | — |
| /opt/051-Vinix24 | 2 (OK) | None needed | — |
| /opt/025-WokrshopDesigner | 2 with VERSION drift | Re-vendor | `fw upgrade /opt/025-WokrshopDesigner` |
| /opt/050-email-archive | 2 + nested (Pattern 6) | Remove nested | `rm -rf /opt/050-email-archive/.agentic-framework/.agentic-framework` |
| /opt/150-skills-manager | 2 + nested (Pattern 6) | Remove nested | `rm -rf /opt/150-skills-manager/.agentic-framework/.agentic-framework` |
| /root/.agentic-framework | 4 (bloated global) | Accept as legacy | No active consumers; `fw upgrade` keeps hooks current |
| ring20-dashboard | 3 only (no vendored dir) | Add Pattern 2 | `fw vendor /root/ring20-dashboard` (if project is to be onboarded) |

### Build tasks implied by this GO decision

1. **T-1099** (write docs/consumer-project-setup.md) — already created, covers G-030
2. **New task:** Add `fw doctor` checks for (a) oversized `.agentic-framework` >100MB, (b) `.agentic-framework` is a symlink, (c) nested `.agentic-framework` inside vendored dir
3. **New task:** Add guard in `fw init` to abort if `$target_dir` contains `.agentic-framework` in its path
4. **New task:** Update CLAUDE.md Quick Reference to add `fw vendor` (currently missing), clarify it's the same as what `fw init` does

### Go/No-Go assessment

**GO if:**
- ✅ A canonical mechanism exists and is already correct: `do_vendor()` via `fw vendor` / `fw init`
- ✅ The shim is orthogonal and composes correctly with the vendored dir model  
- ✅ All currently-working consumer projects already use Pattern 2
- ✅ The broken patterns (4, 5) are either already fixed (5) or unused by active sessions (4)
- ✅ No rewrite needed — documentation + fw doctor checks + fw init guard are additive

**NO-GO if:**
- ❌ No single canonical mechanism existed (not the case — do_vendor is correct)
- ❌ Migration required rewriting consumer projects (not the case — they're already on Pattern 2)

**VERDICT: GO**

---

## Key Evidence Summary

| Finding | Source | Impact |
|---------|--------|--------|
| Patterns 1+2 are identical (do_vendor) | lib/init.sh:110, bin/fw:118-278 | Collapse 5 patterns to 4 real ones |
| Pattern 3 requires Pattern 2 | bin/fw-shim:18-34 | Shim is dispatch, not isolation |
| /root/.agentic-framework = 327MB (Pattern 4) | du -sh | 6× bloat vs expected 56MB |
| /opt/termlink fixed post-T-909 | file .agentic-framework = directory | Pattern 5 eliminated |
| 3 projects have nested .agentic-framework | Disk survey | Pattern 6 discovered |
| /opt/025-WokrshopDesigner VERSION drift | Survey: vendored=1.1.14, yaml=1.5.246 | fw upgrade step 4b has gap |
| fw upgrade step 4c one-time shim migration | lib/upgrade.sh:448-478 | Shim is auto-installed, not manual |
| do_vendor excludes .agentic-framework | bin/fw:193-202 | Pattern 6 not caused by do_vendor itself |
| Global install unused by shim post-upgrade | bin/fw-shim:find_fw | /root/.agentic-framework is stranded legacy |

---

## Assumptions Tested

| Assumption | Verdict | Evidence |
|-----------|---------|---------|
| A-1: Five patterns not all equivalent | CONFIRMED | Patterns 1=2 (same code), 3 orthogonal, 4+5 wrong |
| A-2: Canonical recommendation exists | CONFIRMED | do_vendor via fw vendor is the correct mechanism |
| A-3: Portability concern is real | CONFIRMED | .framework.yaml version field + fw upgrade step 4b provide version pinning |
| A-4: Migrations are non-trivial | PARTIALLY CONFIRMED | Most working projects already on Pattern 2; only edge cases (nested, drift) need attention |
