- agents/<name>/ holds agent.json (strictly validated: version, name, role?, capabilities?, workspace?, session?) + SOUL.md (persona prose) - agent.sh: definition loading (quote-safe node defaults file), runtime SOUL copy to dataRoot/agents/<name>/, MOSAIC_AGENT_SOUL_FILE -> loader fills the SOUL slot from the seat's persona (contract SOUL = default persona; governance never overridden) - seat.json written once at instantiation (seatVersion, name, role, at) - identity section gains agent role; compose passthrough for role+SOUL - live user context (M14) + seat SOUL compose the full persona: governance -> persona -> identity -> user -> mission - RELEASE -> 0.0.10; packaged and health-gated activated - example seat committed: agents/researcher Closes #36
99 lines
3.3 KiB
Bash
Executable File
99 lines
3.3 KiB
Bash
Executable File
#!/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): <dataRoot>/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
|