Skip to main content
Glama
pch8286

Seahorse RAG MCP

by pch8286
README.md
# Seahorse RAG MCP

Seahorse RAG MCP is a local MCP memory server for edge devices that can connect related facts across passages instead of only returning the nearest lexical match.

## Quick Proof

Measured on the committed fixture [`scripts/data/proof_pi5_zram.json`](scripts/data/proof_pi5_zram.json) on this Raspberry Pi 5 (4GB).

| Method | Retrieval mode | Measured top result | Target hit @1 | Time |
| :--- | :--- | :--- | :--- | :--- |
| Vector-only baseline | `same embeddings, cosine top-1, no graph expansion` | `On Raspberry Pi 5, that kind of slowdown usually means memory pressure rather than a CPU bottleneck.` | No | `0.752 ms` |
| Seahorse | `graph retrieval + phrase expansion + synonym linking` | `Raspberry Pi 5 users often enable zram so compressed pages stay in memory before disk swap.` | Yes | `3379.842 ms` |

Proof query: `What should I switch on when a Pi 5 starts crawling during on-device inference?`

Proof artifact: [docs/proofs/pi5_zram_proof_2026-04-11.json](docs/proofs/pi5_zram_proof_2026-04-11.json)

This is a small measured repro case, not a comprehensive benchmark. Results may change with different corpora, models, or hardware.

Actual CLI example:

```bash
seahorse index --text "Raspberry Pi 5 can bog down when a local model overruns RAM."
seahorse index --text "On Raspberry Pi 5, that kind of slowdown usually means memory pressure rather than a CPU bottleneck."
seahorse index --text "Raspberry Pi 5 users often enable zram so compressed pages stay in memory before disk swap."
seahorse search "What should I switch on when a Pi 5 starts crawling during on-device inference?"
```

```text
Indexing content...
Indexing complete.
Indexing content...
Indexing complete.
Indexing content...
Indexing complete.
Searching for: What should I switch on when a Pi 5 starts crawling during on-device inference?

Results:
Raspberry Pi 5 users often enable zram so compressed pages stay in memory before disk swap.
```

## Quick Start

| If you want to... | Run this |
| :--- | :--- |
| **Most users: try the MCP server immediately** | `uvx --from git+https://github.com/pch8286/seahorse-rag-mcp.git seahorse-server` |
| **Install the local CLI** | `git clone https://github.com/pch8286/seahorse-rag-mcp.git && cd seahorse-rag-mcp && pip install .` |
| **Develop or run tests** | `git clone https://github.com/pch8286/seahorse-rag-mcp.git && cd seahorse-rag-mcp && pip install -e ".[dev]"` |

`uvx ... seahorse-server` and `seahorse run` both start a long-lived stdio MCP server. A quiet terminal with the process still running is the expected success state until an MCP client connects.

## What You Get

| Surface | What it does | Example |
| :--- | :--- | :--- |
| `seahorse-server` | Runs the MCP stdio server | `uvx --from git+https://github.com/pch8286/seahorse-rag-mcp.git seahorse-server` |
| `add_document(text)` | Adds text into the graph | Store notes, docs, facts, preferences |
| `search(query, session_id)` | Retrieves linked context | Ask follow-up questions across multiple indexed passages |
| `upsert_memory(...)` | Writes structured high-integrity memory | Save preferences, rules, decisions |
| `delete_memory(...)` | Removes structured memory by key | Clean up outdated facts |
| `graph://stats` | Exposes graph stats as an MCP resource | Inspect node / edge counts |
| `seahorse` CLI | Local indexing and search without an MCP client | `seahorse index ...`, `seahorse search ...` |

## What Gets Created

| After you use it | Where it appears | Why you care |
| :--- | :--- | :--- |
| Local graph database | `./data/knowledge_graph.db` | Your indexed memory persists here |
| Optional ONNX model bundle | `models/gliner_onnx` | Used for local entity extraction |
| Optional quantized embedding bundle | `models_quantized/...` | Used for faster local embedding on constrained hardware |
| Possible upstream model cache | platform-specific Hugging Face cache | First-time model downloads may also populate external caches |

## Performance On This Pi 5

These are reproducible operational numbers measured on a **Raspberry Pi 5 (4GB)** on **2026-04-11**.

| Metric | Result | Why it matters |
| :--- | :--- | :--- |
| Startup ready | **11.167s** | Full model warmup before first heavy real query |
| Search latency | **6.098s/query** | Tiny public smoke-eval average on this machine |
| Indexing duration | **43.164s** for 4 docs | Small-batch local ingestion reference |
| Peak RSS | **1476 MB** | Important on 4GB edge hardware |
| Public smoke eval | **100% raw recall**, **99.17% adaptive recall** | Tiny public sanity set, not a leaderboard benchmark |

