Skip to content
Conduit

mcp server

memkernel

Code-aware, tiered memory for AI coding copilots: MCP server + Python library (ChromaDB, hybrid search, consolidation, LLM-wiki bridge)

gosha70/memkernelcommunitylistedunreviewed

Set up

Runs on your computer. Pick your agent and operating system.

Before you start

  • uv (`curl -LsSf https://astral.sh/uv/install.sh | sh`)
~/Library/Application Support/Claude/claude_desktop_config.json
{
  "mcpServers": {
    "memkernel": {
      "command": "uvx",
      "args": [
        "memkernel"
      ]
    }
  }
}

same format Cursor, GitHub Copilot CLI, Junie, JetBrains AI Assistant, Augment (Auggie CLI), Factory Droid, Kiro, Devin CLI +12

Values in angle brackets are placeholders. Restart Claude Desktop after editing its config.Claude Desktop docs

README

synced Oct 25, 2026

CI PyPI Python License: Apache-2.0

Site: memkernel.dev · Package: pypi.org/project/memkernel

Code-aware memory server for AI coding copilots.

MemKernel gives Claude Code (and any MCP-compatible copilot) persistent memory across sessions. When Claude compacts context or a session ends, project decisions, conventions, and code understanding are normally lost. MemKernel stores them in a local vector database and retrieves them on demand.

Key differentiator: MemKernel understands code as code. It uses tree-sitter AST parsing to chunk files at function and class boundaries, embeds chunks with scope context, and provides hybrid search (dense + BM25) for both semantic queries and exact identifier lookup.

Tiered memory (0.2): memories live in three tiers with different persistence rules — working (the current task, hours), short-term (episodes and observations, decays unless recalled) and long-term (decisions, conventions, facts, procedures, code; never decays, replaced by supersession). Recall ranks by relevance, recency and importance; consolidate and promote move what proved useful into long-term memory; an LLM-wiki bridge keeps a human-readable, git-versioned copy of long-term knowledge. Design: docs/architecture/memory-tiers.md.

What MemKernel is, and what it is not

It is:

  • Persistent memory for a coding agent. What the agent retains survives context compaction and the end of a session, in a local ChromaDB store per project.
  • Explicit. The agent decides what to retain (decisions, conventions, facts, code snapshots, tasks) and what to recall. The lifecycle hooks only automate the protocol around that: checkpoint before compaction, recovery after it, consolidation and revalidation at session start.
  • Code-aware. AST chunking, hybrid search that finds identifiers as well as meaning, and revalidation of code memories against the files they came from.
  • No mandatory generative LLM. Default retrieval uses a local sentence-transformer embedding model plus BM25 and RRF; nothing generates text inside MemKernel. ask with the built-in reasoner returns the best-matching chunks; synthesis is the agent's job, or an optional external reasoner's.

It is not:

  • A token optimizer or context compressor. MemKernel does not shrink the context window, compress prompts, or lower model cost by itself. It adds tool calls and returned content to a session. Any token saving comes from the agent re-reading fewer files or documents after a resume; that is a side effect to measure, not a promise.
  • A replacement for files, specs and git. Progress notes, task lists and review records already carry state between sessions. MemKernel adds retrieval by meaning and identifier, tiers with decay, supersession and revalidation on top. When a file already holds everything the next session needs, expect no gain.
  • An index of the whole repository. code memories are the snapshots the agent chose to retain, not an automatic scan of every file.
  • A cross-project knowledge base. One store per project id. Sharing is deliberate: a second server with a shared id, or the wiki bridge.

