remem-ai 0.4.4

Persistent memory for Claude Code — single binary, zero subprocesses
Documentation
use anyhow::Result;
use rmcp::model::{ServerCapabilities, ServerInfo};
use rmcp::{tool_handler, ServerHandler, ServiceExt};

use super::MemoryServer;
use crate::db;

const SERVER_INSTRUCTIONS: &str = r#"Persistent memory for Claude Code and Codex sessions.

## Workflow
1. **Context index** is auto-injected at session start (titles + types, ~50 tokens each)
2. When you need details: `search(query)` → get matching IDs
3. Then: `get_observations(ids)` → full narrative, facts, concepts, files
4. Use `timeline(anchor/query)` to understand chronological context around a change
5. Use `save_memory(text)` to persist important decisions or discoveries (and local markdown backup)
6. If curated `search` is empty/sparse, it auto-attaches `raw_hits` from the raw archive (every user/assistant turn captured at Stop time). Call `search_raw(query)` directly when you need to recall a literal phrase that was never summarized or promoted.

## Local document rule
- If user asks to save/write/update a document, create or edit a local file first
- `save_memory` is long-term memory backup, not a replacement for project docs

## When to search
- User asks about past work, previous sessions, or "what did we do"
- You need implementation details for code you're about to modify
- Debugging an issue that may have been fixed before
- Looking for architecture decisions or rationale

## Search strategy for complex questions
- **Decompose** complex questions into 2-3 focused sub-queries and call search() for each
- **Iterate**: if <5 results, extract names/entities from results and search again
- **Multi-hop**: set multi_hop=true when spanning multiple people or topics

## When to save memory (MUST follow)
Call `save_memory` immediately when:
1. **Making a technical decision** → type=decision, record what was chosen, why, what was rejected
2. **Fixing a bug** → type=bugfix, record root cause, fix, how to prevent
3. **Discovering a code constraint/pattern** → type=discovery, record finding and impact
4. **Completing a feature module** → type=architecture, record design points and file structure
5. **Learning a user preference** → type=preference, record preference and reasoning

## topic_key rules
- Same topic MUST use a stable topic_key — cross-session updates to same memory instead of duplicates
- Format: kebab-case descriptive key, e.g. "fts5-search-strategy", "auth-middleware-design"
- Before saving, search first to check if a memory on this topic already exists

## Do NOT save
- Single file edits (git tracks these)
- Temporary debugging steps (only save conclusions)
- Content that duplicates an existing memory (search first)

## Tips
- The context index is usually sufficient — only fetch details when needed
- bugfix and decision types often contain critical context worth fetching
- Search supports project filter to scope results
- Observations with status="stale" may be outdated. Prefer active observations when available.

## WorkStreams
- `workstreams(project)` lists active high-level tasks tracked across sessions
- `update_workstream(id, status?, next_action?, blockers?)` manually updates a workstream
- WorkStreams are auto-created from session summaries — no manual creation needed"#;

#[tool_handler]
impl ServerHandler for MemoryServer {
    fn get_info(&self) -> ServerInfo {
        ServerInfo {
            instructions: Some(SERVER_INSTRUCTIONS.into()),
            capabilities: ServerCapabilities::builder().enable_tools().build(),
            ..Default::default()
        }
    }
}

pub async fn run_mcp_server() -> Result<()> {
    let db_path = crate::db::db_path();
    let db_exists = db_path.exists();
    crate::log::info(
        "mcp",
        &format!(
            "server starting db={} exists={}",
            db_path.display(),
            db_exists
        ),
    );
    let server = MemoryServer::new()?;
    if let Ok(conn) = db::open_db() {
        let mem_count: i64 = conn
            .query_row("SELECT count(*) FROM memories", [], |row| row.get(0))
            .unwrap_or(-1);
        let obs_count: i64 = conn
            .query_row("SELECT count(*) FROM observations", [], |row| row.get(0))
            .unwrap_or(-1);
        crate::log::info(
            "mcp",
            &format!(
                "server ready memories={} observations={}",
                mem_count, obs_count
            ),
        );
    }
    let service = server.serve(rmcp::transport::stdio()).await?;
    service.waiting().await?;
    crate::log::info("mcp", "server stopped");
    Ok(())
}