Full snapshot and caveats: [docs/PERFORMANCE_SNAPSHOT_PI5_2026-04-11.md](docs/PERFORMANCE_SNAPSHOT_PI5_2026-04-11.md)

This proof is generated by [`scripts/proof_baseline.py`](scripts/proof_baseline.py). Candidate fixture search lives in [`scripts/search_proof_candidates.py`](scripts/search_proof_candidates.py).

## Who It's For
- **MCP users** who want a local memory/search server behind tools like Claude Desktop, Cursor, Windsurf, or OpenClaw.
- **Agent builders** who want a small Python package exposing graph-backed retrieval rather than standing up a separate vector database service.
- **Edge/offline tinkerers** who care about retrieval quality on constrained machines such as Raspberry Pi 5.

This is a better fit if you want graph-backed retrieval and local control. It is not meant to replace a full hosted search stack or a general-purpose cloud vector database.

## ๐ŸŽฏ Use Cases
Seahorse is designed for high-precision retrieval in environments where reliability and offline capability are paramount:

*   **Personal Knowledge Assistant**: Index your notes and local documents for a second brain that works 100% offline.
*   **Technical Manuals**: Gives field engineers high-recall access to complex equipment manuals without needing an internet connection.
*   **Field Operations**: Fast, secure RAG for agents who need to reason over mission-critical data in the field.
*   **Smart Homes**: Helps local LLMs understand the relationships between devices, routines, and user habits.

## Features
- **HippoRAG 2 Architecture**: Uses passage and phrase nodes with dense-sparse integration.
- **Reliability**: Hub-aware residual teleport (`p_self`, `alpha`, seed-hub `t_H`), sink handling for dangling nodes, and recursion safety to prevent search explosions.
- **Edge Performance**: Built on SQLite for storage (no heavy in-memory loading), uses GLiNER for extraction, and iGraph for fast retrieval.
- **Local INT8 Re-ranking**: Optional ONNX cross-encoder reranker for Top-N precision lift without LLM dependency.
- **Memory Management**: Automatically adjusts retrieval depth based on system RAM and zram.
- **Contextual Search**: Tracks your conversation history to improve relevance while preventing "echo chambers" through drift control.
- **MCP Native**: Plugs directly into any MCP-compliant agent as a tool or skill.

## Retrieval Status
- **Default path**: `igraph.personalized_pagerank` (stable production mode).
- **Applied improvements**: dynamic node budget, typed fanout cap, type budget pruning, degree-based hub self-loop, residual teleport, seed-hub self-return suppression, dynamic damping.
- **Future work (optional mode)**:
  - Truncated PPR (K-step) with mass-policy checks.
  - COO one-shot sparse step kernel + fully vectorized 2-pass pruning for very large edge sets.

## ๐Ÿš€ Quick Start

### 0. Choose Your Path

| If you want to... | Recommended path |
| :--- | :--- |
| **Try Seahorse as an MCP server without cloning the repo** | `uvx --from git+https://github.com/pch8286/seahorse-rag-mcp.git seahorse-server` |
| **Install the CLI locally on your machine** | Clone the repo and run `pip install .` |
| **Develop, run tests, or edit the code** | Clone the repo and run `pip install -e ".[dev]"` |

