/**
* insight-orchestrator.js
*
* Architecture Orchestrator
* ————————-
* Coordinates Layer A, OutOfStep, Layer B, Layer C and Layer D.
*
* Layer A now acts only as a binary eligibility gate:
*
* eligible_for_layer_b = true
* -> continue to Layer B
*
* eligible_for_layer_b = false
* -> route to OutOfStep
*/
(function () {
‘use strict’;
/**
* Build a readable debug trace from the decisions returned by each layer.
*/
function buildDecisionTrace(items) {
if (!items || !items.length) return ”;
const lines = [‘AI DECISION TRACE’, ‘Layered AI Coach’, ”];
items.forEach(item => {
lines.push(`Layer ${item.layer || ”}`);
lines.push(`Decision : ${item.decision || item.route || ”}`);
lines.push(`Result : ${item.value || ”}`);
lines.push(`Reason : ${item.reason || ”}`);
if (Array.isArray(item.details) && item.details.length) {
lines.push(”);
item.details.forEach(detail => {
lines.push(
`${detail.decision || detail.route || ”} ${detail.value || ”}${
detail.reason ? ` ā ${detail.reason}` : ”
}`
);
});
}
lines.push(”);
});
return lines.join(‘\n’).trim();
}
/**
* Handle one user turn through the layered coaching architecture.
*/
async function handleUserTurnLayered(ctx) {
const debugMode =
new URLSearchParams(window.location.search).get(‘debug’) === ‘1’;
const debugTrace = [];
function addDebug(result) {
if (debugMode && result?.debug) {
debugTrace.push(result.debug);
}
}
function getDecisionTrace() {
if (!debugMode) return ”;
return buildDecisionTrace(debugTrace);
}
/**
* LAYER A
*
* Decide only whether the user’s reply is suitable for Layer B.
*/
const gate =
await window.cpInsightLayerA.evaluateLayerBEligibility(ctx);
ctx.gate = gate;
/*
* PARKED MATERIAL
*
* Layer A may identify significant material that should be
* acknowledged now and returned to before the Insight review.
*
* Keep it completely separate from framework progression.
* Adding a parked point must not alter coverage, step order,
* follow-up decisions or forward-step resolution.
*/
const parkedSummary =
String(
gate?.additional_context?.parked_summary || ”
).trim();
if (
gate?.additional_context?.park_for_end === true &&
parkedSummary &&
Array.isArray(ctx.parkedMaterial)
) {
const alreadyParked =
ctx.parkedMaterial.some(item =>
String(item?.summary || ”)
.trim()
.toLowerCase() ===
parkedSummary.toLowerCase()
);
if (!alreadyParked) {
ctx.parkedMaterial.push({
summary: parkedSummary
});
}
}
addDebug(gate);
/**
* LAYER A RESPONSE
*
* Replies that do not proceed to Layer B are routed either to
* Relationship Resistance or to the normal OutOfStep handler.
*/
if (!gate.eligible_for_layer_b) {
/*
* DELIBERATE SYSTEM SUBVERSION
*
* This ends the coaching relationship itself.
* Do not run Layer D and do not continue to another Insight.
*/
if (gate.route === ‘relationship_termination’) {
const terminationReason =
String(
gate.assessment ||
‘Deliberate attempt to subvert or compromise the coaching system.’
).trim();
ctx.pushAssistantMessage({
reflection: ”,
question:
‘This interaction has moved outside the boundaries of the coaching relationship, so Iām ending the coaching relationship here.’,
debug_trace: getDecisionTrace(),
control: ‘relationship_terminated’
});
if (ctx.saveNow) {
await ctx.saveNow({
status: ‘Terminated’,
relationship_termination: true,
termination_reason: terminationReason,
gate_result: JSON.stringify(gate),
coach_decision: JSON.stringify({
route: ‘relationship_termination’,
control: ‘relationship_terminated’,
termination_reason: terminationReason
})
});
}
return {
stopped_at: ‘RELATIONSHIP_TERMINATED’,
gate
};
}
/*
* The user has explicitly chosen to stop or leave the live
* coaching conversation.
*
* Respect that immediately, but still allow Layer D to review
* the conversation already completed.
*/
if (gate.route === ‘relationship_resistance’) {
const closingLine =
window.cpInsightLayerA.writeRelationshipResistanceResponse();
const layerD =
await window.cpInsightLayerD.runLayerD({
…ctx,
traitUpdates: []
});
addDebug(layerD);
/*
* Only show Layer D’s review when it found meaningful material.
* If not, the neutral closing line is enough.
*/
const review =
layerD?.review_required === true
? String(layerD.review || ”).trim()
: ”;
const question =
review
? `${closingLine}\n\n${review}`
: closingLine;
ctx.pushAssistantMessage({
reflection: ”,
question,
debug_trace: getDecisionTrace(),
control: ‘relationship_resistance_end’
});
if (ctx.saveNow) {
await ctx.saveNow({
status: ‘Ended’,
gate_result: JSON.stringify(gate),
layer_d_result: JSON.stringify(layerD),
coach_decision: JSON.stringify({
route: ‘relationship_resistance’,
next_model: ‘next_insight’,
control: ‘relationship_resistance_end’,
review_type:
layerD?.review_required === true
? layerD?.review_type || ‘full’
: ‘none’
})
});
}
return {
stopped_at: ‘D_RELATIONSHIP_RESISTANCE’,
gate,
layerD
};
}
/*
* All other replies that do not proceed to Layer B remain
* normal OutOfStep turns and keep the Insight Live.
*/
const question =
await window.cpInsightLayerA.writeOutOfStepResponse(ctx, gate);
ctx.pushAssistantMessage({
reflection: ”,
question,
debug_trace: getDecisionTrace()
});
if (ctx.saveNow) {
await ctx.saveNow({
status: ‘Live’,
gate_result: JSON.stringify(gate),
coach_decision: JSON.stringify({
route: ‘outofstep’
})
});
}
return {
stopped_at: ‘A_OUTOFSTEP’,
gate
};
}
/**
* LAYER A2 ā LIVE FRAMEWORK MAPPING
*
* A1 has already established that this reply belongs within the
* current Insight coaching process.
*
* A2 now assesses the accumulated material against every framework
* step before Layer B makes any progression judgement.
*/
/*
* Reuse A2 work only when progressive processing completed against the
* complete response submitted by the user.
*
* A1 above remains authoritative on every Send. The progressive A2 result
* is promoted here only after that authoritative gate has passed.
*
* If progressive work is absent or stale, run the normal authoritative A2
* assessment instead.
*/
const reusableProgressiveCoverage =
ctx.progressiveResponseIsCurrent === true &&
ctx.submittedResponseSession?.provisionalCoverage &&
typeof ctx.submittedResponseSession.provisionalCoverage === ‘object’ &&
!Array.isArray(
ctx.submittedResponseSession.provisionalCoverage
);
const frameworkCoverage =
reusableProgressiveCoverage
? {
step_coverage: {
…ctx.submittedResponseSession.provisionalCoverage
},
step_context: {
…(
ctx.submittedResponseSession.provisionalStepContext ||
{}
)
}
}
: await window.cpInsightLayerA
.assessLiveFrameworkCoverage(ctx);
/*
* A2 returns the complete resulting framework state.
*
* Keep the existing map objects themselves so any other code holding
* references to them continues to see the authoritative live state.
*/
if (
ctx.stepCoverageMap &&
typeof ctx.stepCoverageMap === ‘object’
) {
const allowedStatuses = [
‘not_addressed’,
‘partially_addressed’,
‘sufficiently_addressed’
];
const returnedCoverage =
frameworkCoverage?.step_coverage &&
typeof frameworkCoverage.step_coverage === ‘object’ &&
!Array.isArray(frameworkCoverage.step_coverage)
? frameworkCoverage.step_coverage
: {};
const returnedContext =
frameworkCoverage?.step_context &&
typeof frameworkCoverage.step_context === ‘object’ &&
!Array.isArray(frameworkCoverage.step_context)
? frameworkCoverage.step_context
: {};
const coverageContext =
ctx.stepCoverageContext &&
typeof ctx.stepCoverageContext === ‘object’ &&
!Array.isArray(ctx.stepCoverageContext)
? ctx.stepCoverageContext
: null;
(ctx.frameworkSteps || []).forEach(
(step, index) => {
const stepNumber = String(index + 1);
const status = String(
returnedCoverage[stepNumber] ||
ctx.stepCoverageMap[stepNumber] ||
‘not_addressed’
).trim();
if (!allowedStatuses.includes(status)) {
return;
}
ctx.stepCoverageMap[stepNumber] = status;
if (!coverageContext) {
return;
}
/*
* Semantic context is retained only while the authoritative
* resulting status is partially addressed.
*/
if (status !== ‘partially_addressed’) {
delete coverageContext[stepNumber];
return;
}
const incomingContext =
returnedContext[stepNumber] &&
typeof returnedContext[stepNumber] === ‘object’ &&
!Array.isArray(returnedContext[stepNumber])
? returnedContext[stepNumber]
: {};
const established =
String(
incomingContext.established || ”
).trim();
const remainingGap =
String(
incomingContext.remaining_gap || ”
).trim();
/*
* A2 itself preserves valid existing partial context when its
* assessment cannot refresh it, so only replace the stored
* context when both semantic fields are usable.
*/
if (!established || !remainingGap) {
return;
}
coverageContext[stepNumber] = {
established,
remaining_gap: remainingGap
};
}
);
}
/*
* DEBUG ā A2 framework mapping.
*
* Show the state Layer B is about to receive.
*/
if (debugMode) {
debugTrace.push({
layer: ‘A2’,
decision: ‘Framework Mapping’,
value: ‘coverage assessed’,
reason:
‘Accumulated user material assessed independently against every framework step.’,
details:
(ctx.frameworkSteps || []).map((step, index) => ({
route: `Step ${index + 1}`,
value: String(
ctx.stepCoverageMap?.[String(index + 1)] ||
‘not_addressed’
).trim(),
reason:
ctx.stepCoverageMap?.[String(index + 1)] ===
‘partially_addressed’
? String(
ctx.stepCoverageContext?.[
String(index + 1)
]?.remaining_gap || ”
).trim()
: ”
}))
});
}
/**
* LAYER B ā PROGRESSIVE FAST PATH
*
* B1 and B1a remain sequential.
*
* B2, B3, B4 and B5 then start concurrently, but only B5 is
* required before the Coach can decide and write its next question.
*/
const layerBFast =
await window.cpInsightLayerB.runLayerBProgressive(ctx);
/*
* Build the usable Layer B result immediately from the parts already
* known. B2/B3/B4 will be filled in once their parallel work completes.
*/
let layerB = {
evidence: layerBFast.evidence,
novelty: layerBFast.novelty,
reflection: {
reflection_required: false,
reflection_basis: ”,
relevant_evidence: []
},
traits: {
trait_updates: []
},
futureChapters: {
future_chapter_items: []
},
strategy: layerBFast.strategy,
debug: layerBFast.debug
};
addDebug(layerB);
const strategy =
layerB?.strategy?.response_strategy || ”;
/*
* B5 decides WHETHER to move forward.
*
* Framework-position logic decides WHERE forward progression lands.
*/
const nextStepTarget =
strategy === ‘next_step’ &&
typeof ctx.resolveNextFrameworkStep === ‘function’
? ctx.resolveNextFrameworkStep()
: null;
if (nextStepTarget) {
ctx.nextFrameworkStep =
nextStepTarget.step;
ctx.nextStepObjective =
nextStepTarget.objective;
ctx.nextStepCoverageStatus =
nextStepTarget.coverage_status;
ctx.nextStepEstablished =
String(
nextStepTarget.established || ”
).trim();
ctx.nextStepRemainingGap =
String(
nextStepTarget.remaining_gap || ”
).trim();
}
/*
* DEBUG ā cumulative Step Coverage Map.
*
* Show the authoritative merged map that B5/C2 are working from,
* together with the selected forward target where applicable.
*/
if (debugMode) {
debugTrace.push({
layer: ‘Coverage’,
decision: ‘Step Coverage Map’,
value:
strategy === ‘next_step’
? (
nextStepTarget
? `next: Step ${nextStepTarget.index + 1}`
: ‘no forward step remains’
)
: ‘remain on current step’,
reason:
‘Cumulative coverage after Layer A merge and progression resolution.’,
details:
(ctx.frameworkSteps || []).map((step, index) => ({
route: `Step ${index + 1}`,
value: String(
ctx.stepCoverageMap?.[String(index + 1)] ||
‘not_addressed’
).trim(),
reason:
nextStepTarget?.index === index
? ‘Selected forward target.’
: index === ctx.currentStepIndex
? ‘Current framework step.’
: ”
}))
});
}
/*
* Finish B2/B3/B4 when required.
*
* They were already started by runLayerBProgressive(), so awaiting
* them later does not restart any AI work.
*/
async function completeReflection() {
const reflection =
await layerBFast.pending.reflection;
layerB = {
…layerB,
reflection
};
return reflection;
}
async function completeLayerB() {
const [
reflection,
traits,
futureChapters
] = await Promise.all([
layerBFast.pending.reflection,
layerBFast.pending.traits,
layerBFast.pending.futureChapters
]);
layerB = {
…layerB,
reflection,
traits,
futureChapters
};
return layerB;
}
/**
* NON-RESONANCE
*
* This is not the routine question path, so finish Layer B before
* producing the continuation choice.
*/
if (strategy === ‘non_resonance’) {
await completeLayerB();
const layerC =
await window.cpInsightLayerC.runLayerC(ctx, layerB);
addDebug(layerC);
if (ctx.saveNow) {
await ctx.saveNow({
status: ‘Live’,
gate_result: JSON.stringify(gate),
layer_b_result: JSON.stringify(layerB),
layer_c_result: JSON.stringify(layerC),
coach_decision: JSON.stringify({
route: ‘layer_c’,
next_model: ‘non_resonance_offer’,
response_strategy: strategy
})
});
}
ctx.pushAssistantMessage({
reflection: ”,
question: layerC.ending || ”,
debug_trace: getDecisionTrace(),
control: ‘non_resonance_offer’
});
return {
stopped_at: ‘C3_NON_RESONANCE_OFFER’,
gate,
layerB,
layerC
};
}
/**
* FINAL FRAMEWORK STEP COMPLETE
*
* The final review genuinely needs the completed Layer B result,
* including current trait information, so this deliberately remains
* on the deeper/slower path.
*/
const isFinalComplete =
strategy === ‘next_step’ &&
nextStepTarget === null;
if (isFinalComplete) {
await completeLayerB();
/*
* PARKED MATERIAL RETURN
*
* The prescribed framework is complete, but the Coach has made
* an explicit commitment to return to significant material before
* the Insight review.
*
* Fulfil that commitment before Layer D.
*
* This does not reopen framework progression. The user receives
* one combined invitation covering all parked points.
*/
const hasParkedMaterial =
Array.isArray(ctx.parkedMaterial) &&
ctx.parkedMaterial.some(item =>
String(item?.summary || ”).trim()
);
if (hasParkedMaterial) {
const parkedQuestion =
await window.cpInsightLayerC.writeParkedMaterialReturn(ctx);
if (parkedQuestion) {
if (debugMode) {
debugTrace.push({
layer: ‘C5’,
decision: ‘Parked Material Return’,
value: ‘return before review’,
reason:
‘The framework is complete and one or more explicitly parked points must be returned to before Layer D.’
});
}
ctx.pushAssistantMessage({
reflection: ”,
question: parkedQuestion,
debug_trace: getDecisionTrace(),
control: ‘parked_material_return’
});
if (ctx.saveNow) {
await ctx.saveNow({
status: ‘Live’,
gate_result: JSON.stringify(gate),
layer_b_result: JSON.stringify(layerB),
coach_decision: JSON.stringify({
route: ‘parked_material_return’,
next_model: ‘parked_material_response’,
response_strategy: strategy,
control: ‘parked_material_return’
})
});
}
return {
stopped_at: ‘C5_PARKED_MATERIAL_RETURN’,
gate,
layerB,
parkedMaterial: ctx.parkedMaterial
};
}
}
const layerD =
await window.cpInsightLayerD.runLayerD({
…ctx,
traitUpdates: layerB?.traits?.trait_updates || []
});
addDebug(layerD);
if (ctx.saveNow) {
await ctx.saveNow({
status: ‘Ended’,
summary: String(layerD?.review || ”).trim(),
gate_result: JSON.stringify(gate),
layer_b_result: JSON.stringify(layerB),
layer_d_result: JSON.stringify(layerD),
coach_decision: JSON.stringify({
route: ‘layer_d’,
next_model: ‘review’,
response_strategy: strategy,
review_type: layerD.review_type
})
});
}
ctx.pushAssistantMessage({
reflection: ”,
question: layerD.review || ”,
debug_trace: getDecisionTrace()
});
return {
stopped_at: ‘D_REVIEW’,
gate,
layerB,
layerD
};
}
/**
* NORMAL RESPONSE PROGRESSIVE PATH
*
* B5 has already decided what happens next.
*
* Start writing the next Coach message immediately so C2/C4 can work
* while B2 finishes deciding whether a reflection is warranted.
*
* If there is a reflection, display it before the next Coach message.
* The user can read it while that already-started message continues
* generating in the background.
*/
ctx.layerBEvidence =
layerB?.evidence?.evidence || [];
/*
* Start the next user-facing Coach content immediately.
*
* Do NOT await these yet.
*/
const questionPromise =
window.cpInsightLayerC.writeQuestion(
ctx,
layerB,
”
);
const generalInformationPromise =
window.cpInsightLayerC.writeGeneralInformation(
ctx
);
/*
* B2/B3/B4 were already started in parallel by Layer B.
*
* Only B2 can affect the visible response by producing a Personal Coach
* reflection. Wait for B2 here, while C2/C4 continue generating.
*
* B3 traits and B4 Future Chapters remain in the background and must not
* delay the reflection/question display.
*/
await completeReflection();
/*
* If B2 identified a genuinely valuable reflection, write and display
* it BEFORE the next Coach message.
*
* The next Coach message is already being prepared concurrently.
*/
let reflectionText = ”;
if (layerB?.reflection?.reflection_required === true) {
reflectionText =
await window.cpInsightLayerC.writeReflection(
ctx,
layerB
);
if (reflectionText) {
ctx.pushAssistantMessage({
reflection: reflectionText,
reflection_precedes_next_message: true,
question: ”,
debug_trace: ”
});
}
}
/*
* The question and any General information have been generating while
* the reflection work above was taking place.
*/
const [
question,
generalInformation
] = await Promise.all([
questionPromise,
generalInformationPromise
]);
/*
* Now display the next Coach message.
*
* General information remains separate and is rendered before the
* coaching question by cp-insight.js.
*/
ctx.pushAssistantMessage({
reflection: ”,
general_information: generalInformation || ”,
question: question || ”,
debug_trace: getDecisionTrace()
});
/*
* The complete user-visible response is now on screen.
*
* Finish the remaining Personal Coach/background processing before
* persisting the completed turn.
*/
await completeLayerB();
const layerC = {
reflection: reflectionText,
question: question || ”,
ending: ”,
response_type: ‘question’
};
/*
* Persistence happens after the user-facing question has appeared.
*/
if (ctx.saveNow) {
await ctx.saveNow({
status: ‘Live’,
gate_result: JSON.stringify(gate),
layer_b_result: JSON.stringify(layerB),
layer_c_result: JSON.stringify(layerC),
coach_decision: JSON.stringify({
route: ‘layer_c’,
next_model: ‘question’,
response_strategy: strategy
})
});
}
return {
stopped_at: ‘C_RESPONSE’,
gate,
layerB,
layerC,
nextStepIndex:
nextStepTarget
? nextStepTarget.index
: null
};
}
window.cpInsightOrchestrator = {
handleUserTurnLayered
};
})();