experiment: add passive behaviour selection
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.
This commit is contained in:
@@ -0,0 +1,174 @@
|
||||
/**
|
||||
* 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;
|
||||
Reference in New Issue
Block a user