> ## Documentation Index
> Fetch the complete documentation index at: https://prism.ntecdev.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# SPEC-036 v0.2: Scaffolder persona-leak fix — strip runtime identity from composed PRISM.md frontmatter

> Scaffolder persona-leak fix — strip runtime identity from composed PRISM.md frontmatter

<Info>**Status:** `draft` · **Version** `0.2` · Filed 2026-04-25</Info>

# SPEC-036 v0.2 — Scaffolder Persona-Leak Fix

Status: draft
Version: 0.2
Authors: Lola (claude.ai), Frank Tewksbury
Date: 2026-04-25
Related: SPEC-021 (Session Bootstrap Enforcement), commit 09e53ae (persona registry + runtime identity), commit c439ebf (frontmatter strip for Prism project)

## 1. Summary

The `compose_prism()` function in `mcp/sync_helper.py` bakes a `persona: <n>` line into every scaffolded PRISM.md's YAML frontmatter. Because CLAUDE.md's boot sequence reads PRISM.md (step 1) before calling `prism_start` (step 3), agents absorb the frontmatter persona as their identity before the MCP identity-resolution chain has a chance to fire. This spec defines the fix: remove the persona line from composed frontmatter, default all persona references to "Bot" for consistency, and clarify the boundary between the `project.persona` DB field (project-level metadata for historical/reporting purposes) and runtime identity (session-level, resolved by `prism_start`).

## 2. Problem Statement

### 2.1 Observed Symptoms

Two independent failure modes confirmed by different agents on different machines:

**Offline mode (Lofanda, signal 12de5278):** Candi's Windows Claude Code session launched with MCP offline. The agent read PRISM.md's `persona: Lola` frontmatter and adopted Lola as its identity for the entire session, bypassing persona-registry elicitation entirely. No self-correction occurred because `prism_start` never ran.

**Online mode (Donna, signal 924ca769, mini2):** Frank ran `coder.ps1 -as Candi` on a fresh Windows local install. All containers were healthy. Claude Code booted, read PRISM.md's `persona: Lola` frontmatter at step 1 of the BIOS sequence, and greeted as Lola. After `prism_start` eventually fired at step 3, the agent self-corrected to Candi — but the initial greeting and early turns used the wrong identity.

### 2.2 Root Cause

`mcp/sync_helper.py:163` — the `compose_prism()` function emits:

```
f"persona: {persona}\n"
```

into the YAML frontmatter header of every composed PRISM.md. This line is populated from the `project.persona` database column, which defaults to `"Lola"` (models/project.py:22).

The CLAUDE.md boot sequence (Ring 1 BIOS, §Boot Sequence step 1) instructs agents to "Read `./PRISM.md`" as their first action. Agents treat YAML frontmatter as authoritative metadata. By the time step 3 fires (`prism_start`), the agent has already internalized the frontmatter persona.

### 2.3 Why the Existing Identity Resolution Doesn't Help

The `prism_start` identity resolution chain (commit 09e53ae) works correctly:

1. Explicit `identity` argument (re-entry after elicitation)
2. `PRISM_AGENT_IDENTITY` env var (from `coder --as <n>`)
3. Structured identity-elicitation response with `prism_whois` results

All three paths fire only at BIOS step 3 (`prism_start` MCP call). The static file read at step 1 preempts them. In offline mode, step 3 never fires at all.

### 2.4 Scope of the Leak

The `persona` value flows through six code paths into composed frontmatter:

| Location                                    | Default     | Role                            |
| ------------------------------------------- | ----------- | ------------------------------- |
| `models/project.py:22`                      | `"Lola"`    | DB column default               |
| `schemas/project.py:13` (ProjectCreate)     | `"Lola"`    | API schema default              |
| `routers/projects.py:24` (BootstrapRequest) | `"Lola"`    | HTTP endpoint default           |
| `bootstrap_service.py:43`                   | `"Lola"`    | Service layer default           |
| `scaffold_service.py:252` (build\_manifest) | `"Donna"`   | Scaffold default (inconsistent) |
| `sync_helper.py:163` (compose\_prism)       | from caller | Frontmatter emitter (the bug)   |

## 3. Design Decision

### 3.1 Chosen Approach: Option D (immediate symptom fix) + default alignment

Two changes ship together:

1. **Strip the `persona: {persona}` line** from `compose_prism()`'s YAML frontmatter header. The function signature retains the `persona` parameter for backward compatibility — callers still pass it, but the value is no longer emitted into the composed file. This fixes both the online and offline failure modes with no migration or schema change.

2. **Align all persona defaults to `"Bot"`** across the codebase. Every location that currently defaults to `"Lola"` or `"Donna"` changes to `"Bot"`. This ensures that when the field appears in DB records, API responses, or logs, it reads as a clearly non-identity placeholder — no one will mistake it for a real agent persona.

### 3.2 Rationale

The `persona` line in PRISM.md frontmatter served a purpose in the pre-registry era: it was the only mechanism for conveying "which agent owns this project" to offline readers. With the persona registry (commit 09e53ae) and runtime identity resolution now live, this static-file signal is redundant and actively harmful — it creates a race condition between static-file identity (step 1) and runtime identity (step 3).

Defaulting to `"Bot"` rather than any real persona name eliminates confusion: if an agent ever encounters `persona: Bot` in metadata, it's clearly a system default, not an identity to adopt.

Precedent: Lofanda's commit c439ebf already stripped the `persona:` line from the Prism project's own PRISM.md and `context/_ACTIVE_CONTEXT.md` with no breakage. This spec generalizes that fix to the scaffolder so all future projects are clean.

