Skip to content

Commit 74680e0

Browse files
author
Murat
committed
docs(HC): closeout plan — zero-deferred, 8 tasks covering all S-anchors and payoffs
1 parent 89a40c0 commit 74680e0

1 file changed

Lines changed: 156 additions & 0 deletions

File tree

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
# Phase H-Closeout — Zero-Deferred Implementation Plan
2+
3+
> **For agentic workers:** REQUIRED SUB-SKILL: compose:subagent per task. Checkbox syntax.
4+
> **Prerequisite:** Phase G+H завершены (головной 89a40c0). Аудит general-44: 3 разрыва + ~15 S-якорей + 10 заплаток.
5+
> **Принцип (user 2026-09-05):** ZERO-DEFERRED, ZERO-PAYOFFS — чистый отлаженный проект, не костыли.
6+
7+
**Goal:** Все пропущенные S-якоря реализованы, все заплатки устранены, prod-wire завершён.
8+
9+
**Architecture:** Wire-фиксы (Task 1-2) подключают построенное к прод-пути. Минеры (Task 3-4) достраивают граф. Privacy/NLP (Task 5) усиливает гейт. Детерминизм (Task 6) + CLACK (Task 7) + eval-метрики (Task 8) закрывают дизайн.
10+
11+
**Tech Stack:** существующий стек + networkx, sqlite-vec (опция), ru-NER словарь.
12+
13+
## Global Constraints
14+
15+
- Полный pre-push gate после каждого Task (ruff × 2, mypy 10 dirs, pytest junit)
16+
- 65 тулов не растёт (все новые функции — внутренние или CLI)
17+
- 1254 тестов — база; каждый Task не должен ломать
18+
- Взвешенный Hamming и прочие квантовые скальпели — применить к rag/quantize.py
19+
- NEVER break существующие тесты: если тест противоречит новому контракту — адаптировать тест (пометить)
20+
21+
---
22+
23+
### Task C1: Wire dual-route в prod + enrich_sessions в cron + agent-layer
24+
25+
**Covers:** S10 (route_query в prod), S3 (l2_enrich), S7 (agent-layer nightly)
26+
27+
**Files:**
28+
- Modify: `rag/multi_source.py::MultiSourceRAG.search` — если RETRIEVAL_MODE in (full/dense_per_kind/gated): route_query как пре-дверь, RRF = генератор кандидатов для EDM re-rank
29+
- Modify: `features/backup_cron.py::_fire_nightly_hooks` — добавить `enrich_sessions(days=1)` (после sweep)
30+
- Modify: `hooks/agent_hooks.py` — добавить `nightly` хук (mirror user_hooks._nightly: graph_enrich + компакт + свип)
31+
- Test: `tests/test_rag/test_prod_wire.py` — RETRIEVAL_MODE='full' в prod-пути memory_search → EDM-порядок отличается от сырого RRF; enrich_sessions вызывается backup_cron
32+
33+
- [ ] **Step 1: Failing test**: memory_search с включённым full-режимом → результаты отсортированы по EDM (не по raw RRF); enrich_sessions(mock L0) → sessions.summary обновлён.
34+
- [ ] **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!).
35+
- [ ] **Step 3: Коммит** `feat(HC1): wire dual-route to prod, l2_enrich to cron, agent-layer nightly`.
36+
37+
---
38+
39+
### Task C2: import_chat ts + hash-chain writer + min_remain уже есть
40+
41+
**Covers:** S1 (hash-chain), H3-fix (ts)
42+
43+
**Files:**
44+
- 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)
45+
- Modify: `shared/l0.py::capture` — параметр `ts_override: float | None = None`; hash-chain: hash_self = sha256(hash_prev + text + raw_type + str(ts))
46+
- 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]+...))
47+
48+
- [ ] **Step 1: Тест**: import с orig_ts → l0.ts == orig_ts; последовательная запись → hash-chain verify: пересчитать chain, битых нет.
49+
- [ ] **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.
50+
- [ ] **Step 3: Коммит** `feat(HC2): import preserves orig_ts; L0 hash-chain tamper-evidence`.
51+
52+
---
53+
54+
### Task C3: Минеры #10 триплет-минер + behavior-аннотации (S6b)
55+
56+
**Covers:** S6b (триплеты query→tool→outcome, behavior-аннотации)
57+
58+
**Files:**
59+
- Create: `lifecycle/graph_miners.py::miner_tool_triplets` — tool_use + tool_result пары (l0_journal, связка по tool_use_id) → узлы (query/action/outcome) → рёбра `query_tool`/`tool_outcome`
60+
- Create: `lifecycle/tool_stats.py` — per-tool статистика (call count, error rate, avg outcome) → JSON/таблица для Stage 2 MCP hints
61+
- Test: `tests/test_lifecycle/test_tool_triplets.py`
62+
63+
- [ ] **Step 1: Тест**: L0 с tool_use(id=t1) + tool_result(tool_use_id=t1) → miner → узлы + рёбра; behavior-статистика: 3 вызова, 1 ошибка → error_rate 0.33.
64+
- [ ] **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-отчёт.
65+
- [ ] **Step 3: Wire** в graph_enrich MINERS + behavior-stats в report.
66+
- [ ] **Step 4: Коммит** `feat(G-miner10): tool triplets + behavior annotations data`.
67+
68+
---
69+
70+
### Task C4: Privacy ru-тир — словарь персон + condition-splitting repair
71+
72+
**Covers:** S2 (privacy: en-NER gap на русском), conflicts (condition-splitting)
73+
74+
**Files:**
75+
- Modify: `mcp_server/utils/privacy.py` — ru-словарный тир: известные персоны проекта (Лили/Люси/Аня/Эли/мамочка + синонимы из rag.synonyms) маскируются как ⟨PERSON_ru_N⟩; threshold-конфиг
76+
- Modify: `lifecycle/distiller.py` — conflict → condition-splitting: обе записи сохраняются с scope-условием в metadata (не просто contradiction-флаг)
77+
- Test: `tests/test_hooks/test_privacy_ru.py`, расширить test_distiller.py
78+
79+
- [ ] **Step 1: Тест**: «Мамочка сказала перейти на PostgreSQL» → «Мамочка» замаскирован; «Лили красива» → «Лили» замаскирован (sentence-initial!); condition-splitting: конфликтующие факты обе сохранены с metadata.scope условиями.
80+
- [ ] **Step 2: Реализация**: privacy: `RU_PERSONAS` словарь (load from config rag.ru_personas + синонимы) → regex word-boundary; distiller: при conflict → обе записи (не одна с contradiction), metadata.scope = «до X» / «после X» / source-context.
81+
- [ ] **Step 3: Коммит** `feat(F-G0): ru persona tier + condition-splitting repair`.
82+
83+
---
84+
85+
### Task C5: Eval-метрики расширение + memory_kind бэкфилл
86+
87+
**Covers:** S11 (NDCG, drift, negative-controls, two-judge), S13 (estate-quality)
88+
89+
**Files:**
90+
- Modify: `eval/harness.py` — NDCG@k, drift score (old/new confusion), negative-control протокол (shuffled-edge должен падать), two-judge (proxy + второй fuzzy)
91+
- Create: `scripts/backfill_memory_kind.py` — rag_chunks.memory_kind бэкфилл по kind_for_text
92+
- Test: расширить test_eval
93+
94+
- [ ] **Step 1: Тест**: NDCG на mini (perfect ranking = 1.0); drift: KU-пара — old-answer после update = fail; negative-control: shuffled-arms должны давать accuracy < real arms.
95+
- [ ] **Step 2: Реализация**: NDCG формула (DCG/IDCG), drift = old-value в ответе на KU, negative-control — режим report.py с shuffled arms.
96+
- [ ] **Step 3: Бэкфилл**: скрипт rag_chunks → memory_kind по kind_for_text(chunk.content) → UPDATE. Помечено в docstring для ingestor (future fill).
97+
- [ ] **Step 4: Коммит** `feat(H-eval): NDCG/drift/negative-controls + memory_kind backfill`.
98+
99+
---
100+
101+
### Task C6: S9-хвосты — FOK-gate, CAMA N_eff, трёхфазный dream, S2 compression constraint
102+
103+
**Covers:** S9/S10 хвосты
104+
105+
**Files:**
106+
- Modify: `rag/edm.py` — FOK-gate τ=0.12 (reject до LLM), CAMA max-presence + N_eff (Hill diversity) в EDM-фьюжен
107+
- Modify: `lifecycle/graph_enrich.py` — трёхфазный dream (NREM decay → REM bridge → Insight abstract) + S2 compression constraint
108+
- Test: расширить test_graph_enrich.py, test_dual_route.py
109+
110+
- [ ] **Step 1: Тест**: FOK-gate: activation топ-кандидата < 0.12 → reject до LLM; N_eff низкий → abstention-флаг; dream: NREM ослабляет неактивные, REM мостит изолированные, Insight создаёт абстракции; S2 constraint: категория с 1 ребёнком не проходит.
111+
- [ ] **Step 2: Реализация**: FOK-gate в route_query (после ITS, до возврата); N_eff в EDM-фьюжен; dream-фазы в graph_enrich (последовательные проходы); S2 constraint в s2_exhaustive.
112+
- [ ] **Step 3: Коммит** `feat(G): FOK-gate, CAMA N_eff, three-phase dream, S2 constraint`.
113+
114+
---
115+
116+
### Task C7: S13-хвосты — cycles-daemon, cost-cap, gap-reader, Mermaid
117+
118+
**Covers:** S13
119+
120+
**Files:**
121+
- Create: `features/cycles.py` — cycles-конфиг (60s/1ч/3ч/24ч) + chimera cost-cap (per-cycle/rolling-60m/per-task)
122+
- Modify: `features/diagnostics.py::audit_content` — gbrain gap-reader флаги (unknown/stale/uncited/contradicting) + create_safety verdict
123+
- Create: `lifecycle/graph_mermaid.py` — render_mermaid(layer) → Mermaid-строка
124+
- Test: `tests/test_features/test_cycles.py` + расширить audit/mermaid
125+
126+
- [ ] **Step 1: Тест**: cycles конфиг читается; cost-cap блокирует превышение; gap-reader флагует; Mermaid содержит узлы/рёбра.
127+
- [ ] **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).
128+
- [ ] **Step 3: Коммит** `feat(H3): cycles + cost-cap + gap-reader + Mermaid canvas`.
129+
130+
---
131+
132+
### Task C8: S2-хвосты — novelty-gate, topic-классификация, segment-consolidation, pinned/private
133+
134+
**Covers:** S2/S5/S16 хвосты
135+
136+
**Files:**
137+
- Modify: `lifecycle/distiller.py` — semantic novelty-gate (paraphrase-Jaccard > порог → skip как дубликат), topic-классификация (hall_keywords → wiki-тип/epi_tags)
138+
- Modify: `features/inject.py` — pinned-флаг + private-флаг (visibility колонка)
139+
- Create: `lifecycle/segment_consolidation.py` — Lychee boundary detection (Eq1-4) для L0-группировки
140+
- Test: расширить test_distiller.py, test_inject.py, test_segment_consolidation.py
141+
142+
- [ ] **Step 1: Тест**: paraphrase-дубликат → novelty-gate skip; topic → тег; pinned факт всегда в inject; private не в search; boundary detection режет по surprise.
143+
- [ ] **Step 2: Реализация**: novelty-gate (Jaccard против существующих same-key, > 0.85 → skip), topic-классификация (словарь тем), pinned/private колонки (alembic g21), boundary detection (Lychee формулы).
144+
- [ ] **Step 3: Коммит** `feat(F-final): novelty gate, topic classification, pinned/private, segment consolidation`.
145+
146+
---
147+
148+
### Task 9 (финал): Полный gate + коммит + push
149+
150+
**Covers:** все
151+
152+
- [ ] **Step 1: Полный pre-push gate** (4 команды).
153+
- [ ] **Step 2: Коммит + push** `feat(HC): zero-deferred closeout complete`.
154+
155+
## Self-Review
156+
- Все S-якоря и заплатки покрыты C1-C8. Порядок: C1-C2 (wire, быстрые) → C3-C5 (минеры/eval) → C6-C8 (хвосты) → финал.

0 commit comments

Comments
 (0)