Files
confidence-engine/lib/behaviour-selection/behaviour-selector.js
T

194 lines
6.9 KiB
JavaScript

/**
* Behaviour Selection — Experiment 19 (Passive)
*
* A small deterministic selector that maps Investigation State Assessment
* output to one of five behaviours: Acknowledge, Clarify, Summarise, Continue,
* Pause.
*
* This experiment tests whether behaviour selection is useful. It does not
* change engine behaviour — it only observes and reports through Developer
* Details.
*
* Design reference: docs/behaviour-selection.md (v0.1 Implementation Brief)
*/
/* ── Behaviour constants ──────────────────────────────────── */
const BEHAVIOURS = [
"acknowledge",
"clarify",
"summarise",
"continue",
"pause",
];
const PRIORITIES = {
acknowledge: 1,
clarify: 2,
summarise: 3,
pause: 4,
continue: 5, // default
};
/* ── Acknowledge exclusion gate ─────────────────────────────── */
/**
* Deterministic exclusions for Acknowledge.
* Returns true when Acknowledge should not fire, even if its positive trigger matches.
* This gate qualifies the trigger; it does not replace it.
*/
function isAcknowledgeExcluded(assessment) {
// Phase-based exclusions: synthesising and concluding states call for Summarise, not Acknowledge
if (["synthesising", "concluding"].includes(assessment.phase.value)) return true;
// Progress-based exclusion: stalled progress calls for Pause, not Acknowledge
if (assessment.progress.value === "stalled") return true;
// Health-based exclusion: user_overloaded calls for Pause, not Acknowledge
if (assessment.conversationHealth.value === "user_overloaded") return true;
return false;
}
/* ── Selection rules (one rule per behaviour) ─────────────── */
function selectAcknowledge(assessment) {
if (assessment.conversationHealth.value === "healthy" && assessment.phase.confidence !== "low") {
// Apply exclusion gate before returning acknowledge
if (isAcknowledgeExcluded(assessment)) return null;
return {
behaviour: "acknowledge",
confidence: "medium",
reason: "Healthy conversation with established context — user provided useful information that warrants acknowledgment before introducing new uncertainty."
};
}
return null;
}
function selectClarify(assessment) {
if (assessment.conversationHealth.value === "too_broad") {
return {
behaviour: "clarify",
confidence: "high",
reason: "Conversation health is too broad — investigation may be spreading too thin. Narrow focus through a specific clarification question."
};
}
if (assessment.phase.value === "orienting" && assessment.phase.evidence?.observationDensity < 3) {
return {
behaviour: "clarify",
confidence: "medium",
reason: "Investigation is in orienting phase with insufficient observations (< 3). A targeted clarification question will anchor the starting point."
};
}
return null;
}
function selectSummarise(assessment) {
if (assessment.phase.value === "synthesising") {
return {
behaviour: "summarise",
confidence: "high",
reason: "Investigation is in synthesising phase — connected observations have accumulated and a restatement of current understanding will compress without losing detail."
};
}
if (assessment.phase.value === "concluding") {
return {
behaviour: "summarise",
confidence: "high",
reason: "Investigation is concluding — a summary of resolved understanding provides closure anchor before the user decides next steps."
};
}
// Turn-count based summarisation (conservative threshold)
if (assessment.phase.evidence?.resolvedNodeCount >= 3 && assessment.progress.value === "steady") {
return {
behaviour: "summarise",
confidence: "medium",
reason: "Three or more items resolved with steady progress — enough accumulated understanding warrants a compression pass."
};
}
return null;
}
function selectPause(assessment) {
if (assessment.phase.value === "focusing" && assessment.progress.value === "stalled") {
return {
behaviour: "pause",
confidence: "high",
reason: "Focusing phase with stalled progress — the investigation has reached a single active unknown but momentum has stopped. Hold space rather than pushing for more."
};
}
if (assessment.conversationHealth.value === "user_overloaded") {
return {
behaviour: "pause",
confidence: "medium",
reason: "User appears overloaded — reduce pressure by acknowledging progress before inviting further contribution."
};
}
return null;
}
/* ── Main selector ─────────────────────────────────────────── */
/**
* Select a behaviour based on investigation state assessment.
*
* Applies five deterministic rules in priority order. If no rule fires,
* returns continue (the default).
*
* @param {Object} assessment — Investigation State Assessment from Exp 18
* @param {string} assessment.version — Assessment version
* @param {string} assessment.confidence — Overall confidence (high/medium/low)
* @param {Object} assessment.phase — Phase assessment { value, confidence, signals, evidence }
* @param {Object} assessment.progress — Progress assessment { value, confidence, signals, evidence }
* @param {Object} assessment.conversationHealth — Health assessment { value, confidence, signals, evidence }
* @returns {{ behaviour: string, confidence: string, reason: string }}
*/
export function selectBehaviour(assessment) {
if (!assessment) {
return {
behaviour: "continue",
confidence: "low",
reason: "No assessment available — defaulting to continue (ask next question).",
priority: PRIORITIES.continue
};
}
// Guard against partial assessment objects with missing sub-structures
if (!assessment.conversationHealth || !assessment.phase) {
return {
behaviour: "continue",
confidence: "low",
reason: "Assessment incomplete — missing required dimensions, defaulting to continue (ask next question).",
priority: PRIORITIES.continue
};
}
// Apply rules in priority order
let result = selectAcknowledge(assessment);
if (result) return { ...result, priority: PRIORITIES.acknowledge };
result = selectClarify(assessment);
if (result) return { ...result, priority: PRIORITIES.clarify };
result = selectSummarise(assessment);
if (result) return { ...result, priority: PRIORITIES.summarise };
result = selectPause(assessment);
if (result) return { ...result, priority: PRIORITIES.pause };
// Default — Continue
return {
behaviour: "continue",
confidence: "low",
reason: "No explicit rule matched — defaulting to continue (ask the next question).",
priority: PRIORITIES.continue
};
}
export const BEHAVIOUR_OPTIONS = BEHAVIOURS;
export default selectBehaviour;