feat(router): пининг совета роутера по goalHash (переписка шагов не зовёт LLM заново)

Новый router-pin-store: пин совета роутера по (task_id, goalHash) пер-сессионно.
on-plan-write пин-aware: пин-попадание по неизменной цели → совет переиспользуется,
classifyImpl НЕ зовётся; промах/смена цели → classify + сохранение пина. Проводка в
активный наставник-хук инъекцией реального стора с sessionId (инъекция-выкл по умолчанию,
старое поведение/тесты целы). Хвост спеки роутера §4 (пининг по goalHash), эпик роутер-реестр
этап 3, item 2. Граница не тронута (recommended_chain, цепочки, observer-stop-hook, owner-seal).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Дмитрий
2026-06-21 08:18:15 +03:00
parent 4713a65b63
commit f22f8bd2ef
5 changed files with 150 additions and 5 deletions
+17 -5
View File
@@ -48,6 +48,8 @@ export async function onPlanWrite({
registry = null,
declaredSkills = [],
planGoal = '',
pinLoadImpl = null,
pinSaveImpl = null,
roundMemory = {},
} = {}) {
const planHash = planId(planSteps);
@@ -59,14 +61,24 @@ export async function onPlanWrite({
}
// Мерж роутер↔наставник: зовём classify() как функцию (мозг роутера цел). Сбой/недоступен →
// recommendedChain=null → наставник судит план БЕЗ скил-сверки (fail-safe §5, не ложный NO-GO).
// Пининг (спека роутер-реестр §4): пин-попадание по (taskId, goalHash) → совет переиспользуется,
// classifyImpl НЕ зовётся; промах → classifyImpl + сохранение пина. Без пин-имплов — прежнее поведение.
let recommendedChain = null;
let routerClassification = null; // видимость: сырой результат classify наружу для снимка/баннера
if (typeof classifyImpl === 'function') {
try {
const c = await classifyImpl(planGoal, registry);
routerClassification = c ?? null;
recommendedChain = recommendedChainOf(c);
} catch { recommendedChain = null; routerClassification = { unavailable: true }; }
let pinned = null;
try { pinned = typeof pinLoadImpl === 'function' ? pinLoadImpl({ taskId, planGoal }) : null; } catch { pinned = null; }
if (pinned) {
routerClassification = pinned;
recommendedChain = recommendedChainOf(pinned);
} else {
try {
const c = await classifyImpl(planGoal, registry);
routerClassification = c ?? null;
recommendedChain = recommendedChainOf(c);
if (c && typeof pinSaveImpl === 'function') { try { pinSaveImpl({ taskId, planGoal, classification: c }); } catch { /* best-effort */ } }
} catch { recommendedChain = null; routerClassification = { unavailable: true }; }
}
}
const skillContext = renderSkillContext({ declared: declaredSkills, recommendedChain, registry });
// Производитель вердикта (C T5b): сбой → ok:false/wired:false (SE-R6-6, не суд).