From b4552df1a682bd10f21f5d3fedb7c0aad34366c4 Mon Sep 17 00:00:00 2001 From: Pegas Date: Thu, 15 Oct 2026 23:12:52 +0500 Subject: [PATCH] docs: EN source-of-truth workflow (en2ru + DOCSTRAT) - en2ru.py: EN markdown -> Russian via Ollama translategemma-4b (tag: translategemma:latest) strips code-fence artifacts, prints jargon-review hints to stderr - DOCSTRAT.md: rule -- EN canonical source, RU for comprehension only, never edit on drifted Russian (dedup/receipt mistranslation guard) - ROADMAP.ru.md: sample RU generated from EN source - AGENTS.md: section 8 documenting the EN-first workflow --- AGENTS.md | 11 ++++ DOCSTRAT.md | 35 ++++++++++++ ROADMAP.ru.md | 148 ++++++++++++++++++++++++++++++++++++++++++++++++++ en2ru.py | 98 +++++++++++++++++++++++++++++++++ 4 files changed, 292 insertions(+) create mode 100644 DOCSTRAT.md create mode 100644 ROADMAP.ru.md create mode 100644 en2ru.py diff --git a/AGENTS.md b/AGENTS.md index ae5cd36..d1d85df 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -134,3 +134,14 @@ A **gap** is a question you know you cannot answer yet. Manage them, don't just - If the question turned out irrelevant → `status='dismissed'` (prefer this over deleting). - Raise `priority` when the user asks about the topic; lower it when it stops mattering. - Keep questions **specific and answerable** ("How does HNSW build its graph?" not "HNSW"). + +## 8. Documentation language (English source of truth) + +All docs (`*.md`) are written in **English = canonical source of truth**. Russian +(`*.ru.md`) is generated for quick comprehension only; terminology may drift and must +NEVER be used as the editing basis (editing on drifted Russian breaks intent). + +Workflow: write/edit EN → `python3 en2ru.py file.md` → `file.ru.md`. See +[`DOCSTRAT.md`](./DOCSTRAT.md). `translategemma:latest` renders clean orthography but +mishandles jargon (`dedup`→«дублирование», `receipt`→«результат»), so review the +stderr hints; keep unsure terms in Latin. diff --git a/DOCSTRAT.md b/DOCSTRAT.md new file mode 100644 index 0000000..8be1491 --- /dev/null +++ b/DOCSTRAT.md @@ -0,0 +1,35 @@ +# Стратегия документов (English source of truth) + +## Правило (обязательное) + +- **Английский — канонический источник (source of truth).** Все правки вносятся + **только** в исходный `*.md` (EN). +- **Русский — только для бегного понимания.** Это вычислительный **инструмент**; + термиология может «поплыть». +- **Ник не использовать `.ru.md` за основу для правок.** Правим EN → перезапускаем + `en2ru.py`. Нарушение → накаплием ошибочный русский текст и **не можем сделать то, + что задумано** (опора на «поплывшую» термиологию ломает смысл). + +### Причина + +`translategemma:latest` **чисто пишет орфографию**, но **джаргон** переводит криво — +вплоть до переворота значения: `dedup` → «дублирование» (обратное значение), +`receipt` → «результат». Порусскому — вторичен и нуждается в ревью джаргона. + +## Как + +```bash +python3 en2ru.py ROADMAP.md # -> ROADMAP.ru.md +cat doc.md | python3 en2ru.py - # stdin -> doc.ru.md +``` + +- Вывод: `.ru.md`. +- Джаргон-ревью (термы, где модель ошибается) — в **stderr**, не в RU-выводе. +- Оффлайн: зависимостей нет (`urllib`), модель уже в Ollama (`translategemma:latest`). + +## Порядок работ + +1. Пишем/правим **EN** источник. +2. `en2ru.py` → генерим **RU**. +3. Смотри **stderr** — правь flagged-термы (ревью, не авто). +4. Храни **оба** файла рядом: `X.md` (EN, канон) + `X.ru.md` (RU, понимание). diff --git a/ROADMAP.ru.md b/ROADMAP.ru.md new file mode 100644 index 0000000..bd7749d --- /dev/null +++ b/ROADMAP.ru.md @@ -0,0 +1,148 @@ +# Pegas Memory — Roadmap + +Подсистема памяти и базы знаний агента Alicorn («Pegas»). Реализация на Python как отдельный +сервис, который Rust-ядро Alicorn вызывает через MCP/HTTP. Спецификация — в +`alicorn-agent/ARCHITECTURE.md` (ADR-4, ADR-11, ADR-12). + +## Принятые решения (по итогам обсуждения) + +1. **PostgreSQL = единственный источник истины.** Текст, метаданные, трейсы, верификация, outbox. +2. **Qdrant = векторный срез** (EmbeddingGemma 2, dim=768). Замена pgvector. +3. **Memgraph = графовый срез** (связи атомов, зависимости кода). Замена Apache AGE. +4. **pgvector и AGE — НЕ используем.** Векторы только в Qdrant, граф только в Memgraph. +5. **Единица знания — типизированный «атом»**, не чистый текст. + Типы: `text | definition | logical_rule | code | table | fact`. +6. **Память агента (episodic) ≠ база знаний (curated).** Разные таблицы/подсистемы. +7. **Код хранится отдельно**: content-addressed исходники + tree-sitter символы + граф зависимостей. +8. **Логические знания** — структурные правила (pattern→conclusion) с инференцией в графе. +9. **Исследование пробелов** — замкнутый цикл: detect→plan→acquire→parse→extract→value→verify→commit. + +## Инфраструктура (проверено на сервере) + +| Компонент | Адрес | Состояние | +|---|---|---| +| PostgreSQL 16 | atom-db :5434 (user atom, db content_atom) | работает | +| Qdrant | :6333 | работает | +| Memgraph (bolt) | :7687 | работает | +| Ollama | :11434 | работает | +| EmbeddingGemma 2 | `embeddinggemma-2-740m-Q8_0` | dim=768, R@1 0.922 (лучший для ru) | +| LLM-генерация | `lfm25-24b-a2b:128k`, `qwen3.5:4b`, `gemma4:E4B` | работает | + +> EmbeddingGemma 2: полная размерность 768 (проверено curl-тестом). Q8_0 — оптимальный +> баланс производительность/память (MRR 0.944, не теряет производительность). Не путать с v1 `embeddinggemma:300m`. + +## Модель данных (PostgreSQL — канон) + +``` +document — источники (web|pdf|epub|text|code_repo), uri, authority, content_hash +chunk — чистые сегменты (доказательства, locator) — НЕ для извлечения напрямую +atom — типизированные единицы знания (заявление + контент + доверие + статус) +atom_link — связи (inference|cause|reference|contradicts|depends_on|calls|defines) +citation — доказательная база (atom → document → locator → quoted_text) +code_symbol — tree-sitter символы (repo, path, language, name, kind, signature) +episodic_trace— память агента (шаги/действия) — отдельно от базы знаний +research_gap — выявленные пробелы для исследования +outbox — CDC-журнал для синхронизации в Qdrant + Memgraph +``` + +Статусы атома: `candidate → verified | stale | contested`. +Векторы НЕ хранятся в Postgres — только в Qdrant (payload: type/confidence/status/doc_id). + +## Фазы + +### Фаза 1 — Фундамент (MVP) ✅ ЗАВЕРШЕНА +- [x] Структура проекта + venv + зависимости +- [x] DDL Postgres (schema.sql) +- [x] Клиент эмбеддингов EmbeddingGemma 2 (dim=768) +- [x] Клиент Qdrant (коллекция `alicorn_atoms`) +- [x] Клиент Memgraph (узлы atom + рёбра atom_link) +- [x] Ингест: text/pdf/epub/web → чанки +- [x] Дистилляция чанка → атомы (LLM) +- [x] Синхронизация outbox PG → Qdrant + Memgraph +- [x] Smoke-тест end-to-end + +### Фаза 2 — Исследование пробелов ✅ (реализовано, snippet-fallback) +- [x] Детект пробелов (research_gap + add_gap/list_gaps) +- [x] Планирование (приоритетная очередь пробелов) +- [x] Acquire: web-поиск (3 источника) + pdf/epub/text +- [x] Оценка ценности (verify_atom + min_confidence) +- [~] Верификация (verify_atom готов; полный автономный запуск требует прокси для зарубежных сайтов) + +### Фаза 3 — Код ✅ / Логика ✅ +- [x] tree-sitter: извлечение символов (function/class/method/type) — 43 символа сохранено +- [x] Граф зависимостей кода (calls/imports/inherits) — 310 зависимостей, 162 узла CodeSymbol +- [x] Логические правила + инференция в Memgraph (forward-chaining до fixpoint, 38 рёбер выведено) + +### Фаза 4 — Интеграция с DSH (MCP) ✅ ВЫПОЛНЕНА +- [x] MCP-сервер (memory_search/store/ingest/add_gap/list_gaps/research/stats/get_context) +- [x] Подключение к DSH через @deepseek-ai/dsh-mcp-client (cordis.patch.yml, live-reload) +- [ ] Подключение к Rust-ядру Alicorn (Planner/Researcher) — когда ядро будет готово + +## Дополнительно реализовано +- Опциональный прокси (PROXY_URL, SOCKS5/HTTP) + улучшенный User-Agent для полной загрузки страниц. + +- Фильтрация артефактов графа кода (dst="." от относительных импортов). +- Дедупликация в memory_store (опциональный dedup). + (после MVP) +- Семантическая дедупликация атомов (эмбеддинги EmbeddingGemma 2, порог 0.90, конфигуруемо). +- Фильтрация шумных logical_rule (только kind-рёбры) в инференции. + +## Критические риски +- EmbeddingGemma 2 dim=768 — не совместима с bge-m3 (1024) → отдельная коллекция, без миграции. +- Memgraph in-memory: объём графа ограничен RAM. +- Дистилляция зависит от качества локальной LLM — нужна верификация. + +## Самообучение (self-improvement) — статус и планы + +### Что работает (4 механизма, но НЕ замкнутый loop) + +1. **Верификация** (`verify_atom`/`memory_verify` + `verify.py`). LLM self-critique + (`duplicate | corroborates | contradicts`) + **меж-системная подтверждение** (≥1 другой + `doc_id`, cosine ≥ 0.90) → ставка растёт, статус `verified`. Самый зрелый этап. +2. **Логическая инференция** (`memory_infer` + `inference.py`). Применение правил к рёбрам + графа до fixpoint (`imports → depends_on` и т.д.). ⚠️ Даст **только связи**, не новые факты. + Правила **статичные/ручное написание**, новых не выводит сама. +3. **Синтез контекста** (`memory_get_context`). Ищет Qdrant, кластеризует + суммирует атомы. + Может **создавать gap** из исходного текста → есть *реактивное* появление вопроса из источника. +4. **Эпизодические следы** (`pm_episodic_trace`). План/действие/наблюдение/рефлексия — + отдельно от БЗ. Самый «рефлексивный», но **условный**, не привязанный к gap. + +### Что не хватает (дыры в петлях, а не в примитивах) + +| Пробел | Почему критично | +|---|---| +| **① цепочка gap↛answer («дыра commit»)** | При закрытии вакуума **ответ не автоматически + пишется обратно в атомы** → знания улетучиваются, приходится пере-исследовать. | +| **② Самообучение схемы инференции** | Ничего не *предлагает новые правила* из повторяющихся + `verified`-атомов. Это как раз слабое место Hermes-аналога. | +| **③ Застарелость/забывание (staleness guard)** | `verify` ставит `verified`, но **ничто не + помечает `stale`** пере-проверкой по свежему источнику. Путь →`stale` не идёт автоматически. | +| **④ Поверхность противоречий** | `verify` ловит конфликт только в окрестности атома. Ничего + не сканирует весь граф → противоречия не переходят в gap автоматически. | +| **⑤ Процедурная/рецепт-память** | Повторяющиеся успешные воркфлоу не ловятся как переиспользуемые атомы. | +| **⑥ Пассивное создание gap** | Gap создаются через `add_gap` (ручно) или из `memory_get_context`. + Нет авто-обнаружения из не-верифицированного/переносного контента. | + +### Приоритет + +Hermes-анализ показал: Pegas сильна в **«detect»** (verify, infer), слаба в **«commit»** и +**«maintain»**. Слабые места — **feedback-петли**, а не примитивы. Поэтому максимальный выигрыш: +① дыра «commit» × ③ гвард сталкнутости × ② самообучение правил инференции — это **закрывает loop**. + +### Планируемые улучшения (Tier) + +- **Tier 1 — высокий приоритет, безопасно / малый** + - ① **Окончите дырой** — после `verify`, подняв атомы из gap, запустить синтез обратно в + `memory_ingest/synthesis` (надпись верифицированного ответа в атомы). + - ③ **Гвард сталкнутости** — `update_status`: пере-`web_search` по URL/date атома; + противоречит/устарел → `stale`. Реализуйте путь →`stale`, который сейчас не ходит. +- **Tier 2 — средний приоритет** + - ② **Самообучение правил инференции** — `memory_infer` **предлагает кандидаты** + `logical_rule` на ревью (никогда не применяет автоматически). + - ⑥ **Процедурная/рецепт-память** (паттерн из mem0, `procedural memory`) — агент действует + (это я через `mcp__pegas-memory__*`), поэтому повторы успешных `/запрос` или воркфлоу ≥N + раз с `verified`-результатом → **атомный** паттерн-рецепт (не файлы Hermes, а атомно с + dedup). Формат receipt: **goal → шаги → условия → результат** (переиспользуемый воркфлоу). +- **Tier 3 — структурные** + - ④ **Сканирование на противоречия** — периодический бэк-проход графа на противоречия → авто gap. + - ⑥ **Авто-обнаружение gap** — `verify` открывает gap на конфликт/дыру в источнике. \ No newline at end of file diff --git a/en2ru.py b/en2ru.py new file mode 100644 index 0000000..3acb545 --- /dev/null +++ b/en2ru.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python3 +"""en2ru — English-source-of-truth -> Russian comprehension doc. + +RULE (see DOCSTRAT.md): English is the CANONICAL source of truth. Russian is +only a comprehension aid; terminology may drift and MUST NEVER be used as the +editing basis. Always edit the `*.md` (EN), then re-run this to refresh +`.ru.md`. + +Usage: + python3 en2ru.py path/to/doc.md # writes path/to/doc.ru.md + cat doc.md | python3 en2ru.py - # stdin -> stdout + doc.ru.md + +Model: translategemma:latest (Ollama, local @ 127.0.0.1:11434). +Jargon drift hints are printed to STDERR (never inside the RU output). +No external deps (urllib only) so it works offline after the model is pulled. +""" +import sys +import json +import urllib.request + +OLLAMA_URL = "http://127.0.0.1:11434/v1/chat/completions" +MODEL = "translategemma:latest" + +# Terms TranslateGemma-4b mishandles -> flag for human review (never auto-fix). +# Maps the EN token to the safe RU handling. JAPN is intentional: keep short. +JARGON_HINTS = [ + ("dedup", "дедупляция / удаление дублей"), # NOT «дублирование» (reversed!) + ("receipt", "рецепт (паттерн действий)"), # NOT «результат» + ("atom-level", "на уровне атомов"), + ("stale", "устаревший (status=stale)"), + ("gap", "пробел (gap)"), + ("verify", "верификация / verify"), + ("rerank", "реранг / reranking"), + ("cache", "кэш"), + ("inferred", "выведено / inferred"), + ("web_search", "web_search (инструмент)"), + ("ingest", "ингестия / загрузка / ingest"), +] + + +def translate(text: str) -> str: + prompt = ( + "Translate the following Markdown technical text to Russian.\n" + "Rules:\n" + "- Output ONLY the Russian text. Preserve Markdown structure, code blocks,\n" + " backticks, code identifiers, URLs, command and tool names verbatim.\n" + "- Do NOT translate: code, identifiers, tool names, file extensions, URLs,\n" + " and product names (Memgraph, PostgreSQL, Qdrant, Memgraph).\n" + "- If unsure about a term, keep it as-is in Latin (do not guess).\n" + "- Words you must handle carefully: " + + ", ".join(en for en, _ in JARGON_HINTS) + ".\n\n" + "TEXT:\n```\n" + text + "\n```" + ) + payload = { + "model": MODEL, + "stream": False, + "messages": [{"role": "user", "content": prompt}], + } + req = urllib.request.Request( + OLLAMA_URL, + data=json.dumps(payload).encode("utf-8"), + headers={"Content-Type": "application/json"}, + ) + with urllib.request.urlopen(req, timeout=300) as resp: + return json.loads(resp.read())["choices"][0]["message"]["content"] + + +def main() -> None: + if len(sys.argv) < 2 or sys.argv[1] == "-": + src = sys.stdin.read() + base = "doc" + else: + with open(sys.argv[1], encoding="utf-8") as fh: + src = fh.read() + base = sys.argv[1].rsplit(".", 1)[0] + + ru = translate(src) + # model echoes the prompt's ``` fences -> drop leading/trailing fence lines + lines = ru.splitlines() + if lines and lines[0].strip() == "```": + lines = lines[1:] + if lines and lines[-1].strip() == "```": + lines = lines[:-1] + ru = "\n".join(lines).strip() + out_path = base + ".ru.md" + with open(out_path, "w", encoding="utf-8") as fh: + fh.write(ru) + + low = src.lower() + print("# en2ru -> " + out_path, file=sys.stderr) + for en, hint in JARGON_HINTS: + if en.lower() in low: + print(" review: '%s' -> ensure '%s'" % (en, hint), file=sys.stderr) + print("OK: " + out_path) + + +if __name__ == "__main__": + main()