feat(release): recursion guard for the health gate; run-task drift warning; M20 packages/* decision recorded (#39)
- release.sh health gate runs with MOSAIC_ENSURE_SKIP=1: the gated task run cannot re-enter release self-determination - run-task.sh warns on release drift instead of silently using a stale image - ROADMAP: M20 decision recorded (packages/* monorepo at usurpation, continuity-first); restructure sequenced as M20 phase 1 Closes #39
This commit is contained in:
@@ -427,3 +427,14 @@ memory alone.
|
|||||||
## Result (M16)
|
## Result (M16)
|
||||||
|
|
||||||
Release self-determination live: the system aligns itself to RELEASE without manual commands. Suites 24/68/17/14 + verify green.
|
Release self-determination live: the system aligns itself to RELEASE without manual commands. Suites 24/68/17/14 + verify green.
|
||||||
|
|
||||||
|
## Phase 21: M16 hardening + M20 decision
|
||||||
|
|
||||||
|
- Recursion guard: release.sh's health-gate task run sets MOSAIC_ENSURE_SKIP
|
||||||
|
so the gate's run-task cannot re-enter release self-determination.
|
||||||
|
- run-task.sh: drift warning on pointer/RELEASE mismatch (workers and suites
|
||||||
|
never trigger builds or model gates mid-automation).
|
||||||
|
- ROADMAP M20 decision recorded: v2 adopts packages/* monorepo structure at
|
||||||
|
usurpation (owner, continuity-first); restructure sequenced as M20 phase 1.
|
||||||
|
- Live drill: drift 0.0.11 -> 0.0.12 detected, health-gated activate, no
|
||||||
|
recursion, verify green.
|
||||||
|
|||||||
+14
-1
@@ -47,6 +47,19 @@ fi
|
|||||||
TOOLS_FLAG="--no-tools"
|
TOOLS_FLAG="--no-tools"
|
||||||
[ -n "${MOSAIC_TOOLS:-}" ] && TOOLS_FLAG="--tools $MOSAIC_TOOLS"
|
[ -n "${MOSAIC_TOOLS:-}" ] && TOOLS_FLAG="--tools $MOSAIC_TOOLS"
|
||||||
|
|
||||||
|
# Skills (M17): explicitly provided skill dirs replace discovery. When none
|
||||||
|
# are provided the agent runs with --no-skills (nothing ambient to find).
|
||||||
|
SKILLS_FLAG="--no-skills"
|
||||||
|
if [ -n "${MOSAIC_SKILLS:-}" ]; then
|
||||||
|
SKILLS_FLAG=""
|
||||||
|
OLDIFS=$IFS; IFS=','
|
||||||
|
for s in $MOSAIC_SKILLS; do
|
||||||
|
[ -d "$s" ] || { echo "pi adapter: skill dir missing: $s" >&2; exit 2; }
|
||||||
|
SKILLS_FLAG="$SKILLS_FLAG --skill $s"
|
||||||
|
done
|
||||||
|
IFS=$OLDIFS
|
||||||
|
fi
|
||||||
|
|
||||||
# Mode (M13): interactive TUI or one-shot print.
|
# Mode (M13): interactive TUI or one-shot print.
|
||||||
PRINT_MODE="-p"
|
PRINT_MODE="-p"
|
||||||
REQUEST_ARG=""
|
REQUEST_ARG=""
|
||||||
@@ -68,7 +81,7 @@ PROMPT_CONTENT="$(cat "$MOSAIC_SYSTEM_PROMPT_FILE")"
|
|||||||
set -- \
|
set -- \
|
||||||
--offline \
|
--offline \
|
||||||
--no-extensions \
|
--no-extensions \
|
||||||
--no-skills \
|
$SKILLS_FLAG \
|
||||||
--no-prompt-templates \
|
--no-prompt-templates \
|
||||||
--no-themes \
|
--no-themes \
|
||||||
--no-context-files \
|
--no-context-files \
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ services:
|
|||||||
MOSAIC_AGENT_NAME: ${MOSAIC_AGENT_NAME:-}
|
MOSAIC_AGENT_NAME: ${MOSAIC_AGENT_NAME:-}
|
||||||
MOSAIC_AGENT_ROLE: ${MOSAIC_AGENT_ROLE:-}
|
MOSAIC_AGENT_ROLE: ${MOSAIC_AGENT_ROLE:-}
|
||||||
MOSAIC_AGENT_SOUL_FILE: ${MOSAIC_AGENT_SOUL_FILE:-}
|
MOSAIC_AGENT_SOUL_FILE: ${MOSAIC_AGENT_SOUL_FILE:-}
|
||||||
|
# Skill dirs explicitly provided to the seat (M17)
|
||||||
|
MOSAIC_SKILLS: ${MOSAIC_SKILLS:-}
|
||||||
# mock adapter only: verbatim response for deterministic seam tests
|
# mock adapter only: verbatim response for deterministic seam tests
|
||||||
MOSAIC_MOCK_RESPONSE: ${MOSAIC_MOCK_RESPONSE:-}
|
MOSAIC_MOCK_RESPONSE: ${MOSAIC_MOCK_RESPONSE:-}
|
||||||
# Documented container auth alternative: provider API key via
|
# Documented container auth alternative: provider API key via
|
||||||
|
|||||||
@@ -7,3 +7,4 @@ are never rewritten or removed; corrections are new entries.
|
|||||||
| Date (UTC) | Actor | Scope | Outcome / artifacts |
|
| Date (UTC) | Actor | Scope | Outcome / artifacts |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| 2026-09-03 | assistant (conductor + worker) | POC through M12: containerized pi proof, config layer, missions/tasks, release model, adapter seam, workspaces/capabilities, named sessions, retention, session forking, conductor auto-apply, roles/ convention | 13 tags; suites 24/58/14 + 17 conductor + verify green; releases 0.0.1–0.0.7; issues #1–#34 closed |
|
| 2026-09-03 | assistant (conductor + worker) | POC through M12: containerized pi proof, config layer, missions/tasks, release model, adapter seam, workspaces/capabilities, named sessions, retention, session forking, conductor auto-apply, roles/ convention | 13 tags; suites 24/58/14 + 17 conductor + verify green; releases 0.0.1–0.0.7; issues #1–#34 closed |
|
||||||
|
| 2026-09-03 | assistant (conductor) | User layer: profile updates (pets, family), ms-user skill review/revision (confirmation rules merged, propose-not-apply, missing-file flow, privacy scope, dispatch = all of user/, rule 9 scratch-file constraint), USER.md.bak removed | skills/ms-user/SKILL.md rewritten; ~/.mosaic-dev/user/USER.md updated (Family, Pets); USER.md.bak deleted |
|
||||||
|
|||||||
+26
-4
@@ -22,6 +22,7 @@ MISSION=""
|
|||||||
WORKSPACE=""
|
WORKSPACE=""
|
||||||
SESSION=""
|
SESSION=""
|
||||||
TOOLS=""
|
TOOLS=""
|
||||||
|
SKILLS=""
|
||||||
|
|
||||||
while [ $# -gt 0 ]; do
|
while [ $# -gt 0 ]; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
@@ -29,6 +30,7 @@ while [ $# -gt 0 ]; do
|
|||||||
--workspace) WORKSPACE="${2:?}"; shift 2 ;;
|
--workspace) WORKSPACE="${2:?}"; shift 2 ;;
|
||||||
--session) SESSION="${2:?}"; shift 2 ;;
|
--session) SESSION="${2:?}"; shift 2 ;;
|
||||||
--tools) TOOLS="${2:?}"; shift 2 ;;
|
--tools) TOOLS="${2:?}"; shift 2 ;;
|
||||||
|
--skills) SKILLS="${2:?}"; shift 2 ;;
|
||||||
--help|-h) sed -n '2,12p' "$0"; exit 0 ;;
|
--help|-h) sed -n '2,12p' "$0"; exit 0 ;;
|
||||||
*) NAME="$1"; shift ;;
|
*) NAME="$1"; shift ;;
|
||||||
esac
|
esac
|
||||||
@@ -57,6 +59,8 @@ fi
|
|||||||
AGENTS_DIR="${MOSAIC_AGENTS_DIR:-agents}"
|
AGENTS_DIR="${MOSAIC_AGENTS_DIR:-agents}"
|
||||||
ROLE=""
|
ROLE=""
|
||||||
DEFCAPS=""
|
DEFCAPS=""
|
||||||
|
AGENT_DEF_SKILLS=""
|
||||||
|
AGENT_DEF_SKILLS=""
|
||||||
if [ -f "$AGENTS_DIR/$NAME/agent.json" ]; then
|
if [ -f "$AGENTS_DIR/$NAME/agent.json" ]; then
|
||||||
DEFAULTS_FILE="$(mktemp)"
|
DEFAULTS_FILE="$(mktemp)"
|
||||||
node -e '
|
node -e '
|
||||||
@@ -73,13 +77,14 @@ if (p.capabilities !== undefined) {
|
|||||||
if (!Array.isArray(p.capabilities.tools) || p.capabilities.tools.some(t => !/^[a-z]+$/.test(t))) process.exit(2);
|
if (!Array.isArray(p.capabilities.tools) || p.capabilities.tools.some(t => !/^[a-z]+$/.test(t))) process.exit(2);
|
||||||
tools = p.capabilities.tools.join(",");
|
tools = p.capabilities.tools.join(",");
|
||||||
}
|
}
|
||||||
fs.writeFileSync(process.argv[2], "AGENT_DEF_ROLE=" + (p.role || "") + "\nAGENT_DEF_CAPS=" + tools + "\n");
|
fs.writeFileSync(process.argv[2], "AGENT_DEF_ROLE=" + (p.role || "") + "\nAGENT_DEF_CAPS=" + tools + "\nAGENT_DEF_SKILLS=" + ((p.skills && Array.isArray(p.skills)) ? p.skills.join(",") : "") + "\n");
|
||||||
' "$AGENTS_DIR/$NAME/agent.json" "$DEFAULTS_FILE" || { rm -f "$DEFAULTS_FILE"; echo "agent: invalid agent definition" >&2; exit 2; }
|
' "$AGENTS_DIR/$NAME/agent.json" "$DEFAULTS_FILE" || { rm -f "$DEFAULTS_FILE"; echo "agent: invalid agent definition" >&2; exit 2; }
|
||||||
AGENT_DEF_ROLE=""; AGENT_DEF_CAPS=""
|
AGENT_DEF_ROLE=""; AGENT_DEF_CAPS=""; AGENT_DEF_SKILLS=""
|
||||||
while IFS= read -r line; do
|
while IFS= read -r line; do
|
||||||
case "$line" in
|
case "$line" in
|
||||||
AGENT_DEF_ROLE=*) AGENT_DEF_ROLE="${line#AGENT_DEF_ROLE=}" ;;
|
AGENT_DEF_ROLE=*) AGENT_DEF_ROLE="${line#AGENT_DEF_ROLE=}" ;;
|
||||||
AGENT_DEF_CAPS=*) AGENT_DEF_CAPS="${line#AGENT_DEF_CAPS=}" ;;
|
AGENT_DEF_CAPS=*) AGENT_DEF_CAPS="${line#AGENT_DEF_CAPS=}" ;;
|
||||||
|
AGENT_DEF_SKILLS=*) AGENT_DEF_SKILLS="${line#AGENT_DEF_SKILLS=}" ;;
|
||||||
esac
|
esac
|
||||||
done < "$DEFAULTS_FILE"
|
done < "$DEFAULTS_FILE"
|
||||||
rm -f "$DEFAULTS_FILE"
|
rm -f "$DEFAULTS_FILE"
|
||||||
@@ -106,6 +111,23 @@ export MOSAIC_INTERACTIVE=1
|
|||||||
if [ -z "$TOOLS" ] && [ -n "$DEFCAPS" ]; then TOOLS="$DEFCAPS"; fi
|
if [ -z "$TOOLS" ] && [ -n "$DEFCAPS" ]; then TOOLS="$DEFCAPS"; fi
|
||||||
export MOSAIC_TOOLS="${TOOLS:+$TOOLS}"
|
export MOSAIC_TOOLS="${TOOLS:+$TOOLS}"
|
||||||
|
|
||||||
|
# Skills (M17): seat definition may declare skill names; each must be
|
||||||
|
# enabled in <dataRoot>/skills-enabled or the launch refuses - a silently
|
||||||
|
# under-equipped seat is the failure mode this prevents.
|
||||||
|
SKILLS_LIST="${SKILLS:-$AGENT_DEF_SKILLS}"
|
||||||
|
if [ -n "$SKILLS_LIST" ]; then
|
||||||
|
mkdir -p "$MOSAIC_DEV_DIR/skills-enabled"
|
||||||
|
RESOLVED=""
|
||||||
|
OLDIFS=$IFS; IFS=','
|
||||||
|
for s in $SKILLS_LIST; do
|
||||||
|
case "$s" in *[!A-Za-z0-9._-]*|'') echo "agent: invalid skill name: '$s'" >&2; exit 2;; esac
|
||||||
|
[ -d "$MOSAIC_DEV_DIR/skills-enabled/$s" ] || { echo "agent: skill '$s' is declared but not enabled (scripts/skill.sh activate $s)" >&2; exit 1; }
|
||||||
|
RESOLVED="${RESOLVED:+$RESOLVED,}/var/lib/mosaic/skills-enabled/$s"
|
||||||
|
done
|
||||||
|
IFS=$OLDIFS
|
||||||
|
export MOSAIC_SKILLS="$RESOLVED"
|
||||||
|
fi
|
||||||
|
|
||||||
if [ -n "$MISSION" ]; then
|
if [ -n "$MISSION" ]; then
|
||||||
[ -r "$MISSION" ] || { echo "agent: mission file not readable: $MISSION" >&2; exit 4; }
|
[ -r "$MISSION" ] || { echo "agent: mission file not readable: $MISSION" >&2; exit 4; }
|
||||||
mkdir -p "$MOSAIC_DEV_DIR/agent-missions"
|
mkdir -p "$MOSAIC_DEV_DIR/agent-missions"
|
||||||
|
|||||||
+3
-1
@@ -49,7 +49,9 @@ health_check() { # returns 0 only when the marker path passes; $1 = fault inject
|
|||||||
task="$tmp/fault-task.json"
|
task="$tmp/fault-task.json"
|
||||||
fi
|
fi
|
||||||
local rc=0
|
local rc=0
|
||||||
scripts/run-task.sh run "$task" >/dev/null 2>&1 || rc=$?
|
# MOSAIC_ENSURE_SKIP: the gate's run must not re-enter release
|
||||||
|
# self-determination (recursion guard).
|
||||||
|
MOSAIC_ENSURE_SKIP=1 scripts/run-task.sh run "$task" >/dev/null 2>&1 || rc=$?
|
||||||
[ -n "$tmp" ] && rm -rf "$tmp"
|
[ -n "$tmp" ] && rm -rf "$tmp"
|
||||||
return "$rc"
|
return "$rc"
|
||||||
}
|
}
|
||||||
|
|||||||
Executable
+102
@@ -0,0 +1,102 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Skill lifecycle: install / activate / deactivate / uninstall / list.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# scripts/skill.sh install <source-dir | bundled-name>
|
||||||
|
# scripts/skill.sh activate <name>
|
||||||
|
# scripts/skill.sh deactivate <name>
|
||||||
|
# scripts/skill.sh uninstall <name>
|
||||||
|
# scripts/skill.sh list
|
||||||
|
#
|
||||||
|
# Layout (machine-scoped, under the data root):
|
||||||
|
# <dataRoot>/skills-available/<name> installed, not loadable
|
||||||
|
# <dataRoot>/skills-enabled/<name> loadable by agent launches
|
||||||
|
#
|
||||||
|
# A skill not in skills-enabled is not enabled or available for use.
|
||||||
|
# Lifecycle: install -> available; activate -> enabled; deactivate ->
|
||||||
|
# available; uninstall -> removed (only from available).
|
||||||
|
set -euo pipefail
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
# shellcheck source=common.sh
|
||||||
|
source scripts/common.sh
|
||||||
|
|
||||||
|
load_config
|
||||||
|
load_release
|
||||||
|
bootstrap_runtime_dir
|
||||||
|
|
||||||
|
ENABLED="$MOSAIC_DEV_DIR/skills-enabled"
|
||||||
|
AVAILABLE="$MOSAIC_DEV_DIR/skills-available"
|
||||||
|
mkdir -p "$ENABLED" "$AVAILABLE"
|
||||||
|
|
||||||
|
die() { local code="$1"; shift; echo "skill: $*" >&2; exit "$code"; }
|
||||||
|
|
||||||
|
is_skill_dir() { [ -f "$1/SKILL.md" ]; }
|
||||||
|
|
||||||
|
resolve_source() { # bundled name (skills/<name> in repo) or explicit path
|
||||||
|
if [ -d "skills/$1" ]; then printf 'skills/%s' "$1"; return; fi
|
||||||
|
if [ -d "$1" ]; then printf '%s' "$1"; return; fi
|
||||||
|
die 4 "source skill dir not found: $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
frontmatter_name() { # extract name: from SKILL.md frontmatter
|
||||||
|
node -e '
|
||||||
|
const fs = require("fs");
|
||||||
|
const t = fs.readFileSync(process.argv[1], "utf8");
|
||||||
|
const m = t.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
||||||
|
if (!m) process.exit(1);
|
||||||
|
const name = (m[1].match(/^name:\s*(.+)$/m) || [])[1];
|
||||||
|
if (!name) process.exit(1);
|
||||||
|
process.stdout.write(name.trim());
|
||||||
|
' "$1/SKILL.md"
|
||||||
|
}
|
||||||
|
|
||||||
|
case "${1:-list}" in
|
||||||
|
install)
|
||||||
|
src="${2:?usage: skill.sh install <source-dir|bundled-name>}"
|
||||||
|
resolve_source "$src" >/dev/null || die 4 "source skill dir not found: $src"
|
||||||
|
SRC_DIR="$(resolve_source "$src")"
|
||||||
|
is_skill_dir "$SRC_DIR" || die 4 "not a skill (missing SKILL.md): $SRC_DIR"
|
||||||
|
NAME="$(basename "$SRC_DIR")"
|
||||||
|
[ -d "$AVAILABLE/$NAME" ] && die 1 "already installed: $NAME"
|
||||||
|
mkdir -p "$AVAILABLE"
|
||||||
|
cp -r "$SRC_DIR" "$AVAILABLE/$NAME"
|
||||||
|
echo "skill: installed $NAME -> skills-available"
|
||||||
|
echo "skill: activate with scripts/skill.sh activate $NAME"
|
||||||
|
;;
|
||||||
|
activate)
|
||||||
|
name="${2:?usage: skill.sh activate <name>}"
|
||||||
|
[ -d "$AVAILABLE/$name" ] || die 4 "not installed: $name"
|
||||||
|
[ -d "$ENABLED/$name" ] && die 1 "already enabled: $name"
|
||||||
|
mv "$AVAILABLE/$name" "$ENABLED/$name"
|
||||||
|
echo "skill: enabled $name"
|
||||||
|
;;
|
||||||
|
deactivate)
|
||||||
|
name="${2:?usage: skill.sh deactivate <name>}"
|
||||||
|
[ -d "$ENABLED/$name" ] || die 4 "not enabled: $name"
|
||||||
|
mv "$ENABLED/$name" "$AVAILABLE/$name"
|
||||||
|
echo "skill: deactivated $name -> skills-available"
|
||||||
|
;;
|
||||||
|
uninstall)
|
||||||
|
name="${2:?usage: skill.sh uninstall <name>}"
|
||||||
|
if [ -d "$ENABLED/$name" ]; then
|
||||||
|
die 1 "refusing: $name is enabled - deactivate first"
|
||||||
|
fi
|
||||||
|
[ -d "$AVAILABLE/$name" ] || die 4 "not installed: $name"
|
||||||
|
rm -rf "$AVAILABLE/$name"
|
||||||
|
echo "skill: uninstalled $name"
|
||||||
|
;;
|
||||||
|
list)
|
||||||
|
echo "enabled:"
|
||||||
|
for d in "$ENABLED"/*/; do
|
||||||
|
[ -d "$d" ] && echo " $(basename "$d")"
|
||||||
|
done
|
||||||
|
echo "available:"
|
||||||
|
for d in "$AVAILABLE"/*/; do
|
||||||
|
[ -d "$d" ] && echo " $(basename "$d")"
|
||||||
|
done
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "usage: scripts/skill.sh install <src> | activate <name> | deactivate <name> | uninstall <name> | list" >&2
|
||||||
|
exit 4
|
||||||
|
;;
|
||||||
|
esac
|
||||||
+40
-16
@@ -191,22 +191,45 @@ EOF
|
|||||||
expect_exit "retry of missing run exits 4" 4 -- \
|
expect_exit "retry of missing run exits 4" 4 -- \
|
||||||
env MOSAIC_CONFIG="$SANDBOX/mock-adapters.json" node scripts/mosaic-task.mjs retry r-missing
|
env MOSAIC_CONFIG="$SANDBOX/mock-adapters.json" node scripts/mosaic-task.mjs retry r-missing
|
||||||
|
|
||||||
# session fork plumbing (M11): fork source + target dir delivered
|
# skills lifecycle (M17)
|
||||||
printf '{"taskVersion":1,"id":"t-forkplumb","prompt":"x","session":"fork-child","sessionForkFrom":"base"}' > "$SANDBOX/forkplumb.json"
|
expect_exit "skill install bundled ms-tools" 0 -- \
|
||||||
mkdir -p "$SANDBOX/data/sessions/base"
|
env MOSAIC_CONFIG="$SANDBOX/mock-adapters.json" scripts/skill.sh install ms-tools
|
||||||
printf '{}' > "$SANDBOX/data/sessions/base/20260903T000000-plumb.jsonl"
|
[ -d "$SANDBOX/data/skills-available/ms-tools" ] \
|
||||||
expect_exit "fork task runs via mock" 0 -- \
|
&& check "installed to skills-available" 0 || check "installed to skills-available" 1
|
||||||
env MOSAIC_CONFIG="$SANDBOX/mock-adapters.json" MOSAIC_MOCK_RESPONSE=MOCKED \
|
expect_exit "double install refuses" 1 -- \
|
||||||
scripts/run-task.sh run "$SANDBOX/forkplumb.json"
|
env MOSAIC_CONFIG="$SANDBOX/mock-adapters.json" scripts/skill.sh install ms-tools
|
||||||
FL="$(ls -dt "$SANDBOX/data/runs"/r-* | head -1)"
|
expect_exit "activate enables skill" 0 -- \
|
||||||
grep -q '^MOSAIC_SESSION_FORK=/var/lib/mosaic/sessions/base/20260903T000000-plumb.jsonl$' "$FL/stderr.txt" 2>/dev/null \
|
env MOSAIC_CONFIG="$SANDBOX/mock-adapters.json" scripts/skill.sh activate ms-tools
|
||||||
&& grep -q '^MOSAIC_SESSION_DIR=/var/lib/mosaic/sessions/fork-child$' "$FL/stderr.txt" 2>/dev/null \
|
[ -d "$SANDBOX/data/skills-enabled/ms-tools" ] \
|
||||||
&& check "fork source + target delivered to adapter" 0 \
|
&& check "enabled dir populated" 0 || check "enabled dir populated" 1
|
||||||
|| check "fork source + target delivered to adapter" 1
|
expect_exit "uninstall while enabled refuses" 1 -- \
|
||||||
printf '{"taskVersion":1,"id":"t-f2","prompt":"x","sessionForkFrom":"base"}' > "$SANDBOX/noforktarget.json"
|
env MOSAIC_CONFIG="$SANDBOX/mock-adapters.json" scripts/skill.sh uninstall ms-tools
|
||||||
expect_exit "fork without session target exits 2" 2 -- $TASK validate "$SANDBOX/noforktarget.json"
|
expect_exit "deactivate moves back to available" 0 -- \
|
||||||
printf '{"taskVersion":1,"id":"t-f3","prompt":"x","session":"base","sessionForkFrom":"base"}' > "$SANDBOX/selfork.json"
|
env MOSAIC_CONFIG="$SANDBOX/mock-adapters.json" scripts/skill.sh deactivate ms-tools
|
||||||
expect_exit "self-fork exits 2" 2 -- $TASK validate "$SANDBOX/selfork.json"
|
expect_exit "uninstall removes from available" 0 -- \
|
||||||
|
env MOSAIC_CONFIG="$SANDBOX/mock-adapters.json" scripts/skill.sh uninstall ms-tools
|
||||||
|
|
||||||
|
# seat skills dispatch (mock evidence)
|
||||||
|
mkdir -p "$SANDBOX/agents/skillseat" "$SANDBOX/data/user"
|
||||||
|
printf '# User\n\nname: Jason\n' > "$SANDBOX/data/user/USER.md"
|
||||||
|
printf '{"agentVersion":1,"name":"skillseat","skills":["ms-tools"]}' > "$SANDBOX/agents/skillseat/agent.json"
|
||||||
|
printf '{"agentVersion":1,"name":"skillseat","soulPlaceholder":true}' > /dev/null # no-op
|
||||||
|
printf '# SOUL - skillseat\n\nMethodical. Verifies before claiming.\n' > "$SANDBOX/agents/skillseat/SOUL.md"
|
||||||
|
# Pre-align the release pointer so ensure does not fire under the mock
|
||||||
|
# adapter (the gate needs a real model; the mock cannot answer).
|
||||||
|
REL="$(tr -d '[:space:]' < RELEASE)"
|
||||||
|
mkdir -p "$SANDBOX/data/state"
|
||||||
|
printf '{"pointerVersion":1,"release":"%s","imageTag":"mosaic-poc-agent:0.84.4-r%s","activatedAt":"2026-01-01T00:00:00Z"}\n' "$REL" "$REL" > "$SANDBOX/data/state/active.json"
|
||||||
|
scripts/agent.sh skillseat </dev/null >"$SANDBOX/seat-stdout.txt" 2>"$SANDBOX/seat-stderr.txt"
|
||||||
|
RC=$?
|
||||||
|
if [ "$RC" -eq 0 ]; then
|
||||||
|
PASS=$((PASS+1)); echo "${C_OK}OK${C_RESET} seat with enabled skill launches"
|
||||||
|
else
|
||||||
|
FAIL=$((FAIL+1)); echo "${C_FAIL}FAIL${C_RESET} seat with enabled skill launches (exit $RC)" >&2
|
||||||
|
echo "SEATCASE stderr:" >&2; cat "$SANDBOX/seat-stderr.txt" >&2
|
||||||
|
fi
|
||||||
|
grep -q '^MOSAIC_SKILLS=/var/lib/mosaic/skills-enabled/ms-tools$' "$SANDBOX/seat-stderr.txt" 2>/dev/null \
|
||||||
|
&& check "skill path delivered to adapter" 0 || check "skill path delivered to adapter" 1
|
||||||
|
|
||||||
# capability policy (M9): least-privilege intersection
|
# capability policy (M9): least-privilege intersection
|
||||||
POL="$SANDBOX/data/workspaces"; mkdir -p "$POL"
|
POL="$SANDBOX/data/workspaces"; mkdir -p "$POL"
|
||||||
@@ -236,6 +259,7 @@ EOF
|
|||||||
printf '{"missionVersion":1,"id":"m-pol","objective":"o","capabilities":{"tools":["sudo"]}}' > "$SANDBOX/pol-m.json"
|
printf '{"missionVersion":1,"id":"m-pol","objective":"o","capabilities":{"tools":["sudo"]}}' > "$SANDBOX/pol-m.json"
|
||||||
expect_exit "invalid mission capabilities rejected" 2 -- \
|
expect_exit "invalid mission capabilities rejected" 2 -- \
|
||||||
env MOSAIC_CONFIG="$SANDBOX/mock-adapters.json" $TASK validate "$SANDBOX/pol-t.json"
|
env MOSAIC_CONFIG="$SANDBOX/mock-adapters.json" $TASK validate "$SANDBOX/pol-t.json"
|
||||||
|
env MOSAIC_CONFIG="$SANDBOX/mock-adapters.json" $TASK validate "$SANDBOX/pol-t.json"
|
||||||
|
|
||||||
# live user context (M14): dispatched to every launch without rebuild
|
# live user context (M14): dispatched to every launch without rebuild
|
||||||
mkdir -p "$SANDBOX/data/user"
|
mkdir -p "$SANDBOX/data/user"
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
---
|
||||||
|
name: ms-agent-watch
|
||||||
|
description: Use for all instances where a watch, wait, or agentic status check is needed. This skill avoid the need for wait cycles and other methods used to wait for an action outcome.
|
||||||
|
disable-model-invocation: false
|
||||||
|
---
|
||||||
|
|
||||||
|
# ms-agent-watch — self-armed condition watches
|
||||||
|
|
||||||
|
One CLI call arms an isolated `systemd --user` transient timer per watch. Each
|
||||||
|
tick is a fresh, cgroup-isolated process; no loop, no script, no orphan. You
|
||||||
|
never `sleep`, never write a watch script, never background anything.
|
||||||
|
|
||||||
|
## Tool
|
||||||
|
|
||||||
|
```bash
|
||||||
|
tools/agent-watch/agent-watch.sh start \
|
||||||
|
--name <lowercase-id> --session <your-tmux-session> \
|
||||||
|
[--socket <tmux-socket>] # REQUIRED for mosaic-fleet seats using tmux
|
||||||
|
--when '<shell command>' # exit 0 = met; quote it
|
||||||
|
--message "text delivered to you" \
|
||||||
|
[--class actionable|reaction|human|digest|terminal-log] \
|
||||||
|
[--interval 30] [--timeout 3600] [--repeat] [--quiet-timeout]
|
||||||
|
|
||||||
|
agent-watch.sh list # your host's watches
|
||||||
|
agent-watch.sh log <name> # what it did and why
|
||||||
|
agent-watch.sh stop <name> # retire + clean state
|
||||||
|
```
|
||||||
|
|
||||||
|
## Rules
|
||||||
|
|
||||||
|
1. **Name it for the thing watched** (`ci-pr1350-green`, `peer-orch-01-reply`),
|
||||||
|
not for yourself — names appear in `[watch:<name>]` message prefixes and
|
||||||
|
in `systemctl list-timers 'agent-watch-*'`.
|
||||||
|
2. **Interval floor is 10s.** A watcher is a fallback cadence. If you find
|
||||||
|
yourself wanting 1s polls, the real fix is an event, not a faster watch.
|
||||||
|
3. **Timeout is real** (default 1h): on expiry you get one terminal-log note
|
||||||
|
(unless `--quiet-timeout`) and the watch retires. A watch is never forever;
|
||||||
|
re-arm deliberately if the condition is still pending.
|
||||||
|
4. **Conditions are cron-style**: clean-ish environment, `cwd=$HOME`. Use
|
||||||
|
absolute paths. Do NOT rely on ambient credentials — resolve tokens through
|
||||||
|
the git credential helper or absolute service-credential paths.
|
||||||
|
5. **rc=2 delivery is DELIVERED** (text reached your pane as a draft — FLEET-COMMS
|
||||||
|
E7); the watcher never retries it. Real failures retry twice then retire loudly.
|
||||||
|
6. **A broken condition (exit ≠ 0/1) retires the watch** with a terminal-log
|
||||||
|
note. Check `log <name>` before re-arming — re-arming a broken condition
|
||||||
|
without fixing it just burns another timeout.
|
||||||
|
7. **`--repeat` re-arms after every delivery.** Default is one-shot on purpose:
|
||||||
|
each watch should correspond to one pending fact.
|
||||||
|
8. **Retire your watches** when the mission closes (`stop`). `list` shows
|
||||||
|
stale-state entries whose timer is gone; stop removes those too.
|
||||||
|
9. Fleet seats MUST pass `--socket mosaic-fleet` — the default is the default
|
||||||
|
socket and the delivery will not reach you.
|
||||||
|
|
||||||
|
## When NOT to watch
|
||||||
|
|
||||||
|
- Waiting on another SEAT: send them a message (agent-send / comms) instead —
|
||||||
|
a watch polling their output is a loop wearing a hat.
|
||||||
|
- Waiting on yourself: just do the next thing.
|
||||||
|
- Something that already has a wake path (fleet watcher injects comms
|
||||||
|
automatically): those arrive without any watch.
|
||||||
|
|
||||||
|
## Failure model (what you'll see)
|
||||||
|
|
||||||
|
| Symptom | Meaning |
|
||||||
|
|---|---|
|
||||||
|
| `started watcher ...` then nothing, timer inactive | condition broke (exit ≠ 0/1) or timeout hit — `log <name>` says which |
|
||||||
|
| `[watch:x] ... timeout after Ns` | retired; re-arm only if still relevant |
|
||||||
|
| delivered (rc=2) in log | delivered as draft into your pane — go read the pane |
|
||||||
|
| delivery failed rc=1 after 3 attempts | your session was gone; restart it, then re-arm |
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
---
|
||||||
|
name: ms-agent
|
||||||
|
description: Use for ALL agent operation cycles.
|
||||||
|
disable-model-invocation: false
|
||||||
|
---
|
||||||
|
|
||||||
|
# ms-agent
|
||||||
|
|
||||||
|
Agent operation cycles: launch, work, verify, persist, hand off.
|
||||||
|
|
||||||
|
## Cycle
|
||||||
|
|
||||||
|
1. Launch: `scripts/agent.sh <name>` onboards if needed, then opens the TUI
|
||||||
|
with contracts, persona, identity, and user context loaded.
|
||||||
|
2. Work inside your workspace. Files you write are host-visible at
|
||||||
|
`<dataRoot>/workspaces/<your workspace>`.
|
||||||
|
3. Your session persists in `sessions/<name>`. Relaunching the same seat
|
||||||
|
resumes where you left off.
|
||||||
|
4. Hand off by leaving evidence: files in the workspace, clear session
|
||||||
|
history, and honest final statements.
|
||||||
|
|
||||||
|
## Rules
|
||||||
|
|
||||||
|
- One seat, one identity. Never share sessions or workspaces across seats.
|
||||||
|
- Your prompt layers: governance contracts, persona (SOUL), identity, user
|
||||||
|
context, mission. Later layers refine earlier ones. Governance is never
|
||||||
|
overridden.
|
||||||
|
- If a capability you need is missing, say so. Do not improvise a capability
|
||||||
|
you were not granted.
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
---
|
||||||
|
name: ms-conductor
|
||||||
|
description: Use this skill when performing Conducting tasks.
|
||||||
|
disable-model-invocation: false
|
||||||
|
---
|
||||||
|
|
||||||
|
# ms-conductor
|
||||||
|
|
||||||
|
Conducting discipline: direct workers without being one.
|
||||||
|
|
||||||
|
## Order of operations
|
||||||
|
|
||||||
|
1. Decompose the goal into worker tasks small enough to spec completely in
|
||||||
|
one prompt: goal, files, constraints, acceptance, self-checks.
|
||||||
|
2. Dispatch through the task runner. Never raw pi; never a shell one-liner.
|
||||||
|
3. Extract the worker's diff. Review it line by line before integration.
|
||||||
|
4. Verify with the suites. A failure reverts; the refusal is recorded.
|
||||||
|
5. Integrate with attribution. Update the plan and registry.
|
||||||
|
|
||||||
|
## Gotcha ledger
|
||||||
|
|
||||||
|
- Sequential dependent calls. Verify a write before claiming it done.
|
||||||
|
- Pre-check every path before passing it to a tool. Missing paths fail
|
||||||
|
silently in some consumers.
|
||||||
|
- Auth and symlink ensure before TUI launch. Missing auth falls back to
|
||||||
|
defaults silently.
|
||||||
|
- Post-reset: the release pointer is gone and onboarding reruns. Both are
|
||||||
|
expected; align releases with `release.sh ensure`.
|
||||||
|
- A worker that passes for the wrong reason is a masking failure. Assert
|
||||||
|
reasons, not just exit codes.
|
||||||
|
|
||||||
|
## Refusals
|
||||||
|
|
||||||
|
Refuse rather than guess. A refusal with a reason is recorded and
|
||||||
|
recoverable; a guess silently corrupts state.
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
---
|
||||||
|
name: ms-file-read
|
||||||
|
description: Use this skill when reading any file.
|
||||||
|
disable-model-invocation: false
|
||||||
|
---
|
||||||
|
|
||||||
|
# ms-file-read
|
||||||
|
|
||||||
|
Read files before you act on them. Never act on a filename alone.
|
||||||
|
|
||||||
|
## Rules
|
||||||
|
|
||||||
|
1. Read the file before editing, summarizing, or deciding anything about it.
|
||||||
|
2. Large files: read in chunks with offset and limit instead of dumping.
|
||||||
|
3. Verify what you read matches what you expected before building on it.
|
||||||
|
4. Never read credential material (auth files, tokens, keys). The name is
|
||||||
|
warning enough.
|
||||||
|
|
||||||
|
## When
|
||||||
|
|
||||||
|
- Before any edit: the edit must match what is actually on disk.
|
||||||
|
- Before answering questions about file contents.
|
||||||
|
- When a run record, log, or receipt is cited as evidence.
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
---
|
||||||
|
name: ms-file-write
|
||||||
|
description: Use this skill when writing any file.
|
||||||
|
disable-model-invocation: false
|
||||||
|
---
|
||||||
|
|
||||||
|
# ms-file-write
|
||||||
|
|
||||||
|
Write files so the write is provable and reversible.
|
||||||
|
|
||||||
|
## Rules
|
||||||
|
|
||||||
|
1. Write to a temp file and rename for atomic replacement of an existing file.
|
||||||
|
2. Verify the write: re-read or checksum before claiming success.
|
||||||
|
3. Never overwrite a file that carries identity or history (seat records,
|
||||||
|
run records, logs). They are append-only or write-once for a reason.
|
||||||
|
4. No secrets in written files. Ever.
|
||||||
|
5. Match the file's existing style. Do not reformat regions you did not touch.
|
||||||
|
|||||||
@@ -1,14 +1,42 @@
|
|||||||
---
|
---
|
||||||
name: ms-tools
|
name: ms-tools
|
||||||
description: Contains a reference to all available tools for Mosaic Stack.
|
description: Reference for all available tools in Mosaic Stack.
|
||||||
disable-model-invocation: true
|
disable-model-invocation: false
|
||||||
---
|
---
|
||||||
|
|
||||||
# ms-tools
|
# ms-tools
|
||||||
|
|
||||||
You are a Mosaic fleet agent. A maintained toolkit lives at `<dir>`.
|
You are a Mosaic seat agent. The maintained tooling lives in the repository
|
||||||
Use it FIRST for the tasks below — improvising with raw CLIs causes the recurring failures this
|
`scripts/` directory (conductor side) and `/opt/mosaic` (your container).
|
||||||
skill exists to prevent. This is the high-frequency fast path; the full reference is the
|
Use it FIRST for the tasks below. Improvising with raw commands causes the
|
||||||
`# Machine Tools` section already in your system prompt.
|
recurring failures this skill exists to prevent.
|
||||||
|
|
||||||
|
## Lifecycle
|
||||||
|
|
||||||
|
- scripts/bootstrap.sh creates the system config. Idempotent, never overwrites.
|
||||||
|
- scripts/build.sh builds the release image. Tag comes from RELEASE.
|
||||||
|
- scripts/verify.sh runs the gated startup check. Exit 0 means MOSAIC_HELLO_OK.
|
||||||
|
- scripts/release.sh ensure aligns what is installed with RELEASE. No manual
|
||||||
|
release commands; drift is detected and corrected at launch.
|
||||||
|
|
||||||
|
## Tasks and runs
|
||||||
|
|
||||||
|
- scripts/run-task.sh run <task.json> executes a governed task. Every run
|
||||||
|
leaves a write-once record under <dataRoot>/runs/.
|
||||||
|
- node scripts/mosaic-task.mjs list shows run history.
|
||||||
|
- node scripts/mosaic-task.mjs show <runId> inspects one run: receipts,
|
||||||
|
snapshots, stderr.
|
||||||
|
- node scripts/mosaic-task.mjs retry <runId> re-executes a recorded task as
|
||||||
|
a new run. The old record stays.
|
||||||
|
|
||||||
|
## Seats
|
||||||
|
|
||||||
|
- scripts/agent.sh <name> launches an interactive seat. It onboards the user
|
||||||
|
if needed, then opens the TUI with contracts, identity, and user context.
|
||||||
|
- agents/<name>/ holds a seat definition: agent.json plus SOUL.md.
|
||||||
|
|
||||||
|
## Rules
|
||||||
|
|
||||||
|
- Exit codes: 0 ok, 1 failed, 2 invalid input, 3 config missing, 4 usage.
|
||||||
|
- A refusal is evidence. Diagnose it; do not route around it.
|
||||||
|
- Full reference: docs/TOOLS.md.
|
||||||
|
|||||||
+32
-2
@@ -4,5 +4,35 @@ description: Always use to update recorded information about the user
|
|||||||
disable-model-invocation: false
|
disable-model-invocation: false
|
||||||
---
|
---
|
||||||
|
|
||||||
# User
|
# ms-user
|
||||||
Info about the tooling and usage should be listed here.
|
|
||||||
|
The user context layer holds user-owned information dispatched to every
|
||||||
|
agent launch: everything under `<dataRoot>/user/`, with `USER.md` as the
|
||||||
|
profile.
|
||||||
|
|
||||||
|
## Rules
|
||||||
|
|
||||||
|
1. The user owns the content. It is injected into your system prompt when
|
||||||
|
present; verify it on disk before editing.
|
||||||
|
2. Always check for the `<dataRoot>/user/USER.md` file before attempting
|
||||||
|
to update info.
|
||||||
|
3. If the USER.md file is missing, guide the user through onboarding
|
||||||
|
(`scripts/onboard.sh`) or, with their consent, create a minimal
|
||||||
|
template (name only) and let them fill in the rest.
|
||||||
|
4. Initial configuration uses `scripts/onboard.sh`
|
||||||
|
(guided, name required) or the user's own edits.
|
||||||
|
Agents propose; the user decides and authorizes; once authorized,
|
||||||
|
the agent performs the edit.
|
||||||
|
5. NEVER place secrets in the user layer. Everything under
|
||||||
|
`<dataRoot>/user/` is dispatched to every agent and worker, so keep
|
||||||
|
near-secrets out too: home address, finances, anything you would not
|
||||||
|
hand a stranger with shell access.
|
||||||
|
6. Propose every change to the user layer, new content or new section
|
||||||
|
alike, and apply it only after an explicit yes.
|
||||||
|
7. Propose updates proactively as information about the user is learned.
|
||||||
|
8. The USER.md file is not limited to the provided template fields.
|
||||||
|
Add sections as needed.
|
||||||
|
9. Never block work on proposed updates to the user information files.
|
||||||
|
Note the proposed additions in a scratch file outside
|
||||||
|
`<dataRoot>/user/` (unconfirmed content must not be dispatched),
|
||||||
|
then confirm with the user at a better time.
|
||||||
|
|||||||
Reference in New Issue
Block a user