/** * Persona-contract injection at launch (North Star A3b). * * A spawned fleet agent should boot already knowing WHO it is: its class's role * contract (mandate + boundaries). The companion goal A3a exports the agent's * resolved class into the pane env as `MOSAIC_AGENT_CLASS`; here we read that * class at launch (composeContract → system prompt) and inject the resolved * persona contract so the identity is resident from the agent's first turn. * * OVERRIDE-AWARE: resolution goes through fleet-personas' resolver, so a * user-customized persona in the PRESERVE-protected `fleet/roles.local/` layer * WINS over the baseline `fleet/roles/` of the same class. That is the * launch-time proof of AC-NS-7 — a customized persona actually reaches the model * when the agent boots, not just in `mosaic fleet persona show`. * * Tolerant by contract (mirrors readFleetCommsBlock): an empty/missing class, an * unknown class, or a missing role file all yield '' so the launcher no-ops * silently. This MUST never throw during launch. * * Standalone module (no fleet.ts import) to keep launch.ts's prompt path free of * the heavy fleet command module; it depends only on the lightweight persona * resolver. */ import { resolvePersonaSync, defaultRolesDir, defaultOverrideDir, } from '../commands/fleet-personas.js'; /** * Resolve `klass`'s persona contract (override-aware) and render it as a * clearly-delimited launch block. Returns '' on any miss (falsy class, unknown * class, missing/unreadable file) so composeContract can push it unconditionally * and have it no-op silently. Never throws. */ export function readPersonaContractBlock(mosaicHome: string, klass: string | undefined): string { if (!klass || !klass.trim()) return ''; let resolved: ReturnType; try { resolved = resolvePersonaSync(klass.trim(), { rolesDir: defaultRolesDir(mosaicHome), overrideDir: defaultOverrideDir(mosaicHome), }); } catch { // Best-effort onboarding: a resolver hiccup must not abort the launch. return ''; } if (!resolved) return ''; const layerNote = resolved.layer === 'override' ? '_(resolved from the `fleet/roles.local/` override layer — wins over baseline)_' : '_(resolved from the baseline `fleet/roles/` layer)_'; return `# Persona Contract (${resolved.klass}) ${layerNote} You are operating as the **${resolved.klass}** persona. The role contract below is your identity — its mandate and boundaries govern what you own and what you must not do for this assignment. ${resolved.content.trim()}`; }