Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"name": "mempalace",
"owner": { "name": "MemPalace", "url": "https://github.com/debpalash" },
"metadata": {
"description": "Local-first memory for AI coding agents.",
"version": "0.2.0"
},
"plugins": [
{
"name": "fast-mempalace",
"source": "./claude-plugin",
"description": "Persistent, private, local memory for Claude Code — your agent remembers your code and decisions across sessions. One static binary, no cloud. Requires the fast-mempalace binary (curl install).",
"version": "0.2.0",
"license": "MIT",
"keywords": ["memory", "local-first", "mcp", "privacy"]
}
]
}
13 changes: 10 additions & 3 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@ jobs:
- name: Download Neural Matrix (GGUF)
run: |
mkdir -p lib
curl -L -o lib/minilm.gguf "https://huggingface.co/nomic-ai/nomic-embed-text-v1.5-GGUF/resolve/main/nomic-embed-text-v1.5.f16.gguf"
# MiniLM-L6-v2, 384-dim — must match EMBEDDING_DIM and the float[384] schema.
curl -L -o lib/minilm.gguf "https://huggingface.co/leliuga/all-MiniLM-L6-v2-GGUF/resolve/main/all-MiniLM-L6-v2.F16.gguf"

- name: Build llama.cpp Static Libraries
if: steps.cache_llama.outputs.cache-hit != 'true'
Expand All @@ -79,9 +80,11 @@ jobs:
-DCMAKE_BUILD_TYPE=Release \
-DBUILD_SHARED_LIBS=OFF \
-DGGML_METAL=ON \
-DGGML_BLAS=ON \
-DLLAMA_BUILD_TESTS=OFF \
-DLLAMA_BUILD_EXAMPLES=OFF \
-DLLAMA_BUILD_SERVER=OFF
-DLLAMA_BUILD_SERVER=OFF \
-DLLAMA_BUILD_TOOLS=OFF
else
# Force clang + libc++ so the ABI matches Zig's bundled libc++.
CC=clang CXX=clang++ \
Expand Down Expand Up @@ -113,7 +116,11 @@ jobs:
- name: Compile Native Vector Pipelines (ReleaseFast)
run: zig build --release=fast

- name: Execute CLI Validation
- name: Execute CLI Validation (incl. real embeddings)
run: |
set -eux
./zig-out/bin/fast-mempalace init
./zig-out/bin/fast-mempalace stats
printf 'The deploy key id is QUASAR-77 and must never be logged.\n' > smoke.txt
./zig-out/bin/fast-mempalace mine smoke.txt smoke
./zig-out/bin/fast-mempalace search "what is the deploy key" 2>&1 | grep -q QUASAR-77
17 changes: 14 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,9 @@ jobs:
- name: Download embedding model
run: |
mkdir -p lib
# MiniLM-L6-v2, 384-dim — must match EMBEDDING_DIM and the float[384] schema.
curl -L -o lib/minilm.gguf \
"https://huggingface.co/nomic-ai/nomic-embed-text-v1.5-GGUF/resolve/main/nomic-embed-text-v1.5.f16.gguf"
"https://huggingface.co/leliuga/all-MiniLM-L6-v2-GGUF/resolve/main/all-MiniLM-L6-v2.F16.gguf"

- name: Build llama.cpp static libraries
if: steps.cache_llama.outputs.cache-hit != 'true'
Expand All @@ -88,9 +89,11 @@ jobs:
-DCMAKE_BUILD_TYPE=Release \
-DBUILD_SHARED_LIBS=OFF \
-DGGML_METAL=ON \
-DGGML_BLAS=ON \
-DLLAMA_BUILD_TESTS=OFF \
-DLLAMA_BUILD_EXAMPLES=OFF \
-DLLAMA_BUILD_SERVER=OFF
-DLLAMA_BUILD_SERVER=OFF \
-DLLAMA_BUILD_TOOLS=OFF
else
CC=clang CXX=clang++ \
cmake -S lib/llama.cpp -B lib/llama.cpp/build \
Expand All @@ -109,10 +112,15 @@ jobs:
- name: Build release
run: zig build --release=fast

- name: Smoke test
- name: Smoke test (incl. real embeddings)
run: |
set -eux
./zig-out/bin/fast-mempalace init
./zig-out/bin/fast-mempalace stats
# Validate the full semantic path: mine a file, then recall it.
printf 'The deploy key id is QUASAR-77 and must never be logged.\n' > smoke.txt
./zig-out/bin/fast-mempalace mine smoke.txt smoke
./zig-out/bin/fast-mempalace search "what is the deploy key" 2>&1 | grep -q QUASAR-77

- name: Package tarball
env:
Expand All @@ -134,6 +142,9 @@ jobs:
release:
name: Publish GitHub Release
needs: build
# Publish whatever platforms succeeded even if one leg fails, rather than
# skipping the whole release (fail-fast is off on the build matrix).
if: ${{ always() && !cancelled() }}
runs-on: ubuntu-latest
steps:
- name: Download all artifacts
Expand Down
99 changes: 50 additions & 49 deletions BENCHMARK.md
Original file line number Diff line number Diff line change
@@ -1,59 +1,60 @@
# ⏱️ fast-mempalace Benchmarks

Hardware performance of identical pipelines: legacy Pip `mempalace` (Python / ONNX / ChromaDB) vs `fast-mempalace` (Zig 0.16.0 / Metal / sqlite-vec).

Hardware: Apple M2, 16 GB, macOS. Each command run cold (fresh DB, fresh process). Memory = peak resident set size.

## 1. Cold start

