feat: multi-thread experimental apparatus (RTO.A1)

Add fixture-only apparatus for representing multiple concurrent open
investigation items within a fixed case context.

New scenario 'multi-thread' exposes:
- A fixed central situation statement and case summary (product-launch
  timing decision, drawn from existing pre-anchored-product-launch
  data)
- Three open investigation items — none compulsory: enterprise customer
  signing probability, competitor timing, financial viability comparison
- One engine recommendation (mt-ent-customer-signing, ordered first)
- User selection of any item; chosen item becomes visually primary while
  others remain visible as context
- Experimental state isolated in _experimental / _experimentalState —
  never aliases production graph fields
This commit is contained in:
2026-08-18 10:39:16 +01:00
parent 8339b6849a
commit 25a88c5fc3
2 changed files with 337 additions and 4 deletions
+116 -4
View File
@@ -308,6 +308,84 @@ var diagnosisTurns = [
}
];
/* ── Experimental multi-thread apparatus (RTO.A1) ──── */
/**
* FIXTURE-ONLY — not production reasoning.
* Represents a curated set of open investigation threads for a
* product-launch timing decision, allowing user-directed selection
* of which thread becomes visually primary.
*
* The central situation statement is drawn from the existing
* pre-anchored-product-launch-options.json scenario data.
*/
var multiThreadCase = {
centralStatement: "We are evaluating two product-launch timing options: launching the new software product this year or waiting twelve months.",
caseSummary: "An organisation holds a nearly-ready software product and must choose between launching within the current year (with ~£300k immediate cost) or delaying launch by twelve months (forgoing first-year revenue while avoiding near-term costs). One large enterprise customer's potential contract could significantly influence the timing decision. The situation is unresolved with multiple legitimate investigation pathways.",
};
var multiThreadItems = [
{
id: "mt-ent-customer-signing",
label: "Enterprise customer signing probability",
description: "The likelihood, negotiation stage, and targeted signing date for the large enterprise client whose potential contract represents a significant portion of expected revenue.",
recommendationOrder: 1,
questionFrame: "What outcome would demonstrate enough value to justify launching a software product now?",
},
{
id: "mt-competitor-timing",
label: "Competitor timing / first-mover consequences",
description: "Whether competitors are actively developing similar products and how soon they might release them, and what the market share impact would be if we lose the first-mover window.",
recommendationOrder: 2,
questionFrame: "What evidence would clarify whether competitors are actively developing similar products and how soon they might release them?",
},
{
id: "mt-financial-comparison",
label: "Financial viability of launching now versus waiting",
description: "The exact monetary value of the potential enterprise contract relative to the £300k launch cost, projected cash-flow impact, and net present value comparison across the two timing options.",
recommendationOrder: 3,
questionFrame: "What evidence would clarify the exact percentage of total projected revenue attributable to the enterprise customer?",
},
];
var multiThreadTurn = {
centralStatement: multiThreadCase.centralStatement,
situationGraph: {
nodes: [
mkN("mt-state-1", multiThreadCase.caseSummary, { kind: "state", status: "provisional", confidence: "medium" }),
mkN("mt-opt-1", "Launch this year: capture ~£1.2M revenue now, incur ~£300k cost",{ kind: "observation", status: "known", confidence: "high" }),
mkN("mt-opt-2", "Wait twelve months: avoid £300k cost, forgo first-year revenue",{ kind: "observation", status: "known", confidence: "medium" }),
],
edges: [mkE("mt-e-1","mt-opt-1","mt-state-1"), mkE("mt-e-2","mt-opt-2","mt-state-1")],
activeUnknownNodeId: null,
resolvedNodeIds: [],
},
active: null,
question: null,
noQReason: "Multi-thread apparatus — no single active unknown. Choose an investigation thread.",
summary: multiThreadCase.caseSummary + " | 3 open investigation items available. Engine recommendation marked.",
/* Experimental presentation state — NOT production graph fields */
_experimental: {
scenario: "multi-thread",
caseContext: multiThreadCase,
availableThreads: multiThreadItems,
recommendedThreadId: "mt-ent-customer-signing",
selectedThreadId: null,
apparatusMode: "selection",
},
};
/* Re-export situationGraph on the fixture itself for backwards compat */
Object.defineProperty(multiThreadTurn, "nodes", { get() { return this.situationGraph.nodes; } });
Object.defineProperty(multiThreadTurn, "edges", { get() { return this.situationGraph.edges; } });
Object.defineProperty(multiThreadTurn, "resolved", { get() { return this.situationGraph.resolvedNodeIds; } });
/* Export the experimental scenario data */
export var MULTI_THREAD_FIXTURE = multiThreadTurn;
export var MULTI_THREAD_CASE = multiThreadCase;
export var MULTI_THREAD_ITEMS = multiThreadItems;
/* ── Registry ─────────────────────────────────────── */
var SCENARIOS = {
@@ -322,13 +400,46 @@ var SCENARIOS = {
"long": { turns: longTurns, label: "Long investigation (market entry)", centralStatement: longTurns[0].centralStatement },
"complete": { turns: completeTurns, label: "Complete investigation", centralStatement: completeTurns[0].centralStatement },
"diagnosis": { turns: diagnosisTurns, label: "Diagnosis (churn)", centralStatement: diagnosisTurns[0].centralStatement },
"multi-thread": { scenario: multiThreadTurn, label: "[E] Multi-thread apparatus (RTO.A1)", centralStatement: multiThreadCase.centralStatement },
};
/* ── Build a fixture for a named scenario at a given turn index ─ */
export function buildScenarioFixture(scenarioName, turnIdx) {
var s = SCENARIOS[scenarioName];
if (!s || !s.turns) return null;
if (!s) return null;
/* Experimental multi-thread — does not follow the turns model */
if (s.scenario && s.scenario._experimental) {
var mt = s.scenario;
return {
success: true,
situationGraph: {
centralStatement: mt.centralStatement,
currentSummary: mt.summary,
nodes: mt.nodes,
edges: mt.edges,
activeUnknownNodeId: mt.active,
resolvedNodeIds: mt.resolved
},
selectedQuestion: mt.question || null,
noQuestionReason: mt.noQReason,
newlySurfacedNodeIds: [],
diagnostics: {
promptVersion: "v0.4",
modelName: "mock-ollama",
responseDurationMs: 0,
validationStatus: "valid",
nodeCount: mt.nodes.length,
edgeCount: mt.edges.length,
investigationStrategy: { key: "multi_thread_experiment" },
unknownSelectionExplanation: null
},
_experimentalState: mt._experimental
};
}
if (!s.turns) return null;
var t = s.turns[Math.min(turnIdx, s.turns.length - 1)];
return {
success: true,
@@ -361,11 +472,12 @@ export function buildScenarioFixture(scenarioName, turnIdx) {
export var AVAILABLE_SCENARIOS = [];
for (var key in SCENARIOS) {
if (SCENARIOS.hasOwnProperty(key)) {
var entry = SCENARIOS[key];
AVAILABLE_SCENARIOS.push({
key: key,
label: SCENARIOS[key].label,
centralStatement: SCENARIOS[key].centralStatement,
turnCount: SCENARIOS[key].turns.length
label: entry.label,
centralStatement: entry.centralStatement,
turnCount: entry.turns ? entry.turns.length : 1 // experimental scenarios show as single-turn
});
}
}