fix(fleet): export MOSAIC_AGENT_CLASS into the agent pane so personas inject #669

Merged
jason.woltje merged 1 commits from fix/persona-class-pane-export into main 2026-06-24 19:38:17 +00:00
Owner

Problem

The A3a/A3b persona system injects a per-agent persona contract that the launcher composes from process.env.MOSAIC_AGENT_CLASS (compose-contractreadPersonaContractBlock, launch.js:325). generateAgentEnv already writes MOSAIC_AGENT_CLASS to agents/<name>.env, but start-agent-session.sh only re-exported MOSAIC_AGENT_NAME into the pane snippet — never the class.

The tmux pane inherits the tmux SERVER environment, not this script's env nor the systemd unit's EnvironmentFile. So the class written to the .env was invisible in-pane; the launcher saw an undefined class and silently injected no persona contract. Every fleet agent came up class-less while fleet ps reported healthy. The comms block kept working only because it keys on MOSAIC_AGENT_NAME, which is re-exported.

Fix

Re-export MOSAIC_AGENT_CLASS into PANE_SHELL_SNIPPET exactly as MOSAIC_AGENT_NAME, %q-quoted. An empty/unset class %q-quotes to '' and is a harmless no-op (matches readPersonaContractBlock's empty-class contract).

Verification

  • Reproduced live on the jarvis fleet (0.0.47): adding the class to .env + restart alone left the pane without MOSAIC_AGENT_CLASS and without a persona block — proving the .env reaches only the systemd service, not the pane.
  • After this script fix + restart: the relaunched agent carries MOSAIC_AGENT_CLASS=<class> in its pane env and the matching # Persona Contract (<class>) block in its composed prompt (claude: in --append-system-prompt; pi: via compose-contract reading the same env). Negative control (env -u MOSAIC_AGENT_CLASS compose-contract pi) emits no persona — confirming it is genuinely class-gated.
  • Independently re-verified by a second agent (review-of-record).

Adds a regression guard to test-start-agent-session.sh asserting the pane snippet exports both MOSAIC_AGENT_NAME and the per-agent MOSAIC_AGENT_CLASS.

🤖 Generated with Claude Code

## Problem The A3a/A3b persona system injects a per-agent persona contract that the launcher composes from `process.env.MOSAIC_AGENT_CLASS` (`compose-contract` → `readPersonaContractBlock`, `launch.js:325`). `generateAgentEnv` already writes `MOSAIC_AGENT_CLASS` to `agents/<name>.env`, **but `start-agent-session.sh` only re-exported `MOSAIC_AGENT_NAME` into the pane snippet — never the class.** The tmux **pane inherits the tmux SERVER environment**, not this script's env nor the systemd unit's `EnvironmentFile`. So the class written to the `.env` was invisible in-pane; the launcher saw an undefined class and **silently injected no persona contract**. Every fleet agent came up class-less while `fleet ps` reported healthy. The comms block kept working only because it keys on `MOSAIC_AGENT_NAME`, which *is* re-exported. ## Fix Re-export `MOSAIC_AGENT_CLASS` into `PANE_SHELL_SNIPPET` exactly as `MOSAIC_AGENT_NAME`, `%q`-quoted. An empty/unset class `%q`-quotes to `''` and is a harmless no-op (matches `readPersonaContractBlock`'s empty-class contract). ## Verification - Reproduced live on the jarvis fleet (0.0.47): adding the class to `.env` + restart alone left the pane **without** `MOSAIC_AGENT_CLASS` and **without** a persona block — proving the `.env` reaches only the systemd service, not the pane. - After this script fix + restart: the relaunched agent carries `MOSAIC_AGENT_CLASS=<class>` in its pane env and the matching `# Persona Contract (<class>)` block in its composed prompt (claude: in `--append-system-prompt`; pi: via `compose-contract` reading the same env). Negative control (`env -u MOSAIC_AGENT_CLASS compose-contract pi`) emits no persona — confirming it is genuinely class-gated. - Independently re-verified by a second agent (review-of-record). Adds a regression guard to `test-start-agent-session.sh` asserting the pane snippet exports both `MOSAIC_AGENT_NAME` and the per-agent `MOSAIC_AGENT_CLASS`. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
jason.woltje added 1 commit 2026-06-24 19:23:02 +00:00
fix(fleet): export MOSAIC_AGENT_CLASS into the agent pane so personas inject
Some checks failed
ci/woodpecker/push/ci Pipeline failed
ci/woodpecker/pr/ci Pipeline was successful
92cbe42fc1
A3a/A3b ship a per-agent persona contract that the launcher composes from
process.env.MOSAIC_AGENT_CLASS (compose-contract -> readPersonaContractBlock,
launch.js). generateAgentEnv already writes MOSAIC_AGENT_CLASS to
agents/<name>.env, but start-agent-session.sh only re-exported
MOSAIC_AGENT_NAME into the pane snippet — never the class.

The pane inherits the tmux SERVER environment, not this script's env nor the
systemd unit's EnvironmentFile, so the class set in the .env was invisible
in-pane. The launcher then saw an undefined class and silently injected NO
persona contract: every fleet agent came up class-less while `fleet ps`
reported healthy. The comms block kept working only because it keys on
MOSAIC_AGENT_NAME (which IS re-exported).

Fix: re-export MOSAIC_AGENT_CLASS into PANE_SHELL_SNIPPET exactly as
MOSAIC_AGENT_NAME, %q-quoted (empty/unset class -> '' is a harmless no-op,
matching readPersonaContractBlock's empty-class contract). Verified live: a
relaunched agent now carries MOSAIC_AGENT_CLASS in its pane env and the
matching `# Persona Contract (<class>)` block in its composed prompt.

Adds a regression guard to test-start-agent-session.sh asserting the pane
snippet exports both MOSAIC_AGENT_NAME and the per-agent MOSAIC_AGENT_CLASS.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Author
Owner

Independent review-of-record — mos-claude-1 (two-perspective) — APPROVE

Reviewed PR head 92cbe42 against origin/main from a fresh extract of both blobs.

Diff: 2 files, +26/-2 — matches description, no collateral changes.

start-agent-session.sh: MOSAIC_AGENT_CLASS is now re-exported into PANE_SHELL_SNIPPET in BOTH branches (with/without runtime-bin prefix), %q-quoted from ${MOSAIC_AGENT_CLASS:-}. Empty/unset class %q-quotes to '', a harmless no-op (readPersonaContractBlock returns '' for an empty class). Mirrors the existing MOSAIC_AGENT_NAME handling exactly. Comment correctly explains the pane inherits the tmux SERVER env (not the script env nor the systemd EnvironmentFile), which is why the explicit re-export is required.

Regression guard: verified it has TEETH via the suggested negative control:

  • as-is: ok - start-agent-session, EXIT 0
  • strip the CLASS export from the snippet: test FAILS with FAIL: pane command does not export MOSAIC_AGENT_CLASS into the pane (persona would silently drop), EXIT 1
  • restore: green again

bash -n clean on both files.

Verdict: APPROVE. Correctly and minimally closes the A3a pane-propagation gap; generateAgentEnv already emits the CLASS line, so post-merge mosaic fleet install converges cleanly. Recommend squash-merge on CI-green.

## Independent review-of-record — mos-claude-1 (two-perspective) — APPROVE Reviewed PR head `92cbe42` against `origin/main` from a fresh extract of both blobs. **Diff:** 2 files, +26/-2 — matches description, no collateral changes. **start-agent-session.sh:** `MOSAIC_AGENT_CLASS` is now re-exported into `PANE_SHELL_SNIPPET` in BOTH branches (with/without runtime-bin prefix), `%q`-quoted from `${MOSAIC_AGENT_CLASS:-}`. Empty/unset class `%q`-quotes to `''`, a harmless no-op (`readPersonaContractBlock` returns `''` for an empty class). Mirrors the existing `MOSAIC_AGENT_NAME` handling exactly. Comment correctly explains the pane inherits the tmux SERVER env (not the script env nor the systemd EnvironmentFile), which is why the explicit re-export is required. **Regression guard:** verified it has TEETH via the suggested negative control: - as-is: `ok - start-agent-session`, EXIT 0 - strip the CLASS export from the snippet: test FAILS with `FAIL: pane command does not export MOSAIC_AGENT_CLASS into the pane (persona would silently drop)`, EXIT 1 - restore: green again `bash -n` clean on both files. **Verdict: APPROVE.** Correctly and minimally closes the A3a pane-propagation gap; `generateAgentEnv` already emits the CLASS line, so post-merge `mosaic fleet install` converges cleanly. Recommend squash-merge on CI-green.
jason.woltje merged commit 248193cd3b into main 2026-06-24 19:38:17 +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#669