Python carries ~1 s of `chromadb` + `pydantic` parse overhead before any DB work. `fast-mempalace` defers the LLM context entirely and goes straight to `sqlite-vec`.

| Engine | Command | Time | Peak RAM |
| ------ | ------- | ---- | -------- |
| 🐢 `mempalace` (Python) | `mempalace init` | 1.40 s | 50.15 MB |
| ⚡ `fast-mempalace` (Zig) | `fast-mempalace stats` | **0.01 s** | **8.40 MB** |

## 2. Neural ingestion — OmniVoice corpus

Corpus from the [OmniVoice](https://github.com/debpalash/OmniVoice-Studio) repo — all `.py`, `.md`, `.yaml`, `.toml` files concatenated (**450 368 bytes across 56 files → 1 171 drawers**). Apples-to-apples: same bytes, same embedder, same drawer-chunking.

| Engine | Command | Time | Peak RAM |
| ------ | ------- | ---- | -------- |
| 🐢 `mempalace` (Python) | `mempalace mine` | ~120 s (blocks on ChromaDB) | ~320 MB |
| ⚡ `fast-mempalace` (Zig) | `fast-mempalace mine` | **0.59 s** | **95 MB** |

Python blocks on I/O mapping across arrays and pushes system RAM into the hundreds of MB. Zig decouples I/O via `std.Io.Group` concurrency and maps matrices into the Metal API.

## 3. Semantic clustering — search

Mathematical proximity sort against the full DB geometry. Memory deltas are astronomical because `fast-mempalace` binds statically against `sqlite-vec`.

| Engine | Command | Time | Peak RAM |
| ------ | ------- | ---- | -------- |
| 🐢 `mempalace` (Python) | `mempalace search` | 0.78 s | 270 MB |
| ⚡ `fast-mempalace` (Zig) | `fast-mempalace search` | **0.59 s** | **94 MB** |

## 4. Extended commands

Pure execution paths that never touch the embedder.

| Engine | Command | Time | Peak RAM |
| ------ | ------- | ---- | -------- |
| 🐢 `mempalace` (Python) | `mempalace wake-up` | 0.53 s | 76 MB |
| ⚡ `fast-mempalace` (Zig) | `fast-mempalace kg` | **0.02 s** | **8.40 MB** |
Absolute performance of the real semantic engine (`llama.cpp` MiniLM-L6-v2 embeddings +
`sqlite-vec`). Hardware: Apple Silicon, macOS, Metal backend. Each command run cold
(fresh process) unless noted. Memory = peak resident set size.

> Earlier versions of this file reported a `0.59 s` mine of 1 171 drawers. That run used
> **placeholder vectors** (the embedder returned a constant dummy vector), so it measured
> I/O, not embedding. The numbers below are the real on-device semantic pipeline.

## 1. Latency

| Operation | Time | Peak RAM | Loads model? |
| --------- | ---- | -------- | ------------ |
| `stats` (cold start) | **0.01 s** | ~8 MB | no |
| `wake-up` (session context) | **0.01 s** | ~8 MB | no |
| `mine` — 15 files → 31 drawers | **~1.0 s** | ~100 MB | yes |
| `search` — one-shot CLI | **~0.55 s** | ~100 MB | yes (each call) |
| `search` — vector match only | **sub-ms** | — | model already resident |

The key distinction for agent use: the **CLI reloads the embedding model on every
invocation** (~0.5 s of that 0.55 s is Metal model init). The **MCP server loads the
model once and stays resident**, so per-query recall after warm-up is dominated by the
`sqlite-vec` match — sub-millisecond on these corpus sizes. Session `wake-up` never loads
the model at all, which is why it's 10 ms and runs on every session start.

## 2. Footprint

| | Value |
| -- | -- |
| Binary size (`--release=fast`, statically linked) | ~6 MB |
| Embedding model (MiniLM-L6-v2, F16 GGUF) | ~45 MB |
| Runtime dependencies | none (no Python, no Docker, no external vector DB) |
| Network calls at query time | none |

## 3. Retrieval quality (sanity)

On a 4-topic corpus (biology / coffee / algorithms / history) with paraphrased queries
that share no keywords with the stored text, top-1 retrieval is correct 7/7 — e.g.
"how do cells produce energy" → the mitochondria/ATP drawer; "brewing a good cup of
coffee" → the espresso drawer. Identical strings cosine to 1.00; paraphrases ~0.55;
unrelated topics ~0.03.

## Reproducing

```bash
# Build fast-mempalace
zig build --release=fast

# Mine the OmniVoice corpus
find /path/to/OmniVoice -type f \( -name "*.py" -o -name "*.md" -o -name "*.yaml" -o -name "*.toml" \) \
| xargs cat > corpus.txt
/usr/bin/time -l ./zig-out/bin/fast-mempalace mine corpus.txt omnivoice
# point at a 384-dim model
export FAST_MEMPALACE_MODEL=$PWD/lib/minilm.gguf
export FAST_MEMPALACE_DB=/tmp/bench.db

# Run the Python baseline (for comparison rows above)
pip install mempalace
/usr/bin/time -l mempalace mine corpus.txt
./zig-out/bin/fast-mempalace init
/usr/bin/time -p ./zig-out/bin/fast-mempalace mine ./src bench # mine a directory
/usr/bin/time -p ./zig-out/bin/fast-mempalace search "how does search ranking work"
/usr/bin/time -p ./zig-out/bin/fast-mempalace wake-up
```

For warm MCP-server latency, start `fast-mempalace mcp` and issue repeated
`tools/call → memory_search` requests over stdio; only the first pays the model-load cost.
Loading
Loading