#!/bin/sh # Load agent context and write the generated system prompt to # /var/lib/mosaic/system-prompt.md. # # Layers, in normative order: # 1. Immutable contracts (image): CONSTITUTION, STANDARDS, SOUL # 2. Agent identity (when the launcher names the agent) # 3. Mission (when the task/launcher provides one) # 4. Live user context (M14): /user/*.md - user-owned, # dispatched to every launch without rebuilds set -eu CONTRACT_DIR="${1:-/opt/mosaic/contracts}" OUT="${2:-/var/lib/mosaic/system-prompt.md}" FILES="CONSTITUTION.md STANDARDS.md" # SOUL slot (M15): the contract SOUL.md is the DEFAULT persona; a launched # agent seat overrides it with its own runtime SOUL (governance contracts # are never overridden). SOUL_SRC="$CONTRACT_DIR/SOUL.md" SOUL_HEADER="SOUL.md" if [ -n "${MOSAIC_AGENT_SOUL_FILE:-}" ]; then if [ ! -r "$MOSAIC_AGENT_SOUL_FILE" ]; then echo "load-contracts: agent SOUL not readable: $MOSAIC_AGENT_SOUL_FILE" >&2 exit 1 fi SOUL_SRC="$MOSAIC_AGENT_SOUL_FILE" SOUL_HEADER="SOUL.md (agent seat override)" fi if [ ! -d "$CONTRACT_DIR" ]; then echo "load-contracts: contract directory not found: $CONTRACT_DIR" >&2 exit 1 fi PARENT="$(dirname "$OUT")" mkdir -p "$PARENT" TEMP="$OUT.partial" : > "$TEMP" for f in $FILES; do path="$CONTRACT_DIR/$f" if [ ! -r "$path" ]; then echo "load-contracts: missing contract file: $path" >&2 rm -f "$TEMP" exit 1 fi printf '===== CONTRACT: %s =====\n' "$f" >> "$TEMP" cat "$path" >> "$TEMP" printf '\n' >> "$TEMP" done printf '===== CONTRACT: %s =====\n' "$SOUL_HEADER" >> "$TEMP" cat "$SOUL_SRC" >> "$TEMP" printf '\n' >> "$TEMP" # Agent identity (M13): when the launcher names the agent, the generated # prompt states it - SOUL.md provides the persona, this provides the name. if [ -n "${MOSAIC_AGENT_NAME:-}" ]; then printf '===== AGENT IDENTITY =====\n' >> "$TEMP" printf 'agent name: %s\n' "$MOSAIC_AGENT_NAME" >> "$TEMP" [ -n "${MOSAIC_AGENT_ROLE:-}" ] && printf 'agent role: %s\n' "$MOSAIC_AGENT_ROLE" >> "$TEMP" printf '\n' >> "$TEMP" fi # Live user context (M14): every *.md in /var/lib/mosaic/user (sorted) is # appended - the user owns this layer and edits it without rebuilds. USER_DIR="/var/lib/mosaic/user" if [ -d "$USER_DIR" ]; then for f in $(ls "$USER_DIR"/*.md 2>/dev/null | sort); do printf '===== USER CONTEXT: %s =====\n' "$(basename "$f")" >> "$TEMP" cat "$f" >> "$TEMP" printf '\n' >> "$TEMP" done fi # Sanctioned mission injection point (M4): when the task runner provides a # mission snapshot, its objective and directives are appended AFTER the # immutable contracts. Runtime data; never part of the contract fixtures. if [ -n "${MOSAIC_MISSION_FILE:-}" ]; then if [ ! -r "$MOSAIC_MISSION_FILE" ]; then echo "load-contracts: MOSAIC_MISSION_FILE set but not readable: $MOSAIC_MISSION_FILE" >&2 rm -f "$TEMP" exit 1 fi printf '===== MISSION (runtime) =====\n' >> "$TEMP" node -e ' const m = JSON.parse(require("fs").readFileSync(process.env.MOSAIC_MISSION_FILE, "utf8")); process.stdout.write("Objective: " + m.objective + "\n"); for (const d of m.directives ?? []) process.stdout.write("- " + d + "\n"); ' >> "$TEMP" printf '\n' >> "$TEMP" fi mv "$TEMP" "$OUT" echo "load-contracts: wrote $OUT from $CONTRACT_DIR" >&2