From 248193cd3bf272c5a2c2694466b591e452b9196a Mon Sep 17 00:00:00 2001 From: "jason.woltje" Date: Wed, 24 Jun 2026 19:38:15 +0000 Subject: [PATCH] fix(fleet): export MOSAIC_AGENT_CLASS into the agent pane so personas inject (#669) --- .../framework/tools/fleet/start-agent-session.sh | 15 +++++++++++++-- .../tools/fleet/test-start-agent-session.sh | 13 +++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/packages/mosaic/framework/tools/fleet/start-agent-session.sh b/packages/mosaic/framework/tools/fleet/start-agent-session.sh index 96aaec8..7bb9731 100755 --- a/packages/mosaic/framework/tools/fleet/start-agent-session.sh +++ b/packages/mosaic/framework/tools/fleet/start-agent-session.sh @@ -114,10 +114,21 @@ MOSAIC_RUNTIME_BIN_PREFIX=$(_build_runtime_bin_prefix) # safe single bash token regardless of the name's characters. AGENT_NAME_Q=$(printf '%q' "$AGENT_NAME") +# MOSAIC_AGENT_CLASS must ALSO be exported INTO the pane, for the same reason as +# MOSAIC_AGENT_NAME above: the pane inherits the tmux SERVER environment (not this +# script's env, and not the systemd unit's EnvironmentFile), so the per-agent class +# written to agents/.env would otherwise be invisible in-pane. The launcher +# composes the persona contract from process.env.MOSAIC_AGENT_CLASS at launch +# (compose-contract -> readPersonaContractBlock); without this export it sees an +# undefined class and silently injects NO persona contract. %q-quote it so it is a +# safe single bash token; an empty/unset class %q-quotes to '' and is a harmless +# no-op downstream (readPersonaContractBlock returns '' for an empty class). +AGENT_CLASS_Q=$(printf '%q' "${MOSAIC_AGENT_CLASS:-}") + if [ -n "$MOSAIC_RUNTIME_BIN_PREFIX" ]; then - PANE_SHELL_SNIPPET="export MOSAIC_AGENT_NAME=${AGENT_NAME_Q}; export PATH=\"${MOSAIC_RUNTIME_BIN_PREFIX}:\${PATH}\"; exec ${MOSAIC_AGENT_COMMAND}" + PANE_SHELL_SNIPPET="export MOSAIC_AGENT_NAME=${AGENT_NAME_Q}; export MOSAIC_AGENT_CLASS=${AGENT_CLASS_Q}; export PATH=\"${MOSAIC_RUNTIME_BIN_PREFIX}:\${PATH}\"; exec ${MOSAIC_AGENT_COMMAND}" else - PANE_SHELL_SNIPPET="export MOSAIC_AGENT_NAME=${AGENT_NAME_Q}; exec ${MOSAIC_AGENT_COMMAND}" + PANE_SHELL_SNIPPET="export MOSAIC_AGENT_NAME=${AGENT_NAME_Q}; export MOSAIC_AGENT_CLASS=${AGENT_CLASS_Q}; exec ${MOSAIC_AGENT_COMMAND}" fi mkdir -p "$MOSAIC_AGENT_WORKDIR" diff --git a/packages/mosaic/framework/tools/fleet/test-start-agent-session.sh b/packages/mosaic/framework/tools/fleet/test-start-agent-session.sh index 96b9931..837ef93 100755 --- a/packages/mosaic/framework/tools/fleet/test-start-agent-session.sh +++ b/packages/mosaic/framework/tools/fleet/test-start-agent-session.sh @@ -104,6 +104,7 @@ PATH="$FAKE_BIN:$PATH" \ MOSAIC_TMUX_SOCKET="$SOCKET3" \ MOSAIC_AGENT_WORKDIR="$WORKDIR3" \ MOSAIC_AGENT_RUNTIME="pi" \ +MOSAIC_AGENT_CLASS="code" \ MOSAIC_RUNTIME_BIN="$FAKE_RUNTIME_BIN" \ MOSAIC_AGENT_COMMAND="mosaic yolo pi --model openai-codex/gpt-5.5:high" \ MOSAIC_HEARTBEAT_RUN_DIR="$HB_RUN_DIR3" \ @@ -127,6 +128,18 @@ echo "$all_args" | grep -qF "exec " || fail "pane command does not use exec" echo "$all_args" | grep -qF "mosaic yolo pi --model openai-codex/gpt-5.5:high" || \ fail "pane command does not forward MOSAIC_AGENT_COMMAND with flags intact" +# d) MOSAIC_AGENT_NAME and the per-agent MOSAIC_AGENT_CLASS must BOTH be exported +# INTO the pane. The pane inherits the tmux SERVER environment (not this +# script's env, nor the systemd unit's EnvironmentFile), so any per-agent var +# the launcher needs in-pane must be re-exported in the snippet. CLASS is +# load-bearing: the launcher composes the persona contract from +# process.env.MOSAIC_AGENT_CLASS, so a missing export silently drops the +# persona (regression guard for the A3a pane-propagation gap). +echo "$all_args" | grep -qF "export MOSAIC_AGENT_NAME=" || \ + fail "pane command does not export MOSAIC_AGENT_NAME into the pane" +echo "$all_args" | grep -qF "export MOSAIC_AGENT_CLASS=code" || \ + fail "pane command does not export MOSAIC_AGENT_CLASS into the pane (persona would silently drop)" + # ── Test 4: when no extra runtime-bin dirs exist, exec still appears ─────────── TMUX_ARGS_FILE2=$(mktemp) FAKE_BIN2=$(mktemp -d)