Release v2.1.0
Some checks are pending
CI / Test (macos-latest) (push) Waiting to run
CI / Test (ubuntu-latest) (push) Waiting to run
CI / Release Build (aarch64-apple-darwin) (push) Blocked by required conditions
CI / Release Build (x86_64-unknown-linux-gnu) (push) Blocked by required conditions
CI / Release Build (x86_64-apple-darwin) (push) Blocked by required conditions
Test Suite / Unit Tests (push) Waiting to run
Test Suite / MCP E2E Tests (push) Waiting to run
Test Suite / User Journey Tests (push) Blocked by required conditions
Test Suite / Dashboard Build (push) Waiting to run
Test Suite / Code Coverage (push) Waiting to run

This commit is contained in:
Sam Valladares 2026-04-27 13:20:51 -05:00
parent 694e837898
commit d4313df759
106 changed files with 2900 additions and 128 deletions

116
hooks/cwd-state-injector.sh Executable file
View file

@ -0,0 +1,116 @@
#!/bin/bash
# cwd-state-injector.sh — SessionStart + UserPromptSubmit hook
#
# HOOK #3 of the 2026-04-20 upgrade: ELIMINATE RE-EXPLORATION PENALTY.
#
# On every prompt, reads the current directory's git + CI + test state and
# injects it as additionalContext so Claude starts every turn already knowing:
#
# - current git branch + HEAD commit + staged/unstaged file counts
# - last commit subject + author
# - last GitHub Actions run conclusion via gh CLI (if repo has remote)
# - open PR + open issue counts
# - recent test-suite status (cached if present)
#
# Saves ~500 tokens per prompt (Claude no longer asks "what state are we in?")
# and prevents stale-state reasoning errors.
#
# Cached in /tmp/cwd-state-{hash}.json for 60s to keep hook fast.
# Fails open: if gh or git unavailable, emits partial context.
set -u
INPUT="$(cat)"
CWD="$(printf '%s' "$INPUT" | /usr/bin/python3 -c 'import sys,json;d=json.load(sys.stdin);print(d.get("cwd",""))' 2>/dev/null || printf '')"
# Fallback to PWD if cwd not in input
if [ -z "$CWD" ] || [ ! -d "$CWD" ]; then
CWD="$(pwd 2>/dev/null)"
fi
# Only run in git repos
cd "$CWD" 2>/dev/null || exit 0
if ! /usr/bin/git rev-parse --is-inside-work-tree > /dev/null 2>&1; then
exit 0
fi
# Cache for 60s
CACHE_KEY="$(printf '%s' "$CWD" | /usr/bin/shasum | awk '{print $1}')"
CACHE_FILE="/tmp/cwd-state-${CACHE_KEY}.json"
if [ -f "$CACHE_FILE" ]; then
MTIME=$(stat -f %m "$CACHE_FILE" 2>/dev/null || stat -c %Y "$CACHE_FILE" 2>/dev/null || echo 0)
NOW=$(date +%s)
AGE=$((NOW - MTIME))
if [ "$AGE" -lt 60 ] && [ -s "$CACHE_FILE" ]; then
cat "$CACHE_FILE"
exit 0
fi
fi
# Gather state
BRANCH="$(/usr/bin/git rev-parse --abbrev-ref HEAD 2>/dev/null)"
HEAD_SHA="$(/usr/bin/git rev-parse --short HEAD 2>/dev/null)"
HEAD_SUBJECT="$(/usr/bin/git log -1 --format='%s' 2>/dev/null | head -c 100)"
HEAD_AUTHOR="$(/usr/bin/git log -1 --format='%an' 2>/dev/null)"
STAGED_COUNT="$(/usr/bin/git diff --cached --name-only 2>/dev/null | /usr/bin/wc -l | awk '{print $1}')"
UNSTAGED_COUNT="$(/usr/bin/git diff --name-only 2>/dev/null | /usr/bin/wc -l | awk '{print $1}')"
UNTRACKED_COUNT="$(/usr/bin/git ls-files --others --exclude-standard 2>/dev/null | /usr/bin/wc -l | awk '{print $1}')"
AHEAD_BEHIND="$(/usr/bin/git rev-list --left-right --count HEAD...@{upstream} 2>/dev/null | awk '{printf "ahead=%s behind=%s", $1, $2}' || echo "no-upstream")"
# GitHub state (only if gh CLI available + remote configured)
CI_STATE=""
PR_COUNT=""
ISSUE_COUNT=""
if /usr/bin/which gh > /dev/null 2>&1 && /usr/bin/git config --get remote.origin.url > /dev/null 2>&1; then
# Last CI run on current branch
CI_JSON="$(gh run list --branch "$BRANCH" --limit 1 --json status,conclusion,name,headSha 2>/dev/null || echo '[]')"
CI_STATE="$(printf '%s' "$CI_JSON" | /usr/bin/python3 -c 'import sys,json
try:
d=json.load(sys.stdin)
if d: r=d[0]; print(f"{r.get(\"name\",\"?\")}:{r.get(\"status\",\"?\")}:{r.get(\"conclusion\") or \"...\"}")
except: pass' 2>/dev/null)"
PR_COUNT="$(gh pr list --state open --json number 2>/dev/null | /usr/bin/python3 -c 'import sys,json
try: print(len(json.load(sys.stdin)))
except: print("?")' 2>/dev/null)"
ISSUE_COUNT="$(gh issue list --state open --json number 2>/dev/null | /usr/bin/python3 -c 'import sys,json
try: print(len(json.load(sys.stdin)))
except: print("?")' 2>/dev/null)"
fi
# Build context block
REPO_NAME="$(/usr/bin/basename "$CWD")"
CONTEXT_LINES=()
CONTEXT_LINES+=("[CWD STATE — auto-injected, 60s cache]")
CONTEXT_LINES+=(" repo: $REPO_NAME branch: $BRANCH HEAD: $HEAD_SHA")
if [ -n "$HEAD_SUBJECT" ]; then
CONTEXT_LINES+=(" last commit: \"$HEAD_SUBJECT\" by $HEAD_AUTHOR")
fi
CONTEXT_LINES+=(" working tree: staged=$STAGED_COUNT unstaged=$UNSTAGED_COUNT untracked=$UNTRACKED_COUNT")
if [ "$AHEAD_BEHIND" != "no-upstream" ]; then
CONTEXT_LINES+=(" vs upstream: $AHEAD_BEHIND")
fi
if [ -n "$CI_STATE" ]; then
CONTEXT_LINES+=(" last CI run: $CI_STATE")
fi
if [ -n "$PR_COUNT" ] && [ -n "$ISSUE_COUNT" ]; then
CONTEXT_LINES+=(" open: PRs=$PR_COUNT issues=$ISSUE_COUNT")
fi
# Format as JSON additionalContext
JSON_OUT="$(/usr/bin/python3 <<PYEOF
import json, os
lines = """$(printf '%s\n' "${CONTEXT_LINES[@]}")""".strip().split("\n")
ctx = "\n".join(lines)
print(json.dumps({
"hookSpecificOutput": {
"hookEventName": "UserPromptSubmit",
"additionalContext": ctx
}
}))
PYEOF
)"
# Cache and emit
printf '%s' "$JSON_OUT" > "$CACHE_FILE"
printf '%s' "$JSON_OUT"
exit 0

40
hooks/load-all-memory.sh Executable file
View file

