feat(fleet): inject persona contract at launch (A3b) #664

Merged
jason.woltje merged 1 commits from feat/a3b-persona-contract into main 2026-06-24 17:06:52 +00:00
Owner

A3b — inject resolved persona contract at launch by class

At agent launch, composeContract now reads MOSAIC_AGENT_CLASS (exported into the pane env by the companion A3a goal) and injects the agent's resolved persona role contract into the system prompt, so its identity (mandate + boundaries) is resident from the first turn.

What it does

  • New readPersonaContractBlock(mosaicHome, klass) in packages/mosaic/src/fleet/persona-contract.ts (mirrors comms-onboarding.ts structure; standalone, no heavy fleet.ts import).
  • Injected in composeContract before the fleet comms block: identity first, then how-to-reach-peers. Guarded exactly like the comms block (only pushed when non-empty).

Override-aware (AC-NS-7 launch proof)

  • Resolution goes through the persona resolver, so a user-customized persona in fleet/roles.local/ wins over the baseline fleet/roles/ of the same class. A dedicated test asserts the override body is injected and the baseline is NOT — the launch-time proof that a customized persona actually reaches the model (not just mosaic fleet persona show).

Tolerant / no-op

  • Empty/missing/unknown class or missing role file → returns '' so the launcher no-ops silently. Never throws during launch (mirrors readFleetCommsBlock).

Sync resolver twin

  • composeContract is synchronous and cannot await the async resolvePersona. Added resolvePersonaSync / extractClassesFromDirSync in fleet-personas.ts with identical extraction semantics, sharing a pure accumulateEntry helper so async and sync paths never drift.

Tests

  • persona-contract.spec.ts (7): baseline inject, override wins, override-only custom persona, no-op for undefined/empty/whitespace/unknown class, no-throw when no roles dirs exist.
  • compose-contract.spec.ts (+5): baseline inject at launch, override wins at launch (AC-NS-7), no-inject when class unset, no-throw on unknown class, persona placed before fleet comms. Env saved/restored in afterEach.

Gates

  • vitest run: 606 passed (43 files). typecheck: clean. lint: clean. prettier --check: clean.

Reviewable — do NOT merge. Does not touch generateAgentEnv/fleet.ts (A3a's scope); only reads MOSAIC_AGENT_CLASS from env.

🤖 Generated with Claude Code

## A3b — inject resolved persona contract at launch by class At agent launch, `composeContract` now reads **`MOSAIC_AGENT_CLASS`** (exported into the pane env by the companion A3a goal) and injects the agent's **resolved persona role contract** into the system prompt, so its identity (mandate + boundaries) is resident from the first turn. ### What it does - New `readPersonaContractBlock(mosaicHome, klass)` in `packages/mosaic/src/fleet/persona-contract.ts` (mirrors `comms-onboarding.ts` structure; standalone, no heavy `fleet.ts` import). - Injected in `composeContract` **before** the fleet comms block: identity first, then how-to-reach-peers. Guarded exactly like the comms block (only pushed when non-empty). ### Override-aware (AC-NS-7 launch proof) - Resolution goes through the persona resolver, so a user-customized persona in `fleet/roles.local/` **wins** over the baseline `fleet/roles/` of the same class. A dedicated test asserts the override body is injected and the baseline is NOT — the launch-time proof that a customized persona actually reaches the model (not just `mosaic fleet persona show`). ### Tolerant / no-op - Empty/missing/unknown class or missing role file → returns `''` so the launcher no-ops silently. Never throws during launch (mirrors `readFleetCommsBlock`). ### Sync resolver twin - `composeContract` is synchronous and cannot `await` the async `resolvePersona`. Added `resolvePersonaSync` / `extractClassesFromDirSync` in `fleet-personas.ts` with identical extraction semantics, sharing a pure `accumulateEntry` helper so async and sync paths never drift. ### Tests - `persona-contract.spec.ts` (7): baseline inject, **override wins**, override-only custom persona, no-op for undefined/empty/whitespace/unknown class, no-throw when no roles dirs exist. - `compose-contract.spec.ts` (+5): baseline inject at launch, **override wins at launch (AC-NS-7)**, no-inject when class unset, no-throw on unknown class, persona placed before fleet comms. Env saved/restored in `afterEach`. ### Gates - `vitest run`: 606 passed (43 files). `typecheck`: clean. `lint`: clean. `prettier --check`: clean. Reviewable — do NOT merge. Does not touch `generateAgentEnv`/`fleet.ts` (A3a's scope); only reads `MOSAIC_AGENT_CLASS` from env. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
jason.woltje added 1 commit 2026-06-24 16:51:01 +00:00
feat(fleet): inject resolved persona contract at launch by class (A3b)
All checks were successful
ci/woodpecker/pr/ci Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
1bb9fbd83a
At agent launch, composeContract reads MOSAIC_AGENT_CLASS (exported into the
pane env by the companion A3a goal) and injects the agent's resolved persona
role contract into the system prompt, so its identity (mandate + boundaries)
is resident from the first turn.

Override-aware: resolution goes through the persona resolver, so a customized
persona in fleet/roles.local/ wins over the baseline fleet/roles/ of the same
class — the launch-time proof of AC-NS-7. Tolerant: any miss (unset/empty/
unknown class, missing file) no-ops silently and never throws during launch,
mirroring readFleetCommsBlock. Persona is injected BEFORE the fleet comms
block (identity first, then how-to-reach-peers).

Adds a synchronous resolvePersonaSync / extractClassesFromDirSync twin in
fleet-personas.ts (composeContract is sync and cannot await), sharing the same
extraction semantics via a pure accumulateEntry helper (no drift).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
jason.woltje merged commit 28cfecda94 into main 2026-06-24 17:06:52 +00:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: mosaicstack/stack#664