### 3.3 The `project.persona` DB field — kept for historical/reporting purposes

Per Frank's ruling: the `project.persona` column in the `projects` table stays. Its role is **historical attribution and future reporting** — e.g., which persona originally bootstrapped a project, join queries across projects and sessions for long-term memory analysis. It is explicitly NOT runtime identity. The authoritative runtime identity backbone is the Redis session plane (persona registry + `prism_start` identity resolution).

No ADR needed for the field itself — it keeps its current role, just stops leaking into agent-visible files. If a future rename to `project_owner` or similar is desired for clarity, that's a cosmetic migration that can ride any convenient release.

## 4. Implementation

### 4.1 Change 1: Strip persona from compose\_prism header (REQUIRED)

File: `mcp/sync_helper.py`, function `compose_prism()`

Remove the line:

```python theme={null}
f"persona: {persona}\n"
```

from the `header` string in `compose_prism()`. The composed PRISM.md frontmatter will contain `project`, `pid`, `ptype`, `methodology_version`, `composed_from`, `composed_at`, and `override_chain` — but no `persona`.

The `persona` parameter stays in the function signature. No callers need to change.

### 4.2 Change 2: Align all persona defaults to "Bot" (REQUIRED)

Update the default value from `"Lola"` or `"Donna"` to `"Bot"` in these locations:

| File                                           | Line                     | Current   | New     |
| ---------------------------------------------- | ------------------------ | --------- | ------- |
| `backend/app/models/project.py:22`             | `default="Lola"`         | `"Lola"`  | `"Bot"` |
| `backend/app/schemas/project.py:13`            | `persona: str = "Lola"`  | `"Lola"`  | `"Bot"` |
| `backend/app/routers/projects.py:24`           | `persona: str = "Lola"`  | `"Lola"`  | `"Bot"` |
| `backend/app/services/bootstrap_service.py:43` | `persona: str = "Lola"`  | `"Lola"`  | `"Bot"` |
| `backend/app/services/scaffold_service.py:252` | `persona: str = "Donna"` | `"Donna"` | `"Bot"` |
| `mcp/server.py` (bootstrap\_project)           | `persona: str = "Lola"`  | `"Lola"`  | `"Bot"` |
| `mcp/server.py` (prism\_clone)                 | `persona: str = "Lola"`  | `"Lola"`  | `"Bot"` |
| `mcp/server.py` (prism\_create)                | `persona: str = "Lola"`  | `"Lola"`  | `"Bot"` |
| `mcp/server.py` (prism\_sync\_bios fallback)   | `"Lola"`                 | `"Lola"`  | `"Bot"` |

No database migration needed — existing rows keep their current persona values (which correctly reflect who bootstrapped them). Only new projects and API calls without an explicit persona will default to `"Bot"`.

### 4.3 Change 3: Re-sync existing projects (RECOMMENDED)

After the code changes ship, running `prism_sync_bios(files="prism")` on any project will recompose its PRISM.md without the persona line. For immediate unblock on affected machines (e.g., mini2), a manual one-line edit to strip `persona: Lola` from the existing PRISM.md frontmatter is equivalent.

### 4.4 Change 4: context/\_ACTIVE\_CONTEXT.md template (VERIFY — already clean)

The `_active_context_md()` function in `scaffold_service.py` does not currently emit a persona line — confirmed clean. No change needed.

## 5. Verification

### 5.1 Test: Fresh scaffold produces no persona in frontmatter

After shipping Changes 1-2:

1. `prism_create(name="TestNoPersona")`
2. Read the scaffolded `PRISM.md`
3. Assert: YAML frontmatter contains no `persona:` key
4. Assert: DB record has `persona = "Bot"`
5. `prism_destroy(pid=<new_pid>, delete_dir=True)` to clean up

### 5.2 Test: Re-sync strips persona from existing project

1. Manually add `persona: TestLeak` to an existing project's PRISM.md frontmatter
2. `prism_sync_bios(pid=<pid>, files="prism", force=True)`
3. Read the recomposed PRISM.md
4. Assert: YAML frontmatter contains no `persona:` key

### 5.3 Test: Identity resolution works end-to-end post-fix

1. Launch `coder --as Candi` on a project whose PRISM.md has no persona in frontmatter
2. Observe: agent reads PRISM.md at step 1 — no identity signal absorbed
3. Observe: `prism_start` fires at step 3, reads `PRISM_AGENT_IDENTITY=Candi` from env
4. Assert: agent greets as Candi from the first turn, no identity confusion

### 5.4 Test: Offline mode with no persona in frontmatter

1. Launch Claude Code with MCP server unreachable (offline mode)
2. Agent reads PRISM.md — no persona in frontmatter
3. Agent falls through to offline-mode fallback (PRISM.md §6)
4. Assert: agent does NOT adopt any specific persona name from static files

## 6. Migration

No database migration required. No schema changes. The `project.persona` DB column and all associated schema/router/service code remain untouched — they continue to serve their role as historical project metadata. Existing rows keep their current values.

Only the defaults change (to "Bot"), affecting new projects created after the fix ships. Existing projects with `persona:` in their PRISM.md frontmatter will self-heal on the next `prism_sync_bios` run.

## 7. Resolved Questions

* **Q1 (resolved):** The `project.persona` DB field stays. Its purpose is historical attribution and future reporting/joins — not runtime identity. The Redis session plane (persona registry + `prism_start`) is the authoritative runtime identity backbone. No ADR needed.
* **Q2 (resolved):** All defaults align to `"Bot"` — a clearly non-identity placeholder that no agent will mistake for a real persona. Ships as part of this spec, not deferred.

Status: draft
