Compare commits

...

3 commits
main ... v2.1.0

Author SHA1 Message Date
Sam Valladares
1d95347b88 Make all Cognitive Sandwich hooks opt-in 2026-05-01 05:12:54 -05:00
Sam Valladares
cb7ee2dcb5 Remove default Vestige Stop hooks from v2.1.0 2026-05-01 05:02:31 -05:00
Sam Valladares
4f457ec2db Make Sanhedrin optional in v2.1.0 2026-05-01 04:55:54 -05:00
10 changed files with 427 additions and 178 deletions

View file

@ -7,13 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [2.1.0] - 2026-04-27 — "Cognitive Sandwich Goes Local" ## [2.1.0] - 2026-04-27 — "Cognitive Sandwich Goes Local"
The Sanhedrin Executioner — Vestige's veto layer for Claude Code responses — now runs entirely on a local MLX model (`mlx-community/Qwen3.6-35B-A3B-4bit`). Zero API cost per Claude turn, fully offline, no Anthropic round-trip on the critical path. Combined with four pre-cognitive UserPromptSubmit hooks (synthesis-preflight, cwd-state-injector, vestige-pulse-daemon, preflight-swarm), Vestige now ships a complete "Cognitive Sandwich" — Vestige memories injected before the model thinks, local Sanhedrin veto after the model speaks — installable in one command on a MacBook. The Sanhedrin Executioner — Vestige's veto layer for Claude Code responses — can run against a local MLX model (`mlx-community/Qwen3.6-35B-A3B-4bit`) when explicitly enabled. Combined with four pre-cognitive UserPromptSubmit hooks (synthesis-preflight, cwd-state-injector, vestige-pulse-daemon, preflight-swarm), Vestige now ships a complete "Cognitive Sandwich" — Vestige memories injected before the model thinks, optional Sanhedrin veto after the model speaks.
> 2026-05-01 hotfix: Sanhedrin, preflight, and all Vestige Claude Code hooks are optional by default. The default installer activates no hooks, makes no automatic model calls, removes old Vestige hook wiring from previous v2.1.0 installs, no longer starts MLX, and removes the old v2.1.0 MLX launchd job on reinstall. Users who want preflight can opt in with `--enable-preflight`; users who want Sanhedrin can opt in with `--enable-sanhedrin`; Apple Silicon local MLX autostart is a separate `--with-launchd` flag, and x86 users can point `--sanhedrin-endpoint` at any OpenAI-compatible `/v1/chat/completions` endpoint.
### Added ### Added
- **`hooks/`** — first-class harness-side companion to the Vestige MCP server. 9 production hooks designed for `~/.claude/hooks/`: - **`hooks/`** — first-class harness-side companion to the Vestige MCP server. 9 production hooks designed for `~/.claude/hooks/`:
- `sanhedrin.sh`Stop hook that invokes the local Qwen Executioner via the Python bridge. - `sanhedrin.sh`optional Stop hook that invokes the Sanhedrin Executioner via the Python bridge only when `VESTIGE_SANHEDRIN_ENABLED=1`.
- `sanhedrin-local.py`local backend that POSTs to `mlx_lm.server` (`localhost:8080`) with Vestige evidence injected via the dashboard `/api/deep_reference` HTTP endpoint. TRUST_FLOOR=0.55 evidence filter + topical-relevance gate + inference-verb ban + 8 worked few-shots covering true positives AND false-positive guards. - `sanhedrin-local.py`OpenAI-compatible backend that POSTs to the configured Sanhedrin endpoint with Vestige evidence injected via the dashboard `/api/deep_reference` HTTP endpoint. TRUST_FLOOR=0.55 evidence filter + topical-relevance gate + inference-verb ban + 8 worked few-shots covering true positives AND false-positive guards.
- `synthesis-preflight.sh` — UserPromptSubmit hook that POSTs the user prompt to `/api/deep_reference` and injects the trust-scored reasoning chain into context. - `synthesis-preflight.sh` — UserPromptSubmit hook that POSTs the user prompt to `/api/deep_reference` and injects the trust-scored reasoning chain into context.
- `cwd-state-injector.sh` — captures git status, branch, modified files, open PRs/issues. - `cwd-state-injector.sh` — captures git status, branch, modified files, open PRs/issues.
- `vestige-pulse-daemon.sh` — surfaces fresh Vestige dream insights from the past 20 min. - `vestige-pulse-daemon.sh` — surfaces fresh Vestige dream insights from the past 20 min.
@ -21,18 +23,21 @@ The Sanhedrin Executioner — Vestige's veto layer for Claude Code responses —
- `synthesis-stop-validator.sh` — Stop hook regex against forbidden hedging patterns. - `synthesis-stop-validator.sh` — Stop hook regex against forbidden hedging patterns.
- `veto-detector.sh` — fast 50ms regex pre-screen against `veto`-tagged Vestige memories. - `veto-detector.sh` — fast 50ms regex pre-screen against `veto`-tagged Vestige memories.
- `synthesis-gate.sh` — legacy v1 trigger (kept for backward compat). - `synthesis-gate.sh` — legacy v1 trigger (kept for backward compat).
- `settings.fragment.json` — JSON snippet merged into `~/.claude/settings.json` by the installer. - `settings.fragment.json` — empty default fragment used to remove old Vestige hook wiring without enabling new hooks.
- `settings.preflight.fragment.json` — opt-in UserPromptSubmit hooks used only with `--enable-preflight`.
- `settings.sanhedrin.fragment.json` — opt-in JSON snippet used only with `--enable-sanhedrin`.
- **Dashboard `/api/changelog` endpoint** — bounded REST event feed for recent `DreamCompleted` and `ConnectionDiscovered` events, used by the Pulse hook to inject fresh synthesis into Claude Code context. - **Dashboard `/api/changelog` endpoint** — bounded REST event feed for recent `DreamCompleted` and `ConnectionDiscovered` events, used by the Pulse hook to inject fresh synthesis into Claude Code context.
- **`agents/`** — `executioner.md` (legacy/fallback Haiku 4.5 path), `lateral-thinker.md`, `synthesis-composer.md`. - **`agents/`** — `executioner.md` (legacy/fallback Haiku 4.5 path), `lateral-thinker.md`, `synthesis-composer.md`.
- **`launchd/com.vestige.mlx-server.plist.template`** — auto-start `mlx_lm.server` with the Qwen3.6-35B-A3B-4bit model on login. Templated with `__HOME__` and `__MODEL__` placeholders. - **`launchd/com.vestige.mlx-server.plist.template`** — optional Apple Silicon helper that auto-starts `mlx_lm.server` with the Qwen3.6-35B-A3B-4bit model on login. Templated with `__HOME__` and `__MODEL__` placeholders.
- **`scripts/install-sandwich.sh`** — one-command installer that stages hooks, agents, plist, jq-merges the settings fragment, and `launchctl load`s the plist. Backs up `settings.json` to `.bak.pre-sandwich`. Supports `--force`, `--no-launchd`, `--include-memory-loader`, `--src=PATH`. - **`scripts/install-sandwich.sh`** — one-command installer that stages hooks, agents, jq-merges the settings fragment, and backs up `settings.json` to `.bak.pre-sandwich`. Supports `--force`, `--enable-sanhedrin`, `--with-launchd`, `--sanhedrin-endpoint`, `--sanhedrin-model`, `--include-memory-loader`, `--src=PATH`.
- **`scripts/check-sandwich-prereqs.sh`** — comprehensive prereq verifier (Apple Silicon, Python 3.10+, jq, uv, mlx-lm, hf, claude, vestige-mcp, model on disk, MCP HTTP up, server up, plist installed, settings wired). - **`scripts/check-sandwich-prereqs.sh`** — verifier that default installs have no Vestige hooks wired, with `--preflight` and `--sanhedrin` checks for optional layers.
- **`docs/COGNITIVE_SANDWICH.md`** — architecture diagram, install guide, performance notes (82 tok/s on M3 Max), uninstall, configuration env vars. - **`docs/COGNITIVE_SANDWICH.md`** — architecture diagram, install guide, performance notes (82 tok/s on M3 Max), uninstall, configuration env vars.
- **PR #48**`VESTIGE_DATA_DIR` env-var support + tilde expansion + secure unix perms (thanks @Jelloeater) — directly addresses the ghost env-vars exposed by v2.0.9 cleanup. - **PR #48**`VESTIGE_DATA_DIR` env-var support + tilde expansion + secure unix perms (thanks @Jelloeater) — directly addresses the ghost env-vars exposed by v2.0.9 cleanup.
### Changed ### Changed
- **Sanhedrin Executioner default backend swapped from Anthropic Haiku 4.5 → local `mlx_lm.server` + Qwen3.6-35B-A3B-4bit.** Anthropic API key no longer required for the post-cognitive layer. The `executioner.md` agent definition is retained as manual/fallback only when invoked explicitly via `Task(subagent_type='executioner')`. - **Sanhedrin, preflight, and all Vestige Claude Code hooks are optional by default.** Default installs run on x86 and low-memory machines without wiring any Vestige hook, calling Claude, downloading the 19 GB MLX model, or starting MLX. Reinstalling the default v2.1.0 hotfix removes the old Vestige hook wiring and the old mandatory `com.vestige.mlx-server` launchd job if they exist.
- **Sanhedrin Executioner backend swapped from Anthropic Haiku 4.5 → OpenAI-compatible endpoint, with local `mlx_lm.server` + Qwen3.6-35B-A3B-4bit as the Apple Silicon opt-in path.** Anthropic API key no longer required for the post-cognitive layer. The `executioner.md` agent definition is retained as manual/fallback only when invoked explicitly via `Task(subagent_type='executioner')`.
- **All hooks sanitized for public release** — replaced hardcoded personal absolute paths with `$HOME` / `$VESTIGE_*` env vars; removed personal regex tokens. - **All hooks sanitized for public release** — replaced hardcoded personal absolute paths with `$HOME` / `$VESTIGE_*` env vars; removed personal regex tokens.
- **NPM binary installer now follows package version**`vestige-mcp-server@2.1.0` downloads release assets from `v2.1.0` instead of a stale hardcoded binary tag, while local workspace installs skip the release-asset download before the tag exists. - **NPM binary installer now follows package version**`vestige-mcp-server@2.1.0` downloads release assets from `v2.1.0` instead of a stale hardcoded binary tag, while local workspace installs skip the release-asset download before the tag exists.
@ -41,6 +46,7 @@ The Sanhedrin Executioner — Vestige's veto layer for Claude Code responses —
- `cargo test --workspace --release --no-fail-fast`: **1,229 passing, 0 failed** (366 vestige-core + 358 vestige-mcp lib + 4 vestige-mcp bin + 497 e2e + 4 doctests). - `cargo test --workspace --release --no-fail-fast`: **1,229 passing, 0 failed** (366 vestige-core + 358 vestige-mcp lib + 4 vestige-mcp bin + 497 e2e + 4 doctests).
- Sanhedrin bridge smoke checks: Python bytecode compilation passes, fail-open bridge invocation returns `yes`, and public hook settings validate as JSON. - Sanhedrin bridge smoke checks: Python bytecode compilation passes, fail-open bridge invocation returns `yes`, and public hook settings validate as JSON.
- 8-day Sandwich dogfood: **84% pass rate, 16% legitimate vetoes** caught real hallucinations. - 8-day Sandwich dogfood: **84% pass rate, 16% legitimate vetoes** caught real hallucinations.
- 2026-05-01 hotfix checks: `cargo test --workspace --no-fail-fast`, `cargo build --release --workspace`, shell/Python/JSON validation, and default/preflight/Sanhedrin installer dry-runs all pass.
### Closes ### Closes
@ -48,14 +54,20 @@ The Sanhedrin Executioner — Vestige's veto layer for Claude Code responses —
### Prerequisites for the Cognitive Sandwich ### Prerequisites for the Cognitive Sandwich
- macOS Apple Silicon (M1+) — required for MLX
- Python 3.10+ - Python 3.10+
- `jq`
- `vestige-mcp`
- Claude Code
Optional local MLX Sanhedrin backend:
- macOS Apple Silicon (M1+) — required for the launchd MLX helper only
- ~22 GB free RAM (Qwen3.6-35B-A3B-4bit at runtime) - ~22 GB free RAM (Qwen3.6-35B-A3B-4bit at runtime)
- First-run model download: ~19 GB from Hugging Face (cached locally thereafter) - First-run model download: ~19 GB from Hugging Face (cached locally thereafter)
### Migration ### Migration
None required for existing Vestige users. The Cognitive Sandwich is opt-in via `scripts/install-sandwich.sh`. The MCP server, schema, and tool surface are bit-identical to v2.0.9. None required for existing Vestige users. The default installer now removes old v2.1.0 Claude Code hook wiring. The Cognitive Sandwich hook layers are opt-in via `--enable-preflight`, `--enable-sanhedrin`, or `--enable-sandwich`. The MCP server, schema, and tool surface are bit-identical to v2.0.9.
--- ---

