194 lines
6.9 KiB
JavaScript
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;
|