Status: draft · Version 0.2 · Filed 2026-04-25
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:
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:
- Explicit
identity argument (re-entry after elicitation)
PRISM_AGENT_IDENTITY env var (from coder --as <n>)
- 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
Two changes ship together:
-
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.
-
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
File: mcp/sync_helper.py, function compose_prism()
Remove the line:
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:
prism_create(name="TestNoPersona")
- Read the scaffolded
PRISM.md
- Assert: YAML frontmatter contains no
persona: key
- Assert: DB record has
persona = "Bot"
prism_destroy(pid=<new_pid>, delete_dir=True) to clean up
5.2 Test: Re-sync strips persona from existing project
- Manually add
persona: TestLeak to an existing project’s PRISM.md frontmatter
prism_sync_bios(pid=<pid>, files="prism", force=True)
- Read the recomposed PRISM.md
- Assert: YAML frontmatter contains no
persona: key
5.3 Test: Identity resolution works end-to-end post-fix
- Launch
coder --as Candi on a project whose PRISM.md has no persona in frontmatter
- Observe: agent reads PRISM.md at step 1 — no identity signal absorbed
- Observe:
prism_start fires at step 3, reads PRISM_AGENT_IDENTITY=Candi from env
- Assert: agent greets as Candi from the first turn, no identity confusion
5.4 Test: Offline mode with no persona in frontmatter
- Launch Claude Code with MCP server unreachable (offline mode)
- Agent reads PRISM.md — no persona in frontmatter
- Agent falls through to offline-mode fallback (PRISM.md §6)
- 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