/**
* insight-layer-b.js
*
* Layer B – Understanding + Decisions
* ———————————–
*
* Purpose:
* Convert a valid user answer into structured coaching intelligence.
*
* Layer B answers:
* – What did we learn?
* – Is there something worth reflecting?
* – Are there trait signals?
* – Is anything useful for future Chapters?
* – What should happen next?
*
* Layer B must NOT:
* – write the reflection
* – write the next question
* – write the end-of-Insight review
* – directly write to the database
*
* Layer B produces decisions and payloads.
* The application handles storage.
* Layer C handles user-facing writing.
*/
(function () {
‘use strict’;
/**
* Utility: clean()
*
* Purpose:
* Normalise values into trimmed strings before prompt use.
*/
function clean(value) {
return String(value == null ? ” : value).trim();
}
/**
* Utility: buildHeaders()
*
* Purpose:
* Build headers for the AI proxy.
*/
function buildHeaders(config) {
const headers = { ‘Content-Type’: ‘application/json’ };
if (config.token) headers[‘X-CP-Token’] = config.token;
else if (config.restNonce) headers[‘X-WP-Nonce’] = config.restNonce;
return headers;
}
/**
* Utility: callAiJson()
*
* Purpose:
* Send a structured Layer B prompt and parse JSON.
*/
async function callAiJson(config, payload, fallback) {
try {
const res = await fetch(config.aiUrl, {
method: ‘POST’,
headers: buildHeaders(config),
body: JSON.stringify(payload)
});
if (!res.ok) throw new Error(`Layer B AI error ${res.status}`);
const json = await res.json();
const raw = clean(json.text || json.reply || json.content);
const parsed = JSON.parse(raw.replace(/“`json/g, ”).replace(/“`/g, ”).trim());
return parsed;
} catch (err) {
console.log(‘LAYER B JSON ERROR:’, err);
return fallback;
}
}
/**
* B1 – Evidence Extraction
*
* Purpose:
* Extract what the user actually said into stable evidence categories.
*
* This stage observes.
* It should not interpret, reflect, assess traits, or decide progression.
*/
async function extractEvidence(ctx) {
const config = ctx.config || {};
const userText = clean(ctx.userText);
if (!config.aiUrl || !userText) {
return { evidence: [] };
}
const payload = {
model: ‘gpt-4o’,
temperature: 0,
messages: [
{
role: ‘system’,
content:
`You are B1 Evidence Extraction in a structured AI Coach system.
Your single responsibility:
Extract factual evidence from the user’s latest answer.
Return ONLY valid JSON:
{
“evidence”: [
{
“category”: “belief|value|concern|pattern|tension|insight”,
“statement”: “…”,
“quote”: “…”
}
]
}
Definitions:
– belief: something the user considers true.
– value: something the user considers important.
– concern: a worry, obstacle, risk, or issue.
– pattern: a recurring behaviour or tendency.
– tension: a conflict between priorities, needs, beliefs, or values.
– insight: a meaningful realisation or shift in understanding.
Rules:
– Stay close to the user’s words.
– Extract observations, not interpretations.
– A single reply may contain multiple evidence categories.
– Use exact user wording for quote where possible.
– If there is no useful evidence, return {“evidence”:[]}.
– Do NOT assess novelty.
– Do NOT decide reflection.
– Do NOT assess traits.
– Do NOT decide next step.`
},
{
role: ‘user’,
content:
`LATEST USER REPLY:
${userText}`
}
]
};
const parsed = await callAiJson(config, payload, { evidence: [] });
return {
evidence: Array.isArray(parsed.evidence) ? parsed.evidence : []
};
}
/**
* B1a – Novelty Assessment
*
* Purpose:
* Decide whether extracted evidence is new, repeated, or materially developed.
*
* Materially developed means the user has expanded, deepened, clarified,
* or re-contextualised something already known.
*/
async function assessNovelty(ctx, evidenceResult) {
const config = ctx.config || {};
const evidence = evidenceResult?.evidence || [];
if (!config.aiUrl || !evidence.length) {
return { novelty: [] };
}
const payload = {
model: ‘gpt-4o’,
temperature: 0,
messages: [
{
role: ‘system’,
content:
`You are B1a Novelty Assessment.
Your single responsibility:
Compare newly extracted evidence with already accepted evidence.
Return ONLY valid JSON:
{
“novelty”: [
{
“quote”: “…”,
“status”: “new|repeated|materially_developed”,
“reason”: “…”
}
]
}
Definitions:
– new: not already present in accepted evidence.
– repeated: mainly restates something already known.
– materially_developed: significantly expands, deepens, clarifies, or re-contextualises a previous observation.
Rules:
– Do NOT write reflections.
– Do NOT assess traits.
– Do NOT decide next step.
– Be conservative about calling something materially_developed.`
},
{
role: ‘user’,
content:
`NEW EVIDENCE:
${JSON.stringify(evidence)}
RELEVANT ACCEPTED EVIDENCE:
${JSON.stringify(ctx.acceptedEvidence || [])}`
}
]
};
const parsed = await callAiJson(config, payload, { novelty: [] });
return {
novelty: Array.isArray(parsed.novelty) ? parsed.novelty : []
};
}
/**
* B2 – Reflection Assessment
*
* Purpose:
* Decide whether a reflection is justified and what it should be about.
*
* This stage decides WHAT may be reflected.
* Layer C decides HOW to write it.
*/
async function assessReflection(ctx, evidenceResult, noveltyResult) {
const config = ctx.config || {};
const evidence = evidenceResult?.evidence || [];
if (!config.aiUrl || !evidence.length) {
return {
reflection_required: false,
reflection_basis: ”,
relevant_evidence: []
};
}
const payload = {
model: ‘gpt-4o’,
temperature: 0,
messages: [
{
role: ‘system’,
content:
`You are B2 Reflection Assessment.
Your single responsibility:
Decide whether a reflection is justified and identify the reflection basis.
Return ONLY valid JSON:
{
“reflection_required”: true|false,
“reflection_basis”: “…”,
“relevant_evidence”: [“…”]
}
Rules:
– Not every response requires a reflection.
– Reflect only evidence that is meaningful, new, or materially developed.
– Repeated evidence should not normally trigger reflection unless it has been expanded, deepened, clarified, or re-contextualised.
– Reflection basis should be concise.
– Do NOT write the reflection.
– Do NOT ask a question.
– Do NOT assess traits.
– If no reflection is justified, return:
{“reflection_required”:false,”reflection_basis”:””,”relevant_evidence”:[]}`
},
{
role: ‘user’,
content:
`EVIDENCE:
${JSON.stringify(evidence)}
NOVELTY:
${JSON.stringify(noveltyResult?.novelty || [])}
LATEST USER REPLY:
${clean(ctx.userText)}`
}
]
};
const parsed = await callAiJson(config, payload, {
reflection_required: false,
reflection_basis: ”,
relevant_evidence: []
});
return {
reflection_required: !!parsed.reflection_required,
reflection_basis: clean(parsed.reflection_basis),
relevant_evidence: Array.isArray(parsed.relevant_evidence) ? parsed.relevant_evidence : []
};
}
/**
* B3 – Trait Assessment
*
* Purpose:
* Assess whether the user’s answer provides evidence of movement
* in one or more Change Pathway traits.
*
* This stage produces storage recommendations and payloads.
* It does NOT directly write to the database.
*/
async function assessTraits(ctx, evidenceResult, noveltyResult) {
const config = ctx.config || {};
const evidence = evidenceResult?.evidence || [];
if (!config.aiUrl || !evidence.length) {
return { trait_updates: [] };
}
const payload = {
model: ‘gpt-4o’,
temperature: 0,
messages: [
{
role: ‘system’,
content:
`You are B3 Trait Assessment.
Traits:
Awareness, Balance, Commitment, Drive, Engagement, Flexibility, Groundedness, Heart.
Your single responsibility:
Assess whether the current answer provides evidence of trait movement.
Return ONLY valid JSON:
{
“trait_updates”: [
{
“trait”: “…”,
“direction”: “increase|decrease|caution”,
“confidence”: “low|medium|high”,
“evidence”: “…”,
“quote”: “…”,
“store_recommendation”: true|false
}
]
}
Rules:
– Be conservative.
– Do not infer personality.
– Use only user evidence.
– Repeated evidence should not normally create a new update.
– Materially developed evidence may justify an update.
– Return [] if evidence is weak.
– Do NOT write to the database.`
},
{
role: ‘user’,
content:
`EVIDENCE:
${JSON.stringify(evidence)}
NOVELTY:
${JSON.stringify(noveltyResult?.novelty || [])}
CURRENT TRAIT STATUS:
${JSON.stringify(ctx.currentTraitStatus || {})}`
}
]
};
const parsed = await callAiJson(config, payload, { trait_updates: [] });
return {
trait_updates: Array.isArray(parsed.trait_updates) ? parsed.trait_updates : []
};
}
/**
* B4 – Future Chapter Assessment
*
* Purpose:
* Identify information that may be useful later when introducing
* future Chapters.
*
* This produces future-chapter storage recommendations.
* It does NOT directly write to the database.
*/
async function assessFutureChapters(ctx, evidenceResult, noveltyResult) {
const config = ctx.config || {};
const evidence = evidenceResult?.evidence || [];
if (!config.aiUrl || !evidence.length) {
return { future_chapter_items: [] };
}
const payload = {
model: ‘gpt-4o’,
temperature: 0,
messages: [
{
role: ‘system’,
content:
`You are B4 Future Chapter Assessment.
Your single responsibility:
Identify whether anything in the user’s answer may be useful for future Chapter introductions.
Return ONLY valid JSON:
{
“future_chapter_items”: [
{
“chapter_key”: “…”,
“evidence”: “…”,
“quote”: “…”,
“reason”: “…”,
“store_recommendation”: true|false
}
]
}
Rules:
– Store only material likely to make a future Chapter more personal or relevant.
– Useful material may include beliefs, concerns, experiences, quotations, recurring themes, or meaningful tensions.
– Do not store trivial or generic statements.
– Repeated evidence should not normally be stored again.
– Materially developed evidence may enrich an existing future Chapter item.
– Do NOT write to the database.`
},
{
role: ‘user’,
content:
`EVIDENCE:
${JSON.stringify(evidence)}
NOVELTY:
${JSON.stringify(noveltyResult?.novelty || [])}
FUTURE CHAPTER MAP:
${JSON.stringify(ctx.futureChapterMap || {})}`
}
]
};
const parsed = await callAiJson(config, payload, { future_chapter_items: [] });
return {
future_chapter_items: Array.isArray(parsed.future_chapter_items)
? parsed.future_chapter_items
: []
};
}
/**
* B5 – Response Strategy Assessment
*
* Purpose:
* Decide what should happen next.
*
* Possible outcomes:
* – next_step
* – follow_up
* – non_resonance
*
* Closure is not a separate strategy.
* When the final step is complete, the orchestrator moves to Layer D.
*/
async function assessResponseStrategy(ctx, evidenceResult, noveltyResult) {
const config = ctx.config || {};
if (!config.aiUrl) {
return {
response_strategy: ‘follow_up’,
reason: ‘No AI endpoint available.’,
follow_up_gap: ”
};
}
const payload = {
model: ‘gpt-4o’,
temperature: 0,
messages: [
{
role: ‘system’,
content:
`You are B5 Response Strategy Assessment.
Your single responsibility:
Decide what should happen next in the coaching flow.
Return ONLY valid JSON:
{
“response_strategy”: “next_step|follow_up|non_resonance”,
“reason”: “…”,
“follow_up_gap”: “…”
}
Decision rules:
next_step:
– Use when the current framework step objective has been achieved.
– A brief answer may still achieve the step.
– Do not require emotional depth or long disclosure.
follow_up:
– Use only when the step objective has not yet been achieved.
– A specific gap must be identifiable.
– One further question must be likely to add value.
– Only one follow-up opportunity is allowed per framework step.
– If a follow-up has already been used, do NOT choose follow_up.
non_resonance:
– Use when there is repeated rejection, repeated reframing away from the Insight, or sustained disengagement.
– Do not use merely because the answer is brief.
Important:
– Do NOT write the next question.
– Do NOT write a reflection.
– Do NOT decide review content.
– Closure is not a separate response_strategy. If the final step is complete, return next_step and let the orchestrator move to Layer D.`
},
{
role: ‘user’,
content:
`CURRENT FRAMEWORK STEP:
${clean(ctx.currentFrameworkStep)}
CURRENT STEP OBJECTIVE:
${clean(ctx.currentStepObjective)}
FOLLOW-UP ALREADY USED FOR THIS STEP:
${ctx.followUpUsed ? ‘YES’ : ‘NO’}
IS FINAL FRAMEWORK STEP:
${ctx.isFinalFrameworkStep ? ‘YES’ : ‘NO’}
LAYER A GATE RESULT:
${JSON.stringify(ctx.gate || {})}
EVIDENCE:
${JSON.stringify(evidenceResult?.evidence || [])}
NOVELTY:
${JSON.stringify(noveltyResult?.novelty || [])}
LATEST USER REPLY:
${clean(ctx.userText)}`
}
]
};
const parsed = await callAiJson(config, payload, {
response_strategy: ‘follow_up’,
reason: ”,
follow_up_gap: ”
});
const allowed = [‘next_step’, ‘follow_up’, ‘non_resonance’];
const response_strategy = allowed.includes(parsed.response_strategy)
? parsed.response_strategy
: ‘follow_up’;
return {
response_strategy,
reason: clean(parsed.reason),
follow_up_gap: clean(parsed.follow_up_gap)
};
}
/**
* runLayerB()
*
* Purpose:
* Main entry point for Layer B.
*
* Called only after Layer A returns:
* status = answered.
*
* Runs:
* – B1 Evidence Extraction
* – B1a Novelty Assessment
* – B2 Reflection Assessment
* – B3 Trait Assessment
* – B4 Future Chapter Assessment
* – B5 Response Strategy Assessment
*/
async function runLayerB(ctx) {
const evidence = await extractEvidence(ctx);
const novelty = await assessNovelty(ctx, evidence);
const reflection = await assessReflection(ctx, evidence, novelty);
const traits = await assessTraits(ctx, evidence, novelty);
const futureChapters = await assessFutureChapters(ctx, evidence, novelty);
const strategy = await assessResponseStrategy(ctx, evidence, novelty);
return {
evidence,
novelty,
reflection,
traits,
futureChapters,
strategy,
debug: {
layer: ‘B’,
decision: ‘B5’,
value: strategy.response_strategy,
reason: strategy.reason,
details: [
{
route: ‘B1’,
value: `${(evidence.evidence || []).length} evidence item(s)`,
reason: ‘Evidence extracted from user reply.’
},
{
route: ‘B1a’,
value: `${(novelty.novelty || []).length} novelty item(s)`,
reason: ‘Novelty compared with accepted evidence.’
},
{
route: ‘B2’,
value: `reflection_required: ${!!reflection.reflection_required}`,
reason: reflection.reflection_basis || ‘No reflection basis identified.’
},
{
route: ‘B3’,
value: `${(traits.trait_updates || []).length} trait update(s)`,
reason: ‘Trait movement assessed.’
},
{
route: ‘B4’,
value: `${(futureChapters.future_chapter_items || []).length} future chapter item(s)`,
reason: ‘Future chapter relevance assessed.’
}
]
}
};
}
/**
* Public Layer B API
*/
window.cpInsightLayerB = {
runLayerB,
extractEvidence,
assessNovelty,
assessReflection,
assessTraits,
assessFutureChapters,
assessResponseStrategy
};
})();