Evidence. eval/run_eval.py measures retrieval (recall@k, precision@k, MRR). The 0.5 lifecycle evaluation, a maintainer-run supplemental evaluation (memkernel's own runner, preregistered and announced in #17 before the run), ran the same agent in seven arms on 20 tasks: report. No gain is claimed. Against files alone (claude_md), memkernel's memory store was ahead on 6 tasks, behind on none and tied on 14 (Holm-adjusted p 0.0625: "no detectable difference"; with so many ties this task set could not have reached significance); against earlier transcripts (fs_grep) it was equivalent. Two of the four task groups hit a ceiling, every arm passing every trial. The report's disclosures list its deviations and known defects. Results are published with the cases that show no gain; the 0.5 report is the latest evaluation, and 0.6.0 had no new measured run.


Requirements

  • Python 3.11 – 3.13 (the package declares Requires-Python >=3.11,<3.14; pip reports "from versions: none" when the installing interpreter is outside that range)
  • Poetry (for development installs)

Install

MemKernel is a Python package on PyPI. Its memkernel command is the MCP server. There is no npm package: an npx install someone suggests is a hallucination.

Recommended: an isolated install with pipx, which puts the memkernel command on your PATH without touching any other Python environment:

pipx install memkernel
# if pipx's default interpreter is outside 3.11–3.13:
pipx install --python python3.11 memkernel
# optional: wiki-studio lint and health findings on wiki_export (ADR-010)
pipx install "memkernel[wiki]"
pipx inject memkernel "wiki-studio>=0.7.0,<1"   # add it to an existing install

As a library inside a project, install it into that project's virtual environment:

pip install memkernel        # or: poetry add memkernel

Avoid a plain pip install memkernel into a shared or system interpreter (Homebrew's python3, for example). MemKernel pins chromadb 1.x and recent cffi, so pip will replace older copies and can break unrelated tools that need them.

Verify the install with pipx list or which memkernel. Running memkernel with no arguments starts the MCP server on stdio and waits for a client; memkernel --version and memkernel --help print and exit.


Add to Claude Code

1. Register the MCP server

Add MemKernel to .claude/settings.json in your project:

{
  "mcpServers": {
    "memkernel": {
      "command": "/Users/you/.local/bin/memkernel",
      "args": [],
      "env": {
        "MEMKERNEL_PROJECT_ID": "my-project",
        "MEMKERNEL_CHROMA_PERSIST_PATH": "/Users/you/.memkernel/my-project"
      }
    }
  }
}

Replace my-project with a short identifier for the project — memories are isolated per project. Use the absolute path of the command (which memkernel prints it) because Claude Code does not necessarily see the PATH changes your shell profile makes, and an absolute MEMKERNEL_CHROMA_PERSIST_PATH keeps the store outside the repository. On first server initialization, MemKernel downloads the embedding model (about 90 MB) if it is not already cached: build_service() loads the sentence-transformers model when the MCP server starts, before any tool call, so the first connection can take a minute. Later starts and every tool call are fast.

  • pipx install memkernel says "No matching distribution found" — pipx used an interpreter outside 3.11–3.13. Pass one explicitly: pipx install --python /opt/homebrew/opt/python@3.11/bin/python3.11 memkernel.
  • bash: /some/old/path/memkernel: No such file or directory right after reinstalling — the shell cached the old location. Run hash -r (bash) or rehash (zsh), then which memkernel.
  • memkernel --help seems to hang on versions before 0.4.0 — the command had no flags and started the server; press Ctrl-C. From 0.4.0 on, --help and --version print and exit.
  • The first MCP connection is slow or times out — the server is downloading the embedding model at startup (see above). Wait for it once, or pre-warm it with python -c "from sentence_transformers import SentenceTransformer; SentenceTransformer('all-MiniLM-L6-v2')" in the same environment.
  • A global pip install broke another tool (for example langchain-chroma wanting chromadb < 0.6) — uninstall memkernel from that interpreter, reinstall the versions the other tool needs, and install memkernel with pipx instead.

2. Add memory instructions to CLAUDE.md

Copy the contents of templates/claude-memory-instructions.md into your project's CLAUDE.md. This tells Claude when and how to call the memory tools.

3. (Optional) Install lifecycle hooks

Copy the hook scripts from templates/hooks/ to .claude/hooks/ and register them in .claude/settings.json:

HookClaude Code eventWhat it does
session-start.pySessionStartBy source: on startup and clear, consolidates, recovers the latest checkpoint and long-term context, lists promotion candidates; on compact, the recovery after a compaction (the latest checkpoint the agent saved, with its age, and long-term context; no consolidation); on resume and fork, nothing
pre-compact.pyPreCompactRuns a consolidation pass (Claude Code passes no summary here, so no checkpoint is retained)
post-compact.pyPostCompactRetains Claude Code's compaction summary (compact_summary) as a checkpoint for the next session; prints nothing for the model
session-end.pySessionEndWrites one line to the session spool in MEMKERNEL_STATE_DIR (no store, no model, within SessionEnd's 1.5-second budget); the next session start says when a session ended without a saved checkpoint

Each injected memory line ends with its ref_id, so get(ref_id) returns the full text; a block never exceeds 9,000 characters (Claude Code caps hook output at 10,000), and a cut checkpoint says which get holds the rest. At session start the long-term context is the most important decisions, conventions, procedures and facts, not the matches of a fixed query; after a compaction it is recalled with the checkpoint's text. Register SessionStart without a matcher, so it runs for every source, and session-end.py under SessionEnd. Claude Code adds only SessionStart's output to the model's context, not PreCompact's or PostCompact's, so the recovery after a compaction comes from SessionStart with source: "compact". After upgrading memkernel, copy the three scripts again.

See the Prerequisites section inside each script for the Python environment requirement. Each script is a thin wrapper over MemoryLifecycle, so an in-process harness gets the same behaviour without the scripts.


Add to Kiro

Kiro (and the Kiro CLI, formerly Amazon Q Developer CLI) connects to MemKernel as a stdio MCP server. templates/kiro/ holds the three files to copy into a workspace:

TemplateDestinationRole
settings/mcp.json.kiro/settings/mcp.jsonServer entry with env and autoApprove
steering/memory.md.kiro/steering/memory.mdMemory instructions, always included
hooks/memkernel.json.kiro/hooks/memkernel.jsonSessionStart recall, Stop retain, manual checkpoint

templates/kiro/README.md has the setup steps, the Kiro CLI custom-agent variant and the caveats (absolute persist path, hook format by Kiro version).


MCP Tools

ToolWhat it does
retain(content, type, session_id?, checkpoint?, source?, importance?, tags?, supersedes?)Store a memory. Returns ref_id. Idempotent — identical content + type returns the same ref_id. supersedes hides the memory it replaces.
recall(query, type?, tier?, session_id?, checkpoint_only?, top_k?, include_needs_review?, order?)Search by natural language or identifier. Returns lightweight ArtifactRef list ranked by relevance + recency + importance. Records an access (reinforces retention). Code memories flagged needs_review are hidden unless include_needs_review=true.
get(ref_id)Fetch full content of a specific memory. Records an access.
forget(ref_id)Delete a memory from all indexes.
promote(ref_id, type, content?, importance?, tags?)Turn a short-term/working memory into a long-term one (you supply the distilled text). Keeps provenance via promoted_from.
consolidate(session_id?, limit?, synthesize?, revalidate?)Purge expired short-term memories and list the ones most worth promoting (ranked by importance × how often they were recalled). synthesize=true also asks an external reasoner to draft long-term statements (suggested); revalidate=true first checks every code memory against its source file (revalidated). The report always carries a health summary.
ask(ref_id, question, mode?)Answer a question about one memory without loading it — for a document the full body is consulted. mode: direct | rag | rlm | auto, passed to the reasoner unchanged; the built-in reasoner answers in rag mode with the best-matching chunks. Returns answer, mode, citations, usage.
wiki_export(wiki_dir?)Compile long-term memories without a wiki page into draft markdown pages (index.md, log.md updated).
wiki_import(wiki_dir?)Index wiki pages as long-term memories: approved revisions only with wiki-studio ≥ 0.7.0, every page without it (idempotent; changed pages supersede; corrects supersedes the memories a page corrects).

Memory types and tiers

TypeTierUse forDefault importanceExpiry
decisionlong-termArchitecture choices, library selections0.9Never (supersede instead)
conventionlong-termNaming rules, code style, project patterns0.9Never
factlong-termDurable project facts: build commands, environments, contracts0.7Never
procedurelong-termHow-to that worked: workflows, recipes, verified skills0.8Never
codelong-termFunction/class snapshots, API contracts; identified by (source, content), a changed file supersedes the old snapshot, a drifted source flags needs_review0.6Never (superseded on re-retain)
documentlong-termLarge artefacts (specs, transcripts, files): the body is stored and chunk-indexed, get returns a summary, ask answers from the indexed chunks (local reasoner) or the body (RLM Studio)0.6Never
episodeshort-termSession events, compaction checkpoints0.430 days, extended by every recall
observationshort-termFindings, tool outputs, things noticed0.37 days, extended by every recall
taskworkingThe current goal, plan state, open questions0.524 hours

Retention: ttl = base_ttl × (1 + access_boost × access_count), measured from the later of creation and last access. Long-term memories never expire; a newer memory that supersedes an older one hides it from recall.

Session protocol

  1. Session start — recall(tier="long_term", query=<task>), plus the latest checkpoint (checkpoint_only=true, order="newest": newest first, not the most relevant). The session-start.py hook does this and prints promotion candidates; after a compaction it prints a shorter recovery block instead.
  2. While working — retain(type="task") when the plan changes; retain(type="observation") for findings; durable knowledge as decision / convention / fact / procedure.
  3. Before compaction — retain(type="episode", checkpoint=true) with your own summary of where the work stands; the pre-compact.py hook runs consolidate(), and the post-compact.py hook keeps Claude Code's compaction summary as a checkpoint.
  4. After compaction / end of phase — promote(ref_id, type, content) for the candidates worth keeping.
  5. Large artefacts — retain(type="document", content=<body>, source=<name>) then ask(ref_id, question); never get a document to read it.
  6. Code snapshots — retain(type="code", content=<file>, source=<path>); retain the file again after editing it (the new snapshot supersedes the old one). The session-start hook revalidates snapshots against their files and lists the ones that need re-retaining.
  7. Curate — wiki_export → review and approve the drafts in wiki-studio → wiki_import.

Quick examples

retain(
  content="We chose PostgreSQL over SQLite because the team needs concurrent writes",
  type="decision",
  session_id="session-2024-06-01"
)

recall(query="database choice", top_k=5)

recall(query="session checkpoint", checkpoint_only=True, order="newest", top_k=1)  # the latest checkpoint

retain(content="pytest hangs unless MEMKERNEL_PROJECT_ID is set", type="observation", tags=["ci"])

consolidate()                      # -> {"purged": 0, "candidates": [{"ref_id": ..., "access_count": 3, ...}]}

promote(ref_id="<candidate>", type="fact", content="CI must export MEMKERNEL_PROJECT_ID before pytest.")

retain(content="Use arq, not Celery", type="decision", supersedes="<old decision ref_id>")

get(ref_id="<ref_id from recall>")

forget(ref_id="<ref_id>")

retain(content=<the whole spec>, type="document", source="specs/auth/spec.md")

ask(ref_id="<document ref_id>", question="Which endpoints need rate limiting?", mode="auto")

Configuration

All settings are read from environment variables with the MEMKERNEL_ prefix, or from a .env file. Entries in .env without the prefix (keys for other tools) are ignored; a MEMKERNEL_ key that matches no setting is logged as a warning at startup.

VariableDefaultDescription
MEMKERNEL_PROJECT_IDdefaultProject namespace — memories are isolated per project
MEMKERNEL_DEFAULT_SESSION_IDdefaultSession tag applied when session_id is omitted from retain
MEMKERNEL_CHROMA_PERSIST_PATH.memkernel/chromaChromaDB data directory. Empty string = in-memory (ephemeral). On POSIX systems (Linux, macOS), opening it takes a short lock on .memkernel-open.lock inside it, so the MCP server and a hook opening a new store at once do not collide; where fcntl is unavailable (Windows) the store opens without the lock, and two processes creating a new store at the same moment can still fail
MEMKERNEL_CHROMA_COLLECTION_PREFIXmemkernelPrefix for ChromaDB collection names ({prefix}_{project_id})
MEMKERNEL_EMBEDDING_MODELall-MiniLM-L6-v2Sentence-transformers model name
MEMKERNEL_EMBEDDING_DIMENSION384Output dimension of the embedding model
MEMKERNEL_SEARCH_TOP_K5Default number of results returned by recall
MEMKERNEL_RRF_K60Reciprocal rank fusion constant (higher = flatter score distribution)
MEMKERNEL_PREVIEW_LENGTH200Max characters in the preview field of ArtifactRef
MEMKERNEL_CHUNK_SIZE1500Max characters per AST chunk (code memories)
MEMKERNEL_CHUNK_OVERLAP200Character overlap between token-based fallback chunks
MEMKERNEL_DOCUMENT_STORElocalWhere document bodies live (local = the project's own ChromaDB collection)
MEMKERNEL_DOCUMENT_SUMMARY_CHARS1000Characters of the body kept in a document memory's summary record
MEMKERNEL_REASONERlocalReasonerPort behind ask: local (hybrid search over chunks, no LLM) or rlmstudio (falls back to local when rlmstudio is not importable)
MEMKERNEL_ASK_TOP_K5Chunks the local reasoner returns per ask
MEMKERNEL_PROJECT_ROOT.Directory that relative source paths of code memories resolve under; nothing outside it is ever read. Set it whenever the server or a hook is not started from the project directory
MEMKERNEL_HEALTH_DUPLICATE_THRESHOLD0.95Cosine similarity in [0, 1] from which two long-term memories count as near-duplicates in the health pass
MEMKERNEL_HEALTH_DUPLICATE_LIMIT500Most recent long-term memories the near-duplicate check covers
MEMKERNEL_HEALTH_REPORT_LIMIT20Items listed per category in a health report
MEMKERNEL_EPISODE_TTL_DAYS30Base TTL for episode memories (short-term)
MEMKERNEL_OBSERVATION_TTL_DAYS7Base TTL for observation memories (short-term)
MEMKERNEL_TASK_TTL_HOURS24Base TTL for task memories (working)
MEMKERNEL_ACCESS_BOOST0.5Each recall extends a short-term memory's TTL by this fraction of its base TTL
MEMKERNEL_RECENCY_HALF_LIFE_DAYS7.0Recency decay half-life used in recall scoring (short-term tier)
MEMKERNEL_RECALL_WEIGHT_RELEVANCE1.0Weight of hybrid-search relevance in the final recall score
MEMKERNEL_RECALL_WEIGHT_RECENCY0.3Weight of recency in the final recall score
MEMKERNEL_RECALL_WEIGHT_IMPORTANCE0.3Weight of importance in the final recall score
MEMKERNEL_WIKI_DIR``Directory of the LLM wiki used by wiki_export / wiki_import (empty = pass wiki_dir explicitly)
MEMKERNEL_STATE_DIR.memkernel/statememkernel's own state outside ChromaDB: the wiki sync ledger. Resolved like MEMKERNEL_CHROMA_PERSIST_PATH (a relative path is relative to the process's working directory, so set it absolute when the persist path is); keep it out of git
MEMKERNEL_WIKI_PROPOSALS_DIR""Where wiki-studio patch-sets are written before promote (memkernel[wiki]); empty means <project root>/.memkernel/wiki-proposals. Setting it requests the gate: without wiki-studio installed, a warning is logged.
MEMKERNEL_TRANSPORTstdiostdio or streamable-http
MEMKERNEL_HOST127.0.0.1Host for streamable-http transport
MEMKERNEL_PORT8765Port for streamable-http transport

Transports

stdio (default)

Used by Claude Code and most MCP clients. The client spawns the server as a subprocess.

memkernel           # stdio, uses defaults

Streamable HTTP

For persistent server processes or multi-client setups:

MEMKERNEL_TRANSPORT=streamable-http memkernel

The server listens at http://127.0.0.1:8765/mcp by default.


Embedding MemKernel in your own harness

from memkernel.factory import build_service
from memkernel.core.models import MemoryType

svc = build_service()                      # settings from MEMKERNEL_* env vars
svc.retain("Use Postgres", MemoryType.DECISION)
for ref in svc.recall("database"):
    print(ref.tier, ref.score, ref.preview)
report = svc.consolidate()                 # {"purged": n, "candidates": [...]}

build_service() is the supported entry point; the MCP server, the admin API and the hook templates all use it. Integration notes for code-copilot-team and haa are in docs/integration/harness-integration.md.

The session protocol as a component

MemoryLifecycle packages what the hook scripts do, so a harness calls five methods instead of re-implementing the protocol. Every method returns markdown ready for injection and does nothing but talk to the service:

from memkernel.factory import build_service
from memkernel.lifecycle import MemoryLifecycle

memory = MemoryLifecycle(build_service())
system_context = memory.on_session_start()       # checkpoint, long-term context, promotion candidates, wiki pointer
...
memory.on_pre_compact(summary)                   # checkpoint episode (only if summary is not blank) + consolidation
recovered = memory.on_post_compact()             # what to re-inject after your own compaction
memory.on_session_end(summary)                   # final checkpoint + consolidation

# Driving it from Claude Code's hook events instead (what templates/hooks/ do):
memory.on_session_start(source)                  # source "startup"/"clear": full block; "compact": recovery block; "resume"/"fork": ""
memory.capture_compact_summary(compact_summary, session_id=..., trigger=...)  # keep Claude Code's own summary as a checkpoint

Documents and ask

retain(type="document") keeps large artefacts out of the context window: the body goes to the document store (built-in: the project's own ChromaDB collection), its chunks are indexed for hybrid search, and the memkernel record is a summary. ask(ref_id, question, mode) answers through a ReasonerPort:

  • MEMKERNEL_REASONER=local (default) — no LLM: the question is matched against the document's chunks with dense + BM25 + RRF and the best chunks come back as the answer, mode="rag".

  • MEMKERNEL_REASONER=rlmstudio — delegates to rlm-studio's interact(content, question, mode=mode), so direct, rag, rlm and auto select how the document is read (ADR-009). rlm-studio is not a memkernel dependency and is not on PyPI yet; install it from git into the same environment:

    pip install "rlm-studio @ git+https://github.com/gosha70/rlm-studio"
    

    When it is not importable, build_service() logs a warning and uses the local reasoner. With an external reasoner, consolidate(synthesize=True) also drafts long-term statements from the session's short-term memories.

LLM wiki bridge

Long-term memory has a human-readable twin: a markdown wiki in the LLM Wiki pattern (index.md, log.md, typed pages with cited sources — the same schema as code-copilot-team/knowledge/wiki).

  • wiki_export compiles every long-term memory that has no page yet into a draft page under decisions/, concepts/ or workflows/, with the required sections and a memkernel://<project>/<ref_id> citation. Existing pages are never overwritten.

  • wiki_import indexes pages as long-term memories so recall finds the reviewed version; a changed page supersedes its earlier memory. With wiki-studio 0.7.0 or later installed it is governed ("governed": true): it imports only pages wiki-studio's review state reports approved, and only when the page's bytes are still the approved revision, so drafts, self-declared approvals and pages edited after approval are not imported (not_approved says why); an edited page keeps its last approved memory until it is approved again. memkernel never approves a page; a curator does, with wiki-studio approve. Without wiki-studio the import reads the page files, drafts included, and reports "governed": false. An installed wiki-studio that is broken or older than 0.7.0 imports nothing and reports import_error.

  • Withdrawal (governed import, 0.7). A page's memory leaves the current answers, its record kept for history, when:

    • the page is deleted. Only memories this wiki's sync ledger recorded as imported are withdrawn this way; a memory imported before 0.7, or without wiki-studio, never is.
    • the page is deprecated, once the status: deprecated revision is approved.
    • the page is superseded (superseded_by: <page path>). Its memory is replaced by the successor page's memory; if the successor has none yet, the old memory is withdrawn and linked once it does (pending until then).

    The response lists withdrawn, superseded, pending and sync_failed (retried at the next import). The ledger is one JSON file per project and wiki under MEMKERNEL_STATE_DIR/wiki-sync/; a corrupt one is kept aside, reported as ledger_error, and rebuilt.

  • Corrections. An approved page can name the memories it corrects in its frontmatter: corrects: <ref_id>, <ref_id> (one line; tokens of letters, digits, - and _). On import each named memory that did not come from the wiki is superseded by the page's memory: it stops being a current answer, and its record is kept. References that cannot be applied are listed in unresolved with a reason (unknown, another project's, already superseded, or a wiki memory: correct that page instead); a value that breaks the grammar is listed in invalid_corrects, and the page is still imported. An approved page that is already current is listed in unchanged, and its corrections are reconciled again on every import, so a reference that could not be applied earlier takes effect once it can; corrections already applied are not repeated.

Set MEMKERNEL_WIKI_DIR (for example knowledge/wiki) or pass wiki_dir to the tools. The standalone maintainer that owns this page schema is wiki-studio. With the memkernel[wiki] extra installed, every wiki_export response carries wiki-studio's lint and health findings for the whole wiki after the export, curated pages included, under lint (violations, audit_errors, pages_checked, health items with kind, severity, pages, description); without it lint is null and lint_hint says how to enable it; if the lint pass itself fails, lint is null and lint_error says why. The files written are the same in every case.

With wiki-studio 0.7.0 or later installed, wiki_export writes through wiki-studio's promote gate: wiki-studio's init creates index.md and log.md if they are missing (wiki-studio's own scaffold, status: stable), then every new page goes with its index and log lines into one patch-set that is proposed and promoted. Other writers can work in the same wiki: wiki-studio's commit writes only the paths the patch-set names, under its lock, and refuses a page someone else created in the meantime. A rejected export writes no page and the response carries gate_error (a new wiki keeps the index.md and log.md that init created); so does an installed wiki-studio that is broken or older than 0.7.0, rather than falling back to the direct writer. A promoted export's response adds proposal, submission_digest and archived_dir from wiki-studio's receipt. If archived_dir is null, the pages landed but wiki-studio did not finish: it could not archive the proposal, or it failed after committing the pages (memkernel checks wiki-studio's receipt: while it still says committing, every planned write, index and log included, must be at its post-write sha256; a committed receipt needs memkernel's pages at theirs; an unusable receipt falls back to comparing the pages' bytes). archive_hint gives the promote command that tries to finish it, with absolute paths, so it runs from any directory. It applies nothing twice, but if the wiki or the proposal changed since, for example another writer appended to index.md, it may refuse with uncertain_outcome or payload_changed and write nothing; then compare the wiki with the proposal's receipt.json by hand. memkernel never runs it itself. If wiki-studio failed during its commit and its receipt shows neither a commit nor a rollback (say the page was written, the index.md write failed and removing the page failed too), the response carries gate_error with uncertain_outcome and proposal: written is empty, but the pages it names may be on disk; compare the wiki with the receipt.json it names before exporting again. receipt_error says the receipt could not be read or used. A failed export leaves its patch-set under the proposals directory for inspection; delete it once the cause is fixed. Patch-sets go to MEMKERNEL_WIKI_PROPOSALS_DIR, by default .memkernel/wiki-proposals/ under the project root. Commit <wiki>/.audit/, wiki-studio's audit trail of promoted proposals; ignore .memkernel/wiki-proposals/, .memkernel/state/ (memkernel's sync ledger) and wiki-studio's lock file next to the wiki directory, .<wiki-dir>.wiki-studio.lock (for knowledge/wiki, knowledge/.wiki.wiki-studio.lock).

Rationale: docs/adr/ADR-008-llm-wiki-as-semantic-layer.md and docs/adr/ADR-010-wiki-studio-as-wiki-maintainer.md.

Revalidation and store health

A code memory is a snapshot of a file. MemoryService.revalidate() hashes each live snapshot's whole source file and compares it with the stored content hash: a changed file flags the memory needs_review (changed), a vanished file flags it missing, a matching file clears the flag. Flagged memories leave default recall until the file is retained again (which supersedes the stale snapshot) or they are asked for with include_needs_review=true. Revalidation runs from consolidate(revalidate=true), at every session start through MemoryLifecycle.on_session_start() (a ### Needs review section lists what to re-retain), and from POST /revalidate. Sources resolve under MEMKERNEL_PROJECT_ROOT (default: the current directory); set it whenever the server or a hook is not started from the project directory. Nothing outside that root is ever read. 0.4 hashes whole files; per-chunk invalidation is a follow-up.

MemoryService.health() audits the store without changing it: orphaned chunk and document records, dangling supersedes / superseded_by / promoted_from / parent_ref links, supersessions from 0.2/0.3 that lack the durable marker, code memories whose source file is gone, memories flagged for review, expired short-term memories not yet purged and, in full mode only, near-duplicate pairs among the most recent long-term memories (cosine ≥ MEMKERNEL_HEALTH_DUPLICATE_THRESHOLD). The quick report (counts only, no embeddings, no content loads) is part of every consolidate() report and of the memory://status resource; the full report is GET /health/store. The pass never fixes anything: a fix mode is a later feature.

Admin API

A FastAPI admin server provides health and stats endpoints (independent of MCP):

uvicorn memkernel.api.app:app --host 127.0.0.1 --port 8766
EndpointDescription
GET /healthServer health and memory count
GET /statsTotal memory count, per-type and per-tier breakdown, growth rate, last recall latency
GET /collectionsList ChromaDB collections
GET /health/storeFull store health report (orphans, dangling links, stale sources, near-duplicates, …)
POST /revalidateCheck every code memory against its source file; flag changed or missing ones

Development

git clone https://github.com/gosha70/memkernel
cd memkernel
poetry install
poetry run pytest --tb=short -q          # full test suite
poetry run pytest -m "not slow" -q       # skip slow tests
ruff check . && mypy src/                # lint + type check

CI runs on every push and PR: lint, type check, tests (≥ 80% coverage), security scan, and build verification.

Releasing

Releases are published to PyPI by .github/workflows/release.yml using trusted publishing (no token in the repo). To cut a release:

  1. Set version in pyproject.toml and __version__ in src/memkernel/__init__.py to X.Y.Z, and move the [Unreleased] entries in CHANGELOG.md under [X.Y.Z] - <tag date>.

  2. Merge to main with CI green.

  3. Tag and push: git tag -a vX.Y.Z -m "memkernel X.Y.Z" && git push origin vX.Y.Z.

  4. Create the GitHub Release from the tag, with the version's CHANGELOG section as its notes:

    awk -v v=X.Y.Z 'index($0, "## [" v "]") == 1 {f=1; next} f && /^## \[/ {exit} f' CHANGELOG.md > release-notes.md
    gh release create vX.Y.Z --title X.Y.Z --notes-file release-notes.md --verify-tag
    

The workflow refuses a tag that does not match the pyproject.toml version, builds the sdist and wheel, smoke-installs the wheel, and uploads through the pypi environment.


Architecture

src/memkernel/
├── mcp/           # MCP server + tool definitions (memory tools, wiki tools)
├── core/          # Domain models (tiers, types), MemoryService, ports (interfaces)
├── adapters/      # Optional external systems (RLM Studio reasoner), imported lazily
├── storage/       # ChromaDB adapter, local document store
├── retrieval/     # AST/token chunking, embeddings, hybrid search (dense + BM25 + RRF + rescoring), local reasoner
├── memory/        # Lifecycle: TTL/decay policies, retention, deduplication
├── wiki/          # LLM-wiki bridge: page model, export, import
├── api/           # FastAPI admin endpoints
├── factory.py     # build_service() — the supported embedding entry point
├── lifecycle.py   # MemoryLifecycle — the session protocol as a component
└── config.py      # Pydantic settings

Ports-and-adapters design: core/ depends only on protocols in core/ports.py, never on concrete backends. Storage and embedding backends are swappable.

Further reading: docs/architecture/memory-tiers.md (design), docs/research/memory-literature-review.md (HRM, JEPA, MemGPT, Mem0, Zep, HippoRAG, Generative Agents, Titans, …), docs/research/sibling-projects.md (what is shared with rlm-studio, document-assistant, ragkit, code-copilot-team, haa), docs/adr/, docs/roadmap.md.


Related projects

MemKernel is the memory component of a small family of projects by the same author; each is usable on its own.

ProjectRoleRelationship
wiki-studioLLM-wiki maintainer (ingest / promote / query / lint / audit), stdlib-onlythe human-readable form of long-term memory; wiki_export / wiki_import speak its page schema (ADR-008), and CI lints exporter output with it
rlm-studioRecursive Language Models workbench: Direct / RAG / RLM / auto over large documentsthe document layer behind document memories and the ask() tool (MEMKERNEL_REASONER=rlmstudio, ADR-009)
code-copilot-teamEnforceable harness configuration for Claude Code, Pi and other copilotsconsumes MemKernel through MCP and hooks
haaHome-built AI coding harness, future component assemblerconsumes MemKernel in-process through build_service()

The cross-repository sequence is in docs/roadmap.md.

License

Apache-2.0. See LICENSE and NOTICE. Changes are tracked in CHANGELOG.md.

↑↓ navigate openesc close