Generated banner for Fidelis Memory
Generated logo for Fidelis Memory

Fidelis Memory

Memory serverOfficial Hermes Labs project

Created by Hermes Labs

Published in hermes-labs-ai/fidelis. Publisher page

25 GitHub stars

What it is

Zero-LLM agent memory for Claude Code and AI agents: local-first BM25, dense-vector, and reciprocal-rank-fusion retrieval. Returns original passages verbatim by default. Available on PyPI as fidelis-memory. Apache-2.0.
— the description by Hermes Labs

AutoGineer overview, from the brain’s own files

Fidelis is a local-first memory infrastructure designed to provide AI agents with persistent, accurate recall of stored notes. Unlike systems that rely on generative models to summarize or rewrite information, Fidelis focuses on returning original passages verbatim. It operates as a background service on the user's machine, utilizing a vector store and hybrid retrieval techniques—including BM25 and dense-vector search with reciprocal rank fusion—to ensure high-fidelity recall. By integrating via MCP, it allows agents to store, recall, correct, and inspect memory records without requiring external API keys or cloud-based storage, making it suitable for developers and autonomous agents that need to maintain context across sessions.

How it works

Memory model: Vector store

AutoGineer overview, from the brain’s own files

Fidelis runs as a background service on the user's machine, managing a local store of text and Markdown notes. When a user or agent saves a note, it is indexed using local embeddings. The system provides an MCP interface for agents to interact with this memory. Retrieval is performed through a combination of keyword-based BM25 search and dense-vector search, which can be fused to find relevant passages. The system supports versioning through corrections, allowing records to be superseded without deleting historical data.

Folders
FolderRoleFiles
.githubagent-config5
assetsattachments1
docsdocs19
examplesexamples18
scriptsscripts1
srcsource-code42
teststests67
.agentsfolder1
.hermesfolder3
Formulafolder1
benchfolder144
claude-pluginfolder4
experimentsfolder30
extensionsfolder1

Requirements

Runtimes: Python >=3.10

Databases: Chroma

Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Google Antigravity, Kiro, OpenCode, Amp, Roo Code, Cline, JetBrains Junie, Devin (ex-Windsurf)

Set it up

  1. Not scanned by AutoGineerIts dependencies are installed by pip or uv when you run it and were not scanned by AutoGineer (3 declared). https://pypi.org/project/fidelis-memory/
  2. Created by Hermes Labs. Published in hermes-labs-ai/fidelis. Licence: Apache License 2.0.

Usage

AutoGineer overview, from the brain’s own files

  • Install the Fidelis memory server and initialize the background service.
  • Connect the agent to the Fidelis MCP server.
  • Use the 'fidelis_store' tool to save important facts or decisions.
  • Use the 'fidelis_recall' tool to retrieve original text based on natural language queries.
  • Use 'fidelis_correct' to update or supersede existing records while maintaining history.
  • Inspect memory status and availability using 'fidelis_health'.
“Use Fidelis to find my Atlas billing migration note. What must happen before we retry? Quote the relevant text.”
The agent queries the Fidelis memory server and returns the exact sentence: 'Do not retry until the idempotency fix is verified.'
“Store this note: The API endpoint for the user service has changed to /v2/users.”
The agent uses the fidelis_store tool to save the fact, which is then indexed and available for future retrieval.

Documentation by Hermes Labs

<!-- mcp-name: io.github.hermes-labs-ai/fidelis-memory -->

<div align="center">

Fidelis Memory

<img src="assets/fidelis-memory-artwork.png" width="420" alt="Fidelis Memory mascot" />

Your agent can forget a summary. Fidelis brings back the note.

Local memory for Codex, Claude Code, and other agents: save text across sessions and retrieve the original passage when it matters.

by Hermes Labs

PyPI · Connect an agent · Technical reference

</div>

A project summary might say “billing migration rolled back.” The original note says why it failed and do not retry until the idempotency fix is verified. When your next session depends on that condition, the missing sentence matters.

Fidelis stores your Markdown and text notes and retrieves their stored wording. Its default retrieval does not ask a generative model to rewrite your memory.

Try it with one note

This path supports Python 3.10+, macOS or Ubuntu, and a local Ollama service. Start Ollama if needed (ollama serve in a separate terminal). No model API key is required. The current release is 0.3.0rc1.

