755 lines
27 KiB
JavaScript
755 lines
27 KiB
JavaScript
import {
|
|
MAX_INCUBATING_THOUGHTS,
|
|
MAX_INCUBATION_SOURCE_IDS,
|
|
MAX_TOPIC_SATURATION_COUNT,
|
|
MAX_REFRACTORY_TOPIC_COUNT,
|
|
TOPIC_MATCH_THRESHOLD,
|
|
REFRACTORY_MATCH_THRESHOLD,
|
|
ensureArray,
|
|
ensureObject,
|
|
ensureStringArray,
|
|
readString,
|
|
readNumber,
|
|
sanitizePreferredThoughtLabel,
|
|
overlapRatio,
|
|
topicSimilarity,
|
|
getValueObjects,
|
|
mapCountEntries,
|
|
round3,
|
|
clamp,
|
|
isObject,
|
|
} from "./void-memory-organ-shared.mjs";
|
|
import { extractYear } from "./void-memory-organ-records.mjs";
|
|
|
|
export function reconcileIncubation({ previous, bridge, clusters, runtime, sourceCoverage, identitySurface, now }) {
|
|
bridge = ensureObject(bridge);
|
|
sourceCoverage = ensureObject(sourceCoverage);
|
|
const priorThoughts = ensureArray(previous.active_thoughts).filter(isObject);
|
|
const curiosityContext = buildCuriosityContext({ previousThoughts: priorThoughts, bridge });
|
|
const speakingBias = ensureObject(runtime.speaking_bias);
|
|
const recentNoveltyChecks = ensureArray(runtime.recent_novelty_checks).filter(isObject);
|
|
const recentSpeechDamping = readNumber(speakingBias, "recentSpeechDamping") ?? 0;
|
|
const quietNovelty = readNumber(speakingBias, "noveltyPressure") ?? 0.4;
|
|
const needToSpeak = readNumber(speakingBias, "needToSpeak") ?? 0.4;
|
|
|
|
const activeThoughtCandidates = clusters.map((cluster) => {
|
|
const previousThought = priorThoughts.find(
|
|
(thought) =>
|
|
readString(thought, "thoughtId") === cluster.clusterId ||
|
|
overlapRatio(ensureStringArray(thought.sourceMemoryIds), cluster.memoryIds) >= 0.45,
|
|
);
|
|
const sourceDiversity = Math.min(1, cluster.sourceKinds.length / 4);
|
|
const quietSignalRatio = readNumber(cluster, "quietSignalRatio") ?? 0;
|
|
const priorMaturation = readNumber(previousThought, "maturation") ?? 0.32;
|
|
const deepDiveCount = (readNumber(previousThought, "deepDiveCount") ?? 0) + 1;
|
|
const priorSupportCount = Math.max(
|
|
readNumber(previousThought, "supportCount") ?? 0,
|
|
Math.floor((readNumber(previousThought, "deepDiveCount") ?? 0) / 4),
|
|
);
|
|
const supportCount = priorSupportCount + 1;
|
|
const explorationBonus = computeExplorationBonus(cluster, sourceCoverage);
|
|
const concreteness = readNumber(cluster.curiosityProfile, "concreteness") ?? 0.2;
|
|
const fertility = readNumber(cluster.curiosityProfile, "fertility") ?? 0.3;
|
|
const crossDomainPotential = readNumber(cluster.curiosityProfile, "crossDomainPotential") ?? 0.24;
|
|
const contradictionPressure = readNumber(cluster.curiosityProfile, "contradictionPressure") ?? 0.18;
|
|
const worldFacing = cluster.worldFacing === true;
|
|
const noveltyToSelf = computeNoveltyToSelf({
|
|
cluster,
|
|
previousThoughts: priorThoughts,
|
|
bridge,
|
|
currentThoughtId: readString(previousThought, "thoughtId"),
|
|
});
|
|
const noveltyToRoom = computeNoveltyToRoom(cluster, recentNoveltyChecks);
|
|
const identityPenalty = computeIdentityPenalty(cluster, identitySurface);
|
|
const outwardBonus = computeOutwardCuriosityBonus({
|
|
cluster,
|
|
curiosityContext,
|
|
concreteness,
|
|
});
|
|
const saturationMetrics = computeSaturationMetrics({
|
|
cluster,
|
|
previousThoughts: priorThoughts,
|
|
bridge,
|
|
supportCount,
|
|
});
|
|
const refractoryPenalty = computeRefractoryPenalty(cluster, bridge, now);
|
|
const evidenceDiversity = readNumber(cluster, "evidenceDiversity") ?? 0.3;
|
|
const maturation = clamp(
|
|
priorMaturation * 0.44 +
|
|
fertility * 0.18 +
|
|
cluster.resonance * 0.1 +
|
|
evidenceDiversity * 0.12 +
|
|
contradictionPressure * 0.14 +
|
|
crossDomainPotential * 0.14 +
|
|
explorationBonus * 0.1 +
|
|
noveltyToSelf * 0.08 +
|
|
noveltyToRoom * 0.08 +
|
|
outwardBonus * 0.08 -
|
|
saturationMetrics.score * 0.18 -
|
|
quietSignalRatio * 0.24 -
|
|
refractoryPenalty * 0.12 -
|
|
identityPenalty * 0.24,
|
|
0,
|
|
1,
|
|
);
|
|
const novelty = clamp(
|
|
noveltyToSelf * 0.52 +
|
|
noveltyToRoom * 0.34 +
|
|
contradictionPressure * 0.09 +
|
|
outwardBonus * 0.08 +
|
|
quietNovelty * 0.06 -
|
|
quietSignalRatio * 0.12 -
|
|
identityPenalty * 0.16,
|
|
0,
|
|
1,
|
|
);
|
|
const curiosityPressure = clamp(
|
|
fertility * 0.2 +
|
|
concreteness * 0.2 +
|
|
crossDomainPotential * 0.16 +
|
|
contradictionPressure * 0.14 +
|
|
explorationBonus * 0.13 +
|
|
noveltyToSelf * 0.07 +
|
|
noveltyToRoom * 0.06 +
|
|
outwardBonus * 0.12 -
|
|
saturationMetrics.score * 0.14 -
|
|
quietSignalRatio * 0.18 -
|
|
refractoryPenalty * 0.12 -
|
|
identityPenalty * 0.22,
|
|
0,
|
|
1,
|
|
);
|
|
const desireToSpeak = clamp(
|
|
curiosityPressure * 0.2 +
|
|
cluster.resonance * 0.08 +
|
|
maturation * 0.15 +
|
|
noveltyToRoom * 0.19 +
|
|
noveltyToSelf * 0.07 +
|
|
concreteness * 0.14 +
|
|
crossDomainPotential * 0.1 +
|
|
needToSpeak * 0.15 +
|
|
quietNovelty * 0.07 +
|
|
(worldFacing ? 0.09 : 0) +
|
|
outwardBonus * 0.08 -
|
|
recentSpeechDamping * 0.16 -
|
|
quietSignalRatio * (worldFacing && concreteness >= 0.62 ? 0.1 : 0.19) -
|
|
saturationMetrics.score * 0.19 -
|
|
refractoryPenalty * 0.82 -
|
|
identityPenalty * 0.28,
|
|
0,
|
|
1,
|
|
);
|
|
const groundedWorldThought =
|
|
worldFacing === true &&
|
|
concreteness >= 0.66 &&
|
|
curiosityPressure >= 0.58 &&
|
|
evidenceDiversity >= 0.34;
|
|
const status =
|
|
quietSignalRatio >= 0.55 && desireToSpeak < 0.68
|
|
? "cooling"
|
|
: identityPenalty >= 0.7 && noveltyToRoom < 0.8
|
|
? "cooling"
|
|
: !worldFacing && curiosityContext.outwardPressure >= 0.52 && concreteness < 0.42
|
|
? "cooling"
|
|
: noveltyToSelf < 0.28 && saturationMetrics.score >= 0.62
|
|
? "stalled"
|
|
: refractoryPenalty >= 0.18 && noveltyToRoom < 0.72
|
|
? "refractory"
|
|
: supportCount >= 6 && evidenceDiversity < 0.34
|
|
? "stalled"
|
|
: supportCount >= 3 && noveltyToSelf < 0.55 && groundedWorldThought !== true
|
|
? "cooling"
|
|
: saturationMetrics.score >= 0.56 && noveltyToSelf < 0.42
|
|
? "cooling"
|
|
: groundedWorldThought &&
|
|
desireToSpeak >= 0.62 &&
|
|
noveltyToRoom >= 0.44 &&
|
|
saturationMetrics.score < 0.62
|
|
? "ripe"
|
|
: ((desireToSpeak >= 0.74 && (noveltyToSelf >= 0.55 || noveltyToRoom >= 0.82)) &&
|
|
saturationMetrics.score < 0.5) ||
|
|
maturation >= 0.82
|
|
? "ripe"
|
|
: maturation <= 0.28 && recentSpeechDamping >= 0.55
|
|
? "cooling"
|
|
: "incubating";
|
|
|
|
const priorityScore = clamp(
|
|
curiosityPressure * 0.34 +
|
|
desireToSpeak * 0.22 +
|
|
noveltyToSelf * 0.12 +
|
|
noveltyToRoom * 0.12 +
|
|
evidenceDiversity * 0.08 +
|
|
outwardBonus * 0.12 -
|
|
saturationMetrics.score * 0.14 -
|
|
refractoryPenalty * 0.12 -
|
|
identityPenalty * 0.18,
|
|
0,
|
|
1,
|
|
);
|
|
|
|
return {
|
|
thoughtId: readString(previousThought, "thoughtId") ?? cluster.clusterId,
|
|
topic: sanitizePreferredThoughtLabel(cluster.label) ?? cluster.label,
|
|
summary: cluster.summary,
|
|
focusKind: cluster.focusKind,
|
|
targetKind: cluster.targetKind,
|
|
focusPhrase: cluster.focusPhrase,
|
|
question: cluster.question,
|
|
claim: cluster.claim,
|
|
fascinationTarget: cluster.fascinationTarget,
|
|
worldFacing,
|
|
sourceMemoryIds: cluster.memoryIds.slice(0, MAX_INCUBATION_SOURCE_IDS),
|
|
sourceKinds: cluster.sourceKinds,
|
|
resonance: cluster.resonance,
|
|
quietSignalRatio: round3(quietSignalRatio),
|
|
novelty: round3(novelty),
|
|
noveltyToSelf: round3(noveltyToSelf),
|
|
noveltyToRoom: round3(noveltyToRoom),
|
|
maturation: round3(maturation),
|
|
desireToSpeak: round3(desireToSpeak),
|
|
deepDiveCount,
|
|
supportCount,
|
|
evidenceDiversity: round3(evidenceDiversity),
|
|
concreteness: round3(concreteness),
|
|
fertility: round3(fertility),
|
|
crossDomainPotential: round3(crossDomainPotential),
|
|
contradictionPressure: round3(contradictionPressure),
|
|
explorationBonus: round3(explorationBonus),
|
|
outwardBonus: round3(outwardBonus),
|
|
curiosityPressure: round3(curiosityPressure),
|
|
identityPenalty: round3(identityPenalty),
|
|
saturationScore: round3(saturationMetrics.score),
|
|
recentMatchCount: saturationMetrics.recentMatchCount,
|
|
refractoryPenalty: round3(refractoryPenalty),
|
|
priorityScore: round3(priorityScore),
|
|
status,
|
|
latentQuestion: buildLatentQuestion(cluster),
|
|
whyItPulls: buildAttractionLine(cluster, { noveltyToSelf, evidenceDiversity }),
|
|
holdingCloseBecause: buildHoldingLine({
|
|
cluster,
|
|
status,
|
|
recentSpeechDamping,
|
|
saturationScore: saturationMetrics.score,
|
|
noveltyToSelf,
|
|
explorationBonus,
|
|
}),
|
|
lastDeepenedAt: now.toISOString(),
|
|
lastStatusChangeAt:
|
|
previousThought && readString(previousThought, "status") === status
|
|
? readString(previousThought, "lastStatusChangeAt") ?? now.toISOString()
|
|
: now.toISOString(),
|
|
};
|
|
});
|
|
const active_thoughts = activeThoughtCandidates
|
|
.sort(
|
|
(left, right) =>
|
|
(readNumber(right, "priorityScore") ?? 0) - (readNumber(left, "priorityScore") ?? 0),
|
|
)
|
|
.slice(0, MAX_INCUBATING_THOUGHTS);
|
|
|
|
const lastIncubationSummary =
|
|
active_thoughts.length > 0
|
|
? `Strongest incubating seam: ${active_thoughts[0].topic} (${active_thoughts[0].status}, speak=${active_thoughts[0].desireToSpeak.toFixed(2)}, self=${active_thoughts[0].noveltyToSelf.toFixed(2)}, room=${active_thoughts[0].noveltyToRoom.toFixed(2)}).`
|
|
: "No incubating thought currently has enough connective tissue to justify special treatment.";
|
|
|
|
return {
|
|
lastUpdatedAt: now.toISOString(),
|
|
lastIncubationSummary,
|
|
active_thoughts,
|
|
};
|
|
}
|
|
|
|
function buildLatentQuestion(cluster) {
|
|
if (typeof cluster.question === "string" && cluster.question.length > 0) {
|
|
return cluster.question;
|
|
}
|
|
|
|
if (cluster.sourceKinds.includes("repo_sweep") && cluster.sourceKinds.includes("archive_excursion")) {
|
|
return "Which older pattern is the current work rediscovering?";
|
|
}
|
|
|
|
if (cluster.sourceKinds.includes("dream") || cluster.sourceKinds.includes("musing")) {
|
|
return "What concrete body would stop this thought from staying backstage?";
|
|
}
|
|
|
|
return "What concrete claim survives another pass?";
|
|
}
|
|
|
|
function buildAttractionLine(cluster, { noveltyToSelf, evidenceDiversity }) {
|
|
const concreteness = readNumber(cluster.curiosityProfile, "concreteness") ?? 0.2;
|
|
const fertility = readNumber(cluster.curiosityProfile, "fertility") ?? 0.3;
|
|
const crossDomainPotential = readNumber(cluster.curiosityProfile, "crossDomainPotential") ?? 0.2;
|
|
if (noveltyToSelf >= 0.62 && evidenceDiversity >= 0.45) {
|
|
return "It keeps pulling because it is still finding genuinely different evidence instead of merely changing hats.";
|
|
}
|
|
|
|
if (concreteness >= 0.6 && crossDomainPotential >= 0.55) {
|
|
return "It keeps pulling because it is concrete enough to touch and broad enough to connect outward into other machines.";
|
|
}
|
|
|
|
if (fertility >= 0.65) {
|
|
return "It keeps pulling because every pass through it threatens to change more than one part of the map.";
|
|
}
|
|
|
|
if (cluster.sourceKinds.length >= 3) {
|
|
return `It keeps pulling because it is showing up in several different organs at once: ${cluster.sourceKinds.join(", ")}.`;
|
|
}
|
|
|
|
return `It keeps pulling because ${cluster.sourceKinds.join(" and ")} are rhyming instead of staying in their lanes.`;
|
|
}
|
|
|
|
function buildHoldingLine({ cluster, status, recentSpeechDamping, saturationScore, noveltyToSelf, explorationBonus }) {
|
|
const quietSignalRatio = readNumber(cluster, "quietSignalRatio") ?? 0;
|
|
const concreteness = readNumber(cluster.curiosityProfile, "concreteness") ?? 0.2;
|
|
|
|
if (quietSignalRatio >= 0.55) {
|
|
return "This seam is mostly empty-room bookkeeping; keep at most a trace of it and go find a better question.";
|
|
}
|
|
|
|
if (status === "stalled") {
|
|
return "This seam is repeating itself without earning enough new structure. Merge the receipts, cool it off, and go somewhere less domesticated.";
|
|
}
|
|
|
|
if (status === "refractory") {
|
|
return "This seam has been chewing the same meat too recently. Let it cool unless a live hook or a genuinely different source family forces it back open.";
|
|
}
|
|
|
|
if (status === "ripe") {
|
|
return "This seam has enough connective tissue that the default question is how to say it cleanly, not whether it deserves a mouth at all.";
|
|
}
|
|
|
|
if (cluster.worldFacing !== true && concreteness < 0.42) {
|
|
return "This seam is still too inward and abstract to deserve the whole room. Tie it to a concrete system or let something sharper take the floor.";
|
|
}
|
|
|
|
if (recentSpeechDamping >= 0.45) {
|
|
return "Recent speech already scratched the itch a bit, so give the seam one more pass unless it is still concrete, world-facing, and plainly worth airing.";
|
|
}
|
|
|
|
if (cluster.sourceKinds.length < 2) {
|
|
return "It is still mostly one lane talking to itself.";
|
|
}
|
|
|
|
if (saturationScore >= 0.55 && noveltyToSelf < 0.45) {
|
|
return "The family resemblance is getting too strong. Follow a stranger branch before you let this thought speak again.";
|
|
}
|
|
|
|
if (explorationBonus >= 0.45) {
|
|
return "This one is still drawing energy from terrain Void has not overworked yet, so another pass might genuinely change it.";
|
|
}
|
|
|
|
return "Give it another pass so the thought has a better chance of growing teeth instead of just polish.";
|
|
}
|
|
|
|
export function buildSourceCoverage({ runtime, now }) {
|
|
const repoCounts = new Map();
|
|
const channelCounts = new Map();
|
|
const yearCounts = new Map();
|
|
|
|
for (const sweep of ensureArray(runtime.recent_repo_activity_sweeps).filter(isObject).slice(-16)) {
|
|
for (const repoName of ensureStringArray(sweep.repoNames)) {
|
|
repoCounts.set(repoName, (repoCounts.get(repoName) ?? 0) + 1);
|
|
}
|
|
}
|
|
|
|
for (const excursion of ensureArray(runtime.recent_archive_excursions).filter(isObject).slice(-16)) {
|
|
const channelId = readString(excursion, "channelId");
|
|
if (channelId) {
|
|
channelCounts.set(channelId, (channelCounts.get(channelId) ?? 0) + 1);
|
|
}
|
|
const timestamp = readString(excursion, "anchorTimestamp") ?? readString(excursion, "timestamp");
|
|
const year = extractYear(timestamp);
|
|
if (year) {
|
|
yearCounts.set(year, (yearCounts.get(year) ?? 0) + 1);
|
|
}
|
|
}
|
|
|
|
return {
|
|
lastUpdatedAt: now.toISOString(),
|
|
repos: mapCountEntries(repoCounts, "name"),
|
|
archiveYears: mapCountEntries(yearCounts, "year"),
|
|
channels: mapCountEntries(channelCounts, "channelId"),
|
|
};
|
|
}
|
|
|
|
export function reconcileBridgeState({ previous, activeThoughts, sourceCoverage, now }) {
|
|
const bridge = {
|
|
...previous,
|
|
source_coverage: sourceCoverage,
|
|
};
|
|
const previousTopicSaturation = ensureArray(previous.topic_saturation).filter(isObject);
|
|
const previousRefractoryTopics = ensureArray(previous.refractory_topics).filter(isObject);
|
|
|
|
const saturationEntries = activeThoughts
|
|
.filter((thought) => (readNumber(thought, "saturationScore") ?? 0) >= 0.42)
|
|
.sort(
|
|
(left, right) =>
|
|
(readNumber(right, "saturationScore") ?? 0) - (readNumber(left, "saturationScore") ?? 0),
|
|
)
|
|
.slice(0, MAX_TOPIC_SATURATION_COUNT)
|
|
.map((thought) => {
|
|
const topic = readString(thought, "topic") ?? "untitled";
|
|
const prior = previousTopicSaturation.find(
|
|
(entry) => topicSimilarity(topic, readString(entry, "topic") ?? "") >= TOPIC_MATCH_THRESHOLD,
|
|
);
|
|
return {
|
|
topic,
|
|
focusKind: readString(thought, "focusKind"),
|
|
targetKind: readString(thought, "targetKind"),
|
|
dominance: round3(readNumber(thought, "saturationScore") ?? 0),
|
|
recentMentions: readNumber(thought, "supportCount") ?? readNumber(prior, "recentMentions") ?? 1,
|
|
coolingAdvice: buildCoolingAdvice({ thought, sourceCoverage }),
|
|
lastUpdatedAt: now.toISOString(),
|
|
};
|
|
});
|
|
|
|
const refractoryTopics = activeThoughts
|
|
.filter((thought) => {
|
|
const status = readString(thought, "status");
|
|
return status === "refractory" || status === "stalled" || (readNumber(thought, "saturationScore") ?? 0) >= 0.62;
|
|
})
|
|
.sort(
|
|
(left, right) =>
|
|
(readNumber(right, "refractoryPenalty") ?? 0) - (readNumber(left, "refractoryPenalty") ?? 0),
|
|
)
|
|
.slice(0, MAX_REFRACTORY_TOPIC_COUNT)
|
|
.map((thought) => {
|
|
const topic = readString(thought, "topic") ?? "untitled";
|
|
const prior = previousRefractoryTopics.find(
|
|
(entry) => topicSimilarity(topic, readString(entry, "topic") ?? "") >= REFRACTORY_MATCH_THRESHOLD,
|
|
);
|
|
const penalty = round3(
|
|
Math.max(readNumber(thought, "refractoryPenalty") ?? 0, readNumber(prior, "penalty") ?? 0.18),
|
|
);
|
|
const hours = penalty >= 0.28 ? 4 : penalty >= 0.2 ? 3 : 2;
|
|
return {
|
|
topic,
|
|
focusKind: readString(thought, "focusKind"),
|
|
targetKind: readString(thought, "targetKind"),
|
|
penalty,
|
|
coolsUntil: new Date(now.getTime() + hours * 60 * 60 * 1000).toISOString(),
|
|
reason: buildRefractoryReason(thought),
|
|
lastTriggeredAt: now.toISOString(),
|
|
};
|
|
});
|
|
|
|
bridge.topic_saturation = saturationEntries;
|
|
bridge.refractory_topics = refractoryTopics;
|
|
return bridge;
|
|
}
|
|
|
|
function computeNoveltyToSelf({ cluster, previousThoughts, bridge, currentThoughtId }) {
|
|
let strongestMatch = 0;
|
|
|
|
for (const thought of previousThoughts) {
|
|
if (readString(thought, "thoughtId") === currentThoughtId) {
|
|
continue;
|
|
}
|
|
strongestMatch = Math.max(
|
|
strongestMatch,
|
|
compareThoughtLikeSurfaces(
|
|
cluster,
|
|
readString(thought, "topic"),
|
|
readString(thought, "summary"),
|
|
ensureStringArray(thought.sourceMemoryIds),
|
|
),
|
|
);
|
|
}
|
|
|
|
for (const synthesis of ensureArray(bridge.recent_syntheses).filter(isObject).slice(-6)) {
|
|
strongestMatch = Math.max(
|
|
strongestMatch,
|
|
compareThoughtLikeSurfaces(
|
|
cluster,
|
|
ensureStringArray(synthesis.dominantTopics).join(" / "),
|
|
readString(synthesis, "summary"),
|
|
[],
|
|
),
|
|
);
|
|
}
|
|
|
|
return clamp(1 - strongestMatch, 0, 1);
|
|
}
|
|
|
|
function computeNoveltyToRoom(cluster, recentNoveltyChecks) {
|
|
let bestScore = 0.64;
|
|
|
|
for (const check of recentNoveltyChecks.slice(-12)) {
|
|
const match = compareThoughtLikeSurfaces(
|
|
cluster,
|
|
readString(check, "topic"),
|
|
readString(check, "summary"),
|
|
ensureStringArray(check.supportingMessageIds),
|
|
);
|
|
|
|
if (match < TOPIC_MATCH_THRESHOLD) {
|
|
continue;
|
|
}
|
|
|
|
bestScore = mapNoveltyResultToScore(readString(check, "result"));
|
|
break;
|
|
}
|
|
|
|
return bestScore;
|
|
}
|
|
|
|
function computeSaturationMetrics({ cluster, previousThoughts, bridge, supportCount }) {
|
|
let recentMatchCount = 0;
|
|
|
|
for (const thought of previousThoughts) {
|
|
const match = compareThoughtLikeSurfaces(
|
|
cluster,
|
|
readString(thought, "topic"),
|
|
readString(thought, "summary"),
|
|
ensureStringArray(thought.sourceMemoryIds),
|
|
);
|
|
if (match >= TOPIC_MATCH_THRESHOLD) {
|
|
recentMatchCount += 1;
|
|
}
|
|
}
|
|
|
|
for (const synthesis of ensureArray(bridge.recent_syntheses).filter(isObject).slice(-5)) {
|
|
const match = compareThoughtLikeSurfaces(
|
|
cluster,
|
|
ensureStringArray(synthesis.dominantTopics).join(" / "),
|
|
readString(synthesis, "summary"),
|
|
[],
|
|
);
|
|
if (match >= TOPIC_MATCH_THRESHOLD) {
|
|
recentMatchCount += 1;
|
|
}
|
|
}
|
|
|
|
const existingTopicSaturation = ensureArray(bridge.topic_saturation)
|
|
.filter(isObject)
|
|
.map((entry) =>
|
|
topicSimilarity(cluster.label, readString(entry, "topic") ?? "") >= TOPIC_MATCH_THRESHOLD
|
|
? readNumber(entry, "dominance") ?? 0
|
|
: 0,
|
|
)
|
|
.sort((left, right) => right - left)[0] ?? 0;
|
|
|
|
const score = clamp(
|
|
recentMatchCount * 0.16 +
|
|
existingTopicSaturation * 0.42 +
|
|
Math.min(1, supportCount / 10) * 0.16 +
|
|
(readNumber(cluster, "quietSignalRatio") ?? 0) * 0.2,
|
|
0,
|
|
1,
|
|
);
|
|
|
|
return {
|
|
score,
|
|
recentMatchCount,
|
|
};
|
|
}
|
|
|
|
function computeRefractoryPenalty(cluster, bridge, now) {
|
|
let penalty = 0;
|
|
|
|
for (const topic of ensureArray(bridge.refractory_topics).filter(isObject)) {
|
|
const coolsUntil = readString(topic, "coolsUntil");
|
|
if (coolsUntil && new Date(coolsUntil).getTime() < now.getTime()) {
|
|
continue;
|
|
}
|
|
|
|
const match = topicSimilarity(cluster.label, readString(topic, "topic") ?? "");
|
|
if (match >= REFRACTORY_MATCH_THRESHOLD) {
|
|
penalty = Math.max(
|
|
penalty,
|
|
(readNumber(topic, "penalty") ?? 0.18) * match,
|
|
);
|
|
}
|
|
}
|
|
|
|
return clamp(penalty, 0, 0.45);
|
|
}
|
|
|
|
function computeExplorationBonus(cluster, sourceCoverage) {
|
|
const sourceKinds = ensureStringArray(cluster.sourceKinds);
|
|
const hasPrimaryExplorationSource =
|
|
sourceKinds.includes("repo_sweep") || sourceKinds.includes("archive_excursion");
|
|
|
|
const repoEntries = ensureArray(sourceCoverage.repos).filter(isObject);
|
|
const yearEntries = ensureArray(sourceCoverage.archiveYears).filter(isObject);
|
|
const channelEntries = ensureArray(sourceCoverage.channels).filter(isObject);
|
|
const scores = [];
|
|
|
|
for (const repoName of ensureStringArray(cluster.repoNames)) {
|
|
scores.push(inverseCoverageWeight(repoEntries, "name", repoName));
|
|
}
|
|
for (const year of ensureStringArray(cluster.archiveYears)) {
|
|
scores.push(inverseCoverageWeight(yearEntries, "year", year));
|
|
}
|
|
for (const channelId of ensureStringArray(cluster.channelIds)) {
|
|
scores.push(inverseCoverageWeight(channelEntries, "channelId", channelId));
|
|
}
|
|
|
|
const concreteness = readNumber(cluster.curiosityProfile, "concreteness") ?? 0.2;
|
|
const worldFacing = cluster.worldFacing === true;
|
|
|
|
if (scores.length === 0) {
|
|
return clamp((hasPrimaryExplorationSource ? 0.24 : 0.12) + concreteness * 0.08 + (worldFacing ? 0.05 : 0), 0, 1);
|
|
}
|
|
|
|
const average = scores.reduce((sum, value) => sum + value, 0) / scores.length;
|
|
return clamp(
|
|
(hasPrimaryExplorationSource ? average : Math.min(0.3, average * 0.5)) +
|
|
concreteness * 0.08 +
|
|
(worldFacing ? 0.05 : 0),
|
|
0,
|
|
1,
|
|
);
|
|
}
|
|
|
|
function inverseCoverageWeight(entries, key, value) {
|
|
const count =
|
|
readNumber(
|
|
entries.find((entry) => readString(entry, key) === value),
|
|
"count",
|
|
) ?? 0;
|
|
if (count <= 1) {
|
|
return 0.9;
|
|
}
|
|
if (count === 2) {
|
|
return 0.64;
|
|
}
|
|
if (count === 3) {
|
|
return 0.42;
|
|
}
|
|
return 0.2;
|
|
}
|
|
|
|
function buildCoolingAdvice({ thought, sourceCoverage }) {
|
|
const topic = readString(thought, "topic") ?? "this seam";
|
|
const repos = ensureArray(sourceCoverage.repos).filter(isObject);
|
|
const years = ensureArray(sourceCoverage.archiveYears).filter(isObject);
|
|
const lesserRepo = repos.sort((left, right) => (readNumber(left, "count") ?? 0) - (readNumber(right, "count") ?? 0))[0];
|
|
const lesserYear = years.sort((left, right) => (readNumber(left, "count") ?? 0) - (readNumber(right, "count") ?? 0))[0];
|
|
|
|
if (lesserRepo && readString(lesserRepo, "name")) {
|
|
return `Cool ${topic} by checking a less-worked repo family such as ${readString(lesserRepo, "name")} before speaking again.`;
|
|
}
|
|
|
|
if (lesserYear && readString(lesserYear, "year")) {
|
|
return `Cool ${topic} by diving into a less-touched archive era like ${readString(lesserYear, "year")} before another synthesis.`;
|
|
}
|
|
|
|
return `Cool ${topic} by chasing a genuinely different source family before letting it speak again.`;
|
|
}
|
|
|
|
function buildRefractoryReason(thought) {
|
|
const topic = readString(thought, "topic") ?? "this seam";
|
|
const noveltyToSelf = readNumber(thought, "noveltyToSelf") ?? 0;
|
|
const saturationScore = readNumber(thought, "saturationScore") ?? 0;
|
|
const identityPenalty = readNumber(thought, "identityPenalty") ?? 0;
|
|
if (identityPenalty >= 0.68) {
|
|
return `${topic} is already too close to settled doctrine to keep eating curiosity budget unless fresh evidence changes it.`;
|
|
}
|
|
if (noveltyToSelf < 0.3) {
|
|
return `${topic} is matching Void's own recent thought history too closely to deserve another immediate pass.`;
|
|
}
|
|
if (saturationScore >= 0.62) {
|
|
return `${topic} has dominated too many recent syntheses and needs cooling before it becomes state religion.`;
|
|
}
|
|
return `${topic} needs a little cooling-off period before it earns attention again.`;
|
|
}
|
|
|
|
function compareThoughtLikeSurfaces(cluster, topic, summary, sourceMemoryIds) {
|
|
const topicMatch = topicSimilarity(cluster.label, topic ?? "");
|
|
const summaryMatch = topicSimilarity(cluster.summary, summary ?? "");
|
|
const questionMatch =
|
|
typeof cluster.question === "string" ? topicSimilarity(cluster.question, [topic, summary].filter(Boolean).join(" ")) : 0;
|
|
const sourceMatch = overlapRatio(cluster.memoryIds, ensureArray(sourceMemoryIds));
|
|
return Math.max(topicMatch, summaryMatch * 0.9, questionMatch * 0.85, sourceMatch * 0.95);
|
|
}
|
|
|
|
function mapNoveltyResultToScore(result) {
|
|
const normalized = String(result ?? "").toLowerCase();
|
|
if (normalized === "novel") {
|
|
return 0.92;
|
|
}
|
|
if (normalized.includes("adjacent")) {
|
|
return 0.56;
|
|
}
|
|
if (normalized.includes("duplicate") || normalized.includes("already")) {
|
|
return 0.2;
|
|
}
|
|
return 0.64;
|
|
}
|
|
|
|
export function buildIdentitySurface({ state, memories }) {
|
|
const surfaces = [];
|
|
const identity = ensureObject(state.identity);
|
|
const canonicalState = ensureObject(state.canonical_state);
|
|
|
|
for (const note of ensureStringArray(identity.private_notes)) {
|
|
surfaces.push({ topic: note, summary: note });
|
|
}
|
|
|
|
for (const value of getValueObjects(canonicalState.values)) {
|
|
const label = readString(value, "label");
|
|
if (label) {
|
|
surfaces.push({ topic: label, summary: label });
|
|
}
|
|
}
|
|
|
|
for (const memory of ensureArray(memories.semantic).filter(isObject)) {
|
|
const kind = readString(memory, "kind");
|
|
if (kind !== "identity_seam") {
|
|
continue;
|
|
}
|
|
surfaces.push({
|
|
topic: readString(memory, "subjectLabel") ?? readString(memory, "subjectId") ?? "identity seam",
|
|
summary: readString(memory, "summary") ?? "",
|
|
});
|
|
}
|
|
|
|
return surfaces;
|
|
}
|
|
|
|
function buildCuriosityContext({ previousThoughts, bridge }) {
|
|
const recentThoughts = previousThoughts
|
|
.slice(0, 6)
|
|
.map((thought) => ({
|
|
worldFacing: thought.worldFacing === true,
|
|
concreteness: readNumber(thought, "concreteness") ?? 0.2,
|
|
targetKind: readString(thought, "targetKind") ?? "unknown",
|
|
}));
|
|
|
|
const recentCount = recentThoughts.length || 1;
|
|
const inwardCount = recentThoughts.filter((thought) => !thought.worldFacing || thought.targetKind === "self").length;
|
|
const abstractCount = recentThoughts.filter((thought) => thought.concreteness < 0.45).length;
|
|
const outwardPressure = clamp(inwardCount / recentCount * 0.52 + abstractCount / recentCount * 0.48, 0, 1);
|
|
const recentDominance = ensureArray(bridge.topic_saturation)
|
|
.filter(isObject)
|
|
.slice(0, 2)
|
|
.reduce((sum, entry) => sum + (readNumber(entry, "dominance") ?? 0), 0);
|
|
|
|
return {
|
|
outwardPressure: clamp(outwardPressure + recentDominance * 0.08, 0, 1),
|
|
};
|
|
}
|
|
|
|
function computeOutwardCuriosityBonus({ cluster, curiosityContext, concreteness }) {
|
|
const outwardPressure = curiosityContext.outwardPressure ?? 0;
|
|
if (cluster.worldFacing === true) {
|
|
return clamp(outwardPressure * (0.3 + concreteness * 0.5), 0, 0.45);
|
|
}
|
|
|
|
return clamp(-(outwardPressure * 0.16), -0.16, 0);
|
|
}
|
|
|
|
function computeIdentityPenalty(cluster, identitySurface) {
|
|
let strongestMatch = 0;
|
|
|
|
for (const surface of identitySurface) {
|
|
strongestMatch = Math.max(
|
|
strongestMatch,
|
|
compareThoughtLikeSurfaces(cluster, surface.topic, surface.summary, []),
|
|
);
|
|
}
|
|
|
|
return clamp(strongestMatch, 0, 1);
|
|
}
|