AI Skill Hub 推荐使用:本记为器求序会 是一款优质的MCP工具。AI 综合评分 7.5 分,在同类工具中表现稳健。如果你正在寻找可靠的MCP工具解决方案,这是一个值得深入了解的选择。
当前的MCP模式器求序,可代角色的给当前求序。
本记为器求序会 是一款遵循 MCP(Model Context Protocol)标准协议的 AI 工具扩展。通过 MCP 协议,它可以让 Claude、Cursor 等主流 AI 客户端直接访问和操作外部工具、数据源和服务,实现 AI 能力的无缝扩展。无论是文件操作、数据库查询还是 API 调用,都可以通过自然语言在 AI 对话中直接触发,极大提升生产效率。
当前的MCP模式器求序,可代角色的给当前求序。
本记为器求序会 是一款遵循 MCP(Model Context Protocol)标准协议的 AI 工具扩展。通过 MCP 协议,它可以让 Claude、Cursor 等主流 AI 客户端直接访问和操作外部工具、数据源和服务,实现 AI 能力的无缝扩展。无论是文件操作、数据库查询还是 API 调用,都可以通过自然语言在 AI 对话中直接触发,极大提升生产效率。
# 方式一:通过 Claude Code CLI 一键安装
claude skill install https://github.com/udjin-labs/mnemostack
# 方式二:手动配置 claude_desktop_config.json
{
"mcpServers": {
"-------": {
"command": "npx",
"args": ["-y", "mnemostack"]
}
}
}
# 配置文件位置
# macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
# Windows: %APPDATA%/Claude/claude_desktop_config.json
# 安装后在 Claude 对话中直接使用 # 示例: 用户: 请帮我用 本记为器求序会 执行以下任务... Claude: [自动调用 本记为器求序会 MCP 工具处理请求] # 查看可用工具列表 # 在 Claude 中输入:"列出所有可用的 MCP 工具"
// claude_desktop_config.json 配置示例
{
"mcpServers": {
"_______": {
"command": "npx",
"args": ["-y", "mnemostack"],
"env": {
// "API_KEY": "your-api-key-here"
}
}
}
}
// 保存后重启 Claude Desktop 生效
Self-hosted hybrid memory & retrieval for AI apps.
mnemostack is a durable retrieval layer over your own Qdrant (and optional Memgraph): semantic, keyword (BM25), temporal, and graph recall, fused with Reciprocal Rank Fusion and refined by an 8-stage ranking pipeline — with payload filters for multi-tenant isolation, optional LLM answer synthesis (confidence + citations), and an ingest path that enriches and projects structured fields. One recall(query) call, usable as a Python library, an HTTP service, or an MCP server.
Flagship use case — durable memory for AI agents. Long-running agents hit the same wall: context gets compacted, sessions restart, useful decisions disappear, and the next run pays the re-orientation tax again. mnemostack gives them a persistent memory layer to query when the context window is not enough — durable, searchable, scoped, and explainable, not just embedded and hoped for.
The same engine backs other retrieval-heavy work: RAG over mixed corpora, multi-tenant or per-user knowledge stores, and time-aware search backends — anywhere pure vector similarity falls short on its own.
Status: Actively developed — public API is stable; new functionality lands additively in minor releases. Breaking changes are rare and called out in CHANGELOG.md.
Retriever abstraction — add your own sources.reciprocal_rank_fusion(weights=[...]) lets you lift sources you trust more; Recaller(adaptive_weights=True) picks a per-query-shape profile (exact-token / person / temporal / general). See the honest write-up below for where this helps and where it doesn't.search(). Not included in the default Recaller./feedback, and recall exposure logging is off unless --auto-record-ior is enabled.ScoringReranker. See docs/recipes.md for a runnable bge-reranker-v2-m3 example.BM25Retriever(tokenizer=...) for stemming / lemmatization / language routing. Core stays dependency-free; docs/recipes.md has per-language recipes.Recaller.recall_async, recall_flow_async, Ingestor.ingest_async / ingest_one_async, AnswerGenerator.generate_async, synthesize_async, plus AsyncVectorStore over the native async Qdrant client. Retrievers dispatch in parallel; five concurrent HTTP recalls finish in roughly one single-recall wall-clock.store.invalidate(ids, valid_until=...) sets bi-temporal payload keys (invalidated_at system-time, valid_until/valid_from world-time) via a cheap merge write. Recall hides invalidated facts by default; include_invalidated=True shows them and as_of="<iso>" reconstructs what was valid at a past instant from valid_from/valid_until — each optional, and invalidated_at is not read there at all, so a point-in-time view can be a superset of the default one (contract). The vector-side twin of the graph's valid_until model. CLI mnemostack invalidate <id>..., MCP mnemostack_invalidate, and — since 2.2 — HTTP POST /invalidate (with DELETE /memories for irreversible erasure), selecting either an id list or a whole source.telegram_id, handle, and precomputed name_lower so non-ASCII names match correctly (Memgraph's toLower() lower-cases ASCII only).Ingestor API — batched, idempotent, LRU-cached ingest from any Python code. Lazy iterator means large corpora ingest with bounded memory. Same (source, offset, text) → same deterministic UUID-shaped content id, so re-runs are no-ops.mnemostack index-markdown <dir> indexes a folder of markdown with structure: YAML frontmatter → payload filters, header-aware chunking with heading paths, and [[wikilinks]] / [text](note.md) → File -[LINKS_TO]-> File graph edges (with a Memgraph URI). Generic for any markdown folder; Obsidian vaults work as a side effect. Depends only on the already-present pyyaml.pip install 'mnemostack[server]' gives you /recall, /answer, the write/lifecycle surface (POST/GET/DELETE /memories, /invalidate, /triples), /health, /docs, plus /metrics in Prometheus text format. See the HTTP server section below.valid_from/valid_until, query point-in-time state; graph resurrection stage recovers evicted-but-relevant memories.cat_3 inference retry with query decomposition are on by default.synthesize(entity) rolls up everything memory knows about a person, project, or topic into a structured profile (SynthesisFact / SynthesisResult, markdown or JSON). CLI: mnemostack synthesize <entity>. Optional related-entities expansion via graph and LLM summarization pass.search --tier {1,2,3} and answer --tier {1,2,3} bound output size (~50 / ~200 / ~500 tokens) so agents can pay only for the detail they actually need. Omit --tier for unchanged full output.MessagePairChunker for chat transcripts (keeps user↔assistant pairs together). The new vector.window_size config carries adjacent-turn context inside each chunk; window_size=3 was worth +5.8pp strict / +4.1pp combined on LoCoMo (v0.4.0).Recaller(expansion_llm=...) widens recall with reformulated queries; AnswerGenerator(retry_with_expansion=True) retries low-confidence answers with the expanded query and a HyDE-style hypothetical before giving up. Opt-in via --query-expansion on mnemostack answer.filters={"tenant": "a"} applies inside every retriever (exact match + ranges) on HTTP/MCP/CLI/library; results never include points outside the scope, verified by adversarial isolation tests. Filters are caller-supplied, so for a real trust boundary run the server with service-key auth (serve --auth / mcp-serve --auth): the tenant is resolved from the key (a client can't assert another's), enforced across the vector store, the knowledge graph, and per-tenant learning state. Optional per-tenant storage quotas apply at ingest, and request-rate quotas on the authenticated HTTP surface (serve --auth). Off by default. See the HTTP API section.Ingestor(enrich=callable) extracts structured facts into payloads at ingest (fail-open, --refresh-payloads updates existing collections without re-embedding); context_fields=[...] shows them to the answer LLM; rewrite_followup() resolves conversational follow-ups before recall.think is off by default (reasoning models otherwise burn the whole token budget on thoughts and return empty text); options={...} passes any generation option through.pip install 'mnemostack[mcp]'
Run a local Qdrant for the vector store:
```bash
docker run -p 6333:6333 qdrant/qdrant:v1.18.3
Optional: run Memgraph for graph-backed memory:
bash docker run -p 7687:7687 memgraph/memgraph:latest ```
Fastest way to kick the tyres. No Python install, no manual Qdrant / Memgraph setup.
```bash git clone https://github.com/udjin-labs/mnemostack && cd mnemostack cp README.md examples/notes/ # any markdown will do GEMINI_API_KEY=your-key docker compose -f examples/docker-compose.yml up -d --build
```bash
docker run -p 6333:6333 qdrant/qdrant:v1.18.3
Optionally a Memgraph for the knowledge graph:
bash docker run -p 7687:7687 memgraph/memgraph:latest ```
The fastest on-ramp is the MCP server — it gives Claude Desktop, Claude Code, Cursor, ChatGPT, or another MCP-capable agent durable memory in a few commands. Building an app instead of wiring up an agent? Use the HTTP API or the Python library over the same collection.
Agent and chatbot memory (flagship):
filters so one user never sees another's history.Beyond agents — the same engine as a retrieval backend:
answer() (with confidence and source citations) over it from the CLI, HTTP, or library.filters isolate each tenant's data inside every retriever, or enable service-key auth (serve --auth) for a hard, key-resolved tenant boundary with optional per-tenant quotas (see the HTTP API).```bash
| Variable | Purpose | Required for |
|---|---|---|
GEMINI_API_KEY | Google Generative AI key | Gemini embedding + Gemini Flash LLM |
OLLAMA_HOST | Ollama server URL (default http://localhost:11434) | Ollama embeddings / LLM |
MNEMOSTACK_COLLECTION | Qdrant collection name (default mnemostack) | CLI convenience |
MNEMOSTACK_QDRANT_URL | Qdrant URL (default http://localhost:6333) | Remote Qdrant |
MNEMOSTACK_GRAPH_URI / MNEMOSTACK_MEMGRAPH_URI | Memgraph bolt URI | Graph retriever / GraphStore |
MNEMOSTACK_LLM_HOST / MNEMOSTACK_LLM_TIMEOUT | LLM endpoint (ollama: default inherits the embedding --ollama-host; openai: required base URL) and LLM request timeout | Answer / reranker / expansion LLM |
MNEMOSTACK_LLM_API_KEY | Bearer token for the openai LLM provider; unset or none = no auth header (keyless vLLM / llama.cpp) | Answer / reranker / expansion LLM |
MNEMOSTACK_PROVIDER / MNEMOSTACK_EMBEDDING_PROVIDER | Embedding provider | CLI / HTTP / MCP |
MNEMOSTACK_LLM / MNEMOSTACK_LLM_PROVIDER | LLM provider | Answer generation / reranking |
MNEMOSTACK_BM25_PATHS | BM25 corpus paths separated by os.pathsep (: on Unix) | CLI / HTTP / MCP BM25 retriever |
MNEMOSTACK_AUTO_RECORD_IOR | true/false toggle for HTTP recall exposure logging | HTTP stateful pipeline |
MNEMOSTACK_EMBEDDING_MODEL / MNEMOSTACK_LLM_MODEL | Override the embedding / LLM model name | CLI / HTTP / MCP |
MNEMOSTACK_VECTOR_HOST / MNEMOSTACK_VECTOR_COLLECTION | Aliases for the Qdrant URL / collection | CLI / HTTP / MCP |
MNEMOSTACK_VECTOR_FLOOR | Keep top-N raw vector hits in results even when fusion/rerank would drop them (0 = off) | Recall tuning |
MNEMOSTACK_RERANK_MODE | LLM reranker mode: relevant_only (default) or full_reorder | HTTP / MCP runtime reranker |
MNEMOSTACK_TOKEN_BUDGET | Default recall token budget — cut results to the ranked prefix that fits (unset = off) | CLI / HTTP / MCP recall surfaces |
MNEMOSTACK_GRAPH_TIMEOUT / MNEMOSTACK_GRAPH_HEALTH_TIMEOUT | Memgraph query / health-check timeouts in seconds | Graph retriever |
MNEMOSTACK_CONFIG | Path to the YAML config file | All entry points |
Only the providers you actually use need their keys. HuggingFace local-GPU embeddings need no keys at all. mnemostack init writes the same settings as YAML; explicit CLI flags override config/env defaults.
pip install 'mnemostack[huggingface]' # local GPU embeddings pip install 'mnemostack[mcp]' # MCP server pip install 'mnemostack[dev]' # tests + linters
Run a local Qdrant for the vector store:
bash
gen = AnswerGenerator(llm=get_llm("gemini")) answer = gen.generate("what did we decide", results) print(answer.text, answer.confidence, answer.sources)
**Count and "list all X" questions** need set completeness, which similarity top-K does not guarantee — the model counts what it sees and undercounts, returning a subset. For those, retrieve a wide candidate pool and enable the two-pass extract-and-aggregate mode:
python gen = AnswerGenerator(llm=get_llm("gemini"), list_extract_mode=True) pool = recaller.recall("how many trips did user A take", limit=150) # wide pool, not top-10 answer = gen.generate("how many trips did user A take", pool)
`list_extract_mode` routes count/list questions through an extract pass (pulls every matching item as JSON) and a finalize pass (formats the list or count); other question categories are unaffected. The extract pass walks the **whole pool you pass in**, in batches of `list_extract_batch_size` (default 40), merging items across batches — so pool order does not decide whether a memory is seen, and the cost is one LLM call per batch plus finalize. An empty extract over a non-empty pool is retried once before abstaining. For guaranteed exhaustiveness on a bounded slice, build the pool from a full scan (`VectorStore.scroll`) filtered to the relevant slice. To evaluate it on your own data, the benchmark harness exposes the same knobs: `benchmarks/locomo_single.py --list-extract --pool 150`.
`list_finalize="verbatim"` skips the finalize LLM pass and assembles the answer deterministically from the extracted items (the count for count questions, the comma-joined items otherwise). Recommended for non-English corpora: an LLM finalize pass can paraphrase or distort items instead of repeating them verbatim. The default `"llm"` keeps the formatting pass.
**Enriching payloads at ingest.** `Ingestor(enrich=callable)` calls your function for every final item (including assembled window chunks) and merges the returned dict into the chunk payload — the mechanism is core, the extractor is yours (content extraction is corpus- and language-specific, so mnemostack ships none). Fail-open: a raising hook logs a warning and the item is indexed without enrichment; `text`/`source`/`offset` and an explicit item timestamp can't be overridden. From the CLI: `mnemostack index docs/ --enrich mypkg.extractors:invoice_fields`. Enriched fields combine with the rest of the stack: scope recall with `filters={"amount": {"gte": 100}}` and show them to the answer LLM with `context_fields=["amount"]`.
python def invoice_fields(item): # yours — any language, any domain amounts = AMOUNT_RE.findall(item.text) return {"amounts": amounts} if amounts else {}
ing = Ingestor(embedding=emb, vector_store=store, enrich=invoice_fields)
Already-indexed collections don't need re-embedding to pick up enrichment: `mnemostack index docs/ --enrich ... --refresh-payloads` rewrites the payloads of existing chunks in place (Qdrant `set_payload`, vectors untouched) — only genuinely new chunks pay for embedding.
**Structured payload fields in the answer prompt.** By default the answer context shows each memory's timestamp, source and text. `AnswerGenerator(context_fields=["author", "amount"])` additionally projects the named payload fields into each memory's context line (`author=…`, lists comma-joined, long values truncated; memories without the field render without it). Use it for structured facts the answer needs — who said it, amounts, your own ingest-time enrichments. Note the boundary: projection only changes what the answer prompt *shows* — retrieval ranks by `text`, so content that must be *findable* (image captions and similar) belongs in the text itself, not in a payload field.
**Conversational follow-ups.** "And who wrote that?" carries none of the conversation, so recall misses. `rewrite_followup(query, history, llm)` resolves pronouns and ellipses into a standalone question before recall — mnemostack holds no dialog state, you pass the history (`(question, answer)` pairs or plain lines, oldest first). One LLM call; the prompt instructs the model to return a self-contained question unchanged, and any failure falls back to the original query. To skip the call entirely for queries you already know are standalone, pass `needs_rewrite=callable` — that trigger heuristic is language-dependent, so core ships none (same boundary as `question_classifier`).
python from mnemostack.recall import rewrite_followup
standalone = rewrite_followup("а кто это написал?", history, llm) results = recall_flow(recaller, standalone, limit=10, pipeline=pipeline)
**Non-English corpora.** The built-in answer prompts and the question classifier are English; on other languages the extract/finalize passes degrade instead of helping. Both are pluggable:
python gen = AnswerGenerator( llm=llm, list_extract_mode=True, prompt_overrides={ # any subset; templates in YOUR corpus language "list_extract": MY_EXTRACT_TEMPLATE, # must contain {context} and {query} "list_finalize": MY_FINALIZE_TEMPLATE, # must contain {query} and {items} "temporal": MY_TEMPORAL_TEMPLATE, # category prompts: {context} and {query} }, )
If you want mnemostack available to callers that aren't Python — any service written in Node, Go, Rust, or a plain curl from a shell script — install the server extra and expose it over HTTP:
pip install 'mnemostack[server]'
export GEMINI_API_KEY=...
mnemostack serve --provider gemini --collection memory --port 8000
mnemostack serve binds to 127.0.0.1 by default. Use --host 0.0.0.0 only behind your own auth/rate-limit layer.
Endpoints:
| Method | Path | Purpose |
|---|---|---|
GET | /health | Qdrant + Memgraph reachability + config summary |
GET | /healthz | Liveness probe — 200 whenever the process is up (no backend checks) |
GET | /readyz | Readiness probe — 503 when Qdrant is unreachable (graph is fail-soft, never gates) |
GET | /status | Operator snapshot — config, live dependency reachability, headline counters |
POST | /recall | Hybrid recall with optional 8-stage pipeline |
POST | /answer | Recall + LLM answer synthesis with citations |
GET | /resolve/{chunk_id} | Verify a citation — resolve a chunk id back to its source document — read |
POST | /feedback | Explicit click/usefulness feedback for stateful learning |
POST | /memories | Create memories (server-side embedding, store-backed dedup) — write |
GET | /memories | List what the tenant holds from one source (ids + integrity metadata, no text) — read |
DELETE | /memories | Irreversible erasure by id list or source — write |
POST | /invalidate | Non-destructive retraction by id list or source — write |
POST | /triples | Write knowledge-graph facts — write |
GET | /metrics | Prometheus scrape endpoint (counters + summary histograms) |
GET | /docs | Interactive OpenAPI UI |
curl -s http://localhost:8000/recall \
-H 'content-type: application/json' \
-d '{"query": "what did we decide about auth", "limit": 10}' | jq
Response shape (abridged):
{
"query": "what did we decide about auth",
"results": [
{ "id": "...", "text": "...", "score": 0.72, "source": "notes/...md", "metadata": {} }
],
"degraded": [], // components that ACTUALLY fell back, e.g. "retriever:bm25:failed", "reranker:fallback"; empty when healthy
"notes": [], // routine signals for stages that did not apply, e.g. "temporal:no_parse" on a date-less query; never a fault
"tokens_estimate": 512 // estimated text tokens of the returned results
}
The order of results is authoritative — do not re-sort by score: many stages and fallback paths write that number on different scales, and a rerank changes the order without rewriting it. See what score is not.
Pass "include_trace": true in the request body to additionally get a trace object with per-retriever ranked lists, the fused order, and the post-rerank order — useful when debugging why a memory did or didn't surface.
Pass "token_budget": 2000 to cap how much prompt space the results may occupy: the final ranking is cut to the prefix whose total text tokens fit the budget (a hard cap — never overshot, so an oversized top hit yields an empty list rather than a blown prompt). tokens_estimate in the response is the value the budget is enforced against; counting uses a dependency-free heuristic (≈4 chars/token for ASCII, ≈2 for non-ASCII scripts), so leave yourself margin rather than budgeting to the exact context limit. A server-wide default can be set with recall.token_budget in the config file (or MNEMOSTACK_TOKEN_BUDGET); per-request values override it. The same parameter is available on /answer (caps the memories fed to the LLM), on MCP mnemostack_search / mnemostack_answer, on the CLI as --token-budget, and in the library as recall_flow(..., token_budget=...) — where you can also pass an exact token_counter= (e.g. a tiktoken encoder) instead of the heuristic.
Pass "filters": {...} to scope recall by payload fields — exact match ({"tenant": "a"}) or inclusive ranges ({"timestamp": {"gte": "2026-01-01"}}). Filters apply inside every retriever, not as a post-filter on the output: the candidate pool itself is restricted, so top-K stays full and results never include points outside the scope — this is the isolation contract for multi-tenant and per-user memory. Sources that cannot attribute their results to the scope contribute nothing rather than leak. The knowledge-graph retriever attributes its hits where it can: a filter key the hit's own node metadata carries (e.g. index_root) is checked in place, the rest is proven through the hit's vector chunks — a graph file hit (with a recorded root, pinning the probe to its exact document) passes the filter exactly when at least one of its chunks does; entity nodes and anything else without pinnable chunks are still excluded, never leaked. The same filters parameter is available on /answer (the answer is generated only from in-scope memories, including retry sub-recalls), on MCP mnemostack_search / mnemostack_answer, on the CLI as --filters '{"tenant": "a"}', and in the library as recaller.recall(query, filters=...) / recall_flow(..., filters=...).
The /answer endpoint adds { answer, confidence, sources } alongside the memories and carries the same degraded / notes / opt-in trace fields, plus tokens_used — the LLM provider's reported token usage for the generation call that produced the answer (provider-specific semantics; null when the provider reports nothing). If the LLM isn't configured, /answer returns 503 and /recall still works — graceful degradation applies at the HTTP layer too.
Start the server with --retry-on-weak if you want a recall that comes back nearly empty to be paraphrased by the answer LLM and asked again, fusing the rounds by reciprocal rank — so a memory that two phrasings both find outranks one that only a single phrasing did, and a later paraphrase can beat an earlier one. What counts as "weak" is a COUNT (--retry-weak-below, default 1 — only a recall that returned nothing), not a score: fused scores are RRF values encoding rank, not confidence, so a threshold on them would measure nothing. One extra round, at most two paraphrases, and every retry carries the caller's tenant, filters, validity view and budget unchanged. Budget for it accordingly: each variant repeats your recall in full, reranker included, so a weak recall costs up to three LLM calls (one paraphrase plus one rerank per variant) and two extra retrieval rounds — not the single call the name suggests. Two consequences worth knowing before you switch it on: a retried response fuses over several rounds where an unretried one fuses over a single one, so the rank basis behind
When you want to feed items into mnemostack from code — a chatbot that logs every message, a scraper, a daemon tailing a log — use the Ingestor. It handles batching, deduplication, and idempotency for you.
from mnemostack.embeddings import get_provider
from mnemostack.vector import VectorStore
from mnemostack import Ingestor, IngestItem
emb = get_provider("gemini")
store = VectorStore(collection="my-memory", dimension=emb.dimension)
store.ensure_collection()
ing = Ingestor(embedding=emb, vector_store=store, batch_size=64)
stats = ing.ingest([
IngestItem(text="alice joined acme on 2024-03-01", source="notes/alice.md",
timestamp="2024-03-01T09:00:00Z"), # event time — drives temporal recall
IngestItem(text="alice left acme on 2025-06-15", source="notes/alice.md", offset=100),
])
print(stats) # IngestStats(seen=2, embedded=2, upserted=2, skipped=0, failed=0)
Guarantees:
(source, offset, text). Re-running with the same input is a no-op: Qdrant upsert replaces the point onto itself, and an in-process LRU cache skips even the embedding call for items already seen in this session.batch_size, so provider HTTP overhead amortises across many items.indexed_at (UTC). Pass timestamp= (or metadata={"timestamp": ...}) to set the event time the temporal retriever filters on. With window_size > 1, sliding-window chunks also carry the window's temporal range as window_start_ts / window_end_ts payload keys.A memory stack that indexes only text answers "Not in memory" to questions whose answer lived in a photo. If your data contains images, describe them at ingest time and index the description:
from mnemostack.llm import get_llm
llm = get_llm("gemini") # or get_llm("ollama", model="llava") with a local vision model
desc = llm.describe_image(photo_bytes, mime_type="image/jpeg") # one vision call per image
caption = f" [shared a photo: {desc.text}]" if desc.ok and desc.text else ""
item = IngestItem(text=f"{message_text}{caption}", source=..., timestamp=...)
describe_image is fully opt-in — nothing in the ingest or recall paths calls it, and text-only pipelines are unaffected. It works with any provider that has vision support (Gemini; Ollama vision models such as llava, llama3.2-vision, qwen2.5-vl) — providers without it return a normal fail-open error response. The default prompt produces a dense, index-oriented description (objects, any text/signs verbatim, setting, actions); pass prompt= to customize. - Streaming-friendly. ing.stream(item_iter) yields per-batch stats so long feeds can be monitored without waiting for the whole stream to drain. - Graceful. If a single item fails to embed, it is counted as failed but the rest of the batch still lands.
```python
for item in your_firehose(): ing.ingest_one(IngestItem(text=item.body, source=item.channel, metadata={ "user_id": item.user_id, "ts": item.ts.isoformat(), })) ```
```python from mnemostack.embeddings import get_provider from mnemostack.vector import VectorStore from mnemostack.recall import Recaller, AnswerGenerator from mnemostack.llm import get_llm
emb = get_provider("gemini") store = VectorStore(collection="my-memory", dimension=emb.dimension) store.ensure_collection()
The 8-stage pipeline can use a small state store between calls (Q-learning weights, inhibition-of-return history, per-document gravity/hub counters). FileStateStore(path) persists it to a JSON file. HTTP recall applies existing state and can record inhibition-of-return exposure with --auto-record-ior; Q-learning updates only through explicit /feedback calls. CLI/MCP recall still apply existing state but do not collect feedback automatically. For deterministic benchmarks, call build_full_pipeline(enable_stateful_stages=False) so IoR/Q-learning/curiosity state cannot affect scores. For multi-process servers, implement your own StateStore (three methods: get(), set(), update()) backed by Redis or your database.
mnemostack feedback <hit-id> --signal clicked --query "what did we decide about auth" \ --source-list vector --source-list bm25
On LoCoMo, Mnemostack reaches 82.9% strict accuracy in our evaluation setup. The table below includes our baseline runs and externally reported numbers for context. Results depend on dataset version, configuration, judge model, scoring rules, and query type. Treat externally reported numbers as directional unless they were run with the same harness and settings.
Caveat: different judges, evaluation protocols, and in some cases category cherry-picking. Vendor numbers below are taken at face value from their published material.
| System | LoCoMo correct |
|---|---|
| Hindsight (reported range) | 78–85% |
| Memobase (temporal subset) | 85% |
| **mnemostack** | **82.9%** |
| Letta filesystem agent | 74% |
| Mem0 graph variant | ~68.5% |
| Zep (independently replicated) | 58.4% |
docker compose -f examples/docker-compose.yml exec mnemostack \ mnemostack index /data --provider gemini --collection demo
curl -s http://localhost:8000/recall \ -H 'content-type: application/json' \ -d '{"query":"what is this about","limit":5}' | jq
The mnemostack container runs the HTTP API on port 8000 by default. Interactive docs are at [http://localhost:8000/docs](http://localhost:8000/docs). Use `docker compose exec mnemostack mnemostack <cmd>` for CLI-style operations (`index`, `search`, `health`) against the same stack.
Tear down with `docker compose -f examples/docker-compose.yml down -v` (the `-v` wipes Qdrant + Memgraph state).
Prefer Ollama (no cloud key needed)? Run Ollama on the host and pass `--provider ollama` everywhere instead of `gemini`. The endpoint resolves as: `--ollama-host` flag > `MNEMOSTACK_OLLAMA_HOST` env / `embedding.ollama_host` config > the native `OLLAMA_HOST` variable > `http://localhost:11434` — so a client running in a container or VM can reach a remote Ollama daemon directly. An ollama **LLM** follows the same chain and inherits the embedding host by default; set `llm.host` / `MNEMOSTACK_LLM_HOST` only when generation lives on a different box:
bash mnemostack index-markdown memory/ \ --provider ollama \ --embedding-model qwen3-embedding:8b \ --ollama-host http://192.0.2.10:11434 \ --embedding-timeout 180 \ --embedding-batch-size 64
Embedding uses the batch `POST /api/embed` endpoint (one request per batch; servers too old for it are detected once and served per-item with a loud warning). The embedding timeout (`--embedding-timeout` / `MNEMOSTACK_EMBEDDING_TIMEOUT`, default 180s) is independent of the short Qdrant liveness timeout — cold loads of larger local models are legitimately slow. Vector dimensions come from the model tables (quantization-suffix aware) or, for unknown models, a one-shot probe of the live model — there is no blind fallback dimension, so a wrong-size collection can't be created.
**Behind an OpenAI-compatible endpoint** (LiteLLM proxy, vLLM, llama.cpp server, an API gateway)? Use the `openai` LLM provider — it speaks `POST {base}/v1/chat/completions`, which all of them accept:
bash MNEMOSTACK_LLM_API_KEY=sk-... mnemostack serve \ --llm openai --llm-model team-llm ```
with llm.host: http://gateway:4000 in the config (or MNEMOSTACK_LLM_HOST). Both the base URL and the model name are required — gateways have no meaningful defaults, so a missing one is a loud, actionable error (serve logs it and disables /answer) instead of a silent dial to the wrong place. A base URL already ending in /v1 (the OpenAI SDK convention) works too. Leave the key unset (or set it to none) for keyless vLLM / llama.cpp deployments; embeddings are unaffected and keep their own provider. Redirects are refused outright — a gateway 3xx becomes a normal error instead of carrying the bearer token to another origin. Reasoning models pointed straight at the cloud OpenAI endpoint (o1 family) reject the classic fields; via the SDK, get_llm("openai", token_param="max_completion_tokens", options={"temperature": None}) renames the budget field and drops the fields they refuse (gateways normally translate this themselves).
Reasoning models (qwen3, deepseek-r1 and similar): mnemostack disables thinking by default (think=False in OllamaLLM) — with thinking on, these models spend the whole token budget on thoughts and return empty text, silently degrading reranking, expansion and extraction. Pass get_llm("ollama", think=None) to keep the model's own default, or think=True to force it on models that support thinking. Extra generation options go through options={...} (e.g. {"num_ctx": 8192}).
answer = gen.generate(query, pool, category="count")
Override names: the seven category prompts (`general`, `list`, `count`, `temporal`, `multihop`, `inference`, `adversarial`) plus `list_extract` / `list_finalize`. Required placeholders are validated at construction. mnemostack ships no translations by design — prompt quality is corpus- and domain-specific, so you own the templates.
#### Full stack: 4-source retrieval + 8-stage pipeline + reranker
This is the full runtime configuration. The LoCoMo numbers above are produced by a subset of it: the benchmark loop runs Vector + BM25 retrieval, the 8-stage pipeline, `window_size=3`, query expansion, and top-K 25 — the LLM reranker and the graph retriever are runtime-only features and are not part of the benchmark methodology (see `benchmarks/run_locomo.sh` for the exact reproduction path).
python from mnemostack.embeddings import get_provider from mnemostack.llm import get_llm from mnemostack.vector import VectorStore from mnemostack.recall import ( Recaller, Reranker, VectorRetriever, BM25Retriever, MemgraphRetriever, TemporalRetriever, build_full_pipeline, ) from mnemostack.recall.pipeline import FileStateStore, default_state_path
emb = get_provider("gemini") store = VectorStore(collection="my-memory", dimension=emb.dimension)
retrievers = [ VectorRetriever(embedding=emb, vector_store=store), BM25Retriever(docs=bm25_docs), # see "Building a BM25 corpus" below MemgraphRetriever(uri="bolt://localhost:7687"), # optional TemporalRetriever(embedding=emb, vector_store=store), ] recaller = Recaller(retrievers=retrievers) raw = recaller.recall("what did we decide", limit=30)
pipeline = build_full_pipeline(state_store=FileStateStore(default_state_path())) reranked = pipeline.apply("what did we decide", raw) reranker = Reranker(llm=get_llm("gemini"), max_items=20) final = reranker.rerank("what did we decide", reranked)[:10]
`Reranker` is generative: it asks an LLM to return candidate IDs. If you have
a backend that returns numeric relevance scores instead (a local cross-encoder
or a hosted rerank service), use `ScoringReranker`:
python from mnemostack.recall import ScoringReranker
scoring_reranker = ScoringReranker(scorer=my_relevance_scorer, max_items=100) final = scoring_reranker.rerank("retention policy", reranked)[:10]
The scorer object only needs `score(query, documents) -> Iterable[float]`.
Scores are relative; no absolute threshold is applied by default. Generative
LLMs can be wrapped as scorers, but dedicated rerank models/services are the
more stable default because they avoid ID-format parsing.
##### Building a BM25 corpus
`BM25Retriever` needs a list of `BM25Doc`. Each doc is the atomic unit BM25 will rank — typically a paragraph or chunk of one of your source files:
python from mnemostack.recall import BM25Doc from pathlib import Path
docs = [] for i, path in enumerate(Path("my-notes/").rglob("*.md")): text = path.read_text() # chunk however you like — here: 800-char windows for j in range(0, len(text), 800): chunk = text[j : j + 800] if chunk.strip(): docs.append(BM25Doc( id=f"{path.name}:{j}", text=chunk, payload={"source": str(path), "offset": j}, ))
For transcript-like inputs (adjacent user and assistant turns), prefer `MessagePairChunker` so related turns stay in the same chunk. See `mnemostack.chunking`.
If your canonical memory corpus is already stored in Qdrant payloads, build the BM25 corpus from the same collection instead of maintaining a separate markdown export. This keeps exact-token lookup aligned with vector search (IDs, commit hashes, filenames, quoted phrases):
python from qdrant_client import QdrantClient from qdrant_client.models import FieldCondition, Filter, MatchValue from mnemostack.recall import BM25Retriever
client = QdrantClient(host="localhost", port=6333) bm25 = BM25Retriever.from_qdrant( client, "memory", scroll_filter=Filter( must=[FieldCondition(key="chunk_type", match=MatchValue(value="transcript"))] ), limit=40_000, )
hits = bm25.search("api_key_rotation", limit=5)
You can also call `bm25_docs_from_qdrant(...)` directly if you want to combine Qdrant payload chunks with local `BM25Doc`s before constructing `BM25Retriever`.
For morphologically rich languages or domain-specific normalization, pass a
custom tokenizer/analyzer. The same analyzer is applied to corpus and query
text; the default exact-token behavior is unchanged when omitted.
python from mnemostack.recall import BM25Retriever
def analyzer(text: str) -> list[str]: # Normalize only what your corpus needs; preserve IDs, hashes and paths. ...
bm25 = BM25Retriever.from_qdrant(client, "memory", tokenizer=analyzer) ```
If you pre-tokenize BM25Doc objects yourself, pass retokenize=False when constructing BM25/BM25Retriever with the same analyzer. The BM25Retriever.from_qdrant(...) helper does this automatically.
mnemostack 是一个先进的智能体记忆管理系统,旨在为 AI Agent 提供持久化且具备上下文感知能力的存储方案。它通过多层级的检索机制,能够让 Claude、ChatGPT 或 Cursor 等支持 MCP 协议的智能体拥有跨会话的长期记忆,确保在复杂的对话或长时间的代码任务中,智能体依然能够准确检索并理解用户的历史偏好与决策。
mnemostack 采用了创新的 4-source 混合检���技术,集成了 Vector (Qdrant)、BM25 (精确 Token)、Memgraph (知识图谱) 以及 Temporal (时间感知向量) 四种数据源。系统通过 Reciprocal Rank Fusion (RRF) 算法进行加权融合,并支持通过 `adaptive_weights` 实现自适应权重调整,开发者还可以通过可插拔的 `Retriever` 抽象自定义检索源,实现高度灵活的记忆增强。
您可以通过 `pip install 'mnemostack[mcp]'` 进行快速安装。对于需要向量存储和知识图谱功能的场景,建议使用 Docker 部署 Qdrant 和 Memgraph 环境。此外,项目还提供了 Docker Compose 示例,允许开发者在 30 秒内通过一键启动的方式完成整个开发环境的搭建,无需手动配置复杂的 Python 依赖。
mnemostack 提供多种接入方式:对于 Claude Desktop、Cursor 等智能体用户,通过 MCP server 即可实现极速接入;对于开发者,可以通过 HTTP API 或 Python Library 构建自定义应用。其核心应用场景包括为长周期运行的代码 Agent 提供记忆、构建具备用户偏好记忆的聊天机器人,以及通过 payload `filters` 实现多用户记忆隔离的个人助手。
项目通过环境变量进行配置。例如,使用 Gemini 模型时需配置 `GEMINI_API_KEY`;若使用本地 Ollama 服务,需设置 `OLLAMA_HOST`。此外,`MNEMOSTACK_COLLECTION` 用于指定 Qdrant 的集合名称。针对不同的扩展功能,可以通过安装特定的 extras(如 `[huggingface]` 或 `[mcp]`)来灵活管理环境。
mnemostack 提供了强大的流式摄取 API (Ingestor),支持从代码、爬虫或日志流中自动进行数据分批、去重和幂等性处理。开发者可以使用 Python API 构建复杂的检索逻辑,通过 `VectorStore` 进行数据管理,并结合 `Recaller` 和 `AnswerGenerator` 实现从检索到生成答案的完整闭环,支持在 FastAPI 或 Celery 等异步框架中运行。
mnemostack 内部运行一个包含 8 个阶段的高级流水线,并支持通过 `FileStateStore` 持久化存储中间状态(如 Q-learning 权重、抑制历史等)。这种设计使得系统不仅能处理单次查询,还能通过记录反馈信号来不��进化其检索策略,实现从原始数据摄取、多源检索到最终答案生成的智能化工作流。
针对常见问题,mnemostack 支持通过 Docker 容器运行 HTTP API,允许用户直接通过 `curl` 命令对索引后的文档进行查询。系统内置了智能分类器,能够识别查询意图并自动路由。此外,用户可以通过 CLI 工具记录显式反馈(如 `mnemostack feedback`),将交互结果反馈至状态文件,从而持续优化检索质量。
mnemostack是一个有会的MCP模式器求序,当前的器求序会实义会序。不得常很的器求序。
AI Skill Hub 为第三方内容聚合平台,本页面信息基于公开数据整理,不对工具功能和质量作任何法律背书。
建议在沙箱或测试环境中充分验证后,再部署至生产环境,并做好必要的安全评估。
✅ Apache 2.0 — 宽松开源协议,可商用,需保留版权声明和 NOTICE 文件,含专利授权条款。
总体来看,本记为器求序会 是一款质量良好的MCP工具,在同类工具中具备一定竞争力。AI Skill Hub 将持续追踪其更新动态,建议收藏备用,结合自身场景选择合适时机引入使用。
| 原始名称 | mnemostack |
| 原始描述 | 开源MCP工具:Durable hybrid memory for AI agents: vector + BM25 + temporal + graph recall, ex。⭐6 · Python |
| Topics | mcpagent-memoryai-agentshybrid-retrievalllm-memorylong-term-memory |
| GitHub | https://github.com/udjin-labs/mnemostack |
| License | Apache-2.0 |
| 语言 | Python |
收录时间:2026-07-08 · 更新时间:2026-07-08 · License:Apache-2.0 · AI Skill Hub 不对第三方内容的准确性作法律背书。
选择 Agent 类型,复制安装指令后粘贴到对应客户端