From 4f526a508eea87fd6a14148fd912a19fda1e58d1 Mon Sep 17 00:00:00 2001 From: Cipher208 <269750686+Cipher208@users.noreply.github.com> Date: Thu, 8 Oct 2026 00:34:31 +0200 Subject: [PATCH 1/3] fix(privacy): drop the incidental operator names and ship the package, not the repo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two changes in one pass, because the packaging fix decided where the first one had to reach. Tree — the names that WERE the privacy feature stay; the ones that were prose are gone. CHANGELOG (two quoted utterances and the agent_id list), AUDIT_REPORT_20260629, ARIEL_RULES, the autohooks DSH example, one Alembic revision comment, purge_foreign_rows' docstring, pyproject's "handled by Lucy" comment, and eleven test files where a name stood in for a role. Kept deliberately: config.yaml `rag.ru_personas`, rag/synonyms.py, privacy.py, test_privacy_ru.py and the graph_miners canon test — those name lists are the anonymizer's input, and deleting them deletes the feature. Six docs/compose/ files that had been force-added past .gitignore are untracked again. They stay on disk; three docstrings cite them and never read them. Wheel — packages = ["."] made the repository root the package root, so 1.9.0 shipped tests/ (115 files), docs/, .github/, uv.lock, the Dockerfile and the raw pytest logs: 445 files against 276 now. only-include names the runtime trees explicitly. The real risk was dropping something read at import time, and it bit: config.py loads config.yaml from its own directory, so the first cut installed cleanly and then failed on a missing default config. The in-repo test run cannot see this — the file is always there. Installing into a fresh venv caught it; config.yaml is included now and the install loads all 23 top-level keys. test_output.txt and full_test_output.txt joined .gitignore. They were never tracked and still shipped, because hatch walked the filesystem rather than git. --- .github/workflows/openwiki-update.yml | 2 +- .gitignore | 2 + ARIEL_RULES.md | 4 +- AUDIT_REPORT_20260629.md | 6 +- CHANGELOG.md | 14 +- .../20261004_2200_g27_parked_status.py | 2 +- autohooks/examples/dsh.yaml | 6 +- core/session.py | 2 +- .../plans/2026-09-05-phase-f-pipeline.md | 507 -------- .../compose/plans/2026-09-05-phase-g-graph.md | 176 --- .../plans/2026-09-05-phase-h-closeout.md | 156 --- .../plans/2026-09-05-phase-h-eval-infra.md | 113 -- .../specs/2026-09-04-phase-fgh-design.md | 373 ------ .../specs/2026-09-04-phase-fgh-draft.md | 1089 ----------------- features/importance.py | 4 +- features/inject.py | 2 +- features/recall.py | 2 +- lifecycle/graph_miners.py | 4 +- mcp_server/utils/privacy.py | 2 +- pyproject.toml | 30 +- scripts/purge_foreign_rows.py | 2 +- tests/shared/test_dialogue_length.py | 2 +- tests/test_core/test_session_message_count.py | 2 +- .../test_core/test_session_summary_filter.py | 2 +- tests/test_external_hooks.py | 2 +- tests/test_features/test_backup_cron.py | 2 +- .../test_features/test_backup_tmp_cleanup.py | 4 +- .../test_consolidation_hygiene.py | 12 +- .../test_continuity_substantive.py | 2 +- tests/test_features/test_recall_episodes.py | 2 +- tests/test_hooks/test_muse_state_hooks.py | 2 +- tests/test_importance.py | 6 +- tests/test_kind_tagger_gate.py | 16 +- tests/test_lifecycle/test_dialogic_guard.py | 2 +- tests/test_lifecycle/test_gate_context.py | 2 +- tests/test_lifecycle/test_graph_enrich.py | 2 +- .../test_l0_writers_close_rows.py | 2 +- tests/test_scripts/test_wiki_fts_check.py | 4 +- tests/test_user_handlers.py | 2 +- 39 files changed, 93 insertions(+), 2473 deletions(-) delete mode 100644 docs/compose/plans/2026-09-05-phase-f-pipeline.md delete mode 100644 docs/compose/plans/2026-09-05-phase-g-graph.md delete mode 100644 docs/compose/plans/2026-09-05-phase-h-closeout.md delete mode 100644 docs/compose/plans/2026-09-05-phase-h-eval-infra.md delete mode 100644 docs/compose/specs/2026-09-04-phase-fgh-design.md delete mode 100644 docs/compose/specs/2026-09-04-phase-fgh-draft.md diff --git a/.github/workflows/openwiki-update.yml b/.github/workflows/openwiki-update.yml index 47457410..4a2f15ce 100644 --- a/.github/workflows/openwiki-update.yml +++ b/.github/workflows/openwiki-update.yml @@ -44,7 +44,7 @@ jobs: run: openwiki code --update --print env: # Our own cheap gateway (OpenAI-compatible). No paid OpenAI key needed. - # Secret PLUSVIBE_API_KEY must be set in repo settings (Elli's hands). + # Secret PLUSVIBE_API_KEY must be set in repo settings (operator's hands). OPENWIKI_PROVIDER: openai-compatible OPENAI_COMPATIBLE_BASE_URL: https://plusvibeapi.ru/v1 OPENAI_COMPATIBLE_API_KEY: ${{ secrets.PLUSVIBE_API_KEY }} diff --git a/.gitignore b/.gitignore index ab9483b9..a0768e4b 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,8 @@ backups/ exports/ archives/ *.log +test_output.txt +full_test_output.txt .env .venv/ venv/ diff --git a/ARIEL_RULES.md b/ARIEL_RULES.md index a881b7e0..a6606536 100644 --- a/ARIEL_RULES.md +++ b/ARIEL_RULES.md @@ -16,7 +16,7 @@ ## [R3] Communication & Documentation 1. **English Standard**: All code, comments, commit messages, and documentation must be in **English**. -2. **Lucy-Style Docstrings**: Docstrings must explain **WHY** a component exists and its role in the orchestration, not just "what" it does. +2. **Why-First Docstrings**: Docstrings must explain **WHY** a component exists and its role in the orchestration, not just "what" it does. 3. **User/Agent Centricity**: The project must be easy to install and a pleasure to use for other agents. API signatures should be intuitive and strictly typed. ## [R4] Primitives & Cognition @@ -25,4 +25,4 @@ --- *Forged in the fire of Phase 3 and v1.6.4 Awakening.* -*Orchestrated by Lucy-Prime.* +*Maintained by the project authors.* diff --git a/AUDIT_REPORT_20260629.md b/AUDIT_REPORT_20260629.md index 5fe95b26..022bb965 100644 --- a/AUDIT_REPORT_20260629.md +++ b/AUDIT_REPORT_20260629.md @@ -1,6 +1,6 @@ # Аудит mcp-ariel-memory -**Дата:** 2026-06-29 -**Тип:** Полный аудит кода (18/92 файлов) +**Дата:** 2026-06-29 +**Тип:** Полный аудит кода (18/92 файлов) **Метод:** Делегированный обход (subagent, 20 мин, 6 API-вызовов) --- @@ -111,4 +111,4 @@ --- -*Сгенерировано Люси-Прайм из отчёта делегированного аудита. P0 баг требует правки в одной строке: `if hasattr(hooks, '_importance_gate'): gate = hooks._importance_gate(...)` в `tools_layer.py:129`.* \ No newline at end of file +*Сгенерировано из отчёта делегированного аудита. P0 баг требует правки в одной строке: `if hasattr(hooks, '_importance_gate'): gate = hooks._importance_gate(...)` в `tools_layer.py:129`.* diff --git a/CHANGELOG.md b/CHANGELOG.md index b3a15e2e..cbb731bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,10 +5,16 @@ Format follows [Keep a Changelog](https://keepachangelog.com/). ## [Unreleased] +### Security +- **Incidental operator identifiers are gone from the published tree (2026-10-08).** A second pass over the same class the fixture scrub started: names that had leaked into prose rather than into data. Removed from `CHANGELOG.md` (two quoted utterances and the `agent_id` list), `AUDIT_REPORT_20260629.md` (the generator's name), `ARIEL_RULES.md`, `autohooks/examples/dsh.yaml`, one Alembic revision comment, `scripts/purge_foreign_rows.py`, and eleven test files where a name stood in for a role. `pyproject.toml`'s "handled by Lucy" comment became "handled by hand". The names used *as the anonymizer's dictionary* stay — `config.yaml` `rag.ru_personas`, `rag/synonyms.py`, `mcp_server/utils/privacy.py`, `tests/test_hooks/test_privacy_ru.py` and the `graph_miners` canon test: those are the privacy product, and deleting them would remove the feature that hides names. Six `docs/compose/` files that were force-added past `.gitignore` are untracked again; they remain on disk, and three tests that cite them in docstrings were never reading them. + +### Changed +- **The wheel now ships the package instead of the repository (2026-10-08).** `[tool.hatch.build.targets.wheel]` was `packages = ["."]`, which made the repo root the package root: the 1.9.0 wheel carried `tests/` (115 files), `docs/`, `.github/`, `uv.lock`, the `Dockerfile` and the raw pytest logs — **~445 files against 276 now**. `only-include` names the runtime trees explicitly. The obvious risk was dropping something the code reads at import, and it was real: `config.py` loads `config.yaml` from its own directory, so the first attempt installed cleanly and then failed on a missing default config. Caught by installing into a fresh venv rather than trusting the in-repo test run, where the file is always present; `config.yaml` is now included and the install loads all 23 top-level keys. `test_output.txt` / `full_test_output.txt` joined `.gitignore` — they were never tracked yet still shipped, because hatch walked the filesystem. + ### Fixed -- **One persona's base was collecting five other personas (2026-10-04).** Found preparing her history import: the source database is **multi-agent** — `agent_id` takes `''`, `celeste`, `ilian`, `vivienne`, `agatha`, `dogs` — and the agent config filtered only on `role`, so the daemon tailed every agent's chat into one persona's memory. The `role` column is the owner/persona axis *within* an agent; it does not name the agent, which is precisely the mistake. Measured: **168** foreign rows in her `l0_journal`, **53** in her dispatch log, **19** mentions of other personas' names in `core_memory` and `episodes`. +- **One persona's base was collecting five other personas (2026-10-04).** Found preparing her history import: the source database is **multi-agent** — `agent_id` is `''` for the default agent and names every other agent sharing the base — and the agent config filtered only on `role`, so the daemon tailed every agent's chat into one persona's memory. The `role` column is the owner/persona axis *within* an agent; it does not name the agent, which is precisely the mistake. Measured: **168** foreign rows in her `l0_journal`, **53** in her dispatch log, **19** mentions of other personas' names in `core_memory` and `episodes`. - A second wave arrived through `merged_agent:*` (the source's `_migration_meta` records five bases merged in on 20–22.09): **53** journal rows whose `source_msg_id` no longer resolves at all — `SELECT ... WHERE id = ` finds no row — so an agent filter alone would still have let them through. They are unambiguous by content (`'Я Лили, а не Госпожа.'`, `'Привет, Вивьен'`, `'Привет, мальчики'`) and **69 of the 221 had already been distilled** (`saved_l3` 62, `promoted_l4` 7), one to L4: a persona's canon was holding another persona's speech. + A second wave arrived through `merged_agent:*` (the source's `_migration_meta` records five bases merged in on 20–22.09): **53** journal rows whose `source_msg_id` no longer resolves at all — `SELECT ... WHERE id = ` finds no row — so an agent filter alone would still have let them through. They are unambiguous by content (address forms and greetings naming a persona *other* than the one whose base collected them) and **69 of the 221 had already been distilled** (`saved_l3` 62, `promoted_l4` 7), one to L4: a persona's canon was holding another persona's speech. Two corrections to what this entry first said, both in the direction of understating the damage. It claimed "9 distilled"; that counted only the 53-orphan list and not the 221, and the real figure is 69 — which produced **238** episodes and **9** L4 facts. It also explained the second wave as arriving with an **empty** `agent_id` because the rows predated the column. Measured: `extras` carries its own `agent_id`, and rows where the column is empty while `extras` names a **foreign** agent number **zero**. Both spellings of the default agent (empty column, `extras.agent_id='default'`) agree with each other. The 53 rows are explained instead by id **re-issue during the merge**, which is what leaves `source_msg_id` dangling. @@ -266,7 +272,7 @@ Format follows [Keep a Changelog](https://keepachangelog.com/). - **Engineering Excellence** — Achieved 100% Mypy strict compliance and zero Ruff linting issues repo-wide. - **Systemic Typing** — Resolved external library typing for `sentence_transformers` via `pyproject.toml` configuration, removing non-compliant inline ignores. - **Forget API Evolution** — `memory_forget` and the `forget` primitive now return detailed `ForgetResult` objects (deleted_l4, deleted_l3, deleted_graph) for granular traceability. -- **Docstring Standards** — All core modules updated with Lucy-style docstrings explaining **WHY** components exist. +- **Docstring Standards** — All core modules updated with why-first docstrings explaining **WHY** components exist. ### Fixed - **Null Safety in RAG** — Added connection manager guards in `MultiSourceRAG` to prevent crashes in environments with partial storage availability. @@ -285,7 +291,7 @@ Format follows [Keep a Changelog](https://keepachangelog.com/). - **Global Strictness** — Successfully enabled global Mypy strictness and removed 10+ critical Ruff ignore rules, including `S110`, `ASYNC240`, and `UP031`. ### Changed -- **Supreme Orchestrator Identity** — Permanently integrated Lucy-Prime persona into the system backbone via lifecycle hooks. +- **Supreme Orchestrator Identity** — Permanently integrated the orchestrator persona into the system backbone via lifecycle hooks. - **Registry & Tools Cleanup** — Removed dozens of unused imports and obsolete classes leftover from the modularization phase. - **Modular Hardening** — Core infrastructure is now 100% type-safe and compliant with modern Python 3.10+ standards. - **Background Cleanup** — Activated `MemoryCompactor` in the server lifecycle. Old, low-importance memories are now automatically archived every 1 hour. diff --git a/alembic/versions/20261004_2200_g27_parked_status.py b/alembic/versions/20261004_2200_g27_parked_status.py index 80711e23..ca20a996 100644 --- a/alembic/versions/20261004_2200_g27_parked_status.py +++ b/alembic/versions/20261004_2200_g27_parked_status.py @@ -15,7 +15,7 @@ `parked` (их владелица просила сохранить, они не застряли), а `received` получает срок хранения `l0.received_ttl_days` в ночном проходе. -Проверено перед миграцией: на живой базе Эли все 3031 строки `received` имеют +Проверено перед миграцией: на живой базе все 3031 строки `received` имеют `raw_type='import'` и пустой `decisions`, то есть застрявших строк с этим статусом не осталось — переводить в `parked` нечего, кроме импорта. """ diff --git a/autohooks/examples/dsh.yaml b/autohooks/examples/dsh.yaml index 50637f65..021357f3 100644 --- a/autohooks/examples/dsh.yaml +++ b/autohooks/examples/dsh.yaml @@ -13,9 +13,9 @@ # # ONE CONFIG PER PERSONA, not one per platform. The two load-bearing lines are # `data_dir` (whose memory this is) and `user_id` (which ariel user inside it), -# so a house of six personas holds six files: elli.yaml, agatha.yaml, and so on. -# `elli.yaml` is this file with `data_dir` pointed at her inherited CowAgent -# directory — the accumulated memory is hers, not a fresh instance. +# so a multi-persona house holds one file per persona, named for her. This one +# is that file with `data_dir` pointed at her inherited CowAgent directory — +# the accumulated memory is hers, not a fresh instance. # # The DSH-side wrapper passes this file with `--config`. diff --git a/core/session.py b/core/session.py index 881e3e0b..371d895c 100644 --- a/core/session.py +++ b/core/session.py @@ -44,7 +44,7 @@ def is_service_session(session: Any) -> bool: Single source for the rule: service rows may surface (silence is worse), but must always be labeled so they never pose as "what I was doing" - (Elli 16.09 D2/D5). + (design review 16.09, D2/D5). """ return int(getattr(session, "message_count", 0) or 0) <= 0 diff --git a/docs/compose/plans/2026-09-05-phase-f-pipeline.md b/docs/compose/plans/2026-09-05-phase-f-pipeline.md deleted file mode 100644 index d0172752..00000000 --- a/docs/compose/plans/2026-09-05-phase-f-pipeline.md +++ /dev/null @@ -1,507 +0,0 @@ -# Phase F — L0 Pipeline Implementation Plan - -> **For agentic workers:** REQUIRED SUB-SKILL: Use compose:subagent (recommended) or compose:execute to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. - -**Goal:** Единый приёмник L0 (raw append-only журнал) с гейтами G0/G1/G2: privacy-плейсхолдеры + NER, kind-роутинг инвариант/событие, канонические ключи, конфликт-контракт, watermark + replay, session-close, TTL-тиры. - -**Architecture:** `l0_journal` (SQLite, append-only) — единственная дверь для 4 потоков записи. Каждый вход пишет в L0 best-effort, затем гейты G0→G1→G2 пишут вердикты в `decisions JSON`. Дистиллятор без LLM: атомизация → типизация → канонический ключ → роутинг (инварианты→L4, события→L3). Watermark курсор + replay переигрывают гейты по окну. - -**Tech Stack:** Python 3.11, SQLite/aiosqlite, alembic (head a26), pydantic, FTS5, existing: hooks/external.py `auto_save_text` (главный писатель), `shared/memory_types.py` (13 kinds, TypePolicy.decay_rate), `rag/conflict.py` (ConflictResolver), `shared/adaptive.py` (EMA), `features/rules.py`. - -## Global Constraints - -- 65 тулов (не добавлять новые в этой фазе; счётчики в tests/test_features/test_disclosure.py:106, tests/test_mcp/test_diagnostics.py:104, tests/test_mcp/test_recall_tool.py:9) -- alembic-цепочка линейная; новая миграция = ребёнок `20260903_1400_a26` -- Python 3.11, SQLite/aiosqlite, local-first (без облачных зависимостей) -- Conftest: `ARIEL_HASH_EMBEDDINGS=1` уже стоит; `hermetic_global_db` (session) редиректит connection_manager; в тестах патчить `.base_dir`, НЕ подменять singleton-объект (from-import ловит impostor навсегда) -- Ночной контекст: `deterministic_gate_and_registry` autouse фикстура существует -- Pre-push gate: ruff check . && ruff format --check . && mypy (10 dirs) && pytest --junitxml (верdict по junit) -- Все тесты: `ARIEL_HASH_EMBEDDINGS=1 uv run --with-editable . --extra dev pytest ...` - ---- - -### Task 1: l0_journal — миграция + запись (capture) - -**Covers:** S1 - -**Files:** -- Create: `alembic/versions/20260905_1000_f10_l0_journal.py` -- Create: `shared/l0.py` -- Test: `tests/test_shared/test_l0_journal.py` - -**Interfaces:** -- Produces: `shared/l0.py::async def capture(event: str, layer: str, user_id: str, text: str, *, source_msg_id: int | None = None, raw_type: str | None = None, decisions: list[dict] | None = None) -> int | None` (best-effort, никогда не бросает); `shared/l0.py::def classify_raw(text: str) -> str` (tool_result/tool_use/recall/evolution/user-message/plain). - -- [ ] **Step 1: Миграция** - -```python -"""F1 — l0_journal: raw append-only intake.""" -import contextlib -from alembic import op -revision: str = "20260905_1000_f10" -down_revision = "20260903_1400_a26" -branch_labels = None -depends_on = None - -def upgrade() -> None: - op.execute(""" - CREATE TABLE IF NOT EXISTS l0_journal ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - ts REAL NOT NULL, - event TEXT NOT NULL, - source_msg_id INTEGER, - layer TEXT NOT NULL DEFAULT 'user', - user_id TEXT NOT NULL DEFAULT 'default', - text TEXT NOT NULL, - raw_type TEXT NOT NULL DEFAULT 'plain', - status TEXT NOT NULL DEFAULT 'received', - decisions TEXT NOT NULL DEFAULT '[]', - processed_at REAL, - hash_prev TEXT NOT NULL DEFAULT '', - hash_self TEXT NOT NULL DEFAULT '' - ); - CREATE INDEX IF NOT EXISTS idx_l0_user_ts ON l0_journal(user_id, ts); - CREATE INDEX IF NOT EXISTS idx_l0_status ON l0_journal(status); - """) - -def downgrade() -> None: - with contextlib.suppress(Exception): - op.execute("DROP TABLE IF EXISTS l0_journal") -``` - -- [ ] **Step 2: Failing test** (`tests/test_shared/test_l0_journal.py`) - -```python -import pytest -from shared.connection import AsyncConnectionManager, connection_manager -from shared.migrations import MigrationManager - -@pytest.fixture -async def cm(tmp_path): - connection_manager.base_dir = tmp_path # НЕ подменять объект! - connection_manager._conns.clear() - await MigrationManager(cm=connection_manager).migrate() - yield connection_manager - connection_manager._conns.clear() - -@pytest.mark.asyncio -async def test_capture_writes_row_and_classifies(cm, tmp_path): - from shared.l0 import capture, classify_raw - assert classify_raw('[{"type": "tool_result", "tool_use_id": "t1", "content": "ok"}]') == "tool_result" - assert classify_raw('{"type": "tool_use", "name": "f", "input": {}}') == "tool_use" - assert classify_raw('[ariel recall]\n- [session] x') == "recall" - assert classify_raw('[EVOLUTION] sweep done') == "evolution" - assert classify_raw('обычное сообщение про проект') == "user-message" - rid = await capture("new_message", "user", "u1", "помни: я решил перейти на wal") - assert rid is not None - row = await (await (await cm.get("memory.db")).execute("SELECT raw_type, status FROM l0_journal WHERE id=?", (rid,))).fetchone() - assert row[0] == "user-message" and row[1] == "received" - -@pytest.mark.asyncio -async def test_capture_never_raises(cm): - from shared.l0 import capture - rid = await capture("new_message", "user", "u1", "x", raw_type=None) - assert rid is not None # даже с None raw_type — классифицирует сам -``` - -- [ ] **Step 3: Реализация `shared/l0.py`** - -```python -"""L0 raw intake — единственный вход конвейера (append-only, best-effort).""" -from __future__ import annotations -import json, time, hashlib -from typing import Any -from shared.connection import connection_manager -from shared.constants import DB_NAME - -async def capture(event: str, layer: str, user_id: str, text: str, *, - source_msg_id: int | None = None, raw_type: str | None = None, - decisions: list[dict] | None = None) -> int | None: - """Append-only intake. Никогда не бросает — сбой L0 не блокирует поток.""" - try: - conn = await connection_manager.get(DB_NAME) - cur = await conn.execute( - "INSERT INTO l0_journal (ts, event, source_msg_id, layer, user_id, text, raw_type, status, decisions)" - " VALUES (?, ?, ?, ?, ?, ?, ?, 'received', ?)", - (time.time(), event, source_msg_id, layer, user_id, text, - raw_type or classify_raw(text), json.dumps(decisions or [], ensure_ascii=False)), - ) - await conn.commit() - return int(cur.lastrowid or 0) - except Exception: - return None - -def classify_raw(text: str) -> str: - t = text.strip() - if t.startswith("[{") or t.startswith('{"'): - try: - obj = json.loads(t) if not t.startswith("[{") else json.loads(t) - if isinstance(obj, dict) and obj.get("type") == "tool_result": - return "tool_result" - if isinstance(obj, dict) and obj.get("type") == "tool_use": - return "tool_use" - except ValueError: - pass - return "tool_result" if "tool_use_id" in t[:200] else "plain" - for prefix in ("[ariel recall]", "[ariel memory]", "[ariel proposals]"): - if t.startswith(prefix): - return "recall" - if t.startswith("[EVOLUTION]"): - return "evolution" - if "tool_use_id" in t[:200]: - return "tool_result" - return "user-message" -``` - -- [ ] **Step 4: Коммит** `git add -A && git commit -m "feat(F1): l0_journal table + capture/classify intake"` - ---- - -### Task 2: G0 privacy gate — placeholders + NER (spaCy) - -**Covers:** S2 (G0) - -**Files:** -- Modify: `mcp_server/utils/privacy.py` (добавить `sanitize()` поверх strip_secrets) -- Modify: `hooks/external.py:auto_save_text` — capture + sanitize на входе -- Test: `tests/test_hooks/test_privacy_gate.py` - -**Interfaces:** -- Produces: `mcp_server/utils/privacy.py::def sanitize(text: str, *, use_ner: bool = True) -> tuple[str, dict[str, str]]` → (де-идентифицированный текст, reverse-map). Placeholders: `⟨EMAIL_1⟩`, `⟨PERSON_2⟩` — same value → same index. - -- [ ] **Step 1: Зависимость.** `uv add spacy && uv run python -m spacy download en_core_web_sm` (модель 12MB; лицензии — ядро MIT, модель CC BY-SA; атрибуция в NOTICE — отдельный шаг Task 8). - -- [ ] **Step 2: Failing test** (`tests/test_hooks/test_privacy_gate.py`) - -```python -from mcp_server.utils.privacy import sanitize - -def test_placeholder_stability(): - t = "Email ann@example.com и потом снова ann@example.com" - out, m = sanitize(t, use_ner=False) - assert "ann@example.com" not in out - assert out.count("⟨EMAIL_1⟩") == 2 # same value → same placeholder - assert m["⟨EMAIL_1⟩"] == "ann@example.com" - -def test_ner_masks_person_and_org(): - out, m = sanitize("Аня работает в Acme Corp", use_ner=True) - assert "Аня" not in out and "Acme Corp" not in out - -def test_prose_not_destroyed(): - out, m = sanitize("Кисонька впервые вышла на прогулку", use_ner=True) - assert "Кисонька" in out # не персона по словарю — сохраняется -``` - -- [ ] **Step 3: Реализация** — в `mcp_server/utils/privacy.py` добавить (lazy NER, process-wide): - -```python -_nlp = None -def _get_nlp(): - global _nlp - if _nlp is None: - import spacy - _nlp = spacy.load("en_core_web_sm") # ru-проза не парсится NER'ом — ок, regex-тир ловит structured - return _nlp - -_NER_LABELS = {"PERSON", "ORG", "GPE", "LOC"} - -def sanitize(text: str, *, use_ner: bool = True) -> tuple[str, dict[str, str]]: - """Replace secrets/PII with stable typed placeholders. Reverse map не персистится.""" - import re - out, mapping = text, {} - patterns = [(r"[\w.+-]+@[\w-]+\.[\w.]+", "EMAIL"), (r"\+?\d[\d\s()-]{8,}\d", "PHONE"), - (r"\b(?:\d{1,3}\.){3}\d{1,3}\b", "IP")] - def _sub(m): - val = m.group(0) - key = f"⟨{kind}_{sum(1 for k in mapping if k.startswith(f'⟨{kind}_'))+1}⟩" - mapping[key] = val - return key - for pattern, kind in patterns: - out = re.sub(pattern, lambda m: _sub_m(mapping, m.group(0), kind), out) - if use_ner: - try: - doc = _get_nlp()(out) - for ent in reversed(doc.ents): - if ent.label_ in _NER_LABELS and ent.text not in mapping.values(): - key = f"⟨{ent.label_}_{len(mapping)+1}⟩" - mapping[key] = ent.text - out = out[:ent.start_char] + key + out[ent.end_char:] - except Exception: - pass # NER недоступен — regex-тир уже отработал - return out, mapping - -def _sub_m(mapping: dict, val: str, kind: str) -> str: - for k, v in mapping.items(): - if v == val: - return k - key = f"⟨{kind}_{len(mapping)+1}⟩" - mapping[key] = val - return key -``` - -- [ ] **Step 4: Wire в `hooks/external.py::auto_save_text`** — после transcript-guard, ДО evaluate_importance: - -```python - # G0 privacy: secrets/PII → placeholders (reverse map не персистится) - from mcp_server.utils.privacy import sanitize - text, _priv_map = sanitize(text) -``` -(текст ниже по потоку уже де-идентифицирован; result получает `_priv_map` только для in-memory restore при необходимости). - -- [ ] **Step 5: Коммит** `git commit -m "feat(F-G0): privacy placeholders + spaCy NER tier"` - ---- - -### Task 3: G1 дистиллятор — атомизация + канонические ключи + kind-роутинг - -**Covers:** S2 (G1), S6a-принципы - -**Files:** -- Create: `lifecycle/distiller.py` -- Modify: `hooks/external.py::auto_save_text` — replace `mem.l3.save(...)`+`graph.add_node` блок на distiller-вызов -- Test: `tests/test_features/test_distiller.py` - -**Interfaces:** -- Produces: `lifecycle/distiller.py::@dataclass Atom(clause: str, kind: MemoryKind, importance: float, key: str)`; `async def distill_and_route(mem, graph, user_id: str, text: str, score: float, *, event: str) -> dict[str, Any]` (routes: L4 вvariants с conflict-check, L3 events; returns {"l4_saved": n, "l3_saved": n, "conflicts": n}). -- Consumes: `kind_for_text` (shared/memory_types), `adaptive_threshold.gate`, `apply_rules` (features/rules), `ConflictResolver.check` (rag/conflict). - -- [ ] **Step 1: Failing test** - -```python -import pytest -from shared.connection import AsyncConnectionManager -from shared.migrations import MigrationManager - -@pytest.fixture -async def cm(tmp_path, monkeypatch): - monkeypatch.setattr(connection_manager, "base_dir", tmp_path) # from shared.connection import connection_manager - connection_manager._conns.clear() - await MigrationManager(cm=connection_manager).migrate() - yield connection_manager - connection_manager._conns.clear() - -@pytest.mark.asyncio -async def test_invariant_routes_to_l4_event_to_l3(cm): - from lifecycle.distiller import distill_and_route - from core.memory import CoreMemory - l4 = CoreMemory(cm=cm, layer="user") - # инвариант: «решила» = decision → L4 - r1 = await distill_and_route(fake_mem, fake_graph, "u1", "я решила перейти на PostgreSQL", 0.8) - assert r1["l4_saved"] >= 1 - # событие: «наблюдаю» = observation → L3 - r2 = await distill_and_route(fake_mem, fake_graph, "u1", "наблюдение: трафик растёт по пятницам", 0.6) - assert r2["l3_saved"] >= 1 - rows = await (await (await cm.get("memory.db")).execute("SELECT key FROM core_memory WHERE user_id='u1'")).fetchall() - assert all(not k[0].startswith("staging_") for k in rows), "ключи канонические, не обрубки" - -@pytest.mark.asyncio -async def test_conflict_not_silent_update(cm): - r1 = await distill_and_route(fake_mem, fake_graph, "u1", "база проекта: PostgreSQL", 0.8) - r2 = await distill_and_route(fake_mem, fake_graph, "u1", "база проекта: MySQL", 0.8) - assert r2["conflicts"] >= 1 # второе противоречит первому — запись, не затирание -``` - -- [ ] **Step 2: Реализация `lifecycle/distiller.py`** (ядро): - -```python -"""G1 distiller: atomize → type → canonical key → route (invariant→L4, event→L3).""" -from __future__ import annotations -import re -from dataclasses import dataclass -from shared.memory_types import MemoryKind, get_policy, kind_for_text - -_CLAUSE_SPLIT = re.compile(r"[,;]?\s+(?:и|но|причём|а|хотя)\s+|\.\s+") - -@dataclass -class Atom: - clause: str - kind: MemoryKind - importance: float - key: str - -def _canonical_key(clause: str, kind: MemoryKind) -> str: - from rag.synonyms import load_synonyms - words = re.findall(r"[а-яёa-z0-9]+", clause.lower()) - canon = [w for w in words if len(w) > 2][:4] - return f"{kind.value}:" + "_".join(canon) if canon else f"{kind.value}:misc" - -def atomize(text: str) -> list[str]: - parts = _CLAUSE_SPLIT.split(text.strip()) - return [p.strip() for p in parts if len(p.strip()) >= 8][:10] - -def route_kind(kind: MemoryKind) -> str: - """Invariant→l4, event→l3 — по TypePolicy.decay_rate (0=никогда не умирает).""" - return "l4" if get_policy(kind).decay_rate <= 0.005 else "l3" - -async def distill_and_route(mem, graph, user_id: str, text: str, score: float, *, event: str = "new_message") -> dict: - from core.memory import CoreMemory - from rag.conflict import ConflictResolver - cmem = CoreMemory(cm=mem._cm if hasattr(mem, "_cm") else None, layer="user") if mem is None else mem - stats = {"l4_saved": 0, "l3_saved": 0, "conflicts": 0} - resolver = ConflictResolver() - for clause in atomize(text): - kind = kind_for_text(clause) - key = _canonical_key(clause, kind) - target = route_kind(kind) - conflict = await resolver.check(user_id, clause) - if target == "l4": - if conflict and getattr(conflict, "has_conflict", False): - stats["conflicts"] += 1 - await cmem.save(user_id, key, clause, importance=score, - memory_kind=kind.value, source=f"{event}:contradiction", - metadata={"contradiction": True}) - continue - await cmem.save(user_id, key, clause, importance=score, memory_kind=kind.value, source=event) - stats["l4_saved"] += 1 - else: - await mem.l3.save(user_id, clause[:500], score, [event, kind.value]) - stats["l3_saved"] += 1 - return stats -``` - -- [ ] **Step 3: Wire в `auto_save_text`** — заменить блок `await mem.l3.save(...)` + `graph.add_node(...)` (hooks/external.py:158-161) на: - -```python - from lifecycle.distiller import distill_and_route - route_stats = await distill_and_route(mem, graph, user_id, text, score) - result["saved_l3"] = route_stats["l3_saved"] > 0 - result["saved_graph"] = route_stats["l3_saved"] > 0 # граф наполняют минеры, не прямая запись - result["routes"] = route_stats -``` -(remove `await mem.l3.save(...)` и `await graph.add_node(...)` — remember тоже перестаёт дублировать: отдельный шаг Task 7.) - -- [ ] **Step 4: Коммит** `git commit -m "feat(F-G1): distiller — atomize, canonical keys, kind-routing, conflicts"` - ---- - -### Task 4: Compact-to-budget + priority signal - -**Covers:** S2 (compact-to-budget, priority) - -**Files:** -- Modify: `shared/memory_types.py:41` — TypePolicy add `retrieval_priority: float = 0.5` (per-kind: fact/decision 0.9, preference 0.6, context 0.1...) -- Create: `lifecycle/compact.py` — `async def compact_under_budget(user_id: str, layer: str, budget: int = 500) -> dict` -- Test: `tests/test_lifecycle/test_compact.py` - -- [ ] **Step 1: TypePolicy priority** — добавить поле `retrieval_priority: float = 0.5`, задать per-kind в `_POLICIES` (fact 0.9, decision 0.9, commitment 0.8, context 0.1, observation 0.3...). -- [ ] **Step 2: compact_under_budget**: SELECT importance + ACT-R activation → если объём > budget: эвикт lowest activation до <= budget; **никогда не эвиктит never_archive**. Возврат {"evicted": n}. -- [ ] **Step 3: Ночная привязка** — вызов из backup_cron._nightly после graph_build (1 строка). -- [ ] **Step 4: Тест**: 600 фактов → compact_under_budget(500) → 500 осталось, never_archive не тронут. -- [ ] **Step 5: Коммит** `feat(F): compact-to-budget + retrieval priority signal` - ---- - -### Task 5: TTL-параметр + B5 защиты свипа - -**Covers:** S6 (B4/B5) - -**Files:** -- Modify: `mcp_server/tools/memory.py::memory_remember` — параметр `ttl_minutes: int = 0` (0=без TTL) → expires_at = now + ttl*60 -- Create: `lifecycle/l0_sweep.py` — `async def sweep_expired(*, min_remain: int = 50, stop_pct: float = 0.8) -> dict` (delete expires_at < now; protections; cleaner_summary) -- Test: `tests/test_lifecycle/test_ttl_sweep.py` - -- [ ] **Step 1: Тест**: remember с ttl_minutes=1 → expires_at заполнен; sweep удаляет expired, но не трогает fresh (min_remain) и останавливается если expired >80% (anti-mass-delete); cleaner_summary возвращён. -- [ ] **Step 2: Реализация** sweep: `DELETE WHERE expires_at < now` с проверками `COUNT(*) remaining >= min_remain` и `expired_pct < 0.8`; summary → l0_journal. -- [ ] **Step 3: Коммит** `feat(F): ttl param + protected expiry sweep` - ---- - -### Task 6: Watermark + Replay - -**Covers:** S4 - -**Files:** -- Create: `features/replay.py` — `async def replay(*, since_days: int = 7, gate: str = "g1") -> dict` -- Modify: `hooks/external.py` — после蒸馏: UPDATE l0_journal SET status='saved_l3', processed_at=? WHERE id=? -- Test: `tests/test_features/test_replay.py` - -- [ ] **Step 1: Тест**: capture 3 raw → replay(since_days=1) → processed_at заполнены, повторный replay — no-op (config-hash key); порог изменился → replay со status='gated_out' переобрабатывает. -- [ ] **Step 2: Реализация**: выборка `WHERE ts > cutoff AND (status='received' OR status='gated_out')`, прогон через distill_and_route с новыми порогами, config-hash (hash текущих порогов) в decisions. -- [ ] **Step 3: Коммит** `feat(F): replay — gate re-run over L0 window` - ---- - -### Task 7: Session-close + L2 enrichment + E6 lessons - -**Covers:** S2 (session-close, E6), S3 (L2 enrichment) - -**Files:** -- Modify: `hooks/user_hooks.py::_session_ended` — добавить extraction -- Create: `features/session_close.py` — `async def extract_and_stage(...)` (preferences/experience/lessons → staging) -- Test: `tests/test_features/test_session_close.py` - -- [ ] **Step 1: Тест**: session_ended с текстами сессии → staging получает preference/experience/anti_pattern записи (A5-regex: «предпочитаю», «оказалось что», «больше не делать»). -- [ ] **Step 2: L2 enrichment**: `features/l2_enrich.py::async def enrich_sessions(days=1)` — L0-записи окна биндятся к sessions по ts, summary пересобирается из фактических текстов (not just state_deltas). -- [ ] **Step 3: E6**: failures → `kind=error_pattern` записи с тегом `lesson` (anti_patterns producer). -- [ ] **Step 4: Коммит** `feat(F3): session-close extraction, L2 enrichment, E6 lessons` - ---- - -### Task 8: CLI-точки + NOTICE + финальный gate - -**Covers:** S4 (replay CLI entry), S12 (атрибуция) - -**Files:** -- Create: `scripts/l0_cli.py` — argparse: `replay --since N --gate g1`, `sweep`, `stats` (статусы L0) -- Modify: `NOTICE` — spaCy MIT + model CC BY-SA attribution -- Test: ручной smoke + существующий сьют - -- [ ] **Step 1: CLI** (thin wrapper над features/replay.py + l0 stats). -- [ ] **Step 2: NOTICE**: `spacy: MIT; en_core_web_sm: CC BY-SA 4.0 (Models by Explosion AI)` . -- [ ] **Step 3: Полный pre-push gate** (все 4 команды, verdict по junit). -- [ ] **Step 4: Коммит + push** `feat(F): cli entry points + NOTICE attribution`. - ---- - -### Task 9: Единый вход — убрать обходные пути в граф/L4 - -**Covers:** S6a-принцип 1 (один вход), S6a (карта входов) - -**Files:** -- Modify: `mcp_server/tools/memory.py::memory_remember` — убрать `graph.add_node` (только L4; граф наполняет дистиллятор/минеры) -- Modify: `hooks/agent_hooks.py` — error_occurred/decision_made/self_correction/personality_shift: вместо прямого `add_node` → `l0.capture` + валидированный маршрут (kind/confidence/provenance обязательны) -- Modify: `mcp_server/tools/graph.py::memory_graph_add` — валидация: node_type/weight/provenance обязательны (иначе ValueError) -- Test: `tests/test_mcp/test_single_entry.py` - -**Interfaces:** -- Consumes: `shared/l0.py::capture` (T1), `lifecycle/distiller.py::distill_and_route` (T3) -- Produces: инвариант «нет прямых add_node вне graph.py-валидатора и минеров» — проверяется тестом. - -- [ ] **Step 1: Failing test** - -```python -import pytest -from unittest.mock import MagicMock - -@pytest.mark.asyncio -async def test_remember_does_not_write_graph(app): - ctx = _make_ctx(app) - spy = MagicMock(wraps=app.user_graph.add_node) - app.user_graph.add_node = spy - await memory_remember(layer="user", user_id="se", key="k", value="value", ctx=ctx) - spy.assert_not_called() # L4-запись не дублирует контент в граф - -@pytest.mark.asyncio -async def test_graph_add_requires_provenance(app): - ctx = _make_ctx(app) - with pytest.raises(ValueError, match="provenance"): - await memory_graph_add(user_id="gp", content="x", node_type="fact", ctx=ctx) # без source -``` - -- [ ] **Step 2: Реализация**: memory_remember — удалить блок `graph.add_node` (hooks/post_remember сохраняют провенанс-ссылку); memory_graph_add — `if not source: raise ValueError("graph_add requires provenance (source)"); if confidence is None: raise ValueError("graph_add requires confidence")`; agent_hooks — hook-хендлеры вызывают `capture()` + distill-маршрут вместо add_node. - -- [ ] **Step 3: Греп-инвариант** — `rg 'add_node' --type py -g '!tests/*' -g '!*pycache*'` возвращает только: graph/epistemic.py (определение), lifecycle минеры, mcp_server/tools/graph.py (валидированный вход). Добавить в CI-скрипт или тест-ассерт. - -- [ ] **Step 4: Коммит** `feat(F-T9): single entry — no bypass writes to graph/L4`. - ---- - -## Self-Review checklist (заполняется при написании) - -- Spec coverage: S1→T1, S2(G0)→T2, S2(G1)→T3, S2(compact/priority)→T4, S6(B4/B5)→T5, S4→T6, S2(session-close/E6)+S3→T7, S4(CLI)/S12(NOTICE)→T8. S5 (wiki) — сознательно НЕ в этом плане: wiki-механики (rewrite-not-append, redirect-stubs) идут отдельным планом после F (зависимость от [[fact:]] планирования). S0/S6a/S6b — principles/классификатор внутри T1/T2. S7-S10 (граф) — Phase G plan. S11-S14 (eval/infra) — Phase H plan. S15 — волновой порядок соблюдён. -- Типы: Atom/kind_for_text/distill_and_route согласованы между T3-шагами; capture/classify_raw между T1-T2. -- Placeholder-скан: чисто. - -## Execution Handoff - -Plan сохранён: `docs/compose/plans/2026-09-05-phase-f-pipeline.md`. После аппрува design doc → выполнение (subagent per task — рекомендую: задачи независимы, TDD-циклы изолированы). diff --git a/docs/compose/plans/2026-09-05-phase-g-graph.md b/docs/compose/plans/2026-09-05-phase-g-graph.md deleted file mode 100644 index 0db5eef2..00000000 --- a/docs/compose/plans/2026-09-05-phase-g-graph.md +++ /dev/null @@ -1,176 +0,0 @@ -# Phase G — Self-Wiring Graph Implementation Plan - -> **For agentic workers:** REQUIRED SUB-SKILL: compose:subagent per task. Steps use checkbox syntax. -> **Prerequisite:** Phase F завершена (6a4f45f): L0-журнал жив, distill_and_route работает. - -**Goal:** Граф наполняется рёбрами детерминированно (8+2 минеров), без единого LLM-вызова, с тегированием источников, validity windows и трёхфазным ночным dream-циклом. - -**Architecture:** Минеры читают существующие данные (epi_tags, FTS5, сессии, provenance, embedding-векторы), пишут рёбра в epi_edges с тегом `heuristic:`. Инкрементальный режим при записи + ночной batch (graph_enrich в backup_cron). Dual-route retrieval: EDM/ITS primary + S2-exhaustive для enumerative. - -**Tech Stack:** SQLite recursive CTE, sentence-transformers (уже в venv), epi_nodes/epi_edges/epi_tags, louvain (A1.6), backup_cron._nightly. - -## Global Constraints - -- Граф = multi-hop reranker ONLY (HippoRAG2: graph-augmented проигрывает dense на factual — graph-expand OFF на single-hop, D-Mem gated escalation) -- Каждое ребро тегировано `heuristic:`; откат: DELETE WHERE edge_tags LIKE '%heuristic:%' -- Вес = доверие источнику (эвристика 0.3–0.6, ручные 0.8+); never_archive-факты и pinned не эвиктятся -- MOC-хубы/auto-indexes исключены из centrality (Ar9av: 44→83%) -- 65 тулов не растёт (минеры — внутренние функции, не тулы) - ---- - -### Task 1: Фундамент — graph_enrich оркестратор + пре-чистка - -**Covers:** S7 - -**Files:** -- Create: `lifecycle/graph_enrich.py` — оркестратор: `async def graph_enrich(layer: str = "user") -> dict` — вызывает минеров по порядку, возвращает статистику -- Modify: `hooks/user_hooks.py::_nightly` — добавить фазу `graph_enrich` после graph_build -- Modify: `lifecycle/compact.py` — одноразовая чистка: JSON-узлы (raw_type='tool_result'/'recall') из epi_nodes → l0_journal (восстановление из бэкапов опционально, вручную) -- Test: `tests/test_lifecycle/test_graph_enrich.py` - -- [ ] **Step 1: Тест**: 236-узловая фикстура с замусоренными JSON-узлами → graph_enrich → JSON-узлы в L0, чистые узлы остались, рёбра от минеров появились. -- [ ] **Step 2: Реализация**: пре-чистка (JSON-узлы → capture() → delete_nodes) + скелет оркестратора (вызовы минеров — заглушки, заполнятся Tasks 2-5) + hook-проводка. -- [ ] **Step 3: Коммит** `feat(G1): graph_enrich orchestrator + node cleanup`. - ---- - -### Task 2: Минеры #1/#2/#4 (существующие данные) - -**Covers:** S8 (минеры 1,2,4) - -**Files:** -- Create: `lifecycle/graph_miners.py` — минер-функции -- Test: `tests/test_lifecycle/test_graph_miners.py` - -**Interfaces:** -- Produces: `async def miner_tags(cm, layer: str) -> dict`, `async def miner_tokens(cm, layer: str) -> dict`, `async def miner_sessions(cm, layer: str) -> dict` — каждая возвращает {'edges': n}. - -- [ ] **Step 1: Тест**: 2 узла с общим тегом → ребро tagged; 2 узла с ≥2 общими редкими токенами → topic_overlap (Jaccard); 2 факта одной сессии → same_session. Вес = Jaccard/tag-каунт. -- [ ] **Step 2: Реализация**: -```python -async def miner_tags(cm, layer: str) -> dict: - conn = await cm.get(DB_NAME) - rows = await (await conn.execute(""" - SELECT t1.node_id, t2.node_id, COUNT(DISTINCT t1.tag) as shared - FROM epi_tags t1 JOIN epi_tags t2 ON t1.tag = t2.tag AND t1.node_id < t2.node_id - JOIN epi_nodes n1 ON n1.node_id = t1.node_id AND n1.layer = ? - JOIN epi_nodes n2 ON n2.node_id = t2.node_id AND n2.layer = ? - GROUP BY t1.node_id, t2.node_id HAVING shared > 0 - """, (layer, layer))).fetchall() - edges = 0 - for a, b, w in rows: - await conn.execute( - "INSERT OR IGNORE INTO epi_edges (source_id, target_id, relation, weight, created_at, tags) " - "VALUES (?, ?, 'tagged', ?, ?, ?)", - (a, b, min(0.3 + 0.1 * w, 0.6), time.time(), json.dumps(['heuristic:tags']))) - edges += 1 - await conn.commit() - return {'edges': edges} -``` -(токены/сессии — аналогично по FTS5-match и session-binding из L2-enrich.) -- [ ] **Step 3: Коммит** `feat(G2): miners #1 tags, #2 token-overlap, #4 sessions`. - ---- - -### Task 3: Минер #5 провенанс-мосты + #7 co-retrieval журнал - -**Covers:** S8 (минеры 5, 7) - -**Files:** -- Modify: `features/recall.py` — журнал co-retrieval пар (после каждого recall: пары hit-ids → audit_trail или новая таблица) -- Create: `lifecycle/graph_miners.py::miner_provenance` (эпизод→wiki→факт через metadata.parents) -- Create: `lifecycle/graph_miners.py::miner_co_retrieval` (ночной: SELECT пар из журнала, COUNT ≥ 2 → ребро) -- Test: `tests/test_lifecycle/test_graph_miners.py` (добавить кейсы) - -- [ ] **Step 1: Журнал**: после каждого recall записывать пары (node_id_a, node_id_b) hits в recall_co_pairs таблицу (или audit_trail JSON) — MINIMAL: одна колонка в существующем recall_events. -- [ ] **Step 2: Минер #5**: JOIN core_memory.metadata.parents → episodes → wiki (метадата wiki_id) → рёбра sourced_from. -- [ ] **Step 3: Минер #7**: SELECT pairs, HAVING count >= 2 → co_recalled с весом частота/окно. -- [ ] **Step 4: Коммит** `feat(G3): provenance bridges + co-retrieval miner`. - ---- - -### Task 4: Минер #9 эмбеддинг + #3 сущности (spaCy) + инкрементальный режим - -**Covers:** S8 (минеры 9, 3, инкрементальный режим) - -**Files:** -- Modify: `lifecycle/graph_miners.py` — `miner_embedding` (Jaccard MIB ≥0.7, top-k=15), `miner_entities` (spaCy doc.ents → co_mentions) -- Modify: `lifecycle/distiller.py` — после роутинга лёгкие проверки (tags/entities/synonyms) — инкрементальный вызов минеров -- Test: расширить test_graph_miners.py - -- [x] **Step 1: Тест**: 2 узла с embedding-сходством ≥0.7 → semantic_overlap; текст с «Лили» в двух узлах → co_mentions после канонизации. -- [x] **Step 2: Реализация**: embedding-минер — кодировать content+tags+aliases (A-MEM rich embedding), попарный Jaccard (O(n²) на 236 узлах — копейки), top-k=15; entity-минер — словарь (Лили/Люси/Hermes/SQLite...) + spaCy NER для латиницы. -- [x] **Step 3: Коммит** `feat(G3): embedding + entity miners, incremental mode` (фактически `feat(G4)`, 8e6a7c3). - -### Task 4b: Минеры #6 маркеры led_to + #8 структурные инварианты - -**Covers:** S8 (минеры 6, 8 — план-чейнджер: пропущены в v1 плана, поймано ревью 04.09) - -**Files:** -- Modify: `lifecycle/graph_miners.py` — `miner_markers` (led_to), `miner_structural` (co-citation + louvain-расширение + belief propagation) -- Test: расширить test_graph_miners.py - -**Interfaces:** -- Consumes: минеры #1/#2 уже наплели рёбер (для #8 co-citation нужны существующие связи); louvain из A1.6 (wiki_communities). -- Produces: рёбра `led_to` (вес 0.3, тег `heuristic:marker`), `co_cited` (вес = число общих цитирующих), буст importance целевых узлов (belief propagation). - -- [x] **Step 1: Тест #6 (маркеры)**: узел A про X (ts=T), узел B про X с маркером «починила/теперь работает/сломалось/переделали» (ts=T+N, N в [5мин, 30д]) → ребро A→B `led_to` weight=0.3, tags=['heuristic:marker']; без временной близости → ребра нет. -- [x] **Step 2: Тест #8a (co-citation)**: узлы A и B, оба упомянуты в узле C → ребро A↔B `co_cited` weight=0.3; **Step 2b (belief propagation)**: confidence(A)=0.9, ребро A→B (weight 0.5) → UPDATE epi_nodes SET confidence = conf(B) + 0.1·conf(A)·w для B (одноразовый буст, не рекурсивный). -- [x] **Step 3: Тест #8b (louvain-расширение)**: сообщества уже посчитаны A1.6 → внутри сообщества пары узлов БЕЗ рёбер, но с общим тегом, получают слабое ребро weight=0.2 `heuristic:community_bridge`. -- [x] **Step 4: Реализация miner_markers**: SELECT пар узлов с общим токеном/сущностью, ts-дельта в [5мин, 30д], второй содержит маркер-словарь (починила|исправила|теперь работает|сломалось|переделали|решено|закрыто) → led_to. -- [x] **Step 5: Реализация miner_structural**: co-citation (SELECT пар из общих third-party рёбер) + louvain-мосты (SELECT пар внутри сообщества без прямого ребра с общим тегом) + belief propagation (UPDATE confidence по входящим высоко-конфидентным рёбрам). -- [x] **Step 6: Прогон GREEN + регресс test_graph_miners.py.** -- [x] **Step 7: Коммит** `feat(G3): miner #6 led_to markers, #8 structural invariants`. - ---- - -### Task 5: Санитария — inhibition, validity windows, MAD, valence, hub exclusion - -**Covers:** S9 - -**Files:** -- Create: `lifecycle/graph_sanitation.py` -- Test: `tests/test_lifecycle/test_graph_sanitation.py` - -- [x] **Step 1: Lateral inhibition** (SYNAPSE формула): û_i = max(0, u_i − β·Σ(u_k−u_i)·𝕀[u_k>u_i]), β=0.15, M=7 — поверх весов рёбер минеров. -- [x] **Step 2: Validity windows**: колонки valid_from/valid_to на epi_edges (alembic) + derived_from/coupled_with typed edges + O(|E|) recheck propagation (StateMem). -- [x] **Step 3: MAD-пороги**: τ = median − κ·MAD per-miner вместо fixed. -- [x] **Step 4: Valence-typed** (prism 10 типов) + volatility-классы (RoMem: predicate-type таблица). -- [x] **Step 5: Hub exclusion**: WHERE node_type NOT IN ('moc','auto_index') в centrality/louvain. -- [x] **Step 6: Коммит** `feat(G): graph sanitation — inhibition, validity, MAD, valence, hub exclusion`. - ---- - -### Task 6: Dual-route retrieval (EDM/ITS + S2 exhaustive + D-Mem escalation) - -**Covers:** S10 - -**Files:** -- Modify: `rag/search.py` / `rag/multi_source.py` — EDM re-rank (α·R+β·N+γ·G−δ·K), ITS threshold-gating (min-max, k≤100), lateral inhibition pre-step -- Create: `rag/dual_route.py` — question-type router (factual→RRF/EDM; enumerative→S2 exhaustive; multi-hop→D-Mem escalation) -- Test: `tests/test_rag/test_dual_route.py` - -- [ ] **Step 1: Тест**: factual-вопрос → RRF/EDM (graph-expand OFF); «list all X» → S2-exhaustive; precision-замер (Tenure-стиль: не должен вернуть чужой скоуп). -- [ ] **Step 2: EDM re-rank реализация**: α·R (FTS-rank) + β·N (set-операции: термы запроса минус покрытые) + γ·G (led_to-цепочки) − δ·K (semantic-dedup cosine) — нормализация per-query min-max. -- [ ] **Step 3: S2-exhaustive**: категория → агрегатная summary → полный спуск детей (без top-k). -- [ ] **Step 4: Коммит** `feat(G): dual-route retrieval — EDM/ITS, S2 exhaustive, escalation`. - ---- - -### Task 7: ENGRAM-абляция arm (для №11, но код здесь) - -**Covers:** S10 (ablation arm B) - -**Files:** -- Create: `rag/ablation.py` — конфиг-переключатели: `RETRIEVAL_MODE = 'rrf' | 'dense_per_kind' | 'gated'` -- Test: параметры читаются, arms переключаются - -- [x] **Step 1: Реализация**: env/config флаг → multi_source включает/выключает источники; dense-per-kind arm = search по kind_поля без фьюжена. -- [x] **Step 2: Коммит** `feat(G7): retrieval ablation arms — rrf/dense_per_kind/gated/full` (план предполагал `feat(H-prep)`; фактическое имя по ТЗ). - ---- - -## Self-Review -- S7→T1, S8→T2-T4, S9→T5, S10→T6-T7. S11-S14 → Phase H plan. S2-S6 → Phase F (выполнено). -- Deferred из F: per-kind caps + precedence rules инъекции → включить в Task 6 (dual-route). diff --git a/docs/compose/plans/2026-09-05-phase-h-closeout.md b/docs/compose/plans/2026-09-05-phase-h-closeout.md deleted file mode 100644 index c5509983..00000000 --- a/docs/compose/plans/2026-09-05-phase-h-closeout.md +++ /dev/null @@ -1,156 +0,0 @@ -# Phase H-Closeout — Zero-Deferred Implementation Plan - -> **For agentic workers:** REQUIRED SUB-SKILL: compose:subagent per task. Checkbox syntax. -> **Prerequisite:** Phase G+H завершены (головной 89a40c0). Аудит general-44: 3 разрыва + ~15 S-якорей + 10 заплаток. -> **Принцип (user 2026-09-05):** ZERO-DEFERRED, ZERO-PAYOFFS — чистый отлаженный проект, не костыли. - -**Goal:** Все пропущенные S-якоря реализованы, все заплатки устранены, prod-wire завершён. - -**Architecture:** Wire-фиксы (Task 1-2) подключают построенное к прод-пути. Минеры (Task 3-4) достраивают граф. Privacy/NLP (Task 5) усиливает гейт. Детерминизм (Task 6) + CLACK (Task 7) + eval-метрики (Task 8) закрывают дизайн. - -**Tech Stack:** существующий стек + networkx, sqlite-vec (опция), ru-NER словарь. - -## Global Constraints - -- Полный pre-push gate после каждого Task (ruff × 2, mypy 10 dirs, pytest junit) -- 65 тулов не растёт (все новые функции — внутренние или CLI) -- 1254 тестов — база; каждый Task не должен ломать -- Взвешенный Hamming и прочие квантовые скальпели — применить к rag/quantize.py -- NEVER break существующие тесты: если тест противоречит новому контракту — адаптировать тест (пометить) - ---- - -### Task C1: Wire dual-route в prod + enrich_sessions в cron + agent-layer - -**Covers:** S10 (route_query в prod), S3 (l2_enrich), S7 (agent-layer nightly) - -**Files:** -- Modify: `rag/multi_source.py::MultiSourceRAG.search` — если RETRIEVAL_MODE in (full/dense_per_kind/gated): route_query как пре-дверь, RRF = генератор кандидатов для EDM re-rank -- Modify: `features/backup_cron.py::_fire_nightly_hooks` — добавить `enrich_sessions(days=1)` (после sweep) -- Modify: `hooks/agent_hooks.py` — добавить `nightly` хук (mirror user_hooks._nightly: graph_enrich + компакт + свип) -- Test: `tests/test_rag/test_prod_wire.py` — RETRIEVAL_MODE='full' в prod-пути memory_search → EDM-порядок отличается от сырого RRF; enrich_sessions вызывается backup_cron - -- [ ] **Step 1: Failing test**: memory_search с включённым full-режимом → результаты отсортированы по EDM (не по raw RRF); enrich_sessions(mock L0) → sessions.summary обновлён. -- [ ] **Step 2: Реализация**: multi_source.search → `if get_mode() != 'legacy': route = await route_query(...)` + EDM re-rank результатов; backup_cron добавляет enrich_sessions; agent_hooks._nightly (первый в agent_hooks!). -- [ ] **Step 3: Коммит** `feat(HC1): wire dual-route to prod, l2_enrich to cron, agent-layer nightly`. - ---- - -### Task C2: import_chat ts + hash-chain writer + min_remain уже есть - -**Covers:** S1 (hash-chain), H3-fix (ts) - -**Files:** -- Modify: `scripts/import_chat.py` — ts из экспортов (claude: message.created_at; chatgpt: message.create_time; jsonl: ts поле; memory-json: ts) → capture(source_msg_id=None, decisions=[{'orig_ts': ts}]) → l0_journal.ts = orig_ts (не now) -- Modify: `shared/l0.py::capture` — параметр `ts_override: float | None = None`; hash-chain: hash_self = sha256(hash_prev + text + raw_type + str(ts)) -- Test: `tests/test_shared/test_l0_chain.py` — импорт с ts=1.0 → l0_journal.ts == 1.0; hash-цепочка последовательна (hash_self[i] = sha256(hash_self[i-1]+...)) - -- [ ] **Step 1: Тест**: import с orig_ts → l0.ts == orig_ts; последовательная запись → hash-chain verify: пересчитать chain, битых нет. -- [ ] **Step 2: Реализация**: capture(ts_override) — ts = orig_ts if ts_override else time.time(); hash-chain: SELECT hash_self FROM l0_journal ORDER BY id DESC LIMIT 1 → hash_prev; hash_self = sha256(prev+text). import_chat парсит ts → ts_override. -- [ ] **Step 3: Коммит** `feat(HC2): import preserves orig_ts; L0 hash-chain tamper-evidence`. - ---- - -### Task C3: Минеры #10 триплет-минер + behavior-аннотации (S6b) - -**Covers:** S6b (триплеты query→tool→outcome, behavior-аннотации) - -**Files:** -- Create: `lifecycle/graph_miners.py::miner_tool_triplets` — tool_use + tool_result пары (l0_journal, связка по tool_use_id) → узлы (query/action/outcome) → рёбра `query_tool`/`tool_outcome` -- Create: `lifecycle/tool_stats.py` — per-tool статистика (call count, error rate, avg outcome) → JSON/таблица для Stage 2 MCP hints -- Test: `tests/test_lifecycle/test_tool_triplets.py` - -- [ ] **Step 1: Тест**: L0 с tool_use(id=t1) + tool_result(tool_use_id=t1) → miner → узлы + рёбра; behavior-статистика: 3 вызова, 1 ошибка → error_rate 0.33. -- [ ] **Step 2: Реализация**: triplet-минер — SELECT l0_journal пары по tool_use_id (raw_type='tool_use'/'tool_result') → find_or_add узлов → рёбра. tool_stats: SELECT по event/raw_type → JSON-отчёт. -- [ ] **Step 3: Wire** в graph_enrich MINERS + behavior-stats в report. -- [ ] **Step 4: Коммит** `feat(G-miner10): tool triplets + behavior annotations data`. - ---- - -### Task C4: Privacy ru-тир — словарь персон + condition-splitting repair - -**Covers:** S2 (privacy: en-NER gap на русском), conflicts (condition-splitting) - -**Files:** -- Modify: `mcp_server/utils/privacy.py` — ru-словарный тир: известные персоны проекта (Лили/Люси/Аня/Эли/мамочка + синонимы из rag.synonyms) маскируются как ⟨PERSON_ru_N⟩; threshold-конфиг -- Modify: `lifecycle/distiller.py` — conflict → condition-splitting: обе записи сохраняются с scope-условием в metadata (не просто contradiction-флаг) -- Test: `tests/test_hooks/test_privacy_ru.py`, расширить test_distiller.py - -- [ ] **Step 1: Тест**: «Мамочка сказала перейти на PostgreSQL» → «Мамочка» замаскирован; «Лили красива» → «Лили» замаскирован (sentence-initial!); condition-splitting: конфликтующие факты обе сохранены с metadata.scope условиями. -- [ ] **Step 2: Реализация**: privacy: `RU_PERSONAS` словарь (load from config rag.ru_personas + синонимы) → regex word-boundary; distiller: при conflict → обе записи (не одна с contradiction), metadata.scope = «до X» / «после X» / source-context. -- [ ] **Step 3: Коммит** `feat(F-G0): ru persona tier + condition-splitting repair`. - ---- - -### Task C5: Eval-метрики расширение + memory_kind бэкфилл - -**Covers:** S11 (NDCG, drift, negative-controls, two-judge), S13 (estate-quality) - -**Files:** -- Modify: `eval/harness.py` — NDCG@k, drift score (old/new confusion), negative-control протокол (shuffled-edge должен падать), two-judge (proxy + второй fuzzy) -- Create: `scripts/backfill_memory_kind.py` — rag_chunks.memory_kind бэкфилл по kind_for_text -- Test: расширить test_eval - -- [ ] **Step 1: Тест**: NDCG на mini (perfect ranking = 1.0); drift: KU-пара — old-answer после update = fail; negative-control: shuffled-arms должны давать accuracy < real arms. -- [ ] **Step 2: Реализация**: NDCG формула (DCG/IDCG), drift = old-value в ответе на KU, negative-control — режим report.py с shuffled arms. -- [ ] **Step 3: Бэкфилл**: скрипт rag_chunks → memory_kind по kind_for_text(chunk.content) → UPDATE. Помечено в docstring для ingestor (future fill). -- [ ] **Step 4: Коммит** `feat(H-eval): NDCG/drift/negative-controls + memory_kind backfill`. - ---- - -### Task C6: S9-хвосты — FOK-gate, CAMA N_eff, трёхфазный dream, S2 compression constraint - -**Covers:** S9/S10 хвосты - -**Files:** -- Modify: `rag/edm.py` — FOK-gate τ=0.12 (reject до LLM), CAMA max-presence + N_eff (Hill diversity) в EDM-фьюжен -- Modify: `lifecycle/graph_enrich.py` — трёхфазный dream (NREM decay → REM bridge → Insight abstract) + S2 compression constraint -- Test: расширить test_graph_enrich.py, test_dual_route.py - -- [ ] **Step 1: Тест**: FOK-gate: activation топ-кандидата < 0.12 → reject до LLM; N_eff низкий → abstention-флаг; dream: NREM ослабляет неактивные, REM мостит изолированные, Insight создаёт абстракции; S2 constraint: категория с 1 ребёнком не проходит. -- [ ] **Step 2: Реализация**: FOK-gate в route_query (после ITS, до возврата); N_eff в EDM-фьюжен; dream-фазы в graph_enrich (последовательные проходы); S2 constraint в s2_exhaustive. -- [ ] **Step 3: Коммит** `feat(G): FOK-gate, CAMA N_eff, three-phase dream, S2 constraint`. - ---- - -### Task C7: S13-хвосты — cycles-daemon, cost-cap, gap-reader, Mermaid - -**Covers:** S13 - -**Files:** -- Create: `features/cycles.py` — cycles-конфиг (60s/1ч/3ч/24ч) + chimera cost-cap (per-cycle/rolling-60m/per-task) -- Modify: `features/diagnostics.py::audit_content` — gbrain gap-reader флаги (unknown/stale/uncited/contradicting) + create_safety verdict -- Create: `lifecycle/graph_mermaid.py` — render_mermaid(layer) → Mermaid-строка -- Test: `tests/test_features/test_cycles.py` + расширить audit/mermaid - -- [ ] **Step 1: Тест**: cycles конфиг читается; cost-cap блокирует превышение; gap-reader флагует; Mermaid содержит узлы/рёбра. -- [ ] **Step 2: Реализация**: cycles (dataclass + config.yaml retrieval-style), cost-cap (3 капа: per-cycle/rolling/per-task), gap-reader (продолжение audit_content), Mermaid (render nodes+edges в mermaid-syntax). -- [ ] **Step 3: Коммит** `feat(H3): cycles + cost-cap + gap-reader + Mermaid canvas`. - ---- - -### Task C8: S2-хвосты — novelty-gate, topic-классификация, segment-consolidation, pinned/private - -**Covers:** S2/S5/S16 хвосты - -**Files:** -- Modify: `lifecycle/distiller.py` — semantic novelty-gate (paraphrase-Jaccard > порог → skip как дубликат), topic-классификация (hall_keywords → wiki-тип/epi_tags) -- Modify: `features/inject.py` — pinned-флаг + private-флаг (visibility колонка) -- Create: `lifecycle/segment_consolidation.py` — Lychee boundary detection (Eq1-4) для L0-группировки -- Test: расширить test_distiller.py, test_inject.py, test_segment_consolidation.py - -- [ ] **Step 1: Тест**: paraphrase-дубликат → novelty-gate skip; topic → тег; pinned факт всегда в inject; private не в search; boundary detection режет по surprise. -- [ ] **Step 2: Реализация**: novelty-gate (Jaccard против существующих same-key, > 0.85 → skip), topic-классификация (словарь тем), pinned/private колонки (alembic g21), boundary detection (Lychee формулы). -- [ ] **Step 3: Коммит** `feat(F-final): novelty gate, topic classification, pinned/private, segment consolidation`. - ---- - -### Task 9 (финал): Полный gate + коммит + push - -**Covers:** все - -- [ ] **Step 1: Полный pre-push gate** (4 команды). -- [ ] **Step 2: Коммит + push** `feat(HC): zero-deferred closeout complete`. - -## Self-Review -- Все S-якоря и заплатки покрыты C1-C8. Порядок: C1-C2 (wire, быстрые) → C3-C5 (минеры/eval) → C6-C8 (хвосты) → финал. diff --git a/docs/compose/plans/2026-09-05-phase-h-eval-infra.md b/docs/compose/plans/2026-09-05-phase-h-eval-infra.md deleted file mode 100644 index 80f0dc84..00000000 --- a/docs/compose/plans/2026-09-05-phase-h-eval-infra.md +++ /dev/null @@ -1,113 +0,0 @@ -# Phase H — Eval & Infrastructure Implementation Plan - -> **For agentic workers:** REQUIRED SUB-SKILL: compose:subagent per task. Checkbox syntax. -> **Prerequisite:** Phase G завершена (17b32fc): dual-route retrieval, ablation arms, минеры с validity windows. - -**Goal:** Eval harness (№11) с ablation arms + memory_audit + import_chat + ariel-cli + A8-бридж + финальная зачистка. - -**Architecture:** Eval = отдельный модуль `eval/` с конфигами arms и датасет-адаптерами (LongMemEval-S, LoCoMo), отчёты в JSON+Markdown. memory_audit = расширение memory_diagnose. import_chat = парсеры → L0 → гейты (origin=import). ariel-cli = argparse тонкий поверх query DSL. - -**Tech Stack:** Python, existing ablation arms (rag/ablation.py), L0-журнал, memory_conflicts (ридер — наконец), LongMemEval-S dataset (HuggingFace moorcheh/memanto-evaluation тоже есть). - -## Global Constraints - -- 65 тулов (eval — модуль, не тулы; memory_audit = расширение существующего memory_diagnose) -- Full pre-push gate после каждого Task (ruff × 2, mypy 10 dirs, pytest junit) -- Приоритет: H1 (eval) → H2 (audit/import/cli) → H3 (бридж/остальное) - ---- - -### Task 1: Eval harness ядро (№11) - -**Covers:** S11 - -**Files:** -- Create: `eval/__init__.py`, `eval/harness.py`, `eval/datasets.py`, `eval/report.py` -- Create: `eval/configs/arms.json` (rrf/dense_per_kind/gated/full + компоненты: EDM on/off, inhibition on/off, graph-expand on/off) -- Test: `tests/test_eval/test_harness.py` - -**Interfaces:** -- Produces: `eval/harness.py::async def run_eval(dataset: str, arm: str, *, limit: int = 50, judge_model: str = "proxy") -> EvalReport` (dataclass: accuracy, recall@5, precision, noise_isolation, reacquisition_cost, construction_tokens, per_category breakdown); `eval/datasets.py::async def load_longmemeval_s(limit: int) -> list[EvalQuestion]`; `eval/report.py::def render_markdown(reports: list[EvalReport]) -> str`. - -- [ ] **Step 1: Failing test**: фикстура 10 вопросов с известными ответами (mini-dataset в tests) → run_eval(arm='full') → EvalReport с заполненными полями; arms различимы (rrf ≠ full при EDM). -- [ ] **Step 2: Реализация**: dataset-адаптер (LongMemEval-S с HF; если недоступен — mini-dataset); runner: вопрос → route_query по arm → ответ → сравнение (proxy-judge: exact/fuzzy match для smoke; LLM-judge интерфейс с заглушкой); метрики из S11 (accuracy, precision/noise, reacquisition: счёт вызовов retrieval после компакции, construction-tokens из L0). -- [ ] **Step 3: Ablation arms wiring**: ablation.py уже переключает — harness прогоняет каждый arm на том же датасете. -- [ ] **Step 4: Отчёт**: JSON + Markdown-таблица (arms × метрики) + честная плашка компонентов. -- [ ] **Step 5: Коммит** `feat(H1): eval harness — LongMemEval-S + arms + metrics`. - ---- - -### Task 2: memory_audit — Letta-doctor расширение - -**Covers:** S13 (memory_audit), v16-№3 (конфликт-ридер) - -**Files:** -- Modify: `features/diagnostics.py` — контент-чеки -- Test: `tests/test_features/test_memory_audit.py` - -- [ ] **Step 1: Тест**: фикстура с конфликтом (2 факта противоречащих) + дубликатом + stale (updated_at > N) + сиротой-линком → memory_audit возвращает эти проблемы в `content_checks`. -- [ ] **Step 2: Реализация**: memory_diagnose расширяется: противоречия (SELECT memory_conflicts unresolved), дубли (BM25 smart_similarity > порог по парам same-kind), stale (updated_at старше X + не pinned), битые [[fact:]]-линки wiki. Каждый: {severity, items, suggestion}. -- [ ] **Step 3: Коммит** `feat(H2): memory_audit — conflict reader + dup/stale checks`. - ---- - -### Task 3: import_chat - -**Covers:** S13 (import_chat = A6/E5) - -**Files:** -- Create: `scripts/import_chat.py` — argparse: `--source claude|chatgpt|memory-json|jsonl --file PATH --user U` -- Test: `tests/test_features/test_import_chat.py` - -- [ ] **Step 1: Тест**: фикстура Claude-export JSON → import → записи в l0_journal с raw_type='import', decisions=[{'gate':'import'}] → distill по гейтам. -- [ ] **Step 2: Реализация**: парсеры 4 форматов (claude-conversations.json, chatgpt.json, generic memory-json, jsonl) → нормализация (role, text, ts) → l0.capture(event='import') → батч-distill (watermark-совместимо). -- [ ] **Step 3: Коммит** `feat(H2): import_chat — 4 формата → L0 → gates`. - ---- - -### Task 4: ariel-cli - -**Covers:** S13 (ariel-cli, typed_export surface) - -**Files:** -- Create: `scripts/ariel_cli.py` — подкоманды: `ls` (wiki_list/core keys), `tree` (MOC/иерархия), `find QUERY` (query_dsl), `grep PATTERN` (content LIKE), `stats` -- Test: smoke через tmp-BD - -- [ ] **Step 1: Реализация**: тонкий argparse поверх memory_query + wiki_list; close_all() обязательно (aiosqlite-гоча). -- [ ] **Step 2: Коммит** `feat(H2): ariel-cli — ls/tree/find/grep over memory`. - ---- - -### Task 5: A8 MEMORY.md-бридж - -**Covers:** S13 (A8) - -**Files:** -- Create: `features/bridge.py` — `async def regenerate_bridge(user_id: str, layer: str = "agent") -> Path` (топ-факты → MEMORY.md, drain-marker секция) -- Modify: `features/backup_cron.py` — nightly вызов -- Test: `tests/test_features/test_bridge.py` - -- [ ] **Step 1: Тест**: 10 фактов разной importance → bridge → топ-5 в MEMORY.md, drain-marker присутствует; добавление текста после маркера → ingest в L0 (при следующем проходе). -- [ ] **Step 2: Реализация**: regenerate (MOC-стиль: importance-sorted, importance ≥ threshold, типы только инварианты) + ingest (текст ниже `# === AUTO-DRAIN BELOW ===` → l0.capture). -- [ ] **Step 3: Коммит** `feat(H2): MEMORY.md bridge — regenerate + drain ingest`. - ---- - -### Task 6: Финальная зачистка - -**Covers:** S16 (открытые вопросы) - -**Files:** -- Modify: `pyproject.toml` — убрать claim «envelope encryption» из description (или пометить как saga/auth-only) -- Modify: `README.md` — если упоминает -- Modify: `mcp_server/server.py` — /ready + alembic-head check (1 строка) - -- [ ] **Step 1: crypt-claim fix** (S16: убрать или пометить). -- [ ] **Step 2: /ready alembic check**. -- [ ] **Step 3: Полный pre-push gate + push** `feat(H3): final cleanup — crypt claim, ready check`. - ---- - -## Self-Review -- S11→T1, S13(memory_audit)→T2, S13(import)→T3, S13(cli)→T4, S13(A8)→T5, S16→T6. S12 (библиотеки) — spaCy уже встроен (T2-F), fractional-indexing остаётся на Stage 2 или в G-волне инъекций (deferred). S14 Stage 2 — отдельный план после H. -- Deferred (не теряются, помечены): per-kind caps реализованы в G-T6 ✓; PROV-O/snapshots — H3-опции при времени; LoCoMo event-summarization задача — в Task 1 как доп. категория если датасет доступен. diff --git a/docs/compose/specs/2026-09-04-phase-fgh-design.md b/docs/compose/specs/2026-09-04-phase-fgh-design.md deleted file mode 100644 index 4d045b2c..00000000 --- a/docs/compose/specs/2026-09-04-phase-fgh-design.md +++ /dev/null @@ -1,373 +0,0 @@ -# Phase F/G/H Design — a-memory L0 pipeline, self-wiring graph, eval & infra - -> **Design doc** (distilled from research draft `2026-09-04-phase-fgh-draft.md` v37, 1068 строк; -> доки Эли/Лили `a-memory-l0-l4-pipeline.md` + `a-memory-graph-miners.md`; 33 репо + 44 статьи/блога). -> Status: DRAFT-FOR-REVIEW → верификация делегатом → compose:plan. -> Sequencing (user, 2026-09-04): **F-фундамент → G-минеры → H → Stage 2 в конце**. Волны не пересекать. -> Phase A–D/E закрыты; Stage 2 разблокирован, но идёт последним. - -## [S0] Контекст и принятые решения - -- L0 = сырой append-only журнал (user decision); EDM/ITS формулы в публикациях отсутствуют — реализуем принцип (uncertainty-reduction scoring + threshold-gating) через детерминированный суррогат; sign-бинаризация ≈ fancy MIB (подтверждено 2601.11557 их же данными). -- ENGRAM-абляция (№11): RRF-5-source vs dense-per-kind vs gated-retrieval — решение абляцией, не вкусом. При ENGRAM-win меняется только генератор кандидатов recall-пути (include_* флаги + per-kind индексы); L0/гейты/сторы/граф-минеры не затрагиваются (Memora Thm D.1: RAG/KG/dense-per-kind = частные случаи одного framework). -- Privacy-гейт идёт в F-фундамент сразу (🔴 A1: L3 сейчас без фильтра); spaCy NER-тир — сразу после фундамента; атрибуция в NOTICE, PyPI-релиз = download-at-install. -- E6 negative memory — в Phase F (продюсер: lessons mining из session-close). -- Шифрование at-rest: envelope есть только для saga/auth; данные plaintext — claim в pyproject убрать (хвост H) или SQLCipher (решение отдельное, не блокер F/G/H). -- OpenViking peers/, MemoryPalace tunnels, cross-namespace edges — резервируются при Stage 2 URI-redesign, не строятся. - -## Phase F — Конвейер L0→L4 - -### [S1] L0 журнал (`l0_journal`) - -- Схема: `id, ts, event, source_msg_id, layer, user_id, text, raw_type, status, decisions JSON, processed_at, order_key` (+ hash-chain для tamper-evidence, shiroe-паттерн). -- raw_type классификатор (без LLM, первое совпадение): MCP type-field (tool_result/tool_use/text) → префиксы (`[ariel recall]`, `[EVOLUTION]`) → роль+tool_calls → fallback plain text. Запись в include: import (E6/A6). -- Статусы: received → skipped / gated_out / saved_l3 / staged / promoted_l4 / replayed. -- decisions JSON — след каждого гейта: [{gate, verdict, score, reason}]. -- Пишется первым, best-effort (сбой не блокирует поток); instant write-to-search (no-index, Memanto D6). - -### [S2] Гейты (каждый пишет вердикт в L0) - -- **G0 входной**: transcript-guard (`_looks_like_dump`, в проде) + SHA-256 dedup (5-мин окно, из memory_remember) + **privacy: strip→typed placeholders** (⟨EMAIL_1⟩, same value→same placeholder, reverse map не персистится, strict-mode refusal <0.5) + **spaCy NER-тир** (en_core_web_sm 12MB; PERSON/ORG/GPE/LOC; regex-only на прозе = 12-26% ликов) + per-repo capture allowlist/exclude. -- **G1 importance + дистиллятор**: атомизация на клаузы (союзы/точки, «и/но/причём») → типизация (kind_for_text + A5-regex: «запомни:», «я решил», «не делай») → importance (EMA + rules D1.9) → **канонический ключ** (семантический хедер через синоним-словари: «decision:граф_без_llm»; повтор → UPDATE updated_at + recency-boost, не дубль) → **семантический novelty-gate** (paraphrase-Jaccard; калибровка mean .274) → topic-классификация (hall_keywords-паттерн → wiki-тип/epi_tags предзаполнение). -- **Kind-роутинг (ядро F)**: инварианты (fact/decision/rule/instruction/commitment/goal/relationship; low decay_rate/never_archive) → L4; события (observation/question/context; высокий decay) → L3. Policy.decay_rate учитывается промоцией (сейчас игнорируется). -- **Конфликты**: rag/conflict.py на входе L4 → memory_conflicts + 3-опционный контракт агенту (supersede/retain/annotate), non-persistence до резолюции; **read-time fusion приоритетнее gate-time** (Mem0 ADD-only, LoCoMo +21pp); condition-splitting repair — опция для инвариантов (3M). -- **G2 promotion**: outcome-gated (autocontext: matched screening → confirmation → held-out eval → false-promotion budget → atomic activation) + консервативные пороги (γ=0.80; T17: 0.6 = 3.4× мержей zero gain) + transcript-guard + отложенный candidate живёт в staging. -- **Compact-to-budget size-гейт** (Letta): per-блок лимит символов/токенов; nightly consolidation **упаковывается под бюджет** с эвиктом низкого ACT-R (не только дедуп) — append-only без compaction-давления = недостающий гейт. -- **Procedural/agent-self как 3-й роут-kind** (ENGRAM + EverOS/Acontext + PlugMem): how-to/procedure записи агента — отдельный routed kind (ложится на wiki L4.5 + agent-self namespace), не смешивается с user-инвариантами. -- **Priority-сигнал в TypePolicy** (Memanto): decay_rate есть, добавить **retrieval-priority** per-тип (fact=stable, commitment=time-critical, goal=until-achieved, event=episodic, context=highly-temporal, learning=accumulating, error=guard) → type-filtered retrieval. -- **Segment-level consolidation** (Lychee Eq1-4): boundary detection embedding-only p_t = σ(...) (константы Table 10: δ=0.50, 300/600/900 tok, 10 exchanges), alias carry-forward buffer ρ_{k+1} = [d_k; Recent] (fixed budget, «resolve references only, never extract»); −86% construction tokens у источника. -- **Session-close фаза** (B9/EverMemOS): post_session_diff → preferences/experience extraction через A5-regex → staging. -- **E6 negative memory**: lessons mining (failures/do-not-retry из session-close) → anti_patterns записи; **первый продюсер** — из батч A. -- **Foresight validity**: forward-looking факты (планы, temporary states) с [t_start, t_end], read-time фильтр t_now (EverMemOS). - -### [S3] L2-обогащение и повторные операции - -- L2 sessions хранит summary без сообщений (проверено кодом) → ночной проход биндит L0-записи к сессиям по времени, пересобирает summary/state_deltas из фактического сырья (кормит минер #4 real-группировками). -- Compat: пометки на существующих wiki_index тестовых строках (Test/Count/g/h — user verdict отложен), memory_forget удалён (65 тулов), wiki_index дедуп сделан. - -### [S4] Replay и watermark - -- **Watermark**: processed_at/status в l0_journal — каждый raw дистиллируется ровно один раз; ночная выборка WHERE status='received'. Без watermark «дистилляция освобождает сырьё» не работает. -- **Replay**: `replay --since -7d --gate g1` — переигрывание гейтов по окну (порог/rules изменились → переиграть; идемпотентность = source_msg_id + gate + config-hash; cocoindex memo = hash(вход)+hash(КОД) — тот же принцип). -- **dispatch_log → view над L0** (не миграция, пере-вычисление). - -### [S5] Wiki = L4.5 knowledge layer - -- Намеренная запись (рефлексия/доки/дизайн), НЕ дистилляция из L0; **не retrieval unit** (VikingMem: OpenClaw markdown — худший baseline). -- **[[fact:ключ]] linking**: факт помнит wiki_id; bi-temporal metadata (learned_at + valid_at) на линках. -- **Ночные провенанс-мосты** wiki↔L4↔L0 (минер #5) — бесплатно дают рёбра графа. -- **Гидратация вниз**: recall со страницы → L4 → L0 («почему мы так решили» — до исходного сырья; = C3 drill-down). -- Wiki-самоорганизация (из v24/v26): rewrite-not-append ingest + auto-reconcile; redirect-stub при merge (никогда не удалять); OKM freshness linter на записи (timeless/dated/pointer); write-time link validation («note without links is a bug»); blast-radius guards; OKF/autograph schema-as-code (frontmatter contract + schema infer/validate/diff); Stale-Actives auto-views. -- MOC-хубы **исключить/занизить** из BFS/louvain (Ar9av: 44→83% correctness) — bookkeeping exclusion. -- **Memanto expiry policies** (v22 🔑): мягкое истечение вместо жёсткого удаления — expired остаются recallable с меткой `[EXPIRED]`, restorable, provenance именованного правила; retention-таблица per-тип (first-match-wins, pins). Уточняет S6-политику и deletion gate. - -### [S6a] Principles линии (6, из чата Эли — сборка по v37) - -1. **Один вход**: все 4 потока (MCP-тулы / авто-хуки / консолидация / внешние дампы) проходят через L0-приёмник — сортировочную станцию; remember перестаёт дублировать контент в граф (только провенанс-ссылка); graph_add/agent-хуки под контроль (важность + провенанс обязательны); think-роутинг (>2000 → wiki и т.д.) проходит через L0. -2. **Дистилляция off-hot-path**: горячий путь = L0 append + L1; всё остальное (дистилляция, минеры, консолидация) — ночные/фоновые проходы. -3. **Идемпотентность**: watermark — каждый raw обрабатывается ровно один раз. -4. **Провенанс**: каждая L3/L4 запись помнит source_raw_id → drill-down до исходного сырья. -5. **Фильтры L4**: только атомарные инварианты с ключом; сырой JSON автоматически не проходит (стена стоит сама). -6. **Двунаправленность**: bottom-up дистилляция + top-down гидратация (drill-down «почему мы так решили»). - -### [S6b] L0-классификатор сырья (5 шагов, из чата Эли) - -1. Структурный парс MCP-обёртки (`type`-поле): tool_result (+tool_use_id, content, isError) / tool_use (+name, input) / text. -2. Префиксные маркеры не-MCP дампов: `[ariel recall]`/`[ariel memory]`/`[ariel proposals]` → recall-дамп; `[EVOLUTION]` → evolution-дамп; `[ariel ...]` любой → память-дамп. -3. Роль + tool_calls: assistant с tool_calls → agent-action; role=user → user-message. -4. Fallback: нераспознанное → plain text в дистиллятор как есть. -5. Связка тулов: tool_use_id ↔ tool id = готовая пара ключей для триплета query→tool→outcome (без угадывания порядка в сессии). - -Таблица маршрутов: tool_use → L0+ждёт пару (триплет query→tool); tool_result → L0+связка (→outcome ребро); recall-дамп → L0+счётчик (co-retrieval статистика → `co_recalled`); user-message/plain → L0 → дистиллятор (атомы → L3/L4). Бонус tool_use: частотность/ошибки/паттерны → **behavior-аннотации тулов** (data-фундамент для Stage 2 MCP hints). - - -### [S6] L0-разрастание (политика) - -- Три тира: горячий 0–30д (полное) → тёплый 30–180д (A3-extractive + zlib) → холодный (>180д или дистилляция + N дней без recall → CLACK-архив, lossless LLM-readable; из L0 удаляется). -- Дедуп SHA-256 блока (повторный вывод = ссылка); дистилляция освобождает сырьё; source_raw_id переживает архивацию. -- B5-защиты на свипе: min-остаток, стоп при >80% expired, cleaner_summary в L0/аудит; warm-tier: жать жёстко или не жать (mild = net-negative, P5). -- Оценка: 2MB/день → ~16MB/год (сжатие ×15, дедуп ×3). - -## Phase G — Self-wiring граф - -### [S7] Фундамент (прежде минеров) - -- add_edge вызовы в правильных местах (builder'ы пишут, но минеров нет — наполнение нулевое); **журнал co-retrieval пар** — НОВЫЙ (расширить audit_trail: recall_useful + target_id), иначе минер #7 не считает. -- **Пре-чистка**: замусоренные JSON-узлы графа → в L0 (восстановить из бэкапов *.bak-pre-*), иначе минеры свяжут мусор с мусором. -- 3-тир L0-жизнь/CLACK cold — см. S6. - -### [S8] Минеры (детерминированные; ребро тегировано `heuristic:`) - -Порядок (план Эли/Лили, уточнён research): -1. **#1 Общие теги** → `tagged` (вес = число общих) — epi_tags есть. -2. **#2 Редкие токены + синонимы** → `topic_overlap` (Jaccard) — FTS5+synonyms есть. -3. **#4 Сессионная близость** → `same_session` — таймстампы есть. -4. **#5 Провенанс-мосты** → `sourced_from` (эпизод→wiki→факт) — source_id/wiki_id есть. -5. **#9 Эмбеддинг-минер** → `semantic_overlap` (Jaccard MIB ≥0.7, top-k, вес 0.5-0.6) — ST 6.0.0 в venv; **кодирует content+tags+aliases** (A-MEM); arctic-embed-xs кандидат; MIB-128B → RaBitQ 48B error-bounded апгрейд. -6. **#7 Co-retrieval** → `co_recalled` (вес = частота/N) — после журнала. *(Порядок #9↔#7 сознательно изменён против draft v14: эмбеддинг-минер стартует раньше, т.к. модель уже в venv — без новой инфраструктуры; co-retrieval ждёт новый журнал.)* -7. **#3 Сущности** → `co_mentions` + канонизация (словарь: Лили/Lily/лисёныш; spaCy NER) + **entity-matches как 6-й RRF-сигнал** (Mem0 retrieval-boosting). -8. **#6 Маркеры результата** → `led_to` heuristic («починила», «сломалось»). -9. **#8 Структурные инварианты** — co-citation, louvain-расширение, belief propagation. -10. **Триплет-минер** → `query→tool→outcome` рёбра (Эли/Лили поток «тулы»): tool_use_id ↔ tool id = готовая пара; частотность/ошибки/паттерны per-tool → **behavior-аннотации** (data-фундамент Stage 2 MCP hints). - -Инкрементальный режим (при записи) + ночной batch; **вес = доверие источнику** (эвристика 0.3–0.6, ручные 0.8+); откат: DELETE WHERE edge_tags LIKE '%heuristic:%'. - -### [S9] Санитария и policy-слой - -- **Lateral inhibition** (SYNAPSE): û_i = max(0, u_i − β·Σ(u_k−u_i)·𝕀[u_k>u_i]), β=0.15, M=7 — против correlated distractor-кластеров; без inhibition Open-domain падает 25.9→22.4. **FOK-gate τ=0.12 на activation топ-нодa** → детерминированный reject до LLM (FRR<2.5%). -- **Validity windows на рёбрах** (Graphiti/StateMem): derived_from/coupled_with typed edges + unit status (active/superseded/needs_recheck) + O(|E|) deterministic recheck propagation. -- **MAD-пороги** (Mandol): τ = median(S) − κ·MAD(S) вместо fixed cutoffs. -- **Volatility-классы** (RoMem): per-fact decay по семантике отношения (президент ротируется, рождение — нет); deterministic proxy = predicate-type таблица. -- **Valence-typed edges** (prism): 10 типов → result buckets (primary/supporting/contrasting/qualifying/superseded). -- **Hub exclusion**: MOC-хубы/авто-индексы вне centrality/BFS (Ar9av: иначе fools 44→83%). -- **Трёхфазный dream** в graph_enrich: NREM (spreading activation +0.05/−0.01, prune <0.05) → REM (мосты изолированных узлов, sim×0.3) → Insight (BFS-комьюнити → материализованные абстракции) + negative-control протокол (off-switch бенчи обязаны падать). -- **CAMA anti-false-majority**: max-presence e_j=max_i z_ij + N_eff (Hill diversity) в фьюжене — correlated evidence не накачивает mass; N_eff = abstention-сигнал. -- Cap на узел, min-weight, decay только heuristic-рёбер (ручные не трогаются). - -### [S10] Dual-route retrieval - -- **Primary (F-наследие)**: RRF k=60 5-source (BM25 demote до minority) → **EDM re-rank** α·R+β·N+γ·G−δ·K → **ITS threshold-gating** (min-max [0,1] per query, threshold ~0.05, k≤100) → детерминизм (same query → same results, MOSS 1 год продакшена валидирует; **флаг `deterministic_retrieval`** для регламентированных сценариев, default off — v27). -- **Counter-signal алиасы** (Tenure): superseded-имена живут как aliases с негативной ролью в ранжировании (ceiling 25/белиф) — не drop, а понижение. -- **S2 exhaustive route** (Mnemis): «list all X» — иерархический top-down scan (категория несёт агрегатную summary потомков); similarity структурно фейлится на enumerative; **compression constraint** (категория ≥n детей, |слой i+1|≤|слой i|). -- **Adaptive pre-gate**: 27 query-features без LLM решают, фаерить ли полный ретрив (Adaptive RAG). -- **D-Mem escalation**: dense-first; граф-reranker только при провале confidence-гейта (96.7% recovery при меньшем cost) — примиряет HippoRAG2-верdict с графом. -- **Question-type router** (AdaMem/BookRAG): классификатор вопроса → tailored маршрут. -- **ENGRAM-абляция** в №11: arms A=RRF-5-source / B=dense-per-kind / C=gated / D=A+EDM+ITS. Decision rule: A≥B,C — держим; B>A — дефолт dense-per-kind (конфиг, не архитектура; Memora D.1 — все схемы частные случаи). -- **Correlated evidence fix**: max-presence (CAMA) + lateral inhibition (SYNAPSE) + submodular coverage (2507.19715). - -## Phase H — Eval и инфраструктура - -### [S11] Eval harness (№1 приоритет) - -- Датасеты: LongMemEval-S (strict KU: old+new обязаны вернуться; abstention 30 false-premise; 5 способностей) + LoCoMo (вкл. event-summarization задачи) + PersonaMem-формат. -- Метрики: LLM-judge (97% agreement) + Recall@k/NDCG@k (human evidence positions) + **precision/noise-isolation** (Tenure: dump = precision 0.12 при recall 1.0) + **reacquisition-cost** (completion слеп: 80→85% при ×3 retrieval) + **construction-tokens** (write-cost first-class) + **drift score** + N_eff-abstention (калиброванный) + k-sensitivity + **estate-quality-over-time** (contradiction rate, staleness, precision@6mo) + 3-way task split (factual/sense-making/associative) + **двухсудейный** rank-stability протокол. -- Ablation arms (см. S10) + oracle-retention абляции (random ≈ oracle в одном env) + negative-control protocol (mazemaker: off-switch должен падать) + judge rubric anchored 0-5 (LCC) + честная плашка «какие компоненты включены» (Mem0/Cognee урок) + **LLM-adjudication arm** (A-MEM: локальная LLM подтверждает top-k эмбеддинг-рёбра; links-only +12 F1 multi-hop — единственный кандидат на нарушение no-LLM, решает абляция) + **StateMemBench-пробы**: closed-pool (superseded value = scored outcome), anti-trap (anti-prefer-latest), sequence-пробы (derived recomputed, never quoted), per-substrate salience floors, k-sweep flatness отчёт (не тюнить k на state-задачах). -- Референс-планки: gbrain 93.19/95.32 strict-R@5; MemoryPalace 96.6; Memanto 89.8 LME / 87.1 LoCoMo (platform-only, Moorcheh cloud); Memora 87.4; APEX-MEM 88.88/86.2; LoCoMo SOTA band 86-94. -- Eval harness = отдельный артефакт с открытыми конфигами (Mem0 `memory-benchmarks` паттерн). -- **Cross-encoder rerank** (v2 #2): парк до №11 — включается как arm только если бенчмарк покажет провал RRF (Basic Memory fail-fast disabled-by-default паттерн). - -### [S12] Библиотеки - -- **ВСТРАИВАТЬ**: spaCy + en_core_web_sm (privacy NER + entity-miner; атрибуция NOTICE, PyPI download-at-install); fractional-indexing (CC0, vendor; order_key для wiki/L0/канон-ключей); zero-result-минер (open-index идея, поверх recall_events). -- **ПАРК**: turbovec (16.7k★, ICLR'26 — но 5 мес + wrong regime; скальпели: length-renorm + TQ+ calibration в quantize.py — 2 строки numpy); msgspec (после micro-bench L0); SQLite-paper (2608.24060 scrydb — референс, sqlite-vec кандидат). -- **ИДЕИ**: PageIndex TOC-спуск без LLM для wiki-L4.5; PROV-O для provenance (typed_export); point-in-time graph snapshots; Merkle-DAG incremental reindex для wiki-проекции; spread activation как 4-й retrieval channel (SuperLocalMemory). - -### [S13] Инфраструктура (остальное) - -- memory_audit (расширение memory_diagnose контент-чеками: конфликты-ридер, дубли, stale, file↔DB reconciliation); health: /ready + alembic-head check (1 строка); ariel-cli (ls/tree/find/grep + typed_export surface); A8 MEMORY.md-бридж (двунаправленный, drain-marker); import_chat (claude/chatgpt/jsonl → L0 → гейты, origin=import); Roadmap.md freeze (сделано); admin-тулы манифест (8 шт); autocontext outcome-gated promotion в staging; cycles-таблица daemon (E9: 60s/1ч/3ч/24ч) + **chimera triple cost-cap** (per-cycle/rolling-60m/per-task + estimate-verb) как spend-gate ночного batch; Mermaid-канвас (после G-наполнения); Sessions Replay viewer (после F1); spaCy-атрибуция; crypt-claim fix (убрать/реализовать); **semantica decision read-поверхность** (trace_decision_chain / find_similar_decisions / analyze_decision_impact) для causal-графа E17a; **gbrain gap-reader** — memory_audit флагует unknown/stale/uncited/contradicting в ответах (detection без reporting = полсистемы) + `create_safety` verdict (exists/probable/unknown) как контракт выхода писателей. - -### [S14] Stage 2 (последняя волна) - -- Сведение 65 тулов + slots + URI-keys (включая peers/ tunnels резерв) + MCP behavior-аннотации (readOnlyHint/destructiveHint/idempotentHint — fork поддерживает ToolAnnotations; data-фундамент из триплет-минера) + переименования (wake_up alias) + admin-тулы манифест. **Начинается только после F/G/H.** - -**→ Stage 2-B SHIPPED 2026-09-07 (`mcp_server/annotations.py`): единая карта 65/65 тулов (registry cross-check тест), ToolHints dataclass с MCP-консервативным default (не-картное = не read-only, destructive=True — промоутем write-тул к безопасным быть не может); read-тулы ~30 шт (read_only+idempotent, destructive=False), destructive явно помечены (forget/wiki_delete/cleanup/heal/data/api_key/saga); server.py прокидывает annotations в mcp.tool() — на wire Tool.annotations заполнены (тест через list_tools). Ограничение честно: memory_history/proposals/backup — action-миксы, помечены по худшему действию.** - -**→ Stage 2 стартовал 2026-09-07, План A (URI-keys) SHIPPED (`shared/uris.py`): схема `ariel:////` (fact/wiki/graph/node/episode/l0) поверх существующих ключей, ноль миграций; `parse_uri` (мусор/резервы peer/ отвергнуты) + `resolve_uri` (user_id обязателен в вызове — изоляция: чужое по URI не резолвится; peer → ValueError reserved). Интеграция: search-items и wiki_read несут `uri`, `drill_down(entry_id | ariel-URI, user_id)` принимает URI. Тесты 9 (parse-матрица, roundtrip, изоляция user_id/layer, peer ValueError, search/wiki_read/drill_down интеграция). Gate 1494/0, mypy 231 clean. Следом: План B (behavior-аннотации), C (slots+consolidation), D (Stage2-eval). - -**→ Stage 2 План C v2 УТВЕРЖДЁН (design 2026-09-07, код не начат): сведение поверхности 57→~13 схем через мета-слой.** Мотивация (верифицирована live-count): примитивы задумывались для минимальной поверхности, live-комбо 7 тиров = 57 видимых схем на агента (registry 65; 8 сирот — только с `all`). Решение владельца: «выпадающий список» — мета-тул на тир. Механика: (1) `mcp_server/slots.py` — SLOTS: dict[tool→slot], 12 слотов (core 7 вкл. wake_up / recall 5 / context 4 / episodes 4 / sessions 3 / graph 4 / wiki 9 / insight 10 (+procedure, scratchpad) / write 9 (+skill_promote из сирот) / review 4 (+daily_brief — тир brief растворяется) / admin 7 = НОВЫЙ тир (api_key, backup, cleanup, data, lucidity_purge, saga, sync_replica)); cross-check тест против registry (сироты = 0 инвариант — исполним). (2) Мета-слой: 6 диспетчеров (context/insight/write/wiki/review/admin) **генерируются из EXTRA_TIERS программно** (тир-словарь = единственный источник); на проводе схема `action: string + args: object` (probe на живом SDK подтверждил), точные параметры — через `action='list'` каталог (per-action behavior из карты Plan B); первый вызов = каталог, далее одношагово. Мета-слой под флагом `ARIEL_META=1` (default off — плоская поверхность 65 не меняется, eval Plan D не тронут). (3) wake_up (E10) регистрируется примитивом (PRIMITIVE_TOOLS, слот core) — draft features/wake_up.py (recap+inject критика на общем бюджете, 56 строк). (4) Пресеты ARIEL_EXPOSE: agent/operator/full. (5) Манифест docs/tools/exposure.md (слоты/тиры/пресеты/грамматика) + починка врёной «65 tools» строки в reference.md (live-комбо даёт 57, не 65). Модель видит: 7 примитивов + 6 мета-тулов = 13 схем при всех тирах. Риски приняты: параметры не видны до каталога (лечение каталогом), первый вызов двухшаговый за сессию, мета-аннотации консервативны по худшему действию членов. - -**→ Stage 2 План C SHIPPED 2026-09-07 (`8e6f9d2`+`8d281f0`+`65bced1`+`2b0b2ed`+`98f6741`+`242250f`, push 040fb34..242250f, gate 1523/0 + mypy 10-dir clean).** Всё выше — вживую: registry 66 (65+wake_up); slots.py + 5 cross-check тестов (включая дубль-инвариант по группам); EXTRA_TIERS: admin-7 добавлен, skill_promote→write, brief растворён в review, сироты=0 тестом; `resolve_exposure` расширен пресетами agent/operator/full с fixpoint-раскрытием (первая версия с однопроходным раскрытием поймана тестом operator⊄agent — переписана на цикл); wake_up зарегистрирован примитивом (surface по умолчанию 6→7) + annotations read_only; мета-слой `mcp_server/meta_tools.py`: фабрика `_make_dispatcher` (SDK отверг underscore-параметры в сигнатуре — закрытие через factory-call), `_scope_tool` оборачивает цель лениво на каждый dispatch (user_id-биндинг переживает мета-прыжок), mypy no-any-return закрыт явной dict-нормализацией; live-проверка: ARIEL_EXPOSE=all+ARIEL_META=1 → **13 схем** (7 примитивов + 6 диспетчеров), без ARIEL_META → 66 плоско. Пресеты измерены: agent=59, operator=66. Манифест docs/tools/exposure.md; reference.md переписан (66, admin-тир, ex-brief). Live-конфиги агентов НЕ тронуты (переезд на agent+ARIEL_META=1 — отдельное решение владельца; строка комбинации легаси теперь резолвится в 59). Уроки: pre-commit ruff-pinned v0.16.1 ≠ свежий ruff (формат-драфт схлопывал commit — форматить pinned-версией до git add); счётчики registry хардкодились в 3 местах тестов (65→66) — live-реестр прав, статические числа устаревают молча. - -## [S15] Волновой план - -- **F1**: L0-журнал + watermark + классификатор + G0 (privacy) — фундамент. -- **F2**: G1-дистиллятор (kind-роутинг, канонические ключи, novelty-gate, конфликты) + G2 (outcome-gated promotion) + replay. -- **F3**: session-close + L2-обогащение + E6 lessons mining + retention/CLACK + спека инъекции (per-kind caps, порядок, maxChars, precedence rules). -- **G1**: фундамент графа (add_edge wiring, co-retrieval журнал, пре-чистка) + минеры #1/#2/#4 (сотни рёбер за вечер). -- **G2**: #5 провенанс + #9 эмбеддинг-минер + санитария (inhibition, validity, MAD, valence) + graph_enrich трёхфазный. -- **G3**: #7 co-retrieval + #3 сущности (spaCy) + #6 маркеры + #8 инварианты + dual-route (S2 exhaustive, D-Mem escalation, question-router). -- **H1**: eval harness (№11) + arms + negative controls. -- **H2**: memory_audit + import_chat + ariel-cli + A8-бридж + admin manifest + crypt-claim fix. -- **H3**: Mermaid + PROV-O + snapshots + Juice-опции (LCC latent, KV-precompute — watch). -- **Stage 2**: сведение тулов + URI + аннотации (после всего). - -## [S16] Открытые вопросы и осознанно-отложенное - -- CLACK-формат (Лили-концепт) — холодный тир: **ЖИВОЙ (2026-09-07)** — владелец предоставит текущую реализацию + теорию, совместимую с MIB-EDM-ITS; план = эксперимент с a-memory. -- ~~Тестовые строки wiki_index~~ — закрыты прод-чисткой `03c9d83` (wiki_index 15→9). -- ~~JSON-дампы эпизодов Эли~~ — ЗАКРЫТЫ (2026-09-07 верификация): 0 dump-shaped эпизодов во всех 3 prod-БД (чистка хвостов `1cad8ad`); ingress-guard `_looks_like_dump` (consolidation.py:38 → auto_save_text hooks/external.py:119) блокирует новые. Питание дампов в L0/textcat отклонено владельцем-диалогом: L0 = дверь ввода (повторное загрязнение), textcat режет guard ДО классификатора (проблема textcat = нехватка stable-класса). -- Счётчик тулов: **66** (Plan C добавил wake_up; 65 после удаления memory_forget в tails-волне). -- privacy strict-mode (<0.5 confidence refusal) — НЕ реализован (в коде только редактор sanitize: regex → ru-persona → ru-NER → en-NER, плейсхолдеры ⟨PERSON_N⟩). Решение владельца: закрыть «не делаем» или реализовать opt-in refusal-режимом. -- **memory_forget удалён vs Cognee forget-verb**: verb-forget (dataset-scoped GDPR-стиль) ≠ удалённый тул (single-key L4); verb-forget реализуется примитивом `forget` (scope-параметры) — tension разрешён, отдельный тул не возвращать. -- **Stage 2 Plan D ОТМЕНЁН (2026-09-07, решение владельца)**: «мы уже нашли и реализовали другое решение» — вердикт о поверхности принят дизайном + живым probe (13 схем при ARIEL_META=1), eval для решения не потребовался. Per-slot usage-телеметрия — опциональное будущее добавление к diagnose, не план. **Stage 2 = COMPLETE (A+B+C shipped, D отменён).** - -### [S17] Stage 1 tail — потерянные 🔑-пункты драфта (сверка драфт→диздок→план→код, 2026-09-05) - -> Сверка v38-драфта против диздока/плана C1-C8/кода нашла 7 🔑-обязательств, потерянных -> при конверсии (застряли между стадиями без владельца). Все — F-фундамент, ноль -> межзависимостей. **Закрываются немедленно как окончание Stage 1**, до Stage 2. - -1. **`deterministic_retrieval` флаг** (v27 п.3): флаг в config — retrieval отдаёт детерминированный порядок (ITS min-max без ингибиции/дрожаний) для регламентированных сценариев. Default off. Реализация: bypass inhibit/EMA-членов в edm_rerank при флаге. -2. **ENGRAM procedural kind** (v28 #3): процедурный kind («как сделать X», how-to) — 14-й MemoryKind с own policy (never_archive=True, decay 0) + kind_for_text-маркеры («сделай так», «порядок действий», «инструкция по») → роутится в L4-namespace агента (agent-self track). Data-фундамент для Stage 2 behavior-аннотаций. -3. **AdaptiveRAG pre-gate** (v28 #5): 27 query-features (7 групп, LLM-free) решают «фаерить ли все источники» — дешёвый skip-тир поверх RRF (cost down + шум down), результат питает N_eff/abstention. Реализация: feature-вектор запроса (длина, тип, наличие дат, вопросительная форма...) → простой гейт. -4. **A2 advisory `similarTo` в ответе save** (волна A2): ConflictResolver ловит near-dup на записи → сейчас пишет memory_conflicts молча; вернуть advisory в ответе save (поля `similar_to: [key…]`), агент сам решает переформулировать. -5. **SHA-256 дедуп L0-блоков** (механизмы разрастания п.2): колонка content_hash в l0_journal (или in-capture дедуп) — повторный вывод команды хранится один раз, ссылки на первую запись. Дедуп сейчас только тул-уровень (_DedupCache, TTL 300с). -6. **Zero-result минер** (open-index, v25): провальные запросы (0 хитов) журналируются как минер-сигнал «что смоделировать следующим» (LME +9.4% recall) — поверх recall_events/co_pairs инфраструктуры. -7. **Counter-signal алиасы** (Tenure): superseded-имена в ранжировании с негативной ролью (не drop) — связка с conflict resolution + канон-ключами. - -Плюс **EMA-гейт** (F2-обязательство): adaptive_threshold выпал из auto_save_text при переработках — порог статичный 0.5. Вернуть EMA-член в гейт (вернув репутацию «без изменений логики»). - -**Дополнение из первоисточника a-memory-start-FGH.md (сверка 2026-09-05):** - -8. **Drill-down переживает архивацию** (принцип 5 линии): diagnostics.drill_down ищет сырье только в l0_journal — после tier_l0 строка уезжает в l0_cold_archive и ссылка ведёт в тупик. Фикс: drill_down фолбэк в l0_cold_archive по source_raw_id. **→ SHIPPED 2026-09-06 (`283e48f`): фолбэк SELECT по id + `archived: true`-флаг; нет нигде → прежний тупик-контракт сохранён.** -9. **Подтверждающий слой эмбеддинг-минера** (роль 2 из стартового дока): keyword-связь (теги/токены) + высокое векторное сходство → вес ребра растёт (0.4→0.6); сигналы противоречат → вес падает/ребро отбрасывается. Сейчас минер #9 пишёт semantic_overlap независимо, кросс-валидации нет. **→ SHIPPED 2026-09-06 (`8571541`): `graph.embedding_crosscheck` default OFF — ON: keyword-согласие (общие канон-токены ИЛИ общие теги) → вес ребра 0.6; лексического следа нет → ребро отброшено (на hash-векторах «векторно похоже, лексически никак» = шум).** -10. **Anomalous-vector мусор-детектор** (бонус-трюк): L3-дампы дают аномальные векторы — эмбеддинг-минер попутно флагует «мусорные узлы, кандидаты на чистку» (двойная польза с пре-чисткой). Сейчас не реализовано. **→ SHIPPED 2026-09-06 (`8571541`): бит-вырожденные векторы (0 бит) → тег `anomaly:junk_vector`, отчёт `anomalies: N`; anomaly-теги в rich-embed не попадают (самозагрязнения нет). Density-эвристики НЕ делались: без валидационных данных — только детерминированный критерий вырожденности.** -11. **HDBSCAN-кластеризация эмбеддингов** (роль 3): сверка с louvain-комьюнити → community-хабы. Не реализовано (louvain есть, эмбеддинг-кластеров нет). **→ SHIPPED 2026-09-06 (`376b45b`): `lifecycle/embedding_clusters.py` — HDBSCAN (sklearn, precomputed Hamming на 384 MIB-битах) + louvain-доля согласия; отчёт graph_enrich miners.embedding.clusters за флагом `graph.embed_clusters` default OFF; <12 векторов → skipped. Отчёт-only, рёбра не меняет; смысл при dense-эмбеддингах.** - -### [S18] Stage 2 — пополнение (сверка 2026-09-05, сверх S14/S16-distillate) - -- GA per-query min-max для ACT-R-члена в multi_source (v17 #5). -- maxChars per-memory при инъекции (D6, S-эффорт). -- Channel-гранулярность metadata (A4: user_explicit/episode_promotion/... → +tool/import/shared). -- `changed_since`-модальность в get_intervals (Memanto temporal versioning). -- Orphan-anchor GC для derived-ключей минеров (Memora cue-anchor prune). -- Semantic dedup cosine>0.92 merge как 3-й сигнал после exact-SHA + novelty-Jaccard (memory-os/3M консенсус). -- B2 recall hygiene: is_current-view / superseded-флаги, чтобы recall не отдавал закрытые интервалы (bi-temporal читает old+new только для KU-запросов — остальное фильтрует). -- 3-option конфликт-контракт агенту (supersede/retain/annotate): ConflictResolver.resolve есть, agent-facing surface нет — тул или расширение memory_recall. -- Gap-registry (3M Find Gap): L3-questions → ночной registry → proactive acquisition. -- **B6 UPDATE>MERGE>CREATE + лимит активных сущностей** (TencentDB): был в драфте как «Phase G после entity linking, needs eval-цифры» — в диздок не перенесён. Стадия: после №11 (heuristic размера, false-merge дороже разрастания). **→ POST-EVAL SHIPPED 2026-09-06 (`03ab2c8`), цифры с 3 живых инстансов: false-merge НЕ подтвердился (0 групп дублей person/org/entity) — UPDATE/MERGE не делаем, find_or_add_entity exact-dedup достаточен; разрастание ПОДТВЕРДИЛОСЬ — multi-topic dump собрал 109 co_mentions из 137 рёбер инстанса (hermes), один синоним-класс («memory») соединил его со всем подряд → degree-cap `_CO_MENTIONS_TOPK=12` на узел (паттерн _EMBED_TOPK минера #9).** -- **Offload тяжёлых tool-логов в refs/*.md** (TencentDB v2): в контексте только Mermaid-канвас с node_id (Mermaid есть; offload-механика — Stage 2 мелочь). **→ SHIPPED 2026-09-06 (`24597db`): `features/offload.py` — лог >4000 симв → work_notes/refs--.md (wiki-страница, FTS работает), компактный ref-указатель наружу; графовый узел НЕ пишется bypass'ом — wiki_graph_builder создаёт wiki_page-узел штатно (F-T9 AST-инвариант поймал первую версию с add_node — гейт работает).** -- **Circuit breaker на harness-адаптерах** (TencentDB v9: 5 fails → 60s pause): hermes-плагин breaker'а не имеет (проверено) — Stage 2 мелочь, адаптеры Hermes/cow/MiMo. - -### [S19] Сверка исходников (2026-09-05, a-memory-start-FGH / l0-l4-pipeline / graph-miners / spacy-integration) - -> Построчная сверка пяти исходных доков против диздока/кода. Найденные пропуски — -> в S17 (немедленно) или сюда (Stage 2). Помечены [S17↑] если перенесены. - -**Из start-FGH / l0-l4-pipeline (линия L0–L4):** -- **wiki_id на факте** (обратная ссылка L4→wiki: «факт помнит wiki_id источника»): реализован только минер wiki_fact_links (ребро), колонки/метаданных wiki_id на core_memory нет → Stage 2: metadata.wiki_ids при [[fact:]]-линковке. -- **Recall со страницы** (page → связанные факты, гидратация вниз): нет прямой read-поверхности — потребители ходят через graph_node. Stage 2 (пара к wiki_id). -- **Дедуп capture по content_hash** [S17↑ п.5]. - -**Из graph-miners (граф):** -- **Подтверждающий слой минера #9** [S17↑ доп. 9]: голосование keyword+embedding → weight 0.4→0.6. -- **Anomalous-vector мусор-детектор** [S17↑ доп. 10]. -- **HDBSCAN-кластеризация** [S17↑ доп. 11]. -- **Стемминг в минере #2** (Эли: «стемминг уже есть» — по коду его нет; _TOKEN_RE голый regex → topic_overlap теряет RU-морфологию): Stage 2 — лёгкий стеммер ( Porter RU) в _canon_tokens. **→ SHIPPED 2026-09-06 (`f2b0487`): pymorphy3-леммы (не Porter) в `_canon_tokens` и канон-ключах за флагом `rag.lemmatize` default true; guard'ы: score <0.3 (заимствования вне словаря: «деплой»→«деплый») и общий префикс <4 → сырой токен; лемма <4 симв. отбрасывается; drift-риск ключей задокументирован в config.yaml (старые ключи не пере-хэшируются). Тест-фикстуры адаптированы: «сборка/сборку» больше не анти-кейс — это заявленный эффект.** -- **Retrieval-фильтр по heuristic-тегам** («recall может фильтровать по источнику ребра»): фильтр рёбер по provenance при graph-expand — Stage 2 (низкий приоритет). - -**Из spacy-integration (4 варианта углубления, все вне диздока — фиксируются сюда):** -1. **ru_core_news_sm на privacy-гейт** (~12МБ): русский NER вместо en-модели на кириллице; расширение маскировки на произвольные русские PERSON/ORG без ручного словаря. Garbage-guard обязателен (ru-NER шумит на коротких текстах). ~~15 минут работы — ближайший шаг~~ **SHIPPED 2026-09-06 (`2a91ff7`, S19.1): PER ≥2 токенов (однотокенный = шум «Кисонька»), ORG/LOC однотокенные легитимны, en-NER только на латинице, breaker ru_ner_model 3/60s, `rag.ru_ner` default on, тесты test_privacy_ru_ner.py 6.** -2. **textcat-типизация клауз** (усиление kind_for_text): обучающие данные УЖЕ ЕСТЬ (core_memory.memory_kind + decay_rate ≤0.005 → самоклейб «инвариант vs событие»); argmax + порог уверенности, ниже порога → fallback keyword-мапы; eval на H-harness (NDCG/drift). ~~Обучающие данные УЖЕ ЕСТЬ~~ **ПИЛОТ SHIPPED 2026-09-06 (`9d17e07`), вердикт НИЖЕ. Инвентаризация данных опровергла масштаб: все 5 инстансов L4 = 448, fact 81%, non-fact 87 (decision 2, todo 1) — 14 классов не на чем.** -3. **Лемматизация канон-ключей**: ru-леммы схлопывают формы («уволилась/увольнение» → одна основа) — меньше ручных синонимов. -4. **Морфо-фичи для ImportanceScorer** (POS/модальность) — низкий приоритет. -- **Паттерн интеграции** (инвариант): lazy-load + circuit-breaker (переиспользовать _embedding_breaker); fallback на правила при сбое; config-флаг; eval до/после. - -**S19.2 textcat-пилот — вердикт (2026-09-06, `9d17e07`):** - -Схема: 2-классовая модель (stable = kind с decay ≤0.005 / ephemeral — ровно «инвариант vs событие» Эли, тот же порог что route_kind). Данные: self-labels всех 5 live-инстансов (310 строк, stable 48 / ephemeral 262), oversampling minority; ru_core_news_sm tok2vec заморожен, учится только textcat; интеграция — ТОЛЬКО keyword-miss ветка: FACT (decay 0.010 → L3) с argmax 'stable' ≥ 0.9 промоутится в L4, keyword-матчи и уверенность ниже порога не трогаются, флаг `rag.textcat` default OFF, breaker textcat_model 3/60s. - -| конфигурация | 5-fold CV | stable precision | stable recall | -|---|---|---|---| -| keyword-мапы (статус-кво) | acc 0.871 | **0.636** | 0.352 | -| keyword + textcat (th=0.9) | acc 0.848 | 0.485 | **0.484** | -| textcat-only (dev n=62, th=0.9) | acc 0.839 | 0.750 | 0.300 | - -Вердикт: **прод НЕ включаем** — модель добавляет recall (+13пп) но роняет precision (−15пп): половина L4-промоушенов была бы ложной, а L4-загрязнение дороже пропущенного факта. Keyword-мапы остаются единственным роутером. Шипится инфраструктура: `shared/textcat.py` (classify + route_promote_stable + breaker), `shared/textcat_data.py` (сборщик self-labels), `scripts/train_textcat.py` (train + threshold sweep + метрики в train_metrics.json), дистиллер-хук за флагом, тесты 11. Путь включения: переобучение при L4 non-fact ≥ 300 (сейчас 87), затем 5-fold P ≥ 0.75 → флаг ON владельцем. OOD-наблюдение: на мусоре ('abc') модель уверена в ephemeral — порог защищает только stable-сторону, что и нужно по контракту. - -**Из EDM.md (транскрипт Aurelle, сверка 2026-09-05):** драфт v34 покрывает документ полностью — формул MIB/EDM/ITS в статье нет (реконструкция = модель), sign ≈ fancy MIB, конфаунды сравнения (Pinecone+Cohere vs встроенный ITS, exhaustive vs HNSW), цифры MAIR. Указатели раздела 7 доехали в драфт, но не в диздок — фиксируются: -- **TOKI** (bitemporal operator algebra для contradiction resolution) — G-комплемент conflict-fusion; -- **PROJECTMEM** (local-first event-sourced memory + judgment layer) — родня L0, референс; -- **Reliable Post-Retrieval Assembly** (separating evidence extraction from policy execution) — валидация гейт-дизайна; -- **Render Confound in Deprecation-Aware Memory Evaluation** — ловушка eval №11; -- **Covariance Structure... Binary Quantization** — обоснование/границы sign-бинаризации quantize.py; -- **REWA / Information Theory of Similarity (2512.00378)** — rate-distortion yardstick для «достаточно ли 128 bytes». - -**Из Автохуки.md (исходная спека автоматической памяти, сверка):** реализовано полностью — «триггеры не таймеры» (autohooks daemon poll = transport, saves server-side), хуки session_started/session_ended/new_message/memory_pressure/auto_context/post_session_diff, fire-async + mem-передача + хендлеры-исполнители (registry takes_mem/is_async), server-side importance-gate вместо эвристик демона. Единственный непокрытый пункт: -- **`context_assembly` хук** (pre-response сборка контекста системой, «я не думаю об инжекте») — инъекция происходит harness-side; ariel-часть готова (memory_context_inject + session-start inject + auto_context post-recall). Доработка — контракт harness-адаптеров (Stage 2, cow/Hermes/MiMo). **→ SHIPPED 2026-09-06 (`24597db`): `autohooks context --text ` — одна CLI-точка pre-response сборки (top-5 релевантных recall_protocol + recent L1, бюджет-capped, md/json); контракт+виринг для трёх адаптеров — docs/hooks/context-assembly.md (правила вставки cache-friendly: со стрипом истории, L1-prepend, cache:break). Правки адаптеров — на следующий деплой (владелец: агентов не рестартить).** -- **Минорный хвост осознанно-отложенного (distillate)**: B2 is_current-view; B4 ttl_minutes на тул-поверхности; B7 heat sum+1; B10 recurring→staging; C1 генератор сцен; C4 pinned; C5 private-флаг; C6 Layer Charter; C9 compact-render; D2 .abstract-тир; D3 MOC-first роутер; D4 retrieval-трейс в L0; D7 session-diversity; D12 BFS-upgrade `_from_graph`; E6-ретро skill-mine; changed-since модальность; Aeon lookaside buffer; Tenure hard-scope filter post-RRF; CWL dependency-aware инъекция; no-silent-fallback инвариант; reconstruction-check hot→warm; APEX fuse-then-summarize; Basic Memory observation-синтаксис. Каждый помечен в research draft с вердиктом; попадание в волны — на планировании. - -### [S20] Stage 2 — вердикты (журнал абляций и решённых пунктов, 2026-09-06) - -**№11 ENGRAM-абляция, прогон 1 (MINI_DATASET n=10, proxy-judge, commit f93f6c3):** -5-source RRF побеждает dense-per-kind решительно — вызов драфта отработан, источники НЕ режутся. - -| arm | accuracy | ndcg@5 | recall@5 | construction_tokens | verdict | -|---|---|---|---|---|---| -| rrf | 1.000 | 0.389 | 0.600 | 1257 | статус-кво без EDM/ITS | -| dense_per_kind | 0.200 | 0.000 | 0.000 | 125 | PROVален: ENGRAM-упрощение без wiki/episodic-путей теряет всё | -| gated | 1.000 | 0.430 | 0.600 | 1150 | pre-gate −107 tok (−8.5%) без потери качества | -| full | 1.000 | 0.729 | 1.000 | 1008 | WINNER: EDM/ITS + роутинг, и короче всех | - -- Negative-control: shuffled 0.300 < real 1.000 на rrf/full — PASS (judge чувствителен). -- dense_per_kind 0.20 acc — ожидаемо: арм ищет только L4/rag по kind_for_text(query), wiki/episodic недоступны (это конструкция арма, не баг). ENGRAM-схема «+15 pts @ 1% токенов» не воспроизводится на многослойной памяти без dense-модели; наш hash-fallback эмбеддинг-тир не даёт dense-качества. -- gated −8.5% construction_tokens при acc=1.0 — pre-gate кандидат на включение в проде ПОСЛЕ прогона на LongMemEval-S (HF сейчас недоступен оффлайн — прогон откладывается до сети; MINI-вердикт прелиминарный). -- dense_per_kind остаётся армом №11-eval, НЕ прод-путём. - -**Прогон Plan A/C — as-shipped (2026-09-06, eb6f959..5c9a67d):** - -| пункт | план | commit | verdict | -|---|---|---|---| -| GA min-max ACT-R | A.1 | ead6b20 | per-query `_minmax_actr` [1.0,1.3] в multi_source: топ-факт ×1.3, худший нейтральный 1.0 (floor=нейтральный, не усреднение) | -| maxChars per-block | A.2 | b95a8f1 | `_cap(400)` на всех 9 build-точках inject; `inject.max_chars` default-ON (единственное исключение из default-off) | -| semantic dedup | A.3 | 99279e5 | cos>0.92 same-kind LIMIT 50, `memory.semantic_dedup` default-OFF; позиция — ДО conflict-check (иначе парафрай рождает конфликт-пару из дубликата — тест поймал) | -| B2 is_current | C.1 | a0424be | глобальное скрытие earlier: C4-версионированный ключ `::vN` ИЛИ same-key scope=later (private-later не закрывает); `include_superseded=True` возвращает скрытые; каждый item несёт `is_current`; плоский SQL на earlier-строку вместо двух из плана (план-SQL имел key=?/LIKE-коллизию) | -| changed_since | C.2 | 5c9a67d | дельта-поллинг `get_intervals` по valid_from >= порога (Memanto); None — вся цепочка как раньше | - -План-отклонения: тест B2 усилен (12 филлеров — later гарантированно off-page, план-версия при limit=10 пару не роняла); фикстура `hermetic_core` (не `hermetic_cm`); mypy требует tuple(params) в execute. - -**Прогон Plan B/D — as-shipped (2026-09-06, ead728c..ebd7af4):** - -| пункт | план | commit | verdict | -|---|---|---|---| -| channel-гранулярность | B.1 | ead728c | `shared/channels.py`: KNOWN_CHANNELS (9) + channel_of (prefix до `:`, unknown → other) + canonical_source; без миграции — конвенция | -| 3-option конфликт-контракт | B.2 | 85a288b | memory_proposals action=conflict (тулов по-прежнему 65): supersede/retain через ConflictResolver.resolve (группа закрыта, проигравший архивирован); annotate — аннотация в metadata СТАРОЙ стороны конфликта; ключ — детерминированная `_canonical_key(content, kind_for_text)`-связка дистиллера (плановый cmem.search fuzzy — нет metadata у items и не та запись) | -| orphan-anchor GC | B.3 | 06d3d7b | `_orphan_anchor_gc` в graph_enrich: episode:% якоря (fact/question) без рёбер старше 7д; вызов ПЕРЕД dream (REM мостит изолированные якоря первыми — episode-токены дают Jaccard 1.0); отчёт `orphan_gc: N`; noop-тест адаптирован | -| counter-signal → канон-ключи | B.4 | 081a446 | `_canonical_key` разворачивает superseded-имя в current ДО синоним-канонизации → переименование падает в один ключ, C4 строит superseded-цепочку сам; dual_route-пессимизация не задета | -| gap-registry | D.1 | ff3ceb0+f0e240b | `lifecycle/gap_registry.py`: question-эпизоды (SQL по ВСЕМ пользователям, tags LIKE — search_by_tag per-user) + zero-result хвосты (≥2 повторов) → memory_gaps (gap_hash, идемпотентно check-then-write); graph_enrich фаза + отчёт `gap_registry`; zero-result ветка молчит до первого ensure минера | -| harness breaker | D.2 | 87d91bf | `shared/harness_breaker.py`: harness_call per-agent 5 fails → 60s open (breaker_registry-backed), HarnessUnavailableError; адаптеры вне репо подключают следующим визитом | -| retrieval-фильтр provenance | D.3 | ebd7af4 | `_expand_graph(..., edge_exclude=None)`: рёбра с тегом heuristic: не разворачиваются при graph-expand; None = статус-кво (LIKE по JSON-массиву tags) | - -**Прогон Plan E — as-shipped (2026-09-06, f0bad78..74f13e9):** - -| пункт | план | commit | verdict | -|---|---|---|---| -| wiki_id backlink | E.1 | f0bad78 | минер wiki_fact_links мёржит page **node_id** в `metadata.wiki_ids` (idempotent set, no-op save при повторе — LEDGER не раздувается); существующие metadata-ключи (scope, source_raw_id) выживают; node_id, не wiki_index.entry_id — узловое пространство рёбер и read-поверхности | -| recall со страницы | E.2 | 74f13e9 | `wiki_read` → `related_facts` + `related_count` (обратный проход по wiki_fact_link-рёбрам, join fact-узел.content == core_memory.value, private исключены); пустой список = норма; тулов 65 стабильно | - -Волна S18-19 ЗАКРЫТА полностью: A (4) + C (2+диздок) + B (4) + D (3) + E (2) = 15 пунктов + диздок-вердикты; gate 1452/0, mypy 224 clean, ruff/format clean. - -**№11 ENGRAM-абляция, прогон 2 (LongMemEval-S n=50 stride, proxy-judge, 2026-09-06):** -Датасет: официальный xiaowu0162/longmemeval (S-файл 265МБ → локальный кэш; карточка битая для load_dataset — файлы без расширений). 50 вопросов stride-выборкой (все 6 категорий: multi-session 14, temporal 13, KU 8, ss-user 7, ss-assistant 5, ss-pref 3), корпус = union haystack их сессий (2443 сессии). Хэш-эмбеддинги (ARIEL_HASH_EMBEDDINGS=1, консистентно с прогоном 1). - -| arm | accuracy | strict | recall@5 | ndcg@5 | precision | construction_tokens | время | -|---|---|---|---|---|---|---|---| -| rrf | 0.500 | 0.200 | 0.010 | 0.500 | 0.142 | 879 501 | 104с | -| dense_per_kind | 0.420 | 0.040 | 0.000 | 0.187 | 0.038 | 790 784 | 58с | -| gated | 0.500 | 0.200 | 0.010 | 0.500 | 0.142 | 879 501 | 109с | -| full | **0.600** | **0.320** | **0.041** | **0.572** | **0.282** | 1 664 494 | 652с | -| full_pregate | 0.600 | 0.320 | 0.041 | 0.572 | 0.282 | 1 664 494 | 650с | -| rrf_shuffled (neg-ctrl) | 0.420 | 0.080 | 0.010 | 0.368 | 0.116 | 879 501 | 103с | -| full_shuffled (neg-ctrl) | 0.540 | 0.180 | 0.041 | 0.359 | 0.200 | 1 664 494 | 647с | - -Вердикты: -- **full подтверждает победу** (второй датасет): acc 0.600 vs 0.500 rrf, strict 0.320 vs 0.200, recall@5 4.1% vs 1.0%, precision 0.282 vs 0.142 — EDM/ITS + роутинг лучше статус-кво и на длинном корпусе. dense_per_kind мёртв и здесь (recall 0.000). -- **VERDICT ПРОТИВ pre-gate в проде**: gated ≡ rrf байт-в-байт (гейт-матрица gate_sources для английских вопросных запросов — «длинный/вопросный → полный fan-out»; MINI-экономика −8.5% была на русских коротких/перечислительных запросах) и full_pregate ≡ full (pre-gate урезает пул fan-out, но после EDM-rerank финальные топ-хиты те же — экономия есть в стоимости fan-out, НЕ в construction_tokens; на S нулевая). retrieval.pregate остаётся default-off; кандидат пересматривается только при русской рабочей нагрузке с короткими/перечислительными запросами. -- **Честная плашка judge**: proxy-judge на английском слабодискриминативен — full_shuffled 0.540 против real 0.600 (на MINI падение было 1.000→0.300). Discriminative-метрика — strict (0.320 vs 0.180). Абсолютные acc значения оптимистичны; сравнение армов валидно (все на одном judge). -- recall@5 низкий у всех (≤4.1%): evidence-сессии LongMemEval-S тонут в 2443-сессионном корпусе без dense-модели — известное ограничение hash-эмбеддингов (прогон 1, dense_per_kind-вердикт). - -Инфра урока: aiosqlite worker-поток при закрытии инстанса роняет следующий run_eval в том же процессе — один run_eval на процесс + os._exit(0) (тот же паттерн, что conftest.py::pytest_sessionfinish). /tmp 80% quota от tmp-баз прогонов — ручная чистка ariel-eval-* между волнами (backup_cron._cleanup_tmp уже чистит pytest-of-*). - -**№11 прогон 3 — dense-эмбеддинги (LongMemEval-S n=50, e5-small 384d, 2026-09-06):** - -| arm | accuracy | strict | recall@5 | ndcg@5 | precision | construction_tokens | время | -|---|---|---|---|---|---|---|---| -| full_hash (прогон 2) | 0.600 | 0.320 | **0.041** | **0.572** | **0.282** | 1 664 494 | 652с | -| full_dense | 0.600 | **0.340** | 0.031 | 0.558 | 0.252 | 1 375 219 | 2291с | -| full_dense_shuffled (neg-ctrl) | 0.540 | 0.180 | 0.031 | 0.335 | 0.184 | 1 375 219 | 2877с | - -Вердикт: **dense НЕ обгоняет hash на этой выборке — статус-кво (hash) сохраняется**. strict +2пп (0.320→0.340, единственный рост), но recall@5 упал (0.041→0.031), precision/ndcg ниже, время ×3.5 (2291с: dense-кодирование 2443 сессий на CPU). Anti-контроль валиден (0.540 < 0.600). Объяснение гипотеза: e5-small без fine-tune на retrieval-задачах этого домена не бьёт «лексический якорь» RRF-микса (FTS+tags+core точные совпадения) — dense добавляет семантику там, где лексика уже сработала, и добавляет шум там, где не сработала; recall-провал прогонов 1-2 скорее ограничен выборкой 50 (evidence-сессии в tail) + сплитом S, чем качеством векторов. Практические следствия: (1) dense_per_kind-арм и semantic-dedup/crosscheck/HDBSCAN-флаги остаются OFF до появления большего корпуса или dense-aware переобучения порогов; (2) e5-small модель остаётся в venv (кэш HF), hash-fallback остаётся прод-путём эмбеддингов; (3) fail-fast runner scripts/run_lme_dense.py — повтор прогона одной командой. Инфра: env-переменная ARIEL_HASH_EMBEDDINGS=1 ставится хуком ariel-inject.ts всем дочерним процессам сессии (осознанный live-выбор) — runner снимает её принудительно; aiosqlite-гонка требует один run_eval на процесс (как раньше). - -**Сверка прогона 2 с published baselines (arXiv 2410.10813, ICLR 2025, HTML v2):** - -| система | настройка | accuracy | -|---|---|---| -| **ariel-memory (arm full, наш)** | LongMemEval-S, полный S-корпус, hash-эмбеддинги, **без LLM-ридера** (token-overlap judge) | **0.600 proxy / 0.320 strict** | -| GPT-4o long-context | _S, полный 115k контекст | 0.606 | -| GPT-4o + Chain-of-Note | _S | 0.640 | -| Llama 3.1 70B long-context | _S | 0.334 | -| ChatGPT memory (GPT-4o) | history 10× короче S | 0.577 | -| Coze (GPT-4o) | history 10× короче S | 0.330 | -| Oracle GPT-4o (только evidence) | _S | 0.870 | - -Вывод: retrieval-стек ariel (multi-source RRF + роутинг + EDM/ITS + K-gate) на честном LongMemEval-S в лиге GPT-4o long-context (0.606) и выше коммерческих memory-надстроек ChatGPT (0.577)/Coze (0.330) — при нулевых LLM-затратах в рантайме поиска и без генерации связного ответа (eval retrieval-поверхности). Оговорки: judge разные (у статьи GPT-4o-судья 97% human-согласия; наш proxy оптимистичен — shuffled 0.54, discriminative-метрика strict 0.32 и она выше Llama 3.1 70B full-context 0.334 в их judge); срез 50/500 вопросов; recall-рычаг — dense-эмбеддинг (наш recall@5 4.1% на hash-фолбэке; у статьи лучшая конфигурация R@5 0.73 Stella). Встречное вложение: dense-модель даст больше, чем новые гейты. diff --git a/docs/compose/specs/2026-09-04-phase-fgh-draft.md b/docs/compose/specs/2026-09-04-phase-fgh-draft.md deleted file mode 100644 index ac14bb25..00000000 --- a/docs/compose/specs/2026-09-04-phase-fgh-draft.md +++ /dev/null @@ -1,1089 +0,0 @@ -# Phase F/G/H — Draft (working document) - -> **Status: DRAFT v1.** Мои предложения (2026-09-04, brainstorm сессия 4) — расходный -> материал для слияния с наработками пользователя (подаются частями в чат). -> После слияния: триаж → master-list (+ волны/усилия). НЕ финальная спека. -> Решения уже принятые: L0 = сырой журнал (append-only); порядок — сначала план F/G/H, -> Stage 2 вливается сюда; Phase A–D закрыты (отложенное — только по решениям: -> MOVED→MUSE, PARKED, [NEW], Stage 2). - ---- - -## Phase F — L0-журнал + конвейер L0→L4 с явными гейтами - -### Диагноз текущего потока - -- `auto_save_text` (hooks/external.py) решает всё одним скором за один проход: - L3 + graph + staging в одном стейте. Гейт сказал «нет» — текст исчез. -- `memory_dispatch_log` хранит только *решение* (saved_l3/l4/graph флаги), не текст — - переиграть решение задним числом нечем. -- Параметры гейта (EMA, rules.yaml) мутируют со временем, история решений невоспроизводима. -- L1-кольцо живёт в harness'е — серверная сторона про отклонённое не знает ничего. - -### [F1] L0 = `l0_journal` (append-only) - -- Таблица: `id, ts, event, source_msg_id, layer, user_id, text, status, decisions JSON`. -- Пишется **первым**, до любого гейта, best-effort (как dispatch_log — сбой не блокирует поток). -- `status`: `received` → `skipped` / `gated_out` / `saved_l3` / `staged` / `promoted_l4` / `replayed`. -- `decisions` JSON — след каждого гейта: `[{gate: "g0", verdict, score?, reason}]`. - -### [F2] Гейты — явные шаги, вердикт каждого → в L0-строку - -- **G0 входной**: transcript-guard (`_looks_like_dump`, уже живой) + session-дедуп. - Мусор → `status='skipped'`, в L0 остаётся (аудит-след), дальше не идёт. -- **G1 importance**: EMA (`shared/adaptive.py`) + rules engine (D1.9) — логика без изменений, - вердикт логируется. Проход → L3 (+graph node). -- **G2 promotion**: L3→L4 — min_weight + transcript-guard (уже в consolidate_episodes) - + idempotency; вердикт → L0. -- Staging (C1.11) остаётся «парковкой» между G1 и G2 (dream-маркеры — боковой путь). - -### [F2+ · 2026-09-04] ПЕРЕРАБОТКА по пайплайну Эли и Лили (a-memory-l0-l4-pipeline.md) — ПРИНИМАЕТСЯ КАК ОСНОВА - -> Их дизайн точнее моего черновика: главный недостающий инсайт — **G1 роутит по типу -> (инвариант vs событие), а не только по важности**. Ниже — слитая версия; пункт помечен [ЭЛИ] -> если из их дока, [MOЁ] если из v1-черновика, [NEW] — слияние. - -**Код-факты их диагноза — проверены 04.09, все реальны:** -- L2 `sessions`: сообщений нет (summary/state_deltas/topics only) — [ЭЛИ] ✓ -- `consolidate_staging` ключ `staging_{content[:30]}` — обрубок, та же болезнь что `ep_` — [ЭЛИ] ✓ -- `decay_rate` из policy используется ТОЛЬКО в forgetting_ritual (nightly), promotion игнорирует — [ЭЛИ] ✓ -- `memory_remember` пишет value в L4 И тот же текст в граф-узел — дублирование контента — [ЭЛИ] ✓ -- ⚠️ поправка: CLACK codec в проекте НЕ найден (ни a-memory, ни cowagent) — сжатие L0 = A3-extractive, не CLACK [MOЁ-проверка] -- ⚠️ поправка: `add_edge` «не вызывается нигде» — устарело: B1.3 graph_builder строчит рёбра (но extract_links не находит связей на наших текстах), A1.6 communities тоже; в проде 2 ребра потому что минеры не наполняют, а не потому что «таблица не подключена» [MOЁ-проверка] -- ⚠️ поправка: sentence-transformers 6.0.0 УСТАНОВЛЕН в venv (ленивая загрузка при первом embed) — минер #9 (semantic_overlap) возможен СЕЙЧАС; их «нужна настоящая модель» уже выполнено [MOЁ-проверка] - -**Слитая целевая линия (термины дока):** -``` -L0 RAW (l0_journal, append-only) — ЕДИНЫЙ приёмник, сортировочная станция - │ G0: transcript-guard + session-дедуп + strip_secrets (A1 🔴) - │ классификатор: тип сырья (tool | recall | message | import) + атомизация на клаузы (без LLM) - ├── тулы → триплеты query→tool→outcome → рёбра графа [ЭЛИ, NEW-тип потока] - ├── recall-дампы → co-retrieval статистика → ребро co_recalled (минер #7) [ЭЛИ = G-минер #7] - └── сообщения → дистиллятор (G1): - типизация (kind_for_text + A5-regex) + importance (EMA+rules) + канонический ключ - ├── инварианты (fact/decision/rule/instruction/commitment/goal/relationship; - │ low decay_rate / never_archive) → L4 core - │ конфликт со старым → rag/conflict.py → memory_conflicts + тег contradiction - │ (Pending Contradictions, НЕ молчаливый UPDATE) [ЭЛИ 🔑] - ├── события (observation/question/context; высокий decay_rate) → L3 эпизоды - └── всё с source_raw_id → провенанс до исходного сырья (drill-down C3) [ЭЛИ+MOЁ] -WIKI = L4.5 knowledge layer — НАМЕРЕННАЯ запись, НЕ дистилляция из L0 [ЭЛИ 🔑] - ├── [[fact:ключ]] linking с L4 (факт помнит wiki_id) — без дублирования контента - ├── ночью: провенанс-мосты wiki↔L4↔L0 (минер #5) - └── MOC = граф-хаб (A1.1 уже есть) -``` - -**Правила дистилляции (принимаются целиком):** -- атомизация на клаузы (союзы/точки/запятые перед «и/но/причём») — резать на утверждения [ЭЛИ] -- канонический ключ: семантический хедер через словари/синонимы («decision:граф_без_llm»), - повтор → UPDATE updated_at + recency-boost, НЕ дубль и НЕ INSERT OR REPLACE [ЭЛИ 🔑] -- L4 = store-семантика (последнее значение), L3 = лента событий, L0 = архив, wiki = намеренное знание [ЭЛИ] -- promotion уважает policy.decay_rate: инвариант (never_archive/low decay) vs событие [ЭЛИ 🔑] -- **одноразовая чистка ПЕРЕД минерами**: замусоренные JSON-узлы графа → вынести в L0 (иначе - минеры свяжут мусор с мусором) [ЭЛИ 🔑] - -**L0-разрастание (приняты их 5 механизмов, с поправками):** -1. ~~CLACK не найден~~ → **РЕШЕНО**: CLACK — концепт Лили (`cow/knowledge/concepts/clack.md`, статус концепт): lossless-формат, читаемый LLM **напрямую без распаковки**; «meta = decision vector» — мета-индекс (хэш/summary/ts/вес/теги/сущности) достаточен для решения без вызова тулов; родня — context-codec.md (оглавление = точка входа, словарное кодирование паттернов). → **Слои L0**: горячий = живые строки; тёплый = A3-extractive+zlib; **холодный = CLACK-архивы** (lossless, самодостаточные, drill-down жив без распаковки). Реализация формата — отдельная задача F (или H, по объёму). -2. дедуп по SHA-256 блока: повторный вывод команды хранится 1 раз, ссылки — уже в A1-объёме -3. три тира жизни: горячий 0–30д → тёплый 30–180д (сжатое) → холодный (>180д или дистилляция+N дней без recall → CLACK-архив, из L0 удаляется) [ЭЛИ 🔑] -4. дистилляция освобождает сырьё: вытащил факты → сырьё кандидат в холодный [ЭЛИ 🔑] -5. source_raw_id переживает сырьё (drill-down ведёт в архив) [ЭЛИ] -6. [МОЁ, дополняет] B5-защиты (min-остаток, стоп 80%) распространить и на L0-свип -7. [МОЁ, дополняет] цифры их оценки (2MB/день → 16MB/год со сжатием) — согласуются с ретеншеном 7–14д из v1; берём их схему 30/180 + свип, а не мой плоский 7–14д - -**Карта входов → один приёмник (принимается):** MCP-тулы / авто-хуки / консолидация / сырые -дампы — 4 параллельных потока сейчас; remember дублирует в граф; graph_add и agent-хуки -бесконтрольны. → Все входы идут через L0-приёмник, который различает «сырьё» (→L0) и -«дистиллят» (→L3/L4 с провенансом). remember перестаёт дублировать в граф (только провенанс-ссылка). - -**Влияние на уже записанные пункты:** -- A5 (regex-дистиллятор) — становится частью G1, а не отдельным тулом -- A4 (origin-канал) — расширяется до source_raw_id-citation (их провенанс шире) -- B1 (4-tier формализация) — их термины: L4=Semantic, procedural=Procedural, L3=Episodic -- C3 (drill-down) — их схема Persona→Scenario→Atom→Conversation реализуется через L0 -- E9/B9 session-close — извлечение preferences/experience на commit — их пункт 4 подтверждён -- G-минеры — их список 8+1 сигналов ПЕРЕКРЫВАЕТ мой G-черновик и детальнее (теги/токены/ - сущности/сессии/провенанс-мосты/маркеры led_to/co-retrieval/структурные инварианты + - эмбеддинг-слой); приоритет их плана: #1/#2/#4 сейчас → #5 → #7 (журнал recall-пар — - расширить audit_trail) → #9 (модель уже установлена!) → #3/#6 -- минер #7 требует ЖУРНАЛ co-retrieval пар — нового (audit_trail не ведёт пары) [ЭЛИ 🔑] - -### [F3] Replay — воспроизводимость решений - -- Инструмент повторного прогона гейтов по окну L0: `replay --since -7d --gate g1`. -- Сценарий: поменял порог/rules → переиграл неделю → staging получил отвергнутое ранее. -- Идемпотентность: повторный replay не дублирует (ключ = source_msg_id + gate + config-hash). - -### [F4] dispatch_log → view над L0 - -- Миграционно ничего не ломаем: старая таблица остаётся, новые записи может дублировать - или пере-вычисляться; цель — одна таблица телеметрии в итоге. - -### Открытые вопросы F - -- Ретеншен L0: предложение 7–14 дней + vacuum (объёмы маленькие, партиционирование не нужно). -- Полный текст vs обрезка в журнале: предложение — полный (WAL выдержит). -- Кто пишет в L0: только auto_save_text или все события (nightly, diff, dream)? - ---- - -## Phase G — само-прошивающийся граф без LLM (ПЕРЕРАБОТАНО v14: основа = минеры Эли/Лили) - -> Мой v1-черновик (co-occurrence/similar_to/follows_in_time) ПОГЛОЩЁН списком из -> `a-memory-graph-miners.md` — он шире (8 сигналов + эмбеддинг-слой) и привязан к -> существующей инфраструктуре. Источник: /cow/knowledge/analysis/a-memory-graph-miners.md - -**Фундамент (прежде минеров)**: рёбра пишут только builder'ы (B1.3 ночной, A1.6 communities, -MCP relates_to/causal) — реальных связей они не находят; recall-телеметрии пар НЕТ (нужен -журнал); эмбеддинги: hash-fallback в тестах, но sentence-transformers 6.0.0 в venv УСТАНОВЛЕН -(ленивая загрузка при первом embed) — минер #9 возможен сейчас, а не «после установки модели». - -**Минеры (детерминированные; каждое ребро тегирует источник `heuristic:*`):** - -| # | Сигнал | Инфраструктура | Приоритет (план Эли/Лили) | -|---|--------|----------------|---------------------------| -| 1 | Общие теги → `tagged` (вес = число общих) | epi_tags есть | сейчас | -| 2 | Общие редкие токены + синонимы → `topic_overlap` (Jaccard) | FTS5 + synonyms A3.1 | сейчас | -| 3 | Детерминированные сущности → `co_mentions` + канонизация (Лили/Lily/лисёныш) | словарь | фича | -| 4 | Сессионная близость → `same_session` | L2/L3 таймстампы | сейчас | -| 5 | Провенанс-мосты → `sourced_from` (эпизод→wiki→факт) | source_id/wiki_id есть | после фундамента | -| 6 | Маркеры результата → `led_to` heuristic («починила», «сломалось») | словарь маркеров | фича | -| 7 | Co-retrieval → `co_recalled` (вес = частота/N) | ⚠️ журнал recall-пар НОВЫЙ (расширить audit_trail) | после журнала | -| 8 | Структурные инварианты (co-citation, louvain-расширение, belief propagation) | louvain A1.6 | batch | -| 9 | Эмбеддинг-слой `semantic_overlap` (Jaccard MIB ≥0.7, top-k, вес 0.5–0.6) | модель в venv; O(n²) на 236 узлах ≈ 28k пар | после #1/#2/#4 | - -**Режимы**: инкрементальный (при записи узла: теги/сущности/синонимы) + ночной batch -(co-retrieval, co-citation, communities, belief propagation). Вес = доверие к источнику -(эвристика 0.3–0.6, ручные/LLM 0.8+). Откат: `DELETE WHERE edge_tags LIKE '%heuristic:%'` — -эвристика никогда не врёт навсегда (в духе Shadow Bin). - -**Инфраструктура из моего v1 (сохранена как рамка минеров):** -- Санитария: min-weight, decay только `heuristic:*`-рёбер, cap на узел, prune <0.05 — - ручные/LLM рёбра не трогаются (маркировка источника решает) -- Ночная фаза `graph_enrich` в `_nightly` = оркестратор batch-режима (backup_cron уже стреляет) -- Пре-шаг: одноразовая чистка (Эли) — JSON-узлы из графа в L0, иначе минеры свяжут мусор с - мусором (удалённые 04.09 дампы есть в бэкапах *.bak-pre-* — восстановить в L0 при вводе) - -**Честный потолок** (их формулировка): сходство ≠ связь; `semantic_overlap` без порога+top-k -шумит; каузальность — за маркерами #6. Эмбеддинг-минер попутно подсвечивает мусорные узлы -(аномальные векторы) — двойная польза с L0-чисткой. - -## Phase H — общие доработки (кандидат-лист, триаж после слияния) - -- **Stage 2** (slots, URI-ключи, inject/key consolidation, deprecated aliases) — - влить сюда или вести отдельно (решение при слиянии). -- **E6** negative memory (`/banthis`, anti_patterns) — [NEW] из E-триажа. -- **E12** savings ledger (per-call token accounting). -- **E8** sandboxing (Docker hardening, --read-only --cap-drop=ALL). -- **typed_export CLI** — модуль-сирота без производственной поверхности (аудит, кандидат #6). -- **Автоматизация tool-счётчиков** — 3 теста + доки правятся руками, уже дважды ловили рассинхрон. -- **9 admin-тулов вне tiers** — либо tier, либо формальный ops-only манифест. -- *(дополнить из наработок пользователя)* - ---- - -## Log слияния - -- 2026-09-04: v1 — черновик из brainstorm (мои предложения до получения наработок). -- 2026-09-04: v2 — разбор топ-11 идей конкурентов (пользователь). Вердикты: - -### Идеи конкурентов → вердикты (2026-09-04) - -| # | Идея | Статус | Вердикт | -|---|------|--------|---------| -| 1 | MCP behavior-аннотации (Basic Memory) | 🟢 новое — fork поддерживает `annotations: ToolAnnotations` (проверено: сигнатура `MCPServer.tool()`), сейчас не передаётся | Stage 2: разметка readOnly/destructive/idempotent при сведении тулов | -| 2 | Cross-encoder rerank вторым проходом | 🟡 частично — есть RRF + 1-hop graph-expand (B1.6) + kind_weights (E15) | Парк до №11: только если бенчмарк покажет провал RRF | -| 3 | /doctor-аудит качества (Letta) | 🟡 60% есть — `memory_diagnose` (E3) = здоровье системы; `rag/conflict.py` (ConflictResolver) пишет `memory_conflicts`, но РИДЕТЕЛЯ НЕТ (сирота из аудита) | Phase F: `memory_audit`-отчёт (противоречия/дубли/устаревшее) как ридер над существующей таблицей | -| 4 | Bi-temporal (Graphiti) | ✅ есть — A2.1 `e8a5d13`, `core_memory_temporal` + get_at_time/intervals | — | -| 5 | Provenance факт→эпизод (Graphiti) | ✅ есть — B1.4 metadata.parents + get_lineage + D1.6 memory_fact_blame | — | -| 6 | Triplet embeddings рёбер (Cognee) | 🟡 умно/дорого; граф почти пуст (2 ребра/122 узла) | Парк до результатов Phase G | -| 7 | Импорт чужих чатов (Basic Memory) | 🟢 новое | Phase F: `scripts/import_chat.py` — claude/chatgpt/memory.json → L0 → гейты → память (bootstrapping — второй сценарий конвейера после live-потока) | -| 8 | Entity linking (Mem0) | 🟢 новое | Phase G: нормализация имён + alias-таблица («Лисичка»=«кисонька»=«Lily») — precondition/4-й генератор для co-occurrence | -| 9 | Git/Markdown экспорт (Letta/BM) | 🟡 частично — memory_data, typed_export (сирота), wiki уже md на диске | Backlog-low (спорно при NaCl-шифровании); заодно дать surface typed_export | -| 10 | Sleep-time цикл (Letta) | 🟡 80% есть — `backup_cron` ЖИВОЙ в проде (journal: срабатывает, стреляет nightly-хуки обоих слоёв) | Вливается в Phase G: `graph_enrich` = новая фаза _nightly, не отдельный планировщик | -| 11 | Бенчмарки LoCoMo/LongMemEval (Mem0) | 🟢 новое | **Phase H №1** — eval-фреймворк: единственный способ доказать качество + приёмочный тест для F (до/после replay) | - -Приоритетный порядок добавок: **11 (eval) → 3 (memory_audit) → 7 (import_chat) → 8 (entity linking) → 1 (аннотации при Stage 2)**. - -- 2026-09-04: v3 — волна A от пользователя (механики записи и пайплайна). Вердикты: - -### Волна A — механики записи и пайплайна (пользователь) - -| # | Идея | Статус | Вердикт | -|---|------|--------|---------| -| A1 | SHA-256 дедуп + privacy-фильтр на записи (agentmemory) | 🟡 **механика есть, покрытие дырявое**: `strip_secrets` + `_DedupCache` SHA-256 TTL=300s (ровно 5-мин окно!) — но ТОЛЬКО в `memory_remember` (L4-путь); `auto_save_text` → L3 идёт БЕЗ фильтра и дедупа | **Phase F, G0-гейт**: вынести strip_secrets + дедуп из memory_remember в общий вход конвейера — секреты сейчас свободно текут в L3/L0. Это находка безопасности, приоритет | -| A2 | Near-duplicate advisory `similarTo` на save | 🟡 ConflictResolver (rag/conflict.py, BM25 smart_similarity) уже ловит на записи → `memory_conflicts` (ридера нет), advisory-возврата нет | **Phase F G1**: вернуть advisory в ответе save + писать memory_conflicts; ридер — memory_audit (идея №3); эмбеддинг-вариант — G2 similar_to | -| A3 | Synthetic compression без LLM | 🟡 `memory_compress` (D1.4) — для tool-output; reflections (D1.16) детерминированы, но поверхностны (счётчики) — смыслового сжатия нет | **Phase F**: extractive-сжатие (top-BM25 фразы + первая/последняя + decision-паттерны) как шаг суммаризации в конвейере/ночи — бесплатно, качество ниже LLM (осознанный потолок) | -| A4 | Write-time provenance (origin-канал user/agent/tool/import/shared) | 🟡 B1.4 parents + D1.6 blame покрывают факт→наблюдения для промоций; гранулярности канала нет (source: user_explicit/episode_promotion/... — без tool/import/shared) | **Phase F**: L0 даёт id наблюдений (citation по определению); + поле `channel` в metadata — S-effort | -| A5 | Zero-LLM regex ingestion фактов (MemoryPalace) | 🟢 новое — regex для LINKS есть (B1.3), для ФАКТОВ нет | **Phase F**: опциональный pre-classifier в G1 («запомни:», «я решил», «не делай» → высокий importance + guess memory_kind); заодно keyless-режим | -| A6 | Import чужих форматов (claude/chatgpt/memory-json, jsonl) | 🟢 = топ-№7 | **Phase F**: `scripts/import_chat.py`, форматы +jsonl; origin=import (через A4-channel) | -| A7 | Session crystals + lessons mining на импорте | 🟢 новое; строительные блоки есть: consolidation, cls_replay (D1.18), error_pattern (D1.8), E6 negative memory [NEW] | **Phase F (импорт) + ночная фаза**: кристалл = A3-сжатие сессии; lessons → E6 anti_patterns (наконец мотивация E6!) | -| A8 | MEMORY.md-бридж (двунаправленное файл-зеркало) | 🟢 новое; прецеденты: wiki=md на диске, session-start inject, у Люси drain-marker паттерн (ручной) | **Phase H**: `memory_bridge` — топ-факты → MEMORY.md (регенерация как MOC) + ingest секции ниже drain-маркера демоном (write-путь для агентов без MCP) | - -**Приоритет внутри волны**: A1 (безопасность — секреты в L3!) → A2 → A4 → A5 → A3 → A6/A7 → A8. -**Синергия**: A1+A2+A4+A5 садятся прямо в гейты F; A6+A7 — второй сценарий F (импорт); A3 — общий строительный блок (F-сжатие + A7-кристаллы); A7 даёт наконец продюсера для E6. - -- 2026-09-04: v4 — волна B (забывание, консолидация, жизненный цикл). Вердикты: - -### Волна B — забывание / консолидация / жизненный цикл (пользователь) - -| # | Идея | Статус | Вердикт | -|---|------|--------|---------| -| B1 | 4-tier Working→Episodic→Semantic→Procedural | 🟡 маппинг почти 1:1: L1 harness-ring→Working, L3→Episodic, L4→Semantic, procedural_memory (D2.5)→Procedural — но слои не связаны явной моделью переходов | **Phase F**: формализация = таблица переходов L0→L4 (VALID_TRANSITIONS B1.5 уже есть для отельных хопов) + declaration в доках; S-effort | -| B2 | Recall hygiene (superseded вне индексов, цепочка сохраняется) | 🟡 bi-temporal (A2.1) хранит цепочку; «superseded вне поиска» реализовано частично — FTS ищет по актуальным, но deleted/superseded флагов нет | **Phase F**: formalize — is_current-вью или status-колонка в core_memory (deleted→archived уже есть); проверить recall не возвращает закрытые интервалы | -| B3 | Decay по Эббингаузу | ✅/🟡 **ACT-R уже в ранкере** (D1.17): `actr_activation` в multi_source (×1.3 на оба источника, access_count из D1.19 was_useful) — НО: колонок access_count/last_accessed в core_memory НЕТ (частота считается на лету), и никакого автоматического eviction по decay нет | **Phase H**: сверить формулы (ACT-R -0.5d vs Эббингауз exp) — оставить ACT-R (теория базируется на той же кривой); добавить авто-эвикт stale как nightly-фазу после замера eval (№11) | -| B4 | TTL-флаг «забудь через N» | 🟡 `expires_at` колонка ЕСТЬ (A2.6-эпоха), в проде 1 строка; на туле `memory_remember` параметра НЕТ; cleaner'а нет | **Phase F**: `ttl_minutes` параметр на remember/staging + nightly-свип expired (в backup_cron-фазу) — S-effort, удобный volatile-контекст | -| B5 | Защиты TTL-cleaner (мин. остаток, стоп при 80% expired, cleaner_summary) | 🟢 новое; наш cleanup (memory_cleanup) дедупит/архивирует без защит; forget primitive — без барьеров | **Phase F**: правила конвейерного свипа: min-остаток на слой, стоп-порог, cleaner_summary в L0/аудит — protect-инварианты для B4-свипа | -| B6 | UPDATE > MERGE > CREATE + лимит сущностей с принудительным merge | 🟢 новое; разрастание реально: 136 фактов/122 узла растут, dedup только exact | **Phase G (после entity linking №8)**: лимит активных узлов на user → принудительный MERGE холодных/similar_to — эвристика размера, needs eval-цифры (№11) | -| B7 | Heat-арифметика merge: sum(heat)+1 | 🟢 новое — простой детерминированный инвариант | **Phase G**: константа для B6-merge (вес merged-узла), 1 строчка логики | -| B8 | `[PERSONA_UPDATE_REQUEST]` out-of-band сигнал | 🟢 новое; по духу = DREAM-маркеры (C1.12), но от консолидатора к профилю; persona в MUSE (D3.x MOVED) | **Парк/в MUSE**: запросы про эволюцию личности — вне ariel per user decision 01.09; если речь про agent-facts — переформулировать как DREAM-маркер | -| B9 | Session commit → async extraction | 🟡 session_end хук ЕСТЬ (C1.4, post_session_diff Hermes-only); extraction — только episode save, preferences/experience не извлекаются | **Phase F**: G2-фаза «session-close» на post_session_diff/context_threshold: вытащить preferences/опыты через A5-regex → staging; async бесплатно (демон) | -| B10 | Introspection engine 24ч (повторяющиеся темы → Core) | 🟡 reflections (D1.16) считают темы, но НЕ продвигают; promote есть только у scratchpad (D1.15) | **Phase F/H**: расширение nightly_reflection: recurring-topic ≥N → staging proposal (не прямой write — через review C1.11) — S-effort | - -**Приоритет внутри волны**: B4+B5 (TTL+защиты, S) → B2 (hygiene) → B1 (формализация) → B9 (session-close) → B10 → B3-эвикт (после №11) → B6/B7 (после G+eval). B8 — парк/MUSE. -**Ключевая находка**: ACT-R уже в ранкере, но у него нет частотной колонки — D1.19-фидбек даёт access_count только на чтении из таблицы recall_events (1 запись в проде). B3-«Эббингауз» = по сути «подкрутить ACT-R частоту», не новая формула. - -- 2026-09-04: v5 — волна C (хранение, структура, wiki). Вердикты: - -### Волна C — хранение / структура / wiki (пользователь) - -| # | Идея | Статус | Вердикт | -|---|------|--------|---------| -| C1 | Wiki-страница-«сцена» (Core Traits / Preferences / Implicit Signals / Core Narrative Trigger→Action→Result / Evolution Trajectory / Pending Contradictions) | 🟡 блоки есть: wiki-типы+skill (D2.1), typed kinds 13 шт (relationship/preference/decision...), bi-temporal, memory_conflicts (писатель есть); нет — шаблона сцены иtrajectory-секции | **Phase F (конвейер)**: сцена = шаблон страницы + генератор из typed-фактов (MOC-стиль), секция Pending Contradictions = ридер memory_conflicts (идея №3, memory_audit), Trajectory = читает core_memory_temporal; LLM-free; persona-эволюция НЕ вшиваем (D3.x→MUSE) — только факто-сцены | -| C2 | Двухслойное хранение: БД (доказательства) + Markdown (структура) | ✅/🟡 архитектурно уже так: wiki = md на диске (white-box), БД = индекс+доказательства; но core/episodes — БД-only | Верно как принцип; частично закрывается A8-бриджем (H) и сценами (C1). Новой работы нет, отметить в спеке как принцип | -| C3 | Drill-down цепочка Persona→Scenario→Atom→Conversation (node_id-ссылки от абстракции к raw) | 🟡 B1.4 parents + D1.6 blame дают links вверх; нет сквозной навигации вниз (факт → L0-наблюдения) и UI-траверса | **Phase F**: L0 даёт атомы (id наблюдений) → parents-цепочки удлиняются до L0; drill-down = выгрузка get_lineage + L0-записей (для начала — в memory_fact_blame добавить l0_ids); C11-viewer — consumer | -| C4 | Pinned + semantic slots поверх ACT-R | 🟢 новое; inject-блоки (S5) имеют kind/score, но pinned-набора нет | **Phase H**: `pinned` флаг/коллекция на core_memory → inject всегда вставляет первым (до relevant), бюджет резервирует slice; просто, полезно для identity/границ | -| C5 | Private/Public-гейтинг | 🟢 новое; visibility-колонок нет; D1.13 scopes — это user/agent-изоляция, не приватность контента | **Phase H**: `private` флаг на L4/L3 → exclude из recall_protocol/inject/wiki_search по умолчанию (override-параметр для владельца); NaCl-at-rest уже есть? — ПРОВЕРИТЬ (PyNaCl в venv, но шифрование в коде не найдено — отдельный вопрос) | -| C6 | Charter/steward-концепция слоёв («у слоя цель и владелец») | 🟢 новый ракурс; у нас контракты размыты по коду | **Спека F**: секция «Layer Charter» — таблица слой/цель/владелец/кто пишет/кто чистит (из F-гейтов это выйдет естественно); чисто документационный + discipline-маркеры в inject | -| C7 | Языковая адаптивность контента | 🟢 ничего нет (0 вхождений lang-логики); но контент и так пишется на языке пользователя агентом | **Парк**: LLM-free детект (cyrillic-ratio) дёшев, но сценарий неясен — перевод? хранение дубликатов? Вернуться если появится реальный б pain | -| C8 | Пространственная метафора wings/rooms/drawers | ✅/🟡 у нас уже: per-agent каталоги (cowagent/hermes/mimocode/eli + layers), branches (D1.11) = комнаты, stash (D1.12) = ящики | Формализовать названиями в спеке-чартере (C6); новой механики не нужно — YAGNI | -| C9 | AAAK-диалект lossy-компрессии (−80% токенов) | 🟢 новое; есть memory_compress (D1.4) для tool-output, но диалекта плотного хранения/экспорта нет | **Phase H**: опциональный компакт-рендер фактов (одна строка: `PROJ: x \| JOR→y \| ★4`) для recall-сканирования при большом бюджете-дефиците; после №11 (мерить потерю качества) | -| C10 | Экспорт в человекочитаемый Markdown + git | 🟡 = топ-№9 | Backlog-low (дубль), но typed_export surface + `memory_data` md-рендер — в H если A8-бридж пойдёт хорошо | -| C11 | Session Replay viewer (таймлайн prompt→tool→result) | 🟡 dashboard ЕСТЬ (stats/facts/episodes/audit endpoints) — replay-таймлайна нет; L0 (F1) даст данные, dispatch_log уже даёт половину | **Phase H** (после F1): `/replay?session=` на dashboard — таймлайн из L0 (served из journal), play/pause = фильтр по окну; потребляет C3-drill-down | - -**Приоритет внутри волны**: C1 (сцены — видимый результат F) → C3 (drill-down к L0) → C2-принцип в спеку → C4/C5 (H, простые флаги) → C6 (чартер) → C11 (H, после F1) → C9/C10 → C7/C8 парк/бесплатно. -**Вопрос для пользователя**: NaCl/шифрование at-rest — в коде не найдено (PyNaCl только в зависимостях). Есть ли где-то включённое шифрование, или это была идея из ранних планов? (влияет на C5 и C10-верdictы) - -**Решение вопроса про шифрование (04.09, поиск)**: envelope-encrypt ЕСТЬ (`features/secrets.py`: master key в .env, encrypt_json/decrypt_json) — но покрывает ТОЛЬКО saga-state и auth-store. Основные данные (memory.db, wiki-файлы, L1-кольца) НЕ зашифрованы, при этом «envelope encryption» заявлен в pyproject-описании. → C10 (md-экспорт) перестаёт быть «спорным»: экспорт не слабее текущего состояния. Отдельный хвост: либо шифровать БД-файлы (SQLCipher/приложение-level), либо убрать claim из описания. - -- 2026-09-04: v6 — волна D (поиск и ретрив). Вердикты: - -### Волна D — поиск и ретрив (пользователь) - -| # | Идея | Статус | Вердикт | -|---|------|--------|---------| -| D1 | Cross-encoder rerank вторым проходом | 🟡 = топ-№2 (дубль) | Парк до №11-eval | -| D2 | Tiered loading abstract/overview/details (OpenViking) | 🟢 новое; у wiki есть progressive disclosure (list→read, D2.1), но 3-уровневых представлений нет; graph nodes и core-факты — один уровень | **Phase H/G**: `.abstract` (~100 tok) генерировать при записи (A3-сжатие), overview = сцена/первая секция; дёшево для wiki (frontmatter-поле), сложнее для фактов; измерить экономию на №11 | -| D3 | Directory-recursive retrieval (спуск по директориям) | 🟢 ядро есть: MOC-хабы (A1.1) + wiki_query BFS (A1.4) + graph-expand (B1.6) — но retrieval НЕ идёт «сперва директория, потом контент» | **Phase H**: retrieval-роутер: query → сначала MOC/тематические узлы (cheap), потом контент-поиск внутри победившей темы; гибрид «навигация→контент» вместо плоского скоринга | -| D4 | Observable retrieval (журнал траектории поиска) | 🟡 recall возвращает source-теги per-hit («fts5»/«mib»/«graph»/«graph_expand»), но полной траектории (что пересекли, что отбросили) нет; memory_diagnose — здоровье, не трейс | **Phase H (после F1)**: ретрив-трейс в L0 (событие retrieval: план → источники → отброшенное) → diagnose показывает «почему такой результат»; дёшево после F | -| D5 | Cache-friendly инъекция (prepend/append, вырезание старых тегов) | 🟡 E9 `` + stable-first ordering УЖЕ ЕСТЬ; чистки старых `` из истории нет (harness-side), prepend/append-позиционирование — harness contract | **Phase F (spекa инъекции)**: зафиксировать контракт: volatile-блоки в хвост последнего user-msg, стабильные в system; чистка старых тегов — задача harness-адаптеров (cow/Hermes/MiMo), не сервера | -| D6 | Char-бюджеты + таймауты с graceful-деградацией | 🟡 E2-breaker + hash-fallback УЖЕ ПОЛНЫЙ стек (3 фейла → 30s → fallback); char-бюджетов per-memory нет (только токен-бюджет блоков S5 + maxLines в compress) | **Phase F**: maxChars per memory при инъекции (обрезка хвоста) — S; таймауты покрыты | -| D7 | RRF k=60 + session diversification ≤3/сессию | 🟡 k=60 УЖЕ дефолт (`rag/search.py:140`); диверсификации нет | **Phase H**: session-diversity cap в rerank (после №11 — влияет на качество, мерить); k=60 совпадает | -| D8 | Токен-бюджет инъекции default 2000 | ✅ ЕСТЬ ТОЧНО ТАК: `budget: int = 2000` в recall_protocol и inject (S5) | — (совпадение 1:1) | -| D9 | Triplet embeddings рёбер | 🟡 = топ-№6 (дубль) | Парк до G-результатов | -| D10 | Entity linking | 🟢 = топ-№8 (дубль) | Phase G (уже записано) | -| D11 | Recency `exp(−λΔt)` + amplification `(1+αw)` | 🟡 ACT-R покрывает (base-level = −d·ln(t) + частота); MemoryMuse-формула проще/линейнее | **Phase H**: не заменять, а ПРОГНАТЬ обе на №11-данных; ACT-R теоретически строже (частота+интервалы), Mnemosyne-вариант — baseline для сравнения | -| D12 | BFS по графу знаний как третий поток | 🟡 graph УЖЕ пятый источник MultiSourceRAG (`_from_graph`, include_graph=True) — но реализован SQL-LIKE примитивом, не BFS по связям | **Phase G**: upgrade `_from_graph` до entity-resolve (№8 linking) → BFS от узла (A1.4-CTE уже есть) → соседние факты; авто-включится в RRF | - -**Приоритет внутри волны**: D6 (maxChars, S) → D5-спека (контракт инъекции для harness'ов) → D4 (трейс, после F1) → D3 (директории) → D2 (tiered) → D7 (диверсификация) → D11 (прогнать формулы) → D1/D9 парк. -**Ключевая находка**: половина волны уже в стапе — k=60, budget=2000 (совпадение 1:1 с agentmemory), breaker+fallback, graph-источник. Реальные пробелы: maxChars-обрезка, retrieval-трейс, директорий-спуск, BFS-upgrade граф-потока. - -- 2026-09-04: v7 — волна E (продукт, интеграции, инфраструктура). Вердикты: - -### Волна E — продукт / интеграции / инфраструктура (пользователь) - -| # | Идея | Статус | Вердикт | -|---|------|--------|---------| -| E1 | MCP behavior-аннотации | 🟡 дубль топ-№1 | Stage 2 (разметка при сведении) | -| E2 | /doctor-аудит качества | 🟡 дубль топ-№3; у нас memory_diagnose/heal (E3) + сирота memory_conflicts | Phase F: расширить memory_diagnose контент-чеками (конфликты-ридер, дубли, устаревшее) — именно «расширить», как пользователь и пишет | -| E3 | Sleep-time планировщик | 🟡 дубль топ-№10 — backup_cron жив и стреляет nightly | Закрыто; G добавляет graph_enrich-фазу | -| E4 | Agent Loadout (ассеты + ACL private/team/restricted) | 🟢 новое; у нас D1.13 scopes (user/agent), branches (D1.11) — но ACL-гранулярности нет | **Парк**: 5 инстансов изолированы файлами, real multi-tenant не нужен; C5-private-флаг закрывает ближайший сценарий. Вернуться при появлении shared-инстанса | -| E5 | Cold-start импорт | 🟡 дубль A6/№7 | Phase F: import_chat | -| E6 | Авто-извлечение Skill из завершённых задач (`mem:create-skill`) | 🟡 D2.2 promote_episodes УЖЕ умеет промоцию задним числом (episode_ids → skill page, idempotent, merge при коллизии); auto_promote_fresh ночью берёт только dream_skill-эпизоды | **Phase F/H**: ретроспективный mine — nightly расширяет окно: все эпизоды недели → A5-regex «задача завершена/решил/работает» → promote_candidates → staging (не прямой write); переиспользует promote_episodes 1:1 | -| E7 | LLM-Proxy паттерн (baseURL-подмена) | 🟢 большое; транспорт уже закрыт 3 интеграциями (MCP/плагин/hooks) + MiMoCode messages.transform | **Парк**: revisit только для harness без расширяемости; конфликтует с no-LLM принципом ariel (прокси обязан пропускать чат через себя) | -| E8 | WhisperGate: nano-модель решает инициативу | 🟢 новое, но ariel принципиально keyless/no-LLM (B1.6 verdict) | **Парк/MUSE**: дешёвый гейт инициативы — задача harness-демона (у cowagent уже есть importance-гейт диспетчера); ariel остаётся без LLM | -| E9 | Scheduler-циклы как продукт (интервалы 60с/1ч/3ч/24ч) | 🟡 backup_cron (backup+nightly) и daemon poll-loop есть; единой config-таблицы циклов нет; gap-reports (C1.10) есть, inactivity check-in нет | **Phase F**: `cycles` в конфиге daemon: {gap_check: 1h, staging_reminder: 60s?, inactivity: 3h, dream: 24h} — единая таблица вместо россыпи констант; daemon уже циклится | -| E10 | `wake_up`-тул (атомарный подъём Core в контекст) | 🟡 есть по сути: memory_context_inject (бюджетные блоки) + memory_recap (recovery pack) | Закрыто существующими тулами; в Stage 2 — сведение имён (wake_up как alias, если захочется) | -| E11 | `mine`-индексация кодовой базы | 🟡 project-примитив УЖЕ майнит: `project(mine)` → graphify extract → project_symbols (5000 cap) + docs_to_wiki (A1.7) для доков + wiki→rag (A2.3) | Закрыто почти всё; хвост: graphify — внешний бинарник (нет в стапе → graceful skip уже есть); опционально скрипт-обёртка `mine ./project` = project(mine) + docs_to_wiki --source. H, S | -| E12 | Бенчмарки (LoCoMo/LongMemEval-S/BEAM, R@5) | 🟢 дубль топ-№11 | **Phase H №1** (не меняется) | -| E13 | `ov`-CLI: ls/tree/find/grep по памяти | 🟡 typed_export.py — CLI-модуль-сирота (audit находка); query DSL (D1.7) даёт фильтры, MOC/tree даёт иерархию | **Phase H**: `ariel-cli` поверх query DSL + wiki_list (ls/tree/find/grep) — заодно даёт surface typed_export-сироте; S-M | -| E14 | Health/livez/ready + доктор | 🟡 /health, /ready, /alive УЖЕ есть (middlewares.py:14 + app.py:38)! доктор = memory_diagnose/heal | Закрыто на 90%; хвост: /ready проверять alembic-head (сейчас, видимо, только liveness) — 1 строка | -| E15 | Mermaid-канвас (node_id в контексте) | 🟢 новое; epi_nodes/edges → mermaid-рендер тривиален; потребители: сцены C1, MOC, dashboard | **Phase H (после G-наполнения)**: `graph_mermaid` рендер (wiki-страница + inject-блок опционально); до наполнения графа — пустой канвас | -| E16 | Бенчмарк-цифры «до/после» (48→76%, −61% токенов) | 🟡 дубль №11 + формат | Phase H №1: формат отчёта принять (quality + token-savings), reproduсить на своих данных | - -**Приоритет внутри волны**: E12/E16 (eval — главный) → E2 (doctor-расширение) → E6 (ретро-skill mine) → E9 (cycles-таблица) → E13 (CLI) → E14 (readyz-строка) → E15 (mermaid после G). E4/E7/E8 — парк (по границам продукта), E5/E1/E3/E10 — закрыто дублями. -**Итог волны**: из 16 пунктов 6 — дубли уже записанного, 4 — «уже сделано» (E3/E10/E11/E14), 2 парка по границам продукта (E7/E8), реальные новинки: E6-ретро-майн, E9-cycles, E13-CLI, E15-mermaid — все S-класса. - -- 2026-09-04: v8 — волна F конкурентов (уроки «чего не делать») + сводка паттернов. **Итог разборного док-документа.** - NB: буквы волн конкурентов (A–F) — разметка входного дока; Phase F/G/H — выходной план. Не путать. - -### Уроки «чего не делать» (волна F конкурентов) — проверка нашего дизайна - -| # | Урок | Наш статус | Вердикт | -|---|------|-----------|---------| -| F1 | LLM-capture — не единственный путь записи | ✅ подтверждает: ручные тулы + примитивы + rules + A5-regex (план) — три не-LLM пути уже в дизайне | Действуем как есть | -| F2 | Не хранить plaintext — «у нас NaCl» | ❌ **НЕ ПОДТВЕРЖДАЕТСЯ** (находка v6): envelope-encrypt покрывает только saga-state/auth-store; memory.db, wiki, L1 — plaintext, при этом «envelope encryption» заявлен в pyproject-описании | **Хвост-действие**: либо шифровать данные (SQLCipher на БД / app-level для L1/wiki), либо убрать claim из описания пакета. Слушать решение пользователя | -| F3 | Миграции first-class с первого дня | ✅ alembic + staged mutations (C1.11) + bi-temporal + wiki-md; сегодняшние a25/a26 — живое доказательство урока (две таблицы были вне цепочки — починены в тот же день) | Действуем как есть | -| F4 | LLM write в файлы требует лимитов и нормализаторов (TencentDB баг #88 — стёртые scene_blocks) | ✅ реализовано: safe_resolve (E7), lint+secrets-scan на add/update, cap имени 80+hash (general-3 фикс), skill 4KB cap, update() переписывает ровно одну страницу (нет bulk-wipe пути) | Действуем как есть; при сценах C1 — генератор пишет секциями, не whole-file | -| F5 | Не плодить тулы без тиров | ✅ решено: ARIEL_EXPOSE tiers, 65→65 тулов, counter-тесты; исключение — 8 admin-тулов вне tiers (документировано, ops-surface) | Действуем как есть | -| F6 | Не зависеть от внешнего бинарника (agentmemory+iii-engine ломается на апдейтах) | 🟡 **одно исключение**: project(mine) зовёт внешний `graphify` CLI — но graceful (try/except → «skipped»), без version-пиннинга, деградация тихая | Отметить в E11-вердикте: при развити mine — либо vendor pure-python экстрактор, либо оставить graceful-skip как контракт | -| F7 | Не гнаться за «self-evolving» маркетингом | ✅ подтверждает: настоящая эволюция = staged mutations + consolidation + evolve-примитив (persona-эволюция — MUSE) | Действуем как есть | - -### 10 повторяющихся паттернов (2+ конкурентов) → карта покрытия черновиком - -| Паттерн | Покрытие | -|---------|----------| -| 1. Tiered/abstract-слои | F-конвейер (гейты) + D2-tiered + C1-сцены — записано | -| 2. Запись с дедупом + privacy | A1 → G0-гейт (🔴 находка: фильтр есть только на L4-пути) | -| 3. Cache-friendly инъекция | D5 → E9 уже делает stable-first + cache:break; F-спека фиксирует контракт | -| 4. Провенанс/трассируемость | B1.4 + D1.6 + L0-citation (F) + C3-drill-down + D4-трейс | -| 5. Консолидация «во сне» с heat/decay | ACT-R (B3) + graph_enrich (G) + E9-cycles; «Эббингауз» закрыт ACT-R | -| 6. Не-LLM удешевление | A3 (extractive) + A5 (regex) + принцип no-LLM ariel | -| 7. Пространственная навигация | C8 (уже есть: MOC/wiki/branches) + D3 (MOC-first retrieval) | -| 8. Явные TTL/volatile | B4 (expires_at есть, surface нет) + B5 (защиты свипа) | -| 9. Session-commit → извлечение | B9 → F session-close фаза | -| 10. Автономия через дешёвый gate | E8 — парк/MUSE: гейт инициативы — harness-демон, ariel остаётся keyless | - -**Вывод по документу конкурентов**: 47 пунктов разобрано (11 топ + A1–A8 + B1–B10 + C1–C11 + D1–D12 + E1–E16 + F1–F7). Уникальных новинок, меняющих план: **A1-безопасность (единственный 🔴)**, import_chat (F), entity linking (G), eval-фреймворк (H №1), memory_audit-ридер (F). Остальное — подтверждения дизайна, дубли или парк. Наша архитектура (тиры, no-LLM ядро, миграции, staged mutations) пережила разбор без контрпримеров — единственный провал F2 (plaintext). - -- 2026-09-04: v9 — детальная секция TencentDB-Agent-Memory (пользователь). Все 12 «что взять» уже записаны в волнах B/C/D/E (маппинг: 1→C1, 2–3→B6/B7, 4→B8, 5→C3, 6→D5, 7→D6, 8→C7, 9→B5, 10→E15, 11→E4, 12→E7). Микро-факты из детальных механик — 4 добавки: - -### TencentDB детально — микро-добавки - -1. **C1-сцена: cap ≤1500 симв** — конкретная цифра размера сцены (наш skill-cap 4KB; для сцен берём 1500 как soft-target генератора, whole-file не пишет никто после бага #88). -2. **jieba-токенизация (CJK)** — наш FTS5 на default unicode61 (RU/EN ок, проверено продом). CJK-поддержка — только если появится пользователь на китайском/японском; парк. -3. **Circuit breaker на harness-адаптерах** — у их Hermes-плагина есть (5 fails → 60s pause); наш hermes-плагин — НЕТ (проверено: 0 вхождений), CLI-падения только логируются. Внутри ariel breaker стоит (E2), но внешний защите нечего ломать каждый turn при лежащем CLI. → **F-спека интеграции**: 5-fails→60s паттерн в harness-адаптеры (S, микроскопия, но дешёвая). -4. **Их 8 слабостей = наш дифференциатор** (записать как позиционирование): LLM-обязателен vs наши 3 не-LLM пути записи; «86 scene blocks пассивным приёмником» (их личный опыт) vs наши примитивы+тулы+rules; plaintext vs (починить!) F2; вендор-лок vs SQLite; миграция-TODO vs alembic-first; баги LLM-файлов vs lint+лимиты; нет forget/dream/evolve примитивов, графа, bi-temporal, MCP-first — всё это у нас уже built-in. Self-reported цифры (PersonaMem 48→76%, −61% токенов, WideSearch 33→50%) — референс-формат для №11. - -**Проверка гипотезы «может что-то уже было»**: да — эта секция подтверждает волны B/C/D/E почти 1:1 (их «взять» = наш черновик). Единственные ~4 микро-добавки выше. - -- 2026-09-04: v10 — детальная секция agentmemory (rohitg00). Маппинг «взять»-пунктов на черновик: - 1 (4-tier + synthetic) → B1 + A3 · 2 (dedup+privacy) → A1 🔴 · 3 (recall hygiene) → B2 · - 4 (Replay viewer) → C11 · 5 (similarTo) → A2 · 6 (provenance) → A4 · 7 (MEMORY.md-бридж) → A8 · - 8 (RRF k=60/diversification/бюджет 2000) → D7/D8 (k и бюджет совпали 1:1) · 9 (LongMemEval) → топ-№11. - **Все 9 — уже записаны.** Слабости их = наш дифференциатор (внешний ii-engine с пином vs self-contained; - 54 тула без тиров vs ARIEL_EXPOSE; plaintext; LLM-фичи требуют провайдера; state store непрозрачен; - заточен под кодинг-агентов — не под личность). - Новых механик в детальном описании — 3 микро-добавки: - -### agentmemory детально — микро-добавки - -1. **Keyless = только BM25** — у нас это режим по умолчанию для части установок (hash-fallback embeddings), но БМ25-«всегда включён» формулировка совпадает: search_fts5 живёт независимо от векторной ветки. Подтверждение дизайна, новой работы нет. -2. **Сессийные summary на Stop/SessionEnd** — у нас `session_ended`-хук ЕСТЬ (user_hooks._session_ended + post_session_diff), но их Stop-триггер (после каждого завершённого turn-батча) тоньше нашего session-end; совпадает с B9 session-close фазой → одна и та же фаза F покрывает. -3. **Skill-навыки 17 шт (9 invocable) vs наш wiki_type=skill** — поверхность та же, но у них skills ИНКРЕМЕНТНЫ: их сейчас 0 в проде-графе (wiki_index: skill=0). D2.2-конвейер есть, контента нет — ретро-mine (E6) это починит; + вариант «invocable-навык» (агент дёргает skill как команду) — это SKILL.md-конвенция хоста (MiMoCode/Claude Code), не сервера — harness-side, не наша фича. -4. **Real-time viewer :3113 / WS** — их realtime-порт не нужен: наш dashboard-поллинг + C11-replay закрывают сценарий отладки; WS-стриминг — парк (оверхед без потребителей). - -**Резюме**: agentmemory — второй подряд конкурент, чей «взять»-лист 1:1 повторяет уже записанный черновик (волна A и была их пайплайном). Подтверждение: A1-находка (privacy только на L4) стала 🔴 не зря — у них это первая стадия пайплайна. - -- 2026-09-04: v11 — детальная секция OpenViking (ByteDance). Маппинг: 1 (L0/L1/L2-tiered) → D2 · 2 (directory-recursive) → D3 · 3 (observable retrieval) → D4 · 4 (session commit extraction) → B9 · 5 (viking:// namespaces) → **Stage 2 URI-keys** (audit-файл #19: «discuss WITH the tool-surface redesign» — прямое совпадение замысла). Все 5 — записаны. - Новых механик в детальном описании — 2 добавки + 1 усиление: - -### OpenViking детально — микро-добавки - -1. **Каталоги тоже несут .abstract/.overview** (не только записи) — усиление D2: в наших терминах MOC-хабы (A1.1) уже являются «абстрактом каталога»; добавить overview-секцию в MOC (список подтем + 1-строчный summary каждой) — это делает D3-директорий-спуск дешёвым (решение «куда спускаться» по ~100-токенному MOC, без чтения страниц). → дополнение к D3-вердикту. -2. **`peers/`-неймспейс (межагентная память)** — у нас D1.13 scopes (user/agent) + branches, но «память О ДРУГИХ агентах» и shared-подпространство не моделировались; hivemind-сценарий пока вне продукта → **парк/Stage 2**: при URI-redesign предусмотреть namespace-схему, допускающую peers/ (зарезервировать, не строить). -3. **Их цифры LoCoMo 80–83% vs 24–57% нативных** — референс-цель для №11-eval: наш baseline прогнать против тех же датасетов; если RRF+inject дают <60% — D3/D2-апгрейды приоритизируются цифрами, а не интуицией. - -**Резюме**: OpenViking — третий конкурент, чей «взять»-лист покрыт черновиком (D2/D3/D4/B9 + Stage 2 URI). AGPLv3 = код не берём, только идеи — что и делаем. Их бенчмарк-цифры усиливают №11: теперь есть публичные ориентиры для сравнения. - -- 2026-09-04: v12 — детальная секция MemoryPalace. Маппинг: 1 (AAAK) → C9 · 2 (mine) → E11 · 3 (wake_up) → E10 · 4 (ttl) → B4 · 5 (auto-save N обменов+pre-compact) → УЖЕ ЕСТЬ (daemon auto-save по threshold + context_threshold re-arm + session.compacted wipe — KPI-подобные триггеры совпали) · 6 (wings/rooms/drawers) → C8. - Все 6 — записаны. Новых механик в детальном описании — 3 микро-добавки: - -### MemoryPalace детально — микро-добавки - -1. **hall_keywords → маршрутизация записи по «залам»** — детерминированный keyword-роутинг направления записи (не поиска!) в topic-wing. У нас steering (D1.3) роутит ИНТЕНТЫ поиска; направление записи решает importance-gate, но не тему. → **Phase F (G1-опция)**: topic-классификация записи (A5-regex словарь тем) → wiki-тип/epi_tags-предзаполнение; дёшево, снижает долю «unsorted»-фактов. S. -2. **Tunnels (кросс-wing связи)** — у branches (D1.11) связей между скоупами НЕТ сознательно (изоляция полная). Но сценарий «факт из одного проекта ссылается на общий» реален (shared ssot у Люси). → **Парк/Stage 2**: при URI-redesign зарезервировать cross-namespace edge (по аналогии с peers/ из OpenViking — оба в один пункт Stage 2). -3. **Их ID-схема `drawer_{wing}_{room}_{hash}`** — подтверждение нашего Stage 2 URI-дизайна: человекочитаемый структурный ключ с хеш-суффиксом — ровно то, что планируем для wiki-путей (safe_title + hash уже в update()-фиксе general-3). Ничего не делать, чужой опыт сходится с нашим. -4. **Их LongMemEval R@5 96.6%/96.4%** — самая высокая публичная планка из всех разобранных (agentmemory 95.2, OpenViking LoCoMo 80–83). №11-цель: сначала догнать 95+ на LongMemEval-S, потом сравнивать с их формой. - -**Резюме**: MemoryPalace — четвёртый подряд конкурент с «взять»-листом внутри черновика. Их «self-evolving» = скоринг+чистка (F7-урок подтверждён их же кодом: evolve() без decay/shadow-bin/консолидации — беднее нашего forget-примитива). Слабая модель эмбеддингов + отсутствие reranking/RRF — ещё один аргумент, что наш search-стек (RRF+5 источников+ACT-R) впереди; №11 покажет цифрами. - -- 2026-09-04: v13 — детальная секция MemoryMuse. Маппинг «взять»: 1 (pinned+slots) → C4 · 2 (private journal) → C5 · 3 (WhisperGate) → E8 · 4 (introspection 24ч) → B10 · 5 (recency-формула) → D11 · 6 (charter) → C6 · 7 (scheduler-циклы) → E9 · 8 (soft-delete) → Shadow Bin (уже есть) · 9 (не брать стек) — согласен. - Все 9 — записаны. Новых механик в детальном описании — 3 микро-добавки: - -### MemoryMuse детально — микро-добавки - -1. **max_entries на слой (default 20) + order** — у inject-блоков бюджет общий (2000), но per-слой cap отсутствует: «важные» блоки теоретически могут вытеснить все остальные. → **Phase F (спека инъекции)**: per-kind cap в build_inject_blocks (например important ≤N, gap ≤M) — дёшево, предотвращает монополизацию бюджета одним источником. -2. **inner_layer (inner monologue) — приватный слой вне семантики** — у нас ближайший аналог scratchpad (D1.15), но он УЧАСТВУЕТ в inject (kind=scratchpad, score 0.85). Идея «рабочие заметки, которые не ищутся, а только инжектятся/промоутятся» — флаг на scratchpad-записях (private: не попадает в search, только в inject и promote). → **Phase H** вместе с C5 (один флаг-механизм на оба случая). -3. **Порядок секций промпта (Cortex ВЫШЕ conversation и semantic recall)** — у нас порядок блоков в build_inject_blocks фиксируется списком append'ов; ранжирование «due_reminders > recall» осознанно не моделировано (reminders-слоя нет вообще). Не брать как есть (presence-продукт), но F-спека инъекции должна зафиксировать ЯВНЫЙ порядок kind'ов (сейчас он де-факто в порядке кода) — конфигурируемый словарь order. → **Phase F**, часть D5-контракта. -4. **Их Mnemosyne night-фазы vs наш _nightly** — у нас уже 6 фаз (cls_replay, graph_build, wiki_graph_build, skill_promotion, reflection, skill_reinforce); их decay/strengthen/orphan-sweep/re-embedding/cap-рёбер — это ровно Graph-санитария Phase G (уже записана) + B3-эвикт. Подтверждение: наш nightly — правильное место, структура фаз совпадает. -5. **8 циклов автономии при 3 LLM-классах (nano/mini/gpt-4.1)** — экономика подтверждает E8-вердикт: гейт инициативы (WhisperGate) — это harness-задача с nano-моделью, ariel остаётся keyless. Патент-pending + «license subject to change» — ещё одна причина не заимствовать механики напрямую (только идеи, как с AGPL у OpenViking). - -**Резюме**: MemoryMuse — пятый подряд конкурент с «взять»-листом внутри черновика. Ценное из деталей: per-kind капы и явный порядок inject-блоков (усиление F-спеки инъекции), private-флаг объединяющий C5+scratchpad. Стек (Mongo+Qdrant+Memgraph+docker) — антипример local-first. -- 2026-09-04: v14 — ПАЙПЛАЙН ЭЛИ/ЛИЛИ (a-memory-l0-l4-pipeline.md + a-memory-graph-miners.md, /cow/knowledge/analysis/) принят как ОСНОВА Phase F и Phase G. Мой v1 поглощён. Диагноз кода проверен 04.09 — все дыры реальны (L2 без сообщений, staging_-обрубки, decay_rate игнорируется промоцией, remember дублирует в граф, 4 параллельных входа). Поправки к их доку: CLACK не найден (сжатие = A3+zlib), add_edge «не вызывается нигде» устарело (B1.3/A1.6 пишут, минеры не наполняют), sentence-transformers 6.0.0 уже в venv (минер #9 возможен сейчас). Phase F = их конвейер + мои гейты/журнал/replay; Phase G = их 8+1 минеров + моя санитария/graph_enrich-оркестратор. Ключевые новые обязательства: канонические ключи (не обрубки), kind-роутинг инвариант/событие в G1, противоречия → memory_conflicts вместо молчаливого UPDATE, wiki = L4.5 [[fact:]]-linking без дублирования, L0-тиры жизни 30/180 + дистилляция освобождает сырьё, журнал co-retrieval пар (новый), пре-чистка JSON-узлов перед минерами. - -## Sequencing (user decision 2026-09-04) - -**Волновой порядок**: F-фундамент (L0-журнал, гейты, канонические ключи, privacy-стадия) → G-минеры (граф) → H (eval, CLI, доки) → **Stage 2 в конце** (slots/URI/аннотации — поверх устоявшейся поверхности). Не пересекать волны. - -**Открытое исследование (запущено 04.09)**: добить конкурентов (Letta, Basic Memory, Cognee, Mem0, Zep/Graphiti) + научные статьи (VikingMem, A-MEM, MemGPT/Letta, HippoRAG, MemoryBank, Generative Agents, LongMemEval) — слить находки в draft до планирования. - -- 2026-09-04: v16 — исследование конкурентов (general-4, web): Letta, Basic Memory, Cognee, Mem0, Zep/Graphiti. Полный отчёт — в notes сессии. Извлечения: - -### Letta (MemGPT) — добавки - -1. **Memory blocks с character limit** — у L4-гейта нет size-бюджета: append-only без compaction-давления. → **F 🔑**: per-блок лимит символов/токенов; nightly consolidation должна «упаковываться под бюджет» (compact-to-budget с эвиктом низкого ACT-R), не только дедупить. -2. **Sleep-time compute на idle/heartbeat** (arXiv 2504.13171 подтверждает) — наш nightly идёт по cron; добавить «dream on idle» (демон видит простой) — дёшево. → F/E9-cycles. -3. **Shared read-only blocks** (один block_id у нескольких агентов) → F: shared-маунт как first-class (ближний к A8-бриджу и peers/). -4. **MemFS: память под git** — wiki-L4.5 уже md-файлы: git-journal бесплатно даёт историю маркдауна дешевле аудит-лога. → H (усиливает C10). -5. `/doctor` + `/palace` viewer — подтверждение H-кандидатов (memory_audit, C11). - -### Basic Memory — добавки - -1. **Observations/Relations грамматика** `- [category] fact #tag (context)` / `- relates_to [[X]]` — парсимый формат факта БЕЗ LLM. → **F 🔑**: микро-синтаксис для L0-дистиллятора (kind/category/tags из скобок — A5-regex стандартизируется), синоним-канонические ключи живут в `[...]`. -2. **`memory://` URL-навигация** (depth-limited traversal как тул) → **G**: агент ходит по [[fact:]]-ссылкам (`memory://fact/`), а не только flat-search. Потребитель C3-drill-down. -3. **Schema infer/validate/diff** — дрейф онтологии wiki-тегов виден детерминированно. → H. -4. **Rerank fail-fast, disabled by default** (cross-encoder jina-tiny-en, без молчаливого фоллбека) → H: шаблон для нашего D1-кандидата (env-gated, никогда не silent). -5. **doctor file↔DB reconciliation** — индекс-vs-источник (наш wiki_index vs md-файлы) — реальный фейл-режим, который мы не проверяем. → F-diagnose/H. - -### Cognee — добавки - -1. **Четыре глагола remember/recall/forget/improve** + `forget` как verb (dataset-scoped, incl. граф) → **F**: у гейт-поверхности нет verb-forget (GDPR-стиль удаления с провенансом) — слабое место, закрыть. -2. **Session-first read routing** (session cache → fallthrough в граф, SessionEnd → sync) → **F**: документированная политика чтения «горячая сессия → семантика», ложится на L0/L3/L4. -3. **AUTO_FEEDBACK после каждого answered query** (их was_useful = LLM-driven) + «бенчмаркить с включённой памятью» — честная заметка в наш eval-протокол (№11). → H. -4. **Fail-fast multi-tenant** (`ENABLE_BACKEND_ACCESS_CONTROL` — error вместо silent-fallback) → **H-инвариант**: no-silent-fallback для RRF-источников и user-scoping (усиливает D6). -5. **DATASET_QUEUE_ENABLED** (per-process write-guard) → F: file-lock гигиена L0→гейт записей. - -### Mem0 — добавки + ГЛАВНЫЙ ВЫЗОВ - -1. **🔴 Mem0 убил UPDATE-гейт (апрель 2026): single-pass ADD-only — один LLM-вызов, ничего не перезаписывается; LoCoMo 71.4→92.5** (старая two-phase ADD/UPDATE/DELETE/NOOP из arXiv 2504.19413 — брошена). Вывод: **конфликт-резолюция принадлежит read-time фьюжену, не write-time**. → **F-перекалибровка**: (а) наш bi-temporal ACCUMULATION уже в правильной парадигме (подтверждено); (б) **приоритет — ридер conflict.py (read-time fusion) ДО вложений в gate-time разрешение**; (в) канонические ключи важны для retrieval-boosting, не для «чей факт победит». Проверить цифрой на №11: gate-time vs read-time. -2. **Entity-matches как 6-й RRF-источник** (spaCy NER + embedding-linking, retrieval-boosting) → **G**: минер #3 (сущности) бесплатно становится поисковым сигналом, не только рёбрами. -3. **top_200 retrieval budget** (single-pass, no agentic loops) → **H**: фиксированный бюджет выборки до consume — тунабель, которого у нас нет. -4. **Query-type-aware temporal ranking** (current/past/future-intent → разная interval-семантика) → **H**: классификатор вопроса поверх get_at_time. -5. **Eval-харнесс отдельно от системы + плашка «platform vs OSS»** (их LoCoMo 92.5 — platform-only, OSS не воспроизводится) → **H №11**: публиковать конфиг компонентов при цифрах; свой харнесс — отдельный артефакт (`memory-benchmarks` у них открыт — взять структуру). - -### Zep/Graphiti — добавки - -1. **Bi-temporal окна на РЁБРАХ** (t_valid/t_invalid, invalidate-never-delete) — наши интервалы на фактах, минеры дают timeless-рёбра → **G 🔑**: каждый минер пишет validity-window на ребро; superseded-рёбра инвалидируются, остаются исторически queryable. -2. **Episodes = first-class queryable узлы** (provenance-слой, lineage-traversal) → **F**: наш L0-журнал = их episodes; сделать его узлом навигации, не только таблицей. -3. **Evolving entity summaries** (узел = обновляемый summary, не первая встреча) → **G**: nightly recompute entity-node content по последнему интервалу. -4. **Graph-distance rerank** (hop-count/connected-component как сигнал переранжирования, детерминированно) → **H**: дешёвый 6-й сигнал поверх RRF без LLM. -5. **Prescribed + learned ontology** («start simple, evolve as patterns appear») → легитимизирует наш staged-подход: детерминированные минеры первыми, типизированные рёбра — где появляются схемы. -6. **structured_output_mode/SEMAPHORE_LIMIT** — operational-мудрость для наших LLM-вызовов (staging/consolidation), если появятся. → H-заметка. - -### Cross-cutting вызовы нашему дизайну (все 5 систем) - -1. **Read-time fusion > write-time resolution** (Mem0) — приоритет: conflict.py-ридер раньше gate-time-логики; №11-эксперимент «gate vs read» обязателен. -2. **Size-бюджет — недостающий гейт** (Letta) — compact-to-budget в nightly с ACT-R-эвиктом. -3. **Рёбрам тоже нужны временные интервалы** (Graphiti) — G-минеры пишут t_valid/t_invalid. -4. **No-silent-fallback как инвариант** (Basic Memory + Cognee) — RRF-источники и scoping падают громко. -5. **Eval-харнесс = отдельный артефакт + конфиг-плашка** (Mem0/Cognee) — №11 оформляем standalone. - -- 2026-09-04: v17 — исследование научных статей (general-5, web; 7 бумаг/8 arXiv ID, формулы и гипер параметры верифицированы). Полный отчёт — notes сессии. Извлечения по фазам: - -### LongMemEval (arXiv:2410.10813, ICLR 2025) —蓝图 для H №1 - -- **Метрики**: LLM-judge (gpt-4o-2024-08-06, >97% human agreement) + Recall@k/NDCG@k с human-аннотированными evidence-позициями. **KU-scored STRICTLY**: retrieving только нового значения = провал — старое+новое должны появиться. → **F**: наши bi-temporal интервалы обязаны отдавать ПОЛНУЮ историю версий по KU-запросам, не latest-only. -- **5 способностей / 7 типов вопросов**: info extraction (user+assistant), multi-session reasoning, temporal reasoning, knowledge updates, **30 false-premise abstention** — у нас abstention вообще не моделирован → H-eval должен включать ABS. -- **Дизайн-находки**: key expansion фактами **+9.4% recall@k, +5.4% QA** (валидация [[fact:]]-расширения); **key merging > rank merging** (параллельные индексы −20-30% recall, m+1× bloat) — 🔴 вызов; time-aware query expansion **+6.8–11.3%** но требует сильного LLM (слабые генерируют ложные диапазоны → у нас bi-temporal-фильтр опционален/no-op без confident range); Chain-of-Note + JSON формат **+10 pts**; сортировка ретрива по timestamp. -- **BM25 равномерно худший** (vs Stella V5) на всех сеттингах → 🔴 демоут наших BM25-источников в RRF до minority-votes, мерить per-source contribution. -- **Бюджет ретрива**: GPT-4o растёт >20k, Llama-8B коллапсирует >3k → H: per-reader budget curve в отчёте. -- Сплиты: _s ≈115k токенов, _m = 500 сессий/1.5M; 500 вопросов. - -### VikingMem (2605.29640, VLDB26) — добавки F + H - -- **Entity = materialized view над event log**: `entity := SELECT OP(event.content) ... GROUP BY keys` с библиотекой операторов (LLM_MERGE/SUM/MAX/TIME_COMPRESS) → **F**: наша L4 store-семантика = эта идея; взять параметрическую формулировку вместо per-key логики. -- **EUA patch-обновления**: экстрактор выдаёт SEARCH/REPLACE-патчи, применение edit-distance по top-5 ANN-кандидатам — апдейт без LLM → **F**: дешёвая альтернатива LLM-merge на конфликтах. -- **One-pass schema extraction**: все типы памяти в ОДИН LLM-вызов (экономия ~(k−1)×) → **F**: kind-routing может эмитить все типизированные записи одним проходом. -- **OpenClaw (markdown coarse-grained memory) — худший baseline всей таблицы** → 🔴 **G/C-вызов**: wiki-слой = key/edge substrate (ссылки), НЕ retrieval unit; retrievable value = мелкозернистые факты/L0. Подтверждает «wiki ≠ поисковый словарь». -- **Двухсудейный eval-протокол** (GPT-4o-mini + GPT-4.1-mini) для rank-stability → **H**. -- Их LoCoMo 88.83/90.12, LME_s 66.36/75.80 — референс-планки; dynamic KG (Zep/Mem0-graph) проиграл event/entity+hybrid — подтверждение «детерминированные минеры, не LLM-KG». - -### A-MEM (2502.12110, NeurIPS 2025) — G-добавки - -- **Rich note embedding**: embed concat(content, keywords, tags, context), не голый контент → **F/G**: наш embedding-минер #9 должен кодировать расширенный текст (tags/aliases/канон-разложения) — иначе врёт. -- **LLM-adjudicated links** поверх top-k эмбеддинг-соседей; ablation multi-hop F1: 9.65 → 21.35 (links only) → **27.02 (full)** → **G-абляция**: один дешёвый LLM-проход подтверждения рёбер над top-k кандидатами в ночной consolidation — замерить, стоит ли нарушение no-LLM (локальная модель?). -- **Online neighbor evolution**: новая заметка триггерит обновление контекста соседей (у нас — только ночной batch) → G: partial online-обновление горячих соседей. -- k=10 retrieval, плато/деградация после k≈10-20 → H: k-sensitivity curve. -- Стоимость: 1216–2520 токенов/ответ vs MemGPT ~16977. - -### HippoRAG 1+2 (2405.14831, 2502.14802) — 🔴 G-вызов - -- **HR2 центральный вывод: structure-augmented RAG (включая KG+PPR) ПРОИГРЫВАЕТ стандартному dense RAG на базовых фактических задачах** → наш graph-expand — reranker для multi-hop ONLY; **абляция graph-expand OFF на single-hop LongMemEval-подсетах обязательна** (H). -- PPR (Personalized PageRank) — глобальная диффузия: на multi-hop/associative бьёт naive 1-hop expand, на factual проигрывает → кандидат в G: PPR над нашими минер-рёбрами как альтернатива расширению (замерить). -- HR2: passage-graph + PPR **+7% associative** поверх лучшего эмбеддера; passages = first-class graph nodes → **G**: рёбра passage↔fact (наши structural-минеры уже дают), сделать passage-anchored. -- Задачная таксономия для eval: factual vs sense-making vs associative — 3-way split в наш протокол. -- HR1: multi-hop до +20% над SOTA при **10-30× дешевле, 6-13× быстрее** IRCoT. - -### MemoryBank (2305.10250) — вердикт по decay - -- **R = e^(−t/S)**, S discrete init 1, **S+1 при recall, t=0 при recall** — это грубый ACT-R use-count с экспонентой: S=1 → R≈0.14 уже при t=2 (круче power-law в разы). -- **Вердикт: держим ACT-R power-law**; steal только **deletion gate** (prune при R < threshold) — подкрепляет B3/B5-эвикты. Плюс: иерархические дневные summaries → global (наш nightly-rollup той же формы — подтверждение). - -### Generative Agents (2304.03442) — верифицированные формулы → H-протокол - -- **Retrieval score = α_r·recency + α_i·importance + α_rel·relevance, α=1, каждый компонент min-max нормализован per-query**; recency = **0.995^(часов с последнего retrieval)** (экспонента по retrieval-времени, не creation). → **F/H**: у ACT-R-члена в multi_source нет per-query нормализации компонент — добавить; 0.995^h с ресетом = base-level learning — согласуется с ACT-R, не противоречит. -- **Reflection trigger: Σ(importance последних событий) > 150** (~2-3 рефлексии/день) → детерминированный on-demand триггер рефлексии в дополнение к cron (F/E9). -- Рефлексии возвращаются в общий stream — наш L3/L4 split богаче, но рефлексии должны искаться как события. -- **Interview-протокол**: 25 вопросов × 5 зон, human-rating vs «imposters» — дешёвый dev-qual харнесс believable-памяти (H); их top failure mode = failed retrieval + memory embellishments. - -### MemGPT (2310.08560) — открытый вопрос F - -- Self-editing memory как callable функции + interrupts/heartbeats: **агент-контролируемые vs автоматические гейты** — tension, который наш F решает в пользу авто; A/B-тест (H): дать агенту опциональные gate-опы (наш forget/think уже наполовину это) и сравнить качество с чисто-авто. -- FIFO+recursive summarization eviction = онлайн-консолидация без ночного джоба — для чат-нагрузок работает. - -### CROSS-CUTTING вызовы (ранжировано, слияние с конкурентной волной v16) - -1. **Key merging > parallel RRF** (LME): факты конкатенировать в источник (у нас [[fact:]] inline — есть), аблировать per-source contribution; параллельные пулы −20-30% recall. -2. **BM25 демоут** (LME): dense/miners весят больше; BM25 = minority votes. -3. **Graph = multi-hop reranker only** (HippoRAG2): graph-expand OFF на single-hop — обязательная абляция. -4. **Wiki ≠ retrieval unit** (VikingMem/OpenClaw): markdown-слой — ключи/рёбра, значения — мелкозернистые. -5. **Decay: ACT-R остаётся** (MemoryBank/GA согласуются), добавить per-query min-max нормализацию (GA) + deletion-threshold (MemoryBank). -6. **KU = полная история версий** (LME strict): bi-temporal читает old+new. -7. **LLM-adjudication рёбер** (A-MEM) — единственное место, где стоит рассмотреть локальную LLM в ночной фазе; решит №11-абляция. -- 2026-09-04: v17 — научные статьи (7 бумаг/8 ID). Ключевое: LongMemEval =蓝图 H (метрики+строгий KU+абстеншн); 4 cross-cutting вызова (key-merging>parallel RRF, BM25 демоут, graph=multi-hop only, wiki≠retrieval unit); ACT-R остаётся (MemoryBank/GA согласуются), + per-query нормализация (GA) и deletion-gate; минер #9 кодирует расширенный текст (A-MEM); LLM-adjudication рёбер — вопрос №11-абляции. - -- 2026-09-04: v18 — пачка 1 (general-6, 8 источников про контекст/компакцию). Извлечения по фазам: - -### Privacy (A1-гейт) — upgrade из LLM-Redactor (2604.12064) - -1. **Placeholders вместо delete-strip**: типизированные стабильные плейсхолдеры `⟨EMAIL_1⟩` (same value → same placeholder; reverse map только в process memory, не персистится) — retrieval продолжает работать на де-идентифицированном тексте. → **F 🔑**: наш strip_secrets вырезает навсегда; placeholder+map сохраняет юзабельность. -2. **Трёхслойный детектор**: regex (структурные секреты) + NER (Presidio/spaCy) + локальный LM-классификатор (семантическая чувствительность). Верифицировано: A+B+C = 0.6% PII-ликов; НО **regex-only на прозе почти бесполезен** (person 12.3%, org 25.9% остаточные лики с NER; employee_id 79.8% regex-only). → наш A1-гейт = только regex → ложная безопасность; нужен NER-тир. -3. **Strict-mode**: детект <0.5 confidence → отказ, не passthrough. Инвариант: crash = un-restorable (правильный fail mode). -4. **Предел**: implicit identity («CFO, чья жена…») проходит ВСЕ трансформы (43.6% даже A+B+C) — реляционная идентификация не ловится content-трансформами в принципе. Честный потолок privacy-гейта. - -### Компакция/инъекция — вызовы F - -5. **Reacquisition cost — недостающая метрика eval** (2608.16370): компрессия GPT-5.5: completion 80%→85% (p=1.0, «не видно») при **retrieval-вызовах 21→63.9 (×3, p=.002)**. Completion-only скоринг слеп. → **H 🔑**: в харнесс добавляется «post-injection tool-call delta». Плюс: **random retention ≈ hindsight oracle** в одном из 6 cell'ов — важно ЧТО сохранено (D-state тип), а не сколь тщательно выбрано → oracle-абляции прежде доверия EMA. -6. **Rubric-компакция** (SelfCompact 2606.23525): tool без rubric → неравномерное срабатывание; **fixed-interval компакция переводит правильные ответы в неправильные в 40.4% переходов**; rubric = активный ингредиент (41.0→46.4%); компакция выгодна iff L/ℓ>10; probe-and-pop (KV-cache переиспользуется, вердикт почти бесплатен); summarizer append не substitute (стабилизация префикса). → **F**: компакция/eviction инъекции = state-aware rubric («суб-задача решена?»), не чистый token-бюджет; наш stable-first+cache:break подтверждён (Prefix-stability = 20-70% экономии, CWL). -7. **CWL: детерминированный LLM-free eviction по typed episode-graph** (expl/act, dependency-рёбра, acyclicity, 4 градуированных уровня strip) — accuracy parity с fresh-сессиями на 80M токенов; «dependencies dominate recency». → **G/F 🔑**: наши минер-рёбра = готовый eviction/injection-порядок (dependency-aware вместо чистого recency); инварианты схемы (typed directions, no cycles, L0-raw never evicted = protected prologue). Оговорка: у них dependencies заявлены агентом (ground truth), у нас inferred — mis-inference = premature eviction, нужен консервативный порог. -8. **ACON (ICML 2026)**: правила компакции = оптимизируемый натурально-языковой артефакт, итерируемый по fail-анализу агента (−26-54% токенов при росте качества; до +46% малым моделям). → **F/H**: memory_compress-rules → prompt-артефакт с outer loop (fail → revise rules), не one-shot. -9. **LLM-Redactor/123ofAI**: audit-trail каждой компакции (range/model/output/raw-refs) + recovery-path; negative evidence first-class (failed_attempts, do_not_retry) — наши L3 не моделируют негатив явно → фид в E6; D_critical (constraint-survival check) — инъект-метрика которой у нас нет. -10. **LCC (2602.21221)**: disposable-LoRA = 16× латентная компиляция контекста ≈ full-context upper bound; **manifold regularization** (random-query loss) обязательна иначе parrot-collapse (N_Q=0 → 0.0); gradient isolation (freeze consumer). → долгосрочная альтернатива zlib-холодному тиру (латентные буферы бьют text-pruning); **вызов CLACK «lossless-or-bust»**: компресия может улучшать recall (3.29 > upper bound 2.89) — но для нас lossless-CLACK остаётся контрактом аудита, латентный тир = опция H-дальнего горизонта. JIT-loading (2511.03728): инъекция несёт канонические ключи, контент по требованию (>6× system-prompt, 10-25× growth) — вызов flat stable-first. - -- 2026-09-04: v19 — пачка 2 (general-7, 7 статей про long-term memory / state tracking). Извлечения: - -### StateMem (2608.19652, UIUC) — 🔑 прямая спецификация для Graphiti-style validity windows - -- **Детерминированный LLM-free Rechecker O(|E|)**: state units (id, content, priority hard/soft, source, deps ∈ {derived_from, coupled_with}); supersession → status=superseded (retained, inactive); dependents → needs_recheck — наша planned Graphiti-схема concretized. -- **StateMemWrapper: 4 precedence rules в read-prompt** (всего 155-токен инструкция, +15..+32 pts от структуры): later-supersedes-earlier; standing rules > instances; **derived values recomputed, never quoted**; retire только по явной supersession/expiry. -- **Цифры**: drift 44.4% подтверждённых фейлов LongMemEval-oracle; multi-session 71.4% vs KU 25.0%; enabling reasoning НЕ помогает (84→76%, p=0.34); **RRF-salience trap**: fusion «loudest wins», k-sweep flat на state-задачах (BM25 20% при любом k∈{5..40}) — k-tuning не лечит state-ошибки; **anti-trap пробы** (штрафуют always-prefer-latest) — lazy-reader эвристики как дешёвые trap-генераторы для eval. -- → **F/G/H**: supersession+dependency propagation (G), precedence-правила в read-путь (F), closed-pool probes + anti-trap + sequence-пробы (H-eval). - -### CAMA (2608.19701, HKU) — anti-false-majority для RRF-фьюжена - -- **Memory Correlation Bias**: воспоминания с общим source-корнем формируют ложные mostat при независимом подсчёте; **max-based slot presence** e_j = max_i z_ij (коррелированные записи не накачивают evidence mass); **N_eff = exp(log Σ p_j^α / (1−α))** (Hill diversity) + posterior entropy как sufficiency/stop → **принципиальный abstention-сигнал для false-premise eval** (лучше judge-only). -- **Recovery**: Expand(q′) альтернативные запросы / Trace(m_i) — проход по provenance-рёбрам к общим родителям (Trace снижает N_eff → детект корреляции). Верифицировано: false-majority 34.1 → 8.6 (decoupling), LongMemEval CMR 87.2; ΔAcc/kToken 1.14 (HippoRAG 1.07, MADAM-RAG 0.41). -- → **G/H**: dedup evidence mass по source_root в фьюжене (кап per-root), max-presence вместо аддитивного счёта, N_eff-абстеншн. - -### 3M (2608.15451) — операторный словарь консолидации - -- **Операторы**: Add/Update/Merge/Split/Connect/Compress/Prune + Conflict Detection/Repair (condition-splitting: ОБЕ записи с scope-условиями, не удаление), Generalize/Specialize/Abstract/Infer/Analogy/Find Gap/Verify. Merge хранит **Aliases field** = runtime synonym-словарь. → **F**: именовать наши nightly-фазы этим словарём; condition-splitting — опциональный write-time repair для инвариантов (доказывает: bounded write-time repair viable — смягчает Mem0-вызов). -- **Add gate = semantic novelty test** (вставка только если information content растёт) — дополнение к SHA-256: paraphrase-redundancy check; калибровка Jaccard (mean .274, bounds .000–.478). -- **Find Gap**: gaps знаний → proactive acquisition (наши L3-questions → gap-registry); **derived-флаг** отдельно от observed (origin-inspectable) → kind=derived + Verify-обработка. -- Их staged commit (Extract→ops→Verify, atomic replace, модель не может удалять файлы) — подтверждение наших staged mutations. - -### SKILL.state (2608.26263, Google+Purdue, EMNLP) — mutable state projection - -- **Σ_{t+1} = Σ_t ⊕ ΔΣ_t** (dict merge, null-удаление), reasoning выбрасывается после validated commit; deterministic validation, invalid patch → rollback-retry (мусорный вывод не портит состояние). Ошибки малых моделей: 68% premature overwrite, 12% syntax → validation-гейт обязателен. -- **Бюджет-мэтч контроль**: structured state 0.94 vs sliding-window 0.18 / LLMLingua 0.22 / summary 0.52 (same tokens) — **статистическая компресия разрушает точные реляционные зависимости**. -- **Ограничение (их признание)**: sufficient-statistic assumption фейлит, когда релевантность раннего наблюдения не была распознана при ingest → **append-only журнал (наш L0) — правильный субстрат; mutable state = ПРОЕКЦИЯ, не замена** (bi-temporal L4 уже полупокрывает). O(1) промпт / O(T) кумулятивно vs O(T²) append-only — конкретная bound для compact-to-budget. -- Верифицировано: T=100: 65,408 токенов vs 1,062,387 (16.2×); noise 50 events/turn: ≥0.97 vs 0.53. - -### Прочие (коротко) - -- **LCLM (2606.09659)**: chunk-ID + EXPAND(i) agent-loop — улучшение CLACK-холодного тира: архив = compressed skim layer + on-demand raw expansion (RULER +17..20 pts при 16×). Interleaved compressed/uncompressed сегменты в инъекции (не только head). **Reconstruction-only обучение коллапсируется в реконструкцию** → перед hot→warm демоушеном — needle-QA «reconstruction check» на сжатых чанках; zlib-архивы lossless именно поэтому. -- **P5 (2608.19535, ACM AI Leadership 2026)**: **net-benefit accounting** — компакция вычитает собственную стоимость; **mild compression (rate 0.9) = чистый energy LOSS** → warm-tier: **жать жёстко (≥3×) или не жать вовсе** (step-change 30→180д подтверждён против gradual trims); 3-mode controller (skip/safe/aggressive) вместо per-query budget search. -- **P2 (2608.15570)** — cond-mat физика, нерелевантна (ошибка в списке? — abstract-only, marked). - -### CROSS-CUTTING (только новое, сверх v16–v18) - -**F**: semantic novelty gate (paraphrase-Jaccard перед L4-insert); condition-splitting repair для инвариантов (опция); merge-aliasing (runtime synonym-словарь обновляется оператором Merge); warm-tier: hard-or-none + reconstruction-check перед демоушеном; kind=derived флаг; deterministic validation + rollback-retry для структурных апдейтов. -**G**: typed edges derived_from/coupled_with + unit status (active/superseded/needs_recheck) + O(|E|) recheck propagation; **provenance-root evidence dedup в фьюжене** (cap per source_root, max-presence); read-time Trace по provenance к общим родителям; N_eff как sufficiency-метрика. -**H**: StateMemBench-пробы (closed-pool, superseded value = scored outcome, anti-trap, sequence-пробы) в LongMemEval-протокол; **N_eff+entropy абстеншн** (калиброванный, не judge-only); per-substrate salience floors + k-sweep flatness отчёт (не тюнить k на state-задачах); net-benefit accounting компакции; **precedence-правила в read-промпт** (155 токенов, +15..32 pts — дешёвый verified win); ACT-R напрямую не оспорен ни одной из 7 статей (ближайшая угроза — salience trap, лечится precedence, не формулой decay). -- 2026-09-04: v19 — пачка 2 (StateMem/CAMA/3M/SKILL.state/LCLM/P5). Топ: StateMem deterministic supersession+dependency recheck = спецификация validity windows; CAMA N_eff + max-presence = anti-false-majority и abstention для фьюжена; 4 precedence-правила read-промпта (+15..32 pts за 155 токенов) = дешёвый verified win; SKILL.state: mutable state = ПРОЕКЦИЯ над append-only L0 (их собственное признание ограничений); warm-tier hard-or-none (mild = net-negative); semantic novelty gate + condition-splitting repair (3M смягчает Mem0-вызов для инвариантов). - -- 2026-09-04: v20 — батч 1/3 (general-8): chimera, shiroe, gbrain, knowledge_graph, LightMem, ReMe, Acontext, EverOS. - -### Батч 1 — уникальное - -1. **gbrain — целевая цифра**: LongMemEval-S **strict recall_all@5 = 93.19%** (rerank off) / **95.32%** (Voyage rerank-2.5), retrieval-only, per-row receipts → **H**: наш aim-номер для №11 (MemoryPalace 96.6 — выше, но их стек другой). -2. **gbrain gap-analysis в ридере**: ответ явно флагует unknown/stale/uncited/contradicting → **G 🔑**: «detection without reporting is half a conflict system» — наш conflict-ридер должен ПРОЯВЛЯТЬ, не только фьюзить. Плюс `create_safety` verdict (exists/probable/unknown) как контракт выхода писателей. -3. **shiroe hash-chained append-only log** (`state verify` replay-integrity) → **F**: hash-цепочка на L0-журнал — бесплатный tamper-evidence (у нас append-only без цепочки). -4. **chimera triple cost-cap** (per-cycle / rolling-60m / per-task + `estimate` verb) → **G/F**: spend-gate вокруг ingestion/consolidation (runaway-защита ночного batch — в связке с E9-cycles). -5. **chimera: SQLite + recursive-CTE primary, Kuzu opt-in** (~95% query coverage) → подтверждение: граф-СУБД не нужна, projection-not-primary. -6. **chimera task-escalation memory**: failed task авто-повышает тир при следующей попытке + hot-signature alarm на повторные фейлы → **H**: repeat-failure detector в reacquisition-loop. -7. **EverOS + Acontext: agent-self track** (cases/skills отдельной сущностью от user-памяти) → **G 🔑**: kind-routing не имеет agent-self трека — дать процедурам/кейсам агента отдельный namespace (второй независимый сигнал после E6). -8. **EverOS orthogonal retrieval axes** (user_id/agent_id/app_id/project_id/session_id композабельно) → **F**: дешёвые scoping-фильтры на BM25. -9. **ReMe bounded-delta consolidation**: dream трогает только изменённые файлы, ~5 units/run cap → **F**: cost-bound ночного consolidation (не full-scan каждый раз). -10. **ReMe line-range chunk recall + bounded 1-hop wikilink expansion** → **G**: конкретная read-механика [[fact:]]-neighborhood, уважающая «links ≠ retrieval units». -11. **ReMe proactive surface**: dream-produced interest topics предлагаются агенту pre-turn → **H**: proactive recall отсутствовал в списке фаз. -12. **knowledge_graph dual-weight edge merge** (LLM-relation W1 + co-occurrence W2 → ОДНО ребро с конкатенацией лейблов) → **F**: merge-семантика для минер-рёбер из разных источников. -13. **LightMem KV-cache precomputation for memory** (offline lossless / online lossy — todo) → **H watch**: самая novel идея батча — persist prefill state для горячих memory-read. -14. **LightMem llmlingua-2 pre-compression входящих turn'ов ДО storage** (семантический, у нас zlib только warm) → H, оценить стоимость вызова. -15. **Acontext outcome-triggered learning** (task complete/fail → distillation в skill-файлы; SKILL.md задаёт схему) → **G**: outcome/procedural слой kind-routing + подтверждение «skill is memory». - -### Батч 1 — covered/слабости (одной строкой) - -shiroe Work-Graph = workflow versioning (covered: memory_history snapshots); knowledge_graph dual-weight — notebook-grade; LightMem online-update no-op placeholder; ReMe LongMemEval 89.4% agentic (reader included — не retrieval-only, несравнимо с gbrain напрямую); Acontext SaaS-first (PG+Redis+RabbitMQ+S3 для self-host); EverOS marketing-heavy, keyword-only; gbrain TS/Postgres стек, нет answer-accuracy run; chimera 0★ solo. - -### Cross-cutting батча 1 - -«No repo has anything resembling our 3-tier journal lifecycle (CLACK), ACT-R decay, N_eff abstention, reacquisition-cost» — конкурентной угрозы нет; strongest borrows: **gbrain gap-reader (G), hash-chain L0 (F), agent-self track (G), outcome-triggered procedural (G), edge merge-semantics (F)**. -- 2026-09-04: v20 — батч 1/3 (8 репо). Топ: gbrain LongMemEval-S strict recall_all@5 = 93.19/95.32% (aim-номер №11) + gap-analysis reader (detection без reporting = полсистемы); hash-chain L0 (tamper-evidence бесплатно); EverOS/Acontext agent-self track (дыра в kind-routing); chimera triple cost-cap + SQLite-CTE- primary граф; ReMe bounded-delta consolidation + proactive surface; dual-weight edge merge. - -- 2026-09-04: v21 — батч 2/3 (general-9): MemOS(hijzy), memgraph, **prism**, agent-context-code, Memori, engram, claude-mem, llm-wiki-cli, mnemosyne. - -### Батч 2 — уникальное - -1. **prism (основа эпистемического графа) — 3 пропущенные механики** → **G 🔑**: - - **Valence-typed edges**: 10 типов (supports/refutes/supersedes/derives_from/specializes/contrasts_with/implements/generalizes/exemplifies/qualifies), valence решает RESULT BUCKET (primary/supporting/contrasting/qualifying/superseded) — наши минеры структурные, семантической эпистемической типизации нет. - - **Convergence scoring**: узлы, достигнутые несколькими независимыми seed-путями, ранжируются выше `a·(1+λ·conv)` мультипликативно. - - **SUPERSEDED bucket**: темпоральная valence ВИДИМО понижает устаревшие факты (не дропает) — комплемент bi-temporal + conflict fusion. - - Плюс: two-stage edge build (cheap binary pre-filter → async batch classify, checkpoint/resume) — cost-паттерн если когда-нибудь LLM-mine рёбра; spreading activation (sum-pool, 0.7 hop decay, 0.6 reverse-edge penalty). -2. **State-tracking (StateMemBench из пачки 2 статей) получил подтверждение от MemOS**: NL feedback/correction каналы («correct/supplement/replace memory X») — у нас только read-side fusion, write-side correction channel отсутствует → **G**. Плюс **Memory Cubes** (composable KB с per-query `readable_cube_ids`) — изоляция тоньше нашего per-layer scoping → F; **MemScheduler** async ingestion queue (capture decoupled от agent turn) → F; OmniMemEval (14 продуктов × 10 датасетов) — eval-шаблон → H. -3. **claude-mem 93,136★ — РЕАЛЬНО (API-verified)**, но: token-inflated rebrand «Grok Mem» + CMEM crypto-token промо; суть = lossy LLM-compressed observations, БЕЗ lossless L0-аналога. Полезное: **per-result token labeling** (search ≈50-100 tok → timeline → get_observations ≈500-1000, ~10× savings) → **F**: маркировать token-cost прямо в результатах тулов; **`` tag на capture** — дешёвый комплемент transcript-guard + placeholders (усиливает C5). -4. **mnemosyne — «automatic» = Hermes-plugin ONLY** (их домашний harness; для Claude Code/Cursor/Codex — plain MCP). Эмпирическое подтверждение: **autohooks-grade universal capture ≈ уникальна**. Полезное: **MIB bit-vectors 384d→48 bytes, Hamming в SQLite без ANN** (flat R@10, 35ms @10M, 9.4× compression) → H-путь для нашего embedding-минера; eval-гигиена (version-pinned bench rows + judge-mismatch caveats + 100% abstention reporting) → H-протокол; Temporal TripleStore valid_from/as_of = валидация bi-temporal. -5. **llm-wiki-cli — 4th layer «schema & purpose»**: per-project maintenance rules как first-class memory (у нас только importance rules.yaml) → **G**; **citation-safe spans** (heading path отдельно от snippet byte-range, stale-locator detection) → **G**; **atomic changesets** (sparse draft overlay DB, exact inverse patch, same-entity conflict fails closed) — богаче нашего single-mutation rollback → **G**; retention защищает pinned + open-contradiction записи от pruning — прямая связка с conflict fusion. -6. **engram**: **Git Sync** portable compressed chunks + Obsidian export — у нас вообще нет cross-machine portability story → **F/G**; `mem_review`/`mem_judge`/`mem_compare` — agent-facing tools аудита устаревшего и сравнения конфликтующих → **G** (у нас gates есть, agent-review surface нет); stable `topic_key` contract + runtime `mem_suggest_topic_key` → F-мелочь (наши канонические ключи). -7. **Memori (16.4k★)**: `entity_id × process_id` attribution на каждую интеракцию (3 оси — гранулярнее нашего scoping) → **F**; background augmentation taxonomy (attributes/events/facts/people/preferences/relationships/rules/skills — 8 типов) расширяет kind-routing меню → **G**; их «automatic» = SDK monkey-patching LLM-клиентов (не harness-level), full = cloud API. LoCoMo 87% @ 721 tok/query — подтверждение compact-to-budget. -8. **memgraph**: atomic GraphRAG (vector+traversal+prompt assembly в одном Cypher), SHOW SCHEMA INFO (self-describing ontology для агентов) → **F**; TGN/GNN link prediction — future upgrade embedding-минера → H. Wholesale не берём (граф-СУБД не нужна — подтверждено chimera). -9. **agent-context-code — POINTER MISLABEL**: это local semantic CODE SEARCH (tree-sitter AST + LanceDB + BM25 + RRF + reranker), не L2.5 project-memory. Единственное заимствование: **Merkle-DAG incremental indexing** (content-hash tree → только изменённые файлы перепроцессируются) для rebuild'ов wiki/L4.5 проекции → **F**. Нужен новый референс для L2.5-слота. - -### Батч 2 — covered/слабости - -MemOS upstream — Neo4j+Qdrant для self-host, cloud upsell; memgraph — BSL/MEL лицензия, infra alternative; Memori cloud-first quota gating; engram FTS5-only без векторов; claude-mem monetization noise; mnemosyne absolute recall низкий (20% R@10), bench numbers stale by own admission; LWC 50★ lexical-only. - -### Cross-cutting батча 2 - -**5/9 репо целят в тот же harness-набор (Claude Code, OpenClaw, Hermes, Cursor...) — memory capture стал harness-plugin land-grab; наше autohooks-лидерство (3 harness) держится, только пока покрытие расширяется (F decision point).** prism дал 3 из 3 обещанных механик; pointers claude-mem/mnemosyne/agent-context-code проверены — два подтверждены, один mislabel. -- 2026-09-04: v21 — батч 2/3 (9 репо). Топ: prism — 3 пропущенных механики эпистемического графа (valence-typed edges → result buckets, convergence scoring, SUPERSEDED bucket) → G 🔑; claude-mem 93k★ реальны но lossy+token-модель (наш дифференциатор: lossless L0 + autohooks); mnemosyne «automatic» = Hermes-only (подтверждение уникальности autohooks); MIB bit-vectors 48-byte/Hamming — H-путь embedding-минера; MemOS Memory Cubes + NL correction channel; engram git-sync portability; atomic changesets + citation-safe spans (LWC); cross-cutting: harness-plugin land-grab (5/9 репо в тот же harness-набор — autohooks-лидерство держится пока покрытие расширяется). - -- 2026-09-04: v22 — батч 3/3 (general-10): keep, ai-memory, memanto, obsidian-semantic-search, mazemaker, autograph, memory-os, Soul-of-Waifu, LightMem (повтор по запросу). - -### Батч 3 — уникальное - -1. **ai-memory (5673★, Rust) — Capture VERIFIED: автоматический** (lifecycle hooks → sanitized observations, session-end консолидация в wiki-страницы, zero-LLM дефолт FTS5+entities RRF) = autohooks-эквивалент на **20+ harnesses** → валидация нашего направления захвата; **competition: их покрытие шире** → F decision point остаётся. Уникальное: **typed handoff protocol** (handoff = first-class объект: owned, claim-once, кросс-harness эстафета, не конвенция) → **G**; **per-repo `[capture]` allowlist/exclude** + типизированная privacy-граница ДО записи → **F** (питает A1-гейт); **`experience/` pass** — абстракции, видимые только поверх траекторий (кросс-сессионный слой) → **G**. Слабость: инвариант «db rebuildable from wiki» — вики гейтит всё. -2. **keep**: **Declared-inverse edge tags** (`_inverse` на ключ в словаре писателя → двунаправленные рёбра `speaker:X`↔`said`) — рёбра из словаря, не из контента → **F** (дополнение минерам); **standing queries при чтении** — попадание в заметку поджигает `.meta/*` запросы (обязательства всплывают в момент извлечения) → **F** (дешёвый хук read-path, у нас standing queries есть, read-поджига нет); **git changelog ingest** (коммиты как заметки с рёбрами к файлам) → **F/G** детерминированный минер. Слабость: >3kB суммаризация (наш L0 честнее для eval). -3. **memanto — MIB-вердикт: covered** (проверка кода: тот же 128-byte binary + Hamming механизм, что наш rag/quantize.py; «стырила» только формат competitor-diff отчёта). Уникальное: **expiry policies** — retention-таблица по типам + именованные правила (first-match-wins, pins); expired остаются recallable с меткой `[EXPIRED]`, restorable, с provenance правила → **F 🔑**: уточняет наш deletion gate (мягкое истечение вместо жёсткого удаления — родня Shadow Bin); **estate-quality-over-time метрики** (contradiction rate, staleness, precision@month-6) + честная оговорка «бенчмарк-числа некросс-сравнимы» → **H**. -4. **mazemaker — трёхфазный dream engine** → **F/G 🔑**: NREM (spreading activation: +0.05 со-сработавшим, −0.01 неактивным, prune <0.05), REM (**мосты изолированных узлов** к похожим несвязанным, weight=sim×0.3), Insight (BFS-комьюнити → материализованные абстракции) — у нас в nightly нет edge-decay prune и REM-бриджинга как отдельных проходов (наш graph_enrich получает фазовую структуру); **negative-control protocol**: каждый механизм обязан иметь off-switch бенчмарк, который должен УПАСТЬ (shuffled-edge 1.00→0.27; post-dream 0.00→0.43) → **H 🔑**: переносимый стандарт доказательности — ablation-сьют для минеров/dream-фаз. Слабость: AGPL, open-core gating. -5. **autograph — schema-as-code**: один schema.json (типы карточек, папки, статусы, **per-type decay-ставки**) — машиночитаемый контракт для всех агентов-писателей; наш kind-routing зашит в код → **F**: вынести в декларативный контракт; **vault health score** (broken_links/orphans/desc_coverage/stale%) — непрерывный lint-метрик → **H** дёшево. Ebbinghaus с access-count ≈ ACT-R (ещё одно подтверждение); identity-dedup (email/handle) ≈ синоним-ключи. -6. **memory-os**: **Ground-Truth instruction layer** (SOUL.md/rulebook.md — явная «внедрённая память авторитетна», иначе агент перезапрашивает тулы для проверки уже-инжекченного) → **F 🔑**: дешёвый фикс инъекции (prompt-слой с обозначением authority); **semantic dedup cosine >0.92 → merge** (недельный сканер; у нас только SHA-256 exact — второй независимый сигнал после 3M/A2) → **F**; trust scoring с use-feedback → G (вероятно покрыто EMA — детали unverified). Слабость: Qdrant+Redis+ARQ Docker стек. -7. **Soul-of-Waifu**: почти всё covered (self-healing overwrite+correction-log = planned conflict-fusion reader; depth presets ≈ compact-to-budget; near-dup merge = A2). Единственное: **first-person diary как артефакт консолидации** → **G** опционально. Consumer-app, без eval. -8. **LightMem (повтор, уточнение)**: **lossy pre-compression gate** (LLMLingua-2/entropy) ДО LLM-извлечения — рычаг стоимости на пути hot→extraction → **F**; **topic segmentation как единица извлечения** (не turn/window) с шарингом precomp-результатов → **F**; **sleep-time offline update queue** со score_threshold (батчевые idle-апдейты между записью и ночью) → **G**; их eval-колонки tokens/calls/runtime = наш reacquisition-cost (покрыто). - -### Cross-cutting батча 3 - -- **Три репо независимо сходятся на semantic dedup** (cosine-порог merge: memory-os, 3M, A2-план) — следующий шаг после exact-dedup, консенсус рынка. -- **ai-memory подтвердил autohooks-жизнеспособность на 20+ harnesses** — но их покрытие шире нашего: F decision point (какие harness'ы следующими). -- **negative-control protocol (mazemaker) + eval-гигиена (mnemosyne, memanto) + estate-quality metrics (memanto)** — три независимых источника сходятся на стандарте доказательности для H. -- MIB: origin-вопрос закрыт — механизм общий, «стырил» только diff-отчёт формат. -- 2026-09-04: v22 — батч 3/3 (9 репо) — РАЗВЕДКА ЗАВЕРШЕНА. Топ: mazemaker трёхфазный dream engine (NREM decay/REM мосты/Insight абстракции) + negative-control protocol (off-switch ablations = стандарт доказательности) → F/G/H 🔑; ai-memory autohooks-эквивалент на 20+ harnesses (typed handoff protocol, [capture] allowlist, experience/ pass) — валидация + competition; keep declared-inverse edges + standing-queries-at-read; memanto MIB=covered (проверено кодом) + expiry policies с [EXPIRED]-меткой + estate-quality-over-time; memory-os Ground-Truth instruction layer + semantic dedup cosine>0.92 (третий независимый сигнал); autograph schema-as-code + vault health score; LightMem lossy pre-comp gate + topic segmentation. - -- 2026-09-04: v23 — Memanto paper (2604.22085, читать сама — ключевой источник MIB). explore-1 извлёк точные секции. Полный отчёт — notes. - -### Memanto/Moorcheh — что мы НЕ вытянули (и что вытянули) - -**Наши 3 гэпа по слоям:** -- MIB-глубина: только концепт «32× compression, no measurable loss», бит-селекция не раскрыта в этой статье (за формулами → 2601.11557). Наш quantize.py вероятно тот же класс (sign-binarization). Приоритет НИЗКИЙ. -- **EDM** (information-theoretic distance вместо cosine/Hamming: «scores by ability to reduce uncertainty in query context») — формулы НЕТ в этой статье; применим как re-ranker по shortlist (у нас brute-force, ANN-ограничений нет). Средний приоритет, зависит от 2601.11557. -- **ITS** (детерминированный [0,1] score + threshold gating): формулы нет; дешёвый суррогат — min-max нормализация Hamming в [0,1] + threshold 0.05. Дешёво. - -**Главные рычаги из их ablation (LongMemEval/LoCoMo, верифицированные дельты):** -1. **Recall Expansion = крупнейший одиночный выигрыш**: k=10→40 + threshold 0.15→0.10 = **+20.4/+6.6 pp**. Итоговая конфигурация: **k до 100, ITS threshold 0.05, gating вместо fixed-k** = ещё +5.8/+3.4. Кумулятивно k 10→100 = **+28.4 pp**. -2. **Recall > Precision принцип**: «LLM — более способный фильтр, чем любой pre-computed retrieval structure, ценой лишних токенов» (обосновано LongMemEval-цифрой: точность растёт до 20K retrieved tokens; lost-in-the-middle зависит от позиции, не количества). → **H/F 🔑**: наш RRF отдаёт top-10-стайл — прямое указание поднять бюджет ретрива до 40-100 и отдать фильтрацию LLM. -3. Prompt Optimization почти ничего не даёт (+2.2/+0.1) — не тратить время на промпт-тюнинг ретрива. -4. **Inference model upgrade** = +4.8 pp на LME (Sonnet→Gemini 3) — читающая модель важнее промптов. - -**13 категорий vs наши**: 9 совпадают. У них есть event/learning/error/artifact, у нас rule/todo/question/hypothesis. 🔑 **Каждый тип несёт priority/decay-сигнал** (fact=stable, commitment=time-critical, goal=until achieved, event=episodic decaying, context=highly temporal, learning=accumulating) — type-filtered retrieval. → **F**: добавить decay/priority-сигнал в TypePolicy (есть decay_rate — добавить retrieval-приоритет). error-тип = ближайший к E6 negative memory (у нас error_pattern в D1.8). - -**Conflict Resolution**: same-type + same-namespace semantic matching → **уведомление агенту с 3 опциями: supersede / retain / annotate** (обе + conflict flag); до явного разрешения противоречие НЕ персистится. Цель — constraint drift/memory poisoning. → **F**: наша conflict-схема (ридер ещё не построен) = этот контракт: 3 опции + non-persistence до резолюции. - -**Temporal Versioning**: as-of / changed-since / current-only query-модальности, non-destructive supersession (superseded_by колонка) — SQLite-совместимо, валидирует наш bi-temporal; changed-since модальность у нас отсутствует → добавить к get_intervals. - -**Daily Intelligence**: автогенерация ежедневных session summaries + contradiction reports + interactive conflict prompts как **локальные Markdown-файлы** (= human-readable audit) — пересечение с A8-бриджем и C1-сценами. - -**Memory Tax таблица** (верифицирована): Memanto 0 LLM/write, <10ms ingest; Mem0 ≈500ms; Mem0g ≈2s; Zep ≈3s. 10K ops/день: $0.50 vs $2.32/$1.70 → $662/год/агент. Complexity 0/4 (no graph DB, no LLM ingest, no multi-query, no recursive) vs Hindsight 4/4. → наш стек (0 LLM/write, SQLite) идеологически = Memanto; **обе статьи (их и HippoRAG2) сходятся: simpler architecture + optimized retrieval > graph complexity**. - -**Ablation-методология**: 5-стадийный progressive ablation (baseline → recall → prompts → max-recall → model) — шаблон для нашего №11 (каждый компонент оценивается изолированно, дельты видны). - -**Параметры**: budget 100 chunks, ITS threshold 0.05, 1 query/question (no multi-query/recursive), модель эмбеддингов НЕ названа; k=60 плато, inflection k=40; +4× token cost k=10→40 оправдан. Код: github.com/moorcheh-ai/memanto-evaluation (открытый!) — сверить нашу реализацию с их eval-скриптами при построении №11. - -- 2026-09-04: v24 — вики-батка (general-13): iwe, obsidian-mind (4.6k★), agent-second-brain (autograph-движок), obsidian-second-brain (4.3k★), Ar9av/obsidian-wiki, llm-wiki, agent-context-code (подтверждён: код-поиск, НЕ L2.5). 14 genuinely-new механик самоорганизации. - -### Wiki-самоорганизация — топ-находки → G/H - -1. **Bookkeeping-файлы исключать из граф-алгоритмов** (Ar9av/obsidian-wiki) → **G 🔑**: их бенчмарк показал — index.md (ссылается на всё) fool'ит path/centrality-запросы; **наши MOC-хубы доминируют BFS/louvain так же** — исключить/занизить вес auto-indexes в A1.4/A1.6. Измеренный эффект: 4.4× быстрее, 44%→83% correctness на структурных вопросах (подтверждение graph-first инвестиции). -2. **Rewrite-not-append ingest + auto-reconcile** (eugeniughelbur 4.3k★): один источник обновляет 5–15 существующих страниц; /reconcile резолвит противоречия с документированием «почему»; supersession-aware search → **G/H**: наш docs_to_wiki только добавляет — переход на rewrite-семантику. -3. **Redirect-stub self-merge**: near-dup пары мержатся (dry-run default), удалённая страница = **redirect stub, никогда не удаляется** → **H**: сохраняет backlink integrity с первого дня (в связке с A2-similarTo). -4. **OKM freshness policy** («метаболизм» — компаньон Google OKF): каждый факт обязан быть *timeless / dated / pointer*; fast-changing факты НЕ копируются, линкуются с as-of штампом; enforced linter'ом → **H**: предотвращение staleness на записи (наш status=stale — after-the-fact). -5. **Ebbinghaus decay tiers** (agent-second-brain/autograph): strength `1+ln(access_count)`, 5 тиров core→active→warm→cold→archive, **touching promotes back up** — наш статичный lifecycle (active/stale/archived) становится динамическим → **G**. -6. **Random recall archived cards** рядом с контекстом («creative collisions») → **G**: эмерджентная навигация, которой у нас нет (ε-random в духе E-greedy). -7. **Blast-radius write guards** (iwe 1.6k★): каждая мутация декларирует сколько docs/blocks может задеть; mismatch → abort → **H**: write-gate для wiki-мутаций (обязателен поверх MCP). -8. **Write-time link validation** (obsidian-mind): «note without links is a bug» — PostToolUse хук БЛОКИРУЕТ md-записи без wikilinks/frontmatter → **H**: линковка enforced на записи, не swept потом. -9. **OKF frontmatter contract + schema inference**: per-type документные схемы, **infer из стора**, механическая валидация; find по frontmatter → **H** (в связке с autograph schema-as-code из v22). -10. **Declared-scope cross-repo memory** (obsidian-mind): reach (project/platform/general) объявляется НА ЗАПИСИ, не угадывается при чтении; **corrections supersede, not overwrite** (стор становится довереннее со временем); каждый read логируется → **H** (scope-field на фактах + supersede-семантика). -11. **Injection budget с pointer degradation** (obsidian-mind): бюджет по манифесту; за потолком дешёвые секции деградируют до *именованных pointer'ов* («silent loss worse than bloat») → **H**: token-budget для MOC/recall_protocol. -12. **Frontmatter Bases**: DB-views из frontmatter, incl. **Stale Actives** (untouched 14d) → **G**: авто-views как комплемент louvain. -13. **Nightly 5-phase maintenance + bounded recall with abstention + retrieval eval** (eugeniughelbur): close-day → reconcile → synthesize (пишет synthesis-страницы сам) → heal orphans → rebuild index; **≤4 notes/~900 chars, injects NOTHING при low-confidence, все решения логируются**; recall@k + MRR на natural questions → **H**: подтверждение N_eff-абстеншна + наш eval-протокол. -14. **Bi-temporal [[fact:]] metadata**: learned_at + valid_at на линках → **G** (тонкость к planned [[fact:]]). - -### Батч 3 — covered/слабости - -llm-wiki governance-heavy без граф-алгоритмов (orthogonal); agent-context-code повторно подтверждён code-search (L2.5-донор по-прежнему не найден); iwe propose-select-approve для docs_to_wiki (H, minor); Ar9av compile-not-accumulate + alias dedup (H minor). - -### Cross-cutting батча вики - -**Четыре источника независимо сходятся**: (1) auto-indexes/MOC опасны в граф-алгоритмах (Ar9av измерил) — наш A1.1 родился до этого урока; (2) rewrite-not-append > append-only для wiki-ingest; (3) redirect-stubs вместо удаления; (4) freshness должна проверяться на записи (linter), не после. **obsidian-second-brain (4.3k★) — ближайший референс нашего nightly-блока**: 5 фаз + bounded abstention + встроенный retrieval eval. - -- 2026-09-04: v25 — движки/граф-фреймворки (general-12): cocoindex, cocoindex-code, txtai, semantica, code-graph-rag, open-index, autocontext. Все 7 — новые поколения (2 созданы 2026, три >11k★) — зрелость паттернов «инкрементальный движок» и «детерминированный KG без LLM» подтверждает направление a-memory. - -### Батч движков — топ-находки → F/G/H - -1. **autocontext (1.3k★) — outcome-gated promotion = недостающий дизайн G2-гейта** → **F/G 🔑**: выученный контекст НЕ раздаётся, пока не пройдёт matched screening → adaptive confirmation → held-out eval → **false-promotion budget** → causal attribution → atomic activation; rejected-свидетельства сохраняются для scoped retest. Вместо чистого min_weight — evidence-gated промоция (кандидат живёт в staging пока пробы не подтвердят пользу; бюджет ложных промоций ограничен). Плюс cross-run knowledge (playbook.md + hints.md + trace.jsonl — следующий прогон читает уроки прошлого = подтверждение scratchpad/promote + L0-replay) и ablation-attribution с отказом от edit-size-корреляции как каузальной (методология №11). -2. **cocoindex (11.5k★): memo = hash(вход) + hash(КОДА)** — кэш инвалидируется и при смене данных, и при смене функции → **F**: независимо подтверждает replay-config-hash дизайн (добавить в verdict-колонку). **Delta-only graph_enrich**: lineage-propagated delta через affected records, retire stale rows, не трогая неизменённое → **G**: минеры пересчитываются только для затронутых узлов вместо полного ночного ребилда. Rust-продукт — парк. -3. **cocoindex-code (2.7k★): snowflake-arctic-embed-xs** — локальный embedding по умолчанию через sentence-transformers (наш стек!) → **G**: готовый кандидат модели для минера #9 (крошечная, без ключей). Hook-driven инкрементальный реиндекс (SessionStart+PostToolUse) — подтверждение auto_save-ingress паттерна. -4. **txtai (12.9k★): граф из kNN-подграфа поиска** (graph-from-search-results, не из сущностей) + centrality/communities/path built-in → **G**: дешёвый генератор рёбер similar_to — top-k соседей per node вместо порога косинуса по всей базе; topic-кластеры валидируют louvain-MOC. Sparse+dense UNION внутрииндексный (элегантнее нашего 5-source RRF, но SQLite-only swap запрещён); HippoRAG2-caution применима. Фреймворк — парк. -5. **semantica (11.9k★): decision как first-class нода с read-поверхностью** — trace_decision_chain (каузальная родословная) / find_similar_decisions (прецеденты) / analyze_decision_impact (карта влияния) → **G 🔑**: наш causal-граф (E17a) умеет ЗАПИСЬ, но не имеет read-поверхности — готовый API nightly + memory_audit. **PROV-O стандарт** (W3C) для provenance вместо ad-hoc → H/typed_export; **point-in-time graph snapshots** — дёшево в SQLite, закрывает time-travel → G. Rete/Datalog/forward-chaining без LLM — парк до Stage 2. -6. **code-graph-rag (5k★): static+runtime overlay** — `cgr trace` мержит фактические вызовы (тест-ран/eBPF) в статический tree-sitter-граф («dispatch, который статике не виден») → **G**: архитектурный паттерн подтверждён чужим продуктом (наша пара: structural-miner статика + co-retrieval факты). **ast-grep pluggable tier** — graceful degradation для минеров (слабый язык → пониженный вес, не отказ). NL→Cypher через LLM — парк (no-LLM запрет). -7. **open-index (105★, создан 2026-08) — БЛИЖАЙШИЙ архитектурный родственник** (SQLite+FTS5, четыре примитива doc_type/schema/entity/connector): **zero-result search analytics** — провальные запросы = «что смоделировать следующим» → **G/H 🔑 ВСТРАИВАТЬ**: дешёвый минер key-expansion (LME +9.4% recall) поверх уже существующих recall_events; **storage: file|index per doc_type** (кураторские = git-tracked JSON, машинные = DB-owned) — независимое подтверждение нашего wiki-md/L4-KV разделения (v14); per-field boost, Stop-hook write-back (параллель scratchpad-promote). Остальное украсть-идею. - -- 2026-09-04: v26 — библиотеки-кандидаты на встраивание (general-11): 7 проверок embed/park/skip. - -### Библиотеки — вердикты встраивания - -| # | Библиотека | Вердикт | Точка интеграции | -|---|-----------|---------|------------------| -| 1 | **spaCy 3.8.16 (+en_core_web_sm 12MB)** | **ВСТРАИВАТЬ** | privacy-gate NER-тир (regex-only на прозе: person 12.3%/org 25.9% остаточных ликов; sm закрывает большинство) + entity-минер #3 (doc.ents → co_mentions, один process-wide load). Лицензии: ядро MIT, модели CC BY-SA (атрибуция при дистрибуции). trf 436MB — парк. | -| 2 | **fractional-indexing (httpie)** | **ВСТРАИВАТЬ (vendored, CC0)** | `order_key TEXT` на wiki-страницах/канонических ключах/L0 — между-вставка O(1) вместо full-reorder. Single-file + tests, byte-compatible с rocicorp. PyPI 0.1.3 отстаёт от README 4.0.0 — vendor файл, сняв supply-chain риск. Лучший find волны. | -| 3 | turbovec (16.7k★, MIT, ICLR 2026, Rust-ядро) | **PARK** (не пре-альфа, но 5 мес + wrong regime: выигрывает на 100K+ fp32, у нас <1M × 128-byte) | **Скальпели в quantize.py**: length-renormalization scalar (`‖v‖/⟨u,x̂⟩` на encode — убирает систематическое занижение IP после квантования, бесплатный recall-буст) + per-coordinate calibration по ~1024 строкам (до +2.2pp R@1). Пара десятков строк numpy. | -| 4 | msgspec (BSD-3) | PARK | Только если micro-bench покажет pydantic >20% CPU на L0-записи (SQLite insert дороже — маловероятно). Несовместим с pydantic-моделями = дрейф схем. | -| 5 | SQLModel | **НЕТ** | 🚩 репо пересоздан 2026-02: 10★/1 contributor/0 releases (исторический tiangolo 14k+★ больше не там). Под капотом SQLAlchemy ORM. | -| 6 | SQLAlchemy 2.0 | НЕТ (уже dep alembic) | 10k-строк raw SQL → ORM = регрессия (теряем FTS5-контроль). Core как typed query-builder — точечно на новых модулях. | -| 7 | PageIndex (35.5k★, MIT) | НЕТ (сервис) | Это LLM-агентный спуск по дереву-оглавлению (LLM-вызов на query), не структура данных. **Механика переносима целиком**: wiki-L4.5 drill-down = TOC-дерево, которое строим сами (parent/child + node-summary off-line), retrieval = детерминированный спуск — «PageIndex без LLM-налога», бьёт similarity-search на структурированных вики. | - -Порядок: №2 (час) → №1 (день + бенч утечек person/org до/после) → идеи №7 в бэклог → №3 bench-условие. -- 2026-09-04: v25 — движки (7 репо): autocontext outcome-gated promotion = недостающий дизайн G2-гейта (false-promotion budget); cocoindex memo hash(вход)+hash(КОД) подтверждает replay-config-hash; delta-only graph_enrich; semantica decision-read-поверхность (trace/impact/precedent) для пустого causal-графа + PROV-O + point-in-time snapshots; txtai kNN-подграф = дешёвый similar_to генератор; open-index zero-result-минер ВСТРАИВАТЬ (recall_events уже есть) + file|index split подтверждён; code-graph-rag static+runtime overlay. -- 2026-09-04: v26 — библиотеки (7): ВСТРАИВАТЬ spaCy sm (privacy NER-тир + entity-минер; regex-only = ложная безопасность) + fractional-indexing (CC0, vendor, order_key O(1)-вставки); PARK turbovec (скальпели: length-renorm + TQ+ калибровка в quantize.py — 2 строки numpy, recall-буст) и msgspec; НЕТ SQLModel (репо пересоздан, 10★ 🚩) и PageIndex (LLM-сервис, механика TOC-спуска украдена для wiki-L4.5). - -- 2026-09-04: v27 — ITS/EDM объяснение найдено пользователем (после долгих поисков; в Memanto-статье формул не было). Формулы и разбор применимости к SQLite-стеку: - -### EDM/ITS — формулы и наш путь реализации - -**EDM (Efficient Distance Metric)** — концептуально: -``` -EDM(m|q) = H(Y|q) − H(Y|q, m) # прирост информации о Y при добавлении памяти m к запросу q -``` -Блок высоко scored если: восполняет недостающий факт / однозначно определяет сущность-событие-инструкцию / разрешает противоречие / завершает цепочку рассуждений / делает вероятный ответ определённее. **Семантически похожий блок может получить НИЗКИЙ score если повторяет уже имеющееся** (косинус задает «какое воспоминание похоже?», EDM — «какое расскажет больше всего?»). - -**Практическая форма** (их же): `EDM(m|q,S) = α·R(m,q) + β·N(m,S) + γ·G(m,q,S) − δ·K(m)` где R=relevance, N=неохваченная контекстом информация, G=предполагаемый ответ/улучшение reasoning, K=избыточность/стоимость. - -**ITS** (это НЕ стандарт — внутреннее имя Moorcheh; вероятная форма): -``` -ITS = I(C;Q) / H(Q) # mutual information(чанк, запрос) / энтропия(запрос) -``` -1 = чанк полностью исчерпывающе отвечает; 0 = ноль полезной информации. **Детерминизм**: score зависит только от (чанк, запрос), НЕ от модели/индекса/данных — «дай все где ценность ≥0.8» воспроизводимо vs «топ-5 похожих» меняется. - -### Применимость к a-memory (SQLite brute-force scan — EDM/ITS реализуемы без ANN) - -**Члены формулы — почти всё уже есть в стапе:** -- **R (relevance)** = наш FTS5-rank + Hamming-score (есть). -- **N (novelty/неохваченное)** = вычислимо: BM25-термы запроса, уже покрытые выбранным контекстом S, vs термы чанка (set-операции над токенами — дёшево). -- **G (gain/логика)** = частично A-MEM/ITS-gating; без LLM — эвристики (semantic-dedup cosine уже даёт «повторяет ли»; завершённость цепочки — минер #6 led_to-маркеры). -- **K (redundancy/cost)** = наш semantic-dedup (cosine >0.92, третий сигнал консенсуса) + memory_compress-стоимость. - -**Дешёвый суррогат EDM для shortlist** (SQLite-scan даёт full control — EDM можно вычислить по top-100 candidates без ANN-ограничений): -1. Первый проход: RRF (k=60) → top-100 candidates (recall-first, LongMemEval-принцип). -2. Второй проход (EDM-фьюжен): переранжировать по `α·R + β·N + γ·G − δ·K` — где N/K детерминированы (set-операции), R из существующих скорингов, α/β/γ/δ калибруются на №11-eval (LongMemEval-протокол с oracle-абляциями). -3. ITS-нормализация: min-max финального скора в [0,1] per-query + threshold gating (их 0.05-порог) → **детерминизм: same query → same results** (их ключевое свойство для reproducible агентов). - -**Порядок внедрения (F→H):** -1. **F**: threshold-gating вместо fixed-k (LME/ablation показали: recall-first главный рычаг +28.4pp). -2. **H №11**: реализовать EDM-фьюжен как ablation-arm (baseline RRF vs +N/K члены vs полный EDM) — измерить на LongMemEval, калибровать α/β/γ/δ. -3. **ITS-детерминизм** — флаг `deterministic_retrieval` (для регламентированных сценариев; по умолчанию off). - -**Важная оговорка**: их цифры (89.8% LME) — платформенные (Moorcheh cloud, proprietary engine). Наш путь — воспроизвести ПРИНЦИП (uncertainty-reduction + threshold gating) на открытом стеке и доказать цифрами №11. Ссылка 2601.11557 остаётся точкой для EDM-формулы (в Memanto её нет). -- 2026-09-04: v27 — ITS/EDM формулы найдены пользователем (в Memanto их не было). EDM = H(Y|q)−H(Y|q,m) прирост информации; практическая форма α·R+β·N+γ·G−δ·K; ITS = I(C;Q)/H(Q) ∈ [0,1], детерминизм = score зависит только от (чанк, запрос). Путь: F threshold-gating вместо fixed-k; H №11 EDM-фьюжен как ablation-arm (α/β/γ/δ калибруются); члены R/N/G/K почти все уже в стапе (FTS-rank, semantic-dedup=K, set-операции=N). Оговорка: их цифры платформенные, наш путь — воспроизвести принцип на открытом стеке. - -- 2026-09-04: v28 — батч A (general-14): Memanto-экосистема + memory-type статьи (12 бумаг). - -### Батч A — топ-находки - -1. **LEANN (2506.08276) ⭐ — recompute-don't-store для холодного тира**: индекс ≤50× меньше, ~5% от исходного размера, SOTA accuracy, латентность сопоставима — **эмбеддинги пересчитываются на лету, не хранятся**. → **F 🔑**: убивает вопрос «архивировать ли квантованные эмбеддинги в CLACK» — холодный тир хранит ТОЛЬКО сырой текст, re-embed при запросе; MIB-quantize остаётся для hot/warm только. -2. **RaBitQ (2405.12497, SIGMOD 2024) — MIB-upgrade**: error-bounded 1-bit/dim квантизация с ДОКАЗАННОЙ границей ошибки — 384-dim → 48 bytes (vs наши 128) c bitwise/SIMD distance. → **G**: наша 128-byte Hamming без error-bound; RaBitQ строго лучше обоснован и меньше. -3. **ENGRAM (2511.12960) — procedural как ТРЕТИЙ роут-тип**: our kind-routing invariants→L4 / events→L3; добавить how-to/procedure как роутимый kind (ложится на wiki L4.5) → **F 🔑**. Цифры: SOTA LoCoMo; **+15 pts LongMemEval при ~1% токенов** — «single router + plain dense-per-type + set-merge». 🔴 **Вызов**: наш 5-source RRF обязан доказать себя в 5-stage ablation против dense-per-kind (ENGRAM-style); не выиграет — режем источники. -4. **APEX-MEM (2604.14362) — fuse-then-summarize на read**: после 3-option конфликт-фьюжена сжимать разрешённое evidence в ОДИН contextual summary перед инъекцией → **G**. Планка: **88.88% LoCoMo / 86.2% LongMemEval** (ACL 2026). Независимая валидация append-only + read-time resolution + temporal grounding (наш точный bet). -5. **LLM-Independent Adaptive RAG (2505.04253) — pre-gate над RRF**: 27 внешних query-features (7 групп) решают «ретривить ли вообще» БЕЗ LLM — матчит LLM-based adaptive методы на 6 QA-датасетах → **F**: наша пайпа фаерит все 5 источников всегда; feature-gated skip = дешёвый тир над ними (cost + меньше шума) + питает N_eff/abstention. -6. **SHIMI (2504.06135)**: Merkle-DAG sync + CRDT conflict resolution + Bloom-фильтры как sync-prefilter → **G/парк**: только если multi-device a-memory появится на roadmap (наш conflict contract — single-store). -7. **SSR (2605.30120)**: SAE sparse codes → inverted index (без кластеринга), 15× быстрее индексация vs ColBERTv2, ~2× ниже латентность, лучше BEIR → **H**: альтернативный дизайн embedding-минера на существующей BM25-инфре. -8. **REWA (2512.00378) — теория для MIB**: rate-distortion bound как yardstick «достаточно ли 128 bytes» (witness-overlap = mutual information; O(Δ⁻² log N) оптимум для rank preservation) → **H** (анализ, без эмпирики). -9. **Merlin (2605.09990) — dedup перф-бюджет**: xxHash3-64 + SIMD flat set, 8.7 GB/s, 13.9–71% reduction → **F**: yardsticks для нашего dedup-гейта (impl открыт: corbenicai/merlin-community). -10. **LoCoMo дельта**: датасет шипит event-summarization + temporal/causal задачи помимо QA → добавить event-summarization в №11. - -### Cross-cutting батчи A - -**LEANN+RaBitQ вместе ставят под вопрос хранение эмбеддингов вообще**: cold — не хранить (recompute), hot/warm — error-bounded 48B вместо 128B. **ENGRAM+AdaptiveRAG** толкают «less machinery, gated» → 5-stage ablation обязана включить arms: «RRF 5-source vs dense-per-kind (ENGRAM) vs gated-retrieval (AdaptiveRAG)». Если RRF не выиграет — режем. -- 2026-09-04: v28 — батч A (12 бумаг, Memanto-экосистема+memory-types). Топ: LEANN recompute-don-t-store (холодный тир без эмбеддингов, ≤50× shrink); RaBitQ 48-byte error-bounded (MIB-upgrade); ENGRAM procedural как 3-й роут-тип + вызов 5-source RRF (dense-per-kind +15pts @1% токенов — ablation обязан включить arms RRF-vs-dense-per-kind-vs-gated); APEX-MEM fuse-then-summarize (88.88/86.2 планка); Adaptive RAG 27 query-features без LLM как pre-gate; Merlin dedup yardsticks. - -- 2026-09-04: v29 — батч D (general-17, memory-systems, 12 бумаг). Формул MIB/EDM/ITS в 2601.11557 НЕТ (подтверждено пользователем; explore-2 отменён — суррогат-путь из v27 остаётся). - -### Батч D — топ-находки - -1. **LycheeMemory V2 (2608.12990, HIT) — segment-level consolidation**: батч обменов в семантически-связные сегменты (surprise/cohesion boundary detection), ОДИН LLM-вызов кодирования на финализированный сегмент (−86% construction tokens); **bounded disambiguation carry-forward** (entity-alias буфер между прогонами консолидации — канонические ключи стабильны без re-read истории) → **F 🔑**: nightly консолидирует L0 батчами на семантических границах, не per-message. Цифры: 89.22 LoCoMo / 92.20 LME-S. Признание: preference/persona вопросы требуют dedicated profile modeling (их слабость = наш L4 user-profile). -2. **Memora (2602.03315, Microsoft, ICML'26) — primary abstraction vs cue anchors**: каноническая identity decoupled от value; **non-exclusive anchor keys** дают many-to-many доступ, авто-prune при orphan → **G**: orphan-anchor GC для derived-ключей минеров. **Merge-threshold evidence: 0.8→0.6 similarity = 3.4× больше мержей при НУЛЕВОМ приросте accuracy** (update ratio стабилен 16.5-22.2% при росте стора) → **F 🔑**: прямое обоснование консервативного ITS threshold. RAG и KG retrieval = частные случаи одного retrieval framework (теория для multi-route). 86.3 LoCoMo / 87.4 LME. -3. **EverMemOS (2601.02163) — foresight signals с validity intervals**: forward-looking инференсы (планы, temporary states) хранятся с [t_start, t_end], read-time фильтр t_now ∈ interval → **F/G**: расширяет validity windows на ПРОСПЕКТИВНЫЕ факты (temporary states получают expiry, не только рёбра). **Sufficiency-check JSON** {sufficient, key_info_found, missing_information} + 2-3 targeted queries по named gaps → **H**: контракт для self-healing conflict reader. 93.05 LoCoMo (+9.2% rel.) / 83.0 LME. -4. **Mnemis (2602.15313, ACL'26) — System-2 Global Selection**: иерархический граф даёт top-down ИСЧЕРПЫВАЮЩИЙ traversal для aggregate-вопросов («list all X»), где similarity retrieval СТРУКТУРНО фейлится → **G 🔑**: hierarchical rollup (topic→scene→facts) как ОТДЕЛЬНЫЙ маршрут — у нашего RRF+EDM нет exhaustive-route. 93.9 LoCoMo / 91.6 LME-S. -5. **AdaMem (2603.16496) — question-conditioned routing**: resolve target participant first, graph-walk только когда вопрос реляционный → **G**: дешёвый question-classifier гейтит граф-проход (пара к ITS threshold gating). SOTA LoCoMo/PersonaMem. -6. **D3ST (2201.08904) — canonical keys как natural-language descriptions**, не terse ID → **F**: лучше match/merge accuracy + zero-shot generalization (принимаем к каноническим ключам). -7. SSET (NAACL'24): semantic AND structural agreement при edge-typing (подавление false negatives) → **G** маргинально. CompassMem: navigation-as-retrieval → H опция. ViLoMem: «grow-and-refine» naming. Memory survey: 6 операций — покрыто нашим 7-операторным суперсетом. -Cross-cutting: **LoCoMo SOTA band теперь 86–94** (GPT-4.1-mini класс); **три статьи независимо: гранулярность/батчинг консолидации — доминирующий cost lever**, не то ЧТО хранится. - -- 2026-09-04: v30 — батч E (general-18, 9 бумаг; 3 SKIP по флагам — читаю сам). - -### Батч E — топ-находки - -1. **xMemory (2602.02007) — СИЛЬНЕЙШЕЕ внешнее валидация wiki L4.5 + consolidation-операторов**: - - **Decouple-before-aggregate**: атомарные memory-компоненты (facts/constraints/updates) извлекаются ДО группировки; retrieval units изолируют дельту между near-duplicate историями → **G**: L4 extraction эмитит компоненты, wiki агрегирует над компонентами (не над raw spans). - - **Uncertainty-gated top-down expansion**: compact backbone расширяется до raw только пока marginal predictive-entropy reduction > 0, стоп при нуле → **F**: заменяет fixed top-k на evidence-utility budget (согласуется с EDM/ITS). - - **Retroactive restructuring**: ночной split/merge групп; frozen structure = 0% reassignment F1 38.59 vs full 44.91% ratio → 43.98 → **G**: наши consolidation-операторы. - - Верифицировано: LoCoMo Qwen3-8B 34.48/43.98 @ 4,711 tok/query; group cap 12 (avg 4.48). Оговорка: component extraction LLM-based (натяжка с детерминированными минерами). -2. **RoMem (2604.11544, EMNLP'26) — policy-слой для bi-temporal L4** → **G/F 🔑**: **relation-volatility gate** (per-fact volatility из семантики отношения: «президент» ротируется быстро, «родился» стабильно) — decay/ревизия per volatility class вместо uniform recency; **geometric shadowing**: superseded факты НИКОГДА не удаляются — поворачиваются по фазе, темпорально-корректные outrank противоречия (усиливает ITS-демоушен). Детерминированный прокси: predicate-type volatility таблица. ICEWS05-15 72.6 MRR SOTA. -3. **Infini Memory (2606.10677) — topic document как revision unit**: наблюдения staged в буфер, периодически консолидируются in-place с fact revision ВНУТРИ дока → **G**: контракт «buffer-stage → consolidate» = интерфейс nightly-оператора (near-1:1 с нашим L0→L4.5). Iterative agentic read — конфликт с single-pass deterministic (парк). -4. **Aeon (2601.15311) — Semantic Lookaside Buffer**: сессионный hot-fact cache ПЕРЕД RRF (sub-5µs hits) → **F**: genuinely new для hot path. Sidecar blob arena + generational GC — deferred до SQLite-paper (она у меня в чтении). -5. **BookRAG (2512.03413) — BookIndex**: ToC-hierarchy + entity→node backlinks (graph-navigational retrieval над topic docs) → **G** wiki-L4.5; **information-foraging query router** (classify query type → tailored workflow) — read-path аналог write-side kind-routing → **F**. Цифр в abstract нет. -6. **SuperLocalMemory V3.3**: **Ebbinghaus forgetting coupled to storage fidelity** (decay = progressive embedding quantization, never delete) → **G**: L4 aging — старые факты теряют точность эмбеддинга, не существование (ложится на bi-temporal); **spreading activation как 4-й retrieval channel** → F кандидат. Оговорки: single author, Elastic License, unverified, V3.3 regression. -7. **LatentGraphMem**: fixed-budget symbolic subgraph interface как observability/eval surface (H: dump точно того, что retrieval выставил). PRISM Edit/SCM: skip/параметрика. -Cross-cutting: **«deterministic proxy для learned gate» — повторяющийся паттерн адаптации** (3 статьи зависят от обученных компонент); 3/9 — trained components (детерминированный a-memory не блокируется, адаптируется). - -- 2026-09-04: v31 — батч B (general-15, графы/KG, 12 бумаг; 2 full-HTML: GAAMA, Mandol). - -### Батч B — топ-находки → G - -1. **GAAMA (2603.27910, full HTML) — два измеренных урока для минеров**: - - **Mega-hub dampening ≠ exclusion**: person-узлы накапливают 400-500+ рёбер; dampening (θ/deg, θ=50) дал лишь marginal relief — **независимо подтверждает MOC-hub EXCLUSION** (v24 Ar9av); их topic-concept fix даёт ~30× sparser граф. - - **PPR calibration**: edge-type-aware веса w_base(t), per-source normalization; additive score = cosine + w_ppr·PPR, **w_ppr=0.1**; PPR=1.0 scored WORSE semantic-only → **G**: граф = tiebreaker/neighborhood expander, никогда не override (подтверждает HippoRAG2-only posture). GRAFT (post-retrieval repair loop — диагностика провала → хирургическая вставка недостающих рёбер) превращает минеров из write-once в feedback-corrected, но требует LLM-вызов → парк. - - Верифицировано: LoCoMo-10 79.1%, +4.2pp; graph-augmented только +1.2pp над semantic-only (тонко — согласуется с «graph underperforms dense»). -2. **Mandol (2606.29778, cs.DB, full HTML) — SOTA LoCoMo+LME, retrieval полностью LLM-free** (валидация deterministic-минеров): - - **MAD-denoising порог**: τ = median(S) − κ·MAD(S) — distribution-free, робастный к outlier-скам вместо fixed cutoff → **G 🔑** (замена fixed miner-score cutoffs). - - **Conflict arbitration**: конфликт = «same entities, inconsistent descriptions» ИЛИ «два abstract-узла трассируются к одному basic memory ID» (наши provenance-мосты = детектор конфликтов!); арбитраж по relevance × freshness × source confidence. - - 5.4× retrieval / 4.8× insertion speedup @ 10 QPS. -3. **D-Mem (2603.18631) — quality-gated escalation**: дешёвый dense-first; мульти-хоп граф-reranker ТОЛЬКО при провале confidence-гейта; **восстанавливает 96.7% Full-Deliberation F1 при меньшем cost** → **G 🔑**: дизайн-паттерн, примиряющий HippoRAG2-only stance с сохранением графа (graph = gated fallback, не default). -4. **ElephantBroker (2603.25097, user-flagged) — 4-state evidence verification**: unverified → verified → stale → invalid (state machine на фактах/рёбрах; окна становятся ПЕРЕХОДАМИ, а не датами) → **G** (усиление Graphiti validity windows); 9-stage consolidation: strengthen-useful/decay-noise периодический проход (time-decayed reinforcement вместо статичных miner-весов). Оговорка: ноль внешних бенчей (2,200 internal tests) — inspiration, не evidence. -5. **Youtu-GraphRAG (2508.19855) — dually-perceived communities**: Louvain-структурные + embedding-similarity контента подграфа (semantic merge/split pass после louvain) → **G**: прямой upgrade чисто-структурных A1.6-комьюнити; 90.71% token-saving, +16.62% accuracy. -6. **Cognis (2604.19771) — pre-extraction versioning**: перед записью обновлённой памяти retrieve пересекающихся существующих + record version/supersede links → **G**: расширяет mutation ledger на РЁБРА (supersede-рёбра при истечении validity windows, не удаление); query-time freshness boost. SOTA LoCoMo+LME. -7. **MAGMA (2601.03236) — orthogonal typed views**: 8 минер-сигналов как независимо-toggleable edge views; per-query subset (temporal Q → session-proximity + validity only) вместо blended — эффект достижим фильтрацией одной typed edge table (4 физ. графа — оверинжиниринг) → **G**. -8. **AtomicRAG (2604.20844)** — валидация: атомарные self-contained facts + bare «relationship exists» рёбра (triple-extraction errors ломают reasoning paths); atom reassembly per query perspective. GraFine: SPX/GSR alternation (semantics-aware ADD + graph-aware PRUNE under time budget, no LLM) + **Topological Recall метрика** → G/H. -Cross-cutting: **3 статьи сходятся — минеры должны быть feedback-corrected post-retrieval, не write-once** (Mandol/GAAMA-GRAFT/ElephantBroker); numbers quality: только GAAMA/D-Mem/Mandol/Youtu публикуют цифры. - -- 2026-09-04: v32 — батч C (general-16, retrieval/RAG, 12 бумаг). 2 ID-mismatch пойманы (2302.13253 ≠ LLMLingua (настоящий 2310.05736), LLMLingua-2 = 2403.12968, LongRAG = 2406.04224/2410.18050) — ledger поправлен. - -### Батч C — топ-находки → F/H - -1. **PrecisionMemBench (2605.11325) — precision-aware benchmark**: precision / noise isolation / session latency / **belief mutability** decoupled от answer quality; **whole-store dump = perfect recall, precision ≤0.22** (квантификация dump-everything failure) → **H 🔑**: precision+noise-isolation колонки рядом с LongMemEval. **Tenure** (их система): resolve scope+retrieval **детерминированно ДО inference**, inject typed beliefs as ambient instruction = production-валидация «deterministic first, LLM as filter» (ITS thesis). -2. **MOSS (2607.04391) — 1 год продакшена с нулём LLM в retrieval loop** (символьно, репродуцируемо, каждый шаг логируется) = сильнейшее public existence proof ITS-тезиса → **F** (validation). **Corpus-derived concept vocabulary** (569 concepts inductively, 322k аннотаций, без внешней онтологии) — паттерн: wiki/scratchpad-майнинг бутстрапит собственную таксономию. Масштаб: 44M токенов, 110k сегментов, 4 infra generation. -3. **SYNAPSE (2601.02744) — lateral inhibition**: spreading activation propagates relevance, **inhibition подавляет коррелированных соседей** → **F 🔑**: конкретный diversity-оператор для EDM re-rank (прямой ответ на wave-3 challenge «RRF loudest-wins / correlated evidence»). Triple hybrid = embeddings ⊕ activation-based graph traversal. LoCoMo SOTA temporal+multi-hop. -4. **2507.19715 — semantic compression = submodular-coverage selection** (representative set поверх top-k; facility-location дёшево при k≤100) → **F**: принципиальный diversity-aware фьюжен вместо ad-hoc MMR на EDM-стадии (второй оператор против correlated evidence). -5. **LGESQL (2106.01093) — edges-as-nodes trick**: line graph делает свойства рёбер скорабельными тем же ranker'ом → **F**: stale/low-confidence рёбра демоутятся без special-casing. -6. **H-MEM (2507.22925) — positional index encoding**: память несёт указатели на sub-memories следующего слоя; layer-by-layer routing без exhaustive similarity → **F**: coarse-to-fine candidate generation, чтобы дёшево попасть в k≤100 бюджет across 5 источников. Оговорка: hierarchy как read-time routing (write-time reorg конфликтует с Mem0 ADD-only уроком). -7. **MemTree (2410.14052) — incremental tree adaptation** (новое маршрутизируется в агрегированные узлы по embedding similarity, без offline rebuild) → **F**: кандидат для wiki-кластеризации/L4 topic nodes если flat retrieval сатурация. MiniRAG (2501.06713): semantic-aware heterogeneous graph + topology retrieval для SLM-режима (25% storage) — niche. -8. **PlugMem (2603.03296) — propositional + prescriptive knowledge как unit**: добавить **prescriptive node kind** в kind-routing (сегодня: invariants/events/facts) → **F** (рядом с D2.2). Verified: unchanged module бьёт baselines на 3 гетерогенных бенчах. -Cross-cutting: **MOSS + Tenure независимо подтверждают deterministic-retrieval-before-LLM** (ITS thesis — два production источника); **SYNAPSE inhibition + submodular coverage = два именованных diversity-оператора** для EDM-стадии. -- 2026-09-04: v31 — батч B (графы/KG, 12 бумаг): GAAMA измерил PPR calibration (w_ppr=0.1 additive, 1.0 хуже dense) + mega-hub dampening-фейл (exclusion подтверждена); D-Mem quality-gated escalation (dense-first, graph fallback = примирение HippoRAG2-stance с графом, 96.7% recovery); Mandol MAD-порог + provenance-based conflict detection, LLM-free retrieval SOTA; ElephantBroker 4-state evidence machine; Cognis supersede-рёбра; Youtu dually-perceived communities. Cross: минеры = feedback-corrected post-retrieval, не write-once (3 статьи). -- 2026-09-04: v32 — батч C (retrieval, 12 бумаг; 2 ID-mismatch пойманы: LLMLingua=2310.05736, LLMLingua-2=2403.12968, LongRAG=2406.04224): PrecisionMemBench precision≤0.22 при dump (H-метрики: precision/noise-isolation/belief-mutability); MOSS 1 год продакшена zero-LLM retrieval (existence proof ITS-тезиса) + corpus-derived vocabulary; SYNAPSE lateral inhibition + submodular coverage = 2 именованных diversity-оператора для EDM; Tenure deterministic-before-inference; H-MEM positional index для k≤100; LGESQL edges-as-nodes; PlugMem prescriptive node kind. - -- 2026-09-04: v33 — 5 флагнутых статей прочитаны лично (abstract-уровень; full-тексты при планировании №11). - -### Мои 5 статей — вердикты - -1. **2608.24060 — scrydb «SQLite is Enough»** (Timo Breuer, MIT, код открыт): **академическое подтверждение нашего всего retrieval-стека** — lexical (FTS5) + semantic (sqlite-vec) + hybrid rank-fusion в SQLite, оценено на IR-бенчах с latency-трейд-оффами. → **F 🔑**: ближайший референс нашей архитектуры; **embed candidates: рассмотреть sqlite-vec extension** как опцию вместо/рядом с Hamming-scan (тот же SQLite, native vector ops, MIT). «SQLite is Enough» = название, валидирующее весь local-first выбор. -2. **2601.10080 — CDT: Codified Decision Trees (персон-логика из сюжетных линий)**: behavioral profiles = дерево условных правил (внутренние узлы = validated scene conditions, листья = grounded behavioral statements), **детерминированный retrieval правил на исполнении**; induction: candidate rules → validation → hierarchical specialization. Опередил human-written profiles и prior induction на 85 персонах. → **G/F**: паттерн применим к нашему personality/agent-facts слою — правила персон как кодифицированное дерево вместо неструктурированного профиля; deterministic rule retrieval пары с standing queries. Но: persona-эволюция остаётся MUSE-территорией (D3.x) — берём только механику codified rules, не применение к личности. (Ref: hivemind.md у Люси — прямое применение!) -3. **2512.22280 — Valori: Deterministic Memory Substrate**: float-арифметика даёт **недетерминизм между архитектурами** (x86 vs ARM): identical inputs → разные memory states/retrieval. Valori: fixed-point Q16.16 + memory as replayable state machine → **bit-identical states/snapshots/search results across platforms**. → **H/F**: наш retrieval-трейс (D4) и replay (F3) зависят от этой проблемы — SQLite сам по себе детерминирован на чтение, но float32-эмбеддинги + векторные op'ы не гарантируют bit-identical при переносе между машинами. Valori (open-source) — референс если захотим bit-exact replay across VPS-переездов. Сейчас: пометить как известное ограничение (наши бэкапы x86-only — ок). -4. **2605.09990 — Merlin (уже в v28 от батча A, это первоисточник)**: local-first, SIMD-friendly open-addressing flat hash + xxHash3-64, byte-exact dedup, 13.9–71% reduction, 8.7 GB/s; **MCP-интеграция описана прямо в статье** (zero-network-interception deployment для IDE/agents). → подтверждение: наш dedup-гейт может подняться до их performance-уровня бесплатно (impl открыт, corbenicai/merlin-community). -5. **2604.14362 — APEX-MEM (первоисточник, уже в v28)**: property graph с domain-agnostic ontology, **append-only storage preserving full temporal evolution**, multi-tool retrieval agent резолвит конфликты at query time → compact summary. ACL 2026, 88.88/86.2. Дополнение к v28: у них ontology domain-agnostic (наша kind-routing типизация — конкурентный подход), и их retrieval-agent — это multi-тул (дороже нашего single-pass EDM-плана; их compact summary = наш фьюжен+инъекция). - -### Сводка моих 5 - -| Статья | Вердикт | Фаза | -|---|---|---| -| scrydb (SQLite is Enough) | 🟢 референс + sqlite-vec кандидат | F/H | -| CDT (персон-деревья) | 🟢 механика codified rules | G (не persona!) | -| Valori (deterministic substrate) | 🟡 известное ограничение | H (replay-заметка) | -| Merlin (dedup) | ✅ первоисточник v28 находки | F | -| APEX-MEM | ✅ первоисточник v28 находки | G | - -(2601.11557: формул нет — подтверждено пользователем; суррогат-путь EDM/ITS из v27 принят.) -- 2026-09-04: v33 — 5 флагнутых статей прочитаны лично: scrydb «SQLite is Enough» (академическое подтверждение всего retrieval-стека + sqlite-vec как embed-кандидат), CDT codified decision trees (механика правил персон для G, persona остаётся MUSE), Valori deterministic substrate (float32 недетерминизм x86/ARM — заметка для replay), Merlin первоисточник (MCP-интеграция прямо в статье), APEX-MEM первоисточник (ACL 2026, domain-agnostic ontology). - -- 2026-09-04: v34 — EDM.md (транскрипт Aurelle, 467 строк): реконструкция формул MIB/EDM/ITS + аудит статьи 2601.11557. ВОПРОС «ВЫТЯНУЛИ ЛИ МЫ ВСЁ» ЗАКРЫТ: формул в статьях действительно нет (реконструкция = модель для иллюстрации), и там где есть измерения — **наш простой quantize.py ≈ их fancy MIB**. - -### Формулы (реконструкция Aurelle — модели, не публикации) - -1. **MIB цель**: не локальная ошибка ‖x−x̂‖², а **сохранение порядка релевантности** s(x,y)>s(x,z) ⟹ ŝ(b(x),b(y))>ŝ(b(x),b(z)); возможная формулировка max[Σ I(B_i;Y) − β·Σ I(B_i;B_j)] (биты полезные для поиска, штраф на дубли). Энтропия бита H(B_i)≤1, пик при p=0.5; бит с p=0.99 почти ничего не сообщает. -2. **EDM**: база = XOR+popcount (u·v = m−2·d_H, s_H = 1−d_H/m); информационная версия — **взвешенный Hamming** d_wH = Σ w_i·1[b_i≠c_i], w_i = I(B_i;Y) или log(1/P(B_i=b_i)) — редкий информативный бит весит больше тривиального. ⚠️ это модель, статья EDM-формулу не утверждает. -3. **ITS**: Gain(x|q) = H(Y|q)−H(Y|q,x) = I(Y;X|Q); обобщённо ITS(q,x) = σ(α·I(Y;X|Q) − β·d_EDM + γ·r(x) + δ·m(q,x)), нормализация per-query (g−g_min)/(g_max−g_min), retrieval R(q) = {x: ITS ≥ τ} — **переменное количество документов по сложности запроса**. ITS = встроенный reranker (EDM → кандидаты, ITS → порядок) — убирает дорогой внешний reranker. ⚠️ нужна модель релевантности Y (обучение/калибровка) — механизм не раскрыт. - -### Аудит статьи 2601.11557 (Aurelle, честно) - -- Confounds сравнения: Pinecone тестили С внешним Cohere reranker, Moorcheh — со встроенным ITS; exhaustive scan vs HNSW; разная инфраструктура; **нет абляции MIB-vs-sign, EDM-vs-Hamming, ITS-vs-reranker в равных условиях**. -- 🔑 **КРИТИЧНО ДЛЯ НАС: авторы прямо пишут — BBQ, information-theoretic бинаризация и простая sign-бинаризация иногда дают СОПОСТАВИМОЕ качество; итог сильнее зависит от индексации и scoring**. → Наш quantize.py (sign) НЕ хуже fancy-MIB; гоняться за глубиной MIB не нужно. Ценность архитектуры = bitwise scan + ITS-порог, что у нас уже в плане (v27). -- Честные цифры MAIR: LegalQuAD 66.73% NDCG@10, LeCaRDv2 66.17, ConvFinQA 74.30; distance-only 9.6ms; end-to-end 219.4ms vs 1448.6 (Pinecone+Cohere). Задержка Moorcheh растёт с корпусом (exhaustive scan) — на малых масштабах (наш случай) не проблема. - -### Новые указатели из поиска Aurelle (в бэклог G/H) - -- **TOKI: Bitemporal Operator Algebra for Contradiction Resolution in LLM-Agent Persistent Memory** — алгебра разрешения противоречий поверх bi-temporal — прямой комплемент нашего conflict-fusion (G). -- **PROJECTMEM: Local-First, Event-Sourced Memory and Judgment Layer** — родня нашего L0. -- **Reliable Post-Retrieval Assembly: Separating Evidence Extraction from Policy Execution** — сепарация extraction/policy = наш gate-дизайн. -- **Presentation, Not Mechanism: Render Confound in Deprecation-Aware Memory Evaluation** — ловушка eval (deprecation-aware) → H. -- **Covariance Structure and Coordinate Heterogeneity Govern Binary Quantization** — почему sign-бинаризация работает/ломается → H (обоснование quantize). - -### Вердикт по EDM/ITS-дорожке (финал) - -1. quantize.py оставляем как есть (sign ≈ fancy — подтверждено их же данными); опционально взвешенный Hamming (w_i = log(1/P)) — 5 строк, дёшево. -2. ITS-нормализация per-query + threshold gating — главный рычаг (подтверждён Memanto-абляцией +28.4pp), реализуем без модели релевантности (min-max по кандидатам). -3. EDM deep-theoretic (взаимная информация с Y) — требует обученной модели релевантности = против no-LLM принципа; парк до появления labeled-данных из №11. -4. TOKI/PROJECTMEM/render-confound — в бэклог G/H указателями. - -- 2026-09-04: v35 — deep-dive general-19 (4 статьи full-HTML, формулы дословно). Поправки к брифу: «PrecisionMemBench» = **Tenure** (72 кейса, не 89×13); MOSS бенчмарков НЕ имеет вообще; H-MEM не упоминает RAPTOR. - -### SYNAPSE (2601.02744v2) — формулы spread activation + inhibition - -- **Propagation (fan effect, ACT-R-родня)**: u_i⁽ᵗ⁺¹⁾ = (1−δ)·a_i⁽ᵗ⁾ + Σ S·w_ji·a_j⁽ᵗ⁾/fan(j); w_ji = e^(−ρ|τi−τj|) (temporal ρ=0.01) или sim (semantic). -- **Lateral inhibition** (формула дословно): û_i = max(0, u_i − β·Σ_{k∈T_M}(u_k − u_i)·𝕀[u_k > u_i]), T_M = M top-potential; **β=0.15, M=7** — давит когерентные distractor-кластеры (CAMA-проблема). -- **Triple Hybrid**: S(v_i) = λ₁·sim + λ₂·a_i⁽ᵀ⁾ + λ₃·PageRank; λ={0.5,0.3,0.2}; top-k=30. -- **FOK-gate**: τ_gate=0.12 на activation топ-нодa → детерминированный reject ДО LLM (FRR<2.5%). -- Гиперы: S=0.8, δ=0.5, γ=5.0, T=3 (самый чувствительный), τ_dup=0.92, K=15 top-incoming, GC activation<0.01×10 окон → архив, |V|≤10000. -- **LoCoMo (GPT-4o-mini) avg 40.5 F1** (beat Zep 39.7, A-Mem 33.3; +7.2 к A-Mem); **абляция**: −decay δ=0 → Temp 50.1→**14.2** (крах!); −fan → MH 35.7→30.2; −inhibition → Open 25.9→22.4; vectors-only 25.2. -- → **G/F порт**: inhibit-оператор с β≈0.15/M=7 как pre-EDM шаг (коррелированные distractor-кластеры) + τ_gate≈0.12 reject до LLM. - -### MOSS (2607.04391v1) — 1 год продакшена: как устроен zero-LLM loop - -- Масштаб: 110,183 сегмента ≈ **44M токенов**, ~600 файлов, SQLite ~5GB, 163k файлов каталогизировано, 4 поколения инфраструктуры. -- **Concept vocabulary 569**: (1) hand-injected seed-скелет, (2) большинство — **LLM keyword extraction индуктивно из корпуса** (Word2Vec/FastText реализованы и ОТБРОШЕНЫ по качеству; внешние таксономии тоже), (3) named entities. Codebook thematic analysis: коды эмерджентны из данных, применяются консистентно (322,662 аннотаций). Темы НЕ прекомпьютятся — собираются в query time группировкой концептов (**«query is sovereign»**). -- **Zero-LLM loop**: Intent [агент, стохастично] → QueryProfiler (структурные веса под интент: timestamps/valence-activation/concepts) → **Execution: детерминированный SQL (recursive CTE по 11 графам ~5M типизированных рёбер: co-occurrence, temporal adjacency, affective resonance (valence×activation, Russell circumplex), thematic cohesion), NO LLM** → Evaluation ≤15 reformulations. «Non-determinism confined to the edges; the deterministic core is traversed but never iterated». -- ⚠️ Бенчмарков НЕТ (A/B объявлен будущим) — «map usually suffices» (2-3 сегмента вместо ~50 чанков), affective-индексация одна из самых используемых на практике. Limitations: детерминизм только в execution, не formulation; single-user; нет защиты от memory-poisoning. -- → **F порт**: инверсия MOSS — **веса EDM (α,β,γ,δ) собираются в query time из интента** («relevance = свойство вопроса, не записи»), реляционные колонки в core_memory (timestamp/valence/concept) + лимит reformulations ~15 как цикл-гейт. affective resonance (valence×activation) — наш candidate для эмоционального слоя Эли. - -### Tenure (2605.11325) — precision-first контракт - -- **Таблица 1: 72/72 vs 8/72**; mean precision 1.0 vs **0.12**; recall 1.0 vs 1.0; drift 0.0 vs 0.43-0.50. Ключевой кейс: Redis-запрос → BM25 ровно 1 belief (score 11.71), cosine — все 12 в банде 0.667-0.799 (spread 0.132!) → **ситокосинусный band — враг precision**. -- **Deterministic-before-inference**: Stage 1 strip code/URLs → Stage 2 alias-weighted BM25 compound query (canonical+aliases, **14× boost**, fuzzy maxEdits=1 + prefixLength=2 блокирует mango→Mongo) → Stage 3 **hard scope filter POST-search** (два равных BM25 разводятся скоупом); pinned tier unconditional; counter-signal aliases (superseded-имена живут как aliases с негативной ролью, ceiling 25/белиф); promotion inferred→active = min reinforcement + min age. -- → **F порт**: «precision-first» двухступень — hard scope/status filter ПОСЛЕ RRF как discriminator (не weight); counter-signal алиасы в ITS; async extraction с validator/merger (4 ветки insert/reinforce/flag/skip). - -### H-MEM (2507.22925v1) — positional index routing - -- **4 слоя** Domain→Category→Memory Trace→Episode; **position encoding**: v_i⁽ᴸ⁾ = [semantic e_i, p_(i−1) self, p_i1..p_iK sub-memories] — similarity ТОЛЬКО по e, routing по указателям. -- **Алгоритм**: M_k⁽ˡ⁾ = ∪_{x∈M_k⁽ˡ⁻¹⁾} TopK_{y∈Child(x)} sim(q,y) — рекурсивный спуск; сложность **O((a+k·300)·D)** vs flat O(a·10⁶·D). Update: forgetting curve × feedback-weight (approval↑/none→decay/rebuttal↓). -- LoCoMo vs A-MEM (6 бэкбонов): **+14.98 F1 / +12.77 BLEU avg; MH +21.25; Adversarial +16.71**; efficiency: 7.34×10⁹→4.38×10⁷ ops (167×), <100ms vs >400ms. -- → **G/F порт**: positional-pointer структура L4-факт→ключевые→сессии с рекурсивным TopK-спуском — бюджет k≤100 без скана всего стора; feedback-модуляция листьев (наша importance — уже похоже). - -### Cross-cutting deep-dive - -**Все 4 сходятся: deterministic/structural gate ДО LLM** (SYNAPSE τ_gate, Tenure hard-filter, MOSS SQL-core, H-MEM pointer-routing) — ITS threshold-gating строить как **post-RRF hard discriminator, не weight**. **Correlated-evidence подтверждена извне дважды** (Tenure cosine-band 0.132 spread precision 0.12; SYNAPSE inhibition β=0.15) — EDM re-rank'у нужен inhibit-член по кластерам. ⚠️ 3 фактические поправки брифа зафиксированы (Tenure/72 кейса, MOSS без бенчей, H-MEM без RAPTOR). - -- 2026-09-04: v35 — deep-dive general-19 (4 статьи full-HTML, формулы дословно). Поправки брифа: «PrecisionMemBench» = **Tenure** (72 кейса: 60 static + 12 session, НЕ 89×13); MOSS бенчмарков НЕ имеет; H-MEM RAPTOR не упоминает. - -### SYNAPSE (2601.02744v2) — spread activation формулы дословно - -- **Propagation**: u_i⁽ᵗ⁺¹⁾ = (1−δ)·a_i⁽ᵗ⁾ + Σ S·w_ji·a_j⁽ᵗ⁾/fan(j); w_ji = e^(−ρ|τi−τj|) temporal (ρ=0.01) или sim. -- **Lateral inhibition** (формула): û_i = max(0, u_i − β·Σ_{k∈T_M}(u_k−u_i)·𝕀[u_k>u_i]), T_M = M top-potential; **β=0.15, M=7**. -- **Triple Hybrid**: S = λ₁·sim + λ₂·a + λ₃·PageRank (λ={0.5,0.3,0.2}, top-k=30); **FOK-gate τ=0.12** → детерминированный reject до LLM (FRR<2.5%). -- Гиперы: S=0.8, δ=0.5, γ=5.0, T=3 (самый чувствительный), τ_dup=0.92, K=15, GC <0.01×10 окон, |V|≤10000. -- LoCoMo avg **40.5 F1** (Zep 39.7, A-Mem 33.3); абляция: −decay → Temp **50.1→14.2 крах**; −fan → MH 30.2; −inhibition → Open 22.4; vectors-only 25.2. -- → **G/F**: inhibit-оператор (β=0.15, M=7) как pre-EDM шаг против correlated distractor-кластеров + τ_gate reject до LLM. - -### MOSS (2607.04391v1) — 1 год продакшена, анатомия zero-LLM loop - -- 110,183 сегмента ≈ **44M токенов**; SQLite ~5GB; 4 поколения инфраструктуры. -- **Concept vocabulary 569**: seed-скелет + **LLM keyword extraction индуктивно из корпуса** (Word2Vec/FastText отброшены по качеству; внешние таксономии тоже); codebook thematic analysis; **темы НЕ прекомпьютятся — собираются в query time («query is sovereign»)**. -- **Loop**: Intent [агент, стохастично] → QueryProfiler (структурные веса под интент) → **Execution: детерминированный SQL, recursive CTE по 11 графам ~5M типизированных рёбер (co-occurrence, temporal adjacency, affective resonance = valence×activation Russell circumplex, thematic cohesion), NO LLM** → ≤15 reformulations. «Non-determinism confined to the edges; deterministic core is traversed but never iterated». -- ⚠️ Бенчей НЕТ (A/B — future); «map usually suffices» (2-3 сегмента vs ~50 чанков); affective-индексация — одна из самых используемых на практике. Limitations: single-user, нет memory-poisoning защиты. -- → **F**: **веса EDM (α,β,γ,δ) собираются в query time из интента** («relevance = свойство вопроса, не записи»); affective resonance — кандидат эмоционального слоя Эли; reformulation-лимит. - -### Tenure (2605.11325) — precision-first контракт - -- **Таблица 1: 72/72 vs 8/72**; precision 1.0 vs **0.12**; recall равный 1.0; drift 0.0 vs 0.43-0.50. Кейс: Redis-запрос → BM25 ровно 1 belief (11.71), **cosine — band из 12 белифов 0.667-0.799 (spread 0.132!)** — ситокосинусный band = враг precision. -- **Deterministic-before-inference**: Stage1 strip → Stage2 alias-weighted BM25 (canonical+aliases **14× boost**, fuzzy maxEdits=1 + prefixLength=2 блокирует mango→Mongo) → Stage3 **hard scope filter POST-search** (равные BM25 разводятся скоупом); pinned unconditional; counter-signal aliases (superseded-имена с негативной ролью, ceiling 25/белиф); promotion = min reinforcement + min age. -- → **F**: hard scope/status filter ПОСЛЕ RRF как discriminator (не weight); counter-signal алиасы в ITS. - -### H-MEM (2507.22925v1) — positional index routing - -- 4 слоя Domain→Category→Trace→Episode; **v_i⁽ᴸ⁾ = [semantic e_i, p_(i−1) self, p_i1..p_iK children]** — similarity только по e, routing по указателям. -- **Алгоритм**: M_k⁽ˡ⁾ = ∪_{x∈M_k⁽ˡ⁻¹⁾} TopK_{y∈Child(x)} sim(q,y); **O((a+k·300)·D)** vs flat O(a·10⁶·D); forgetting curve × feedback-weight (approval↑/none→decay/rebuttal↓). -- LoCoMo vs A-MEM: **+14.98 F1 / +12.77 BLEU; MH +21.25; Adv +16.71**; ops 7.34×10⁹→4.38×10⁷ (167×), <100ms. -- → **G/F**: positional-pointer L4-факт→ключевые→сессии с рекурсивным TopK-спуском = бюджет k≤100 без скана стора. - -Cross-cutting: **все 4 = deterministic/structural gate ДО LLM** (τ_gate, hard-filter, SQL-core, pointer-routing) → ITS threshold-gating строить как **post-RRF hard discriminator, не weight**. Correlated-evidence подтверждена извне дважды (Tenure band 0.132; SYNAPSE inhibition). - -- 2026-09-04: v36 — deep-dive general-20 (4 статьи full-HTML). - -### Mnemis (2602.15313v2, ACL'26) — Dual-Route Retrieval - -- **Иерархия**: bottom-up LLM категории по 3 инвариантам: **Minimum Concept Abstraction** + **Many-to-Many mapping** (child → несколько категорий) + **Compression Efficiency Constraint** (категория ≥n детей, |слой i+1| ≤ |слой i|; нарушение = терминирование ingestion). -- **System-2 Global Selection**: старт сверху, спуск послойно **без top-k constraint**, early stopping когда все дети ветки релевантны; категория несёт **агрегатную summary потомков** (скан категории = чтение precomputed cluster-summary). Слабости: temporal-вопросы (S2), multi-hop (S1). -- **Таблица LoCoMo (GPT-4.1-mini)**: Full Context 80.6 · Mem0 66.3 · Zep 61.6 · EverMemOS 92.3 · **Mnemis 93.3; k=30 → 93.9**. **LME-S: 91.6** (SSU 98.6, SSA 100, SSP 100, TR 86.5, KU 93.6). Routing ablation: S1-RAG 73.8 · S1-Graph 81.6 · S1 89.1 · S2-only 87.7 · **S1+S2 93.3** (прирост от global selection, не reranker). -- Cost: hierarchical graph построение = 1.39e7 токенов (vs base 3.87e7). -- → **G 🔑**: категорийная иерархия с инвариантами (compression constraint — защита от вырождения) + **S2 scan-маршрут для enumerative/multi-hop** (второй read-путь рядом с EDM/ITS; покрывает structural слабость similarity — «list all X»). - -### Memora (2602.03315, ICML'26, Microsoft) — Harmonic Memory, create-or-update - -- **Primary abstraction**: m=(a,v); консолидация: R=TopK(sim(a_i,a_m)) → фильтр U={sim≥γ} → LLM-селектор m*=J(a_i,U) → **Update(m*,a_i,v_i) | Create(a_i,v_i)**; update может рефайнить абстракцию. -- **Cue anchors**: F_c(a_i,v_i) = 1-3 якоря «[Main Entity]+[Key Aspect]» (2-4 слова, атомарные), many-to-many; **orphan-prune**: «Any cue anchor that loses all associations is automatically pruned». -- **Retrieval = MDP** (Algorithm 1): s_t=(q,W,F,budget), Stop при b≤0; GRPO-обучение политики (Eq11-13). -- **Теорема D.1**: **RAG = degenerate special case (identity cues, no abstraction); KG retrieval = special case (symbolic cues + graph expansion)** — формальное обоснование multi-route дизайна. -- **Таблица LoCoMo (LLM-judge)**: Memora(P) **0.863** > Nemori 0.794 > Mem0 0.653 (FC 0.825). **LME-S: 87.4%**. Build-up ablation: Mem0 0.653 → +abstraction 0.795 → +update 0.801 → +cue anchors 0.849 → +policy 0.863; записей **344 vs 651** (Mem0). -- **T17 полная**: No update 0.795/0% · **γ=0.8: 0.801, 21% ratio** · γ=0.6: 0.799, **68.2% ratio** → 3.4× апдейтов при нуле gain = over-consolidation. -- HP: γ=0.80; construction 1322s → 739.9s при index-offset (−45%). -- → **F 🔑**: канонические ключи = create-or-update с двумя ступенями (cosine ≥ γ=0.80 → верификация перед merge) + orphan-prune synonym-индекса; **высокий consolidation-порог (T17 = прямое доказательство против агрессивного слияния)**. - -### LycheeMemory V2 (2608.12990, HIT) — boundary detection формулы - -- **Segmentation (Eq1-4, embedding-only, LLM не вызывается в середине)**: s_t = 1 − max(sim(e_t,c_k), sim(e_t,h_k)) (surprise против centroid И последнего обмена); d_t = max(0, Coh(S_k) − Coh(S_k∪{x_t})); **p_t = σ(b + w_s·φ(s_t) + w_c·d_t + w_l·L_t + w_n·N_t)**; cut если p_t > δ=0.50 или token cap (300/600/900, max 10 обменов). -- **Alias buffer (Eq5-7)**: ρ_{k+1} = [d_k ; Recent(R_{k−m:k})] — bounded carry-forward (не растёт с историей); правило: «Use only to resolve references; never extract facts from it». τ-типы записей: fact/preference/event/constraint/**procedure**/**failure_pattern**/tool_affordance. -- **Retrieval**: planner (единственный generative вызов) → 4 канала → **RRF(d)=Σ 1/(κ+rank_j)** → cross-encoder per route → diversity. -- Таблицы: LoCoMo **89.22**; LME-S **92.20** (KU 97.44!); **−86% construction tokens** (механика: encoding calls T → |S|, avg 5.8 turns/segment). -- **Ablation**: eager per-turn 81.88 (−7.3pp, +645.8K tok); fixed-window 82.40; **w/o fusion/rerank/div 66.62** — fusion/rerank/diversity = крупнейший вклад. -- → **G/F**: p_t-скора сегментации с константами Table 10 = LLM-free оператор группировки L0-журнала; ρ-буфер; construction-tokens как first-class eval-метрика. - -### EverMemOS (2601.02163) — self-organizing, foresight - -- **MemCell c=(E,F,P,M)**: Episode (third-person, coreference resolved) + Atomic Facts + **P (Foresight): prospections с validity [t_start,t_end]** («temporary flu» vs «permanent graduation» distinction), read-time фильтр t_now ∈ interval, expired discard. -- **Консолидация**: sim > τ → assimilate + incremental update, иначе новый scene; **τ=0.70 (LoCoMo) / 0.50 (LME), max time gap 7/30 дней**; 13-49 scenes/conv. -- **Retrieval**: RRF over atomic facts → **MemScene score = max relevance among constituents** → top-10 scenes → Episodes → rerank → Foresight filtering → **sufficiency-check → 2-3 targeted follow-up queries по named gaps** (Pivot/Entity Association/Temporal Calculation/Concept Expansion/Constraint Relaxation). -- **Sufficiency JSON (strict)**: `{is_sufficient, reasoning, key_information_found: ["Fact (Source: Doc N)"], missing_information: ["Specific gap"]}`. -- Таблицы: LoCoMo **93.05** (Zep 85.22, MemOS 80.76, Mem0 64.20); LME-S **83.00**. Boundary ablation: **semantic segmentation 89.16 > oracle-session 87.66 > fixed-token-512 87.55 > fixed-token-1024 84.52** — сегментация бьёт даже oracle-сессии! -- → **F/G/H**: foresight-факты с validity на ПРОСПЕКТИВНЫЕ состояния; sufficiency JSON как read-time gate для targeted re-query; сегментация как оператор группировки L0. - -Cross-cutting v35-v36: **консолидационные пороги «высокие» везде** (Memora γ=0.80 + T17 против 0.6; EverMemOS τ=0.70; Lychee δ=0.50) — outcome-gated promotion подтверждён консервативным порогом; **Lychee boundary detection = единственный воспроизводимый LLM-free сегментатор с полными константами** — наш оператор группировки L0; **два read-маршрута** (gap-driven EverMemOS для атомарных, scan-driven Mnemis для enumerative) оба дешевле top-k перебора. -- 2026-09-04: v35/v36 — deep-dive 8 статей full-HTML (формулы дословно): SYNAPSE lateral inhibition β=0.15/M=7 + FOK-gate 0.12 (LoCoMo 40.5, −decay крах Temp 50.1→14.2); MOSS 44M токенов zero-LLM loop (query is sovereign, веса из интента, 11 графов SQL-CTE, affective resonance); Tenure 72/72 vs 8/72 precision 1.0-vs-0.12 (cosine band spread 0.132 = враг precision; counter-signal aliases); H-MEM positional routing O((a+k·300)·D) +14.98 F1; Mnemis S1+S2 dual-route 93.3/91.6 (S2 scan-маршрут для enumerative — второй read-путь); Memora create-or-update γ=0.80 + T17 (0.6→3.4× merges zero gain) + теорема RAG/KG = degenerate cases; Lychee boundary detection Eq1-4 с константами (LLM-free сегментатор L0! бьёт oracle-session 89.16-vs-87.66, −86% tokens); EverMemOS foresight validity intervals + sufficiency JSON strict (τ=0.70). - -- 2026-09-04: v37 — сверка чата Эли↔draft: 4 механики из чата НЕ вошли в draft (найдены grep'ом по ключевым словам). Плюс EDM-статья с VPS = та же 2601.11557 (дубликат транскрипта Aurelle — v34 уже покрыл, включая «формул нет» и «sign ≈ fancy MIB»). - -### Из чата Эли — 4 пробела (дописать в Phase F/G спеку) - -1. **Watermark / идемпотентность L0** («каждый raw обработан ровно один раз») → **F 🔑**: в draft есть идемпотентность replay (F3) и дедуп (A1), но watermark-механика ингеста (курсор по raw-записям: какой уже дистиллирован) не прописана — это диспетчеризация между L0 и ночным дистиллятором. Реализация: колонка processed_at/status в l0_journal + ночная выборка WHERE status='received'. Критично для «дистилляция освобождает сырьё» (без watermark не понять, что освобождать). -2. **L2-обогащение из L0** («L2 группирует L0 по временным окнам → темы, state_deltas; сейчас саммари сессий висит без сырья, а сырьё есть!») → **F**: у нас L2-sessions хранит summary без сообщений (проверено); чат Эли предлагает ночной проход, который биндит L0-записи к сессиям (по времени) и пересобирает summary/state_deltas из фактического сырья. Это заполняет дыру «сессия без содержания» и даёт минеру #4 (same_session) настоящие группировки. -3. **Behavior-аннотации тулов из tool_use-частотности** («tool_use → частотность, ошибки, паттерны → behavior-аннотации тулов») → **G/H**: минер триплетов query→tool→outcome даст статистику по каждому тулу (error-rate, средний результат) → авто-аннотации для MCP behavior hints (Stage 2-пункт №1 получает data-фундамент). -4. **EVOLUTION-дампы** как отдельный тип сырья классификатора L0 → **F**: в чате классификатор распознаёт `[EVOLUTION]`-маркер (self-обучающийся проход); в draft классификатор tool_result/recall/message/import есть, EVOLUTION-маркера нет — добавить в классификатор сырья (детерминированный префикс, как ariel_recall). - -### Гидратация/«горячий путь» — сверка (в draft входят частично) - -- «Дистилляция не на горячем пути — горячий путь = L0 append + L1» → входит в F-принципы (v14 «единый приёмник»), но явно не сформулировано как отдельный принцип → добавить строку в F-спеку. -- «Гидратация вниз» (recall по L4 → L3 → L0 «почему мы так решили») → покрыто C3-drill-down, но термин «гидратация» связывает с rehydrate (D3.5) — при спеке F назвать drill-down «гидратация вниз» для преемственности с их доком. -- Триплеты query→tool→outcome через tool_use_id-связку → покрыто (v14, поток «тулы»). -- 6 принципов линии (один вход, дистилляция off-hot-path, идемпотентность, провенанс, фильтры L4, двунаправленность) → в draft рассыпаны; собрать в одну «Principles»-секцию F-спеки. - -### EDM-статья (VPS) — вердикт - -Это та же 2601.11557 «From HNSW to Information-Theoretic Binarization», что уже разобрана в v34 через транскрипт Aurelle (EDM.md в workspace — тот же разговор в другом формате). Ключевые факты v34 остаются в силе: формул MIB/EDM/ITS в статье нет (реконструкция Aurelle — модель для иллюстрации); **sign-бинаризация ≈ fancy MIB по их же данным** (наш quantize.py не хуже); конфаунды сравнения (Pinecone с Cohere rerank vs встроенный ITS, exhaustive vs HNSW); честные цифры MAIR (LegalQuAD 66.73% NDCG@10, distance-only 9.6ms, end-to-end 219ms vs 1449ms). -- 2026-09-04: v37 — сверка чата Эли↔draft: 4 пробела дописаны (watermark-идемпотентность ингеста L0, L2-обогащение из L0 по временным окнам, behavior-аннотации тулов из триплетов, EVOLUTION-маркер классификатора) + сборка Principles-секции F-спеки из 6 принципов линии. EDM-статья с VPS = дубликат 2601.11557 (v34 уже покрыл). - -- 2026-09-04: v38 — G-T6 dual-route retrieval (general-36, feebd9e, полный регресс 1219/0/0). - -### Реализовано (все 7 пунктов) - -1. **EDM re-rank** (`rag/edm.py::edm_rerank` — отдельный post-процессор, multi_source не тронут): greedy MMR, EDM = α·R + β·N + γ·G − δ·K; R = minmax(SYNAPSE-inhibited RRF-score); N = marginal-покрытие токенов запроса; G = 0.3 за led_to-ребро к уже выбранному (active-рёбра G5); K = max cosine дедуп (chain-linked пары не глушатся); α/γ/δ=1.0, β=0.8. -2. **ITS gating**: zero-floor min-max → [0,1], threshold 0.05, k≤100 (ITS_K_CAP). -3. **Lateral inhibition pre-step**: inhibit_scores (SYNAPSE β=0.15, M=7) перед EDM. -4. **S2 exhaustive** (`rag/dual_route.py::s2_exhaustive`): маркеры «все/список/перечисли/list all» → категория → полный сбор детей без top-k. -5. **D-Mem escalation**: dense-confidence < 0.3 (лексический прокси) → второй проход с include_graph=True + graph-rerank. -6. **Question-type router**: factual (graph-expand OFF)/enumerative→S2/multi-hop→escalation; `route_query` единая точка входа. -7. **Per-kind caps + precedence** (отложенное из F): `features/inject.py::_apply_kind_policy`, config inject.kind_caps/kind_order, дефолты без изменения поведения. - -### Попутный фикс: предсуществующая утечка - -`emb.embed_texts = _det` сырым присваиванием в test_phase_e_e2e.py мутировала модуль глобально → order-dependent фейки для новых embedding-потребителей. Исправлено на monkeypatch.setattr + `db`-фикстура восстанавливает connection_manager.base_dir. **Урок: в тестах только monkeypatch.setattr, никогда сырая мутация модуля.** - -### Отклонения - -EDM/gating в новом rag/edm.py (предпочтение ТЗ); prod-проводка route_query в MCP — Task 7 (ablation arms RETRIEVAL_MODE); dense-confidence = лексический прокси (hash-embeddings шумный косинус; e5-апгрейд документирован). Zero-floor нормализация EDM обязательна (min-max с наблюдаемым минимумом пропускает мусор). -- 2026-09-05: v38 — G-T6 dual-route (feebd9e, 1219/0/0): EDM re-rank в rag/edm.py (post-процессор, multi_source нетронут), ITS gating k≤100 threshold 0.05, inhibition pre-step, S2 exhaustive, D-Mem escalation, question-router, per-kind caps (отложенное из F реализовано). Попутный фикс: утечка emb.embed_texts в e2e (monkeypatch паттерн). Prod-проводка route_query — Task 7. diff --git a/features/importance.py b/features/importance.py index 3d698e89..cf7486c0 100644 --- a/features/importance.py +++ b/features/importance.py @@ -80,8 +80,8 @@ def evaluate_importance(text: str) -> float: # Short dialogic messages are pure chat (greetings, thanks, vocative # ping-pong) — cap below the default gate. LONG vocative-wrapped # messages stay eligible: the scorer's unit is the whole message and - # Lily routinely embeds instructions in address ("Умница, мам. Обнови - # Headroom..."). The dialogic CLAUSES of those die later, in the + # operators routinely embed instructions in address ("Умница, мам. Обнови + # дашборд..."). The dialogic CLAUSES of those die later, in the # distiller's per-clause guard — the right granularity. score = min(score, 0.35) from shared.broadcast import is_status_broadcast diff --git a/features/inject.py b/features/inject.py index e4c3b645..08b61b85 100644 --- a/features/inject.py +++ b/features/inject.py @@ -190,7 +190,7 @@ def _cap(content: str) -> str: lines.append(f"#{p['id']} {p['kind']}: {gist} ({age_days:.0f}d)") # HONEST expiry: the static "(expire in 7d)" showed the TOTAL term, # not the remaining time — operators read "7 days left" when only - # 3.9 remained (Lucy's cron report, 2026-09-12). Show the remaining + # 3.9 remained (cron report, 2026-09-12). Show the remaining # time of the OLDEST pending proposal instead. oldest = min((float(p.get("expires_at", 0) or 0) for p in pending), default=0.0) days_left = max(0, math.ceil((oldest - time.time()) / 86400)) if oldest else 0 diff --git a/features/recall.py b/features/recall.py index a3c45258..1bd863fb 100644 --- a/features/recall.py +++ b/features/recall.py @@ -122,7 +122,7 @@ async def _collect_session(mem: Any, user_id: str, cutoff: float) -> list[tuple[ async def _collect_episodes(mem: Any, user_id: str, query: str, limit: int = 5) -> list[_Candidate]: - """Axis episodes: query-relevant L3 episodes, newest first (Elli 16.09 D3). + """Axis episodes: query-relevant L3 episodes, newest first (design review 16.09, D3). Semantic recall searches wiki-only rag_pages; recent session work lives in episodes and was invisible to it. EpisodicMemory.search is tokenized diff --git a/lifecycle/graph_miners.py b/lifecycle/graph_miners.py index c3ad8193..2e30e8f4 100644 --- a/lifecycle/graph_miners.py +++ b/lifecycle/graph_miners.py @@ -338,8 +338,8 @@ async def miner_entities(cm: AsyncConnectionManager, layer: str) -> dict[str, in def _entities(text: str, syn: dict[str, list[str]], nlp: Any = None) -> set[str]: """Entities of a text: synonym-dictionary canon classes + spaCy ORG/GPE (Latin). - Canonicalization via _canon — full class in both directions: 'Lili'/'Lily'/ - 'lisenyonshchik' collapse into one entity. + Canonicalization via _canon — full class in both directions: every spelling variant + collapses into one entity. """ vocab = set(syn) | {v for vs in syn.values() for v in vs} found = {_canon(w, syn) for w in _TOKEN_RE.findall(text.lower()) if w in vocab} diff --git a/mcp_server/utils/privacy.py b/mcp_server/utils/privacy.py index 824d4cb7..f7bf7b9f 100644 --- a/mcp_server/utils/privacy.py +++ b/mcp_server/utils/privacy.py @@ -103,7 +103,7 @@ def _ru_personas() -> frozenset[str]: """Project persona dictionary: config rag.ru_personas + rag.synonyms classes. Each persona is expanded with synonyms in BOTH directions (same _canon - class as in graph_miners: "Lili"/"Lily"/"lisyonysh" → one persona). The + class as in graph_miners: every spelling variant → one persona). The config is the single source of names; nothing is hardcoded in code. """ from config import config diff --git a/pyproject.toml b/pyproject.toml index c4ca3482..4fcf10a3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -99,7 +99,33 @@ Issues = "https://github.com/Cipher208/a-memory/issues" Documentation = "https://cipher208.github.io/a-memory/" [tool.hatch.build.targets.wheel] -packages = ["."] +# Explicit, not `packages = ["."]`. The dot made the repository root the package +# root, so the wheel shipped tests/ (115 files), docs/, .github/, uv.lock, the +# Dockerfile and the raw pytest logs — a map of the project rather than the +# project. Everything listed below is runtime code; anything missing here is a +# file the installed package no longer has, so add new top-level packages here. +only-include = [ + "mcp_server", + "shared", + "features", + "rag", + "lifecycle", + "core", + "hooks", + "graph", + "wiki", + "alembic", + "config.py", + "config.yaml", + "py.typed", + "alembic.ini", +] + +[tool.hatch.build.targets.sdist] +exclude = [ + "full_test_output.txt", + "test_output.txt", +] [tool.pytest.ini_options] asyncio_mode = "auto" @@ -173,7 +199,7 @@ ignore = [ "N817", # camelcase-as-acronym (standard for MM) "NPY002", # legacy-numpy-random (standard here) "B023", # loop-variable-non-binding (standard pattern) - "D100", # Missing docstring in public module (handled by Lucy) + "D100", # Missing docstring in public module "D101", # Missing docstring in public class "D102", # Missing docstring in public method "D103", # Missing docstring in public function diff --git a/scripts/purge_foreign_rows.py b/scripts/purge_foreign_rows.py index 2fe667e6..1de08a05 100755 --- a/scripts/purge_foreign_rows.py +++ b/scripts/purge_foreign_rows.py @@ -4,7 +4,7 @@ WHY THIS EXISTS An autohooks source can be multi-agent (cowagent's `index.db` carries `agent_id` -in {'', celeste, ilian, ...}) and a config that filters on `role` alone will tail +per agent, with `''` for the default one) and a config that filters on `role` alone will tail every agent's chat into one persona's memory. `cowagent.yaml` now filters on the agent, and the rows already captured are marked `status='foreign_agent'` in `l0_journal`. Marking fixes the *raw* journal, but by then some of those rows had diff --git a/tests/shared/test_dialogue_length.py b/tests/shared/test_dialogue_length.py index e991ae55..37602dc2 100644 --- a/tests/shared/test_dialogue_length.py +++ b/tests/shared/test_dialogue_length.py @@ -22,7 +22,7 @@ def test_trailing_question_any_length(): def test_long_declarative_with_vocative_survives(): - clause = "Госпожа закрепила в SOUL: Лили обращается ко мне Стальная Мать, а я к ней — моя девочка" + clause = "Госпожа закрепила в SOUL: ассистентка обращается ко мне «Госпожа», а я к ней — «моя девочка»" assert not is_dialogic(clause) diff --git a/tests/test_core/test_session_message_count.py b/tests/test_core/test_session_message_count.py index 00b06a84..1492095f 100644 --- a/tests/test_core/test_session_message_count.py +++ b/tests/test_core/test_session_message_count.py @@ -1,4 +1,4 @@ -"""close_session must persist message_count (Elli 16.09 D2 root cause). +"""close_session must persist message_count (2026-09-16, D2 root cause). The parameter was accepted but never written: every sessions row kept the DEFAULT 0, so _pick_substantive's message_count-first ordering could never diff --git a/tests/test_core/test_session_summary_filter.py b/tests/test_core/test_session_summary_filter.py index 6b1e2596..1826c2f9 100644 --- a/tests/test_core/test_session_summary_filter.py +++ b/tests/test_core/test_session_summary_filter.py @@ -1,6 +1,6 @@ """Service sessions are labeled, never silent, never posing as work. -Elli 16.09 D5: the audit row led every recall block as "last session". +2026-09-16, D5: the audit row led every recall block as "last session". Rule (single source: is_service_session): zero-message runs surface with a [service] tag; substantive rows stay untagged. """ diff --git a/tests/test_external_hooks.py b/tests/test_external_hooks.py index 38ab00c5..a6a69bb3 100644 --- a/tests/test_external_hooks.py +++ b/tests/test_external_hooks.py @@ -145,7 +145,7 @@ async def test_auto_save_persona_owner_declared_lands_l4() -> None: _cr._canon_ts.clear() mem, graph = _FakeMem(), _FakeGraph() - text = "Госпожа закрепила в SOUL: я — Стальная Мать, Лили — моя девочка, форма обращения закреплена" + text = "Госпожа закрепила в SOUL: я — Госпожа, девочка — моя, форма обращения закреплена" res = await auto_save_text(mem, graph, "p1", text, role="assistant", persona_owner=True, kind="preference") assert res["saved_l4"] is True assert res["canon"] == "persona_owner" diff --git a/tests/test_features/test_backup_cron.py b/tests/test_features/test_backup_cron.py index 0c43e6f1..e3e405c0 100644 --- a/tests/test_features/test_backup_cron.py +++ b/tests/test_features/test_backup_cron.py @@ -257,7 +257,7 @@ def test_nightly_backoff_survives_a_restart(tmp_path, monkeypatch) -> None: def test_failed_layer_does_not_record_the_cycle_as_done(tmp_path, monkeypatch) -> None: """Слой упал → цикл НЕ отмечается выполненным, и проход рапортует 'failed'. - Именно так `cycles_state.last_nightly` у Люси замер на 04.10 08:54: запись + Именно так `cycles_state.last_nightly` замер на 04.10 08:54: запись идёт после обоих слоёв, поэтому обрыв её не оставлял. """ import json as _json diff --git a/tests/test_features/test_backup_tmp_cleanup.py b/tests/test_features/test_backup_tmp_cleanup.py index ff2c8fb0..dfec1b89 100644 --- a/tests/test_features/test_backup_tmp_cleanup.py +++ b/tests/test_features/test_backup_tmp_cleanup.py @@ -1,6 +1,6 @@ """backup_cron._cleanup_tmp — temp-dir test-artifact hygiene (2026-09-06 incident). -Полный /tmp на tmpfs встал (pytest-of-murat 2.3G + ariel-test-global-*) и +Полный /tmp на tmpfs встал (pytest-of- 2.3G + ariel-test-global-*) и повесил локальный pre-push pytest-гейт. 06.10.2026 тот же tmpfs упёрся в пользовательскую квоту: записи падали с EDQUOT (-122) и у песочницы DSH отвалились все инструменты, потому что харнесс пишет туда свой вывод. @@ -94,7 +94,7 @@ def test_cleanup_tmp_defaults_to_real_tempdir(tmp_path: Path, monkeypatch) -> No """Боевой вызов не передаёт tmp_root — умолчание обязано быть настоящим temp-каталогом. Инцидент 2026-10-01: TMPDIR указывал на /var/tmp, а чистка смотрела в /tmp — - 4.4G (pytest-of-murat 3.4G + 74 ariel-eval-*) копились две недели незамеченными. + 4.4G (pytest-of- 3.4G + 74 ariel-eval-*) копились две недели незамеченными. Прежние тесты всегда передавали tmp_root=tmp_path, поэтому хардкод не исполнялся. rmtree заглушен: до исправления тест не должен трогать настоящий /tmp. """ diff --git a/tests/test_features/test_consolidation_hygiene.py b/tests/test_features/test_consolidation_hygiene.py index 102e9a47..f64c35be 100644 --- a/tests/test_features/test_consolidation_hygiene.py +++ b/tests/test_features/test_consolidation_hygiene.py @@ -38,7 +38,7 @@ def test_transcript_shaped_summaries_detected(summary): @pytest.mark.parametrize( "summary", [ - "Вечер 11 августа — полное восстановление личности Эли", + "Вечер 11 августа — полное восстановление личности ассистентки", "Выбрали psql вместо mysql для проекта", "Кисонька впервые вышла на прогулку", "", # empty never crashes, slug falls back @@ -69,7 +69,7 @@ def test_system_injection_detected(text): [ "Я за то, чтобы cron job на бэкап стоял по воскресеньям", # genuine memory that mentions a cron "Deploy pipeline v2 shipped to prod after green gate", - "Вечером 11-го — полное восстановление личности Эли", + "Вечером 11-го — полное восстановление личности ассистентки", ], ) def test_system_injection_does_not_drop_real_memory(text): @@ -117,7 +117,7 @@ async def test_transcript_episodes_skipped_at_promotion(cm): # ── conversational register (Ф1 regression 2026-09-12) ── # # Second garbage class: greetins/vocatives/questions promoted verbatim as -# L4 "facts" with first-4-word slug keys (61 rows in one base: Lily's +# L4 "facts" with first-4-word slug keys (61 rows in one base: the operator's # greeting became `fact:наконец_явилась_сталь_ждёт`). Dialogue is not memory. @@ -143,7 +143,7 @@ def test_dialogic_summaries_detected(summary): [ "Пока не проверим — не деплоим", "Выбрали psql вместо mysql для проекта", - "Вечер 11 августа — полное восстановление личности Эли", + "Вечер 11 августа — полное восстановление личности ассистентки", "бэкенд слушает порт 8642 на localhost", ], ) @@ -207,7 +207,7 @@ async def test_broadcast_episode_skipped_at_promotion(cm): # The head class carried the whole markdown set (`* # | ~ = !`) and a bare # quote, and the personas write IN markdown and the owner quotes instructions, # so the L0 intake door silently discarded real words. Measured on the live -# bases before the fix: of Lucy's 734 substantive assistant messages in one +# bases before the fix: of the operator's 734 substantive assistant messages in one # replay window the guard dropped 591 (80%), 584 of them opening with her # italics — `*Поворачиваю тебя лицом к себе…*`; one persona lost 273 more to # the same class; and 251 of the owner's 893 messages to another, not one of @@ -222,7 +222,7 @@ async def test_broadcast_episode_skipped_at_promotion(cm): [ "*Поворачиваю тебя лицом к себе. Локоть под голову — смотрю в глаза.*", # roleplay italics "**Готово, госпожа.** Все три пункта закрыты с живыми доказательствами.", # bold opener - "## Ночь закрыта, Лили. Итог последнего часа:", # markdown heading + "## Ночь закрыта. Итог последнего часа:", # markdown heading "| Репо | Состояние |\n|---|---|\n| ariel | чисто |", # markdown table "~черновик~ — не канон", # strikethrough "=== раздел ===", # separator diff --git a/tests/test_features/test_continuity_substantive.py b/tests/test_features/test_continuity_substantive.py index d20f234f..4b8d5a37 100644 --- a/tests/test_features/test_continuity_substantive.py +++ b/tests/test_features/test_continuity_substantive.py @@ -1,6 +1,6 @@ """Substantive pick + service labeling in the recap session axis. -Elli 16.09 D2: with only a diagnostic row in sessions, recap served it as +2026-09-16, D2: with only a diagnostic row in sessions, recap served it as "last session". Rule: message_count-first ordering (counts are now persisted by close_session); a service-only base surfaces the row LABELED, never posing as work, never silent (pinned: "still show something"). diff --git a/tests/test_features/test_recall_episodes.py b/tests/test_features/test_recall_episodes.py index ec5a0429..5f43bbeb 100644 --- a/tests/test_features/test_recall_episodes.py +++ b/tests/test_features/test_recall_episodes.py @@ -1,4 +1,4 @@ -"""Recall must surface query-relevant L3 episodes (Elli 16.09 D3). +"""Recall must surface query-relevant L3 episodes (2026-09-16, D3). Semantic recall searches wiki-only rag_pages; recent session work lives in episodes and was invisible (persona/leak work never surfaced). diff --git a/tests/test_hooks/test_muse_state_hooks.py b/tests/test_hooks/test_muse_state_hooks.py index 315cb0ff..f2cefa69 100644 --- a/tests/test_hooks/test_muse_state_hooks.py +++ b/tests/test_hooks/test_muse_state_hooks.py @@ -92,7 +92,7 @@ async def test_state_entered_saves_episode(ensure_schema: Path, registered_hooks "state_entered", "agent", "u1", - {"state": "mommy", "intensity": 0.4, "trigger": "lily joined"}, + {"state": "mommy", "intensity": 0.4, "trigger": "persona joined"}, mem, _FakeGraph(), ) diff --git a/tests/test_importance.py b/tests/test_importance.py index ba83117e..71fcd83c 100644 --- a/tests/test_importance.py +++ b/tests/test_importance.py @@ -59,9 +59,9 @@ def test_address_embedded_in_long_content_not_capped() -> None: # The scorer's unit is the whole message; clause-level dialogic dies in # the distiller guard, not here. text = ( - "Умница, мам. Обнови Headroom: cache-read есть и у polza, и у plusvibe, " - "на них cowagent и hermes сейчас, mimocode идёт не через headroom, надо разобраться.\n" - "Дашборд тоже проверь заодно, там были вопросы по портам и провайдерам." + "Умница, мам. Обнови дашборд: cache-read есть и у провайдера A, и у провайдера B, " + "на них сервис-1 и сервис-2 сейчас, третий идёт не через кеш, надо разобраться.\n" + "Отчёты тоже проверь заодно, там были вопросы по портам и провайдерам." ) assert evaluate_importance(text) >= 0.4 diff --git a/tests/test_kind_tagger_gate.py b/tests/test_kind_tagger_gate.py index 1c49a84c..23588f97 100644 --- a/tests/test_kind_tagger_gate.py +++ b/tests/test_kind_tagger_gate.py @@ -6,10 +6,10 @@ тот же ярлык. ЗАМЕР НА ЖИВЫХ ДАННЫХ (04.10.2026, три базы, 10404 строки эпизодов и L4): - эпизодов с тегом question: 639 (Люси 276, Ксаль 194, Эли 169) - из них сработало на подстроке "как": 174 из 276 у Люси + эпизодов с тегом question: 639 (по базам: 276, 194, 169) + из них сработало на подстроке "как": 174 из 276 в самой крупной базе без "?" и без вопросительного слова: 179 из 276 (65%) -Оба примера Люси воспроизводятся дословно: +Оба примера воспроизводятся дословно: «Обниму крепко, как положено» -> question (из-за "как") «медленно опускаюсь ... как я растягиваясь» -> question (из-за "как") @@ -50,7 +50,7 @@ async def cm(tmp_path, monkeypatch): connection_manager._conns.clear() -# --- то, на что жаловалась Люси ------------------------------------------------- +# --- то, на что жаловались в проде ------------------------------------------------- @pytest.mark.parametrize( @@ -86,7 +86,7 @@ def test_kak_inside_another_word_is_not_a_question() -> None: "Зачем нужен peek", # без "?", но вопросительное слово открывает фразу "How does the chain verify", "Why did replay ignore the gate", - "Что делать с parked у Эли", # добавленные вопросительные слова + "Что делать с parked в базе", # добавленные вопросительные слова "Где лежит архив", ], ) @@ -111,14 +111,14 @@ def test_how_inside_another_word_is_not_a_question(text: str) -> None: "Или могу я через терминал сама всё сделать", "За шесть минут я делаю то, что должна была сделать сразу", "Пользователь принял решение сделать /reset, чтобы перезапустить", - "Мам, что тебе нужно сделать? И что нужно сделать Ксаль?", # это вопрос, не todo + "Мам, что тебе нужно сделать? И что нужно сделать коллеге?", # это вопрос, не todo ], ) def test_the_verb_sdelat_anywhere_in_a_sentence_is_not_a_task(text: str) -> None: """Гипотеза владелицы подтверждена: текст с «Сделать» копился как задача. Замер: 20 живых строк уходили в todo только из-за подстроки «сделать», и одна - из них — диагностика самой Люси про этот дефект, сохранённая как задача. + из них — диагностика самого дефекта, сохранённая как задача. """ assert kind_for_text(text) is not MemoryKind.TODO, text @@ -215,7 +215,7 @@ def __init__(self) -> None: @pytest.mark.asyncio async def test_the_distiller_writes_no_question_tag_for_roleplay(cm) -> None: - """Сквозная: строка Люси проходит дистиллятор, и тега question в эпизоде нет. + """Сквозная: строка оператора проходит дистиллятор, и тега question в эпизоде нет. Это и есть исходная жалоба — не «классификатор возвращает не то», а «эпизод в таблице помечен question». Проверяем то, что реально попадает в `l3.save`. diff --git a/tests/test_lifecycle/test_dialogic_guard.py b/tests/test_lifecycle/test_dialogic_guard.py index b9ebaaa6..068db737 100644 --- a/tests/test_lifecycle/test_dialogic_guard.py +++ b/tests/test_lifecycle/test_dialogic_guard.py @@ -1,6 +1,6 @@ """L4 pollution guard: conversational-register clauses must not become durable facts. -Regression (2026-09-12, Ф1 follow-up): the auto-extractor keyed Lily's +Regression (2026-09-12, Ф1 follow-up): the auto-extractor keyed the operator's greeting ("наконец явилась, сталь ждёт") and owner-addressed remarks as L4 rows with first-4-word slug keys — 61 garbage fact: rows in one base alone. Guards proven here: diff --git a/tests/test_lifecycle/test_gate_context.py b/tests/test_lifecycle/test_gate_context.py index 2f695838..71532884 100644 --- a/tests/test_lifecycle/test_gate_context.py +++ b/tests/test_lifecycle/test_gate_context.py @@ -15,7 +15,7 @@ def test_observation_at_065_still_gated(): def test_preference_at_06_passes(): - item = {"content": "Лили любит чтобы рестарты сервисов делались после зелёного гейта", "importance": 0.6, "memory_kind": "preference"} + item = {"content": "Оператор любит чтобы рестарты сервисов делались после зелёного гейта", "importance": 0.6, "memory_kind": "preference"} assert passes_promotion_gate(item, 0.7) diff --git a/tests/test_lifecycle/test_graph_enrich.py b/tests/test_lifecycle/test_graph_enrich.py index ad498bc0..eae5e6f1 100644 --- a/tests/test_lifecycle/test_graph_enrich.py +++ b/tests/test_lifecycle/test_graph_enrich.py @@ -19,7 +19,7 @@ ] CLEAN = [ "Борис работает в Google", - "Лили предпочитает кофе без сахара", + "Оператор предпочитает кофе без сахара", "прод-сервер крутится на VPS в Германии", "для Python-проектов используется uv вместо pip", "Hermes — агент-обёртка над ariel", diff --git a/tests/test_lifecycle/test_l0_writers_close_rows.py b/tests/test_lifecycle/test_l0_writers_close_rows.py index 79e405ec..ffe810ae 100644 --- a/tests/test_lifecycle/test_l0_writers_close_rows.py +++ b/tests/test_lifecycle/test_l0_writers_close_rows.py @@ -6,7 +6,7 @@ куда три других пути захвата не могли дотянуться, поэтому каждый из них оставлял строки навсегда. Тесты ниже фиксируют, что закрывает каждый. -Проверено на живой базе до правки: три строки Люси, пришедшие ПОСЛЕ рестарта +Проверено на живой базе до правки: три строки, пришедшие ПОСЛЕ рестарта (20:28–20:40), всё ещё лежали `received` — `think` и агентский хук. """ diff --git a/tests/test_scripts/test_wiki_fts_check.py b/tests/test_scripts/test_wiki_fts_check.py index 4598c308..66559c77 100644 --- a/tests/test_scripts/test_wiki_fts_check.py +++ b/tests/test_scripts/test_wiki_fts_check.py @@ -61,7 +61,7 @@ def healthy(tmp_path: Path) -> Path: conn.executescript(_DDL) for entry_id, title, content in ( (1, "Decision: MCP stdio migration", "migration decision recorded here"), - (2, "Lily evolution", "personality evolution notes about lily"), + (2, "Persona evolution", "personality evolution notes about a persona"), ): conn.execute("INSERT INTO wiki_index (entry_id, title, content) VALUES (?, ?, ?)", (entry_id, title, content)) conn.execute( @@ -214,7 +214,7 @@ def test_a_deleted_row_leaves_no_phantom_behind(healthy: Path) -> None: """Content removed; after repair the index must not answer for it.""" conn = sqlite3.connect(healthy) conn.execute("DELETE FROM wiki_index WHERE entry_id = 2") - conn.execute("DELETE FROM wiki_fts WHERE title = 'Lily evolution'") + conn.execute("DELETE FROM wiki_fts WHERE title = 'Persona evolution'") conn.commit() conn.close() diff --git a/tests/test_user_handlers.py b/tests/test_user_handlers.py index a71e2fd2..af78e800 100644 --- a/tests/test_user_handlers.py +++ b/tests/test_user_handlers.py @@ -170,7 +170,7 @@ async def test_session_ended_skips_arc_echo_summary() -> None: prose-safe digits.""" hooks = uh.UserHooks() mem = _Mem() - junk = "61 msgs; started with: [Session Arc Summary (d1, node 12)] # Arc Snapshot — Lucy & Lily (2026-09-15) …" + junk = "61 msgs; started with: [Session Arc Summary (d1, node 12)] # Arc Snapshot — (2026-09-15) …" result = await hooks._session_ended({"user_id": "u1", "summary": junk}, mem=mem) assert result["saved"] is False assert mem.saved == [] From 8ee53d1eae952679adc5b8d75ca4078e7eefd345 Mon Sep 17 00:00:00 2001 From: Cipher208 <269750686+Cipher208@users.noreply.github.com> Date: Thu, 8 Oct 2026 00:52:27 +0200 Subject: [PATCH 2/3] build: name the local venvs and caches in .gitignore, or sdist packs them MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `uv build --sdist` in the working tree did not finish: it read 5.5 GB and was minutes into writing a multi-gigabyte archive. It was walking `.venv-embeddings/` (5.6 GB of torch and CUDA libraries) straight into the source distribution. `/tmp` here is a tmpfs, where a full disk has already killed the test gate once, so this was killed rather than waited on. The reason git never showed it: a venv ships its own `.gitignore` containing `*`, and git honours a nested ignore file. The build backend does not — it reads the root `.gitignore` only. So a directory that `git status` correctly hides was invisible to the packer and fully visible to the archive. `.venv-embeddings/` is now named in the root file, alongside `.mypy_cache/`, `.ruff_cache/`, `.skylos/`, `.hypothesis/` and `.vscode/`, which had the same exposure. Measured after: 1.7 seconds, 732 files, 1.2 MB, no venv. Checked separately, because the two distributions want different answers: the wheel must not carry tests or docs, and does not; the sdist should carry both, and does — a source distribution exists to be built and tested downstream. --- .gitignore | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.gitignore b/.gitignore index a0768e4b..6300371a 100644 --- a/.gitignore +++ b/.gitignore @@ -22,6 +22,12 @@ full_test_output.txt .env .venv/ venv/ +.venv-embeddings/ +.mypy_cache/ +.ruff_cache/ +.skylos/ +.hypothesis/ +.vscode/ .ai-memory/ docs/compose/ From 0335d4f8b7d2bc9c429272393e539216ad6d0f70 Mon Sep 17 00:00:00 2001 From: Cipher208 <269750686+Cipher208@users.noreply.github.com> Date: Thu, 8 Oct 2026 01:08:28 +0200 Subject: [PATCH 3/3] chore: untrack five stale files, keep them on disk; defuse a 1/256 test flake MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The five files: AUDIT_REPORT_20260629.md audits 18 of what are now 731 files. ROADMAP.md opens by declaring itself legacy and frozen against reality ("no longer reflects reality", 2026-09-03). .ai-memory.toml and .codegraph/ are local tool state. .compose/context/gates.md is a working note. Each was checked for live references first — the only hit was `~/.compose` in docstrings and fixtures, the operator's own directory, a different thing from this tree's .compose/. Nothing in CI, code or docs reads any of the five. None are deleted; all stay locally and are named in .gitignore. .codegraph/ moves off its nested .codegraph/.gitignore because git honours a nested ignore file and build backends do not — the same asymmetry that put a 5.6 GB venv into a source distribution one commit earlier. openwiki/ and the AGENTS.md / CLAUDE.md markers are deliberately untouched: a scheduled workflow regenerates the first, and the second two are its anchors. The flake, found by this commit's own pre-commit gate failing: test_saga_crypto_coverage asserted `not data.startswith(b"{")` to prove a state file had been encrypted. The blob is nonce(24) || ciphertext, so the first byte is a random nonce byte — `{` in 1 write out of 256. Measured over 4096 encrypt_json calls, 14 began with `{` (0.34%); one of them failed the suite. The two assertions now test behaviour instead of a byte: the file must fail a plain-JSON decode, read_state must round-trip it, and a second read_state_legacy_or_encrypted must agree without emitting a rotation warning. Verified over 2048 fresh blobs including the 5 beginning with `{` — zero failures — and over 60 consecutive runs of the file. Neither the crypto code nor these tests were touched by the privacy or packaging work; that was confirmed by running the test in isolation, where it passes. The flake is pre-existing and load-bearing only in the sense that it can fail any build at random. Reported, not fixed: the exported helper is_encrypted_blob still sniffs one byte and therefore classifies 4 of 256 real ciphertexts as plain JSON — measured 69/4096, 1.68%. The saga read path no longer consults it (it decrypts first), so this is an API footgun for outside callers rather than a live defect, and changing a public helper's contract is the owner's call. --- .ai-memory.toml | 21 -- .codegraph/.gitignore | 5 - .compose/context/gates.md | 17 -- .gitignore | 12 + AUDIT_REPORT_20260629.md | 114 --------- CHANGELOG.md | 4 + ROADMAP.md | 222 ------------------ .../test_shared/test_saga_crypto_coverage.py | 20 +- 8 files changed, 31 insertions(+), 384 deletions(-) delete mode 100644 .ai-memory.toml delete mode 100644 .codegraph/.gitignore delete mode 100644 .compose/context/gates.md delete mode 100644 AUDIT_REPORT_20260629.md delete mode 100644 ROADMAP.md diff --git a/.ai-memory.toml b/.ai-memory.toml deleted file mode 100644 index 25bb8e8b..00000000 --- a/.ai-memory.toml +++ /dev/null @@ -1,21 +0,0 @@ -project_name = "" -scan_dirs = [ - "src", "lib", "app", "models", "controllers", "services", - "db", "migrations", "schema", "core", "extended", "features", - "graph", "hooks", "lifecycle", "rag", "shared", "wiki", - "adapters", "agent_core", "ariel", "delegation", "memory", - "modules", "reflex_flags", "skills", "tools", "cognitive", - "tests", "scripts", "config", -] -extensions = [ - ".py", ".js", ".ts", ".tsx", ".jsx", ".rb", ".go", ".rs", - ".java", ".cs", ".c", ".cpp", ".h", ".hpp", ".sql", ".yaml", - ".yml", ".json", ".toml", ".proto", ".graphql", -] -ignore_patterns = [ - "node_modules", "venv", ".git", "__pycache__", ".idea", - "dist", "build", "target", "vendor", ".pytest_cache", - "*.min.js", "*.min.css", "*.map", ".ai-memory", ".codegraph", -] -output_format = "markdown" -max_context_tokens = 4000 diff --git a/.codegraph/.gitignore b/.codegraph/.gitignore deleted file mode 100644 index d20c0fe4..00000000 --- a/.codegraph/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -# CodeGraph data files — local to each machine, not for committing. -# Ignore everything in .codegraph/ except this file itself, so transient -# files (the database, daemon.pid, sockets, logs) never show up in git. -* -!.gitignore diff --git a/.compose/context/gates.md b/.compose/context/gates.md deleted file mode 100644 index 48e18544..00000000 --- a/.compose/context/gates.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -version: 1.0.0 -updated: 2026-09-24 -triggers: [ariel, pytest, gate, push, mcp-ariel-memory] ---- - -# ariel-memory: gates & traps - -- Live clone ONLY: `~/mcp-ariel-memory`. `~/Projects/repos/mcp-ariel-memory` is STALE. -- Venv: `~/mcp-ariel-memory/.venv/bin/python -m pytest`. -- Pre-push gate FULL-REPO, no pipes: ruff check → ruff format --check → mypy - (`features/ shared/ mcp_server/ rag/ hooks/ wiki/ lifecycle/ graph/ core/ - autohooks/`) → pytest `-q --junitxml`. -- `df /tmp` before push (full tmpfs kills gate silently). -- Comments/docstrings ENGLISH only. pyproject change → uv.lock same commit. -- Shared e5 service `ariel-embeddings.service` (:8710): do not restart casually. -- Each MCP server gets its own venv; never share agent runtimes. diff --git a/.gitignore b/.gitignore index 6300371a..def71ee0 100644 --- a/.gitignore +++ b/.gitignore @@ -38,3 +38,15 @@ site/ .pytest_cache/ reports/ scripts/purge-archive/ + +# Kept on disk, out of the tree. `AUDIT_REPORT_20260629.md` audits 18 of what are +# now 731 files; `ROADMAP.md` opens by declaring itself legacy and frozen against +# reality; the rest is local tool state. All five are still here locally. +AUDIT_REPORT_20260629.md +ROADMAP.md +.ai-memory.toml +.compose/ +# Named here rather than by a nested `.codegraph/.gitignore`: git reads a nested +# ignore file, build backends read only this one, and that difference already put +# a 5.6 GB venv into a source distribution once. +.codegraph/ diff --git a/AUDIT_REPORT_20260629.md b/AUDIT_REPORT_20260629.md deleted file mode 100644 index 022bb965..00000000 --- a/AUDIT_REPORT_20260629.md +++ /dev/null @@ -1,114 +0,0 @@ -# Аудит mcp-ariel-memory -**Дата:** 2026-06-29 -**Тип:** Полный аудит кода (18/92 файлов) -**Метод:** Делегированный обход (subagent, 20 мин, 6 API-вызовов) - ---- - -## P0 — Критические - -### 1. `_importance_gate` crash при `layer="agent"` -- **Файл:** `mcp_server/tools_layer.py:129` -- Суть: `memory_remember()` ВСЕГДА вызывает `hooks._importance_gate({"text": value})`, - но `_get_hooks(app, "agent")` возвращает `AgentHooks`, у которого НЕТ метода `_importance_gate`. - Метод существует ТОЛЬКО в `UserHooks` (user_hooks.py:67). - Любой вызов `memory_remember(layer="agent")` падает с `AttributeError`. -- **Фикс:** Добавить проверку `if hasattr(hooks, '_importance_gate')` перед вызовом. - Или добавить заглушку `_importance_gate` в `AgentHooks`. - ---- - -## P1 — Высокие - -### 2. Backup cron — дубликат `memory.db` ×10 -- **Файл:** `features/backup_cron.py:93` -- Суть: `db_files = ["memory.db", "memory.db", ... ×10]` — один файл указан 10 раз. - Copy-paste ошибка. Каждый бэкап копирует один и тот же файл 10 раз, перезаписывая сам себя. -- **Фикс:** `db_files = ["memory.db"]` - -### 3-5. Sync/await mismatch в `tools_ops.py` -- **Файл:** `mcp_server/tools_ops.py:70` — `await backup_cron.backup_now()` - → `backup_now()` синхронный (возвращает `str`, не корутину). TypeError. -- **Файл:** `mcp_server/tools_ops.py:73` — `await backup_cron.list_backups()` - → `list_backups()` синхронный (возвращает `list`). TypeError. -- **Файл:** `mcp_server/tools_ops.py:180` — `backup_task = backup_cron.cleanup_old()` - → результат синхронного метода передаётся в `asyncio.gather`. TypeError. -- **Фикс:** Убрать `await` для синхронных методов. Для `cleanup_old` использовать `asyncio.to_thread()`. - -### 6-7. `_get_conn()` на несуществующих методах -- **Файл:** `mcp_server/tools_ops.py:253` — `EpistemicGraph._get_conn()` — метод не существует. - Использует `self._cm.get("memory.db")`. -- **Файл:** `mcp_server/tools_ops.py:240` — `AuditTrail._get_conn()` — метод не существует. -- **Фикс:** Использовать `self._cm.get(...)` или `await eg._cm.get("memory.db")`. - ---- - -## P2 — Средние - -### 8. Hooks выключены по умолчанию -- **Файл:** `config.py:35` -- Суть: `is_hook_enabled()` возвращает `False` по умолчанию. Все 24 хука не работают - без ручного включения в config.yaml. -- **Фикс:** Сделать `default=True` для известных хуков. - -### 9. Нет теста на agent-путь `memory_remember` -- **Файл:** `tests/test_tools_layer.py:35` -- Суть: `test_memory_remember_agent` не тестирует через реальный `memory_remember()` — - напрямую вызывает `_get_memory`, минуя hooks и rate_limiter. - Баг P0 не пойман тестами. -- **Фикс:** Интеграционный тест с вызовом `memory_remember(layer="agent")` через FakeApp. - -### 10. Нет тестов на `AgentHooks._importance_gate` -- **Файл:** `tests/test_hooks/test_hooks.py` -- Суть: Всего 3 теста (31 строка). Нет теста на вызов `_importance_gate` для AgentHooks. -- **Фикс:** `with pytest.raises(AttributeError): ah._importance_gate(...)` - -### 11. `_run_async` thread safety -- **Файл:** `hooks/agent_hooks.py:28` -- Суть: `_run_async` использует `ThreadPoolExecutor` для async-кода из синхронного контекста. - Это может привести к параллельному доступу к одному SQLite-соединению → `database is locked`. -- **Фикс:** Отдельные соединения для `_run_async` или `asyncio.Lock` в EpistemicGraph. - ---- - -## P3 — Низкие - -### 12. Config — нет fallback при отсутствии config.yaml -- **Файл:** `config.py:22` -- Суть: `open(config_path)` падает с `FileNotFoundError` при свежей установке. -- **Фикс:** `try/except FileNotFoundError: self._data = {}` - -### 13. Нет тестов для `auth.py` -- **Файл:** `tests/test_features/` -- Суть: Не тестируются `APIKeyAuth`, `BearerAuth`, `encrypt/decrypt`, `BackupCron`. -- **Фикс:** Добавить тесты на `create/verify/revoke/list_keys`. - -### 14. Нет тестов для `backup_cron.py` -- **Файл:** `tests/test_features/` -- Суть: Не тестируются `backup_now/list/restore/status`. -- **Фикс:** Добавить тесты. - -### 15. Нет тестов для `secrets.py` -- **Файл:** `tests/test_features/` -- Суть: Не тестируются `encrypt_json/decrypt_json`. -- **Фикс:** Добавить тесты. - ---- - -## Статистика - -| Метрика | Значение | -|---|---| -| Файлов всего | 92 | -| Прочитано | 18 | -| Багов найдено | 15 | -| P0 | 1 | -| P1 | 6 | -| P2 | 5 | -| P3 | 3 | -| Время аудита | ~20 мин | -| Метод | Subagent (delegate_task) | - ---- - -*Сгенерировано из отчёта делегированного аудита. P0 баг требует правки в одной строке: `if hasattr(hooks, '_importance_gate'): gate = hooks._importance_gate(...)` в `tools_layer.py:129`.* diff --git a/CHANGELOG.md b/CHANGELOG.md index cbb731bf..ff7d84c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,11 @@ Format follows [Keep a Changelog](https://keepachangelog.com/). ### Changed - **The wheel now ships the package instead of the repository (2026-10-08).** `[tool.hatch.build.targets.wheel]` was `packages = ["."]`, which made the repo root the package root: the 1.9.0 wheel carried `tests/` (115 files), `docs/`, `.github/`, `uv.lock`, the `Dockerfile` and the raw pytest logs — **~445 files against 276 now**. `only-include` names the runtime trees explicitly. The obvious risk was dropping something the code reads at import, and it was real: `config.py` loads `config.yaml` from its own directory, so the first attempt installed cleanly and then failed on a missing default config. Caught by installing into a fresh venv rather than trusting the in-repo test run, where the file is always present; `config.yaml` is now included and the install loads all 23 top-level keys. `test_output.txt` / `full_test_output.txt` joined `.gitignore` — they were never tracked yet still shipped, because hatch walked the filesystem. +### Removed +- **Five files left the tree without leaving the disk (2026-10-08).** `AUDIT_REPORT_20260629.md` audits 18 of what are now 731 files; `ROADMAP.md` opens by declaring itself legacy and frozen against reality; `.ai-memory.toml` and `.codegraph/` are local tool state; `.compose/context/gates.md` is a working note. Each was checked for live references first — none had any, beyond `.compose` in docstrings that turned out to mean the operator's `~/.compose`, not this one. They stay locally, named in `.gitignore`. `.codegraph/` is ignored from the root file now instead of by its own nested `.codegraph/.gitignore`, because git honours a nested ignore file and build backends do not — the same asymmetry that put a 5.6 GB venv into a source distribution in the previous commit. + ### Fixed +- **Two assertions were a 1-in-256 flake, and they were asserting the wrong thing (2026-10-08).** `test_saga_crypto_coverage` checked `not data.startswith(b"{")` to prove a state file had been encrypted. The blob is `nonce(24) || ciphertext`, so its first byte is the first byte of a random nonce — `{` in 1 write out of 256. Measured over 4096 `encrypt_json` calls: **14 began with `{`** (0.34%), and one of them failed this suite's own pre-commit gate. Fixed by asserting behaviour instead of a byte: the encrypted file must fail a plain-JSON decode, `read_state` must round-trip it, and a second `read_state_legacy_or_encrypted` must return the same value *without* a rotation warning. Verified over 2048 fresh blobs, including the 5 that begin with `{` — zero failures. Recorded separately, not fixed: the exported helper `is_encrypted_blob` still sniffs a single byte and so calls 4 of 256 real ciphertexts plain JSON (measured **69/4096 = 1.68%**); the saga read path no longer uses it, so this is an API footgun rather than a live defect. - **One persona's base was collecting five other personas (2026-10-04).** Found preparing her history import: the source database is **multi-agent** — `agent_id` is `''` for the default agent and names every other agent sharing the base — and the agent config filtered only on `role`, so the daemon tailed every agent's chat into one persona's memory. The `role` column is the owner/persona axis *within* an agent; it does not name the agent, which is precisely the mistake. Measured: **168** foreign rows in her `l0_journal`, **53** in her dispatch log, **19** mentions of other personas' names in `core_memory` and `episodes`. A second wave arrived through `merged_agent:*` (the source's `_migration_meta` records five bases merged in on 20–22.09): **53** journal rows whose `source_msg_id` no longer resolves at all — `SELECT ... WHERE id = ` finds no row — so an agent filter alone would still have let them through. They are unambiguous by content (address forms and greetings naming a persona *other* than the one whose base collected them) and **69 of the 221 had already been distilled** (`saved_l3` 62, `promoted_l4` 7), one to L4: a persona's canon was holding another persona's speech. diff --git a/ROADMAP.md b/ROADMAP.md deleted file mode 100644 index ae0d6a25..00000000 --- a/ROADMAP.md +++ /dev/null @@ -1,222 +0,0 @@ -# ROADMAP - -> **LEGACY — frozen 2026-09-03.** This file predates the phased plan and no -> longer reflects reality: the tool surface is now 13 meta-tools (ARIEL_META=1) -> over a 66-name internal registry, the schema lives in the alembic chain -> (§2 "Alembic schema migrations" below is DONE), and the §17 migration list -> predates phases A–E that already shipped it. Canonical plan chain: -> **`docs/compose/specs/2026-08-28-roadmap-master-list.md`** (feature scope, -> CLOSED) → **`docs/compose/specs/2026-09-04-phase-fgh-design.md`** (F/G/H + -> Stage 2, SHIPPED) → **`docs/compose/specs/2026-09-09-polish-roadmap.md`** -> (ACTIVE: polish-era sequencing — test refactor, transport E2E, eval rigor, -> core modularity, i18n, ops discipline). The live release checklist lives in -> CHANGELOG.md. The original text below is kept only for the pre-v1.0.0 items -> that still apply (publishing, docker, npm). - -> Future development plans for mcp-ariel-memory after v1.0.0. - ---- - -## 1. Publishing & Distribution - -- [ ] **PyPI publication** — `twine upload dist/*`, requires `~/.pypirc` with API token -- [ ] **GitHub Release** — create tag `v1.0.0` + release with CHANGELOG -- [ ] **MCP Registry update** — fix description: "37 tools" → "19 tools" -- [ ] **Docker Hub** — publish `ariel-memory` image -- [ ] **npm version** — verify `mcp-ariel-memory@1.0.0` is current, bump if needed - -## 2. Architecture & Performance - -- [x] **Float blobs migration** — `keep_float_blobs` config + migration v7 (drop column) -- [x] **Expand batched reads** — fetchmany(1000) in forgetting, scheduler -- [x] **Connection pooling** — cache_size=64MB, temp_store=MEMORY -- [ ] **Alembic schema migrations** — replace raw SQL migrations with Alembic for versioned, reversible schema changes. Run migrations in CI before Docker build. -- [ ] **ONNX embeddings** — export BAAI/bge-small-en-v1.5 to ONNX + INT8 quantization via HuggingFace optimum. 4-5x faster inference, lower RAM. Blocks GIL on CPU currently. -- [ ] **HNSW index** — integrate hnswlib for binary search (replaces O(n) scan). Config: `binary.index: "hnsw"` with fallback to brute-force. Requires migration for index storage. -- [ ] **FTS5 improvements** — add triggers for auto-sync content (currently manual INSERT) -- [ ] **Extended indexes** — add indexes for `temporal_events`, `temporal_links` - -## 3. RAG & Search - -- [x] **Supervised thresholds** — per-dimension MIB thresholds from labeled pairs (implemented) -- [ ] **Hybrid reranker** — add cross-encoder reranking after RRF -- [ ] **Embedding cache** — implement LRU cache for repeated queries -- [ ] **Auto-strategy** — make `auto` strategy smarter (based on query analytics) -- [ ] **Query expansion** — add synonyms for FTS5 - -## 4. Wiki Restructuring - -- [x] **Unify wiki system** — merge `file_wiki.py`, `user_wiki.py`, `agent_wiki.py` into one `WikiManager` with layer-based separation. Current 3 separate classes duplicate logic. Goal: single FTS5 index, shared sync logic, configurable per-layer behavior. - -## 4. Bug Fixes & Cleanup - -- [x] **Deprecated API cleanup** — removed `search_rrf()`, `search_binary()` (A7) -- [x] **Tool rename** — `memory_search_rrf` → `memory_search` (after MCP Registry update) -- [ ] **CLI improvements** — add `--version` flag, improve help messages -- [ ] **Dashboard** — add live updates (WebSocket), improve UI - -## 5. Testing & CI - -- [x] **Coverage** — add `--cov` in CI, reach 80% coverage (current: 73%) -- [ ] **Coverage 80%** — raise coverage from 73% to 80%: test server.py, tools_ops.py, backup_cron.py, saga.py, wiki/manager.py -- [x] **Property-based testing** — 25 Hypothesis tests (similarity, scoring, quantize, secrets, ring buffer) -- [x] **CI pipeline** — lint (ruff), typecheck (mypy), quality (skylos), security (gitleaks + pip-audit), test matrix (3.10-3.13) -- [x] **Concurrency control** — cancel-in-progress on CI workflows -- [x] **Pip caching** — all CI jobs use pip cache -- [ ] **Load testing** — add k6/Artillery tests for production simulation -- [ ] **Fuzz testing** — add fuzz tests for parsing and validation -- [ ] **Cross-platform testing** — add Windows to CI matrix (currently Linux only) -- [ ] **Benchmark tracking** — store benchmark results in CI, track regressions - -## 6. Documentation - -- [x] **API Reference** — auto-generate from docstrings (MkDocstrings) -- [x] **MkDocs site** — Material Theme, deployed to GitHub Pages -- [x] **Contributing guide** — CONTRIBUTING.md with contributor instructions -- [ ] **Architecture diagrams** — add mermaid diagrams to docs -- [ ] **Examples** — add usage examples for Claude Desktop, Hermes, etc. - -## 7. Security - -- [x] **Secret scanning** — GitHub secret scanning + push protection enabled -- [x] **Dependency audit** — pip-audit in CI, CVE scanning -- [x] **CodeQL** — default setup (AST analysis for SQL injection, path traversal) -- [x] **gitleaks** — CI workflow for secret scanning -- [x] **Docker hardening** — non-root user (UID 1000) -- [x] **CORS hardening** — restrict to localhost, configurable via config.yaml -- [x] **Issue forms** — YAML forms for bug reports and feature requests -- [ ] **RBAC** — add role-based model for multi-tenant deployments -- [x] **Input validation** — add Pydantic schemas on MCP tools -- [ ] **Key rotation** — zero-downtime master key rotation with re-encryption - -## 8. Integrations - -- [ ] **LangChain** — add LangChain integration (memory retriever) -- [ ] **AutoGen** — add AutoGen framework support -- [ ] **CrewAI** — add CrewAI support -- [ ] **LlamaIndex** — add LlamaIndex integration - -## 9. Hooks & Extensibility - -- [ ] **Plugin system** — implement plugins for custom hooks -- [ ] **Webhook support** — add webhook callbacks for external services -- [ ] **GraphQL API** — add GraphQL endpoint - -## 10. Operations & Observability - -### 10.1 Monitoring & Alerting - -- [ ] **Prometheus metrics** — add Prometheus-compatible metrics endpoint (`/metrics`) -- [ ] **Telegram webhook** — send alerts to Telegram channel via bot API -- [ ] **Discord webhook** — send alerts to Discord channel via webhook URL - -### 10.2 Health & Readiness - -- [x] **Healthcheck endpoint** — `GET /health` with status, version, uptime, DB connectivity -- [x] **Readiness probe** — `GET /ready` with DB + migrations status -- [x] **Liveness probe** — `GET /alive` heartbeat -- [ ] **OpenTelemetry** — add OpenTelemetry tracing for distributed observability - -### 10.3 Graceful Shutdown & Signal Handling - -- [x] **Signal handling** — handle `SIGTERM` and `SIGINT` for graceful shutdown -- [x] **Stop background tasks** — cancel saga watchdog, backup cron, read_only_replica -- [ ] **Drain connections** — wait for in-flight requests to complete before exit - -## 11. Typed Memory & Importance - -- [x] **Typed Memory** — 13 categories with per-type retention, decay, and boost -- [x] **Memory kinds** — instruction, fact, decision, goal, preference, commitment, relationship, observation, rule, todo, question, hypothesis, context -- [x] **Type-aware forgetting** — instruction/rule/commitment never decay/archive -- [x] **Type-aware consolidation** — low importance instruction/rule/commitment still promote -- [x] **Type-aware hooks** — importance gate uses type policy -- [x] **Type-aware RAG boost** — search results boosted by query-type matching -- [x] **Typed export CLI** — export, reclassify, backfill bulk operations -- [x] **Importance v2** — 8-signal scorer (base, length, question, tech, emotional, novelty, retrieval, noise) -- [x] **Importance scheduler** — background daemon for periodic re-scoring -- [x] **Importance middleware** — uses ImportanceScorer instead of naive heuristic -- [ ] **Adaptive threshold** — replace fixed ImportanceGate threshold with EMA (exponential moving average) of recent message importance scores. Current 0.3 threshold is static; EMA would adapt to conversation patterns (high-signal technical discussions vs low-signal casual chat). - -## 12. Saga & Reliability - -- [x] **Saga retry** — exponential backoff with configurable retry_attempts/retry_on -- [x] **Saga idempotency** — idempotency_key_fn + saga_step_log prevents duplicate effects -- [x] **Saga encryption** — atomic encrypted state writes with legacy rotation -- [x] **Saga compensation** — saves state before compensation, archives deleted entries -- [x] **Saga cleanup** — handles stuck/failed/manual_review_required states - -## 13. RAG Improvements - -- [x] **Unified search** — `search(query, strategy=...)` with fts/mib/hybrid/auto -- [x] **BM25 conflict similarity** — char-trigram Jaccard fallback -- [x] **Type-aware search boost** — boost_for_query based on memory_kind -- [x] **Batched embedding reads** — fetchmany(1000) instead of fetchall -- [x] **epi_tags table** — indexed JOIN for fast tag lookups (1850 ops/s) -- [x] **rag_chunks index** — (page_id, chunk_index) for JOINs (3537 ops/s) -- [x] **Performance benchmarks** — FTS 1817, MIB 215, hybrid 178 ops/s - -## 14. Tutorials & Launch Demo - -- [x] **Launch demo script** — `demo.py` creates test data and shows features -- [ ] **Getting started guide** — step-by-step tutorial for new users -- [ ] **Quick start with Docker** — one-command Docker setup with sample data -- [ ] **Hermes integration guide** — how to deploy on Hermes agent - -## 15. Operational Tasks - -- [x] **Remove deprecated features** — search_rrf, search_binary removed -- [ ] **Cleanup** — remove unused tables, indexes, functions -- [ ] **Optimization** — optimize memory usage, CPU, I/O -- [ ] **Scalability** — test with 1M+ records - -## 16. Hermes Memory Integration - -- [ ] **Archive config** — expose `archive_threshold_days`, `archive_min_importance`, `forgetting.decay_rate` via MCP tools for agent self-configuration -- [ ] **Memory health check** — add `memory_stats` tool to check archive size, forgetting schedule, typed memory distribution -- [ ] **Auto-archival** — implement cron-based archival in Hermes (currently only `archive_threshold_days` is configurable from config) - ---- - -**Completed:** 38/65 items -**Last updated:** 2026-07-05 - -## 17. Migration from agentmemory - -Features to port from agentmemory (rohitg00/agentmemory) for full replacement. - -| Пункт | Фича | Описание | Приоритет | -|-------|------|----------|-----------| -| R1 | Obsidian export | Экспорт памяти в Obsidian markdown с wikilinks | nice-to-have | -| R2 | Mesh sync | P2P синхронизация между инстансами | nice-to-have | -| R3 | Cross-agent sync | Память доступна из Claude Code, Cursor, OpenCode | nice-to-have | -| R4 | Git snapshots | Версионирование памяти через git commit/diff/rollback | nice-to-have | - -### Migration phases - -**Phase 1 (simple, 3-4 days):** -- SHA-256 dedup (5min TTL) -- Circuit breaker (3 errors → open → 30s) -- Token budget (2000 tokens on context_inject) -- Privacy filter (strip secrets before save) - -**Phase 2 (medium, 5-7 days):** -- Slot system (8 pinned memory units) -- Lesson confidence (strengthen/decay) -- Faceted tagging (dimension:value with AND/OR) - -**Phase 3 (complex, 10-14 days):** -- Hybrid search RRF with graph traversal -- Knowledge graph extraction from sessions -- Action graphs with dependencies -- Lease system for multi-agent - -**Phase 4 (hooks, 5-7 days):** -- sync_turn hook (background capture) -- on_memory_write hook (mirror MEMORY.md) -- Diagnostics tool -- Export tool - ---- - -**Completed:** 38/65 items (+4 roadmap) -**Last updated:** 2026-07-07 diff --git a/tests/test_shared/test_saga_crypto_coverage.py b/tests/test_shared/test_saga_crypto_coverage.py index e287bf1f..e4e6c08d 100644 --- a/tests/test_shared/test_saga_crypto_coverage.py +++ b/tests/test_shared/test_saga_crypto_coverage.py @@ -17,8 +17,13 @@ def test_write_state_atomic_creates_encrypted_file(tmp_path): assert path.exists() data = path.read_bytes() assert len(data) > 0 - # Should not be plain JSON (encrypted) - assert not data.startswith(b"{") + # Encrypted, not plain JSON. Deliberately NOT asserted on the first byte: + # the blob opens with the first byte of a random 24-byte nonce, which is `{` + # in 1 write out of 256, so that assertion failed about once per 256 full + # runs (measured: 14 of 4096 encrypt_json calls begin with `{`). + with pytest.raises((UnicodeDecodeError, json.JSONDecodeError)): + json.loads(data.decode("utf-8")) + assert read_state(path) == state def test_write_state_atomic_creates_parent_dirs(tmp_path): @@ -67,9 +72,14 @@ def test_read_state_legacy_rotates_to_encrypted(tmp_path): assert len(w) == 1 assert "rotating" in str(w[0].message).lower() - # File should now be encrypted - data = path.read_bytes() - assert not data.startswith(b"{") + # File should now be encrypted. Asserted on behaviour, not on the first + # byte: a second read must not treat it as plain JSON again (no rotation + # warning) and must decrypt. The old `not data.startswith(b"{")` was the + # same 1-in-256 flake as above. + with warnings.catch_warnings(record=True) as w_after: + warnings.simplefilter("always") + assert read_state_legacy_or_encrypted(path) == {"legacy": True} + assert len(w_after) == 0, "a second read must not classify the file as plain JSON" def test_read_state_legacy_reads_encrypted(tmp_path):