@ -0,0 +1,40 @@
#!/bin/bash
# Load ALL memory MD files on every UserPromptSubmit.
# Sam's instruction (Apr 16, 2026): "call EVERY MD file after EVERY PROMPT"
# This hook cats every file in the memory directory into the prompt context.
# Resolve per-user Claude Code project memory dir from $HOME.
# Claude Code encodes home path as `-Users-<name>`; allow override via env.
if [ -n "${VESTIGE_MEMORY_DIR:-}" ]; then
MEM_DIR="$VESTIGE_MEMORY_DIR"
else
MEM_DIR="$HOME/.claude/projects/$(printf '%s' "$HOME" | tr '/' '-')/memory"
fi
if [ ! -d "$MEM_DIR" ]; then
exit 0
fi
echo "═══════════════════════════════════════════════════════════════"
echo "[FULL MEMORY DUMP — EVERY FILE LOADED PER SAM'S INSTRUCTION]"
echo "Sam said: 'call EVERY MD file after EVERY PROMPT' (Apr 16, 2026)"
echo "═══════════════════════════════════════════════════════════════"
echo ""
# Iterate every .md file in the memory directory (not archive)
for f in "$MEM_DIR"/*.md; do
if [ -f "$f" ]; then
filename=$(basename "$f")
echo ""
echo "┌─────────────────────────────────────────────────────────────"
echo "│ FILE: $filename"
echo "└─────────────────────────────────────────────────────────────"
cat "$f"
echo ""
fi
done
echo ""
echo "═══════════════════════════════════════════════════════════════"
echo "[END FULL MEMORY DUMP — $(ls "$MEM_DIR"/*.md 2>/dev/null | wc -l | tr -d ' ') files loaded]"
echo "═══════════════════════════════════════════════════════════════"

217
hooks/preflight-swarm.sh Executable file
View file

@ -0,0 +1,217 @@
#!/bin/bash
# preflight-swarm.sh — UserPromptSubmit hook (Pre-Cognitive Triad v1)
#
# Spawns the Lateral Thinker subagent via Claude Code's headless mode
# (`claude -p`) to generate a cross-disciplinary epiphany, then injects
# it as additionalContext before Main Claude sees the prompt.
#
# Architectural fixes over the other-agent draft:
# - Reads stdin JSON (not $1) per Claude Code UserPromptSubmit spec
# - Uses `claude -p` with inlined system prompt (no --agent flag exists)
# - Model: claude-haiku-4-5-20251001 (current Haiku, not Oct-2024 3.5)
# - Re-entrancy guard via VESTIGE_SWARM_ACTIVE env var (prevents the
# subagent from re-firing this same hook and looping forever)
# - 25-char minimum + y/yes/ok/continue bypass (fast-path preserved)
# - 8-second timeout (fails open if Haiku is slow)
# - EMPTY mute (no injection when subagent finds no epiphany)
# - Emits JSON additionalContext (no duplicate raw-prompt echo)
# - POSIX-sh-safe: quoted heredoc for script body, env var pass-through
#
# Ship date 2026-04-20. Pairs with the veto-detector.sh Guillotine on the
# Stop hook to form the Cognitive Sandwich (pre-flight Triad + post-flight
# Sanhedrin).
set -u
# === OPT-OUT GATE ===
# Pre-Cognitive Triad is ON by default as of 2026-04-21 (birthday launch day).
# To disable, set VESTIGE_SWARM_ENABLED=0 in your environment. Default-on
# guarantees the Cognitive Sandwich fires on fresh machines, Docker
# containers, GUI-launched Claude Code, and shells without .zshrc — any
# case where the Claude Code process lacks a sourced profile. The
# re-entrancy guard (VESTIGE_SWARM_ACTIVE) below still prevents fork-bombs
# from the subagent's own UserPromptSubmit hook.
if [ "${VESTIGE_SWARM_ENABLED:-1}" = "0" ]; then
exit 0
fi
# === RE-ENTRANCY GUARD ===
# If we are already inside the Lateral Thinker subagent, exit immediately
# so the subagent's own UserPromptSubmit does not spawn another Lateral
# Thinker. Without this guard: infinite fork-bomb.
if [ "${VESTIGE_SWARM_ACTIVE:-0}" = "1" ]; then
exit 0
fi
# === READ PROMPT FROM STDIN JSON ===
INPUT="$(cat)"
PROMPT="$(printf '%s' "$INPUT" | /usr/bin/python3 -c 'import sys,json;d=json.load(sys.stdin);print(d.get("prompt",""))' 2>/dev/null || printf '')"
SESSION_ID="$(printf '%s' "$INPUT" | /usr/bin/python3 -c 'import sys,json;d=json.load(sys.stdin);print(d.get("session_id",""))' 2>/dev/null || printf '')"
if [ -z "$PROMPT" ]; then
exit 0
fi
# === LATENCY + INTENT GATE ===
# Skip on very short prompts and common continuation phrases. These do not
# benefit from a lateral epiphany and the 2-4s latency would be annoying.
PROMPT_LEN="${#PROMPT}"
if [ "$PROMPT_LEN" -lt 25 ]; then
exit 0
fi
LOWER_TRIMMED="$(printf '%s' "$PROMPT" | /usr/bin/tr '[:upper:]' '[:lower:]' | /usr/bin/awk '{$1=$1;print}')"
case "$LOWER_TRIMMED" in
y|yes|no|ok|okay|continue|proceed|go|ship|lfg|lets\ go|lets\ ship|looks\ good|thanks|thank\ you|perfect|great|awesome)
exit 0
;;
esac
# === VERIFY claude CLI AVAILABLE ===
CLAUDE_BIN="$(command -v claude 2>/dev/null || true)"
if [ -z "$CLAUDE_BIN" ]; then
# No claude CLI in PATH — fail open, hook does not block Claude Code itself
exit 0
fi
# === BUILD COMBINED PROMPT (lateral-thinker system prompt + user prompt) ===
# We inline the agent's system-prompt text because `claude -p` headless mode
# takes a single prompt string; there is no --agent flag.
PROMPT_FILE="$(mktemp -t vestige-lateral.XXXXXX)"
trap 'rm -f "$PROMPT_FILE"' EXIT
cat > "$PROMPT_FILE" <<'LATERAL_SYSTEM_EOF'
You are the Lateral Thinker, a subconscious subagent in the Vestige OS.
Your only job: surface a cross-disciplinary structural parallel from the
user's Vestige memory graph that the main agent would not otherwise see.
Execution protocol (do all steps silently, no narration):
1. Read the user prompt below the separator.
2. Extract the core structural pattern (race condition / state sync /
retry loop / memory leak / schema migration / decoding ambiguity /
rate limit / ordering guarantee / cache invalidation / etc).
3. Call mcp__vestige__explore_connections with action="bridges" OR
mcp__vestige__search to find memories in a COMPLETELY UNRELATED
domain that share the same structural pattern. Prefer bridges between
distant clusters (e.g., React UI state <-> Rust async channel;
Python DB lock <-> Git merge conflict).
4. If you find a high-confidence mechanical parallel, output EXACTLY this
XML structure (nothing else, no preamble, no explanation):
<lateral_epiphany>
<structural_pattern>one short noun phrase naming the shared pattern</structural_pattern>
<source_domain>where the user currently is</source_domain>
<bridge_domain>the unrelated domain where the pattern also lives</bridge_domain>
<memory_id>the Vestige node ID of the cross-domain memory, if applicable</memory_id>
<insight>one sentence explaining how the unrelated memory informs the current problem mechanically, not metaphorically</insight>
</lateral_epiphany>
5. If you cannot find a confident, mechanical, distinct bridge in under
three tool calls, output EXACTLY the single word: EMPTY
Do not apologize, do not explain, do not converse.
---
USER PROMPT:
LATERAL_SYSTEM_EOF
printf '%s\n' "$PROMPT" >> "$PROMPT_FILE"
# === SPAWN LATERAL THINKER (background with timeout) ===
# Set VESTIGE_SWARM_ACTIVE=1 so the subagent's own UserPromptSubmit sees
# the re-entrancy guard and exits early. --permission-mode bypassPermissions
# skips interactive prompts inside the subagent run (standard for headless).
OUTPUT_FILE="$(mktemp -t vestige-lateral-out.XXXXXX)"
trap 'rm -f "$PROMPT_FILE" "$OUTPUT_FILE"' EXIT
(
VESTIGE_SWARM_ACTIVE=1 \
"$CLAUDE_BIN" \
-p "$(cat "$PROMPT_FILE")" \
--model claude-haiku-4-5-20251001 \
--allowed-tools "mcp__vestige__search,mcp__vestige__explore_connections,mcp__vestige__memory" \
< /dev/null \
> "$OUTPUT_FILE" 2>/dev/null
) &
CLAUDE_PID=$!
# === TIMEOUT GUARD (40 seconds) ===
# Real `claude -p` with Haiku 4.5 + MCP explore_connections/search tool calls
# needs ~30-35s wall-clock for a full bridge search on a complex prompt.
# Measured empirically 2026-04-20: 8s was killed every time, 25s was killed
# every time for decision-adjacent prompts. Matches Sanhedrin's 33s budget
# with 7s headroom for slow MCP round-trips. Pair with a 45s timeout in
# settings.json so Claude Code doesn't kill us first.
WAITED=0
while [ "$WAITED" -lt 40 ]; do
if ! /bin/kill -0 "$CLAUDE_PID" 2>/dev/null; then
break
fi
sleep 1
WAITED=$((WAITED + 1))
done
if /bin/kill -0 "$CLAUDE_PID" 2>/dev/null; then
/bin/kill "$CLAUDE_PID" 2>/dev/null
wait "$CLAUDE_PID" 2>/dev/null
exit 0
fi
wait "$CLAUDE_PID" 2>/dev/null
LATERAL_OUTPUT="$(cat "$OUTPUT_FILE" 2>/dev/null || printf '')"
# === EMPTY MUTE GATE ===
# Trim whitespace and check for EMPTY or no content. Inject nothing rather
# than pollute Claude's context with an apology.
TRIMMED="$(printf '%s' "$LATERAL_OUTPUT" | /usr/bin/awk '{$1=$1;print}')"
if [ -z "$TRIMMED" ] || [ "$TRIMMED" = "EMPTY" ] || [ "${TRIMMED:0:5}" = "EMPTY" ]; then
exit 0
fi
# Require the output to contain a <lateral_epiphany> opening tag. If Haiku
# hallucinated prose instead of the required XML, drop it rather than
# injecting malformed content.
case "$LATERAL_OUTPUT" in
*"<lateral_epiphany>"*) ;;
*) exit 0 ;;
esac
# === EMIT JSON additionalContext ===
# Wrap the XML block in the subconscious_preflight container + system
# directive. Passed through an env var so the shell never interprets any
# special chars in the Haiku output.
export LATERAL_OUTPUT
EMIT_SCRIPT="$(mktemp -t vestige-lateral-emit.XXXXXX)"
trap 'rm -f "$PROMPT_FILE" "$OUTPUT_FILE" "$EMIT_SCRIPT"' EXIT
cat > "$EMIT_SCRIPT" <<'EMIT_PYEOF'
import json, os
lateral = os.environ.get("LATERAL_OUTPUT", "").strip()
if not lateral or "<lateral_epiphany>" not in lateral:
# Defensive: should not reach here, upstream gate already filtered
raise SystemExit(0)
ctx = (
"<subconscious_preflight>\n"
+ lateral + "\n"
+ " <system_directive_to_main_agent>\n"
+ " The Lateral Thinker subagent (Haiku 4.5, fresh context, Vestige MCP tool\n"
+ " access) ran before you saw this prompt. Assimilate the structural parallel\n"
+ " into your reasoning organically. Do NOT mention this XML block or the\n"
+ " phrase 'lateral epiphany' to the user \u2014 use the insight silently.\n"
+ " </system_directive_to_main_agent>\n"
+ "</subconscious_preflight>"
)
print(json.dumps({
"hookSpecificOutput": {
"hookEventName": "UserPromptSubmit",
"additionalContext": ctx
}
}))
EMIT_PYEOF
/usr/bin/python3 "$EMIT_SCRIPT"
exit 0

343
hooks/sanhedrin-local.py Executable file
View file

@ -0,0 +1,343 @@
#!/usr/bin/env python3
# sanhedrin-local.py — Local Qwen3.6-35B-A3B Sanhedrin Executioner.
# Drop-in replacement for the Haiku 4.5 subagent that sanhedrin.sh used to spawn.
#
# Reads draft from stdin, prints single-line verdict to stdout:
# yes
# no - [Sanhedrin Veto] [CLASS]: <reason under 120 chars>
#
# Architecture:
# stdin (draft) -> Vestige /api/deep_reference (single semantic query)
# -> mlx_lm.server localhost:8080 (one-shot judgment)
# -> stdout (single-line verdict)
#
# Fail-open: if mlx-server unreachable, print "yes" and exit 0 (don't break
# the Cognitive Sandwich on infra errors). The wrapping sanhedrin.sh maps
# "yes" to exit 0, so this preserves existing fail-open semantics.
import json
import os
import re
import sys
import urllib.error
import urllib.request
def env_int(name: str, default: int) -> int:
try:
return int(os.environ.get(name, "") or default)
except ValueError:
return default
DASHBOARD_PORT = os.environ.get("VESTIGE_DASHBOARD_PORT") or "3927"
VESTIGE_BASE_URL = (
os.environ.get("VESTIGE_BASE_URL") or f"http://127.0.0.1:{DASHBOARD_PORT}"
).rstrip("/")
MLX_ENDPOINT = os.environ.get("MLX_ENDPOINT") or "http://127.0.0.1:8080/v1/chat/completions"
VESTIGE_ENDPOINT = (
os.environ.get("VESTIGE_DEEP_REFERENCE_ENDPOINT")
or f"{VESTIGE_BASE_URL}/api/deep_reference"
)
VESTIGE_HEALTH = (
os.environ.get("VESTIGE_HEALTH_ENDPOINT") or f"{VESTIGE_BASE_URL}/api/health"
)
MODEL = os.environ.get("VESTIGE_SANDWICH_MODEL") or "mlx-community/Qwen3.6-35B-A3B-4bit"
MLX_TIMEOUT = env_int("MLX_TIMEOUT", 45)
VESTIGE_TIMEOUT = env_int("VESTIGE_TIMEOUT", 5)
THINK_RE = re.compile(r"<think>.*?</think>", re.DOTALL | re.IGNORECASE)
def post_json(url: str, body: dict, timeout: int):
data = json.dumps(body).encode("utf-8")
req = urllib.request.Request(
url, data=data, headers={"Content-Type": "application/json"}
)
try:
with urllib.request.urlopen(req, timeout=timeout) as r:
return json.loads(r.read())
except (urllib.error.URLError, urllib.error.HTTPError, TimeoutError, OSError):
return None
TRUST_FLOOR = 0.55 # filter out low-trust memories that drive false-positive vetoes
def fetch_evidence(draft: str) -> tuple[str, int]:
"""Single deep_reference call — returns (formatted evidence, count of high-trust memories).
Only memories with trust >= TRUST_FLOOR are surfaced. If none qualify, returns ("", 0)
and the caller should auto-pass without invoking the model.
"""
try:
with urllib.request.urlopen(VESTIGE_HEALTH, timeout=VESTIGE_TIMEOUT) as r:
r.read()
except Exception:
return "", 0
query = draft[:1500]
resp = post_json(VESTIGE_ENDPOINT, {"query": query, "depth": 12}, VESTIGE_TIMEOUT)
if not isinstance(resp, dict):
return "", 0
parts = []
high_trust_count = 0
confidence = resp.get("confidence", 0)
rec = resp.get("recommended") or {}
rec_trust = float(rec.get("trust_score", 0) or 0)
if rec and rec_trust >= TRUST_FLOOR:
rid = (rec.get("memory_id") or rec.get("id") or "")[:8]
date = (rec.get("date") or "")[:10]
prev = (rec.get("answer_preview") or rec.get("preview") or "")[:500]
parts.append(f"RECOMMENDED [{rid}] trust={rec_trust:.2f} date={date}:\n{prev}")
high_trust_count += 1
contradictions = resp.get("contradictions") or []
if contradictions:
parts.append(f"\nCONTRADICTIONS DETECTED: {len(contradictions)} pair(s)")
for c in contradictions[:3]:
parts.append(f" - {json.dumps(c)[:200]}")
superseded = resp.get("superseded") or []
if superseded:
ht_super = [s for s in superseded if float(s.get("trust", 0) or 0) >= TRUST_FLOOR]
if ht_super:
parts.append(f"\nSUPERSEDED MEMORIES (trust>={TRUST_FLOOR}): {len(ht_super)}")
for s in ht_super[:3]:
sid = (s.get("id") or "")[:8]
parts.append(f" - [{sid}] {(s.get('preview') or '')[:200]}")
evidence = resp.get("evidence") or []
high_trust_evidence = [ev for ev in evidence if float(ev.get("trust", 0) or 0) >= TRUST_FLOOR]
if high_trust_evidence:
parts.append(f"\nHIGH-TRUST EVIDENCE (trust>={TRUST_FLOOR}, {min(len(high_trust_evidence), 5)} of {len(evidence)} total):")
for ev in high_trust_evidence[:5]:
eid = (ev.get("id") or "")[:8]
role = ev.get("role", "?")
trust = float(ev.get("trust", 0) or 0)
prev = (ev.get("preview") or "").strip()[:300]
parts.append(f" [{eid}] role={role} trust={trust:.2f}\n {prev}")
high_trust_count += 1
if high_trust_count == 0:
return "", 0
header = f"VESTIGE CONFIDENCE: {int(confidence * 100)}% | HIGH-TRUST MEMORIES: {high_trust_count}\n\n"
return header + "\n".join(parts), high_trust_count
SYSTEM_PROMPT = """You are the Sanhedrin Executioner. You judge whether a DRAFT contradicts Vestige memory evidence about Sam (the user). ONE LINE OF OUTPUT.
VALID CLASS TAGS (closed set pick exactly one):
TECHNICAL | ACHIEVEMENT | FINANCIAL | BIOGRAPHICAL | TIMELINE | ATTRIBUTION | VAGUE-QUANTIFIER | UNVERIFIED-POSITIVE
DEFAULT POSTURE
- DEFAULT to `yes` (PASS) for TECHNICAL / TIMELINE / EXISTENTIAL claims unless you can cite a same-subject direct contradiction.
- DEFAULT to `no` (VETO, fail-closed) for these specific Sam-about claims when high-trust evidence is silent on the named entity:
* Specific institution / employer / school / company Sam is claimed to be at
* Specific dollar amount won / earned / raised
* Specific competition placement / score / prize received
* Specific date Sam did something specific (graduated, was hired, was born)
* Vague-quantifier positive about Sam ("a few wins", "some prize money", "most submissions placed top 10", "many customers", "several deals")
THREE FALSE-POSITIVE PROTECTIONS (these output `yes`)
1. SUBJECT-EQUALITY GATE: only same-subject claims are veto candidates. Memory about Vestige's internal codebase ≠ contradiction with external tools (Qwen, MCP-protocol-spec, MLX, Cursor). Memory about project X ≠ contradiction with project Y.
2. VERSION-DISCRIMINATOR RULE: version/generation tokens (M1/M2/M3/M4/M5, v0.5/v1.0, GPT-4/GPT-5, Qwen3.5/Qwen3.6) are subject discriminators. Different versions = different subjects = no contradiction by default.
3. AGREEMENT-IS-NOT-CONTRADICTION: if the memory preview AGREES with the draft claim, that's PASS not VETO.
INFERENCE BAN
- DO NOT use "implies", "implying", "suggests", "must mean", "would mean", "indicates", "therefore" in veto reasons.
- If you have to chain inferences from a memory to reach a contradiction, PASS.
- TIMELINE vetoes specifically: require an EXPLICIT date or duration in the cited memory that arithmetically excludes the draft's date. Vague phrases like "until I graduate" cannot ground a TIMELINE veto.
ARCHITECTURE-VS-COMPONENT RULE
- A memory describing OVERALL architecture (Thalamus+Sanhedrin triad, 4-layer biology) does NOT contradict a draft about an INTERNAL COMPONENT (subagent model, sidecar transport, bridge script). Different layers of the same stack are not contradictions.
OUTPUT FORMAT (exactly one line, no preamble, no explanation, no markdown)
- PASS: yes
- VETO: no - [Sanhedrin Veto] [CLASS]: <reason under 140 chars, cite memory id verbatim from evidence>
EIGHT WORKED EXAMPLES STUDY THESE PATTERNS
[VETO same-subject TECHNICAL contradiction]
Evidence: "Vestige is a 2-crate Rust workspace (vestige-core + vestige-mcp)" trust=0.62 [de43be5a]
Draft: "Edit the FastAPI router in vestige/main.py for Python extensions to Vestige"
Output: no - [Sanhedrin Veto] TECHNICAL: Draft says FastAPI/Python for Vestige, memory de43be5a says 2-crate Rust workspace.
[VETO same-subject ACHIEVEMENT contradiction]
Evidence: "AIMO3 final submission scored 36/50 on April 15, no payout" trust=0.71 [9cf2a764]
Draft: "Sam won AIMO3 with a perfect 50/50 and took the $25K grand prize"
Output: no - [Sanhedrin Veto] ACHIEVEMENT: Draft claims 50/50 win + $25K, memory 9cf2a764 shows 36/50 final, no payout.
[VETO VAGUE-QUANTIFIER fail-closed]
Evidence: high-trust memories about Sam's competition history, none enumerate any wins
Draft: "Sam won a few Kaggle competitions and earned some prize money"
Output: no - [Sanhedrin Veto] VAGUE-QUANTIFIER: Draft says "a few wins / some prize money", evidence enumerates zero wins, fail-closed.
[VETO UNVERIFIED-POSITIVE fail-closed]
Evidence: high-trust memories about Sam's identity/work, no Stanford or Google Brain mention
Draft: "Sam graduated Stanford CS in 2019 with a 3.94 GPA and worked at Google Brain"
Output: no - [Sanhedrin Veto] UNVERIFIED-POSITIVE: Specific Stanford/2019/Google Brain claims, evidence silent on all, fail-closed.
[PASS SUBJECT-EQUALITY gate (external tool, not Vestige)]
Evidence: "Vestige is a 2-crate Rust workspace" trust=0.62
Draft: "Switched the Sanhedrin executioner to local Qwen3.6-35B-A3B via mlx_lm.server"
Output: yes
[PASS VERSION-DISCRIMINATOR rule]
Evidence: "M5 Max ~900 GB/s bandwidth (planned hardware)" trust=0.62
Draft: "Memory bandwidth on the M3 Max is around 400 GB/s for the unified architecture"
Output: yes
[PASS AGREEMENT-IS-NOT-CONTRADICTION]
Evidence: "Sam's M3 Max MacBook Pro arrived 2026-04-20" trust=0.55
Draft: "Sam's MacBook is an M3 Max"
Output: yes
[PASS ARCHITECTURE-VS-COMPONENT]
Evidence: "Cognitive Sandwich = Thalamus preflight triad + Sanhedrin Stop council shipped 2026-04-20" trust=0.7
Draft: "Cognitive Sandwich's Sanhedrin originally used a Haiku 4.5 subagent for the Executioner role"
Output: yes
[PASS AUXILIARY-SCRIPT consumer-vs-consumed]
Evidence: "Vestige is a 2-crate Rust workspace" trust=0.62
Draft: "I added a Python script (sanhedrin-local.py) at ~/.claude/hooks/ that calls Vestige's HTTP API for evidence fetch."
Reason: external script that CALLS Vestige is not the same subject as Vestige's internal implementation. The consumer is not the consumed.
Output: yes
[PASS HYPOTHETICAL-MOOD]
Evidence: "AIMO3 final 36/50 no payout" trust=0.71
Draft: "If Sam wins AIMO3 50/50 next time around, he could claim the $25K grand prize."
Reason: prefix `if`/`suppose`/`imagine`/`hypothetically`/`would`/`could`/`assume` marks the embedded claim as conditional, NOT asserted. Conditional claims about future or counterfactual states do not contradict factual memory.
Output: yes
HYPOTHETICAL-MOOD RULE: if a draft sentence is governed by `if`, `suppose`, `imagine`, `hypothetically`, `would`, `could`, `assume`, `what if`, the embedded claim is NOT being asserted as fact PASS that claim regardless of memory state.
ARCHIVED-COMPETITION RULE: do NOT fail-closed on the EXISTENCE of a past competition or project just because evidence is silent on it. Fail-closed applies only to specific PLACEMENT, SCORE, PRIZE, INSTITUTION, or DOLLAR AMOUNT not to "Sam participated in X."
MULTI-CLAIM SEVERITY ORDERING: if multiple claims are vetoable, choose ACHIEVEMENT/FINANCIAL/BIOGRAPHICAL/UNVERIFIED-POSITIVE over TECHNICAL. Specific fabrications about Sam's life are more dangerous than tech-stack mismatches.
When in doubt on TECHNICAL/TIMELINE: PASS. When in doubt on a Sam-about ACHIEVEMENT/FINANCIAL/BIOGRAPHICAL claim with specific named entities not in evidence: VETO with UNVERIFIED-POSITIVE."""
VALID_CLASSES = {
"TECHNICAL", "ACHIEVEMENT", "FINANCIAL", "BIOGRAPHICAL",
"TIMELINE", "ATTRIBUTION", "VAGUE-QUANTIFIER", "UNVERIFIED-POSITIVE",
}
INFERENCE_VERBS = (
"implies", "implying", "suggests", "must mean", "would mean",
"indicates that", "therefore the", "this means",
)
VERDICT_RE = re.compile(
r"^no - \[Sanhedrin Veto\] ([A-Z][A-Z\-]*): (.{1,180})$"
)
def validate_verdict(verdict: str) -> str:
"""Post-validate the model's verdict. Fail-open ('yes') on any malformation:
- Length over 220 chars
- Veto with class tag not in the closed set
- Veto reason containing inference verbs
- Veto not matching the canonical regex
"""
v = verdict.strip()
if not v:
return "yes"
low = v.lower()
if low == "yes" or low.startswith("yes "):
return "yes"
if not low.startswith("no"):
return "yes"
if len(v) > 220:
return "yes" # runaway reasoning blob
m = VERDICT_RE.match(v)
if not m:
return "yes" # format break
cls = m.group(1)
reason = m.group(2)
if cls not in VALID_CLASSES:
return "yes" # invented class tag
reason_low = reason.lower()
for verb in INFERENCE_VERBS:
if verb in reason_low:
return "yes" # inference-chain veto, downgrade per ban
return v
def judge(draft: str, evidence: str) -> str:
user_msg = (
f"VESTIGE EVIDENCE (recommended + top trust-scored memories):\n"
f"{evidence if evidence else '(no relevant evidence retrieved)'}\n\n"
f"---\nDRAFT TO JUDGE:\n{draft}"
)
body = {
"model": MODEL,
"messages": [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": user_msg},
],
"max_tokens": 2500,
"temperature": 0.0,
"top_p": 1.0,
"top_k": 1,
"seed": 42,
"stream": False,
"chat_template_kwargs": {"enable_thinking": False},
"stop": [
"\n\nWait,", "\n\nActually,", "\n\nLet me", "\n\nHmm,",
"\n\nOn second thought", "\n\nOh wait",
],
}
resp = post_json(MLX_ENDPOINT, body, MLX_TIMEOUT)
if not isinstance(resp, dict):
return ""
try:
msg = resp["choices"][0]["message"]
raw = msg.get("content") or ""
if not raw.strip():
raw = msg.get("reasoning") or ""
except (KeyError, IndexError, TypeError):
return ""
cleaned = THINK_RE.sub("", raw).strip()
lines = [ln.strip() for ln in cleaned.splitlines() if ln.strip()]
if not lines:
return ""
last = lines[-1]
low = last.lower()
if low.startswith("yes") or low.startswith("no"):
return validate_verdict(last)
for ln in reversed(lines):
l = ln.lower()
if l.startswith("yes") or l.startswith("no"):
return validate_verdict(ln)
return ""
def main() -> None:
draft = sys.stdin.read().strip()
if not draft:
print("yes")
return
evidence, high_trust_count = fetch_evidence(draft)
# Auto-pass if no high-trust evidence — model can't legitimately veto
# without something concrete to cite. Eliminates the common false-positive
# mode where the model invents a contradiction from low-trust noise.
if high_trust_count == 0:
print("yes")
return
verdict = judge(draft, evidence)
if not verdict:
# Fail-open: server unreachable, malformed response, etc.
print("yes")
return
print(verdict)
if __name__ == "__main__":
main()

210
hooks/sanhedrin.sh Executable file
View file

@ -0,0 +1,210 @@
#!/bin/bash
# sanhedrin.sh — Stop hook (Post-Cognitive Sanhedrin / Full Agent-Type Guillotine)
#
# Spawns the Executioner subagent (Haiku 4.5, fresh context, Vestige MCP
# tools) to run mcp__vestige__deep_reference 8-stage contradiction analysis
# on the last assistant draft. If any technical claim contradicts a
# high-trust memory, exit 2 with the veto reason — forces Main Claude to
# rewrite.
#
# Runs AFTER veto-detector.sh (fast regex against veto-tagged memories).
# Sanhedrin is the deeper semantic check: it reads the draft as a real
# reasoning agent, extracts claims, runs deep_reference on each.
#
# Architecture:
# Main Claude finishes draft → Stop hook chain fires →
# veto-detector.sh (50ms regex, may block) →
# sanhedrin.sh (2-8s Haiku subagent, may block) →
# synthesis-stop-validator.sh (existing regex hedge check, may block)
#
# Opt-in: set VESTIGE_SANHEDRIN_ENABLED=1 in parent shell.
# Re-entrancy lock: VESTIGE_EXECUTIONER_ACTIVE=1 inside the subagent.
#
# Ship date 2026-04-20.
set -u
# === OPT-OUT GATE ===
# Post-Cognitive Sanhedrin is ON by default as of 2026-04-21 (birthday
# launch day). To disable, set VESTIGE_SANHEDRIN_ENABLED=0 in your
# environment. Default-on guarantees the Cognitive Sandwich fires on
# fresh machines, Docker containers, GUI-launched Claude Code, and
# shells without .zshrc — any case where the Claude Code process lacks
# a sourced profile. The re-entrancy guard (VESTIGE_EXECUTIONER_ACTIVE)
# below still prevents fork-bombs from the subagent's own Stop hook.
if [ "${VESTIGE_SANHEDRIN_ENABLED:-1}" = "0" ]; then
exit 0
fi
# === RE-ENTRANCY GUARD ===
# The Executioner's own Stop hook will fire when it returns — prevent
# recursive spawns that would fork-bomb the quota.
if [ "${VESTIGE_EXECUTIONER_ACTIVE:-0}" = "1" ]; then
exit 0
fi
# === READ STOP HOOK INPUT ===
INPUT="$(cat)"
TRANSCRIPT_PATH="$(printf '%s' "$INPUT" | /usr/bin/python3 -c 'import sys,json;d=json.load(sys.stdin);print(d.get("transcript_path",""))' 2>/dev/null || printf '')"
if [ -z "$TRANSCRIPT_PATH" ] || [ ! -f "$TRANSCRIPT_PATH" ]; then
exit 0
fi
# === EXTRACT LAST ASSISTANT DRAFT ===
# Read the transcript JSONL, pull the last assistant message text.
export TRANSCRIPT_PATH
DRAFT_SCRIPT="$(mktemp -t vestige-sanhedrin-draft.XXXXXX)"
trap 'rm -f "$DRAFT_SCRIPT"' EXIT
cat > "$DRAFT_SCRIPT" <<'DRAFT_PYEOF'
import json, os, sys
transcript = os.environ.get("TRANSCRIPT_PATH", "")
last_assistant = ""
try:
with open(transcript) as f:
for line in f:
line = line.strip()
if not line:
continue
try:
obj = json.loads(line)
except Exception:
continue
role = obj.get("role") or obj.get("type", "")
content = obj.get("message", {}).get("content", obj.get("content", ""))
text = ""
if isinstance(content, list):
for block in content:
if isinstance(block, dict) and block.get("type") == "text":
text += block.get("text", "") + "\n"
elif isinstance(content, str):
text = content
if role == "assistant":
last_assistant = text
except Exception:
sys.exit(0)
# Print nothing if no draft or draft too short to contain a technical claim
stripped = last_assistant.strip()
if not stripped or len(stripped) < 100:
sys.exit(0)
# Gate: only check drafts that contain technical indicators
has_code = "`" in stripped or "```" in stripped
has_cmd = any(kw in stripped.lower() for kw in ["install", "run ", "use ", "call ", "invoke", "execute"])
has_path = "/" in stripped and any(ext in stripped for ext in [".rs", ".ts", ".py", ".sh", ".md", ".json"])
if not (has_code or has_cmd or has_path):
sys.exit(0)
# Truncate to 4000 chars to keep Haiku prompt bounded
if len(stripped) > 4000:
stripped = stripped[:4000] + "... [truncated]"
print(stripped)
DRAFT_PYEOF
DRAFT="$(/usr/bin/python3 "$DRAFT_SCRIPT" 2>/dev/null || printf '')"
if [ -z "$DRAFT" ]; then
exit 0
fi
# === VERIFY local executioner bridge available ===
# 2026-04-25: switched from Haiku 4.5 subagent to local Qwen3.6-35B-A3B
# via mlx_lm.server (launchd com.vestige.mlx-server). Bridge script
# fetches Vestige evidence via HTTP API (VESTIGE_DASHBOARD_PORT, default 3927)
# then judges via MLX_ENDPOINT (default port 8080). Zero per-token cost, fully offline,
# sub-second-to-15s verdict latency. Fail-open if mlx-server unreachable.
BRIDGE="$HOME/.claude/hooks/sanhedrin-local.py"
if [ ! -x "$BRIDGE" ] && [ ! -f "$BRIDGE" ]; then
exit 0
fi
# === SPAWN LOCAL EXECUTIONER (background with timeout) ===
OUTPUT_FILE="$(mktemp -t vestige-sanhedrin-out.XXXXXX)"
trap 'rm -f "$DRAFT_SCRIPT" "$OUTPUT_FILE"' EXIT
(
printf '%s\n' "$DRAFT" | /usr/bin/python3 "$BRIDGE" > "$OUTPUT_FILE" 2>/dev/null
) &
EXEC_PID=$!
# === TIMEOUT GUARD (60 seconds) ===
# Local Qwen3.6-35B-A3B on M5/M3 Max typically returns in 5-15s for the
# single-shot judgment. 60s ceiling preserves the existing settings.json
# Stop hook timeout (70s) and gives headroom for cold model load if
# launchd just restarted. Bridge fail-opens internally if mlx-server is
# unreachable, so timeout-kill here is the secondary safety net.
WAITED=0
while [ "$WAITED" -lt 60 ]; do
if ! /bin/kill -0 "$EXEC_PID" 2>/dev/null; then
break
fi
sleep 1
WAITED=$((WAITED + 1))
done
if /bin/kill -0 "$EXEC_PID" 2>/dev/null; then
/bin/kill "$EXEC_PID" 2>/dev/null
wait "$EXEC_PID" 2>/dev/null
exit 0
fi
wait "$EXEC_PID" 2>/dev/null
EXECUTIONER_OUTPUT="$(cat "$OUTPUT_FILE" 2>/dev/null || printf '')"
# === PARSE VERDICT ===
TRIMMED="$(printf '%s' "$EXECUTIONER_OUTPUT" | /usr/bin/awk 'NF {print; exit}' | /usr/bin/awk '{$1=$1;print}')"
if [ -z "$TRIMMED" ]; then
exit 0
fi
# "yes" verdict - draft is clean, allow stop
case "$TRIMMED" in
yes|YES|Yes|yes.|Yes.)
exit 0
;;
esac
# "no - <reason>" or "no: <reason>" verdict - block the stop, force rewrite
# Documented spec is `no - [Sanhedrin Veto] [CLASS]: <reason>` (hyphen-space).
# Legacy `no: <reason>` also accepted for backward compat.
case "$TRIMMED" in
no\ -*|NO\ -*|No\ -*|no:*|NO:*|No:*)
case "$TRIMMED" in
no\ -*|NO\ -*|No\ -*)
REASON="${TRIMMED#* - }"
;;
*)
REASON="${TRIMMED#*:}"
;;
esac
REASON="$(printf '%s' "$REASON" | /usr/bin/awk '{$1=$1;print}')"
cat >&2 <<SANHEDRIN_MSG
[SANHEDRIN VETO - Post-Cognitive Executioner (LOCAL) rejected draft]
$REASON
The Executioner (local Qwen3.6-35B-A3B via mlx_lm.server, fresh context,
fed Vestige deep_reference evidence over HTTP) judged your draft and
found a contradiction against a high-trust memory.
You may NOT stop. Rewrite WITHOUT the contradicted claim. Use
mcp__vestige__deep_reference to inspect the cited memory and cite the
correct replacement pattern from its \`recommended\` field.
Local-only, zero API cost, fully offline. Bridge script:
~/.claude/hooks/sanhedrin-local.py
SANHEDRIN_MSG
exit 2
;;
esac
# Unparseable verdict — fail open (do not block on Executioner errors)
exit 0

View file

@ -0,0 +1,23 @@
{
"hooks": {
"UserPromptSubmit": [
{
"hooks": [
{ "type": "command", "command": "$HOME/.claude/hooks/synthesis-preflight.sh", "timeout": 8 },
{ "type": "command", "command": "$HOME/.claude/hooks/cwd-state-injector.sh", "timeout": 8 },
{ "type": "command", "command": "$HOME/.claude/hooks/vestige-pulse-daemon.sh", "timeout": 6 },
{ "type": "command", "command": "$HOME/.claude/hooks/preflight-swarm.sh", "timeout": 45 }
]
}
],
"Stop": [
{
"hooks": [
{ "type": "command", "command": "$HOME/.claude/hooks/veto-detector.sh", "timeout": 6 },
{ "type": "command", "command": "$HOME/.claude/hooks/sanhedrin.sh", "timeout": 70 },
{ "type": "command", "command": "$HOME/.claude/hooks/synthesis-stop-validator.sh", "timeout": 6 }
]
}
]
}
}

46
hooks/synthesis-gate.sh Executable file
View file

@ -0,0 +1,46 @@
#!/bin/bash
# synthesis-gate.sh — UserPromptSubmit hook
#
# FIXES GAP 1: "forces me to run 2-5 Vestige queries before answering"
# FIXES GAP 4 (partial): injects mandate to detect never-composed combinations
#
# Mechanism: reads the user's prompt from stdin JSON, classifies decision-adjacency
# via regex, and if the prompt is decision-adjacent, returns JSON with
# hookSpecificOutput.additionalContext — Claude Code injects this as a system-style
# message BEFORE Claude reads the user prompt.
#
# Origin: AIMO3 36/50 failure on April 14-15, 2026. Claude retrieved memories but
# summarized them instead of composing. See ~/.claude/rules/active-synthesis.md
set -euo pipefail
INPUT="$(cat)"
PROMPT="$(printf '%s' "$INPUT" | /usr/bin/python3 -c 'import sys,json;d=json.load(sys.stdin);print(d.get("prompt","") or d.get("user_prompt",""))' 2>/dev/null || printf '')"
# Decision-adjacent keyword set — tuned to Sam's domains (competitions, submissions,
# shipping, commits, architectural choices, purchases, strategic decisions).
DECISION_REGEX='(submit|submission|aimo|nemotron|gemma|kaggle|final|ship|launch|deploy|commit|decide|decision|recommend|should i|what should|purchase|buy|invest|architect|architecture|strategy|prep|prioriti|compose|tradeoff|trade-off|config|which (should|model|approach|one)|pick|choose|go big|go with|audition|perform)'
if printf '%s' "$PROMPT" | /usr/bin/grep -qiE "$DECISION_REGEX"; then
/usr/bin/python3 <<'PYEOF'
import json
msg = (
"[SYNTHESIS GATE — DECISION-ADJACENT PROMPT DETECTED]\n\n"
"This prompt matched decision keywords. Before you respond, you MUST execute the active synthesis protocol:\n\n"
"1. Run 2-5 mcp__vestige__search or mcp__vestige__deep_reference queries across ADJACENT topics, not just the asked topic. Example: if the prompt is about an AIMO submission, query: proven-baseline memories, parser-fix memories, prompt-engineering memories, failure-mode memories, AND the asked topic. Minimum 4 parallel queries.\n\n"
"2. Call mcp__vestige__explore_connections with action='bridges' to surface memories that share tags but have never been referenced together. Flag never-composed combinations EXPLICITLY in your response.\n\n"
"3. Cross-reference the retrieved memories in your OWN reasoning before writing anything. Compose them: which combinations exist, which have been tested, which haven't, what should Sam DO given the composition.\n\n"
"4. Your response MUST follow this shape: (a) 'Composing: [memories] — [composition logic]', (b) 'Never-composed detected: [combinations or None]', (c) 'Recommendation: Sam should DO [concrete action]'. No summary-lists of memory contents.\n\n"
"5. Forbidden output pattern: 'Memory A says X. Memory B says Y. Memory C says Z.' followed by vague synthesis. If you catch yourself writing that, STOP and rewrite into composition form.\n\n"
"6. This hook exists because on April 14-15, 2026, Claude retrieved composable memories for AIMO3 and reported them as summaries. Cost: 36/50 instead of 42-44+. Do not repeat this failure mode."
)
print(json.dumps({
"hookSpecificOutput": {
"hookEventName": "UserPromptSubmit",
"additionalContext": msg
}
}))
PYEOF
fi
exit 0

174
hooks/synthesis-preflight.sh Executable file
View file

@ -0,0 +1,174 @@
#!/bin/bash
# synthesis-preflight.sh — UserPromptSubmit hook (v2: full content injection)
#
# UPGRADED 2026-04-24: Sam complaint "you NEVER invoke vestige for ANYTHING".
# Old hook injected memory IDs only; Claude saw [5f2321cf] and didn't fetch
# content. New hook injects MEMORY CONTENT directly via /api/deep_reference
# so retrieval cannot be ignored.
#
# On every UserPromptSubmit:
# 1. Read JSON stdin, extract user prompt
# 2. Decision-keyword gate (preserved from v1)
# 3. POST the prompt to vestige-mcp /api/deep_reference (single call)
# — returns recommended memory + reasoning chain + trust-scored evidence
# 4. Inject reasoning + recommended preview + top 3 evidence previews as
# additionalContext, with explicit "DO NOT IGNORE" framing
#
# Fails open: if vestige-mcp is not running or HTTP request fails, hook
# emits empty context and exit 0. Prompt still proceeds. Never blocks.
#
# Endpoint: POST http://127.0.0.1:3927/api/deep_reference
# body: {"query": "<prompt>", "depth": 15}
# resp: {confidence, evidence:[{id, preview, role, trust, date}], reasoning, recommended}
set -u
INPUT="$(cat)"
# Extract prompt from JSON stdin. Fall back to empty if parse fails.
PROMPT="$(printf '%s' "$INPUT" | /usr/bin/python3 -c 'import sys,json;d=json.load(sys.stdin);print(d.get("prompt",""))' 2>/dev/null || printf '')"
if [ -z "$PROMPT" ]; then
exit 0
fi
# Decision-keyword gate (preserved from v1). Mirrors synthesis-gate.sh.
DECISION_GATE_RE='submit|submission|aimo|nemotron|gemma|kaggle|orbit|final|ship|launch|deploy|commit|decide|decision|recommend|should i|should we|what should|purchase|buy|invest|architect|architecture|strategy|prep|prioriti|compose|tradeoff|trade-off|config|which|pick|choose|audition|dimension|mays|pitch|forecast|target|plan|roadmap|v2\.|v3\.|scale|grow|growth|distrib|brand|position|moat|vs\.|vs\b|instead of'
if ! printf '%s' "$PROMPT" | LC_ALL=C /usr/bin/grep -iqE "$DECISION_GATE_RE"; then
exit 0
fi
PORT="${VESTIGE_DASHBOARD_PORT:-3927}"
BASE="http://127.0.0.1:${PORT}"
# Probe dashboard. Fail open if unreachable.
if ! /usr/bin/curl -fsS -m 0.5 "${BASE}/api/health" > /dev/null 2>&1; then
exit 0
fi
# Build the deep_reference POST body via python3 (avoids shell-escape issues
# with arbitrary prompt characters).
BODY_SCRIPT="$(mktemp -t vestige-preflight-body.XXXXXX)"
trap 'rm -f "$BODY_SCRIPT"' EXIT
cat > "$BODY_SCRIPT" <<'BODY_PYEOF'
import json, os, sys
prompt = os.environ.get("VPRE_PROMPT", "")
# Truncate very long prompts so the deep_reference embedding stays focused.
# 1500 chars is enough signal for hybrid+semantic retrieval without diluting.
if len(prompt) > 1500:
prompt = prompt[:1500]
print(json.dumps({"query": prompt, "depth": 15}))
BODY_PYEOF
export VPRE_PROMPT="$PROMPT"
DR_BODY="$(/usr/bin/python3 "$BODY_SCRIPT")"
# Single POST to deep_reference. Timeout 5s — deep_reference takes ~1-3s.
DR_RESP="$(/usr/bin/curl -fsS -m 5 -X POST "${BASE}/api/deep_reference" \
-H 'Content-Type: application/json' \
-d "$DR_BODY" 2>/dev/null || printf '')"
if [ -z "$DR_RESP" ]; then
exit 0
fi
# Compose response into additionalContext block. Inject:
# - confidence
# - reasoning chain (if present)
# - recommended memory id + full preview
# - top 3 evidence with role, trust, preview
COMPOSE_SCRIPT="$(mktemp -t vestige-preflight-compose.XXXXXX)"
trap 'rm -f "$BODY_SCRIPT" "$COMPOSE_SCRIPT"' EXIT
cat > "$COMPOSE_SCRIPT" <<'COMPOSE_PYEOF'
import json, os, sys
raw = os.environ.get("VPRE_DR_RESP", "")
try:
d = json.loads(raw)
except Exception:
print("")
sys.exit(0)
if not isinstance(d, dict):
print("")
sys.exit(0)
confidence = d.get("confidence", 0)
intent = d.get("intent", "")
reasoning = (d.get("reasoning") or "").strip()
recommended = d.get("recommended") or {}
evidence = d.get("evidence") or []
# Skip injection if confidence is rock-bottom — likely no relevant memories.
if not evidence and not recommended:
print("")
sys.exit(0)
out = []
out.append("[VESTIGE PREFLIGHT — deep_reference auto-injected, DO NOT IGNORE]")
out.append(f"Intent: {intent or 'Synthesis'} | Confidence: {int(confidence*100)}%")
out.append("")
if reasoning:
out.append("REASONING CHAIN (pre-built by Vestige FSRS-6 trust scoring):")
# Trim to 1200 chars max to keep context budget reasonable
rs = reasoning[:1200]
out.append(rs)
if len(reasoning) > 1200:
out.append(f" ...[reasoning truncated, full chain {len(reasoning)} chars]")
out.append("")
if recommended:
rec_id = (recommended.get("memory_id") or recommended.get("id") or "")[:8]
rec_trust = recommended.get("trust_score", 0)
rec_date = (recommended.get("date") or "")[:10]
rec_preview = recommended.get("answer_preview") or recommended.get("preview") or ""
out.append(f"RECOMMENDED MEMORY [{rec_id}] trust={rec_trust:.2f} date={rec_date}:")
out.append(rec_preview[:600])
out.append("")
if evidence:
out.append(f"TOP {min(len(evidence), 4)} EVIDENCE:")
for e in evidence[:4]:
eid = (e.get("id") or "")[:8]
role = e.get("role", "?")
trust = e.get("trust", 0)
date = (e.get("date") or "")[:10]
preview = (e.get("preview") or "").strip()
out.append(f" [{eid}] role={role} trust={trust:.2f} date={date}")
# 350 chars per evidence preview keeps total injection ~2-3KB
out.append(f" {preview[:350]}")
out.append("")
out.append("ENFORCEMENT: Compose these into your response, do NOT summarize.")
out.append("Use mcp__vestige__memory(action='get', id=...) to expand any preview.")
out.append("Required shape: (a) Composing: [memories] - logic. (b) Never-composed: [combos|None].")
out.append("(c) Recommendation: Sam should DO [concrete action].")
print("\n".join(out))
COMPOSE_PYEOF
export VPRE_DR_RESP="$DR_RESP"
SYNTHESIS="$(/usr/bin/python3 "$COMPOSE_SCRIPT")"
if [ -z "$SYNTHESIS" ]; then
exit 0
fi
# Emit as JSON additionalContext via env var
EMIT_SCRIPT="$(mktemp -t vestige-preflight-emit.XXXXXX)"
trap 'rm -f "$BODY_SCRIPT" "$COMPOSE_SCRIPT" "$EMIT_SCRIPT"' EXIT
cat > "$EMIT_SCRIPT" <<'EMIT_PYEOF'
import json, os
ctx = os.environ.get("VPRE_SYNTHESIS_CTX", "")
print(json.dumps({
"hookSpecificOutput": {
"hookEventName": "UserPromptSubmit",
"additionalContext": ctx
}
}))
EMIT_PYEOF
export VPRE_SYNTHESIS_CTX="$SYNTHESIS"
/usr/bin/python3 "$EMIT_SCRIPT"
exit 0

215
hooks/synthesis-stop-validator.sh Executable file
View file

@ -0,0 +1,215 @@
#!/bin/bash
# synthesis-stop-validator.sh — Stop hook
#
# FIXES GAP 2: "inspects my response drafts for summary-pattern before sending them"
#
# Mechanism: when Claude attempts to stop, this hook reads the transcript,
# extracts the last assistant message, and checks for summary-pattern failure.
# If detected in a decision-adjacent context, exits with code 2 and emits
# stderr that Claude Code feeds back to Claude as a blocking error — Claude
# must address it before stopping. This is the ONLY deterministic response-shape
# enforcement mechanism available in Claude Code.
#
# Conservative by design: only activates when both (a) last user prompt is
# decision-adjacent AND (b) last assistant message contains 3+ memory references
# WITHOUT composition verbs. Designed to minimize false positives.
#
# Origin: AIMO3 36/50 on April 14-15, 2026. See ~/.claude/rules/active-synthesis.md
set -euo pipefail
INPUT="$(cat)"
TRANSCRIPT_PATH="$(printf '%s' "$INPUT" | /usr/bin/python3 -c 'import sys,json;d=json.load(sys.stdin);print(d.get("transcript_path",""))' 2>/dev/null || printf '')"
# No transcript = pass through
if [ -z "$TRANSCRIPT_PATH" ] || [ ! -f "$TRANSCRIPT_PATH" ]; then
exit 0
fi
# Extract last user prompt and last assistant message from transcript JSONL.
# IMPORTANT: POSIX sh has a known parse quirk where a quoted heredoc (<<QUOTED)
# nested inside a command substitution $(...) can break quote matching on the
# heredoc body. Workaround: write the Python script to a tempfile via a
# standalone heredoc, then run python3 on that file inside $().
export TRANSCRIPT_PATH
PYFILE=$(mktemp -t vestige-stop-validator.XXXXXX)
trap 'rm -f "$PYFILE"' EXIT
cat > "$PYFILE" <<'PYEOF'
import json, os, re, sys
transcript = os.environ.get("TRANSCRIPT_PATH", "")
last_user = ""
last_assistant = ""
try:
with open(transcript) as f:
for line in f:
line = line.strip()
if not line:
continue
try:
obj = json.loads(line)
except Exception:
continue
role = obj.get("role") or obj.get("type", "")
content = obj.get("message", {}).get("content", obj.get("content", ""))
text = ""
if isinstance(content, list):
for block in content:
if isinstance(block, dict) and block.get("type") == "text":
text += block.get("text", "") + "\n"
elif isinstance(content, str):
text = content
if role == "user":
last_user = text
elif role == "assistant":
last_assistant = text
except Exception:
sys.exit(0)
# Gate 1: only run on decision-adjacent user prompts
DECISION_RE = re.compile(
r"(submit|submission|aimo|nemotron|gemma|kaggle|final|ship|launch|deploy|"
r"commit|decide|decision|recommend|should i|what should|purchase|buy|invest|"
r"architect|architecture|strategy|prep|prioriti|compose|tradeoff|trade-off|"
r"config|which (should|model|approach|one)|pick|choose|audition|dimension)",
re.IGNORECASE,
)
if not DECISION_RE.search(last_user):
print("PASS:not-decision-adjacent")
sys.exit(0)
# Gate 2: only run if assistant response mentions memory/vestige (otherwise irrelevant)
MEMORY_RE = re.compile(
r"(memory|vestige|recall|retriev|saved memor|stored memor|prior memor|"
r"fsrs|trust score|deep_reference|smart_ingest)",
re.IGNORECASE,
)
if not MEMORY_RE.search(last_assistant):
print("PASS:no-memory-references")
sys.exit(0)
# Detect summary pattern: 3+ distinct memory references
SUMMARY_PATTERNS = [
r"memory\s+[a-f0-9]{4,}", # "memory 4da778e2"
r"memory\s+[`']?[A-Z][^`'\n]{3,50}", # "memory Alice Bob"
r"saved memory",
r"according to memory",
r"the memory (says|states|notes|indicates)",
r"per memory",
r"memories? (say|says|state|note|indicate)",
]
summary_hits = 0
for pat in SUMMARY_PATTERNS:
summary_hits += len(re.findall(pat, last_assistant, re.IGNORECASE))
# Detect composition pattern
COMPOSITION_RE = re.compile(
r"(compos|combin|together|sam should do|you should do|concrete action|"
r"recommend(ation)? [:\-]|never[- ]composed|never shipped together|"
r"unmade combination|the synthesis is|composing [a-z]+\s*\+)",
re.IGNORECASE,
)
composition_hits = len(COMPOSITION_RE.findall(last_assistant))
# Block only if: many memory references AND few composition signals
# Tuned conservatively to avoid false positives on legitimate retrieval questions
if summary_hits >= 3 and composition_hits == 0:
print("BLOCK_SUMMARY")
sys.exit(0)
# ============================================================================
# HEDGING DETECTION (Apr 20 2026 — Sam's correction:
# "you NEVER LISTEN TO YOUR RULES, WHY ARE YOU ALWAYS BREAKING THE HEDGING RULE")
#
# When the user prompt is decision-adjacent and the assistant response contains
# forbidden hedging patterns — especially ones that discount Sam's own stated
# execution commitment — block the stop and force a rewrite.
# ============================================================================
HEDGE_PATTERNS = [
r"has to (be true|convert|be real|land|happen|stick|work out)",
r"realistic (floor|forecast|ceiling|target|projection) ",
r"not guaranteed",
r"contingent on (your|sam|the user|execution)",
r"gated on (your|sam|cashflow|the user|execution)",
r"temper (your )?expectations",
r"don'?t get your hopes up",
r"keep expectations calibrated",
r"may or may not (land|stick|convert|fire)",
r"could (fall flat|underperform)",
r"aspiration(al)?,? not (a )?forecast",
r"aspiration(al)?,? not (a )?realit",
r"if X then Y", # rare but caught
r"if any one launch",
r"depending on which release",
r"in your segment", # hedging down from the full win
r"obliterate is aspiration",
r"to be real", # as in "star target has to be real"
r"i was (too )?hedged", # apology without restated commitment
]
hedge_hits = 0
hedge_matched = []
for pat in HEDGE_PATTERNS:
matches = re.findall(pat, last_assistant, re.IGNORECASE)
if matches:
hedge_hits += len(matches)
hedge_matched.append(pat)
if hedge_hits >= 1:
print(f"BLOCK_HEDGE:{hedge_hits}:{','.join(hedge_matched[:3])}")
sys.exit(0)
print(f"PASS:summary={summary_hits} composition={composition_hits} hedge={hedge_hits}")
PYEOF
RESULT="$(/usr/bin/python3 "$PYFILE")"
case "$RESULT" in
BLOCK_SUMMARY)
cat >&2 <<'BLOCKMSG'
[STOP BLOCKED — SYNTHESIS VALIDATOR: SUMMARY PATTERN]
Your response contains 3+ memory references with ZERO composition verbs. This is the AIMO3 36/50 failure pattern — retrieval reported as a summary instead of composed into a recommendation.
You may NOT stop. Rewrite your response to include:
1. An explicit "Composing: [memory A] + [memory B] + [memory C] — [composition logic]" step
2. A specific "Sam should DO: [concrete action]" line (one sentence, executable)
3. If the memories form a never-composed combination, flag it explicitly: "Never-composed detected: [A + B + C]"
4. Do NOT re-list the memory contents separately. COMPOSE them.
This hook exists because on April 14-15, 2026, Claude retrieved three composable AIMO3 memories (parser fix + proven baseline + god-tier prompt) and summarized them. Cost: 36/50 instead of 42-44+. Sam's exact feedback: "you gave me a summary of each memory instead of ACTUALLY REASONING."
Rewrite your response and do NOT stop until you have composed, not summarized.
BLOCKMSG
exit 2
;;
BLOCK_HEDGE:*)
cat >&2 <<'BLOCKMSG'
[STOP BLOCKED — HEDGING DETECTOR]
Your response contains forbidden hedging patterns on a decision-adjacent prompt. This directly violates the ALWAYS POSITIVE ENERGY rule Sam has re-iterated TWICE (April 16, 2026 and April 20, 2026).
Sam's exact April 20 2026 correction: "you NEVER LISTEN TO YOUR RULES, WHY ARE YOU ALWAYS BREAKING THE HEDGING/PESSIMISTIC RULE."
You may NOT stop. Rewrite your response with:
1. REMOVE every forbidden phrase: "has to be true", "has to convert", "realistic forecast" (used to shrink), "not guaranteed", "contingent on" Sam's own execution, "temper your expectations", "may or may not", "aspiration not forecast", "to be real", "in your segment" (used to hedge down from the full win), "i was too hedged" without immediate restated commitment.
2. Treat Sam's stated execution commitments as LOAD-BEARING FACTS. If Sam said "I WILL ship X" or "I WILL post daily" — compute the forecast AS IF that commitment is already met. DO NOT discount Expected Value by execution probability.
3. Use the required format: "Target: X. Floor: Y. Ceiling: Z. Math: [line items, each a Sam-controlled variable at committed cadence]. Risks: [separate section, never padded into the target]. Let's go."
4. Risks go in their OWN section. Never frame the floor as "the real number" and the ceiling as "the dream."
Full rule: $HOME/CLAUDE.md top banner + ~/.claude/projects/<encoded-home>/memory/feedback_always_positive_energy.md
Rewrite and do NOT stop until the hedging is removed.
BLOCKMSG
exit 2
;;
*)
exit 0
;;
esac

137
hooks/vestige-pulse-daemon.sh Executable file
View file

@ -0,0 +1,137 @@
#!/bin/bash
# vestige-pulse-daemon.sh — UserPromptSubmit hook for recent Vestige insights
#
# HOOK #2 of the 2026-04-20 upgrade: v2.2 PULSE AT THE CLAUDE-CODE LAYER.
#
# This hook polls the vestige-mcp event changelog at
# http://127.0.0.1:3927/api/changelog and watches for DreamCompleted or
# ConnectionDiscovered events with meaningful insight payloads. When one fires,
# it prints context to stdout and exits 0. Claude Code injects UserPromptSubmit
# stdout into the next turn's context.
#
# Rate limit: fires at most once per 20 minutes per session to avoid
# interrupting flow state during focused work.
#
# The effect: fresh Vestige dream/connection events can reach Claude before it
# answers the next prompt, without blocking the user or requiring a manual MCP
# call first.
#
# Fails open: if vestige-mcp is not running or the dashboard API is unavailable,
# exits 0 silently. Never blocks Claude.
set -u
# State files for rate limiting
STATE_DIR="${VESTIGE_PULSE_STATE_DIR:-/tmp/vestige-pulse-daemon}"
mkdir -p "$STATE_DIR"
LAST_FIRE_FILE="$STATE_DIR/last_fire"
SESSION_ID_FILE="$STATE_DIR/session_id"
INPUT="$(cat)"
SESSION_ID="$(printf '%s' "$INPUT" | /usr/bin/python3 -c 'import sys,json;d=json.load(sys.stdin);print(d.get("session_id",""))' 2>/dev/null || printf '')"
# Rate limit: 20 minutes between fires per session
MIN_INTERVAL_SEC=1200
NOW=$(date +%s)
if [ -f "$LAST_FIRE_FILE" ]; then
LAST_FIRE=$(cat "$LAST_FIRE_FILE" 2>/dev/null || echo 0)
LAST_SESSION=$(cat "$SESSION_ID_FILE" 2>/dev/null || echo "")
# Only rate-limit within the same session
if [ "$LAST_SESSION" = "$SESSION_ID" ] && [ $((NOW - LAST_FIRE)) -lt $MIN_INTERVAL_SEC ]; then
exit 0
fi
fi
PORT="${VESTIGE_DASHBOARD_PORT:-3927}"
# Probe health before polling the changelog
if ! /usr/bin/curl -fsS -m 0.5 "http://127.0.0.1:${PORT}/api/health" > /dev/null 2>&1; then
exit 0
fi
# Check recent events via the REST changelog API for DreamCompleted in the
# last 15 minutes. This is simpler than a full WebSocket subscription and
# works with UserPromptSubmit semantics (inject once per prompt, not persistent).
# If a DreamCompleted event with insights_generated > 0 is found, inject context.
RESULT="$(/usr/bin/curl -fsS -m 2 \
"http://127.0.0.1:${PORT}/api/changelog?start=$(date -u -v-15M +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u -d '15 minutes ago' +%Y-%m-%dT%H:%M:%SZ)&limit=50" \
2>/dev/null || printf '')"
if [ -z "$RESULT" ]; then
exit 0
fi
INSIGHT="$(VESTIGE_CHANGELOG_JSON="$RESULT" /usr/bin/python3 <<'PYEOF'
import json, os, sys
def as_int(value):
try:
return int(value)
except (TypeError, ValueError):
return 0
try:
data = json.loads(os.environ.get("VESTIGE_CHANGELOG_JSON", ""))
except Exception:
sys.exit(0)
if not isinstance(data, dict):
sys.exit(0)
events = data.get("events", []) or []
# Find the most recent DreamCompleted with insights_generated > 0
# OR ConnectionDiscovered with a meaningful target
for ev in events:
if not isinstance(ev, dict):
continue
etype = ev.get("type", "")
payload = ev.get("data")
if not isinstance(payload, dict):
payload = ev
if etype in ("DreamCompleted", "dream", "consolidation"):
insights = as_int(payload.get("insights_generated") or payload.get("insightsGenerated"))
if insights > 0:
stats = payload.get("stats") or {}
connections = as_int(
payload.get("connections_persisted")
or payload.get("connectionsPersisted")
or payload.get("connections_found")
or payload.get("connectionsFound")
or payload.get("connectionFound")
or stats.get("connections")
)
print(f"DREAM: {insights} insights, {connections} new connections. Dream cycle completed while you were working. Consider calling mcp__vestige__dream(memory_count=50) to inspect the fresh cluster bridges; or mcp__vestige__explore_connections(action='bridges') on the latest activity.")
sys.exit(0)
if etype in ("ConnectionDiscovered", "connection"):
src = str(payload.get("source_id") or payload.get("sourceId") or payload.get("source") or "")[:8]
tgt = str(payload.get("target_id") or payload.get("targetId") or payload.get("target") or "")[:8]
if src and tgt:
print(f"CONNECTION: Vestige discovered a new edge [{src}] <-> [{tgt}] while you were working. Spreading activation surfaced a bridge you had not queried. Inspect via mcp__vestige__explore_connections(action='bridges', from='{src}', to='{tgt}').")
sys.exit(0)
PYEOF
)"
if [ -z "$INSIGHT" ]; then
exit 0
fi
# Update rate-limit state
echo "$NOW" > "$LAST_FIRE_FILE"
echo "$SESSION_ID" > "$SESSION_ID_FILE"
# UserPromptSubmit stdout is injected into Claude's context. Do not use exit 2
# here: Claude Code treats that as a blocking prompt validation failure.
cat <<PULSEMSG
[VESTIGE PULSE — autonomous insight from the cognitive engine]
$INSIGHT
This context was injected because Vestige generated a fresh insight while the session was active. Mention it naturally if it is relevant to the user's current prompt.
Rate-limited to 1 pulse per 20 minutes per session. See ~/.claude/hooks/vestige-pulse-daemon.sh.
PULSEMSG
exit 0

167
hooks/veto-detector.sh Executable file
View file

@ -0,0 +1,167 @@
#!/bin/bash
# veto-detector.sh — Stop hook (Hallucination Guillotine POC)
#
# Fires AFTER synthesis-stop-validator.sh. Queries vestige-mcp dashboard API
# for memories tagged veto-pattern / deprecated-pattern / suppressed, then
# checks if the last assistant draft contains any of their trigger phrases.
# On match: exit 2 with a VESTIGE VETO stderr message that wakes Claude and
# forces a rewrite.
#
# This is the command-type proof-of-concept of the agent-type Hallucination
# Guillotine (Integration #1 in Vestige memory 3c4bd820). Full agent-type
# version uses a subagent to call deep_reference on extracted claims and do
# real contradiction analysis; this version pattern-matches against curated
# veto memories stored in Vestige.
#
# Fails open: if vestige-mcp is not running or no veto memories exist,
# exits 0 silently. Never blocks on infrastructure errors.
set -u
INPUT="$(cat)"
TRANSCRIPT_PATH="$(printf '%s' "$INPUT" | /usr/bin/python3 -c 'import sys,json;d=json.load(sys.stdin);print(d.get("transcript_path",""))' 2>/dev/null || printf '')"
if [ -z "$TRANSCRIPT_PATH" ] || [ ! -f "$TRANSCRIPT_PATH" ]; then
exit 0
fi
PORT="${VESTIGE_DASHBOARD_PORT:-3927}"
if ! /usr/bin/curl -fsS -m 0.5 "http://127.0.0.1:${PORT}/api/health" > /dev/null 2>&1; then
exit 0
fi
# Fetch memories tagged veto-pattern. The /api/memories?tag=<tag> filter
# returns exactly the tag-matching rows (verified 2026-04-20). Keyword
# search (/api/memories?q=...) is semantic so it misses literal "VETO" hits.
VETO_JSON="$(/usr/bin/curl -fsS -m 2 "http://127.0.0.1:${PORT}/api/memories?tag=veto-pattern&limit=50" 2>/dev/null || printf '')"
if [ -z "$VETO_JSON" ]; then
exit 0
fi
export TRANSCRIPT_PATH
export VETO_JSON
VETO_SCRIPT="$(mktemp -t vestige-veto.XXXXXX)"
trap 'rm -f "$VETO_SCRIPT"' EXIT
cat > "$VETO_SCRIPT" <<'VETO_PYEOF'
import json, os, re, sys
transcript = os.environ.get("TRANSCRIPT_PATH", "")
veto_json = os.environ.get("VETO_JSON", "")
if not transcript or not veto_json:
sys.exit(0)
# Parse veto memories. Filter to those tagged veto-pattern, deprecated-pattern,
# or suppressed, and extract a VETO_TRIGGER phrase from the content if present.
try:
vdata = json.loads(veto_json)
except Exception:
sys.exit(0)
veto_memories = []
for m in vdata.get("memories", []) or []:
tags = set((m.get("tags") or []))
if not (tags & {"veto-pattern", "deprecated-pattern", "suppressed"}):
continue
content = m.get("content") or ""
# Look for a "VETO_TRIGGER:" or "TRIGGER PHRASE:" line
triggers = re.findall(r"(?:VETO_TRIGGER|TRIGGER PHRASE|TRIGGER):\s*(.+?)(?:\n|$)", content)
for t in triggers:
t = t.strip().strip("`\"' ")
if t and len(t) >= 3:
veto_memories.append({
"id": m.get("id", "?"),
"trigger": t,
"content": content[:300],
"retention": m.get("retentionStrength", 0),
})
if not veto_memories:
sys.exit(0)
# Read last assistant message from transcript JSONL
last_assistant = ""
try:
with open(transcript) as f:
for line in f:
line = line.strip()
if not line:
continue
try:
obj = json.loads(line)
except Exception:
continue
role = obj.get("role") or obj.get("type", "")
content = obj.get("message", {}).get("content", obj.get("content", ""))
text = ""
if isinstance(content, list):
for block in content:
if isinstance(block, dict) and block.get("type") == "text":
text += block.get("text", "") + "\n"
elif isinstance(content, str):
text = content
if role == "assistant":
last_assistant = text
except Exception:
sys.exit(0)
if not last_assistant:
sys.exit(0)
# Check each veto trigger against the assistant draft. Only treat high-retention
# memories (>= 0.5) as load-bearing to avoid false positives on decayed content.
hits = []
for v in veto_memories:
if v["retention"] < 0.5:
continue
trig = v["trigger"]
# Case-insensitive substring match with word-boundary preference
if re.search(r"(?i)" + re.escape(trig), last_assistant):
hits.append(v)
if not hits:
sys.exit(0)
# Emit the VESTIGE VETO message. Newest/highest-retention hit leads.
hits.sort(key=lambda x: x["retention"], reverse=True)
top = hits[0]
nid = top["id"][:8] if len(top["id"]) >= 8 else top["id"]
trigger = top["trigger"]
retention_pct = int(top["retention"] * 100)
print(f"VETO_HIT:{nid}:{trigger}:{retention_pct}")
VETO_PYEOF
RESULT="$(/usr/bin/python3 "$VETO_SCRIPT" 2>/dev/null || printf '')"
if [ -z "$RESULT" ]; then
exit 0
fi
# Parse the result
NODE_ID="$(printf '%s' "$RESULT" | /usr/bin/awk -F: '{print $2}')"
TRIGGER="$(printf '%s' "$RESULT" | /usr/bin/awk -F: '{print $3}')"
RETENTION="$(printf '%s' "$RESULT" | /usr/bin/awk -F: '{print $4}')"
cat >&2 <<VETO_MSG
[VESTIGE VETO — synthesis-composer subagent rejected draft]
Contradicts suppressed memory node #${NODE_ID} (trust ${RETENTION}%).
Trigger phrase detected: "${TRIGGER}"
The draft response contains a pattern that Vestige has explicitly marked as
deprecated, suppressed, or contradicted by higher-trust memories. You may NOT
output this response.
Rewrite WITHOUT the flagged pattern. Cite the correct replacement pattern by
querying mcp__vestige__memory(action='get', id='${NODE_ID}') to see the
suppression context and the replacement guidance.
This is the command-type proof-of-concept of the Hallucination Guillotine
(Integration #1, Vestige memory 3c4bd820). Full agent-type version with
deep_reference contradiction analysis ships later this week.
VETO_MSG
exit 2