ollama pull nomic-embed-text
python3 -m venv ~/.venvs/fidelis
source ~/.venvs/fidelis/bin/activate
python3 -m pip install "fidelis-memory[hybrid]==0.3.0rc1"
fidelis init

demo_dir=$(mktemp -d)
cat > "$demo_dir/atlas.md" <<'NOTE'
Atlas billing migration:
Duplicate charges appeared in staging. Rolled back.
Do not retry until the idempotency fix is verified.
NOTE

fidelis watch "$demo_dir" --once
fidelis recall-hybrid "Atlas billing migration retry condition" --tier zero_llm

fidelis watch and fidelis recall-hybrid use the background service started by fidelis init.

Look for both the rollback and the retry condition in the retrieved text. This is retrieval of a saved note, not a generated answer. For your own files, replace "$demo_dir" with a notes directory. If retrieval fails, run fidelis health and confirm Ollama has nomic-embed-text available. Keep the virtual environment after fidelis init; the background service uses it.

What this changes on your machine: fidelis init installs a per-user background service (launchd on macOS, systemd --user on Linux) that starts fidelis-server automatically at login. fidelis watch sends the note to that server, which saves it in the local store at ~/.cogito/store by default, not in the temporary demo directory. fidelis recall-hybrid also queries that same running server and searches the whole shared store, so its results can include notes you saved earlier, not only the demo note. The watcher also records ingested files in ~/.fidelis/watched.json. To remove the default service on macOS, run fidelis init --uninstall; it stops the service and deletes the launchd plist only. On Linux, find the installed unit with systemctl --user list-unit-files '*fidelis-server*', then run systemctl --user disable --now <unit>, delete that unit file from ~/.config/systemd/user/, and run systemctl --user daemon-reload. Neither path deletes your data. The store is shared by everything you save with Fidelis, so deleting ~/.cogito/store erases all of it, not just the demo note; do that only on a throwaway install. Also delete the temporary demo directory ($demo_dir), and ~/.fidelis/watched.json if you want its record of the file path gone.

Connect an agent

Once the note is retrievable locally, install the MCP connection for your client:

fidelis mcp install --client codex
# Or, for Claude Code:
fidelis mcp install

Restart the client and try: “Use Fidelis to find my Atlas billing migration note. What must happen before we retry? Quote the relevant text.”

The client gets tools to recall, store, correct, fetch, and inspect memory. It decides when to call them; installing the connection does not make every turn recall automatically. Other clients and uninstall commands include Cursor, Gemini CLI, Copilot CLI, and OpenClaw. A separate Pi extension provides prompt-time recall.

What Fidelis preserves

  • Original wording. watch, store, and other verbatim write paths keep the text you supplied, so retrieval can return the condition that a summary might omit.

  • Local retrieval. Fast vector search is the default MCP recall path. Explicit hybrid search combines keyword and vector results without a generative LLM at its zero_llm tier; local embeddings still require Ollama.

  • Corrections over time. A correction can supersede a prior record without erasing it. Recall can show validity and historical status instead of silently replacing the old note.

Fidelis also has optional extraction and model-assisted routes. Those can transform or filter inputs; use the verbatim path when the original wording is the point. Retrieved text can still be wrong or out of date, and your agent's model provider may see it when answering.

Current scope

Fidelis 0.3.0rc1 is a single-machine pre-release, not a hosted team memory service. The LongMemEval-S retrieval run measured whether the redesigned default path retrieved material across 470 questions; it did not measure answer accuracy. See the results, user-fit guide, and security policy for deeper detail.

Full reference · Upgrade notes · Contribute · Changelog

Apache-2.0. Hermes Labs builds agentic infrastructure for autonomous systems.

docs/DISPATCHER_DESIGN.md

Workload-Aware Dispatcher — Design Spec

Status: Draft v1 — 2026-04-16
Author: Architect review, Hermes Labs
Sources read:

  • src/cogito/recall.py — Path A: multi-query RRF + integer-pointer LLM filter

  • src/cogito/recall_hybrid.py — Path B: BM25+dense+RRF + tiered LLM escalation

  • bench/eval.py — 31-case eval harness (modes A–E)

  • README.md — public API surface + benchmark claims

  • CHANGELOG.md — v0.3.0 architecture history


1. Memory Classification at Insert Time

Problem