View file

@ -22,10 +22,10 @@ Built on 130 years of memory research — FSRS-6 spaced repetition, prediction e
## What's New in v2.1.0 "Cognitive Sandwich Goes Local" ## What's New in v2.1.0 "Cognitive Sandwich Goes Local"
v2.1.0 adds an opt-in Claude Code hook harness around the existing Vestige MCP server. The MCP tool surface and database schema stay backward compatible, while the new local Sanhedrin verifier and preflight hooks can inject trusted memory context before Claude answers and check drafts against high-trust Vestige evidence before delivery. v2.1.0 adds an opt-in Claude Code hook harness around the existing Vestige MCP server. The MCP tool surface and database schema stay backward compatible, while optional preflight hooks can inject trusted memory context before Claude answers and optional Sanhedrin hooks can check drafts against high-trust Vestige evidence before delivery.
- **Local Sanhedrin Executioner.** The post-response verifier now runs through `mlx_lm.server` with `mlx-community/Qwen3.6-35B-A3B-4bit` by default, so the veto layer can run offline on Apple Silicon without Anthropic API calls. - **Optional Sanhedrin Executioner.** The post-response verifier can run through `mlx_lm.server` with `mlx-community/Qwen3.6-35B-A3B-4bit` on Apple Silicon, or through any OpenAI-compatible endpoint on x86, but it is never enabled by default.
- **One-command Cognitive Sandwich installer.** `scripts/install-sandwich.sh` stages hooks, agents, and a launchd plist, merges the Claude Code hooks block, and prints real verification commands. - **One-command Cognitive Sandwich installer.** `scripts/install-sandwich.sh` stages hook files and agents, removes old v2.1.0 hook wiring by default, and only activates Claude Code hooks with explicit `--enable-preflight`, `--enable-sanhedrin`, or `--enable-sandwich`.
- **Pulse hook backed by `/api/changelog`.** Fresh dream and connection events can be injected into the next Claude Code prompt context without blocking the prompt. - **Pulse hook backed by `/api/changelog`.** Fresh dream and connection events can be injected into the next Claude Code prompt context without blocking the prompt.
- **`VESTIGE_DATA_DIR` support.** `--data-dir` now has an env-var fallback, tilde expansion, secure directory creation, and clear precedence docs. - **`VESTIGE_DATA_DIR` support.** `--data-dir` now has an env-var fallback, tilde expansion, secure directory creation, and clear precedence docs.
- **NPM release wrapper fixed.** `vestige-mcp-server@2.1.0` now downloads binaries from the matching `v2.1.0` GitHub release tag instead of an old hardcoded release. - **NPM release wrapper fixed.** `vestige-mcp-server@2.1.0` now downloads binaries from the matching `v2.1.0` GitHub release tag instead of an old hardcoded release.

View file