### 1. Prerequisites
- Python **3.11+**
- `pip` or [**uv**](https://github.com/astral-sh/uv)
- On Linux/ARM systems, be ready to install `libigraph-dev` if a suitable `python-igraph` wheel is not available

For the fastest experience and pre-built wheel support, we recommend `uv`:
```bash
# macOS/Linux
curl -LsSf https://astral.sh/uv/install.sh | sh

# Helpful on systems where python-igraph needs system headers
sudo apt-get install -y libigraph-dev
```

### 1.1 What You Actually Need To Install
- **Just run the server locally**: Python 3.11+, then `pip install .` or use `uvx`.
- **Develop or run tests**: `pip install -e ".[dev]"`.
- **Use vector search reliably**: a Python build with SQLite loadable extension support.
- **Use the ONNX GLiNER path**: run `seahorse models` once after install.

### 2. Run Without Cloning
This is the fastest copy-paste path if you just want a portable MCP server process:
```bash
uvx --from git+https://github.com/pch8286/seahorse-rag-mcp.git seahorse-server
```

The expected success state is a long-running process waiting on stdio for an MCP client.

### 3. Clone And Install Locally
```bash
git clone https://github.com/pch8286/seahorse-rag-mcp.git
cd seahorse-rag-mcp
pip install .
```

For development:
```bash
pip install -e ".[dev]"
```

### 3.1 Development And Test Setup
If you want to run the test suite, install the `dev` extra first:
```bash
pip install -e ".[dev]"
pytest -q
```

Optional `pyenv` workflow:
```bash
pyenv exec python -V
pyenv exec python -m pytest -q
```

### 4. OpenClaw Preset
The repository ships an OpenClaw preset at [`presets/openclaw.json`](presets/openclaw.json).
The preset is the reusable integration surface. The raw `uvx --from ... seahorse-server` command above is just the underlying portable launch command.

### 5. Vector Search Requirement
**Vector Search requires SQLite Extension Support.**
Standard Python builds on some platforms (e.g., Ubuntu/Debian system Python) may be compiled without `--enable-loadable-sqlite-extensions`.
- If extensions are **unavailable**, Seahorse will safely disable vector search and fall back to pure graph traversal.
- To enable vector search, ensure your Python environment supports `sqlite3.enable_load_extension`.

### 6. Run The Server
Start the server directly:
```bash
seahorse run
```

This also stays alive as a long-running stdio MCP server rather than printing a short demo result.

Legacy compatibility commands `pam run` and `hippo run` are still supported.

### 6.1 One-Time Model Setup
Download/prepare ONNX GLiNER bundle:
```bash
seahorse models
```
This uses the default repo `lmo3/gliner2-multi-v1-onnx` and local dir `models/gliner_onnx`.
If `seahorse` is not installed on PATH yet, run the Python module entrypoint:
```bash
python -m seahorse.cli models
```
Legacy `python -m edge_hippo.cli models` still works.

### 6.2 What The MCP Server Exposes
- Tool: `add_document(text)`
- Tool: `search(query, session_id="default")`
- Tool: `upsert_memory(key, value, scope="global", kind="fact", ...)`
- Tool: `delete_memory(key, scope="global")`
- Resource: `graph://stats`

### 7. CLI Usage (Optional)
Manage the knowledge graph from your terminal:
```bash
# Show stats
seahorse stats

# Index text
seahorse index --text "Raspberry Pi 5 is the latest model."

# Link synonyms (Critical for hybrid search)
seahorse optimize-graph --threshold 0.6

# Search
seahorse search "RPi 5"
```

## Example: End-To-End CLI

```bash
seahorse index --text "Raspberry Pi 5 has 8GB of RAM."
seahorse index --text "ZRAM can reduce memory pressure on Raspberry Pi devices."
seahorse search "How can I reduce memory pressure on Raspberry Pi 5?"
```

Typical indexing output:
```text
Indexing content...
Indexing complete.
```

Typical search output:
```text
Searching for: How can I reduce memory pressure on Raspberry Pi 5?

Results:
ZRAM can reduce memory pressure on Raspberry Pi devices.
```

## Runtime Notes
- Default storage path: `./data/knowledge_graph.db`
- Settings can be overridden with environment variables or a local `.env` file
- First use may auto-download model assets if `GLINER_ONNX_AUTO_SETUP=True`
- If you want deterministic setup for demos or deployment, run `seahorse models` before starting the server

## โš™๏ธ Configuration
| Variable | Default | Description |
|----------|---------|-------------|
| `GLINER_MODEL` | `fastino/gliner2-multi-v1` | Default GLiNER fallback model id (Hugging Face). |
| `GLINER_ONNX_PATH` | `models/gliner_onnx` | Local ONNX GLiNER bundle directory. |
| `GLINER_ONNX_REPO_ID` | `lmo3/gliner2-multi-v1-onnx` | ONNX GLiNER repo id for auto/setup download. |
| `GLINER_ONNX_AUTO_SETUP` | `True` | Auto-download ONNX GLiNER when local bundle is missing. |
| `HIPPO_PERFORMANCE_PROFILE` | `auto` | `auto`, `low`, `mid`, or `high`. |
| `HIPPO_NODE_MAX` | `None` | Override profile's max nodes limit. |
| `HIPPO_MEMORY_ALPHA` | `None` | Override memory allocation fraction (e.g. 0.1). |
| `RERANK_ENABLED` | `False` | Enable local cross-encoder reranker. |
| `RERANK_MODEL_PATH` | `None` | Path to reranker ONNX model (`.onnx`). |
| `RERANK_TOKENIZER_PATH` | `None` | Path to reranker `tokenizer.json`. |
| `RERANK_TOP_N` | `20` | Number of top PPR candidates passed to fusion. |
| `RERANK_FUSION_METHOD` | `weighted_sum` | `weighted_sum` or `rrf`. |
| `RERANK_W_PPR` | `0.7` | PPR fusion weight (auto-normalized with rerank weight). |
| `RERANK_W_RERANK` | `0.3` | Reranker fusion weight (auto-normalized with PPR weight). |
| `RERANK_RRF_K` | `60` | RRF constant `k`. |
| `RERANK_MODEL_MAX_LEN` | `512` | Cross-encoder max input length. |
| `RERANK_QUERY_MAX_LEN` | `64` | Query token cap before packing. |
| `RERANK_PASSAGE_MAX_LEN` | `448` | Passage token cap before packing. |

### Reranker Activation Example
```bash
export RERANK_ENABLED=true
export RERANK_MODEL_PATH=/absolute/path/to/model_quantized.onnx
export RERANK_TOKENIZER_PATH=/absolute/path/to/tokenizer.json
export RERANK_TOP_N=20
export RERANK_FUSION_METHOD=weighted_sum
```

### Reranker Fixed Behavior (Summary)
- Dedup by `passage_id` is applied before Top-N selection.
- `top_n=0` falls back immediately to PPR-only.
- `top_n=1` is zero-division safe (`s_ppr=1.0`).
- Input weights are auto-normalized by `w_ppr + w_rerank`; non-positive sum falls back.
- Pair tokenization uses manual `[CLS] query [SEP] passage [SEP]` packing with `only_second` overflow trimming.
- ORT inputs are fixed `int64`; logits are processed in `float32`.

## ๐Ÿง  Contextual Re-ranking
Weighted context from previous turns is automatically applied to `search` results if `session_id` is reused.
- **Drift Control:** Automatically detects topic shifts and flushes context if the new query is topologically disconnected from history.
- **Preserves Narrative:** Keeps relevant entities in the "Reset Vector" for PPR to maintain focus.

## โœ… Core Coverage (85%+ Gate)
Run concise core tests (reranker + retrieval) with module-level coverage threshold:
```bash
./scripts/test_core_coverage.sh
```

## ๐Ÿ”Œ Integrations

### 1. Generic MCP Client
To use Seahorse with any MCP-compliant agent (such as Cursor, Windsurf, or your own custom agent), add the following to your agent's configuration:

```json
{
  "mcpServers": {
    "seahorse-rag-mcp": {
      "command": "uvx",
      "args": [
        "--from",
        "git+https://github.com/pch8286/seahorse-rag-mcp.git",
        "seahorse-server"
      ]
    }
  }
}
```

If you already installed the package locally, you can also use `command: "seahorse"` with `args: ["run"]`.

### 2. Claude Desktop
Add this to your `claude_desktop_config.json`:
```json
{
  "mcpServers": {
    "seahorse-rag-mcp": {
      "command": "uvx",
      "args": [
        "--from",
        "git+https://github.com/pch8286/seahorse-rag-mcp.git",
        "seahorse-server"
      ]
    }
  }
}
```

### 3. OpenClaw
We provide a pre-configured preset for OpenClaw at [`presets/openclaw.json`](presets/openclaw.json).

## ๐Ÿ“ˆ Performance At A Glance

Numbers below were measured on this exact Raspberry Pi 5 4GB machine on **2026-04-11**. Treat them as operational reference numbers for this hardware, not guarantees for other environments.

| Question | What to expect on this Pi 5 (4GB) | Confidence |
| :--- | :--- | :--- |
| **Can it start quickly?** | Time-to-interactive was about **0.01s**, but full model readiness took about **11.17s**. | High |
| **Can it answer searches locally?** | On a tiny public smoke set, average search latency was about **6.10s/query**. | Medium |
| **Can it index small batches?** | Indexing **4 docs** took about **43.16s** end-to-end on the current setup. | Medium |
| **How much memory does it use?** | Peak RSS reached about **1476 MB** during indexing/retrieval on this 4GB machine. | High |
| **Is there a public sanity check?** | The tiny public eval set reached **100% raw recall** and **99.17% adaptive recall**. | Medium |

For the full snapshot, commands, and caveats, see [docs/PERFORMANCE_SNAPSHOT_PI5_2026-04-11.md](docs/PERFORMANCE_SNAPSHOT_PI5_2026-04-11.md).

## ๐Ÿงช Measured On This Machine

| Component | Value |
| :--- | :--- |
| Date | 2026-04-11 |
| Machine | Raspberry Pi 5 Model B Rev 1.1 |
| CPU | Cortex-A76, 4 cores, 2.4 GHz |
| RAM | 4 GB |
| Swap / zram | 2 GB |
| OS | Debian 13 (trixie), aarch64 |
| Python | 3.13.5 |

| Metric | Result | Notes |
| :--- | :--- | :--- |
| Startup TTI | **0.014s** | CLI becomes interactive quickly before full model warmup |
| Startup ready | **11.167s** | Models loaded and ready for first real use |
| Peak RSS at ready | **1185 MB** | During startup warmup |
| Indexing duration | **43.164s** | `4` docs from the public eval set, batch size `1` |
| Indexing throughput | **0.093 docs/s** | Tiny public set, not a throughput leaderboard |
| Retrieval avg latency | **6.098s/query** | `5` queries from the public eval set |
| Retrieval peak RSS | **1476 MB** | Peak resident set during retrieval run |
| SQLite smoke query | **10.600s** | Includes engine/model startup overhead in the benchmark path |

## Public Smoke Eval

This is a tiny public sanity set for regression checking, not a leaderboard benchmark.

| Public dataset | Docs | Queries | Metric | Result | Interpretation |
| :--- | :--- | :--- | :--- | :--- | :--- |
| `scripts/data/eval_scenarios.json` | 4 | 5 | Raw recall | **100.00%** | All tiny-set targets were recovered by the current heuristic check |
| `scripts/data/eval_scenarios.json` | 4 | 5 | Adaptive recall | **99.17%** | Slightly penalized version of recall that factors estimated node usage |
| `scripts/data/eval_scenarios.json` | 4 | 5 | Drift control | **100.00%** | On this tiny set, irrelevant-topic drift was rejected cleanly |
| `scripts/data/eval_scenarios.json` | 4 | 5 | Linkage density | **5.33** | Phrase-to-phrase linkage remained dense after synonym optimization |

## ๐Ÿ—๏ธ Architecture & Optimization
For detailed architecture decisions, data schema, and Raspberry Pi 5 specific optimizations, refer to [TECH_SPEC.md](TECH_SPEC.md).

## Internal Benchmark Snapshot

The following numbers come from a larger internal benchmark snapshot comparing Seahorse against a vector-only baseline. The exact corpus and harness are not fully published in this repository yet, so treat these numbers as directional architecture evidence, not a public scorecard.

| Metric | Seahorse | Vector-only baseline | Status |
| :--- | :--- | :--- | :--- |
| Peak raw recall | **72.0%** | 22.4% | Internal snapshot |
| Adaptive recall | **20.2%** | 14.8% | Internal snapshot |
| Context control | **100.0%** | 100.0% | Internal snapshot |
| Token savings | **71.3%** | -131.4% | Internal snapshot |
| Startup time | **< 1.2s** | < 1.0s | Internal snapshot |

### Recall vs. Node Budget (Scalability)
Seahorse dynamically adjusts its search depth based on available RAM. The table below reflects intended tradeoffs from the same internal benchmark snapshot above rather than a fresh public repro.

| Profile | Node budget | Intended memory range | Bias | Best use case |
| :--- | :--- | :--- | :--- | :--- |
| **Max Profile** | Uncapped | 16GB+ | Quality-first | Server or desktop evaluation |
| **High Profile** | 10,000 | 8GB+ edge boxes | Higher recall | Heavier local retrieval |
| **Mid Profile** | 5,000 | 4GB to 8GB-class edge devices | Balanced | Everyday Pi usage |
| **Low Profile** | 2,000 | Tight-memory environments | Latency/memory-first | Strict constrained mode |

> **Bottom line**: the public numbers you can reproduce today are the Pi 5 operational snapshot and the tiny smoke eval above. The larger comparative recall claims remain informative, but they should be read as internal evidence until the full benchmark corpus is published.

## ๐Ÿ“š References & Research
This implementation is based on the following research papers:
1. **HippoRAG 2**: [https://arxiv.org/pdf/2502.14802](https://arxiv.org/pdf/2502.14802)
2. **Edge-Optimized GraphRAG**: [https://arxiv.org/pdf/2602.01965](https://arxiv.org/pdf/2602.01965)
3. **Dense-Sparse Integration**: [https://arxiv.org/pdf/2510.08958](https://arxiv.org/pdf/2510.08958)

*Public snapshot in this README was measured on a Raspberry Pi 5 (4GB). Older internal benchmark notes may come from different Pi 5 or server-class configurations.*

## License
This project is licensed under the MIT License. See [LICENSE](LICENSE).

Downloaded model assets are not covered by this repository's MIT license. If you use automatically downloaded GLiNER or other upstream model artifacts, you must also comply with the licenses and terms attached to those upstream models.