awhm-mcp
AWHM Lite
Внешняя долговременная память для LLM-агентов. Без облака, без API-ключей, работает полностью локально.
AWHM Lite даёт любому LLM постоянную память между разговорами: журналирование в режиме «только добавление», сопоставление по регулярным выражениям, граф памяти с учётом противоречий, консолидация без вызовов LLM, а также поиск через слияние лексических и семантических признаков.
Статус: исследовательский прототип. Создан в феврале 2026 года, опубликован в августе 2026 года; v0.2.0 укрепила его, v0.3.0 добавила хуки, Stage 2, разрешение сущностей, путешествия во времени, хранение в SQLite и оценку на реальном корпусе. 145 тестов, CI на Python 3.11–3.13.
Документация проекта
docs/awhm-whitepaper.md: полная статья об архитектуре AWHM, подмножеством которой является этот проектdocs/awhm-whitepaper-vs-lite.md: что Lite сохраняет, а что оставляет за бортомdocs/Future Plans.md: запланированный следующий шаг (незаметное промежуточное ПО для каждого хода)
Related MCP server: claude-memory-mcp
Как это было создано
Архитектура и стоящие за ней идеи — мои. Код был целиком написан ИИ-агентами кодирования (в основном Claude Code) под моим руководством: я задавал дизайн, определял объём задач, просматривал результаты и направлял работу. Уайтпейпер был создан тем же способом.
INTERACTION TIME OFFLINE (SESSION END)
──────────────────── ─────────────────────
┌──────────────────┐ real-time log ┌──────────────────────┐
│ PRIMARY AGENT │──────────────────► │ STAGE 1 CONSOLIDATION│
│ (user-facing) │ (middleware, │ (symbolic only, │
└──────┬───────────┘ no LLM) │ zero LLM calls) │
│ └──────────┬───────────┘
│ queries │ writes
▼ ▼
┌──────────────┐ ┌──────────┐ ┌──────────────────────┐
│ RETRIEVAL │◄───│ SESSION │ │ FLAT MEMORY GRAPH │
│ ENGINE │ │ BUFFER │ │ │
│ │◄───┤(checked │ │ nodes: episodic, │
│ BM25 + │ │ first) │ │ semantic, procedural│
│ embedding │ └──────────┘ │ │
│ similarity │◄───────────────────│ edges: typed │
│ │ │ strength: rec + freq│
└──────────────┘ └──────────────────────┘
▲
│ fallback (first ~10 sessions)
┌──────┴───────┐
│ RAW LOGS │
│ (append-only)│
└──────────────┘Установка
# Core (numpy, spaCy, dateparser) plus the sentence-transformers embedding model
pip install -e ".[embeddings]"
# spaCy NER model (used in consolidation; without it, entity extraction is skipped)
python -m spacy download en_core_web_sm
# Claude Code MCP integration
pip install -e ".[mcp]"
# Optional: Anthropic SDK client for Stage 2 (the default Stage 2 client is
# the Claude Code CLI and needs nothing extra)
pip install -e ".[anthropic]"sentence-transformers опционален, поскольку тянет за собой PyTorch. Без него запускайте сессии с use_mock_embeddings=True (детерминированные векторы на основе хеша — достаточно для тестов и для знакомства с CLI). Настоящая модель (all-MiniLM-L6-v2, 22 МБ) загружается при первом использовании.
Быстрый старт
API на Python
from awhm import AWHMSession
from awhm.types import Role
# Start a session (also usable as a context manager: `with AWHMSession.start_session() as session:`)
session = AWHMSession.start_session()
# Log messages
session.log_message(Role.USER, "My name is Alice")
session.log_message(Role.ASSISTANT, "Hello Alice!")
session.log_message(Role.USER, "I prefer Python over JavaScript")
session.log_message(Role.USER, "The API endpoint is https://api.example.com/v2")
# Query memory (works immediately via session buffer)
results = session.query("What language does the user prefer?")
for r in results:
print(f"[{r.source}] {r.content}")
# Consolidate into long-term memory graph
session.consolidate_current()
# End session (flushes WAL, saves graph)
session.end_session()Интеграция с LLM
AWHM работает как промежуточный слой. Он не вызывает никакой LLM — вы подключаете его к чему угодно:
from awhm import AWHMSession
from awhm.types import Role
session = AWHMSession.start_session()
def handle_message(user_text):
session.log_message(Role.USER, user_text)
# Retrieve relevant memories
memories = session.query(user_text, k=5)
memory_context = "\n".join(f"- {m.content}" for m in memories)
# Inject into system prompt
system = f"Memories from past conversations:\n{memory_context}"
response = your_llm_call(system_prompt=system, user_message=user_text)
session.log_message(Role.ASSISTANT, response)
return response
# At end of conversation:
session.consolidate_current()
session.end_session()CLI
awhm status # Show system stats
awhm query "Python preferences" # Search memory
awhm query "API endpoint" --include-history --trace
awhm consolidate # Run Stage 1 on pending sessions
awhm snapshot create # Backup current graph
awhm snapshot list # List snapshots
awhm snapshot restore --path FILE # Restore from snapshot
awhm delete NODE_ID # Hard-delete a node (privacy)
awhm eval --json # Run built-in benchmark reportИнтеграция с Claude Code (хуки, рекомендуемый способ)
Благодаря хукам память работает на каждом ходе, и модели не нужно вызывать инструмент. Каждый хук — это отдельный короткоживущий процесс; между ними буфер сессии восстанавливается из своего журнала упреждающей полезной записи.
Событие | Команда | Что делает |
|
| Журналирует запрос, извлекает лучшие воспоминания (BM25 + буфер; добавьте |
|
| Журналирует ответ ассистента |
|
| Консолидирует сессию в граф (добавьте |
awhm hook settings # prints the block to merge into ~/.claude/settings.jsonХуки никогда не блокируют сессию: любая ошибка пишется в stderr, и процесс завершается с кодом 0. Задайте AWHM_DATA_DIR, чтобы изменить расположение памяти.
Интеграция с Claude Code (MCP)
AWHM Lite поставляется как MCP-сервер, чтобы Claude Code мог использовать его как инструмент.
Настройка
# Install with MCP support
cd awhm-lite
pip install -e ".[mcp]"
# Register with Claude Code
claude mcp add --transport stdio awhm-lite -- awhm-mcpИли добавьте вручную в .claude/settings.json:
{
"mcpServers": {
"awhm-lite": {
"type": "stdio",
"command": "awhm-mcp",
"env": {
"AWHM_DATA_DIR": "~/.awhm"
}
}
}
}Доступные MCP-инструменты
Инструмент | Описание |
| Поиск в памяти по теме запроса на линейном языке (необязательные |
| Записать сообщение в закрытый журнал разговора |
| Извлечь воспоминания из ожидающих обработки сессий в граф |
| Показать количество узлов, рёбер и сессий |
| Создать резервный снимок |
| Полностью удалить узел, заодно очистить попадающие в сё данные снимков |
После подключения Claude Code автоматически получает доступ к этим инструментам и может искать и сохранять воспоминания между кластерными разговорами.
Как это работает
Исходные журналы
Каждое сообщение добавляется в файл JSONL (по одному на сессию). Режим только добавить, не изменяется никогда, кроме жёстких удалений для конфиденциальности. Это источник правды.
Буфер сессии
По каждому сообщению пользователя в реальном времени работает сопоставитель на основе регулярных выражений, ловящий:
Исправления: «на самом деле, X — это Y», «нет, это X»
Предпочтения: «я предпочитаю X», «всегда X», «никогда не делай X»
Факты: «endpoint — это X», «меня зовут X»
Результаты: «это сработало», «это не сработало»
Он захватывает примерно 60–70% явных сигналов с нулём обращений к LLM. При поиске буфер проверяется первым — это даёт мгновенную целостность внутри сессии. При поиске по умолчанию записи буфера, которые более позднее утверждение заменяет (тот же слот или явное исправление через пару сообщений), скрываются, так что исправления побеждают. Сохраняется через журналы упреждающей записи на сессию (сброс каждые 30 см, если нечего изменений нет — не сбрасывается).
Граф памяти
Плоский ориентированный граф с тремя типами узлов (эпизодический, семантический, процедурный) и тремя типами рёбер (временное, абстракция, ассоциация).
Теперь каждый узел несёт метаданные жизственного цикла противоречий:
canonical_key(идентификатор типа слота, напримерфакт: мой любимый язык,)status(active,superseded,outdated)supersedes(идентификаторы старых узлов, заменённых этим)valid_from/valid_toconfidence
Хранится как JSON, загружается в память.
Обратная совместимость: старые файлы графа (без полей жизненного цикла) автоматически мигрируются в памяти при загрузке.
Оценка силы
У каждого узла есть составная сила-оценка:
S(v) = 0.4 * recency + 0.6 * frequencyСвежесть — по степенному распаду: s_rec = (1 + 0.1 * hours)^(-0.3) — примерно 0.79 через 24 часа, 0.40 через 7 дней, 0.27 через 30 дней. Частота — количество обращений, нормированное на 90-й перцентиль.
Консолидация (Stage 1)
Запускается в конце сессии, ноль запросов LLM:
Named Entity через spaCy: люди, организации, места, продукты. Числовые и временные метки лексем (CARDINAL, MONEY, DATE, …) отфильтровываются: они создавали шумнее вершины. Настраивается через
ner_labels.Разбор времени через dateparser: распознавание «вчера», «5 марта» в ISO-метки.
Извлечение по правилам: те же регулярные выражения, что и в буфере сессии, для новых сообщений.
Связывание сущностей: сопоставление сущностей уже существующим вердам (одно умножение косинусных матриц, затем согласование типа сущности и проверка строковой близости).
Дедупликация: одинаковые утверждения в пределах пакета сжимаются; почти дубли существующих узлов (косинус > 0.92) усиливают имеющийся узел, а не создают новый.
Коммит: назначение канонических ключей, замена противоречивых воспоминаний, добавление вершин и рёбер, обновление сил.
Противоречия: канонические ключи
Канонический ключ задаёт слот, который заполняет утверждение. Два активных воспоминания с одинаковым ключом занимают одно место, поэтому новое заменяет старое (status=superseded, valid_to выставляется, на новом узле появляется связь supersedes).
Выражение | Ключ |
«Любимый язык — Python»` |
|
«Я живу в Кейптауне» |
|
«Я предпочитаю тёмную тему» |
|
«Никогда не используй табы» |
|
«Я использую Python для скриптовэтинг» | нет (аддитивное) |
Правила сознательно консервативны, потому нет LLM-модели — нет судьи намерений:
Одинаковый ключ: всегда заменать (слот переформулся новым значением).
Семейства «предпочитаю» и «политика»: явное указание («на самом деле я предпочитаю Rust») переопределяет предыдущее утверждение того же семействуют, если оно появилось в том же кластере в течение
correction_window_messages(по умолчанию 3) от него. Без маркера исправления предпочтения аддитивны: «prefer tabs» и «prefer dark mode» остаются активны оба.Семейство фактов: замена происходит только при точном совпадении ключа, так что исправление по API-эндпоинту не заценит ваше имя.
Всё нераспознанное не получает ключа и никогда ничего не заменяет.
Сущности
Named entities резолвятся в один узел, как бы они ни были записаны. Поверхностные формы нормализуются (регистр, притяжательность, кортеж суффиксов, домены: „Acme Holdings Ltd“ и „acme.com“ оба упрощаются до „acme“), затем к исходному выполняются идеальный элас, и по безусловному токен- вхождению („Acme“ внутри „Acme Holdings“), и вообще по эмбеддингам, той же тип wala. Каждый найденный упомен записывается как алиас узла, а заявления получают ассоц-ребя к называемым сущностям, так что retrieval может перейти от „Acme“ ко всему, что о ней известно.
St. 2 (необязательный LLM-рефаймент, без наружного API-ключе)
У Stage 1 жёсткий предел: он ловит фразу «я предпочитаю Rust», но пропускает обходную «давай то оneal Rust». Stage 2 запускают после полуания офлайн, он просит model на предложи memories, которые правила не нашли. LLM только предлагает: код wh-проверяет каждая предложение (схема, наличие цитируемыхе сообщенийные номера обязательны, минимальный капитал) и сбрасывает уже captured, и проходят через те же слот-идеи и правила вытеснения. Ретривер стаётся zero-LLM.
Клиент по умолчанию запускает out-of-process Claude Code CLI (claude -p со strict output structure) — использует вашу существующую логин-процедуру, никакого ключа API не хранится. Он помечается вызов, чтобы memory hooks не сработали внутри этого вызова.
awhm consolidate --stage2 # Claude Code CLI, default model
awhm consolidate --stage2 --stage2-model sonnetfrom awhm import AWHMSession, AWHMConfig
config = AWHMConfig(stage2_enabled=True, stage2_model="sonnet")
with AWHMSession.start_session(config) as session: # builds ClaudeCodeClient
...
session.consolidate_current()Любой объект против complete_json(system, user, schema) -> str может действовать как client (llm_client=...). Для применяющих оплату по API, приложеный в комплект кли Spec (Anthropic SDK) (stage2_client="anthropic", extra intro [anthropic]).
Поиск
Zero LLM-вызовов. Признаковая фюзия:
Проверка буфера: сначала том в буфера сессии (мгновенные попадания, всегда выше результатов из графа)
Поиск якорей: BM25 терм обозначений + косинусительная близость эмбеддингов (union). Индекс BM25 построится в процессе (Lucene-style IDF, даже мелкие корпусы адекватно составляются) и кеш до изменений узлов.
История filter: по умолчанию доступны только nodes с
status=activeФпризнак исчисляет: семическая близость + лексикальный цичет + сила + уверенность, минус штраф за противоречие* от number вы precision. Сила пересчитывается только для кандидатов на уровне рейтингования
Вернуть топ-к (default 10)
Распространение соседей: к ним из первый-hopов (связанной entities, последовательных episode) объединяются в кандидаты с распащей веса, оцениваемым уже по feature
association. Смещение может только из-за, которые сами currentCold-start fallback: у ф- (для первых ~10 сессий) также запускается BM25 по raw logs. Эти покрыты разряда в
[0,0, raw_log_score_scale], никогда не могут стоять над реальными graph-матчами.
Путешествия во времени
Тот факт времени несёт окно действительности. Даты introduced через „from“/„since" ставится понимают valid_from, „until" означает valid_to, а supersesjor закрывает окно старого факта. query(..., as_of="2026-03-01") отвечает, что было true в тот момент, включая superseded memories:
awhm query "API endpoint" # what is true now
awhm query "API endpoint" --as-of 2026-02-01 # what was true thenИспользуйте include_history=True, чтобы выдать на свет superseded/retracted воспоминания.
Используйте with_trace=True, чтобы получить на каждым результаты ranking-feature trace.
Evaluation
Встроенный benchmark — это синтетический smoke test (трое умеющие correction-heavy запросы плюс удаление) продіння подозрительный места. Реальные цифранов при повторении его playback:
awhm eval # built-in synthetic benchmark
awhm eval --corpus my_sessions.json # native format, see below
awhm eval --corpus longmemeval_s.json --longmemeval --limit 50Оба возвращают Recall@k, nDCG@k, уровень ошибок противоречий, задержку p50/p95 и полноту по категориям. Родной формат корпуса — {"sessions": [{"id", "messages": [{"role", "content"}]}], "questions": [{"id", "question", "expected": [...], "forbidden": [...], "as_of", "category"}]}.
Экземпляры LongMemEval консолидируются и опрашиваются изолированно, в соответствии с протоколом бенчмарка. Совпадение определяется по подстроке ответа — это намеренно заниженная нижняя граница: перефразированные совпадения не учитываются.
Измерено (только этап 1, oracle-сплит, 500 вопросов): Recall@5 0.196 — от 0.40 на односеансовых пользовательских фактах до 0.00 на предпочтениях, при 4 ms на запрос. Это наглядный предел regex; этап 2 существует, чтобы его поднять. Полная таблица, оговорки и воспроизведение результатов в docs/benchmarks.md.
Конфигурация
Все параметры настраиваются через AWHMConfig:
Параметр | По умолчанию | Описание |
| 0.3 | Скорость затухания (показатель степенного закона) |
| 0.1 | Константа масштабирования затухания |
| 0.4 | Вес недавности в оценке силы |
| 0.6 | Вес частоты в оценке силы |
|
| Профиль взвешивания при поиске |
| 0.55 | Вес семантической близости |
| 0.20 | Вес лексической составляющей BM25 |
| 0.15 | Вес силы узла |
| 0.10 | Вес достоверности консолидации |
| 0.35 | Штраф за неактивные воспоминания |
|
| Включать заменённые/отозванные воспоминания по умолчанию |
|
| Выводить трассировку ранжирования по умолчанию |
| 10 | Число элементов в top-k поиске |
| 0.85 | Косинусный порог для связывания сущностей |
| 0.92 | Косинусный порог для дедупликации |
| 0.5 | Лексический якорь, если оценки >= доля × лучшая оценка BM25 |
| 0.3 | Минимальное косинусное сходство для множества якорей |
| 0.5 | Верхняя граница для cold-start raw-log оценок попаданий |
|
| Добавлять соседей графа якоря на один переход, с этим множителем веса рёбер |
| 0.10 | Вес свидетельств соседей в смеси |
|
|
|
|
| Офлайн-доработка LLM после первого этапа |
|
|
|
| 60 / 0.5 | Сообщений на один вызов LLM; варианты с уверенностью ниже этого порога отбрасываются |
| 3 | Насколько близко должно быть явное исправление, чтобы заменить действие/политику |
| PERSON, ORG, GPE, ... | Метки сущностей spaCy, которые становятся узлами |
| 30s | Интервал синхронизации WAL |
|
| Зарезервированный режим ANN-индекса |
|
| При жёстком удалении затирать соответствующие снапшотные воспоминания |
from awhm.config import AWHMConfig
config = AWHMConfig(
data_dir="~/.my-project-memory",
k=20,
w_rec=0.5,
w_freq=0.5,
)Каталог данных
~/.awhm/
├── logs/ # Raw JSONL logs (one per session)
│ ├── {session_id}.jsonl
│ └── ...
├── graph/
│ ├── memory_graph.json # The memory graph (storage_backend="json")
│ └── memory_graph.sqlite # ... or one row per node (storage_backend="sqlite")
├── snapshots/
│ └── snapshot_{timestamp}.json # Manual backups
├── wal/
│ └── {session_id}.wal # Per-session write-ahead logs
└── meta/
├── consolidated_sessions.json # Tracks which sessions have been processed
├── deletion_tombstones.jsonl # Deletion tombstones
└── deletion_ledger.jsonl # Deletion audit ledgerТестирование
pip install -e ".[dev]"
pytest tests/ -vВсе тесты используют MockEmbeddingService (детерминировано между процессами, без загрузки моделей). ruff check . запускает линтер; CI запускает его и тесты на Python 3.11, 3.12 и 3.13.
Зависимости
Пакет | Размер | Назначение |
name | ~29 MB | Векторные вычисления |
numpy | ~29 MB | Векторные вычисления |
spacy + en_core_web_sm | ~35 MB | NER-разметка |
dateparser | ~2 MB | Разбор дат |
tensor (not always?) | ~3 MB (+PyTorch ~350 MB) | Модель эмбеддеров |
sentence-transformers (optional, | ~3 MB (+PyTorch ~350 MB) | Модель эмбеддингов |
mcp (optional, | ~1 MB | Интеграция с Claude Code |
BM25 реализован внутри пакета (около 60 строк), поэтому зависимостей для ранжирования нет.
Эмбеддиинг-модель (all-MiniLM-L6-v2, 22 MB) загружается при первом использовании в ~/.cache/huggingface.
Структура проекта
src/awhm/
├── __init__.py # AWHMSession facade (top-level API)
├── config.py # All parameters + path helpers
├── types.py # Enums: Role, NodeType, NodeStatus, EdgeType, BufferEntryType
├── mcp_server.py # MCP server for Claude Code
├── hooks.py # Claude Code hook commands (prompt / stop / session-end)
├── timeutil.py # Timestamp parsing, validity windows
├── eval/ # Built-in benchmark + real-corpus replay (LongMemEval loader)
├── raw_log/ # Append-only JSONL logging
├── session_buffer/ # Regex pattern matching + WAL
├── graph/ # Memory graph, strength scoring, JSON/SQLite stores
├── consolidation/ # NER, temporal, extraction, entities, dedup, Stage 2, pipeline
├── retrieval/ # Embedding, BM25, ranking, retrieval engine
├── snapshots/ # Snapshot create/restore/list
├── deletion/ # Hard-delete cascade
└── cli/ # argparse CLIThis server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceAn MCP server that allows Claude and other LLMs to manage persistent memories across conversations through text file storage, enabling commands to add, search, delete and list memory entries.657MIT
- AlicenseNot gradedqualityCmaintenanceAn MCP server that gives Claude Code cross-session memory persisted to a plain .claude-memory.md file in your repo.MIT
- AlicenseNot gradedqualityDmaintenanceA persistent memory MCP server for Claude Code that enables long-term recall across sessions via hybrid search, code intelligence, and tools for reading/writing memory.231MIT
- AlicenseNot gradedqualityBmaintenanceA MCP server that gives Claude Code and other AI assistants long-term memory by automatically extracting technical knowledge from conversations and retrieving relevant experiences in future sessions.14MIT
Related MCP Connectors
Cloud-hosted MCP server for durable AI memory
Hosted MCP server connecting claude.ai, ChatGPT and other AI apps to your own computer
One memory, every AI: Claude, ChatGPT, Perplexity, Gemini, Cursor, OpenClaw, Hermes, any MCP client.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/juderosendev/awhm-lite'
If you have feedback or need assistance with the MCP directory API, please join our Discord server