From 5772cdcb19cfa07de4583c1e65f0098f2b4797ec Mon Sep 17 00:00:00 2001 From: Sam Valladares Date: Sat, 18 Apr 2026 18:19:02 -0500 Subject: [PATCH] feat(mcp): opt-in VESTIGE_SYSTEM_PROMPT_MODE=full composition mandate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The MCP `instructions` field is injected into every connecting client's system prompt on Initialize — Claude Code, Cursor, Zed, Windsurf, and anyone else running Vestige. That reach demands a default that serves every user, not the project maintainer's personal workflow. Default ("minimal", 3 sentences) tells the client how to use Vestige and how to respond to explicit feedback signals. It is safe for every audience: competitive coders, hobbyists saving recipes, Rails devs saving bug fixes, enterprise deployments under system-prompt review. The full composition mandate — Composing / Never-composed / Recommendation shape + FSRS-trust blocking phrase + origin case study — is load-bearing for decision-adjacent work but misfires on trivial retrievals ("what's my favorite color"). Opt in on your own machine with `VESTIGE_SYSTEM_PROMPT_MODE=full`; four hundred strangers do not inherit one maintainer's trauma scar on every session. Extracted into build_instructions() so the branch is a single env-var check on Initialize, not a compile-time switch. main.rs --help output advertises the new variable alongside VESTIGE_DASHBOARD_ENABLED. --- crates/vestige-mcp/src/main.rs | 3 ++ crates/vestige-mcp/src/server.rs | 53 +++++++++++++++++++++++++++----- 2 files changed, 48 insertions(+), 8 deletions(-) diff --git a/crates/vestige-mcp/src/main.rs b/crates/vestige-mcp/src/main.rs index 4ac6237..32681ed 100644 --- a/crates/vestige-mcp/src/main.rs +++ b/crates/vestige-mcp/src/main.rs @@ -91,6 +91,9 @@ fn parse_args() -> Config { println!(" VESTIGE_HTTP_PORT HTTP transport port (default: 3928)"); println!(" VESTIGE_DASHBOARD_ENABLED Enable dashboard (default: disabled)"); println!(" VESTIGE_DASHBOARD_PORT Dashboard port (default: 3927)"); + println!( + " VESTIGE_SYSTEM_PROMPT_MODE Inject the full composition mandate into every MCP session (minimal|full, default: minimal)" + ); println!(); println!("EXAMPLES:"); println!(" vestige-mcp"); diff --git a/crates/vestige-mcp/src/server.rs b/crates/vestige-mcp/src/server.rs index 4f3f1e3..c6d87b1 100644 --- a/crates/vestige-mcp/src/server.rs +++ b/crates/vestige-mcp/src/server.rs @@ -22,6 +22,50 @@ use crate::resources; use crate::tools; use vestige_core::Storage; +/// Build the MCP `instructions` string injected into every connecting client's +/// system prompt. +/// +/// Default ("minimal", 3 sentences) is safe for any user: competitive coders, +/// hobbyists saving recipes, Rails devs saving bug fixes, enterprise deployments. +/// It earns its per-session token cost by telling the client *how* to use +/// Vestige without imposing one maintainer's workflow on strangers. +/// +/// The "full" variant is the composition mandate that enforces the +/// Composing / Never-composed / Recommendation response shape, names the +/// AIMO3 36/50 case study as the origin, and includes the "Vestige is +/// blocking this:" refusal phrase. It is load-bearing for Sam's own +/// decision-adjacent work but would misfire on trivial retrievals for a +/// general audience, so it is opt-in via `VESTIGE_SYSTEM_PROMPT_MODE=full`. +/// +/// Anything other than `full` falls back to minimal. +fn build_instructions() -> String { + let mode = std::env::var("VESTIGE_SYSTEM_PROMPT_MODE").unwrap_or_default(); + if mode.eq_ignore_ascii_case("full") { + "Vestige is your long-term cognitive memory AND reasoning engine, not a RAG database. \ + Every retrieval MUST be composed into a recommendation, never summarized.\ + \n\nCOMPOSITION MANDATE: When you receive memories from search, deep_reference, \ + cross_reference, or explore_connections, your response MUST follow this shape. \ + (a) Composing: [memory IDs], followed by your composition logic (your chain-of-thought \ + about how the memories relate, NOT a restatement of their contents). \ + (b) Never-composed detected: list combinations of retrieved memories that share \ + tags/topics but have never been referenced together, or write 'None.' \ + (c) Recommendation: what the user should DO, as a concrete executable action. \ + If your draft begins 'Memory A says X. Memory B says Y.' STOP and rewrite.\ + \n\nBLOCKING PHRASE: If retrieved high-trust memories (retention > 0.7, reps > 0) \ + contradict what you were about to say, start your response with 'Vestige is blocking this:' \ + and surface the contradiction verbatim before proceeding. FSRS trust overrides fresh guesses.\ + \n\nFEEDBACK: If the user confirms a memory was helpful, call memory(action='promote'). \ + If they correct it, call memory(action='demote'). Do not ask permission, just act." + .to_string() + } else { + "Vestige is your long-term memory system. Compose retrievals into recommendations \ + rather than listing their contents when the user is making a decision. \ + On user feedback, call memory(action='promote') for helpful retrievals and \ + memory(action='demote') for wrong ones — do not ask permission, just act." + .to_string() + } +} + /// MCP Server implementation pub struct McpServer { storage: Arc, @@ -157,14 +201,7 @@ impl McpServer { }), prompts: None, }, - instructions: Some( - "Vestige is your long-term memory system. Use it to remember important information, \ - recall past knowledge, and maintain context across sessions. The system uses \ - FSRS-6 spaced repetition to naturally decay memories over time. \ - \n\nFeedback Protocol: If the user explicitly confirms a memory was helpful, use \ - memory(action='promote'). If they correct a hallucination or say a memory was wrong, use \ - memory(action='demote'). Do not ask for permission - just act on their feedback.".to_string() - ), + instructions: Some(build_instructions()), }; serde_json::to_value(result).map_err(|e| JsonRpcError::internal_error(&e.to_string()))