feat(agents): agent seats - per-agent SOUL, role, definitions dir (#36)

- 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
This commit is contained in:
2026-09-03 11:56:38 -05:00
parent a7b612435b
commit 9bd4f1c405
9 changed files with 129 additions and 10 deletions
+39 -4
View File
@@ -1,14 +1,33 @@
#!/bin/sh
# Load the four immutable contract files in fixed order and write the
# generated system prompt to /var/lib/mosaic/system-prompt.md.
# Load agent context and write the generated system prompt to
# /var/lib/mosaic/system-prompt.md.
#
# Order is normative: CONSTITUTION.md, STANDARDS.md, SOUL.md, USER.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.md USER.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
@@ -33,14 +52,30 @@ for f in $FILES; do
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.