Долгая память
Механизмов два, и они не заменяют друг друга: карточка memory.md в системном префиксе и плагин memory с базой фактов, из которой достаётся релевантное.
memory.md — карточка
Файл <данные>/memory.md читается каждый ход и целиком приклеивается к системному промпту. Лимит — около 4 КБ по смыслу, а не по проверке: файл платится на каждом ходу любой сессии, поэтому в него идут только факты, нужные всегда и не добываемые разведкой за минуту. Остальное — указателями: «грабли проекта — в тикетах таких-то».
Правит агент. Дописывая, сжимай: две строки вместо одной — это рост префикса в каждой сессии навсегда.
Плагин memory
Один бинарь, один файл SQLite (FTS5 + sqlite-vec), без демона. Ядро знает только argv-контракт:
memory recall <запрос> # строки фактов; пусто = нечего сказать
memory retain # JSON {"user","assistant"} на stdin → очередь
memory flush # очередь → факты (LLM извлекает 0–5 фактов + эмбеддинги)
memory stats # живые/устаревшие факты, очередь, типы
memory reindex # досчитать вектора, если провайдер лежал
memory forget <uid> # закрыть факт по uid
recall — гибридный поиск: BM25 (FTS5) ∪ вектор (sqlite-vec, int8) → RRF → реранк cross-encoder'ом → относительный порог → не больше max_facts строк. LLM на пути чтения нет. Пустая выдача — законный исход: один нерелевантный факт роняет точность сильнее, чем помогает случайный.
flush — стадия дистилляции: модель извлекает факты, дедуп по векторам, перекрытие через supersedes — старый факт получает valid_to и остаётся в таймлайне. Зовут его таймером или руками, ядро этого не делает.
Что уходит в сеть
recall— эмбеддинг запроса: один HTTP-вызов кapi_base(/embeddings), плюс/rerankу реранкера. Локальная часть (BM25, вектор, RRF, порог) — на своей машине.flush— эмбеддинги каждого извлечённого факта и один вызов/chat/completionsдля самого извлечения.retain— ничего. Ход целиком пишется локально в очередь на дистилляцию, ноль сети. Ядро зовёт его после каждого ответа и ждать не должно.
Настройки
Свой конфиг плагина — ~/.config/snens/memory.toml:
db = "~/.local/share/snens/memory.db"
api_base = "https://…/v1" # любой OpenAI-совместимый
embed_model = "…"
distill_model = "…"
rerank_model = "…" # rerank = "off" — выключить реранк
max_facts = 6
max_tokens = 400
Ключ — SNENS_API_KEY в окружении или в ~/.config/snens/.env рядом с конфигом ядра; реранкер может жить у другого провайдера — тогда свой ключ и своя переменная. MEMORY_DEBUG=1 печатает в stderr скоры кандидатов до отсева.
Секция [memory] в config.toml управляет каналом, а не базой:
[memory]
enabled = true # мастер-выключатель: false — ни recall, ни retain
recall = "always" # always | session | off
retain = true
timeout_seconds = 60
cmd = "memory" # имя программы в каталоге тулов
recall — политика впрыска справки: always каждый ход, session один раз на сессию, off только по явному вызову тула. Справка короткая по замыслу: она уезжает в префикс и остаётся там.
Как выключить
snens memory off # весь канал
snens memory retain off # только запись ходов в банк
snens memory recall off # только впрыск справки
snens memory recall session # или always
snens memory # показать состояние
Правки действуют со следующего хода: живой процесс перечитывает секцию на старте цикла. Бинаря нет в PATH — канала нет, тихо.
Отличить пустую выдачу от неработающего канала можно по memory stats: он показывает живые/устаревшие факты и очередь.
Дальше: файлы инстанса · контекст