Files
stack/scripts/agent-host-dev.sh
T
jason.woltjeandClaude Opus 5.5 fe5c68fa03 feat(harness): meta-harness and PM launching off T3 (#1523, row 41)
Slice 1 S6. packages/harness runs a role session under the pi or claude
adapter behind a tool gate, and packages/seat holds the session and its
process. The bus host gains a launch socket: `mosaic bus start
<business> --pm <harness>:<model>` runs the PM without a window, the PM
launches role sessions through the broker within the business file's
`launch.max`, and every launch, refusal and end is a launch-log line and
a broker event. `mosaic talk`, `mosaic launches off|on|list` and
`mosaic stop <run>` are new CLI verbs. packages/bus gains the IPC ops
identity, authorize, refuse, endLaunch and credentialStatus, plus
Broker.endLaunch (decision 77); no socket verb, event kind or schema
changed. The session environment is an allowlist, and the runner exits
before claiming if a founder credential reaches it.

Filbert built it (agents/filbert/work/s6). Darkwing reviewed on #1523:
round 1 changes (26991/26995), round 2 changes (27004/27010), round 3
changes (27012/27032), round 4 approve (27033/27034) on manifest
08a78972, 42 paths.

Sage's gate on 64b0f9f1 (12:32Z), sequential and teed: node suites
business 60, bus 74, cli 83, control-board 124, conversation 182,
discord 178, harness 56, ledger 78, mosaic 69, queue 149, runs 41,
seat 27, tasks 51, webui 39, every one 0 failed; test-auth 15,
test-conductor 17, test-config 24, test-discord 66,
test-extension-package 18, test-foundation 44, test-queue 27,
test-release 14, test-task 98 and 26 without Docker, all 0 failed.
64b0f9f1..ee7f909b touches none of the 42 paths. It landed after the
Q14 hold ended at 2026-10-11T17:19:23.857Z. The recorded PM-to-coder
run on a scratch data root is still owed before row 41 moves to done.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-11 12:22:50 -05:00

144 lines
6.0 KiB
Bash
Executable File

#!/usr/bin/env bash
# Host-development implementation for scripts/agent.sh --host-dev <name>.
set -euo pipefail
REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$REPO"
usage() {
echo 'Usage: scripts/agent.sh --host-dev AGENT [--fresh] [--check] [--soul FILE] [--constitution FILE] [--user FILE] [--provider NAME] [--model ID] [--thinking LEVEL]'
echo 'Default: resume the latest agent development conversation; first launch starts one.'
}
fail() { echo "${AGENT:-tui}: $*" >&2; exit 1; }
case "${1:-}" in
--help|-h) usage; exit 0 ;;
"") usage >&2; exit 1 ;;
esac
AGENT="$1"
shift
[[ "$AGENT" =~ ^[a-z0-9][a-z0-9._-]{0,63}$ ]] || fail "invalid agent name"
[ -d "$REPO/agents/$AGENT" ] || fail "unknown agent: $AGENT"
SOUL="$REPO/agents/$AGENT/SOUL.md"
CONSTITUTION="$REPO/contracts/CONSTITUTION.md"
USER_FILE=""
PROVIDER=""
MODEL=""
THINKING=""
FRESH=false
CHECK=false
while [ "$#" -gt 0 ]; do
case "$1" in
--fresh) FRESH=true; shift ;;
--check) CHECK=true; shift ;;
--provider|--model|--thinking)
[ "$#" -ge 2 ] && [ -n "$2" ] && [[ "$2" != --* ]] || fail "$1 needs a value"
case "$1" in
--provider) PROVIDER="$2" ;;
--model) MODEL="$2" ;;
--thinking) THINKING="$2" ;;
esac
shift 2 ;;
--soul|--constitution|--user)
[ "$#" -ge 2 ] && [ -n "$2" ] || fail "$1 needs a file"
case "$1" in
--soul) SOUL="$2" ;;
--constitution) CONSTITUTION="$2" ;;
--user) USER_FILE="$2" ;;
esac
shift 2 ;;
--help|-h) usage; exit 0 ;;
*) usage >&2; fail "unknown argument: $1" ;;
esac
done
# Reuse the deployment's validated provider/model and live user profile.
source "$REPO/scripts/common.sh"
load_config
[ "$MOSAIC_ADAPTER" = pi ] || fail 'native launcher requires the pi adapter'
PROVIDER="${PROVIDER:-$MOSAIC_PROVIDER}"
MODEL="${MODEL:-$MOSAIC_MODEL}"
THINKING_ARGS=()
if [ -n "$THINKING" ]; then
case "$THINKING" in
off|minimal|low|medium|high|xhigh|max) THINKING_ARGS=(--thinking "$THINKING") ;;
*) fail "invalid thinking level: $THINKING" ;;
esac
fi
USER_FILE="${USER_FILE:-$MOSAIC_DATA_ROOT/user/USER.md}"
PI="$REPO/node_modules/.bin/pi"
[ -x "$PI" ] || fail 'run npm ci --ignore-scripts --no-audit --no-fund in the repository first'
PIN="$(node -p "require('./package.json').dependencies['@earendil-works/pi-coding-agent']")"
PI_VERSION="$("$PI" --version)"
[ "$PI_VERSION" = "$PIN" ] || fail "local Pi is $PI_VERSION, package.json pins $PIN; run npm ci in $REPO"
command -v flock >/dev/null || fail 'flock is required'
CONTEXT_FILES=("$CONSTITUTION" "$REPO/contracts/STANDARDS.md" "$SOUL" "$USER_FILE" "$REPO/AGENTS.md" "$REPO/agents/$AGENT/CONTEXT.md")
for file in "${CONTEXT_FILES[@]}"; do
[ -f "$file" ] && [ -r "$file" ] && [ -s "$file" ] || fail "missing, unreadable or empty context: $file"
done
SKILLS=(ms-tools ms-file-read ms-file-write ms-conductor ms-communications
ms-watch ms-sdlc ms-proactive-agent ms-goal ms-unslop)
SKILL_ARGS=()
for skill in "${SKILLS[@]}"; do
[ -s "$REPO/skills/$skill/SKILL.md" ] || fail "missing skill: $skill"
SKILL_ARGS+=(--skill "$REPO/skills/$skill")
done
[ -s "$REPO/extensions/goal/index.ts" ] || fail 'missing canonical goal extension'
STATE="$REPO/.pi/state/$AGENT"
echo "$AGENT: native host TUI | Pi $PIN | $PROVIDER/$MODEL${THINKING:+:$THINKING}"
echo "$AGENT: workspace $REPO"
echo "$AGENT: SOUL=$SOUL | CONSTITUTION=$CONSTITUTION | USER=$USER_FILE"
echo "$AGENT: sessions $STATE/sessions | skills: ${SKILLS[*]}"
if "$CHECK"; then
echo "$AGENT: configuration checks passed (no TUI, authentication request or model call)"
exit 0
fi
[ -t 0 ] && [ -t 1 ] || fail 'launch from an interactive terminal (or use --check)'
umask 077
mkdir -p "$STATE/sessions" "$STATE/launches"
exec 8>"$STATE/launch.lock"
flock -n 8 || fail "another $AGENT TUI from this launcher is active"
# The native packaging path verifies source copies and refuses installation drift.
scripts/sync-dev-extensions.sh
SESSION_ARGS=(--session-dir "$STATE/sessions")
shopt -s nullglob dotglob
SESSION_FILES=("$STATE/sessions/"*.jsonl)
SESSION_ENTRIES=("$STATE/sessions/"*)
if ! "$FRESH"; then
if [ "${#SESSION_FILES[@]}" -gt 0 ]; then
# Pi --continue otherwise silently skips broken files and may start fresh.
node "$REPO/agents/$AGENT/validate-sessions.mjs" "${SESSION_FILES[@]}"
SESSION_ARGS+=(--continue)
echo "$AGENT: resuming latest development conversation with current launch context"
elif [ "${#SESSION_ENTRIES[@]}" -gt 0 ]; then
fail 'session directory has state but no JSONL conversation; inspect it before using --fresh'
else
echo "$AGENT: starting first development conversation"
fi
else
echo "$AGENT: starting a fresh conversation; earlier conversations are preserved"
fi
# A private snapshot per launch avoids the container loader's shared prompt file.
LAUNCH="$(mktemp -d "$STATE/launches/launch.XXXXXXXX")"
PROMPT="$LAUNCH/context.md"
for file in "${CONTEXT_FILES[@]}"; do
printf '\n===== %s (%s) =====\n' "$(basename "$file")" "$file" >> "$PROMPT"
cat "$file" >> "$PROMPT"
printf '\n' >> "$PROMPT"
done
sha256sum "$PROMPT" > "$LAUNCH/context.sha256"
echo "$AGENT: context snapshot $PROMPT"
unset MOSAIC_LAUNCH_INCARNATION
export MOSAIC_AGENT_NAME="$AGENT"
# Explicit resources only; preserve Pi's built-in coding prompt and tool guidance.
# goal_report must be in the tool allowlist as well as the extension being loaded.
# --no-approve ignores project-local .pi/ files (settings.json, SYSTEM.md,
# APPEND_SYSTEM.md); the -e and --skill paths given here still load.
exec "$PI" --no-approve --offline --no-context-files --no-extensions --no-skills \
--no-prompt-templates --no-themes \
--extension "$REPO/.pi/extensions/goal/index.ts" \
"${SKILL_ARGS[@]}" \
--tools read,bash,edit,write,grep,find,ls,goal_report \
--provider "$PROVIDER" --model "$MODEL" "${THINKING_ARGS[@]}" \
--append-system-prompt "$PROMPT" "${SESSION_ARGS[@]}"