Implement Experiment 19: deterministic behaviour selector with five behaviours (Acknowledge, Clarify, Summarise, Continue, Pause). - lib/behaviour-selection/behaviour-selector.js — Pure function selector applying v0.1 rules in priority order (acknowledge > clarify > summarise > pause > continue). Defaults to Continue with low confidence when no rule matches or assessment is incomplete. Guards against partial objects. - tests/behaviour-selector.test.js — 51 tests covering all five behaviours, priority ordering, contract conformance, determinism, edge cases, and scenario-based validation with mock investigations. - docs/design-evolution-log.md — Close Experiment 18 (record what assessor enabled for Behaviour Selection), add Experiment 19 section with hypothesis, scope, evaluation criteria, and open questions. Passive integration only: no changes to reasoning engine, prompts, graph generation, decomposition, narrative generation, API contracts, UI behaviour, or Ollama integration.
175 lines
5.9 KiB
JavaScript
175 lines
5.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
|
|
};
|
|
|
|
/* ── Selection rules (one rule per behaviour) ─────────────── */
|
|
|
|
function selectAcknowledge(assessment) {
|
|
if (assessment.conversationHealth.value === "healthy" && assessment.phase.confidence !== "low") {
|
|
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;
|