@ -2,7 +2,7 @@
**Vestige's defense-in-depth safety architecture for Claude Code.** **Vestige's defense-in-depth safety architecture for Claude Code.**
The Cognitive Sandwich wraps every Claude Code response in two layers of cognitive scaffolding: The default Cognitive Sandwich installer only stages files and removes old v2.1.0 hook wiring. It activates no Claude Code hooks and makes no automatic model calls. Both the preflight layer and the Stop-hook layer are explicit opt-ins:
``` ```
┌────────────────────────────────────────────────┐ ┌────────────────────────────────────────────────┐
@ -15,32 +15,31 @@ The Cognitive Sandwich wraps every Claude Code response in two layers of cogniti
├────────────────────────────────────────────────┤ ├────────────────────────────────────────────────┤
│ 🥩 MEAT — Claude Code reasons │ │ 🥩 MEAT — Claude Code reasons │
├────────────────────────────────────────────────┤ ├────────────────────────────────────────────────┤
│ 🥪 BOTTOM BREAD — Stop hooks │ │ 🥪 OPTIONAL BOTTOM BREAD — Stop hooks │
│ • Veto-detector (fast 50ms regex pre-screen) │ │ • Veto-detector / synthesis validator │
│ • Sanhedrin Executioner (LOCAL Qwen3.6-35B) │ │ • Sanhedrin Executioner verifier │
│ • Synthesis stop validator (hedge detector) │
└────────────────────────────────────────────────┘ └────────────────────────────────────────────────┘
``` ```
The Sanhedrin Executioner is the headline of v2.1.0. As of v2.1.0 it runs entirely on a local MLX model (`mlx-community/Qwen3.6-35B-A3B-4bit`), replacing the v2.0.x Haiku 4.5 subagent. **Zero API cost per Claude turn, fully offline, ~515s verdict latency on M-series Apple Silicon.** Sanhedrin, preflight, and all Vestige Claude Code hooks are optional. The default installer wires none of them; it does not call Claude, start MLX, require a 19 GB model download, or require 20+ GB of RAM. Users who want preflight context can opt in with `--enable-preflight`. Users who want the post-response verifier can opt in with `--enable-sanhedrin` and point it at any OpenAI-compatible `/v1/chat/completions` endpoint. On Apple Silicon, an additional `--with-launchd` flag can auto-start the local MLX Qwen backend.
--- ---
## How a single response flows through the Sandwich ## How a single response flows through the Sandwich
1. **You type a prompt in Claude Code.** 1. **You type a prompt in Claude Code.**
2. **UserPromptSubmit hooks fire in parallel** (none can block — all fail-open): 2. **If explicitly enabled, UserPromptSubmit hooks fire in parallel** (none can block — all fail-open):
- `load-all-memory.sh` (opt-in) — dumps every memory MD into context - `load-all-memory.sh` (opt-in) — dumps every memory MD into context
- `synthesis-preflight.sh` — POSTs your prompt to `vestige-mcp` `/api/deep_reference`, injects the trust-scored reasoning chain - `synthesis-preflight.sh` — POSTs your prompt to `vestige-mcp` `/api/deep_reference`, injects the trust-scored reasoning chain
- `cwd-state-injector.sh` — captures git status, branch, open PRs/issues, modified files - `cwd-state-injector.sh` — captures git status, branch, open PRs/issues, modified files
- `vestige-pulse-daemon.sh` — injects fresh Vestige dream insights from the past 20 min into the next prompt context - `vestige-pulse-daemon.sh` — injects fresh Vestige dream insights from the past 20 min into the next prompt context
- `preflight-swarm.sh` — spawns the `lateral-thinker` subagent in fresh context to surface cross-disciplinary structural parallels - `preflight-swarm.sh` — spawns the `lateral-thinker` subagent in fresh context to surface cross-disciplinary structural parallels
3. **Claude reads the assembled context and generates a draft.** 3. **Claude reads the assembled context and generates a draft.**
4. **Stop hooks fire serially** (any can VETO with `exit 2`, forcing a rewrite): 4. **By default, no Vestige Stop hooks are installed.** If explicitly enabled, Stop hooks fire serially (any can VETO with `exit 2`, forcing a rewrite):
- `veto-detector.sh` — fast regex against `veto`-tagged Vestige memories (~50ms) - `veto-detector.sh` — fast regex against `veto`-tagged Vestige memories (~50ms)
- `sanhedrin.sh``sanhedrin-local.py`single-shot local Qwen3.6-35B-A3B verdict - `sanhedrin.sh``sanhedrin-local.py`optional single-shot semantic verdict
- `synthesis-stop-validator.sh` — regex against forbidden patterns (hedging, summary-instead-of-composition) - `synthesis-stop-validator.sh` — regex against forbidden patterns (hedging, summary-instead-of-composition)
5. **If all 3 Stop hooks return `exit 0`, the response is delivered.** 5. **If all enabled Stop hooks return `exit 0`, the response is delivered.**
--- ---
@ -82,30 +81,65 @@ curl -fsSL https://raw.githubusercontent.com/samvallad33/vestige/v2.1.0/scripts/
git clone https://github.com/samvallad33/vestige git clone https://github.com/samvallad33/vestige
cd vestige cd vestige
./scripts/install-sandwich.sh # add --force to overwrite existing hooks ./scripts/install-sandwich.sh # add --force to overwrite existing hooks
./scripts/check-sandwich-prereqs.sh # verify everything's wired ./scripts/check-sandwich-prereqs.sh # verify no Vestige hooks are wired by default
```
The default command does not activate any Claude Code hook. It removes old v2.1.0 Vestige hook wiring from `~/.claude/settings.json` while preserving unrelated user hooks.
### Optional Preflight
Preflight is a separate opt-in layer. It includes `preflight-swarm.sh`, which uses `claude -p --model claude-haiku-4-5-20251001`; it is not wired by default.
```bash
./scripts/install-sandwich.sh --enable-preflight
./scripts/check-sandwich-prereqs.sh --preflight
```
### Optional Sanhedrin
Sanhedrin is a separate opt-in layer.
```bash
# Wire the Sanhedrin Stop hook, using the default OpenAI-compatible endpoint.
./scripts/install-sandwich.sh --enable-sanhedrin
# Apple Silicon only, and only if the machine has enough memory:
./scripts/install-sandwich.sh --enable-sanhedrin --with-launchd
# x86 / Linux / Intel Mac: use any OpenAI-compatible endpoint.
./scripts/install-sandwich.sh \
--enable-sanhedrin \
--sanhedrin-endpoint=http://127.0.0.1:11434/v1/chat/completions \
--sanhedrin-model=qwen2.5:14b
``` ```
### Prerequisites ### Prerequisites
| Tool | Install | | Tool | Install |
|---|---| |---|---|
| macOS Apple Silicon (M1+) | required for MLX |
| Python 3.10+ | typically preinstalled | | Python 3.10+ | typically preinstalled |
| `jq` | `brew install jq` | | `jq` | `brew install jq` |
| `vestige-mcp` | `cargo install vestige-mcp` |
| Claude Code | https://claude.ai/code |
Optional Apple Silicon local Sanhedrin backend:
| Tool | Install |
|---|---|
| macOS Apple Silicon (M1+) | required for MLX launchd only |
| `uv` | `brew install uv` | | `uv` | `brew install uv` |
| `mlx-lm` | `uv tool install mlx-lm` | | `mlx-lm` | `uv tool install mlx-lm` |
| `huggingface_hub[cli]` | `uv tool install 'huggingface_hub[cli]'` | | `huggingface_hub[cli]` | `uv tool install 'huggingface_hub[cli]'` |
| `vestige-mcp` | `cargo install vestige-mcp` |
| Claude Code | https://claude.ai/code |
| Qwen3.6-35B-A3B-4bit | `hf download mlx-community/Qwen3.6-35B-A3B-4bit` (~19 GB) | | Qwen3.6-35B-A3B-4bit | `hf download mlx-community/Qwen3.6-35B-A3B-4bit` (~19 GB) |
### What the installer does ### What the installer does
1. Verifies prereqs (warnings for missing tools, fatal only on jq/python3). 1. Verifies prereqs (warnings for missing tools, fatal only on jq/python3).
2. Copies hooks to `~/.claude/hooks/`, agents to `~/.claude/agents/`. 2. Copies hooks to `~/.claude/hooks/`, agents to `~/.claude/agents/`.
3. Renders `launchd/com.vestige.mlx-server.plist.template` with your `$HOME` and chosen model, writes to `~/Library/LaunchAgents/`. 3. Backs up existing `~/.claude/settings.json` to `.bak.pre-sandwich`, then removes old Vestige hook wiring from previous v2.1.0 installs.
4. `launchctl load` the plist (auto-start mlx_lm.server with the Qwen model on boot). 4. With `--enable-preflight`, merges the UserPromptSubmit hooks block.
5. Backs up existing `~/.claude/settings.json` to `.bak.pre-sandwich`, then `jq`-merges the hooks block. 5. With `--enable-sanhedrin`, writes `~/.claude/hooks/vestige-sanhedrin.env` and merges a Sanhedrin-enabled Stop hooks block.
6. With `--enable-sanhedrin --with-launchd` on Apple Silicon, renders and loads `launchd/com.vestige.mlx-server.plist.template`.
### Uninstall ### Uninstall
@ -120,7 +154,7 @@ cp ~/.claude/settings.json.bak.pre-sandwich ~/.claude/settings.json
## Performance notes ## Performance notes
On M3 Max 16-core (400 GB/s memory bandwidth): Optional local MLX backend on M3 Max 16-core (400 GB/s memory bandwidth):
- Sanhedrin verdict: 515 seconds end-to-end (single deep_reference + single Qwen call) - Sanhedrin verdict: 515 seconds end-to-end (single deep_reference + single Qwen call)
- mlx_lm.server token generation: ~82 tok/s - mlx_lm.server token generation: ~82 tok/s
- mlx_lm.server peak resident memory: ~19.7 GB - mlx_lm.server peak resident memory: ~19.7 GB
@ -134,11 +168,12 @@ On M3 Max 14-core or M2/M1 Max: closer to 37s prompt processing, ~5060 tok
| Env var | Default | Effect | | Env var | Default | Effect |
|---|---|---| |---|---|---|
| `VESTIGE_SANHEDRIN_ENABLED` | `1` | Set to `0` to disable Sanhedrin Stop hook entirely | | `VESTIGE_SANHEDRIN_ENABLED` | `0` | Set to `1` to enable the optional Sanhedrin Stop hook |
| `VESTIGE_SWARM_ENABLED` | `1` | Set to `0` to disable preflight lateral-thinker swarm | | `VESTIGE_SWARM_ENABLED` | `1` | Set to `0` to disable preflight lateral-thinker swarm |
| `VESTIGE_DASHBOARD_PORT` | `3927` | Vestige MCP HTTP API port used by hooks | | `VESTIGE_DASHBOARD_PORT` | `3927` | Vestige MCP HTTP API port used by hooks |
| `MLX_ENDPOINT` | `http://127.0.0.1:8080/v1/chat/completions` | OpenAI-compatible chat completions endpoint for Sanhedrin | | `VESTIGE_SANHEDRIN_ENDPOINT` | `http://127.0.0.1:8080/v1/chat/completions` | OpenAI-compatible chat completions endpoint for Sanhedrin |
| `VESTIGE_SANDWICH_MODEL` | `mlx-community/Qwen3.6-35B-A3B-4bit` | Model launchd serves and Sanhedrin requests | | `VESTIGE_SANHEDRIN_MODEL` | `mlx-community/Qwen3.6-35B-A3B-4bit` | Model name sent to the Sanhedrin endpoint |
| `MLX_ENDPOINT` / `VESTIGE_SANDWICH_MODEL` | legacy aliases | Backward-compatible names still read by the bridge |
| `VESTIGE_MEMORY_DIR` | (auto) | Override per-user Claude memory dir | | `VESTIGE_MEMORY_DIR` | (auto) | Override per-user Claude memory dir |
--- ---
@ -151,11 +186,12 @@ Full architecture memory: search Vestige for `god-tier-plan` or `cognitive-sandw
--- ---
## Linux / Intel Mac ## Linux / Intel Mac / x86
The launchd layer is macOS-arm64-only. On Linux or Intel Mac: The base hook harness runs on x86. The launchd MLX helper is macOS-arm64-only.
- Hooks + agents install fine with `--no-launchd`
- The Sanhedrin Stop hook will fail-open (mlx-server unreachable → exit 0)
- Optional: run a remote mlx_lm.server / vLLM / Ollama OpenAI-compatible endpoint and set `MLX_ENDPOINT` to its `/v1/chat/completions` URL
Future v2.2.0 will add Linux-native MLX equivalents. On Linux, Windows under WSL, or Intel Mac:
- Run `scripts/install-sandwich.sh` normally to stage files and remove old Vestige hook wiring. No hooks are activated.
- If you want Sanhedrin, run an OpenAI-compatible endpoint such as vLLM, Ollama, llama.cpp server, or a remote MLX/vLLM box.
- Install with `--enable-sanhedrin --sanhedrin-endpoint=<url> --sanhedrin-model=<model>`.
- If the endpoint is unreachable, Sanhedrin fails open and does not block Claude Code.

View file

@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# sanhedrin-local.py — Local Qwen3.6-35B-A3B Sanhedrin Executioner. # sanhedrin-local.py — OpenAI-compatible Sanhedrin Executioner bridge.
# Drop-in replacement for the Haiku 4.5 subagent that sanhedrin.sh used to spawn. # 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: # Reads draft from stdin, prints single-line verdict to stdout:
@ -8,10 +8,10 @@
# #
# Architecture: # Architecture:
# stdin (draft) -> Vestige /api/deep_reference (single semantic query) # stdin (draft) -> Vestige /api/deep_reference (single semantic query)
# -> mlx_lm.server localhost:8080 (one-shot judgment) # -> OpenAI-compatible chat endpoint (one-shot judgment)
# -> stdout (single-line verdict) # -> stdout (single-line verdict)
# #
# Fail-open: if mlx-server unreachable, print "yes" and exit 0 (don't break # Fail-open: if the endpoint is unreachable, print "yes" and exit 0 (don't break
# the Cognitive Sandwich on infra errors). The wrapping sanhedrin.sh maps # the Cognitive Sandwich on infra errors). The wrapping sanhedrin.sh maps
# "yes" to exit 0, so this preserves existing fail-open semantics. # "yes" to exit 0, so this preserves existing fail-open semantics.
@ -35,7 +35,11 @@ VESTIGE_BASE_URL = (
os.environ.get("VESTIGE_BASE_URL") or f"http://127.0.0.1:{DASHBOARD_PORT}" os.environ.get("VESTIGE_BASE_URL") or f"http://127.0.0.1:{DASHBOARD_PORT}"
).rstrip("/") ).rstrip("/")
MLX_ENDPOINT = os.environ.get("MLX_ENDPOINT") or "http://127.0.0.1:8080/v1/chat/completions" SANHEDRIN_ENDPOINT = (
os.environ.get("VESTIGE_SANHEDRIN_ENDPOINT")
or os.environ.get("MLX_ENDPOINT")
or "http://127.0.0.1:8080/v1/chat/completions"
)
VESTIGE_ENDPOINT = ( VESTIGE_ENDPOINT = (
os.environ.get("VESTIGE_DEEP_REFERENCE_ENDPOINT") os.environ.get("VESTIGE_DEEP_REFERENCE_ENDPOINT")
or f"{VESTIGE_BASE_URL}/api/deep_reference" or f"{VESTIGE_BASE_URL}/api/deep_reference"
@ -43,8 +47,12 @@ VESTIGE_ENDPOINT = (
VESTIGE_HEALTH = ( VESTIGE_HEALTH = (
os.environ.get("VESTIGE_HEALTH_ENDPOINT") or f"{VESTIGE_BASE_URL}/api/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" MODEL = (
MLX_TIMEOUT = env_int("MLX_TIMEOUT", 45) os.environ.get("VESTIGE_SANHEDRIN_MODEL")
or os.environ.get("VESTIGE_SANDWICH_MODEL")
or "mlx-community/Qwen3.6-35B-A3B-4bit"
)
SANHEDRIN_TIMEOUT = env_int("VESTIGE_SANHEDRIN_TIMEOUT", env_int("MLX_TIMEOUT", 45))
VESTIGE_TIMEOUT = env_int("VESTIGE_TIMEOUT", 5) VESTIGE_TIMEOUT = env_int("VESTIGE_TIMEOUT", 5)
THINK_RE = re.compile(r"<think>.*?</think>", re.DOTALL | re.IGNORECASE) THINK_RE = re.compile(r"<think>.*?</think>", re.DOTALL | re.IGNORECASE)
@ -289,7 +297,7 @@ def judge(draft: str, evidence: str) -> str:
"\n\nOn second thought", "\n\nOh wait", "\n\nOn second thought", "\n\nOh wait",
], ],
} }
resp = post_json(MLX_ENDPOINT, body, MLX_TIMEOUT) resp = post_json(SANHEDRIN_ENDPOINT, body, SANHEDRIN_TIMEOUT)
if not isinstance(resp, dict): if not isinstance(resp, dict):
return "" return ""
try: try:

View file

@ -17,25 +17,37 @@
# sanhedrin.sh (2-8s Haiku subagent, may block) → # sanhedrin.sh (2-8s Haiku subagent, may block) →
# synthesis-stop-validator.sh (existing regex hedge check, may block) # synthesis-stop-validator.sh (existing regex hedge check, may block)
# #
# Opt-in: set VESTIGE_SANHEDRIN_ENABLED=1 in parent shell. # Opt-in: set VESTIGE_SANHEDRIN_ENABLED=1 in parent shell, or install with
# scripts/install-sandwich.sh --enable-sanhedrin.
# Re-entrancy lock: VESTIGE_EXECUTIONER_ACTIVE=1 inside the subagent. # Re-entrancy lock: VESTIGE_EXECUTIONER_ACTIVE=1 inside the subagent.
# #
# Ship date 2026-04-20. # Ship date 2026-04-20.
set -u set -u
# === OPT-OUT GATE === # === OPT-IN GATE ===
# Post-Cognitive Sanhedrin is ON by default as of 2026-04-21 (birthday # Sanhedrin is heavyweight: the default local backend is a ~19 GB model and
# launch day). To disable, set VESTIGE_SANHEDRIN_ENABLED=0 in your # needs roughly 20+ GB of free RAM. Keep it disabled unless the user explicitly
# environment. Default-on guarantees the Cognitive Sandwich fires on # opts in. The installer writes this env file only for --enable-sanhedrin.
# fresh machines, Docker containers, GUI-launched Claude Code, and SANHEDRIN_ENV="${VESTIGE_SANHEDRIN_ENV:-$HOME/.claude/hooks/vestige-sanhedrin.env}"
# shells without .zshrc — any case where the Claude Code process lacks if [ -f "$SANHEDRIN_ENV" ]; then
# a sourced profile. The re-entrancy guard (VESTIGE_EXECUTIONER_ACTIVE) set +u
# below still prevents fork-bombs from the subagent's own Stop hook. set -a
if [ "${VESTIGE_SANHEDRIN_ENABLED:-1}" = "0" ]; then # shellcheck disable=SC1090
exit 0 . "$SANHEDRIN_ENV" 2>/dev/null || {
set +a
set -u
exit 0
}
set +a
set -u
fi fi
case "${VESTIGE_SANHEDRIN_ENABLED:-0}" in
1|true|TRUE|yes|YES|on|ON) ;;
*) exit 0 ;;
esac
# === RE-ENTRANCY GUARD === # === RE-ENTRANCY GUARD ===
# The Executioner's own Stop hook will fire when it returns — prevent # The Executioner's own Stop hook will fire when it returns — prevent
# recursive spawns that would fork-bomb the quota. # recursive spawns that would fork-bomb the quota.
@ -114,11 +126,11 @@ if [ -z "$DRAFT" ]; then
fi fi
# === VERIFY local executioner bridge available === # === VERIFY local executioner bridge available ===
# 2026-04-25: switched from Haiku 4.5 subagent to local Qwen3.6-35B-A3B # 2026-04-25: switched from Haiku 4.5 subagent to an OpenAI-compatible
# via mlx_lm.server (launchd com.vestige.mlx-server). Bridge script # local/remote endpoint. On Apple Silicon the optional launchd path starts
# fetches Vestige evidence via HTTP API (VESTIGE_DASHBOARD_PORT, default 3927) # mlx_lm.server; on x86 users can point VESTIGE_SANHEDRIN_ENDPOINT at vLLM,
# then judges via MLX_ENDPOINT (default port 8080). Zero per-token cost, fully offline, # Ollama, llama.cpp, or any compatible /v1/chat/completions endpoint.
# sub-second-to-15s verdict latency. Fail-open if mlx-server unreachable. # Fail-open if the endpoint is unreachable.
BRIDGE="$HOME/.claude/hooks/sanhedrin-local.py" BRIDGE="$HOME/.claude/hooks/sanhedrin-local.py"
if [ ! -x "$BRIDGE" ] && [ ! -f "$BRIDGE" ]; then if [ ! -x "$BRIDGE" ] && [ ! -f "$BRIDGE" ]; then
exit 0 exit 0
@ -191,15 +203,15 @@ case "$TRIMMED" in
$REASON $REASON
The Executioner (local Qwen3.6-35B-A3B via mlx_lm.server, fresh context, The Executioner (Sanhedrin endpoint, fresh context, fed Vestige
fed Vestige deep_reference evidence over HTTP) judged your draft and deep_reference evidence over HTTP) judged your draft and
found a contradiction against a high-trust memory. found a contradiction against a high-trust memory.
You may NOT stop. Rewrite WITHOUT the contradicted claim. Use You may NOT stop. Rewrite WITHOUT the contradicted claim. Use
mcp__vestige__deep_reference to inspect the cited memory and cite the mcp__vestige__deep_reference to inspect the cited memory and cite the
correct replacement pattern from its \`recommended\` field. correct replacement pattern from its \`recommended\` field.
Local-only, zero API cost, fully offline. Bridge script: Bridge script:
~/.claude/hooks/sanhedrin-local.py ~/.claude/hooks/sanhedrin-local.py
SANHEDRIN_MSG SANHEDRIN_MSG
exit 2 exit 2

View file

@ -1,23 +1 @@
{ {}
"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 }
]
}
]
}
}

View file

@ -0,0 +1,14 @@
{
"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 }
]
}
]
}
}

View file

@ -0,0 +1,13 @@
{
"hooks": {
"Stop": [
{
"hooks": [
{ "type": "command", "command": "$HOME/.claude/hooks/veto-detector.sh", "timeout": 6 },
{ "type": "command", "command": "VESTIGE_SANHEDRIN_ENABLED=1 $HOME/.claude/hooks/sanhedrin.sh", "timeout": 70 },
{ "type": "command", "command": "$HOME/.claude/hooks/synthesis-stop-validator.sh", "timeout": 6 }
]
}
]
}
}

View file

@ -5,21 +5,53 @@ set -u
ok() { printf ' \033[1;32m[ OK ]\033[0m %s\n' "$*"; } ok() { printf ' \033[1;32m[ OK ]\033[0m %s\n' "$*"; }
warn() { printf ' \033[1;33m[WARN]\033[0m %s\n' "$*"; FAIL=1; } warn() { printf ' \033[1;33m[WARN]\033[0m %s\n' "$*"; FAIL=1; }
miss() { printf ' \033[1;31m[MISS]\033[0m %s\n' "$*"; FAIL=1; } miss() { printf ' \033[1;31m[MISS]\033[0m %s\n' "$*"; FAIL=1; }
info() { printf ' \033[1;36m[INFO]\033[0m %s\n' "$*"; }
FAIL=0 FAIL=0
CHECK_PREFLIGHT=0
CHECK_SANHEDRIN=0
DASHBOARD_PORT="${VESTIGE_DASHBOARD_PORT:-3927}" DASHBOARD_PORT="${VESTIGE_DASHBOARD_PORT:-3927}"
MLX_ENDPOINT="${MLX_ENDPOINT:-http://127.0.0.1:8080/v1/chat/completions}" SANHEDRIN_ENV="${VESTIGE_SANHEDRIN_ENV:-$HOME/.claude/hooks/vestige-sanhedrin.env}"
MLX_ENDPOINT="${MLX_ENDPOINT%/}"
MLX_MODELS_URL="${MLX_ENDPOINT%/chat/completions}/models" for arg in "$@"; do
case "$arg" in
--preflight|--enable-preflight) CHECK_PREFLIGHT=1 ;;
--sanhedrin|--enable-sanhedrin) CHECK_SANHEDRIN=1 ;;
-h|--help)
cat <<'EOF'
Usage: scripts/check-sandwich-prereqs.sh [--preflight] [--sanhedrin]
Without flags, verifies that the default install has no Vestige hooks wired.
With --preflight, checks the optional UserPromptSubmit hook layer.
With --sanhedrin, checks the optional OpenAI-compatible verifier endpoint.
EOF
exit 0
;;
esac
done
if [ -f "$SANHEDRIN_ENV" ]; then
set +u
set -a
# shellcheck disable=SC1090
. "$SANHEDRIN_ENV" 2>/dev/null || true
set +a
set -u
fi
SANHEDRIN_ENDPOINT="${VESTIGE_SANHEDRIN_ENDPOINT:-${MLX_ENDPOINT:-http://127.0.0.1:8080/v1/chat/completions}}"
SANHEDRIN_ENDPOINT="${SANHEDRIN_ENDPOINT%/}"
SANHEDRIN_MODELS_URL="${SANHEDRIN_ENDPOINT%/chat/completions}/models"
echo "Vestige Cognitive Sandwich — Prereq Check" echo "Vestige Cognitive Sandwich — Prereq Check"
echo echo
# Platform # Platform
if [ "$(uname -s)" = "Darwin" ] && [ "$(uname -m)" = "arm64" ]; then OS_NAME="$(uname -s)"
ok "Apple Silicon macOS ($(sw_vers -productVersion 2>/dev/null || echo darwin))" ARCH_NAME="$(uname -m)"
else ok "Platform: $OS_NAME $ARCH_NAME"
miss "Apple Silicon Mac required (M1+). Detected $(uname -s) $(uname -m)." if [ "$OS_NAME" != "Darwin" ] || [ "$ARCH_NAME" != "arm64" ]; then
info "Local MLX launchd is Apple Silicon-only; base hooks and endpoint-backed Sanhedrin can run on x86."
fi fi
# Python # Python
@ -35,56 +67,95 @@ fi
# CLI tools # CLI tools
command -v jq >/dev/null && ok "jq" || miss "jq missing — brew install jq" command -v jq >/dev/null && ok "jq" || miss "jq missing — brew install jq"
command -v uv >/dev/null && ok "uv" || miss "uv missing — brew install uv" if [ "$CHECK_PREFLIGHT" -eq 1 ]; then
command -v mlx_lm.server >/dev/null && ok "mlx-lm" || miss "mlx-lm — uv tool install mlx-lm" command -v claude >/dev/null && ok "claude CLI" || miss "claude CLI — install Claude Code"
command -v hf >/dev/null && ok "huggingface_hub CLI" || miss "hf — uv tool install 'huggingface_hub[cli]'" command -v vestige-mcp >/dev/null && ok "vestige-mcp" || miss "vestige-mcp — cargo install vestige-mcp"
command -v claude >/dev/null && ok "claude CLI" || miss "claude CLI — install Claude Code"
command -v vestige-mcp >/dev/null && ok "vestige-mcp" || miss "vestige-mcp — cargo install vestige-mcp"
# Model on disk — HF cache uses `models--<org>--<name>` (double-dash separators). # Vestige MCP HTTP API
MODEL="${VESTIGE_SANDWICH_MODEL:-mlx-community/Qwen3.6-35B-A3B-4bit}" if curl -fsS -m 2 "http://127.0.0.1:${DASHBOARD_PORT}/api/health" >/dev/null 2>&1; then
HF_HOME_DEFAULT="${HF_HOME:-$HOME/.cache/huggingface}" ok "vestige-mcp dashboard responding on :$DASHBOARD_PORT"
ENC_MODEL="models--$(printf '%s' "$MODEL" | sed 's|/|--|g')" else
if [ -d "$HF_HOME_DEFAULT/hub/$ENC_MODEL" ]; then warn "vestige-mcp dashboard not responding on :$DASHBOARD_PORT"
ok "Model cached: $MODEL" fi
else
printf ' \033[1;33m[INFO]\033[0m Model not yet downloaded — first run will fetch ~19GB\n'
printf ' hf download %s\n' "$MODEL"
# NOT a failure — first-run download is expected.
fi
# Vestige MCP HTTP API
if curl -fsS -m 2 "http://127.0.0.1:${DASHBOARD_PORT}/api/health" >/dev/null 2>&1; then
ok "vestige-mcp dashboard responding on :$DASHBOARD_PORT"
else
warn "vestige-mcp dashboard not responding on :$DASHBOARD_PORT"
fi
# OpenAI-compatible local/remote model endpoint
if curl -fsS -m 2 "$MLX_MODELS_URL" >/dev/null 2>&1; then
ok "model endpoint responding at $MLX_MODELS_URL"
else
warn "model endpoint not responding at $MLX_MODELS_URL — install + load launchd plist or set MLX_ENDPOINT"
fi
# launchd plist
if [ -f "$HOME/Library/LaunchAgents/com.vestige.mlx-server.plist" ]; then
ok "launchd plist installed"
else
warn "launchd plist missing — run: install-sandwich.sh"
fi fi
# Settings hook wiring # Settings hook wiring
if [ -f "$HOME/.claude/settings.json" ] && \ if [ "$CHECK_PREFLIGHT" -eq 0 ] && [ "$CHECK_SANHEDRIN" -eq 0 ]; then
jq -e '.hooks.UserPromptSubmit and .hooks.Stop' "$HOME/.claude/settings.json" >/dev/null 2>&1; then if [ -f "$HOME/.claude/settings.json" ] && \
ok "settings.json hooks block present" jq -e 'any((.hooks.UserPromptSubmit[]?.hooks[]?, .hooks.Stop[]?.hooks[]?); ((.command? // "") | test("synthesis-preflight\\.sh|cwd-state-injector\\.sh|vestige-pulse-daemon\\.sh|preflight-swarm\\.sh|load-all-memory\\.sh|veto-detector\\.sh|sanhedrin\\.sh|synthesis-stop-validator\\.sh|synthesis-gate\\.sh")))' "$HOME/.claude/settings.json" >/dev/null 2>&1; then
warn "Vestige hooks are still wired; run: install-sandwich.sh --force"
else
ok "no Vestige Claude Code hooks wired by default"
fi
fi
if [ "$CHECK_PREFLIGHT" -eq 1 ]; then
echo
echo "Optional Preflight"
if [ -f "$HOME/.claude/settings.json" ] && \
jq -e 'any(.hooks.UserPromptSubmit[]?.hooks[]?; ((.command? // "") | contains("synthesis-preflight.sh"))) and any(.hooks.UserPromptSubmit[]?.hooks[]?; ((.command? // "") | contains("preflight-swarm.sh")))' "$HOME/.claude/settings.json" >/dev/null 2>&1; then
ok "preflight UserPromptSubmit hooks wired"
else
warn "preflight hooks not wired — run: install-sandwich.sh --enable-preflight"
fi
info "preflight-swarm.sh uses claude -p with Haiku when enabled; default installs do not wire it."
fi
if [ "$CHECK_SANHEDRIN" -eq 1 ]; then
echo
echo "Optional Sanhedrin"
if [ -f "$SANHEDRIN_ENV" ]; then
ok "Sanhedrin env file present"
else
warn "Sanhedrin env file missing — run: install-sandwich.sh --enable-sanhedrin"
fi
if [ "$OS_NAME" = "Darwin" ] && [ "$ARCH_NAME" = "arm64" ]; then
command -v uv >/dev/null && ok "uv" || warn "uv missing — brew install uv"
command -v mlx_lm.server >/dev/null && ok "mlx-lm" || warn "mlx-lm — uv tool install mlx-lm"
command -v hf >/dev/null && ok "huggingface_hub CLI" || warn "hf — uv tool install 'huggingface_hub[cli]'"
MODEL="${VESTIGE_SANHEDRIN_MODEL:-${VESTIGE_SANDWICH_MODEL:-mlx-community/Qwen3.6-35B-A3B-4bit}}"
HF_HOME_DEFAULT="${HF_HOME:-$HOME/.cache/huggingface}"
ENC_MODEL="models--$(printf '%s' "$MODEL" | sed 's|/|--|g')"
if [ -d "$HF_HOME_DEFAULT/hub/$ENC_MODEL" ]; then
ok "Model cached: $MODEL"
else
info "Model not cached: $MODEL (local MLX path downloads ~19GB)"
fi
if [ -f "$HOME/Library/LaunchAgents/com.vestige.mlx-server.plist" ]; then
ok "launchd plist installed"
else
info "launchd plist not installed; endpoint-backed Sanhedrin can still run"
fi
else
info "Skipping MLX/launchd checks on $OS_NAME $ARCH_NAME"
fi
if curl -fsS -m 2 "$SANHEDRIN_MODELS_URL" >/dev/null 2>&1; then
ok "Sanhedrin model endpoint responding at $SANHEDRIN_MODELS_URL"
else
warn "Sanhedrin endpoint not responding at $SANHEDRIN_MODELS_URL"
fi
if [ -f "$HOME/.claude/settings.json" ] && \
jq -e '.hooks.Stop[]?.hooks[]?.command | contains("sanhedrin.sh")' "$HOME/.claude/settings.json" >/dev/null 2>&1; then
ok "Sanhedrin Stop hook wired"
else
warn "Sanhedrin Stop hook not wired — run: install-sandwich.sh --enable-sanhedrin"
fi
else else
warn "settings.json missing hooks block — run: install-sandwich.sh" echo
info "Sanhedrin is optional and not checked. Use --sanhedrin to verify an enabled endpoint."
fi fi
echo echo
if [ $FAIL -eq 0 ]; then if [ $FAIL -eq 0 ]; then
echo " Ready. Cognitive Sandwich will fire on next Claude Code prompt." echo " Ready. Default install has no Vestige Claude Code hooks wired and makes no automatic model calls."
exit 0 exit 0
else else
echo " Fix the items above, then re-run." echo " Fix the items above, then re-run."

View file

@ -4,26 +4,26 @@
# Usage: # Usage:
# curl -fsSL https://raw.githubusercontent.com/samvallad33/vestige/v2.1.0/scripts/install-sandwich.sh | sh # curl -fsSL https://raw.githubusercontent.com/samvallad33/vestige/v2.1.0/scripts/install-sandwich.sh | sh
# # or, from a checkout: # # or, from a checkout:
# ./scripts/install-sandwich.sh [--force] [--no-launchd] [--include-memory-loader] # ./scripts/install-sandwich.sh [--force] [--enable-preflight] [--enable-sanhedrin] [--with-launchd] [--include-memory-loader]
# ./scripts/install-sandwich.sh --enable-sanhedrin --sanhedrin-endpoint=http://127.0.0.1:11434/v1/chat/completions --sanhedrin-model=qwen2.5:14b
# #
# What it does: # What it does:
# 1. Verifies required local tools # 1. Verifies required local tools
# 2. Stages ~/.claude/hooks/, ~/.claude/agents/, ~/Library/LaunchAgents/ # 2. Stages ~/.claude/hooks/ and ~/.claude/agents/
# 3. Copies sanitized hooks + agents # 3. Copies sanitized hooks + agents
# 4. Renders launchd plist template with $HOME and chosen MODEL # 4. Removes old Vestige hook wiring from ~/.claude/settings.json by default
# 5. Merges hooks block into ~/.claude/settings.json (preserves existing keys) # 5. Optionally enables preflight hooks and/or Sanhedrin. Only with --with-launchd on Apple Silicon,
# 6. launchctl load com.vestige.mlx-server (auto-starts mlx_lm.server with Qwen3.6-35B-A3B) # auto-starts mlx_lm.server with Qwen3.6-35B-A3B
# 7. Prints next-steps for model download
set -euo pipefail set -euo pipefail
VERSION="${VESTIGE_SANDWICH_VERSION:-v2.1.0}" VERSION="${VESTIGE_SANDWICH_VERSION:-v2.1.0}"
REPO="samvallad33/vestige" REPO="samvallad33/vestige"
MODEL_ID="${VESTIGE_SANDWICH_MODEL:-mlx-community/Qwen3.6-35B-A3B-4bit}" MODEL_ID="${VESTIGE_SANHEDRIN_MODEL:-${VESTIGE_SANDWICH_MODEL:-mlx-community/Qwen3.6-35B-A3B-4bit}}"
DASHBOARD_PORT="${VESTIGE_DASHBOARD_PORT:-3927}" DASHBOARD_PORT="${VESTIGE_DASHBOARD_PORT:-3927}"
MLX_ENDPOINT="${MLX_ENDPOINT:-http://127.0.0.1:8080/v1/chat/completions}" SANHEDRIN_ENDPOINT="${VESTIGE_SANHEDRIN_ENDPOINT:-${MLX_ENDPOINT:-http://127.0.0.1:8080/v1/chat/completions}}"
MLX_ENDPOINT="${MLX_ENDPOINT%/}" SANHEDRIN_ENDPOINT="${SANHEDRIN_ENDPOINT%/}"
MLX_MODELS_URL="${MLX_ENDPOINT%/chat/completions}/models" SANHEDRIN_MODELS_URL="${SANHEDRIN_ENDPOINT%/chat/completions}/models"
HOOKS_DIR="$HOME/.claude/hooks" HOOKS_DIR="$HOME/.claude/hooks"
AGENTS_DIR="$HOME/.claude/agents" AGENTS_DIR="$HOME/.claude/agents"
@ -31,45 +31,70 @@ LAUNCHD_DIR="$HOME/Library/LaunchAgents"
SETTINGS="$HOME/.claude/settings.json" SETTINGS="$HOME/.claude/settings.json"
FORCE=0 FORCE=0
NO_LAUNCHD=0 ENABLE_PREFLIGHT=0
ENABLE_SANHEDRIN=0
WITH_LAUNCHD=0
INCLUDE_MEMORY_LOADER=0 INCLUDE_MEMORY_LOADER=0
SRC="" SRC=""
for arg in "$@"; do for arg in "$@"; do
case "$arg" in case "$arg" in
--force) FORCE=1 ;; --force) FORCE=1 ;;
--no-launchd) NO_LAUNCHD=1 ;; --enable-preflight) ENABLE_PREFLIGHT=1 ;;
--enable-sandwich) ENABLE_PREFLIGHT=1; ENABLE_SANHEDRIN=1 ;;
--enable-sanhedrin) ENABLE_SANHEDRIN=1 ;;
--with-launchd) WITH_LAUNCHD=1 ;;
--no-launchd) WITH_LAUNCHD=0 ;;
--include-memory-loader) INCLUDE_MEMORY_LOADER=1 ;; --include-memory-loader) INCLUDE_MEMORY_LOADER=1 ;;
--sanhedrin-endpoint=*|--endpoint=*)
SANHEDRIN_ENDPOINT="${arg#*=}"
SANHEDRIN_ENDPOINT="${SANHEDRIN_ENDPOINT%/}"
SANHEDRIN_MODELS_URL="${SANHEDRIN_ENDPOINT%/chat/completions}/models"
;;
--sanhedrin-model=*|--model=*)
MODEL_ID="${arg#*=}"
;;
--src=*) SRC="${arg#--src=}" ;; --src=*) SRC="${arg#--src=}" ;;
-h|--help) -h|--help)
sed -n '2,20p' "$0" sed -n '2,24p' "$0"
exit 0 exit 0
;; ;;
esac esac
done done
if [ "$WITH_LAUNCHD" -eq 1 ] && [ "$ENABLE_SANHEDRIN" -eq 0 ]; then
ENABLE_SANHEDRIN=1
fi
say() { printf '\033[1;36m[sandwich]\033[0m %s\n' "$*"; } say() { printf '\033[1;36m[sandwich]\033[0m %s\n' "$*"; }
warn() { printf '\033[1;33m[sandwich]\033[0m %s\n' "$*" >&2; } warn() { printf '\033[1;33m[sandwich]\033[0m %s\n' "$*" >&2; }
die() { printf '\033[1;31m[sandwich]\033[0m %s\n' "$*" >&2; exit 1; } die() { printf '\033[1;31m[sandwich]\033[0m %s\n' "$*" >&2; exit 1; }
# --- Platform check (honors --no-launchd for Linux/Intel users) --- # --- Platform check ---
if [ "$(uname -s)" != "Darwin" ]; then OS_NAME="$(uname -s)"
if [ "$NO_LAUNCHD" -eq 0 ]; then ARCH_NAME="$(uname -m)"
die "macOS required for the launchd auto-start of mlx_lm.server. Re-run with --no-launchd to install hooks only and run mlx_lm.server manually." say "platform: $OS_NAME $ARCH_NAME"
fi if [ "$ENABLE_SANHEDRIN" -eq 1 ] && [ "$WITH_LAUNCHD" -eq 0 ]; then
warn "Non-Darwin platform — installing hooks/agents only (no launchd). Run an OpenAI-compatible model endpoint and set MLX_ENDPOINT if it is not $MLX_ENDPOINT." say "Sanhedrin enabled without launchd; using OpenAI-compatible endpoint: $SANHEDRIN_ENDPOINT"
elif [ "$(uname -m)" != "arm64" ]; then fi
warn "Apple Silicon recommended (M1+). Detected $(uname -m). The local Qwen3.6 model requires arm64 + Metal." if [ "$WITH_LAUNCHD" -eq 1 ] && { [ "$OS_NAME" != "Darwin" ] || [ "$ARCH_NAME" != "arm64" ]; }; then
warn "--with-launchd is Apple Silicon only; skipping local MLX autostart on $OS_NAME $ARCH_NAME"
warn "Sanhedrin can still run on x86 via --sanhedrin-endpoint or VESTIGE_SANHEDRIN_ENDPOINT."
WITH_LAUNCHD=0
fi fi
# --- Prereqs (warnings only, install proceeds) --- # --- Prereqs (warnings only, install proceeds) ---
command -v jq >/dev/null || die "jq required: brew install jq" command -v jq >/dev/null || die "jq required: brew install jq"
command -v python3 >/dev/null || die "python3 required (3.10+)" command -v python3 >/dev/null || die "python3 required (3.10+)"
command -v claude >/dev/null || warn "'claude' CLI not found — install Claude Code first." if [ "$ENABLE_PREFLIGHT" -eq 1 ]; then
command -v vestige-mcp >/dev/null || warn "'vestige-mcp' not found — install with: cargo install vestige-mcp" command -v claude >/dev/null || warn "'claude' CLI not found — preflight-swarm.sh will fail open."
command -v uv >/dev/null || warn "'uv' not found — install with: brew install uv" command -v vestige-mcp >/dev/null || warn "'vestige-mcp' not found — Vestige preflight hooks will fail open."
command -v mlx_lm.server >/dev/null || warn "mlx-lm not installed — run: uv tool install mlx-lm" fi
command -v hf >/dev/null || warn "'hf' not found — run: uv tool install 'huggingface_hub[cli]'" if [ "$WITH_LAUNCHD" -eq 1 ]; then
command -v uv >/dev/null || warn "'uv' not found — install with: brew install uv"
command -v mlx_lm.server >/dev/null || warn "mlx-lm not installed — run: uv tool install mlx-lm"
command -v hf >/dev/null || warn "'hf' not found — run: uv tool install 'huggingface_hub[cli]'"
fi
# --- Resolve source: local checkout or release tarball --- # --- Resolve source: local checkout or release tarball ---
if [ -n "$SRC" ]; then if [ -n "$SRC" ]; then
@ -89,7 +114,21 @@ fi
[ -d "$SCRIPT_DIR/hooks" ] || die "hooks/ not found in $SCRIPT_DIR — wrong source?" [ -d "$SCRIPT_DIR/hooks" ] || die "hooks/ not found in $SCRIPT_DIR — wrong source?"
# --- Stage directories --- # --- Stage directories ---
mkdir -p "$HOOKS_DIR" "$AGENTS_DIR" "$LAUNCHD_DIR" mkdir -p "$HOOKS_DIR" "$AGENTS_DIR"
if [ "$WITH_LAUNCHD" -eq 1 ]; then
mkdir -p "$LAUNCHD_DIR"
fi
# v2.1.0 originally installed the MLX server as part of the default path.
# Default reinstalls now retire that job; users can restore it with --with-launchd.
if [ "$WITH_LAUNCHD" -eq 0 ] && [ "$OS_NAME" = "Darwin" ]; then
LEGACY_PLIST="$LAUNCHD_DIR/com.vestige.mlx-server.plist"
if [ -f "$LEGACY_PLIST" ]; then
launchctl unload "$LEGACY_PLIST" 2>/dev/null || true
rm -f "$LEGACY_PLIST"
say "removed old Sanhedrin launchd job (use --with-launchd to opt back in)"
fi
fi
# --- Copy hooks --- # --- Copy hooks ---
copied=0; skipped=0 copied=0; skipped=0
@ -121,8 +160,25 @@ for f in "$SCRIPT_DIR/agents"/*.md; do
done done
say "agents installed to $AGENTS_DIR" say "agents installed to $AGENTS_DIR"
# --- Render launchd plist (macOS only) --- # --- Persist optional Sanhedrin env ---
if [ "$NO_LAUNCHD" -eq 0 ]; then quote_env() {
printf "'%s'" "$(printf '%s' "$1" | sed "s/'/'\\\\''/g")"
}
if [ "$ENABLE_SANHEDRIN" -eq 1 ]; then
SANHEDRIN_ENV="$HOOKS_DIR/vestige-sanhedrin.env"
{
printf 'VESTIGE_SANHEDRIN_ENABLED=1\n'
printf 'VESTIGE_SANHEDRIN_ENDPOINT=%s\n' "$(quote_env "$SANHEDRIN_ENDPOINT")"
printf 'VESTIGE_SANHEDRIN_MODEL=%s\n' "$(quote_env "$MODEL_ID")"
printf 'VESTIGE_DASHBOARD_PORT=%s\n' "$(quote_env "$DASHBOARD_PORT")"
} > "$SANHEDRIN_ENV"
chmod 0600 "$SANHEDRIN_ENV"
say "Sanhedrin opt-in config written to $SANHEDRIN_ENV"
fi
# --- Render launchd plist (Apple Silicon opt-in only) ---
if [ "$WITH_LAUNCHD" -eq 1 ]; then
PLIST="$LAUNCHD_DIR/com.vestige.mlx-server.plist" PLIST="$LAUNCHD_DIR/com.vestige.mlx-server.plist"
TEMPLATE="$SCRIPT_DIR/launchd/com.vestige.mlx-server.plist.template" TEMPLATE="$SCRIPT_DIR/launchd/com.vestige.mlx-server.plist.template"
[ -f "$TEMPLATE" ] || die "launchd template missing: $TEMPLATE" [ -f "$TEMPLATE" ] || die "launchd template missing: $TEMPLATE"
@ -140,31 +196,80 @@ else
cp "$SETTINGS" "$HOME/.claude/settings.json.bak.pre-sandwich" cp "$SETTINGS" "$HOME/.claude/settings.json.bak.pre-sandwich"
fi fi
TMP_MERGE="$(mktemp)" TMP_MERGE="$(mktemp)"
jq -s '.[0] * .[1]' "$SETTINGS" "$SCRIPT_DIR/hooks/settings.fragment.json" > "$TMP_MERGE" PREFLIGHT_FRAGMENT="$SCRIPT_DIR/hooks/settings.fragment.json"
SANHEDRIN_FRAGMENT="$SCRIPT_DIR/hooks/settings.fragment.json"
if [ "$ENABLE_PREFLIGHT" -eq 1 ]; then
PREFLIGHT_FRAGMENT="$SCRIPT_DIR/hooks/settings.preflight.fragment.json"
fi
if [ "$ENABLE_SANHEDRIN" -eq 1 ]; then
SANHEDRIN_FRAGMENT="$SCRIPT_DIR/hooks/settings.sanhedrin.fragment.json"
fi
jq -s '
def is_vestige_hook:
(.command? // "") as $cmd
| [
"synthesis-preflight.sh",
"cwd-state-injector.sh",
"vestige-pulse-daemon.sh",
"preflight-swarm.sh",
"load-all-memory.sh",
"veto-detector.sh",
"sanhedrin.sh",
"synthesis-stop-validator.sh",
"synthesis-gate.sh"
] | any(. as $needle | $cmd | contains($needle));
def scrub_vestige_hooks:
.hooks.UserPromptSubmit = (
(.hooks.UserPromptSubmit // [])
| map(.hooks = ((.hooks // []) | map(select((is_vestige_hook | not)))))
| map(select(((.hooks // []) | length) > 0))
)
| if ((.hooks.UserPromptSubmit // []) | length) == 0 then del(.hooks.UserPromptSubmit) else . end
| .hooks.Stop = (
(.hooks.Stop // [])
| map(.hooks = ((.hooks // []) | map(select((is_vestige_hook | not)))))
| map(select(((.hooks // []) | length) > 0))
)
| if ((.hooks.Stop // []) | length) == 0 then del(.hooks.Stop) else . end
| if ((.hooks // {}) | length) == 0 then del(.hooks) else . end;
(.[0] | scrub_vestige_hooks) * .[1] * .[2]
' "$SETTINGS" "$PREFLIGHT_FRAGMENT" "$SANHEDRIN_FRAGMENT" > "$TMP_MERGE"
mv "$TMP_MERGE" "$SETTINGS" mv "$TMP_MERGE" "$SETTINGS"
say "merged hooks block into $SETTINGS (backup at .bak.pre-sandwich)" if [ "$ENABLE_PREFLIGHT" -eq 1 ] || [ "$ENABLE_SANHEDRIN" -eq 1 ]; then
enabled_layers=""
[ "$ENABLE_PREFLIGHT" -eq 1 ] && enabled_layers="${enabled_layers} preflight"
[ "$ENABLE_SANHEDRIN" -eq 1 ] && enabled_layers="${enabled_layers} sanhedrin"
say "merged optional hook layer(s) into $SETTINGS:${enabled_layers} (backup at .bak.pre-sandwich)"
else
say "removed Vestige hook wiring from $SETTINGS; default install activates no Claude Code hooks (backup at .bak.pre-sandwich)"
fi
# --- Next steps --- # --- Next steps ---
cat <<EOF cat <<EOF
┌──────────────────────────────────────────────────────────────┐ ┌──────────────────────────────────────────────────────────────┐
│ Cognitive Sandwich installed. │ │ Cognitive Sandwich files installed. No hooks enabled by default.
└──────────────────────────────────────────────────────────────┘ └──────────────────────────────────────────────────────────────┘
Next steps: Next steps:
1. Download the local model (~19 GB, one-time): 1. Restart Claude Code if you enabled optional hooks.
hf download $MODEL_ID Default installs activate no Vestige Claude Code hooks and make no model calls.
2. Restart Claude Code so it picks up the new hooks. 2. Verify the install:
3. Verify the install:
vestige health # if vestige CLI installed vestige health # if vestige CLI installed
curl http://127.0.0.1:$DASHBOARD_PORT/api/health curl http://127.0.0.1:$DASHBOARD_PORT/api/health
curl $MLX_MODELS_URL scripts/check-sandwich-prereqs.sh # from a checkout
4. Try a prompt — the Sanhedrin Stop hook will fire and judge 3. Optional hook layers:
Claude's draft against your Vestige memory before delivery. ./scripts/install-sandwich.sh --enable-preflight
./scripts/install-sandwich.sh --enable-sanhedrin --sanhedrin-endpoint=$SANHEDRIN_ENDPOINT --sanhedrin-model=$MODEL_ID
On Apple Silicon with >20 GB free RAM, add --with-launchd to auto-start
the local MLX Qwen server. On x86, point --sanhedrin-endpoint at vLLM,
Ollama, llama.cpp, or another OpenAI-compatible /v1/chat/completions URL.
To uninstall: To uninstall:
launchctl unload $LAUNCHD_DIR/com.vestige.mlx-server.plist launchctl unload $LAUNCHD_DIR/com.vestige.mlx-server.plist 2>/dev/null || true
rm $LAUNCHD_DIR/com.vestige.mlx-server.plist rm -f $LAUNCHD_DIR/com.vestige.mlx-server.plist
cp $HOME/.claude/settings.json.bak.pre-sandwich $HOME/.claude/settings.json cp $HOME/.claude/settings.json.bak.pre-sandwich $HOME/.claude/settings.json
EOF EOF