Path A is tuned for atomic facts (~50–200 chars, single-claim sentences).
Path B was benchmarked on multi-turn sessions (2,000+ chars, turn-structured).
Mixing them in one index degrades both.

Heuristic classifier (no LLM, zero latency)

Applied at /store and /add time. Result stored as metadata field mem_type.

Signals and thresholds:

SignalAtomicSession
len(text)< 400 chars>= 400 chars
Newline count0–1>= 3
Turn markers presentFalseTrue
Sentence count1–2>= 4

Turn markers (regex): r"(User:|Assistant:|Human:|>\s|\[\d{2}:\d{2}\]|Turn \d+)" — matches common dialog formats.

Sentence count: split on [.!?]\s+[A-Z], count separators + 1.

Decision rule (all signals weighted, majority wins):

def classify_memory(text: str) -> str:  # "atomic" | "session"
    score = 0
    if len(text) >= 400: score += 2
    if text.count('\n') >= 3: score += 1
    if re.search(TURN_MARKER_RE, text): score += 2
    if _sentence_count(text) >= 4: score += 1
    return "session" if score >= 3 else "atomic"

Edge cases:

  • Code blocks with newlines but no dialog → len(text) is the tiebreaker. A 600-char code snippet scores session but retrieves fine under Path B (BM25 handles token-exact code terms better). Acceptable.

  • Structured logs → same as code. Acceptable.

  • Multi-sentence atomic facts (e.g., a decision record with rationale, 300 chars, 3 sentences) → stays atomic because len < 400 and no turn markers. Correct.

  • Empty text or < 10 chars → atomic by default; invalid inserts are mem0's problem.

No measurement exists for the production insert-size distribution. Adjust the 400-char threshold after observing the bimodal gap (see Open Questions).


2. Query Dispatching at Recall Time

Option analysis

Option A — Per-type indexes: Atomic in one ChromaDB collection, sessions in another. Route by mem_type. Clean separation, no cross-contamination.

Option B — Blended: Always query both indexes, merge with a reranker. Best coverage, but doubles latency and requires a unified score scale.

Option C — Query classifier → pick one path: Single index, query intent selects the retrieval function. No per-memory routing. Misclassified memories surface under the wrong path.

Decision: Option A — per-type indexes.

