Files
brain/tools/missed-activations.mjs
T

81 lines
3.3 KiB
JavaScript

#!/usr/bin/env node
/**
* Missed-activation matcher (Pravila §16.4 + §17, Phase 2 Task 11).
* Pure deterministic — read-only, no exec, no fs.
*
* Two episode schemas supported:
*
* SCHEMA v4 (LLM-first router, §17):
* 1. schema_version === 4
* 2. NOT observer_error
* 3. classifier_output.task_type ∉ {conversation, micro, manual_override} (§17 exempt set)
* 4. classifier_output.no_skill_found !== true (classifier honestly admits no match → not a miss)
* 5. classifier_output.recommended_node is set
* 6. dormancy[recommended_node] !== true (still callable)
* 7. execution_trace.actual_node_invoked_first === 'direct' (no real node fired first)
* → byNode[recommended_node]++, byClassification[task_type]++
*
* SCHEMA v2/v3 (legacy, §16.4 conditional rule):
* 1. schema_version >= 2 && < 4
* 2. NOT observer_error
* 3. primary_rationale.task_classification ∈ classificationMap AND map[c].length > 0
* 4. primary_rationale.node_chosen === 'direct'
* 5. AT LEAST ONE recommended node is non-dormant
*
* Threshold: single episode (per Pravila §16.4 v1.36).
* classificationMap/dormancy positional args remain (back-compat with brain-retro-
* analyzer + observer-coverage-checker call sites); for v4 episodes the map arg
* is ignored — recommended_node is inline in the episode.
*/
const V4_EXEMPT_TASK_TYPES = new Set(['conversation', 'micro', 'manual_override']);
export function detectMissedActivations(episodes, classificationMap = {}, dormancy = {}) {
const byNode = {};
const byClassification = {};
let totalMissed = 0;
for (const e of episodes) {
if (!e || e.observer_error) continue;
if (typeof e.schema_version !== 'number') continue;
// ── v4 path (§17 LLM-first) ─────────────────────────────────────────
if (e.schema_version >= 4) {
const co = e.classifier_output || {};
const tr = e.execution_trace || {};
if (!co.task_type || V4_EXEMPT_TASK_TYPES.has(co.task_type)) continue;
if (co.no_skill_found) continue;
if (!co.recommended_node) continue;
if (dormancy[co.recommended_node] === true) continue;
const invokedFirst = tr.actual_node_invoked_first;
if (invokedFirst && invokedFirst !== 'direct') continue;
totalMissed += 1;
byClassification[co.task_type] = (byClassification[co.task_type] || 0) + 1;
byNode[co.recommended_node] = (byNode[co.recommended_node] || 0) + 1;
continue;
}
// ── v2/v3 legacy path (§16.4) ───────────────────────────────────────
if (e.schema_version < 2) continue;
const pr = e.primary_rationale || {};
const cls = pr.task_classification;
const chosen = pr.node_chosen;
if (!cls || chosen !== 'direct') continue;
const recommended = classificationMap[cls];
if (!Array.isArray(recommended) || recommended.length === 0) continue;
const live = recommended.filter((id) => dormancy[id] === false);
if (live.length === 0) continue;
totalMissed += 1;
byClassification[cls] = (byClassification[cls] || 0) + 1;
for (const id of live) {
byNode[id] = (byNode[id] || 0) + 1;
}
}
return { totalMissed, byNode, byClassification };
}