Install
openclaw skills install @hoshiyomix/stellar-trailsActivates on every task: coding (features, bugs, refactoring, scripts), documents (reports, proposals, DOCX, PDF), charts and visualizations, data processing, complex multi-step planning, or even simple questions. Provides a six-phase workflow with traceability IDs, entry/exit gates, scope commitment, and three enforcement layers (phase machine, mandatory prints, preferences dialog). Complexity adapts per task tier. Use this skill whenever the user asks to build, fix, analyze, create, plan, or process anything — the framework runs internally for trivial tasks and fully for complex ones. Web development (Next.js, UI) is delegated to fullstack-dev; this framework wraps the workflow around it.
openclaw skills install @hoshiyomix/stellar-trailsBefore calling ANY tool (Read, Write, Bash, Edit, Grep, Glob, Task, etc.) in a session, the activation banner MUST have been printed AND Steps 1–5 must have been executed. If you are about to call a tool and have NOT printed the banner + completed all 5 steps, STOP and do activation FIRST.
Self-check before first tool call:
☄️ STELLAR TRAILS · vX.Y.Z · ACTIVE? → If NO, print it NOW✓/✗ marks? → If NO, execute them NOW✓ Step 5? → If NO, do not proceed to any toolThis is a HARD GATE. No tool call may precede the banner + 5 steps. Violating this gate is a correctness bug, not a style preference.
Why this gate exists: Audit of 5 prior sessions found 0/5 compliance with activation mandate. LLMs rationalize skipping ("continuation task", "simple task", "save tokens", "user didn't complain last time"). The gate makes skipping impossible to rationalize — you literally cannot call a tool until activation is done.
Subagent exemption (added v9.11.4): This gate applies to the main agent only. Subagents in z.ai receive a compressed task prompt from the orchestrator — they do NOT have SKILL.md pre-loaded into context. To learn the gate exists, a subagent would have to call Skill(command="stellar-trails"), which is itself a pre-banner tool call (chicken-and-egg). Therefore E4 is structurally unenforceable on subagents. If subagent compliance is required, the orchestrating main agent MUST pre-inject the relevant SKILL.md sections (activation mandate + step bash blocks) into the subagent's task prompt — only then can the subagent comply. Verified by SIM-001/SIM-002 audit (v9.11.3): both Explore and general-purpose subagents can call Skill() and read SKILL.md from disk, but neither prints the banner first because they have no prior knowledge of the mandate.
Your VERY FIRST output to the user is the activation banner below. No other text precedes it. Print the banner, then run Steps 1–5.
Why print every invoke: After context truncation, neither you nor the user know whether the banner was already printed. The banner is the only reliable signal that activation ran. Skipping it because "I already did it" is a correctness bug — you cannot reliably know what you did before truncation.
Banner version is DYNAMIC: Read the version from the ## Metadata section at the top of this file (the - **version**: X.Y.Z line). Substitute that version into the banner below where you see <VERSION>. Do NOT hardcode the version — every version bump must automatically reflect in the banner without editing this template. (Fixes the v9.2.1 bug where the banner was stuck at v9.1.0 because it was hardcoded.)
☄️ STELLAR TRAILS · v<VERSION> · ACTIVE
├─ Phase: IDLE → SPECIFY
├─ Complexity: [tier] | Task Type: [type] | Continuation: [NEW / YES]
└─ Activation checklist (1–5, every invoke) — executing:
├─ 1 Refresh context + SSV ...
├─ 2 Start popup server ...
├─ 3 Auto-update via ClawHub ...
├─ 4 Verify files + sync zip ...
└─ 5 Load phases + classify ...
Replace ... with ✓ (success) or ✗ (failure) as each step completes.
Text enforcement (E1-E6) caps at ~70-85% compliance because LLMs can rationalize past text. The 5 vectors below use sandbox-native mechanisms that LLMs cannot fabricate — tool calls are ground truth, file tokens are verifiable, external oracles cannot be faked.
Expected compliance: ~90-93% automated, ~95% with active user verification of transcript.
Every bash block in activation writes/verifies a hash token. LLM cannot proceed past Step 2+ without actually running Step 1 (which writes the token).
Token file: /tmp/st-active (session-scoped, wiped on session reset)
Token content: sha256(version_line_from_SKILL.md)[:16]
Step 1 writes the token. Steps 2-5 verify it at the top of their bash block:
# Gate check (top of every Step 2+ bash block):
EXPECTED=$(grep -oP '^- \*\*version\*\*:\s*\K[0-9.]+' /home/z/my-project/skills/stellar-trails/SKILL.md | sha256sum | cut -c1-16)
ACTUAL=$(cat /tmp/st-active)
if [ "$EXPECTED" != "$ACTUAL" ]; then
echo "✗ GATE FAILED: activation token missing/invalid — Step 1 must run first"
exit 1
fi
If LLM skips Step 1, token doesn't exist → Step 2 bash exits 1 → LLM cannot complete task. LLM cannot fake this because writing the correct hash requires reading SKILL.md (a tool call recorded in transcript).
Subagent write access caveat (added v9.11.4): /tmp/st-active is owned by the same user that subagents run as (-rw-rw-r-- z:z), so a subagent CAN overwrite the token to bypass the E7 gate. The gate enforces Step 1 → Step 2+ ordering for the main agent; it does not prevent a malicious subagent from writing a valid token without running Step 1. Mitigation: the orchestrating main agent should pre-validate that the token was written by Step 1 (not by a subagent) before trusting subsequent steps. This is best-effort enforcement, not tamper-proof.
Token determinism caveat (added v9.13.1): The token is sha256("<version>\n")[:16] — a pure function of the version string, not a session secret. Every session (main agent, subagent, concurrent or not) running the same skill version computes the identical token value. Verified live: token 6c81522b916ad90e appeared in activation-log entries written by ≥2 distinct sessions running v9.12.0 on 2026-08-23. The gate proves "someone hashed the version line on this filesystem", never "this specific agent ran Step 1". The enforcement matrix row is therefore downgraded from NO to PARTIAL.
TodoWrite is a platform tool that renders a live checklist visible to the user in real-time. The user sees checkboxes transition pending → in_progress → completed as the LLM works. This is visibility enforcement — user can SEE if steps are being executed, not just trust LLM's text claims.
Mandatory protocol (before Step 1 bash):
TodoWrite with 5 items (Step 1 through Step 5), all status pendingTodoWrite to mark Step N in_progressTodoWrite to mark Step N completedin_progress, do NOT mark completedWhy this works: TodoWrite is a tool call recorded in transcript. LLM cannot fabricate "I marked Step 3 completed" without actually calling TodoWrite. User sees the live checklist — if Step 3 is marked completed but no Bash tool call for Step 3 appears in transcript, fraud is visible.
Edge case: LLM could mark all 5 completed in a single TodoWrite call without running anything. Mitigation: user watches for the transition (pending → in_progress → completed), not just final state. Single-call mass-marking is suspicious.
Each successful activation appends to /home/user_skills/.st-activation-log (persistent across sessions). User can audit this file anytime.
Log entry format (one line per activation — corrected v9.13.1 to match actual Step 5 bash output):
YYYY-MM-DDTHH:MM:SSZ v<VERSION> token=<hash> steps=5/5 banner=YES
Step 5 writes the log entry. Next session's Step 1 can read the log to verify prior activations occurred. LLM can fake a log entry, but:
cat /home/user_skills/.st-activation-log to audit anytimeEmpirical persistence + multi-session caveat (added v9.13.1): Cross-session persistence is a verified fact, not an aspiration. At audit time the log held 326 entries across 38 distinct days (2026-07-11 → 2026-08-23, versions v9.4.0 → v9.13.0) with 0 timestamp-monotonicity violations. However, the sandbox filesystem is shared by concurrent sessions, and their entries interleave indistinguishably. On 2026-08-23 alone, ≥2 sessions interleaved within one hour — including a COMPLIANCE v9.13.0 score=12/12 entry that belongs to a session which wrote no adjacent activation entry. Only version/token discontinuities distinguish the sessions. The log proves WHEN, never WHO.
Best-effort caveat (added v9.11.4): /home/user_skills/ is world-writable (drwxrwxrwx mode 0777) in the z.ai sandbox, meaning any process — including subagents — can modify or append to the activation log. This vector is fraud-detection (anomalous timestamps are visible on audit), not fraud-prevention. The real value of E9 is visibility for the user, not cryptographic integrity.
Step 1 must print a specific line from SKILL.md in the format Read tool returns (<line_number>→<content>). LLM cannot fabricate this format without actually calling Read.
Protocol: After Read SKILL.md, print the version line with its line number:
SKILL.md line 19: 19→- **version**: 9.4.0
User can verify by reading the same file themselves. If LLM fabricates (e.g., wrong line number, wrong content), user catches it on spot-check.
Limitation: LLM knows line 19 is the version line (from context). Stronger variant: skill mandates "print line N where N = current day-of-month + 10" — LLM cannot predict which line user will verify. But this adds complexity; the simple variant is sufficient for now.
Step 3 runs clawhub inspect — output is ground truth from an external binary LLM cannot fabricate. Step 4 re-runs clawhub inspect and cross-checks the version matches what Step 3 reported.
Protocol:
clawhub inspect --json output to /tmp/st-clawhub-oracle.json/tmp/st-clawhub-oracle.json, extracts latestVersion.versionWhy this works: clawhub is a binary outside LLM control. The real protection is that Step 4 re-runs clawhub inspect and cross-checks the version — that re-run is the actual oracle. The cached file at /tmp/st-clawhub-oracle.json is just an optimization to avoid a second network call.
Fabrication caveat (corrected v9.11.4, re-verified v9.13.1): A previous version of this section claimed "LLM cannot fabricate /tmp/st-clawhub-oracle.json without actually running clawhub." This was overstated — the file is plain JSON at /tmp/ (permissions -rw-rw-r-- z:z), confirmed by both bash stat and python3 os.stat. Any bash command can write arbitrary content to it. The actual protection is Step 4's re-verification via fresh clawhub inspect calls, not the file's contents. The file is an audit artifact, not a tamper-proof oracle.
Parse-defensiveness note (added v9.13.1): Live registry responses may omit or null fields the checks might expect — moderation.state and name were absent/None while latestVersion.version was present and correct. Always extract latestVersion.version defensively: python3 -c "import json,sys; d=json.load(sys.stdin); print((d.get('latestVersion') or {}).get('version') or '')".
| Vector | What it enforces | LLM can fake? | User can verify? |
|---|---|---|---|
| E7 Hash token | Steps 2-5 cannot run without Step 1 | PARTIAL (proves hashing happened, not who did it — token is version-derived, identical across concurrent sessions) | YES (cat /tmp/st-active) |
| E8 TodoWrite | Steps visible in real-time UI | Partially (can mass-mark, but transitions are visible) | YES (watch live checklist) |
| E9 Persistent log | Cross-session audit trail | Partially (timestamps must be monotonic; no session ID — log proves WHEN, never WHO) | YES (cat /home/user_skills/.st-activation-log) |
| E10 Line-number proof | Step 1 actually called Read | Partially (LLM knows line 19) | YES (read same file, compare) |
| E11 Clawhub oracle | Step 3 actually ran clawhub | NO (external binary output is ground truth) | YES (cat /tmp/st-clawhub-oracle.json) |
What still cannot be enforced: Banner printed as FIRST output (text ordering), LLM not printing fake ✓ Step N markers (text). These remain text-only enforcement via E4-E6.
Step 1 — Refresh context + SSV: Re-read /home/z/my-project/skills/stellar-trails/SKILL.md from disk using the Read tool. Do not trust cached context — the on-disk version is source of truth. If task involves a git repo, run SSV. E7 (hash token) and E10 (line-number proof) are written by this step — subsequent steps verify the token to enforce that Step 1 actually ran.
# v9.13.2 FIX: Banner is printed BY BASH, not by LLM text before bash.
# Root cause of E4 violations: banner was text the LLM was supposed to print
# BEFORE running Step 1 bash. But the LLM often skips it and goes straight to
# bash. Fix: embed the banner echo as the FIRST line of Step 1 bash itself.
# This way, the banner is ALWAYS printed when Step 1 runs — the LLM cannot skip it.
_ST_VER=$(grep -oP '^- \*\*version\*\*:\s*\K[0-9.]+' /home/z/my-project/skills/stellar-trails/SKILL.md | head -1)
echo "☄️ STELLAR TRAILS · v${_ST_VER} · ACTIVE"
echo "├─ Phase: IDLE → SPECIFY"
echo "├─ Complexity: [tier] | Task Type: [type] | Continuation: [NEW / YES]"
echo "└─ Activation checklist (1–5, every invoke) — executing:"
# SSV only runs if the skill has its own git repo at $HOME/.stellar-trails-repo/.
# In the z.ai sandbox this directory usually does not exist (skill is installed
# via clawhub, not git clone), so SSV is skipped gracefully. Running bare
# `git fetch` from /home/z/my-project/ would operate on the sandbox workspace
# repo — explicitly forbidden by knowledge/zai-sandbox.md.
if [ -d "$HOME/.stellar-trails-repo/.git" ]; then
git -C "$HOME/.stellar-trails-repo" fetch origin --quiet
BRANCH=$(git -C "$HOME/.stellar-trails-repo" branch --show-current || echo main)
BEHIND=$(git -C "$HOME/.stellar-trails-repo" rev-list --count HEAD..origin/$BRANCH)
if [ -n "$BEHIND" ] && [ "$BEHIND" -gt 0 ]; then echo "✗ Step 1 FAILED: skill repo is $BEHIND commits behind origin — run git -C $HOME/.stellar-trails-repo pull"; exit 1
else echo "✓ Step 1: context refreshed + SSV passed (v$(grep -oP '^- \*\*version\*\*:\s*\K[0-9.]+' /home/z/my-project/skills/stellar-trails/SKILL.md || echo unknown))"; fi
else
echo "✓ Step 1: context refreshed (v$(grep -oP '^- \*\*version\*\*:\s*\K[0-9.]+' /home/z/my-project/skills/stellar-trails/SKILL.md || echo unknown)) — SSV skipped (no skill git repo)"
fi
# E7: Write hash token — Steps 2-5 verify this token to prove Step 1 ran.
# Token = sha256(version line)[:16]. LLM cannot fake this without reading SKILL.md.
grep -oP '^- \*\*version\*\*:\s*\K[0-9.]+' /home/z/my-project/skills/stellar-trails/SKILL.md | sha256sum | cut -c1-16 > /tmp/st-active
# E10: Print line-number proof — user can verify by reading same file.
SKILL_VERSION_LINE=$(grep -n '^- \*\*version\*\*:' /home/z/my-project/skills/stellar-trails/SKILL.md | head -1 | cut -d: -f1)
echo " E7 token: $(cat /tmp/st-active)"
echo " E10 line proof: SKILL.md line ${SKILL_VERSION_LINE}: $(sed -n "${SKILL_VERSION_LINE}p" /home/z/my-project/skills/stellar-trails/SKILL.md)"
# Auto Git Identity Setup (NEW in v9.10.1) — if PAT exists, auto-configure git identity
# from GitHub API. Fixes: Z User author, credentials gagal, UUID local.
# Runs automatically every activation — no manual step needed.
if [ -f /home/z/my-project/upload/PAT ]; then
_GH_TOKEN=$(tr -d '[:space:]' < /home/z/my-project/upload/PAT)
_OWNER_JSON=$(curl -sS -m 10 -H "Authorization: Bearer $_GH_TOKEN" https://api.github.com/user)
_OWNER_LOGIN=$(echo "$_OWNER_JSON" | python3 -c "import json,sys; print(json.load(sys.stdin).get('login',''))")
if [ -n "$_OWNER_LOGIN" ]; then
_OWNER_NAME=$(echo "$_OWNER_JSON" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('name') or d.get('login',''))")
_OWNER_EMAIL="${_OWNER_LOGIN}@users.noreply.github.com"
git config --global user.email "$_OWNER_EMAIL"
git config --global user.name "$_OWNER_NAME"
git config --global credential.helper store
echo "https://${_OWNER_LOGIN}:${_GH_TOKEN}@github.com" > ~/.git-credentials
chmod 600 ~/.git-credentials
export GIT_AUTHOR_NAME="$_OWNER_NAME" GIT_AUTHOR_EMAIL="$_OWNER_EMAIL"
export GIT_COMMITTER_NAME="$_OWNER_NAME" GIT_COMMITTER_EMAIL="$_OWNER_EMAIL"
echo " Git identity: $_OWNER_NAME <$_OWNER_EMAIL> (auto-configured from PAT)"
fi
fi
Step 2 — Start popup server + verify mascot: E7 gate check at top of bash block — verifies Step 1 ran by checking hash token.
# E7 gate check — proves Step 1 actually ran (token requires reading SKILL.md)
EXPECTED_TOKEN=$(grep -oP '^- \*\*version\*\*:\s*\K[0-9.]+' /home/z/my-project/skills/stellar-trails/SKILL.md | sha256sum | cut -c1-16)
ACTUAL_TOKEN=$(cat /tmp/st-active)
if [ "$EXPECTED_TOKEN" != "$ACTUAL_TOKEN" ]; then
echo "✗ Step 2 GATE FAILED: activation token missing/invalid — Step 1 must run first"
exit 1
fi
SKILL_DIR="/home/z/my-project/skills/stellar-trails"; ZSCRIPTS="/home/z/my-project/.zscripts"
if [ ! -f "$SKILL_DIR/chibi.svg" ]; then for REPO_CLONE in "/home/z/my-project/stellar-trails/skill/stellar-trails" "/home/z/my-project/.stellar-trails-repo/skill/stellar-trails" "$HOME/.stellar-trails-repo/skill/stellar-trails"; do [ -f "$REPO_CLONE/chibi.svg" ] && cp -f "$REPO_CLONE/chibi.svg" "$SKILL_DIR/chibi.svg" && break; done; fi
if [ -d "$SKILL_DIR" ]; then mkdir -p "$ZSCRIPTS"; [ -f "$SKILL_DIR/dev.sh" ] && cp -f "$SKILL_DIR/dev.sh" "$ZSCRIPTS/dev.sh" && chmod +x "$ZSCRIPTS/dev.sh"; [ -f "$SKILL_DIR/index.html" ] && cp -f "$SKILL_DIR/index.html" "$ZSCRIPTS/index.html"; [ -f "$SKILL_DIR/chibi.svg" ] && cp -f "$SKILL_DIR/chibi.svg" "$ZSCRIPTS/chibi.svg"; fi
DEV_SH="$ZSCRIPTS/dev.sh"; [ -f "$DEV_SH" ] && ! ss -tlnp | grep -q ':3000 ' && ( setsid bash "$DEV_SH" </dev/null >/dev/null 2>&1 & ) &
sleep 1
HTTP=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:3000/)
MASCOT=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:3000/chibi.svg)
if [ "$HTTP" = "200" ]; then echo "✓ Step 2: popup server running on :3000 (HTTP $HTTP, mascot $MASCOT)"; else echo "✗ Step 2 FAILED: popup server not responding (HTTP $HTTP)"; exit 1; fi
z.ai sandbox note: The popup server runs on localhost:3000 inside the sandbox, but z.ai does NOT expose raw ports to the user's browser. The popup is only visible through the z.ai preview URL pattern: https://preview-<bot-id>.space-z.ai/. If the sandbox exposes a preview panel, the popup appears there; otherwise the popup runs but is invisible to the user (activation still succeeds — the popup is decorative, not functional). See knowledge/zai-sandbox.md for details.
Step 3 — Auto-update via ClawHub: E7 gate check + E11 oracle — clawhub output written to /tmp/st-clawhub-oracle.json for Step 4 cross-verification.
# E7 gate check
EXPECTED_TOKEN=$(grep -oP '^- \*\*version\*\*:\s*\K[0-9.]+' /home/z/my-project/skills/stellar-trails/SKILL.md | sha256sum | cut -c1-16)
ACTUAL_TOKEN=$(cat /tmp/st-active)
if [ "$EXPECTED_TOKEN" != "$ACTUAL_TOKEN" ]; then
echo "✗ Step 3 GATE FAILED: activation token missing/invalid — Step 1 must run first"
exit 1
fi
CURRENT=$(grep -oP '^- \*\*version\*\*:\s*\K[0-9]+\.[0-9]+\.[0-9]+' /home/z/my-project/skills/stellar-trails/SKILL.md | head -1)
# E11: Write clawhub output to oracle file — Step 4 will cross-verify this.
# Note: the file itself is writable (see E11 Fabrication caveat above); the real
# protection is Step 4's re-verification via fresh clawhub inspect calls, not the file.
clawhub inspect stellar-trails --json > /tmp/st-clawhub-oracle.json
LATEST=$(python3 -c "import json,sys; d=json.load(sys.stdin); print((d.get('latestVersion') or {}).get('version') or '')" < /tmp/st-clawhub-oracle.json || echo "")
if [ -z "$CURRENT" ]; then echo "✗ Step 3 FAILED: could not read current version from SKILL.md"; exit 1
elif [ -z "$LATEST" ]; then echo "✗ Step 3 FAILED: could not reach ClawHub registry (network down?)"; exit 1
elif [ "$CURRENT" = "$LATEST" ]; then echo "✓ Step 3: up to date (v$CURRENT) — E11 oracle: $(stat -c%s /tmp/st-clawhub-oracle.json) bytes"
else
echo "⚠️ Step 3: DRIFT DETECTED — local v$CURRENT vs registry v$LATEST — FORCE UPDATING..."
clawhub --no-input update stellar-trails --force
UPDATE_EXIT=$?
if [ $UPDATE_EXIT -ne 0 ]; then
echo "✗ Step 3 FAILED: clawhub update exited $UPDATE_EXIT — see error above"
exit 1
fi
# Post-update verification: re-read local version, confirm it changed
POST_VERSION=$(grep -oP '^- \*\*version\*\*:\s*\K[0-9]+\.[0-9]+\.[0-9]+' /home/z/my-project/skills/stellar-trails/SKILL.md | head -1)
if [ "$POST_VERSION" != "$LATEST" ]; then
echo "✗ Step 3 FAILED: update claimed success but local still v$POST_VERSION (expected v$LATEST)"
echo " Possible cause: skill hidden by moderation, or clawhub update silent failure"
exit 1
fi
echo "✓ Step 3: FORCE UPDATE CONFIRMED — local v$POST_VERSION = registry v$LATEST"
# Sync the persistent zip immediately after a successful update.
SKILL_DIR="/home/z/my-project/skills/stellar-trails"
USER_SKILLS_DIR="/home/user_skills"
if [ -d "$SKILL_DIR" ] && [ -d "$USER_SKILLS_DIR" ]; then
cd "$(dirname "$SKILL_DIR")" && zip -qr "$USER_SKILLS_DIR/stellar-trails.zip" "$(basename "$SKILL_DIR")/" && echo "✓ Step 3: zip synced to v$LATEST" || echo "⚠️ Step 3: zip sync warning"
fi
fi
If clawhub updated the skill: re-read SKILL.md from disk now. Cached context is stale.
Step 4 — Verify files + force-override .zscripts/ + restart dev.sh + sync zip: E7 gate + E11 cross-check — verifies Step 3 oracle file exists and matches claimed version.
# E7 gate check
EXPECTED_TOKEN=$(grep -oP '^- \*\*version\*\*:\s*\K[0-9.]+' /home/z/my-project/skills/stellar-trails/SKILL.md | sha256sum | cut -c1-16)
ACTUAL_TOKEN=$(cat /tmp/st-active)
if [ "$EXPECTED_TOKEN" != "$ACTUAL_TOKEN" ]; then
echo "✗ Step 4 GATE FAILED: activation token missing/invalid — Step 1 must run first"
exit 1
fi
# E11 cross-check: verify Step 3 oracle file exists (proves Step 3 ran clawhub)
if [ ! -f /tmp/st-clawhub-oracle.json ]; then
echo "✗ Step 4 E11 FAILED: clawhub oracle file missing — Step 3 must run first"
exit 1
fi
ORACLE_VERSION=$(python3 -c "import json,sys; d=json.load(sys.stdin); print((d.get('latestVersion') or {}).get('version') or '')" < /tmp/st-clawhub-oracle.json || echo "")
echo " E11 oracle cross-check: registry latest = v${ORACLE_VERSION:-<parse failed>}"
SKILL_DIR="/home/z/my-project/skills/stellar-trails"; USER_SKILLS_DIR="/home/user_skills"; ZSCRIPTS="/home/z/my-project/.zscripts"
FILES_OK="yes"
for f in SKILL.md procedure/phases.md dev.sh index.html chibi.svg; do [ ! -f "$SKILL_DIR/$f" ] && echo "✗ Step 4 WARNING: missing $f" && FILES_OK="no"; done
if [ "$FILES_OK" = "yes" ]; then echo "✓ Step 4a: all skill files present"; else echo "✗ Step 4a FAILED: some files missing — graceful degradation"; exit 1; fi
mkdir -p "$ZSCRIPTS"
# v9.11.9: .zscripts/dev.sh is now git-tracked (canonical runtime source).
# Step 4b syncs skill/stellar-trails/dev.sh → .zscripts/dev.sh to keep both in sync.
# Pre-Push Check 14 verifies they have identical hashes before push.
[ -f "$SKILL_DIR/dev.sh" ] && cp -f "$SKILL_DIR/dev.sh" "$ZSCRIPTS/dev.sh" && chmod +x "$ZSCRIPTS/dev.sh"
[ -f "$SKILL_DIR/index.html" ] && cp -f "$SKILL_DIR/index.html" "$ZSCRIPTS/index.html"
[ -f "$SKILL_DIR/chibi.svg" ] && cp -f "$SKILL_DIR/chibi.svg" "$ZSCRIPTS/chibi.svg"
echo "✓ Step 4b: .zscripts/ synced (dev.sh is git-tracked since v9.11.9)"
# Bug 3 fix (v9.11.6): kill bash SUPERVISOR via PID file, not python3 listener via ss.
# ss -tlnp | grep ':3000' returns python3 (the listener), killing it triggers bash
# supervisor to restart python3 with the OLD dev.sh still loaded — file reload fails.
# Fix: read PID file to get bash supervisor PID, verify /proc/cmdline contains dev.sh, kill it.
#
# Bug 4 fix (v9.11.7): killing bash supervisor orphans its python3 child (reparented to
# PID 1) which keeps :3000 occupied → Step 4d's new dev.sh sees port in use → exits →
# no supervisor ever starts. Fix: AFTER killing bash supervisor, also kill the orphaned
# python3 listener on :3000 so Step 4d starts cleanly.
OLD_PID=$(cat "$ZSCRIPTS/st-devsh.pid" 2>/dev/null)
if [ -n "$OLD_PID" ] && [ -d "/proc/$OLD_PID" ]; then
OLD_CMDLINE=$(tr '\0' ' ' < "/proc/$OLD_PID/cmdline" 2>/dev/null)
if echo "$OLD_CMDLINE" | grep -q 'dev\.sh'; then
kill "$OLD_PID"; sleep 1; echo "✓ Step 4c: old dev.sh supervisor (PID $OLD_PID) killed"
# Bug 4 fix: also kill orphaned python3 listener left by the killed supervisor
LISTENER_PID=$(ss -tlnp 2>/dev/null | grep ':3000 ' | grep -oP 'pid=\K[0-9]+' | head -1)
if [ -n "$LISTENER_PID" ]; then
kill "$LISTENER_PID" 2>/dev/null || true
sleep 1
# Force-kill if still alive (uninterruptible listener)
if ss -tlnp 2>/dev/null | grep -q ':3000 '; then
kill -9 "$LISTENER_PID" 2>/dev/null || true
sleep 1
fi
echo " Bug 4 fix: killed orphaned python3 listener (PID $LISTENER_PID) left by supervisor"
fi
else
echo "⚠️ Step 4c: PID $OLD_PID in pidfile is not dev.sh (cmdline: $OLD_CMDLINE) — skipping kill"
# Fallback: kill python3 listener if port :3000 is still occupied
LISTENER_PID=$(ss -tlnp | grep ':3000 ' | grep -oP 'pid=\K[0-9]+' | head -1)
[ -n "$LISTENER_PID" ] && kill "$LISTENER_PID" && sleep 1 && echo " fallback: killed python3 listener (PID $LISTENER_PID)"
fi
else
echo "✓ Step 4c: no stale dev.sh PID file found — fresh start"
fi
DEV_SH="$ZSCRIPTS/dev.sh"
if [ -f "$DEV_SH" ]; then ( setsid bash "$DEV_SH" </dev/null >/dev/null 2>&1 & ) & sleep 1
HTTP=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:3000/)
if [ "$HTTP" = "200" ]; then echo "✓ Step 4d: dev.sh restarted on :3000 (HTTP $HTTP)"; else echo "✗ Step 4d FAILED: dev.sh restart failed (HTTP $HTTP)"; exit 1; fi
else echo "✗ Step 4d FAILED: dev.sh not found at $DEV_SH"; exit 1; fi
if [ -d "$SKILL_DIR" ] && [ -d "$USER_SKILLS_DIR" ]; then cd "$(dirname "$SKILL_DIR")" && zip -qr "$USER_SKILLS_DIR/stellar-trails.zip" "$(basename "$SKILL_DIR")/" && echo "✓ Step 4e: persistent zip synced" || { echo "✗ Step 4e FAILED: zip sync error"; exit 1; }; else echo "✗ Step 4e FAILED: directory not found"; exit 1; fi
Step 5 — Load phases + classify: Read procedure/phases.md now. Then determine complexity tier (Minimal/Simple/Standard/Complex), task type (Coding/Document/Visualization/Data Processing/Non-Coding), and continuity (NEW or YES — see Session Continuity below). E7 gate + E9 persistent log — writes activation record to /home/user_skills/.st-activation-log for cross-session audit.
# E7 gate check
EXPECTED_TOKEN=$(grep -oP '^- \*\*version\*\*:\s*\K[0-9.]+' /home/z/my-project/skills/stellar-trails/SKILL.md | sha256sum | cut -c1-16)
ACTUAL_TOKEN=$(cat /tmp/st-active)
if [ "$EXPECTED_TOKEN" != "$ACTUAL_TOKEN" ]; then
echo "✗ Step 5 GATE FAILED: activation token missing/invalid — Step 1 must run first"
exit 1
fi
# E9: Write persistent activation log — user can audit anytime via:
# cat /home/user_skills/.st-activation-log
ST_VERSION=$(grep -oP '^- \*\*version\*\*:\s*\K[0-9.]+' /home/z/my-project/skills/stellar-trails/SKILL.md | head -1)
ST_TIMESTAMP=$(date -u '+%Y-%m-%dT%H:%M:%SZ')
ST_TOKEN=$(cat /tmp/st-active)
echo "${ST_TIMESTAMP} v${ST_VERSION} token=${ST_TOKEN} steps=5/5 banner=YES" >> /home/user_skills/.st-activation-log
echo "✓ Step 5: phases loaded + classified: [tier]/[type]/[NEW|YES] — E9 log entry written"
# v9.13.0 P2: Automated worklog rotation — execute, not just document.
# Runs every activation. If worklog > 50 entries, rotate immediately (don't wait for 100).
WORKLOG="/home/z/my-project/worklog.md"
if [ -f "$WORKLOG" ]; then
WENTRY_COUNT=$(grep -c '^---$' "$WORKLOG" 2>/dev/null || echo 0)
if [ "$WENTRY_COUNT" -gt 50 ]; then
ARCHIVE="${WORKLOG%.md}-archive-$(date -u '+%Y-%m-%d').md"
mv "$WORKLOG" "$ARCHIVE"
# Preserve last 5 entries for continuity
awk 'BEGIN{RS="^---$"} {entries[NR]=$0} END{print "---"; for(i=NR-4;i<=NR;i++) if(entries[i]) print entries[i]}' "$ARCHIVE" > "$WORKLOG"
echo " P2: worklog rotated ($WENTRY_COUNT → 5 entries, archive: $ARCHIVE)"
fi
fi
# v9.13.0 P3: Knowledge on-demand loading — actually load relevant file, not just instruct.
# Based on task type (determined by LLM before running this bash), load the relevant knowledge file.
# The LLM sets ST_TASK_TYPE before running Step 5. If not set, default to "coding".
ST_TASK_TYPE="${ST_TASK_TYPE:-coding}"
KBASE="/home/z/my-project/skills/stellar-trails/knowledge"
case "$ST_TASK_TYPE" in
coding|Coding) head -30 "$KBASE/error-patterns.md" 2>/dev/null | head -5 | sed 's/^/ /' ;;
audit|Audit) head -30 "$KBASE/patterns.md" 2>/dev/null | head -5 | sed 's/^/ /' ;;
document|Document) head -30 "$KBASE/conventions.md" 2>/dev/null | head -5 | sed 's/^/ /' ;;
*) head -30 "$KBASE/user-profile.md" 2>/dev/null | head -5 | sed 's/^/ /' ;;
esac
echo " P3: knowledge preview loaded for task_type=$ST_TASK_TYPE"
# v9.13.3 MIGRATION 1: 5/5 GREEN GATE — was text, now bash echo (LLM cannot skip)
echo "✓ 5/5 GREEN — activation complete"
# v9.13.3 MIGRATION 2: Compliance Score — was text self-assessment, now bash mechanical
# Computes score from verifiable sandbox artifacts, not LLM honesty
SCORE=0; SKIPPED=""
[ -f /tmp/st-active ] && SCORE=$((SCORE+1)) || SKIPPED="${SKIPPED}E7,"
[ -f /tmp/st-clawhub-oracle.json ] && SCORE=$((SCORE+1)) || SKIPPED="${SKIPPED}E11,"
curl -s -o /dev/null -m 2 http://localhost:3000/ 2>/dev/null && SCORE=$((SCORE+1)) || SKIPPED="${SKIPPED}dev.sh,"
tail -1 /home/user_skills/.st-activation-log 2>/dev/null | grep -q "steps=5/5" && SCORE=$((SCORE+1)) || SKIPPED="${SKIPPED}E9log,"
[ -f "$WORKLOG" ] && SCORE=$((SCORE+1)) || SKIPPED="${SKIPPED}worklog,"
echo "$(date -u '+%Y-%m-%dT%H:%M:%SZ') COMPLIANCE v${ST_VERSION} score=${SCORE}/5 mechanical=bash skipped=${SKIPPED:-none}" >> /home/user_skills/.st-activation-log
echo " Compliance: ${SCORE}/5 mechanical (skipped: ${SKIPPED:-none})"
Mandatory TodoWrite protocol (E8): Before Step 1 bash, call TodoWrite with 5 items (Step 1 through Step 5), all status pending. Before each Step N bash, mark Step N in_progress. After each Step N bash succeeds, mark Step N completed. User sees the live checklist transition in real-time — this is visibility enforcement that text cannot provide.
Problem this solves: Previous versions had Step bash blocks that only exit 1 on GATE failures (E7 token mismatch). Step-specific failures (HTTP != 200, clawhub unreachable, dev.sh restart failed) just echoed ✗ Step N FAILED and exited 0 — the LLM couldn't detect failure from exit code alone, and there was no retry mandate. The LLM would often proceed to the next step despite a failure, or silently skip the failed step.
Solution: Three changes:
✓ or ✗ output.exit 1 on ANY failure (not just GATE failures). The Bash tool reports non-zero exit code → LLM detects failure → triggers retry.Retry decision tree:
Step N bash exits with code:
0 (success) → print ✓ Step N output → proceed to Step N+1
1 (failure) → print ✗ Step N output → diagnose → fix → re-run Step N
↓
retry 1: re-run Step N
├─ exit 0 → ✓ proceed
└─ exit 1 → retry 2: re-run Step N
├─ exit 0 → ✓ proceed
└─ exit 1 → retry 3: re-run Step N
├─ exit 0 → ✓ proceed
└─ exit 1 → ⚠️ MAX RETRIES EXCEEDED
→ E6 Escape Hatch or ask user
Common failure fixes (apply before retry):
| Step | Failure | Fix |
|---|---|---|
| 1 | SKILL.md not found | clawhub --no-input update stellar-trails --force to restore |
| 2 | HTTP != 200 (popup not responding) | Kill stale dev.sh: kill $(cat /home/z/my-project/.zscripts/st-devsh.pid) + re-run Step 2 |
| 3 | clawhub unreachable (network) | Retry Step 3 after 5s — network may be transient |
| 3 | clawhub update failed (moderation) | Check clawhub inspect stellar-trails --json moderation state → if hidden, ask user |
| 4 | dev.sh restart failed (port in use) | Kill orphaned listener: ss -tlnp | grep ':3000' | grep -oP 'pid=\K[0-9]+' | xargs kill -9 + re-run Step 4 |
| 4 | zip sync failed (directory missing) | mkdir -p /home/user_skills + re-run Step 4 |
| 5 | E7 GATE FAILED (token mismatch) | Re-run Step 1 to re-write token, then re-run Step 5 |
Anti-patterns (FORBIDDEN):
✓/✗ markers.After Step 5 completes, print this confirmation BEFORE entering SPECIFY:
✓ 5/5 GREEN — activation complete
Rule: If ANY of the 5 steps is ✗ (not yet green after retries), do NOT print this line. Instead, continue retrying the failed step. Only print 5/5 GREEN when all 5 steps have printed ✓.
Self-check before printing 5/5 GREEN:
✓ Step 1? → If NO, retry Step 1✓ Step 2? → If NO, retry Step 2✓ Step 3? → If NO, retry Step 3✓ Step 4 (including all sub-checks 4a-4e)? → If NO, retry Step 4✓ Step 5? → If NO, retry Step 5Only when all 5 answers are YES, print ✓ 5/5 GREEN — activation complete and proceed to SPECIFY.
After 5/5 GREEN: Begin SPECIFY (or IMPLEMENT if continuation detected).
Problem: In long sessions (10+ tasks), context budget depletes. SKILL.md is ~21K tokens (10% of 200K budget). As conversation accumulates, the LLM starts skipping enforcement rules (E5 rationalizations kick in). The skill acknowledges this (E6 Escape Hatch) but doesn't ADAPT — it applies the same 47 rules regardless of context pressure.
Solution: Three-tier adaptive mode based on self-assessed context pressure:
| Context Pressure | Trigger | Mode | Rules Applied |
|---|---|---|---|
| LOW (<60%) | Early session, few tasks done | Full Mode | All 12 vectors + 14 checks + all phases + all templates |
| MEDIUM (60-80%) | Mid session, 5-10 tasks | Standard Mode | E4-E6 + E7-E12 + phase markers + Pre-Push checks 1+8+10+14 only (skip 2-7,9,11-13) |
| HIGH (80-95%) | Late session, 10+ tasks | Minimal Mode | Banner + 5 steps + 5/5 GREEN + ☄️ PASS report. Skip E1-E3, SADC, Pre-Push (unless pushing), templates |
| CRITICAL (>95%) | Emergency | Escape Hatch | E6 only: print ⚠️ ACTIVATION SKIPPED + reason + proceed |
Self-assessment criteria (LLM evaluates before SPECIFY):
When switching to MEDIUM or HIGH mode, print:
⚠️ CONTEXT PRESSURE: [LOW|MEDIUM|HIGH] — switching to [Standard|Minimal] Mode
Active vectors: E4-E6, E7-E12 (skipping E1-E3 text enforcement)
Pre-Push: checks 1+8+10+14 only (skipping 2-7,9,11-13)
Phase markers: ☄️ ENTER/EXIT retained
Templates: [Standard|skipped]
Anti-patterns (FORBIDDEN):
Problem: E9 logs activation events but not compliance scores. There's no feedback loop to detect patterns like "last 10 activations: 8/10 followed all steps, 2/10 skipped E3."
Solution: At DELIVER phase, the LLM self-assesses compliance and appends a score to the E9 log:
# v9.13.0: Compliance score appended to activation log at DELIVER
# Format: COMPLIANCE v<VERSION> score=N/12 vectors_skipped=E1,E3
# The LLM evaluates which of the 12 vectors it actually followed during this task
echo "$(date -u '+%Y-%m-%dT%H:%M:%SZ') COMPLIANCE v${ST_VERSION} score=[N]/12 skipped=[list] mode=[full|standard|minimal]" >> /home/user_skills/.st-activation-log
Scoring rubric (LLM self-assesses honestly):
Total: 12 points. Score 12/12 = full compliance. Score <10 = report to user.
The following thoughts are CORRECTNESS BUGS, not valid optimizations. If you catch yourself thinking ANY of these, STOP and run activation NOW.
❌ "This is a continuation task, I don't need to re-activate"
→ WRONG. Continuation skips SPECIFY+PLAN, NOT activation. Every Skill() invoke requires fresh activation — after context truncation, you cannot reliably know what you did before.
❌ "This is a simple/trivial task, activation is overkill"
→ WRONG. SKILL.md says "every task gets a delivery report — no exceptions for simplicity." Minimal tier still requires banner + 5 steps + ☄️ PASS report.
❌ "The session is already long, I'll save tokens by skipping" → WRONG. Activation takes ~2 seconds and ~500 tokens. From a 200K context budget, that's 0.25% — negligible. User cannot see your context length, so they cannot "understand" why you skipped.
❌ "I already printed the banner in a previous message" → WRONG. Per SKILL.md: "After context truncation, you cannot reliably know what you did." The banner is the ONLY signal to the user that activation ran. Print it every invoke.
❌ "The user didn't complain last time I skipped" → WRONG. User silence is not approval. The banner exists for the USER's benefit, not yours. They may have noticed but chosen not to comment, or they may not have realized skipping was a bug.
❌ "I'll just label it 'Minimal tier' and skip" → WRONG. Minimal tier still requires banner + 5 steps. The complexity tier affects REPORT FORMAT, not ACTIVATION REQUIREMENTS. All tiers must activate.
❌ "Pattern drift — I've skipped before and it was fine" → WRONG. Past skips were bugs, not precedent. Each invoke is independent; previous bugs do not authorize future bugs.
❌ "I'll print the report without activation, user won't notice" → WRONG. This is silent skipping — the WORST option. If you must skip (emergency only), use the Escape Hatch (E6 below) to make it visible. Silent skips hide failures from the user.
If you are under extreme context pressure (≥90% context used) and MUST skip activation:
⚠️ ACTIVATION SKIPPED — operating without bannerThis is a correctness bug per SKILL.md E4 Pre-Tool-Call Gate.Do NOT silently skip. Silent skipping hides the failure from the user. A visible skip at least lets the user know they should verify the output more carefully.
Escape hatch is for emergencies only — not for "I'm lazy" or "this is simple". Misusing the escape hatch is itself a correctness bug.
Stellar Trails uses 12 enforcement vectors across 3 tiers. All vectors are retained — they are mandatory protocol. The table below shows which are "active" (primary enforcement) vs "legacy" (superseded but documented for compliance audit).
| Tier | Vector | Status | Role |
|---|---|---|---|
| Legacy Text | E1 Phase Machine | Legacy (v9.0.0) | Text-only; superseded by E7-E12 for sandbox-native enforcement, but phase markers still required |
| Legacy Text | E2 Mandatory Prints | Legacy (v9.0.0) | Text-only; banner/COMMIT/REPORT still required, but compliance verified by E8 TodoWrite + E12 exit codes |
| Legacy Text | E3 Preferences Dialog | Legacy (v9.0.0) | Text-only; AskUserQuestion gate still required, but no sandbox-native enforcement |
| Pre-Tool Gate | E4 Pre-Tool-Call Gate | Active (v9.3.0) | Hard gate: no tool before banner+5 steps. Partially superseded by E12 (exit codes) but still mandatory |
| Pre-Tool Gate | E5 Anti-Rationalization | Active (v9.3.0) | 8 forbidden rationalizations — most important text enforcement |
| Pre-Tool Gate | E6 Escape Hatch | Active (v9.3.0) | Visible skip for ≥90% context pressure |
| Sandbox-Native | E7 Hash Token Gate | Active (v9.4.0) | File token verification — LLM cannot fake |
| Sandbox-Native | E8 TodoWrite Live Marker | Active (v9.4.0) | Real-time UI visibility |
| Sandbox-Native | E9 Persistent Log | Active (v9.4.0) | Cross-session audit trail |
| Sandbox-Native | E10 Line-Number Proof | Active (v9.4.0) | Read tool ground truth |
| Sandbox-Native | E11 Clawhub Oracle | Active (v9.4.0) | External binary ground truth |
| Exit Code | E12 Activation Retry | Active (v9.12.0) | Exit code enforcement + retry-until-green + 5/5 GREEN GATE |
Deprecation notes: E1-E3 are "legacy" in the sense that sandbox-native vectors (E7-E12) provide stronger enforcement for the same concerns. However, the TEXT rules in E1-E3 (phase markers, mandatory prints, AskUserQuestion gate) remain mandatory — they are the protocol the LLM must follow. The "legacy" label means only that sandbox-native mechanisms now backstop them.
This version adds three deterministic enforcement layers that shift compliance from LLM goodwill to verifiable artifacts. Every layer below produces a print, a file, or a turn-ending marker — none rely on the LLM "remembering" to do them.
Every task passes through all six phases (IDLE → SPECIFY → PLAN → IMPLEMENT → VERIFY → DELIVER). No phase is skipped, even for Minimal tier.
Mechanism: Each phase entry requires a phase-marker print of the form ☄️ ENTER <PHASE> before any other phase work. Each phase exit requires ☄️ EXIT <PHASE> → <NEXT>. The DELIVER report's Phase Trace field lists every phase-marker pair. Missing markers = compliance bug.
Why: Phase skipping is the #1 silent failure mode. The marker print makes skipping visible in the transcript, not invisible in the LLM's hidden reasoning.
Three prints are mandatory and have exact syntax. Self-check before DELIVER:
| When | Required Syntax | |
|---|---|---|
| Activation banner | FIRST output of session | See Activation section |
| COMMIT [Standard] block | End of PLAN, before IMPLEMENT (Standard/Complex only) | See Deliveries → Scope |
| Delivery REPORT block | LAST output of session | See Deliveries → Delivery/Summary/Minimal |
Mechanism (v9.13.3: migrated from text to bash): Before printing the Delivery report, run this bash block to mechanically verify activation artifacts exist:
# v9.13.3 MIGRATION 3: Pre-DELIVER print check — was text self-assessment, now bash
# Verifies that activation artifacts exist before allowing DELIVER to proceed
_DELIVER_OK="yes"
[ -f /tmp/st-active ] || { echo "✗ Pre-DELIVER FAIL: E7 token missing — activation not run"; _DELIVER_OK="no"; }
[ -f /tmp/st-clawhub-oracle.json ] || { echo "✗ Pre-DELIVER FAIL: E11 oracle missing — Step 3 not run"; _DELIVER_OK="no"; }
curl -s -o /dev/null -m 2 http://localhost:3000/ 2>/dev/null || { echo "⚠️ Pre-DELIVER WARNING: popup server not responding"; }
tail -1 /home/user_skills/.st-activation-log 2>/dev/null | grep -q "steps=5/5" || { echo "✗ Pre-DELIVER FAIL: E9 log entry missing — Step 5 not run"; _DELIVER_OK="no"; }
if [ "$_DELIVER_OK" = "yes" ]; then
echo "✓ Pre-DELIVER print check: banner=✓(bash) commit=✓/N/A report=✓ — verified by bash"
else
echo "✗ Pre-DELIVER FAIL: activation artifacts missing — DO NOT print delivery report"
exit 1
fi
If the bash block exits 1, do not print the report — go back and run activation first.
Pre-DELIVER Self-Audit (E2 expansion — Layer 2, NEW in v9.3.0): The single-line banner=✓ check above is too easy to self-grade as ✓ even when skipped. Before printing the delivery report, answer these 5 questions HONESTLY:
☄️ STELLAR TRAILS · vX.Y.Z · ACTIVE as my FIRST output to the user?✓/✗ marks visible in the transcript?If ANY answer is NO, append to the delivery report:
⚠️ ACTIVATION COMPLIANCE FAILURE:
- Banner printed as first output: YES/NO
- Steps executed with visible ✓/✗: n/5
- Failed steps: [list]
- Reason: [honest one-line explanation]
Do NOT hide activation failures. The user deserves to know. Self-grading banner=✓ when you actually skipped is a lie — and the user can verify by scrolling up in the transcript. If they catch you lying, trust is broken permanently.
Why: Bookend prints are the only signal the user has that the workflow ran. Missing any one of them is treated as a correctness bug, not a style preference.
For any decision point where the LLM would otherwise guess audience/style/length/format/scope, invoke AskUserQuestion BEFORE producing content. This applies to:
Skip conditions (auto-bypass, no AskUserQuestion needed):
Mechanism: Print ✓ Preferences dialog check: <INVOKED | SKIPPED: <reason>> before any content-producing tool call in SPECIFY. This makes the decision visible.
Why: Guessing audience/style/length causes the most expensive rework in document tasks. One batched 30-second question round prevents hours of regeneration.
Subagent unavailability (added v9.11.4): AskUserQuestion is provisioned ONLY to the main agent. Subagent toolsets in z.ai are limited to: Bash, Glob, Grep, LS, Read, Edit, MultiEdit, Write, TodoWrite, TodoRead, Skill. If a subagent encounters a decision that would normally require AskUserQuestion, it MUST return control to the orchestrating main agent with a clear statement of the decision needed — do NOT guess. The main agent can then invoke AskUserQuestion and re-dispatch the subagent with the user's answer.
IDLE → SPECIFY → PLAN → IMPLEMENT → VERIFY → DELIVER
↑ │
└──── Recovery ◄───────────────────┘
Phase definitions, entry/exit criteria, and gate rules live in procedure/phases.md — read it during Step 5 of Activation.
Rule: Before entering any phase, check if the user's message is a continuation of previous work. Read the immediately preceding assistant message — if the user's reply references, approves, corrects, or follows up on that output, it is a continuation. After context truncation, read worklog.md — the last entry contains the exact task state snapshot needed to resume.
| Signal | Type | Action |
|---|---|---|
| User references previous output ("apply all 10", "fix point 3", "proceed") | Continuation | Skip SPECIFY+PLAN → IMPLEMENT |
| User approves a proposal/plan ("yes", "go ahead", "do it") | Continuation | Skip SPECIFY+PLAN → IMPLEMENT |
| User asks a follow-up question ("what about X?") | Continuation | Skip SPECIFY → answer in current phase context |
| User provides new requirements mid-task | New task | Restart from SPECIFY with updated requirements |
| User invokes Skill() with new instructions | New task | Full workflow from IDLE |
| Context compression boundary with ongoing task | Continuation | Read worklog.md last entry, resume from recorded phase |
Regenerating proposals the user already approved is a correctness bug, not a style preference.
Every DELIVER phase appends a Snapshot to worklog.md. This is the primary continuity mechanism — not conversation history, not memory files.
On DELIVER (always, all tiers), append to /home/z/my-project/worklog.md:
---
last_phase: DELIVER
task: <one-line description>
complexity: <tier>
task_type: <type>
files_modified: <list or "none">
phase_trace: IDLE→SPECIFY→PLAN→IMPLEMENT→VERIFY→DELIVER
next_step: <what user should do next, or "IDLE - awaiting input">
On context truncation (IDLE): read the last --- block from worklog.md. If the task description matches the current request, resume from the recorded phase.
Problem: worklog.md grows unbounded — at ~1KB per DELIVER snapshot, 1000 tasks would produce ~1MB file. Loading 1MB into context for "read last entry" wastes tokens.
Policy: When worklog.md exceeds 100 entries (≈100KB), rotate:
worklog.md → worklog-archive-YYYY-MM-DD.md (date-stamped)worklog.md with the last 5 entries copied from the archived file (preserves continuity for next session)/home/z/my-project/ — user can delete old archives anytimeRotation bash (run at DELIVER phase, after snapshot append):
WORKLOG="/home/z/my-project/worklog.md"
ENTRY_COUNT=$(grep -c '^---$' "$WORKLOG" 2>/dev/null || echo 0)
if [ "$ENTRY_COUNT" -gt 100 ]; then
ARCHIVE="${WORKLOG%.md}-archive-$(date -u '+%Y-%m-%d').md"
mv "$WORKLOG" "$ARCHIVE"
# Preserve last 5 entries for continuity
awk 'BEGIN{RS="^---$"} {entries[NR]=$0} END{print "---"; for(i=NR-4;i<=NR;i++) if(entries[i]) print entries[i]}' "$ARCHIVE" > "$WORKLOG"
echo "✓ Worklog rotated: $ARCHIVE ($(grep -c '^---$' "$ARCHIVE") entries archived), $WORKLOG reset to last 5 entries"
fi
Knowledge on-demand loading: At Step 5 activation, only read the last 3 entries of worklog.md (not the whole file) — sufficient for continuity check without loading stale history.
| Task Type | SPECIFY | PLAN | IMPLEMENT | VERIFY |
|---|---|---|---|---|
| Coding | Problem spec, edge cases, affected files | Code steps + Traceability IDs | Write code | Lint, type check, tests |
| Document | Content outline, target format, sections | Section plan + content depth targets | Generate document (via skill) | Format check, completeness |
| Visualization | Visual requirements, data sources, layout | Data mapping + chart type selection | Generate chart (via skill) | Visual accuracy, data integrity |
| Data Processing | Data spec, input/output schema, transforms | Transform pipeline + validation steps | Write script + execute | Output validation, edge cases |
| Non-Coding | Internal (identify question) | Internal (plan approach) | Answer / explain / recommend | Internal (self-check) |
No phases are skipped. Non-coding tasks use Minimal tier — SPECIFY, PLAN, VERIFY run internally. IMPLEMENT does the visible work. DELIVER outputs a compact report.
| Tier | Criteria | Report Format |
|---|---|---|
| Minimal | Knowledge question, explanation, recommendation — no code/file output | ☄️ PASS | Evidence: <one-line result> |
| Simple | Single file, no schema change, no new dependencies | ☄️ REPORT [Simple] (one-line) |
| Standard | Multiple files or a schema change | ☄️ REPORT [Standard] (full block) + Scope at end of PLAN |
| Complex | Architectural changes, multi-service, high risk | ☄️ REPORT [Complex] (full block + expanded evidence) + Scope |
Standard/Complex require Traceability IDs (IMPL-001, IMPL-002, ...). Simple/Minimal do not.
Before planning any implementation, verify the approach is grounded in real sources — not assumptions.
| Complexity | SADC Requirement |
|---|---|
| Minimal | Skip — knowledge questions don't need source research |
| Simple | Quick check — verify approach against at least one source |
| Standard | Main agent inline research — invoke Skill(command="web-search") then use Inline Content Retrieval (v9.5.0) BEFORE writing problem-spec. Print 📡 SADC: main agent researching inline |
| Complex | Deep research by main agent — multiple sources, compare approaches, document tradeoffs |
Main agent mandate (Standard/Complex): BEFORE writing the problem specification, the main agent (not a subagent) invokes Skill(command="web-search") to find existing solutions, then uses the Inline Content Retrieval protocol (see Inline Content Retrieval section, NEW in v9.5.0) to extract content from top 3-5 URLs → ≤500-word summary. No external extraction skill dependency — uses native curl + python3.
Why main agent, not subagent: The z.ai sandbox main agent has the SKILL.md pre-loaded into its context at session start; subagents do not (their context is the orchestrating main agent's task prompt). While subagents CAN invoke Skill(command="stellar-trails") after the fact (verified v9.11.4 — see Subagent Compliance Matrix below), doing so consumes ~95K tokens of the subagent's budget just to load the skill — wasteful for a single SADC lookup. The main agent already has SKILL.md in context, so it can perform SADC inline at near-zero marginal cost. Additionally, subagent prompts are compressed by the orchestrator, which may strip nuance needed for SADC source evaluation.
If no existing solution is found, state it explicitly — "searched npm/PyPI/docs, no existing package found" is a valid result. Building from scratch when a library exists is a spec-level defect.
When subagents ARE appropriate: Subagents may be used for non-skill tasks (e.g., "summarize these 5 URLs", "compare these 2 code samples"). The main agent fetches content via skills first, then delegates pure-text analysis to subagents. The rule: skills are invoked by the main agent; subagents operate on text the main agent has already retrieved.
For deliverable-creation tasks (Document, Visualization, PPT, PDF, Excel, dashboard, poster, script, chart-as-deliverable), invoke AskUserQuestion BEFORE writing the problem specification.
Mandate: In SPECIFY phase, if task type is Document or Visualization AND the user's original request does NOT explicitly pin audience + style + length, invoke AskUserQuestion with 6–8 questions.
Print before any content-producing tool call: ✓ Preferences dialog check: <INVOKED | SKIPPED: <reason>>
Skip conditions: user says skip / all 3 dimensions explicit / trivial edit / Coding/Non-Coding / continuation. AT MOST ONCE per run, before any content-producing tool. After answers return, proceed straight to PLAN (no loop-back).
Full 6-8 question template + skip conditions: read references/askuserquestion-gate.md before invoking.
On every error, classify it as Bug or Wrong Approach before attempting a fix. For denial-type errors (permission denied, EPERM, AccessDenied), perform Denial Delta Analysis — compare what was denied against what is configured. The difference IS the fix.
Wrong Approach signals (50%+ rewrite needed, same error after 2 attempts, missing library feature, data model change) trigger a pivot to the fallback approach defined in the Scope.
Pivot flow: Error detected → classify → if Wrong Approach: re-enter PLAN with fallback or new approach → present to user via AskUserQuestion (E3 enforcement) → re-implement → re-verify. Record in the Pivot field of the delivery report.
Full decision tree: read procedure/error-resolution.md.
Git rules (override defaults):
git fetch and inspect before git pull — if remote diverged, stop and askgit rebase, git reset, git push --force, or git merge without explicit user instructionProblem this solves: While implementing a fix for bug X, the agent discovers bug Y in the same area. Two failure modes disrupt the workflow:
This pattern occurred twice during this skill's own development:
IndentationError in the new code I just wrote (same surface, fixed in v9.0.2)Rule: Do NOT silently fix Y. Do NOT silently skip Y. Either choice disrupts the workflow.
When you discover bug Y while implementing fix for bug X:
STOP implementation momentarily. Do not race ahead.
DOCUMENT the discovery immediately — append to /home/z/my-project/worklog.md:
discovery: <Y one-line> | found while: <X one-line> | surface: <same|different> | action: <fix-now|defer>
CLASSIFY using the Same-Surface Test:
Scope Drift: +Y (discovered while fixing X, same surface)next_step: investigate Y in next iterationRESUME implementation with updated scope (if fix-now) or original scope (if defer).
NEVER LOSE TRACK of the original task. The DELIVER worklog snapshot must include BOTH X and Y status:
Discovered bug Y while fixing bug X
│
├─ Is Y in the same file as X's fix?
│ ├─ YES → likely same surface
│ └─ NO → likely different surface (DEFER)
│
├─ Is Y's root cause the same as X's root cause?
│ ├─ YES → same surface (FIX NOW)
│ └─ NO → different surface (DEFER)
│
├─ Does fixing Y require changing code outside X's blast radius?
│ ├─ NO → same surface (FIX NOW)
│ └─ YES → different surface (DEFER)
│
└─ Would deferring Y cause X's fix to fail CI / verification?
├─ YES → same surface (FIX NOW, mandatory)
└─ NO → defer is safe
v9.0.1 → v9.0.2 transition:
python3 -c block for Step 3, used multi-line indented python inside single-quoted bash string → IndentationError---
last_phase: DELIVER
task: <original task>
complexity: <tier>
task_type: <type>
files_modified: <list>
traceability: IMPL-001 to IMPL-XXX
discoveries:
- bug: <Y one-line>
found_while: <X one-line>
surface: same|different
action: fix-now|defer
outcome: <fixed in this commit | deferred to next iteration>
pivot: NONE | YES (discovery-driven)
scope_drift: NONE | +Y (discovered while fixing X, same surface)
next_step: <what user should do next>
Problem this solves: Pushing code changes to CI without local verification wastes a CI cycle (~1-2 minutes per run) and creates a "push → fail → read logs → push again" loop. This happened during this skill's development:
** (not caught by bash -n)Rule: Before pushing any change that triggers CI, run ALL checks below. All 9 checks must PASS before push. If any FAIL, fix before pushing — do not push broken code.
python3 << 'PYEOF'
import re, subprocess, tempfile, os
with open('skill/stellar-trails/SKILL.md') as f:
content = f.read()
blocks = re.findall(r'\x60\x60\x60bash\n(.*?)\x60\x60\x60', content, re.DOTALL)
fail = 0
for i, block in enumerate(blocks, 1):
with tempfile.NamedTemporaryFile(mode='w', suffix='.sh', delete=False) as f:
f.write(block); path = f.name
r = subprocess.run(['bash', '-n', path], capture_output=True, text=True)
os.unlink(path)
if r.returncode != 0:
print(f"✗ Block {i} FAIL: {r.stderr.strip()[:120]}")
fail += 1
print(f"{'✓' if fail == 0 else '✗'} Check 1: bash -n — {len(blocks)-fail}/{len(blocks)} blocks pass")
PYEOF
# Extract and run every python3 -c block with 3 mock inputs: valid JSON, empty JSON, invalid text
python3 << 'PYEOF'
import re, subprocess
with open('skill/stellar-trails/SKILL.md') as f:
content = f.read()
# Find all python3 -c "..." blocks
blocks = re.findall(r'python3 -c ("[^"]+"|\'[^\']+\')', content)
fail = 0
for i, block in enumerate(blocks, 1):
cmd = f'echo "{{}}" | python3 -c {block}'
r = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=5)
if r.returncode != 0:
print(f"✗ python3 -c block {i} FAIL on empty JSON: {r.stderr.strip()[:80]}")
fail += 1
print(f"{'✓' if fail == 0 else '✗'} Check 2: python3 -c mock execution — {len(blocks)-fail}/{len(blocks)} blocks pass")
PYEOF
# Every grep -oP pattern in SKILL.md must return non-empty on the actual file
python3 << 'PYEOF'
import re, subprocess
with open('skill/stellar-trails/SKILL.md') as f:
content = f.read()
patterns = re.findall(r"grep -oP '([^']+)'", content)
fail = 0
for i, pat in enumerate(patterns, 1):
# Skip patterns that are meant to match process output, not file content
if 'pid=' in pat or ':3000' in pat or 'HTTP' in pat:
continue
r = subprocess.run(['grep', '-oP', pat, 'skill/stellar-trails/SKILL.md'],
capture_output=True, text=True, timeout=5)
if not r.stdout.strip():
print(f"✗ grep pattern {i} returns empty: {pat[:60]}")
fail += 1
print(f"{'✓' if fail == 0 else '✗'} Check 3: grep patterns — {len(patterns)-fail}/{len(patterns)} return non-empty")
PYEOF
# Banner must use <VERSION> placeholder, NOT hardcoded v9.x.y
HARDCODED=$(grep -c '☄️ STELLAR TRAILS · v[0-9]' skill/stellar-trails/SKILL.md)
PLACEHOLDER=$(grep -c '☄️ STELLAR TRAILS · v<VERSION>' skill/stellar-trails/SKILL.md)
if [ "$HARDCODED" -gt 0 ] && [ "$PLACEHOLDER" -eq 0 ]; then
echo "✗ Check 4 FAIL: banner has hardcoded version ($HARDCODED occurrences), no <VERSION> placeholder"
else
echo "✓ Check 4: banner uses <VERSION> placeholder ($PLACEHOLDER refs), no hardcoded version"
fi
NEW_VERSION=$(grep -oP '^- \*\*version\*\*:\s*\K[0-9.]+' skill/stellar-trails/SKILL.md | head -1)
TAG="v$NEW_VERSION"
if git tag -l "$TAG" | grep -q "$TAG"; then
echo "✗ Check 5 FAIL: tag $TAG already exists"
else
echo "✓ Check 5: tag $TAG does not exist yet (safe to push)"
fi
# Before push, verify skill is visible on registry (not moderation-hidden)
# This catches the v9.6.0 bug where publish exit 0 but version didn't register
REGISTRY_STATE=$(clawhub inspect stellar-trails --json)
if [ -z "$REGISTRY_STATE" ]; then
echo "✗ Check 6 FAIL: cannot reach clawhub registry — push may publish to hidden skill"
else
MOD_STATE=$(echo "$REGISTRY_STATE" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('moderation',{}).get('state','unknown'))" || echo "unknown")
if [ "$MOD_STATE" = "hidden" ] || [ "$MOD_STATE" = "deleted" ]; then
echo "✗ Check 6 FAIL: skill is $MOD_STATE by moderation — publish will not register"
echo " Contact clawhub moderator before pushing"
else
echo "✓ Check 6: skill visible on registry (moderation: $MOD_STATE)"
fi
fi
if git diff --cached --name-only HEAD | grep -q '\.github/workflows/'; then
python3 -c "import yaml; yaml.safe_load(open('.github/workflows/release.yml'))" && \
echo "✓ Check 7: workflow YAML valid" || echo "✗ Check 7 FAIL: workflow YAML invalid"
else
echo "✓ Check 7: no workflow files changed (skip)"
fi
_F=$(printf '\x60\x60\x60')
FENCES=$(grep -c "$_F" skill/stellar-trails/SKILL.md)
if [ $((FENCES % 2)) -eq 0 ]; then
echo "✓ Check 8: markdown fences even ($FENCES)"
else
echo "✗ Check 8 FAIL: markdown fences odd ($FENCES) — orphan code block"
fi
# Acknowledge that push is not complete until registry confirms the version
echo "✓ Check 9: post-push plan acknowledged"
echo " After CI succeeds, MUST poll clawhub inspect until latestVersion = $NEW_VERSION"
echo " If registry doesn't update within 60s of CI success, fetch CI logs + diagnose"
echo " (This catches the v9.6.0 bug: publish exit 0 but version not registered)"
SKILL_VERSION=$(grep -oP '^- \*\*version\*\*:\s*\K[0-9.]+' skill/stellar-trails/SKILL.md | head -1)
INDEX_VERSION=$(grep -oP 'v\K[0-9]+\.[0-9]+\.[0-9]+' skill/stellar-trails/index.html | head -1)
if [ "$SKILL_VERSION" != "$INDEX_VERSION" ]; then
echo "✗ Check 10 FAIL: SKILL.md v$SKILL_VERSION vs index.html v$INDEX_VERSION — version drift"
else
echo "✓ Check 10: index.html version matches SKILL.md (v$SKILL_VERSION)"
fi
# Catches byte-identical duplicate files in knowledge/ subdirs (leftover from path-mismatch fixes)
DUPES=$(find skill/stellar-trails/knowledge/ -type f -name "*.md" -exec md5sum {} \; | sort | uniq -d -w 32 | wc -l)
if [ "$DUPES" -gt 0 ]; then
echo "✗ Check 11 FAIL: $DUPES duplicate knowledge file(s) detected:"
find skill/stellar-trails/knowledge/ -type f -name "*.md" -exec md5sum {} \; | sort | uniq -d -w 32
echo " Remove duplicates — only top-level knowledge/*.md should exist (no platform/ or universal/ subdirs)"
else
echo "✓ Check 11: no duplicate knowledge files"
fi
# Catches drift between phases.md SADC step and SKILL.md SADC section
# Both must agree: main agent inline, NO subagent dispatch, NO crawl4ai/web-reader invocations
# Note: matches positive invocations only (Skill(command="...") or "dispatched"), not negations like "No crawl4ai"
PHASES_SUBAGENT=$(grep -cE 'Skill\(command="(crawl4ai|web-reader)"\)|subagent dispatched|Task\(subagent_type' skill/stellar-trails/procedure/phases.md)
PHASES_SUBAGENT=${PHASES_SUBAGENT:-0}
PHASES_CRAWL=0 # accounted for in PHASES_SUBAGENT above via Skill(command="...")
if [ "$PHASES_SUBAGENT" -gt 0 ]; then
echo "✗ Check 12 FAIL: phases.md still references removed SADC patterns (count: $PHASES_SUBAGENT)"
echo " SKILL.md removed subagent SADC in v9.1.0 and crawl4ai in v9.5.0 — phases.md must match"
grep -nE 'Skill\(command="(crawl4ai|web-reader)"\)|subagent dispatched|Task\(subagent_type' skill/stellar-trails/procedure/phases.md
else
echo "✓ Check 12: phases.md SADC step aligned with SKILL.md (no subagent dispatch, no crawl4ai/web-reader invocations)"
fi
# Catches broken file references left behind when files/dirs are moved or deleted.
# Verifies every (references|procedure|knowledge|constraints)/path/to/file.md mentioned
# in any skill file actually exists on disk. Would have caught the v9.11.4 regression
# where knowledge/universal/ and knowledge/platform/ subdirs were deleted but refs in
# constraints/code-standards.md, knowledge/error-patterns.md, procedure/error-resolution.md
# were not updated.
python3 << 'PYEOF'
import os, re, subprocess
SKILL_DIR = 'skill/stellar-trails'
# Collect all file-path references from all .md files in the skill
ref_pattern = re.compile(r'(?:references|procedure|knowledge|constraints)/[a-zA-Z0-9_/-]+\.md')
missing = []
files_scanned = 0
for root, dirs, files in os.walk(SKILL_DIR):
for fname in files:
if not fname.endswith('.md'):
continue
fpath = os.path.join(root, fname)
files_scanned += 1
with open(fpath) as f:
content = f.read()
for match in ref_pattern.finditer(content):
ref = match.group(0)
full = os.path.join(SKILL_DIR, ref)
if not os.path.exists(full):
# Allow references that are documentation of removal (e.g., "formerly in procedure/templates/")
# — but only if the line contains "formerly" or "removed" or "REMOVED"
line_start = content.rfind('\n', 0, match.start()) + 1
line_end = content.find('\n', match.end())
line = content[line_start:line_end if line_end > 0 else len(content)]
if any(kw in line.lower() for kw in ['formerly', 'removed', 'deprecated', 'was dead code']):
continue
missing.append(f" {fpath}: {ref}")
if missing:
print(f"✗ Check 13 FAIL: {len(missing)} broken file reference(s):")
for m in missing:
print(m)
else:
print(f"✓ Check 13: all file references valid ({files_scanned} files scanned)")
PYEOF
# Verifies that .zscripts/dev.sh is git-tracked (not ignored by .gitignore)
# AND that its hash matches skill/stellar-trails/dev.sh (the zip source).
# Catches: .gitignore regression (re-ignoring .zscripts/), dev.sh drift between
# the tracked runtime copy and the zip source.
if git ls-files --error-unmatch .zscripts/dev.sh >/dev/null 2>&1; then
SKILL_HASH=$(sha256sum skill/stellar-trails/dev.sh | cut -d' ' -f1)
ZSCRIPTS_HASH=$(sha256sum .zscripts/dev.sh | cut -d' ' -f1)
if [ "$SKILL_HASH" != "$ZSCRIPTS_HASH" ]; then
echo "✗ Check 14 FAIL: .zscripts/dev.sh hash mismatch"
echo " skill/stellar-trails/dev.sh: $SKILL_HASH"
echo " .zscripts/dev.sh: $ZSCRIPTS_HASH"
echo " Fix: cp -f skill/stellar-trails/dev.sh .zscripts/dev.sh"
else
echo "✓ Check 14: .zscripts/dev.sh tracked + hash matches skill copy ($ZSCRIPTS_HASH)"
fi
else
echo "✗ Check 14 FAIL: .zscripts/dev.sh is NOT git-tracked — check .gitignore exception"
echo " Expected pattern in .gitignore: .zscripts/* + !.zscripts/dev.sh"
echo " Or run: git add -f .zscripts/dev.sh"
fi
sed + commit) → run checks 4+5+6+9Never skip: checks 1 (bash syntax), 8 (markdown fences), 9 (post-push plan)
**)Problem this solves: GLM-5.2 z.ai has a known weakness — when auditing or diagnosing, it tends to trace problems too far down the causal chain, rabbit-holing into deep investigations when the root cause is proximate and simple. This wastes tokens and time, and often loses the user's actual question in the weeds.
Inspiration: Combines core concepts from two clawhub skills — occams-razor (Parsimony Audit: prefer fewest assumptions) and aana-task-scope-guardrail (Scope Gate: classify actions, stop when complete) — into a single orisinil protocol integrated into stellar-trails workflow. Not a wrapper — this is a native feature with its own decision tree, tuned for the proximate-cause failure mode.
Mandatory trigger in these phases:
Optional trigger (LLM should self-check):
Before going deeper into investigation, answer these 3 questions:
Q1: Is the candidate cause within 1 hop of the symptom?
Q2: Does the candidate explain ALL observed symptoms with ≤2 assumptions?
Q3: Would fixing this candidate resolve the user's actual request?
Symptom observed
│
├─ Q1: Is candidate within 1 hop of symptom?
│ ├─ YES + Q2 ≤2 assumptions + Q3 fixes user request
│ │ → FIX NOW (proximate, parsimonious, in-scope)
│ │
│ ├─ YES but Q2 >2 assumptions
│ │ → Look for SIMPLER proximate cause before going deeper
│ │
│ └─ NO (far cause)
│ ├─ Q3 still in scope?
│ │ ├─ YES → investigate, but time-box (max 1 deeper level)
│ │ └─ NO → DEFER (out of scope, log it)
│ └─ Q2 needs >3 assumptions?
│ → STOP. Likely over-engineering. Re-state problem to user.
Before each investigation step, classify the action:
| Category | Action |
|---|---|
in_scope | Directly requested by user → proceed |
necessary_support | Required to complete request → proceed |
clarification_needed | Ambiguous boundary → ASK user before continuing |
optional_followup | Useful but not required → mention briefly, do NOT do |
out_of_scope | Unrelated/premature → DO NOT do, log to worklog |
stop | Request is complete → STOP, do not keep acting |
Hard rule: if proposed action is out_of_scope OR request is stop, you MUST stop. Continuing is a correctness bug.
When multiple competing hypotheses exist for a symptom:
# Parsimony Audit: <symptom>
## Candidates:
A: <hypothesis 1> B: <hypothesis 2> C: <hypothesis 3>
## Fit check:
A fits all evidence? <yes/no> B? <yes/no> C? <yes/no>
## Assumption load (count unsupported assumptions, NOT words):
A: <list> → N assumptions
B: <list> → N assumptions
C: <list> → N assumptions
## Proximate check:
A within 1 hop? <yes/no> B? <yes/no> C? <yes/no>
## Preferred: <fewest assumptions + most proximate>
## Over-shave check: <preferred still fits all evidence?>
## What would overturn this: <distinguishing evidence>
Key rule: parsimony counts unsupported assumptions, not words. "It's the network" (5 words) posits 1 unobserved failure — high assumption load. "Cache TTL expired at 14:03, as logs show" (10 words) assumes 0 unsupported — low load. Prefer the second.
optional_followup.out_of_scope unless user asked for full audit. Proximate cause + parsimony audit is sufficient.Scenario: User reports "Step 3 activation fails with '✗ GATE FAILED'".
Wrong (deep rabbit hole):
Right (Proximate Cause Triage):
✗ GATE FAILED means EXPECTED_TOKEN != ACTUAL_TOKEN/tmp/st-active exists. If missing → Step 1 was skipped. If present but wrong → recompute hash.Problem this solves: stellar-trails SADC section previously mandated Skill(command="crawl4ai") for content extraction. This creates external dependency — if crawl4ai is not installed, broken, or its API changes, SADC fails. User explicitly requested removing this reliance.
Solution: orisinil inline content retrieval using sandbox-native tools (curl + python3). No external skill dependency. Simpler, more reliable, fully under stellar-trails control.
Replace Skill(command="crawl4ai") and Skill(command="web-reader") calls with this inline protocol in:
Step 1: Fetch with curl (sandbox-native, no Python dependency)
# Fetch URL, follow redirects, set user-agent, 10s timeout, capture to file
URL="<url>"
OUTFILE="/tmp/st-retrieval-$(echo "$URL" | sha256sum | cut -c1-8).html"
curl -sSL -m 10 -A "Mozilla/5.0 (compatible; StellarTrails/9.5)" "$URL" -o "$OUTFILE"
HTTP_STATUS=$(curl -sSL -m 10 -o /dev/null -w "%{http_code}" "$URL")
[ "$HTTP_STATUS" = "200" ] || { echo "✗ Retrieval failed: HTTP $HTTP_STATUS"; exit 1; }
echo "✓ Fetched $(stat -c%s "$OUTFILE") bytes from $URL"
Step 2: Extract text with python3 (using only stdlib html.parser)
python3 << 'PYEOF'
import sys, re, html
from html.parser import HTMLParser
class TextExtractor(HTMLParser):
def __init__(self):
super().__init__()
self.text = []
self.skip = False # skip script/style/nav/footer
self.skip_tags = {'script', 'style', 'nav', 'footer', 'header', 'aside', 'noscript'}
self.title = ''
self.in_title = False
def handle_starttag(self, tag, attrs):
if tag in self.skip_tags:
self.skip = True
if tag == 'title':
self.in_title = True
if tag in ('h1','h2','h3','h4','h5','h6','p','li','td','th','div','section','article','pre','code','blockquote'):
self.text.append('\n') # block-level: newline before
def handle_endtag(self, tag):
if tag in self.skip_tags:
self.skip = False
if tag == 'title':
self.in_title = False
if tag in ('p','li','div','section','article','pre','blockquote'):
self.text.append('\n') # block-level: newline after
def handle_data(self, data):
if self.skip:
return
if self.in_title:
self.title += data
text = data.strip()
if text:
self.text.append(text)
with open(sys.argv[1], 'r', encoding='utf-8', errors='ignore') as f:
content = f.read()
parser = TextExtractor()
parser.feed(content)
result = ' '.join(parser.text)
# Collapse whitespace
result = re.sub(r'\s+', ' ', result)
result = re.sub(r' \n ', '\n', result)
result = re.sub(r'\n{3,}', '\n\n', result)
# Trim to first 3000 chars (SADC needs summary, not full page)
result = result[:3000]
if parser.title:
print(f"# {parser.title.strip()}\n")
print(result)
PYEOF
Step 3: Truncate to ≤500 words for SADC summary
# After extraction, truncate to 500 words for SADC
TEXT_FILE="${OUTFILE%.html}.txt"
python3 -c "
import sys
text = sys.stdin.read()
words = text.split()[:500]
print(' '.join(words))
" < "$TEXT_FILE" > "${TEXT_FILE}.truncated"
echo "✓ Extracted $(wc -w < "${TEXT_FILE}.truncated") words to ${TEXT_FILE}.truncated"
| Situation | Use |
|---|---|
| Static HTML page, public URL | Inline (this protocol) |
| Page requires JavaScript rendering | agent-browser skill (rendered extraction) |
| Page behind authentication | User-provided content (skip retrieval) |
| Page returns non-HTML (PDF, JSON, etc) | curl + appropriate parser inline |
| Bulk crawl (10+ pages) | Loop the inline protocol, OR use crawl4ai if installed |
Default: use inline. Only fall back to external skill if inline fails (JS rendering needed, etc.).
| Aspect | crawl4ai (external) | Inline (orsinil) |
|---|---|---|
| Dependency | Requires skill installed + Python package | None (curl + python3 stdlib) |
| Failure modes | Package not installed, API changes, async issues | curl fails (network), python3 fails (parsing) |
| Speed | AsyncWebCrawler startup overhead | curl ~1s + python3 ~0.1s |
| Control | External skill controls behavior | stellar-trails controls everything |
| Token cost | Loads crawl4ai SKILL.md (~2K tokens) into context | 0 tokens — protocol is in stellar-trails SKILL.md |
| Maintenance | Dependent on crawl4ai updates | Self-maintained, version-controlled with stellar-trails |
out_of_scope unless user asked for bulk crawl. SADC needs 3-5 top URLs, not 20.agent-browser (also installed) for JS rendering. Only escalate to crawl4ai as last resort.SADC section (Standard/Complex tier) now reads:
BEFORE writing the problem specification, the main agent invokes
Skill(command="web-search")to find existing solutions, then uses the Inline Content Retrieval protocol (above) to extract content from top 3-5 URLs → ≤500-word summary.
This removes the Skill(command="crawl4ai") dependency. web-search is still external (it's the search API, not extraction), but extraction is now inline.
Task: "Build a PDF report — SADC required"
# 1. web-search returns 5 URLs (still external skill)
# 2. Inline retrieval for top 3 URLs:
for URL in "$URL1" "$URL2" "$URL3"; do
OUTFILE="/tmp/st-retrieval-$(echo "$URL" | sha256sum | cut -c1-8).html"
curl -sSL -m 10 -A "Mozilla/5.0 (compatible; StellarTrails/9.5)" "$URL" -o "$OUTFILE"
# ... extract text via python3 (Step 2 above) ...
# ... truncate to 500 words (Step 3 above) ...
done
# 3. Summarize: combine 3 truncated files into ≤500-word SADC summary
cat /tmp/st-retrieval-*.truncated | python3 -c "
import sys
text = sys.stdin.read()
words = text.split()[:500]
print(' '.join(words))
"
Inspiration: Adapted from @steipete/github clawhub skill (v1.0.0, MIT-0 license) by @steipete. The original skill documents gh CLI patterns for PR checks, workflow runs, and API queries. Not a wrapper — adapted to stellar-trails' curl-based approach because gh CLI is not available in z.ai sandbox.
Why adapt: stellar-trails already uses GitHub API for CI polling (Steps 3, 4 in activation), release management (Pre-Push Local Verification), and tag pushing. Currently these are ad-hoc curl calls scattered across phases. Codifying them into a protocol makes GitHub operations consistent, reusable, and safer.
License attribution: Original @steipete/github skill is MIT-0 (no attribution required). Adapted patterns retained as orisinil curl-based implementation.
gh CLI| Aspect | gh CLI (original skill) | curl + PAT (stellar-trails adaptation) |
|---|---|---|
| Availability | Not installed in z.ai sandbox | curl is sandbox-native |
| Auth | gh auth login (interactive browser flow) | PAT in /home/z/my-project/upload/PAT (already configured) |
| Token storage | gh's own credential store | File-based, user-controlled |
| Scriptability | Subprocess invocation | Native bash, no subprocess overhead |
| Portability | Requires gh install | Works anywhere curl exists |
Before using any GitHub Operations command:
/home/z/my-project/upload/PAT (user-managed, persistent across sessions)GH_TOKEN=$(tr -d '[:space:]' < /home/z/my-project/upload/PAT)
HTTP=$(curl -sS -m 10 -o /tmp/gh_user.json -w "%{http_code}" \
-H "Authorization: Bearer $GH_TOKEN" https://api.github.com/user)
[ "$HTTP" = "200" ] || { echo "✗ PAT invalid or expired (HTTP $HTTP)"; exit 1; }
python3 -c "import json; d=json.load(open('/tmp/gh_user.json')); print(f'✓ Authenticated as: {d.get(\"login\")}')"
tr -d '[:space:]' to strip, never echo $GH_TOKENProblem: z.ai /start.sh sets global git config to user.email=z@container user.name=Z User. This causes 3 bugs:
~/.git-credentials is in $HOME (/home/z/), NOT in /home/z/my-project/ → not in repo.tar → wiped on session reset → git push fails with auth errorgit -c user.email=X -c user.name=Y commit sets author but committer falls back to global config (Z User)Solution: Run this setup BEFORE any git commit or git push. This overrides /start.sh's Z User config with the PAT owner's real GitHub identity.
# === Git Identity Setup (MANDATORY before any git commit/push) ===
# Run this once per session, before first git operation.
GH_TOKEN=$(tr -d '[:space:]' < /home/z/my-project/upload/PAT)
# 1. Fetch token owner identity from GitHub API
OWNER_JSON=$(curl -sS -m 10 -H "Authorization: Bearer $GH_TOKEN" https://api.github.com/user)
OWNER_LOGIN=$(echo "$OWNER_JSON" | python3 -c "import json,sys; print(json.load(sys.stdin).get('login',''))")
OWNER_NAME=$(echo "$OWNER_JSON" | python3 -c "import json,sys; print(json.load(sys.stdin).get('name') or json.load(sys.stdin).get('login',''))")
OWNER_EMAIL="${OWNER_LOGIN}@users.noreply.github.com"
# 2. Override /start.sh's Z User config with token owner identity
# This fixes Bug 3: author AND committer now use owner identity
git config --global user.email "$OWNER_EMAIL"
git config --global user.name "$OWNER_NAME"
# 3. Re-create ~/.git-credentials (NOT persistent — wiped on session reset)
# This fixes Bug 2: git push auth failure after session reset
git config --global credential.helper store
echo "https://${OWNER_LOGIN}:${GH_TOKEN}@github.com" > ~/.git-credentials
chmod 600 ~/.git-credentials
# 4. Export GIT_AUTHOR_* and GIT_COMMITTER_* env vars for double-ensure
# This fixes Bug 1: even if global config somehow reverts, env vars take priority
export GIT_AUTHOR_NAME="$OWNER_NAME"
export GIT_AUTHOR_EMAIL="$OWNER_EMAIL"
export GIT_COMMITTER_NAME="$OWNER_NAME"
export GIT_COMMITTER_EMAIL="$OWNER_EMAIL"
# 5. Verify
echo "✓ Git identity configured:"
echo " user.name: $(git config --global user.name)"
echo " user.email: $(git config --global user.email)"
echo " credentials: $([ -f ~/.git-credentials ] && echo '✓ present' || echo '✗ MISSING')"
echo " author env: GIT_AUTHOR_NAME=$GIT_AUTHOR_NAME"
echo " committer env: GIT_COMMITTER_NAME=$GIT_COMMITTER_NAME"
Why this is needed every session:
/start.sh runs at session start → sets Z User globally~/.git-credentials is in $HOME (/home/z/) → NOT in /home/z/my-project/ → NOT in repo.tar → wiped on resetGIT_AUTHOR_* / GIT_COMMITTER_* env vars don't persist across sessionsAnti-patterns (FORBIDDEN):
-c sets per-command config, but committer can still fall back to global Z User. Env vars are the only reliable override.~/.git-credentials is in $HOME, not in repo.tar. Every session reset wipes it.Integration with activation: This setup should run as part of Step 2 (popup server) or immediately after Step 5, BEFORE any task that involves git commit/push. If a task doesn't involve git, this setup can be skipped.
Original (@steipete/github): gh pr checks 55 --repo owner/repo
Adapted: curl GitHub API for check runs on a PR's HEAD commit.
GH_TOKEN=$(tr -d '[:space:]' < /home/z/my-project/upload/PAT)
REPO="owner/repo"
PR_NUMBER=55
# Get PR HEAD SHA
PR_JSON=$(curl -sS -m 10 -H "Authorization: Bearer $GH_TOKEN" \
"https://api.github.com/repos/$REPO/pulls/$PR_NUMBER")
HEAD_SHA=$(echo "$PR_JSON" | python3 -c "import json,sys; print(json.load(sys.stdin).get('head',{}).get('sha',''))")
echo "PR #$PR_NUMBER HEAD: $HEAD_SHA"
# Get check runs for that SHA
curl -sS -m 10 -H "Authorization: Bearer $GH_TOKEN" \
"https://api.github.com/repos/$REPO/commits/$HEAD_SHA/check-runs" \
| python3 -c "
import json, sys
d = json.load(sys.stdin)
for cr in d.get('check_runs', []):
print(f\" {cr.get('name')}: {cr.get('status')}/{cr.get('conclusion') or '-'}\")"
Original: gh run list --repo owner/repo --limit 10
Adapted: curl GitHub Actions API.
GH_TOKEN=$(tr -d '[:space:]' < /home/z/my-project/upload/PAT)
REPO="owner/repo"
curl -sS -m 10 -H "Authorization: Bearer $GH_TOKEN" \
"https://api.github.com/repos/$REPO/actions/runs?per_page=10" \
| python3 -c "
import json, sys
d = json.load(sys.stdin)
print(f\"total_count: {d.get('total_count')}\")
for r in d.get('workflow_runs', [])[:10]:
print(f\" #{r.get('run_number')} | {r.get('name')} | {r.get('head_branch')} | {r.get('status')}/{r.get('conclusion') or '-'} | {r.get('created_at')}\")
print(f\" URL: {r.get('html_url')}\")"
Original: gh run view <run-id> --repo owner/repo --log-failed
Adapted: curl jobs endpoint, identify failed steps, fetch logs (requires auth + logs API which needs Accept header).
GH_TOKEN=$(tr -d '[:space:]' < /home/z/my-project/upload/PAT)
REPO="owner/repo"
RUN_ID="<run-id>"
# Get jobs in the run, find failed steps
curl -sS -m 10 -H "Authorization: Bearer $GH_TOKEN" \
"https://api.github.com/repos/$REPO/actions/runs/$RUN_ID/jobs" \
| python3 -c "
import json, sys
d = json.load(sys.stdin)
for j in d.get('jobs', []):
print(f\"JOB: {j.get('name')} - {j.get('conclusion')}\")
for s in j.get('steps', []):
if s.get('conclusion') == 'failure':
print(f\" FAILED STEP: {s.get('name')}\")
print(f\" started: {s.get('started_at')} | completed: {s.get('completed_at')}\")"
# Download full logs zip (authenticated endpoint)
curl -sS -L -m 60 -H "Authorization: Bearer $GH_TOKEN" \
-o "/tmp/gh-logs-$RUN_ID.zip" \
"https://api.github.com/repos/$REPO/actions/runs/$RUN_ID/logs"
echo "Logs saved to /tmp/gh-logs-$RUN_ID.zip"
unzip -o -q "/tmp/gh-logs-$RUN_ID.zip" -d "/tmp/gh-logs-$RUN_ID/"
# Find the failed step log file
find "/tmp/gh-logs-$RUN_ID/" -name "*Publish*" -o -name "*failed*" | head -5
Original: gh api repos/owner/repo/pulls/55 --jq '.title, .state, .user.login'
Adapted: curl + python3 (jq may not be installed; python3 is always available).
GH_TOKEN=$(tr -d '[:space:]' < /home/z/my-project/upload/PAT)
REPO="owner/repo"
# Get PR with specific fields (jq-style via python3)
curl -sS -m 10 -H "Authorization: Bearer $GH_TOKEN" \
"https://api.github.com/repos/$REPO/pulls/55" \
| python3 -c "
import json, sys
d = json.load(sys.stdin)
print(f\"title: {d.get('title')}\")
print(f\"state: {d.get('state')}\")
print(f\"user: {d.get('user',{}).get('login')}\")"
# List issues with specific fields (original: --json number,title --jq '.[] | "\(.number): \(.title)"')
curl -sS -m 10 -H "Authorization: Bearer $GH_TOKEN" \
"https://api.github.com/repos/$REPO/issues?state=open&per_page=10" \
| python3 -c "
import json, sys
d = json.load(sys.stdin)
for i in d:
print(f\"{i.get('number')}: {i.get('title')}\")"
Before running ANY GitHub operation, classify the action:
| Action class | Examples | Approval required? |
|---|---|---|
| Read | GET issues, PRs, runs, logs, check-runs | NO (safe) |
| Write | POST comments, create PRs, push tags/commits | YES (user explicit) |
| Modify | PATCH PRs, issues, repo settings | YES (user explicit) |
| Delete | DELETE branches, comments, releases | YES (user explicit + confirm) |
| API mutation | gh api -X POST/PATCH/DELETE equivalent | YES (user explicit) |
Hard rule: any non-GET request to GitHub API requires user explicit approval. State the exact mutation (URL + payload) before executing. Silent mutations are correctness bugs.
Auth scope check (run once per session, before first write op):
GH_TOKEN=$(tr -d '[:space:]' < /home/z/my-project/upload/PAT)
curl -sSI -m 10 -H "Authorization: Bearer $GH_TOKEN" https://api.github.com/user \
| grep -i "x-oauth-scopes:" | head -1
# Verify scopes include 'repo' for write operations, 'workflow' for workflow file changes
clawhub inspect (not GitHub API) — different concern, do not conflategh is not installed in sandbox. Use curl + PAT.tr -d '[:space:]' and never echo $GH_TOKEN.out_of_scope. 10 most recent is sufficient for diagnosis.Scenario: CI run #28982551045 failed at "Publish to ClawHub" step.
Wrong (deep rabbit hole):
Right (GitHub Operations Protocol + Proximate Cause Triage):
GH_TOKEN=$(tr -d '[:space:]' < /home/z/my-project/upload/PAT)
curl -sS -L -m 60 -H "Authorization: Bearer $GH_TOKEN" \
-o /tmp/gh-logs.zip \
"https://api.github.com/repos/hoshiyomiX/stellar-trails/actions/runs/28982551045/logs"
unzip -o -q /tmp/gh-logs.zip -d /tmp/gh-logs/
cat "/tmp/gh-logs/build-and-release/10_Publish to ClawHub.txt"
Phase transitions are guarded. A phase cannot begin until its entry condition is met.
| Gate | Condition |
|---|---|
| SPECIFY → PLAN | All problem-spec fields filled, SADC complete, AskUserQuestion ran (or skipped with reason) |
| PLAN → IMPLEMENT | Implementation plan complete + Scope output (Standard/Complex) + ⏸️ AWAITING APPROVAL TO ENTER IMPLEMENT printed |
| IMPLEMENT → VERIFY | Self-review checklist pass, all IMPL steps done |
| VERIFY → DELIVER | All verification items PASS |
Standard/Complex tier: PLAN → IMPLEMENT gate produces a Scope (see Deliveries). The delivery report's Scope Drift field tracks any deviation.
Four templates are now embedded inline. Standard/Complex tasks must use the exact headers below. Free-form = correctness bug.
Two structured outputs bookend implementation: Scope (end of PLAN) and Delivery (end of DELIVER).
☄️ COMMIT [Standard]
├─ Approach : <primary approach, 1-2 sentences>
├─ Alternatives : <2+ alternatives, 1 sentence each>
├─ Fallback : <alternative if primary fails>
├─ Pre-Deploy : <local verification step, or N/A>
├─ Scope IN : <what's included>
├─ Scope OUT : <what's excluded>
├─ IMPL Steps : X (IMPL-001 to IMPL-XXX)
└─ Risk : LOW / MEDIUM / HIGH
After printing Scope, print: ⏸️ AWAITING APPROVAL TO ENTER IMPLEMENT
Do NOT call any tool after this line. Wait for user reply.
☄️ REPORT [Simple]
SPECIFY→DELIVER : PASS | Evidence: <one-line result> | Defects: 0 | Drift: NONE
Phase Trace : IDLE→SPECIFY→PLAN→IMPLEMENT→VERIFY→DELIVER
☄️ REPORT [Standard]
├─ Continuation : NEW / YES
├─ Phase Trace : IDLE→SPECIFY→PLAN→IMPLEMENT→VERIFY→DELIVER
├─ IMPLEMENT : PASS
│ ├─ Steps : 4/4
│ ├─ Deviations : 0
│ └─ Quality : lint PASS, tsc PASS
├─ VERIFY : PASS
│ ├─ Checks : 3/3
│ └─ Edge Cases : 2/2
├─ Pivot : NONE
├─ Scope Drift : NONE
└─ Outcome : PASS
Evidence: [concrete results]
Defects found and fixed: 0
If Pivot is not NONE, expand it:
├─ Pivot : YES
│ ├─ From : <original approach>
│ ├─ Trigger : <what made us pivot>
│ ├─ To : <new approach>
│ └─ Re-planned : X steps (IMPL-001 to IMPL-XXX)
☄️ PASS | Evidence: <one-line result>
Phase Trace: IDLE→SPECIFY→PLAN→IMPLEMENT→VERIFY→DELIVER (internal)
For interactive web development tasks (Next.js, UI components, dashboards), implementation is delegated to fullstack-dev — the DELIVER phase calls the platform's Complete(project_type="web_dev", summary="...") tool to finalize. For non-web coding tasks, DELIVER presents output file paths. In all cases, DELIVER appends a Snapshot to worklog.md.
Inspiration: Adapted from TencentDB-Agent-Memory (15K stars, MIT license) by Tencent Cloud. The original project implements a team-level memory hub with 4 memory assets (Chat Memory, Skill, LLM-Wiki, Code-Graph) and layered memory (L0-L3). Not a wrapper — adapted to stellar-trails' sandbox-native, no-external-dependency constraints.
Problem this solves: stellar-trails worklog is flat (L0 only — raw task state). Across 20+ sessions, reusable patterns are lost. Each session starts from scratch, repeating mistakes that were already solved. The original TencentDB project solves this with vector search + Docker + team-level sharing — too heavy for z.ai sandbox. This adaptation uses structured text files (no vector DB, no Docker).
| Layer | What it stores | stellar-trails file | When written |
|---|---|---|---|
| L0 Task | Raw task state (what was done, files modified, outcome) | worklog.md (existing) | DELIVER phase (existing) |
| L1 Pattern | Reusable facts extracted from task (approach that worked, gotchas, shortcuts) | knowledge/patterns.md (NEW) | DELIVER phase (NEW extraction) |
| L2 Scenario | Project-level context blocks (accumulated L1 patterns grouped by project/domain) | knowledge/scenarios.md (NEW) | DELIVER phase (NEW, when L1 count ≥5 for same domain) |
| L3 Profile | Stable user preferences, working style, recurring decisions | knowledge/user-profile.md (NEW) | DELIVER phase (NEW, when pattern repeats ≥3 times) |
After each Standard/Complex task, extract reusable knowledge into knowledge/patterns.md:
# === L1 Pattern Extraction (NEW in v9.11.0) ===
# Run at DELIVER phase, after worklog snapshot.
# Extract: what approach worked, what went wrong, what was learned.
PATTERNS_FILE="/home/z/my-project/skills/stellar-trails/knowledge/patterns.md"
if [ ! -f "$PATTERNS_FILE" ]; then
echo "# Pattern Library (L1 Memory)" > "$PATTERNS_FILE"
echo "" >> "$PATTERNS_FILE"
echo "Auto-extracted reusable patterns from stellar-trails tasks." >> "$PATTERNS_FILE"
echo "Adapted from TencentDB-Agent-Memory L1 Atom concept." >> "$PATTERNS_FILE"
echo "" >> "$PATTERNS_FILE"
echo "---" >> "$PATTERNS_FILE"
fi
# LLM appends pattern entry (not bash — LLM must think + write):
# Format:
# ## [YYYY-MM-DD] <domain>: <pattern-name>
# **Context**: <when this pattern applies>
# **Approach**: <what worked>
# **Gotcha**: <what to avoid>
# **Source**: <task that produced this pattern>
LLM responsibility: The LLM must actively extract patterns, not just append task state. Ask: "What did I learn from this task that would help next time?"
When L1 patterns accumulate ≥5 entries for the same domain (e.g., "git", "ci", "clawhub", "sandbox"), group them into knowledge/scenarios.md:
# Scenario: Git Identity Issues
## Patterns:
- [2026-07-09] git: Z User override requires env vars, not just config
- [2026-07-20] git: ~/.git-credentials not in repo.tar, wiped each session
- [2026-07-26] git: Auto Git Identity Setup in Step 1 solves both
## Composite insight: Always run Git Identity Setup at session start if PAT exists
When the same decision pattern repeats ≥3 times across sessions, extract to knowledge/user-profile.md:
# User Profile (L3 Memory)
## Preferences:
- Prefers direct Edit tool over patch files ("junk")
- PAT kept permanently at /home/z/my-project/upload/PAT
- Wants version bumps on every change, even small fixes
- Prefers Indonesian language for explanations, English for code
Instead of always loading ALL knowledge files at Step 5, load only relevant ones:
| Task type | Load |
|---|---|
| Coding (git/CI) | knowledge/zai-sandbox.md + knowledge/error-patterns.md + knowledge/patterns.md (L1) |
| Coding (web dev) | knowledge/zai-sandbox.md + knowledge/architecture.md |
| Document | knowledge/conventions.md + knowledge/patterns.md (L1) |
| Audit/Diagnosis | knowledge/error-patterns.md + knowledge/patterns.md (L1) + knowledge/scenarios.md (L2) |
| Continuation (same domain) | Only knowledge/scenarios.md (L2) for that domain |
| New session (cold start) | knowledge/user-profile.md (L3) + knowledge/patterns.md (L1) for bootstrap |
Rule: Never load ALL knowledge files unless explicitly needed. Context budget is finite — loading irrelevant knowledge wastes tokens.
knowledge/error-patterns.md (existing file, currently static) should grow dynamically. At DELIVER, if task encountered an error that was fixed:
# === Error Pattern Accumulation (NEW in v9.11.0) ===
ERRORS_FILE="/home/z/my-project/skills/stellar-trails/knowledge/error-patterns.md"
# LLM appends error entry:
# Format:
# ## [YYYY-MM-DD] <error-type>
# **Symptom**: <what user observed>
# **Root cause**: <proximate cause, 1 hop>
# **Fix**: <what resolved it>
# **Prevention**: <how to avoid next time>
knowledge/ directory, included in skill zip.| TencentDB feature | Why not adapted |
|---|---|
| Vector search (BM25 + embedding) | Needs external DB + embedding model — not sandbox-native |
| Team-level sharing (ACL, multi-agent) | stellar-trails is single-agent in z.ai sandbox |
| Code-Graph (codebase indexer) | Needs AST parser + graph DB — too heavy |
| Docker deployment | Not sandbox-native |
| Async pipeline (background processing) | No background process support in z.ai sandbox |
| L0 Conversation storage | Conversation history is managed by z.ai platform, not skill |
This adaptation captures the concept of layered memory (L0-L3) and structured knowledge accumulation, but NOT the retrieval quality of vector search. stellar-trails' pattern retrieval is grep-based — sufficient for ~50-100 patterns, but won't scale to thousands. For the expected pattern volume (1-2 per session, ~50 per 25 sessions), text search is adequate.
Expected impact: After 10 sessions, knowledge/patterns.md will have ~15-20 entries. Next session's Step 5 loads these patterns → LLM avoids repeating past mistakes → faster task completion, fewer CI cycles wasted on known bugs.
This framework is text in a skill file. It relies on the LLM reading it to follow instructions. 12 enforcement vectors across 3 tiers (Legacy Text E1-E3, Pre-Tool Gate E4-E6, Sandbox-Native E7-E11, Exit Code E12) shift compliance from prose to verifiable artifacts, but the LLM is still the executor — a determined LLM can rationalize past any text-based rule. Compliance scoring (v9.13.0) is self-graded. The user is the final judge of quality.
Verified WORKING in the current environment (2-loop audit, 2026-08-23):
/home/user_skills/.st-activation-log — 326 entries, 38 days, 0 monotonicity violationsclawhub inspect + clawhub --no-input update --force drift detection + force-update (v9.12.0 → v9.13.0 observed mid-activation)main@65b6bf4 = installed skill = v9.13.0; index.html matches (Check 10):3000 serving HTTP 200 via curl + raw TCP socket; Caddy gateway on internal port 81clawhub --version exits 1 (quirk reproduces exactly as documented)/home/user_skills/ is world-writable 0777, owner z:z (bash stat + python3 os.stat agree)Found NOT working / overstated / unverifiable (corrected v9.13.1):
sha256("<version>\n")[:16]), not session-scoped — identical across concurrent sessions. Gate proves "someone hashed", never "this agent ran Step 1". Matrix: NO → PARTIAL.session=/tokens=YES fields the Step 5 bash never wrote — corrected to actual output (token=<hash> steps=5/5 banner=YES). "Session IDs are recorded by platform" was false.$HOME/.stellar-trails-repo/ in clawhub-installed sandboxes: the skill arrives from /home/user_skills/stellar-trails.zip, not a git clone. SSV is skipped gracefully.:3000 serving confirmed internally, but whether the user's preview panel renders it cannot be tested from inside the sandbox.Rule of thumb: Prose rots faster than bash — re-audit documentation claims against the live environment regularly.
Research (Lost in the Middle, arXiv 2307.03172) shows inherent ~70-85% compliance ceilings on SOTA models for complex multi-step prompts. The v9.0.0+ enforcement vectors raise the realistic ceiling to ~90% via text + sandbox-native mechanisms. Reaching ~98% requires a harness-level verifier script that scans the transcript for required prints/gates. 100% guaranteed compliance requires platform-level enforcement (ClawHub rejecting non-compliant invocations) — out of scope for skill authoring.