Rationale: the performance gap (21pp on Path A's own eval) comes from path design, not the query. Atomic memories under BM25+dense fusion have worse R@1 because the BM25 component rewards term frequency, which is uninformative for short 1-sentence facts. Session memories under Path A's LLM filter see truncated 150-char snippets (hardcoded in recall.py:88) — insufficient context for multi-turn content. Separating the stores eliminates the mismatch without requiring a query classifier.

Implementation

Two ChromaDB collections per user:

  • {collection}_atomic (e.g., cogito_memory_atomic)

  • {collection}_session (e.g., cogito_memory_session)

At query time:

/recall (or /recall_hybrid)
  │
  ├─ query_type = classify_query(query)  [existing, from recall_hybrid.py]
  │
  ├─ if user passes mem_types=["atomic"]  → run Path A on atomic index only
  ├─ if user passes mem_types=["session"] → run Path B on session index only
  └─ if mem_types=["atomic","session"] (default)
       → parallel fetch from both paths
       → merge by score (cosine-normalized), deduplicate
       → return top N

Merge strategy for dual-index default:

  • Path A scores: cosine similarity (0–1 scale already)

  • Path B scores: cosine-blended RRF (0–1 scale already, recall_hybrid.py:302)

  • Simple merge: interleave by score, deduplicate by text hash. No additional LLM call.


3. Config + API Surface

New config keys
{
  "dispatch_mode": "auto",
  "atomic_collection": "cogito_memory_atomic",
  "session_collection": "cogito_memory_session"
}
Env varConfig keyDefaultDescription
COGITO_DISPATCH_MODEdispatch_mode"auto""auto" | "atomic" | "session" | "legacy"
COGITO_ATOMIC_COLLECTIONatomic_collection"{collection}_atomic"Atomic facts index name
COGITO_SESSION_COLLECTIONsession_collection"{collection}_session"Session memories index name
Endpoint changes

POST /store and POST /add — unchanged signature, new behavior:

  • Classify incoming text → write to the appropriate collection

  • Response adds "mem_type": "atomic" | "session" field (informational)

POST /recall — unchanged signature, unchanged default behavior when dispatch_mode="legacy":

  • dispatch_mode="auto" (new default for fresh installs): routes by mem_type index

  • dispatch_mode="legacy" (set automatically on upgrade for existing users): single-collection behavior, identical to v0.3.0

POST /recall_hybrid — gains optional mem_types field:

{"text": "...", "tier": "filter", "mem_types": ["atomic", "session"]}

Default: both. Existing callers without mem_types get both, same as before.

Breaking-change protection

Upgrade path: On server start, if the legacy collection exists and the atomic collection does not, set dispatch_mode="legacy" automatically and log a warning:

[cogito] Legacy single-collection store detected. Running in compatibility mode.
Run `cogito migrate-dispatch` to split into typed indexes.

cogito migrate-dispatch — new CLI command:

  1. Reads all memories from the legacy collection

  2. Classifies each, writes to the appropriate typed collection

  3. Verifies count parity before deleting legacy records

  4. Updates .cogito.json → sets dispatch_mode="auto"

Existing users are never silently migrated. Manual opt-in only.


4. Generalizability Guardrails

Cross-benchmark suite
BenchmarkWorkload typeWhat it testsStatus
cogito 31-case eval (bench/eval.py)Atomic paraphrase recalldirect_recall, semantic_gap, adversarial, cross-referenceExists
LongMemEval_SSession multi-turn retrievalTemporal ordering, multi-hop, countingExists (bench/longmemeval_combined_pipeline_flagship.py)
LOCOMO (add this)Conversational memory + question groundingMulti-session with structured eventsNot yet — download from https://github.com/snap-research/LOCOMO
MemGPT MemoryBench (add this)Agent task memory (tool-use sessions)Procedural + declarative in one corpusNot yet — https://github.com/letta-ai/letta

Why LOCOMO: Structured multi-session with explicit temporal scaffolds — validates Path B on a corpus not used to tune it. First out-of-distribution test.

Why MemoryBench: Tool-use agent memories are shorter than LongMemEval sessions but longer than cogito's atomic facts. Tests the classification boundary directly.

Regression gates (CI)

Add to bench/ci_gates.yaml:

gates:
  - benchmark: cogito_31case
    metric: recall_at_1
    path: A  # /recall
    floor: 0.72          # current 75%, allow 3pp measurement noise
    block_pr_if_below: true

  - benchmark: cogito_31case
    metric: recall_at_1
    path: E  # /recall_hybrid
    floor: 0.50          # current 54%; any improvement must not regress
    block_pr_if_below: true

  - benchmark: longmemeval_s
    metric: recall_at_1
    path: hybrid_flagship
    floor: 0.944         # current 96.4%, allow 2pp measurement noise
    block_pr_if_below: true

  - benchmark: cogito_31case
    metric: hit_at_any
    path: E
    floor: 0.93          # current 96.4%; must not regress even if R@1 improves
    block_pr_if_below: true

Gate logic: A PR fails CI if any gate's floor is breached. Floors are set at (current - measurement_noise). Noise budget is 3pp for 31-case (small N, stochastic LLM filter) and 2pp for LongMemEval (larger N, more stable).

Dual reporting requirement

Every cogito bench run (new CLI, see roadmap) must emit:

WORKLOAD COVERAGE MATRIX
                          /recall (Path A)   /recall_hybrid (Path B)
cogito 31-case R@1            75%                  54%
cogito 31-case hit@any        96%                  96%
LongMemEval_S R@1             [not measured]       96.4% (runP-v35)
LOCOMO R@1                    [pending]            [pending]
MemoryBench R@1               [pending]            [pending]

PRs that only update one cell must still report all measured cells. If a benchmark is not run (e.g., LOCOMO not installed), cells show [skipped], not blank. No commit can merge with a cell that regressed from its previous value without an explicit # regression-acknowledged: <reason> comment in the PR.

Workload coverage matrix (current state)
WorkloadPath A /recallPath B /recall_hybrid
Atomic paraphrase (direct_recall)75% R@154% R@1
Semantic gap (vocab mismatch)measured in evalmeasured in eval
Adversarial negativemeasured in evalmeasured in eval
Multi-turn session retrieval[not measured]96.4% R@1 (runP-v35)
Temporal ordering queries[not measured]tested via regex router
Counting/aggregation queries[not measured]tested via regex router

5. Publishing the Gap

Why both numbers are competitive advantage

Most retrieval libraries publish one benchmark on one workload and present it as universal accuracy. cogito-ergo already documents the gap honestly in the README (v0.3.0 Regression notice). This is not a liability — it is differentiation. A system that knows when it is wrong is more deployable than one that silently overfits.

Generalizability scorecard (public, in README):

COGITO RETRIEVAL SCORECARD
                        R@1     hit@any   Latency   Workload
/recall                 75%     96%       1197ms    atomic paraphrase
/recall_hybrid          96.4%   —         3500ms    session multi-turn (runP-v35)
/recall_hybrid (filter) 90%+    —         1300ms    session multi-turn
/recall_b               56%     96%       127ms     atomic (zero-LLM)

Benchmark sources: cogito 31-case eval (internal), LongMemEval_S (Xiao et al. 2024)

Format rule: every row shows benchmark source. No aggregate. No cherry-picking.

When a user asks "which memory system handles multi-turn sessions?", cogito-ergo answers with a number from a specific benchmark. When they ask "which handles atomic facts cheaply?", it answers with a different number from a different benchmark. That workload-specificity is the pitch.

Next public claim to support: After LOCOMO integration, publish Path B R@1 on LOCOMO. If it holds at >=85%, the generalization story is credible without a paper.


Open Questions (measure before building)

  1. Insert distribution: What fraction of current production memories are session-like (>400 chars)? If <5%, the per-type index split adds complexity with minimal benefit. Measure: cogito query "." --limit 500 | jq '[.memories[].text | length] | add/length'.

  2. Cross-type queries: When a query spans both types (decision = atomic, context = session), the dual-index merge handles it by returning from both. Needs a dedicated test case before shipping.

  3. LongMemEval_S on Path A: Currently unknown. Measure it to confirm the 21pp regression is directionally real, not a 31-case artifact.

367 files · 337 not served here

Advantages

  • Retrieves original passages verbatim by default, avoiding potential inaccuracies introduced by generative summarization. evidence: README.md
  • Supports hybrid search combining BM25 and dense-vector retrieval for improved accuracy. evidence: README.md
  • Maintains historical integrity by allowing corrections to supersede records without erasing the original data. evidence: README.md

Limitations

  • Requires a local Ollama service for embedding generation. evidence: README.md
  • Currently a single-machine pre-release and not a hosted team memory service. evidence: README.md
  • Retrieved text may still be outdated or incorrect depending on the quality of the stored information. evidence: README.md

Security

Risk level: Low risk

This brain runs

Agent instructions that run or fetch something (4)

  • .agents/skills/fidelis/SKILL.md: Wiring an MCP memory tool into Claude Code, Codex, GitHub Copilot CLI, Gemini CLI, or OpenClaw via `fidelis mcp install`
  • .agents/skills/fidelis/SKILL.md: Executable name is `fidelis`, not `fidelis-memory` — `uvx fidelis-memory` fails;
  • .agents/skills/fidelis/SKILL.md: use `uvx --from fidelis-memory fidelis <cmd>`.
  • .agents/skills/fidelis/SKILL.md: install `fidelis-memory`.

Git hooks (1)

  • .pre-commit-config.yaml: bash -c 'git grep -nE "rbr_lpci" -- ":!.pre-commit-config.yaml" && exit 1 || exit 0'

MCP servers (3)

  • claude-plugin/.mcp.json (fidelis): uvx --from fidelis-memory==0.3.0rc1 fidelis mcp serve
  • gemini-extension.json (fidelis): uvx --from fidelis-memory==0.3.0rc1 fidelis mcp serve
  • mcp.json (fidelis): uvx --from fidelis-memory==0.3.0rc1 fidelis mcp serve
  • Low risk: network-instructions (.agents/skills/fidelis/SKILL.md:5)

Attribution and licence

Created by
Hermes Labs
Declared authors
  • Hermes Labs (declared in pyproject.toml)
  • Rolando Bosch Rodriguez (declared in CITATION.cff)
Publisher
Hermes Labs (Official Hermes Labs project)
Source
hermes-labs-ai/fidelis · commit 9504c4a
Licence
Apache License 2.0 (files served here)
  • ATTRIBUTION.md (written by AutoGineer)
Synced

AutoGineer is not affiliated with or endorsed by Hermes Labs.

Install from source

337 of its files are not served here, so there is no ZIP. hermes-labs-ai/fidelis at 9504c4a