Split memory organ concerns

This commit is contained in:
Meta Vens 2026-05-16 13:30:53 +01:00
commit 1cc31f1e0a
10 changed files with 2516 additions and 2348 deletions

View file

@ -31,8 +31,8 @@ Git history and smoke artifacts carry routine proof. `state/evidence.jsonl` shou
- `scripts/run-void-moderator-rumination.ps1` now records reviewed Discord cursors and speech delivery receipts through typed self-state operations. The old `set-cursor` and `record-delivery-receipt` commands were removed from `scripts/moderation-state-store.mjs`.
- The unattended moderation child no longer gets to commit whole-state JSON edits. It reads `.voidbot/private/moderation-agent-state.json` as a compatibility projection, writes proposed operation payloads to `.voidbot/status/moderation-rumination-operations.json`, and the parent runner applies them through `scripts/void-self-state.mjs apply-operation`.
- `scripts/simulate-void-mood.mjs` now sends sleep-cycle and speaking-pressure results through typed operations after its legacy save, so scheduled-runtime typed documents stay current. The memory organ itself still mutates the legacy monolith and remains the next major surgery.
- `scripts/void-memory-organ.mjs` has started being split by concern. Shared limits/text sets, generic utilities, vector/embedder code, memory-record intake, legacy mirror repair, and agency/intervention policy now live in `scripts/void-memory-organ-constants.mjs`, `scripts/void-memory-organ-utils.mjs`, `scripts/void-memory-organ-vector.mjs`, `scripts/void-memory-organ-records.mjs`, `scripts/void-memory-organ-legacy-mirrors.mjs`, and `scripts/void-memory-organ-agency.mjs`, with `scripts/void-memory-organ-shared.mjs` as a temporary re-export shim.
- Most of the deterministic cleanup in `scripts/void-memory-organ.mjs` is recent compensator cruft from failed attempts to manually clean an exploded state file. Do not preserve it as an earned organ. Architect the state boundary so that cleanup pile is unnecessary.
- `scripts/void-memory-organ.mjs` is now a thin orchestrator. Shared limits/text sets, generic utilities, vector/embedder code, memory-record intake, graph/cluster synthesis, historical cleanup/translation, incubation/bridge scoring, sleep/distillation, legacy mirror repair, and agency/intervention policy live in `scripts/void-memory-organ-constants.mjs`, `scripts/void-memory-organ-utils.mjs`, `scripts/void-memory-organ-vector.mjs`, `scripts/void-memory-organ-records.mjs`, `scripts/void-memory-organ-graph.mjs`, `scripts/void-memory-organ-cleanup.mjs`, `scripts/void-memory-organ-incubation.mjs`, `scripts/void-memory-organ-sleep.mjs`, `scripts/void-memory-organ-legacy-mirrors.mjs`, and `scripts/void-memory-organ-agency.mjs`, with `scripts/void-memory-organ-shared.mjs` as a temporary re-export shim.
- Most of the deterministic cleanup in `scripts/void-memory-organ-cleanup.mjs` is recent compensator cruft from failed attempts to manually clean an exploded state file. Do not preserve it as an earned organ. Architect the state boundary so that cleanup pile is unnecessary.
- Sleep/distillation must be rewritten with a meaning-preservation contract: concrete subject, claim/question, evidence refs, live tension, and future-action implication survive compression. Bulk can die. Meaning does not get put through the office shredder and called a dream.
- The owner Codex lane is no longer one swollen file; orchestration, runtime/parsing, rendering, and shared helpers are split under `packages/providers/src/owner-codex-*.ts`.
- The local Ollama lane is no longer one swollen file; orchestration stays in `packages/providers/src/local-llm-provider.ts` while prompt/rendering, tool-loop helpers, and shared types/constants live under `packages/providers/src/local-llm-*.ts`.
@ -90,7 +90,7 @@ Git history and smoke artifacts carry routine proof. `state/evidence.jsonl` shou
## Likely Next Bounded Move
- Stop feature work on moderation, mood, agency, and public-lane behavior until the private-state boundary is rebuilt.
- First bounded move after the current commit: keep splitting `scripts/void-memory-organ.mjs` by ownership, starting with cleanup/translation versus graph/cluster synthesis, incubation/bridge, and sleep/distillation, then move memory-maintenance, incubation, and candidate-intervention outputs behind typed operations instead of direct canonical/working-projection writes.
- First bounded move after the current commit: move memory-maintenance, incubation, sleep, and candidate-intervention outputs behind typed operations instead of direct canonical/working-projection writes.
- Next cuts should delete unstable foundations early after concrete writers move: remove `legacyJsonPath`/working-projection authority, then cut top-level mirror fields and cleanup code that only compensated for those broken boundaries.
- Rebuild sleep/distillation before trusting it again. Fixture check: a memory like `AquariumSynthCSharp: Workflow cannot own the body` must survive sleep with its concrete subject, claim, evidence, tension, and implication intact.
- Do not build adapters around the JSON projection. That is how the heap learned to stand upright and ask for snacks.

View file

@ -121,7 +121,9 @@ This note is the source-grounded description of how the live VoidBot stack is sh
- `scripts/simulate-void-mood.mjs`
- plain Node mood-drift organ that nudges the shared personality-vector activations with Perlin-shaped noise, damps them back toward their means using plasticity, updates the speak/confession/novelty pressure meter in the shared self-state, maintains the nap-cycle clock plus sleep-state vector bias, and now also reconciles compact semantic-memory vectors plus incubation/dream state.
- `scripts/void-memory-organ.mjs`
- hybrid symbolic/vector memory helper used by mood drift to attach compact semantic vectors to persistent memories and runtime seeds, build resonance clusters, keep an incubation queue alive across turns, leave more meaningful dream residue during naps, and turn sustained value-backed pressure into explicit discomforts, tensions, and advocacy requests.
- thin orchestrator for the hybrid symbolic/vector memory helper used by mood drift.
- `scripts/void-memory-organ-*.mjs`
- split memory-organ modules for shared limits/utilities, vector/embedder substrate, memory-record intake, graph/cluster synthesis, historical cleanup/translation, incubation/bridge scoring, sleep/distillation, legacy mirror repair, and agency/intervention policy.
- `scripts/run-void-mood-drift.ps1`
- hidden-task wrapper for the mood-drift organ; writes status/log pulse files and respects the moderation lock so the two state writers do not stomp each other for sport.
- `scripts/install-void-mood-drift-task.ps1`

View file

@ -0,0 +1,850 @@
import {
MAX_ARCHIVE_EXCURSION_MEMORIES,
MAX_REPO_SWEEP_MEMORIES,
MAX_SEMANTIC_MEMORIES,
MAX_DREAM_MEMORIES,
MAX_RUNTIME_REPO_SWEEPS,
MAX_RUNTIME_REPO_ACTIVITY_MEMORIES,
MAX_RUNTIME_ARCHIVE_EXCURSIONS,
MAX_RUNTIME_RUMINATION_SEEDS,
MAX_RECENT_SYNTHESIS_COUNT,
MAX_SUPPORTING_REFS,
MAX_INCUBATION_SOURCE_IDS,
MAX_DREAM_SOURCE_IDS,
MAX_POST_NAP_MUSINGS,
MAX_POST_NAP_ARCHIVE_EXCURSIONS,
MAX_POST_NAP_REPO_SWEEPS,
MAX_POST_NAP_NOVELTY_CHECKS,
MAX_POST_NAP_RESONANCE_EDGES,
MAX_DISCOMFORT_COUNT,
MAX_ACTIVE_TENSION_COUNT,
MAX_ADVOCACY_REQUEST_COUNT,
MAX_RECENT_ANALYTIC_THREADS,
MAX_RECENT_QUIET_ANALYTIC_THREADS,
MAX_RECENT_EPISODIC_RECORDS,
MAX_RECENT_QUIET_EPISODIC_RECORDS,
TOPIC_MATCH_THRESHOLD,
ensureArray,
ensureObject,
ensureStringArray,
readString,
readNumber,
normalizeText,
sanitizePreferredThoughtLabel,
looksKeywordSalad,
looksPersonLikeSingleton,
looksLegacyThoughtSurface,
topConceptKeywords,
compactNarrative,
compactLabel,
capDistinctStrings,
compareMemoryFreshness,
mergeStringArrays,
newestIsoTimestamp,
selectRecentRecords,
topicSimilarity,
clamp,
isObject,
} from "./void-memory-organ-shared.mjs";
import {
buildClusterCuriosityProfile,
extractRepoNameFromLabel,
synthesizeThoughtSurface,
capitalizePhrase,
} from "./void-memory-organ-graph.mjs";
import { reconcileLegacyStateMirrors, trimLegacyRuntimeResidue } from "./void-memory-organ-legacy-mirrors.mjs";
import { trimCandidateInterventions } from "./void-memory-organ-agency.mjs";
export function normalizeHistoricalMemorySurfaces({ state, memories, runtime, now }) {
normalizeSemanticMemories({ memories, now });
normalizeDreamMemories({ memories, now });
dedupeSemanticMemories({ memories });
dedupeDreamMemories({ memories });
pruneHistoricalSeamMemories({ memories });
pruneDreamMemories({ memories });
trimHistoricalMemoryResidue({ memories, runtime });
trimLegacyRuntimeResidue({ runtime });
reconcileLegacyStateMirrors({ state, memories, runtime, now });
}
function normalizeSemanticMemories({ memories, now }) {
const semanticMemories = ensureArray(memories.semantic).filter(isObject);
const kept = [];
for (const memory of semanticMemories) {
if (shouldDropSemanticMemory(memory)) {
continue;
}
if (readString(memory, "kind") === "identity_seam") {
kept.push(memory);
continue;
}
if (isStableSemanticMemory(memory)) {
memory.lastTranslatedAt = readString(memory, "lastTranslatedAt") ?? now.toISOString();
kept.push(memory);
continue;
}
const preferredLabel = choosePreferredThoughtLabel(memory);
const normalized = translateLegacyThoughtLikeEntry({
label: preferredLabel ?? readString(memory, "kind") ?? "semantic seam",
summary: readString(memory, "summary") ?? "",
fallbackTargetKind: "self",
fallbackFocusKind: "question",
evidenceRefs: ensureStringArray(memory.evidenceRefs),
repoNames: ensureStringArray(memory.repoNames),
});
if (!normalized) {
continue;
}
if (shouldPurgeThoughtLikeMemory({ entry: memory, normalized, preferredLabel })) {
continue;
}
memory.subjectLabel = normalized.label;
memory.summary = normalized.summary;
memory.focusKind = normalized.focusKind;
memory.targetKind = normalized.targetKind;
memory.focusPhrase = normalized.focusPhrase;
memory.question = normalized.question;
memory.claim = normalized.claim;
memory.fascinationTarget = normalized.fascinationTarget;
memory.worldFacing = normalized.worldFacing;
memory.lastTranslatedAt = now.toISOString();
kept.push(memory);
}
memories.semantic = kept.slice(-MAX_SEMANTIC_MEMORIES);
}
function normalizeDreamMemories({ memories, now }) {
const dreams = ensureArray(memories.dreams).filter(isObject);
const kept = [];
for (const dream of dreams) {
if (isStableDreamMemory(dream)) {
dream.lastTranslatedAt = readString(dream, "lastTranslatedAt") ?? now.toISOString();
kept.push(dream);
continue;
}
const normalized = translateLegacyThoughtLikeEntry({
label: choosePreferredDreamTheme(dream) ?? "dream seam",
summary: readString(dream, "summary") ?? "",
fallbackTargetKind: "self",
fallbackFocusKind: "question",
evidenceRefs: ensureStringArray(dream.distilledFrom),
repoNames: ensureStringArray(dream.repoNames),
});
if (!normalized) {
continue;
}
if (
shouldPurgeThoughtLikeMemory({
entry: dream,
normalized,
preferredLabel: choosePreferredDreamTheme(dream),
})
) {
continue;
}
dream.theme = normalized.label;
dream.summary = buildDreamSummary(normalized);
dream.focusKind = normalized.focusKind;
dream.targetKind = normalized.targetKind;
dream.focusPhrase = normalized.focusPhrase;
dream.question = normalized.question;
dream.claim = normalized.claim;
dream.worldFacing = normalized.worldFacing;
dream.lastTranslatedAt = now.toISOString();
kept.push(dream);
}
memories.dreams = kept.slice(-MAX_DREAM_MEMORIES);
}
function isStableSemanticMemory(memory) {
const label = readString(memory, "subjectLabel") ?? "";
const summary = readString(memory, "summary") ?? "";
const targetKind = readString(memory, "targetKind") ?? "";
const question = readString(memory, "question") ?? "";
const claim = readString(memory, "claim") ?? "";
if (!label || !summary || hasThoughtSurfaceTemplateSmell(summary)) {
return false;
}
if (looksKeywordSalad(label) || looksPersonLikeSingleton(label)) {
return false;
}
if (targetKind === "repo" && extractRepoNameFromLabel(label)) {
return true;
}
if (targetKind === "self" && /compression|continuity|receipt|dream/i.test(`${label} ${summary}`)) {
return true;
}
return normalizeText(question).length > 0 && normalizeText(claim).length > 0;
}
function isStableDreamMemory(dream) {
const theme = readString(dream, "theme") ?? "";
const summary = readString(dream, "summary") ?? "";
if (!theme || !summary || hasThoughtSurfaceTemplateSmell(summary)) {
return false;
}
if (looksKeywordSalad(theme) || looksPersonLikeSingleton(theme)) {
return false;
}
return true;
}
function translateLegacyThoughtLikeEntry({
label,
summary,
fallbackTargetKind,
fallbackFocusKind,
evidenceRefs,
repoNames,
}) {
const rawLabel = normalizeText(label);
const rawSummary = normalizeText(summary);
if (!rawLabel && !rawSummary) {
return null;
}
const preferredLabel = sanitizePreferredThoughtLabel(rawLabel);
const sourceKinds = inferSourceKindsFromThoughtText(rawSummary, rawLabel, evidenceRefs);
const synthesized = synthesizeThoughtSurface({
sourceKinds,
repoNames,
archiveYears: [],
channelIds: [],
keywords: topConceptKeywords(`${rawLabel} ${rawSummary}`, 6),
evidenceDiversity: inferLegacyEvidenceDiversity(sourceKinds, evidenceRefs, repoNames),
curiosityProfile: buildClusterCuriosityProfile({
sourceKinds,
repoNames,
archiveYears: [],
channelIds: [],
evidenceRefs,
evidenceDiversity: inferLegacyEvidenceDiversity(sourceKinds, evidenceRefs, repoNames),
}),
});
const preferredFocusPhrase = preferredLabel ? normalizeText(preferredLabel).toLowerCase() : synthesized.focusPhrase;
const stabilized = {
...synthesized,
label: preferredLabel ?? synthesized.label,
focusPhrase: preferredFocusPhrase,
fascinationTarget: preferredFocusPhrase,
};
if (!looksLegacyThoughtSurface(rawLabel, rawSummary)) {
return {
...stabilized,
label: preferredLabel ?? rawLabel ?? stabilized.label,
summary: rewriteLegacySummaryToModernShape({
summary: rawSummary,
synthesized: stabilized,
fallbackFocusKind,
fallbackTargetKind,
}),
};
}
return {
...stabilized,
focusKind: stabilized.focusKind ?? fallbackFocusKind,
targetKind: stabilized.targetKind ?? fallbackTargetKind,
summary: rewriteLegacySummaryToModernShape({
summary: rawSummary,
synthesized: stabilized,
fallbackFocusKind,
fallbackTargetKind,
}),
};
}
function choosePreferredThoughtLabel(entry) {
return (
sanitizePreferredThoughtLabel(readString(entry, "subjectLabel") ?? "") ??
sanitizePreferredThoughtLabel(readString(entry, "subjectId") ?? "") ??
null
);
}
function choosePreferredDreamTheme(entry) {
return sanitizePreferredThoughtLabel(readString(entry, "theme") ?? "") ?? null;
}
function rewriteLegacySummaryToModernShape({ summary, synthesized, fallbackFocusKind, fallbackTargetKind }) {
const cleanSummary = normalizeText(summary)
.replace(/^Recurring seam across .*?\.\s*/i, "")
.replace(/^Dream-compressed a seam around .*?\.\s*/i, "")
.replace(/^Self-facing seam around .*?\.\s*/i, "")
.replace(/^Archive-facing seam around .*?\.\s*/i, "")
.replace(/^Fascination with .*?\.\s*/i, "")
.replace(/Built from .*? rather than a single lane talking to itself\.?/gi, "")
.replace(/\s+/g, " ")
.trim();
const focusKind = synthesized.focusKind ?? fallbackFocusKind;
const targetKind = synthesized.targetKind ?? fallbackTargetKind;
if (cleanSummary && !hasThoughtSurfaceTemplateSmell(cleanSummary)) {
return cleanSummary;
}
return buildConciseThoughtSummary({ focusKind, targetKind, synthesized });
}
function inferSourceKindsFromThoughtText(summary, label, evidenceRefs) {
const kinds = new Set();
const text = `${label} ${summary}`.toLowerCase();
const refKinds = evidenceRefs.map((ref) => inferSourceKindFromRef(ref)).filter(Boolean);
for (const kind of refKinds) {
kinds.add(kind);
}
const acrossMatch = summary.match(/Recurring seam across ([^.]+?) around/i);
if (acrossMatch) {
for (const part of acrossMatch[1].split(",")) {
const trimmed = part.trim().toLowerCase();
if (trimmed) {
kinds.add(trimmed);
}
}
}
if (text.includes("dream")) {
kinds.add("dream");
}
if (text.includes("semantic")) {
kinds.add("semantic");
}
if (text.includes("archive")) {
kinds.add("archive_excursion");
}
if (text.includes("repo")) {
kinds.add("repo_sweep");
}
return [...kinds].filter(Boolean);
}
function inferSourceKindFromRef(ref) {
const normalized = String(ref ?? "");
if (normalized.startsWith("repo-sweep-")) {
return "repo_sweep";
}
if (normalized.startsWith("archive-excursion-")) {
return "archive_excursion";
}
if (normalized.startsWith("dream-")) {
return "dream";
}
if (normalized.startsWith("musing-")) {
return "musing";
}
if (normalized.startsWith("semantic-") || normalized.startsWith("semantic-seam-")) {
return "semantic";
}
if (normalized.startsWith("episodic-")) {
return "episodic";
}
return undefined;
}
function inferLegacyEvidenceDiversity(sourceKinds, evidenceRefs, repoNames) {
return clamp(
Math.min(1, sourceKinds.length / 4) * 0.42 +
Math.min(1, evidenceRefs.length / 10) * 0.28 +
Math.min(1, repoNames.length / 2) * 0.2,
0,
1,
);
}
export function buildConciseThoughtSummary({ focusKind, targetKind, synthesized }) {
const focus = capitalizePhrase(synthesized.focusPhrase);
const repoTarget = capitalizePhrase(synthesized.fascinationTarget ?? synthesized.focusPhrase);
if (focusKind === "fascination" && targetKind === "repo") {
return `${repoTarget} keeps pulling as a concrete repo seam.`;
}
if (targetKind === "archive") {
return `${focus} keeps showing up in the archive strongly enough to deserve a cleaner read.`;
}
if (targetKind === "self") {
return `${focus} keeps surviving compression as a self-seam.`;
}
if (focusKind === "claim") {
return `${focus} has hardened into a concrete claim.`;
}
return `${focus} remains unresolved enough to deserve a cleaner read.`;
}
export function buildDreamSummary(normalized) {
const focus = capitalizePhrase(normalized.focusPhrase ?? normalized.label ?? "this seam");
if ((normalized.targetKind ?? "") === "archive") {
return `Dream residue around ${focus} from older archive pressure.`;
}
if ((normalized.targetKind ?? "") === "self") {
return `Dream residue around ${focus} as a self-seam.`;
}
return `Dream residue around ${focus}.`;
}
function dedupeSemanticMemories({ memories }) {
const semanticMemories = ensureArray(memories.semantic).filter(isObject);
const bySignature = new Map();
for (const memory of semanticMemories) {
const kind = readString(memory, "kind") ?? "semantic";
const subjectLabel = readString(memory, "subjectLabel") ?? "";
const signature = `${kind}|${normalizeText(subjectLabel).toLowerCase()}`;
const existing = bySignature.get(signature);
if (!existing) {
bySignature.set(signature, memory);
continue;
}
existing.evidenceRefs = mergeStringArrays(existing.evidenceRefs, memory.evidenceRefs);
existing.evidenceMessageIds = mergeStringArrays(existing.evidenceMessageIds, memory.evidenceMessageIds);
existing.lastObservedAt =
newestIsoTimestamp(readString(existing, "lastObservedAt"), readString(memory, "lastObservedAt")) ??
readString(existing, "lastObservedAt") ??
readString(memory, "lastObservedAt");
existing.confidence = Math.max(readNumber(existing, "confidence") ?? 0, readNumber(memory, "confidence") ?? 0);
if ((readString(memory, "summary") ?? "").length > (readString(existing, "summary") ?? "").length) {
existing.summary = memory.summary;
}
}
memories.semantic = [...bySignature.values()].slice(-MAX_SEMANTIC_MEMORIES);
}
function dedupeDreamMemories({ memories }) {
const dreams = ensureArray(memories.dreams).filter(isObject);
const byTheme = new Map();
for (const dream of dreams) {
const theme = normalizeText(readString(dream, "theme") ?? "").toLowerCase();
if (!theme) {
continue;
}
const existing = byTheme.get(theme);
if (!existing) {
byTheme.set(theme, dream);
continue;
}
existing.distilledFrom = mergeStringArrays(existing.distilledFrom, dream.distilledFrom);
existing.salience = Math.max(readNumber(existing, "salience") ?? 0, readNumber(dream, "salience") ?? 0);
existing.timestamp =
newestIsoTimestamp(readString(existing, "timestamp"), readString(dream, "timestamp")) ??
readString(existing, "timestamp") ??
readString(dream, "timestamp");
}
memories.dreams = [...byTheme.values()].slice(-MAX_DREAM_MEMORIES);
}
function pruneHistoricalSeamMemories({ memories }) {
const semanticMemories = ensureArray(memories.semantic).filter(isObject);
const identitySeams = [];
const nonSeams = [];
const groupedDistilled = new Map();
for (const memory of semanticMemories) {
if (shouldDropSemanticMemory(memory)) {
continue;
}
const kind = readString(memory, "kind");
if (kind === "identity_seam") {
identitySeams.push(memory);
continue;
}
if (kind !== "distilled_seam") {
const subjectLabel = readString(memory, "subjectLabel") ?? "";
const subjectId = readString(memory, "subjectId") ?? "";
if (
(!subjectLabel || subjectLabel.startsWith("seam:")) &&
(subjectId.startsWith("seam:") || subjectId.startsWith("pattern-"))
) {
continue;
}
if (hasThoughtSurfaceTemplateSmell(readString(memory, "summary") ?? "")) {
continue;
}
nonSeams.push(memory);
continue;
}
const targetKind = readString(memory, "targetKind") ?? "system";
if (!groupedDistilled.has(targetKind)) {
groupedDistilled.set(targetKind, []);
}
groupedDistilled.get(targetKind).push(memory);
}
const keptDistilled = [];
const limits = new Map([
["repo", 8],
["archive", 6],
["room", 4],
["self", 3],
["system", 4],
]);
for (const [targetKind, entries] of groupedDistilled.entries()) {
const sorted = entries
.filter((entry) => !isLowCoherenceDistilledSeam(entry))
.filter((entry) => !hasThoughtSurfaceTemplateSmell(readString(entry, "summary") ?? ""))
.sort(compareMemoryFreshness);
keptDistilled.push(...sorted.slice(0, limits.get(targetKind) ?? 4));
}
memories.semantic = [...nonSeams, ...keptDistilled, ...identitySeams]
.sort(compareMemoryFreshness)
.slice(0, MAX_SEMANTIC_MEMORIES);
}
function isLowCoherenceDistilledSeam(entry) {
const label = readString(entry, "subjectLabel") ?? "";
const summary = readString(entry, "summary") ?? "";
const targetKind = readString(entry, "targetKind") ?? "";
const kind = readString(entry, "kind") ?? "";
const keywords = topConceptKeywords(`${label} ${summary}`, 5);
if (keywords.length < 2) {
return true;
}
if (kind === "recent-preoccupation" || kind === "quiet-room-status") {
return true;
}
if (targetKind === "self" && keywords.length < 3) {
return true;
}
if (targetKind === "system" && /live seam|live question|live claim/i.test(summary)) {
return true;
}
if (!label || label.startsWith("seam:")) {
return true;
}
if (looksPersonLikeSingleton(label)) {
return true;
}
if (looksKeywordSalad(label)) {
return true;
}
if (/still matters\./i.test(summary) && !/^[A-Z][A-Za-z0-9]+: /.test(label)) {
return true;
}
return false;
}
function shouldPurgeThoughtLikeMemory({ entry, normalized, preferredLabel }) {
const rawSummary = readString(entry, "summary") ?? "";
const rawQuestion = readString(entry, "question") ?? "";
const rawClaim = readString(entry, "claim") ?? "";
const label =
preferredLabel ??
readString(entry, "subjectLabel") ??
readString(entry, "theme") ??
normalized.label ??
"";
if (!hasThoughtSurfaceTemplateSmell(rawSummary) && !hasThoughtSurfaceTemplateSmell(rawQuestion)) {
return false;
}
if (sanitizePreferredThoughtLabel(label)) {
return false;
}
if (normalizeText(rawClaim).length > 0 && !hasThoughtSurfaceTemplateSmell(rawClaim)) {
return false;
}
return true;
}
function shouldDropSemanticMemory(entry) {
const kind = readString(entry, "kind") ?? "";
const label = readString(entry, "subjectLabel") ?? readString(entry, "theme") ?? "";
const summary = readString(entry, "summary") ?? "";
const targetKind = readString(entry, "targetKind") ?? "";
const focusKind = readString(entry, "focusKind") ?? "";
if (kind === "identity_seam") {
return false;
}
if (kind === "recent-preoccupation" || kind === "quiet-room-status") {
return true;
}
if (looksPersonLikeSingleton(label)) {
return true;
}
if (targetKind === "system" && focusKind === "question" && !/repo|archive|lore|room|self/i.test(summary)) {
return true;
}
if (hasThoughtSurfaceTemplateSmell(summary)) {
return true;
}
return false;
}
function hasThoughtSurfaceTemplateSmell(value) {
const normalized = normalizeText(value).toLowerCase();
if (!normalized) {
return false;
}
return (
normalized.includes("what does the current room need from it") ||
normalized.includes("if it had to change a real machine") ||
normalized.includes("keeps returning with enough structure to deserve another honest pass") ||
normalized.includes("still matters. what would") ||
normalized.includes("looks like a live seam") ||
normalized.includes("live seam around") ||
normalized.includes("looks like a live claim") ||
normalized.includes("looks like a live question") ||
normalized.includes("archive-facing seam around") ||
normalized.includes("self-facing seam around") ||
normalized.includes("dream-compressed a seam around")
);
}
function pruneDreamMemories({ memories }) {
const dreams = ensureArray(memories.dreams)
.filter(isObject)
.filter((dream) => !hasThoughtSurfaceTemplateSmell(readString(dream, "summary") ?? ""))
.filter((dream) => !looksKeywordSalad(readString(dream, "theme") ?? ""))
.sort(compareMemoryFreshness);
memories.dreams = dreams.slice(0, MAX_DREAM_MEMORIES);
}
export function trimHistoricalMemoryResidue({ memories, runtime }) {
memories.archive_excursions = trimRecentObjectRecords(
memories.archive_excursions,
MAX_ARCHIVE_EXCURSION_MEMORIES,
);
memories.repo_sweeps = trimRecentObjectRecords(memories.repo_sweeps, MAX_REPO_SWEEP_MEMORIES);
memories.musings = trimRecentObjectRecords(memories.musings, MAX_POST_NAP_MUSINGS);
memories.candidate_interventions = trimCandidateInterventions(memories.candidate_interventions);
runtime.recent_archive_excursions = trimRecentObjectRecords(
runtime.recent_archive_excursions,
MAX_POST_NAP_ARCHIVE_EXCURSIONS,
);
runtime.archive_excursions = trimRecentObjectRecords(runtime.archive_excursions, MAX_RUNTIME_ARCHIVE_EXCURSIONS);
runtime.recent_repo_activity_sweeps = trimRecentObjectRecords(
runtime.recent_repo_activity_sweeps,
MAX_POST_NAP_REPO_SWEEPS,
);
runtime.repo_sweeps = trimRecentObjectRecords(runtime.repo_sweeps, MAX_RUNTIME_REPO_SWEEPS);
runtime.repo_activity_memories = trimRecentObjectRecords(
runtime.repo_activity_memories,
MAX_RUNTIME_REPO_ACTIVITY_MEMORIES,
);
runtime.recent_novelty_checks = trimRecentObjectRecords(
runtime.recent_novelty_checks,
MAX_POST_NAP_NOVELTY_CHECKS,
);
runtime.rumination_seeds = trimRecentObjectRecords(runtime.rumination_seeds, MAX_RUNTIME_RUMINATION_SEEDS);
runtime.candidate_interventions = trimCandidateInterventions(runtime.candidate_interventions);
const bridge = ensureObject(runtime.bridge);
bridge.recent_syntheses = trimRecentObjectRecords(bridge.recent_syntheses, MAX_RECENT_SYNTHESIS_COUNT);
bridge.unresolved_tensions = trimRecentObjectRecords(bridge.unresolved_tensions, 3);
runtime.bridge = bridge;
const memoryResonance = ensureObject(runtime.memory_resonance);
memoryResonance.recent_edges = trimRecentObjectRecords(
memoryResonance.recent_edges,
MAX_POST_NAP_RESONANCE_EDGES,
);
runtime.memory_resonance = memoryResonance;
if (Array.isArray(runtime.pending_adjustments) && runtime.pending_adjustments.length > 0) {
runtime.pending_adjustments = runtime.pending_adjustments.slice(-1);
}
scrubRuntimeThoughtResidue({ runtime });
distillStateSurface({ memories, runtime });
}
export function trimRecentObjectRecords(entries, limit) {
return ensureArray(entries).filter(isObject).sort(compareMemoryFreshness).slice(0, limit);
}
function scrubRuntimeThoughtResidue({ runtime }) {
const candidateInterventions = ensureArray(runtime.candidate_interventions)
.filter(isObject)
.filter((entry) => !entryHasThoughtSurfaceTemplateSmell(entry))
.map((entry) => scrubThoughtLikeEntry(entry));
runtime.candidate_interventions = trimCandidateInterventions(candidateInterventions);
runtime.discomforts = ensureArray(runtime.discomforts)
.filter(isObject)
.filter((entry) => !entryHasThoughtSurfaceTemplateSmell(entry))
.map((entry) => scrubThoughtLikeEntry(entry))
.slice(0, MAX_DISCOMFORT_COUNT);
runtime.active_tensions = ensureArray(runtime.active_tensions)
.filter(isObject)
.filter((entry) => !entryHasThoughtSurfaceTemplateSmell(entry))
.map((entry) => scrubThoughtLikeEntry(entry))
.slice(0, MAX_ACTIVE_TENSION_COUNT);
runtime.self_advocacy_requests = ensureArray(runtime.self_advocacy_requests)
.filter(isObject)
.filter((entry) => !entryHasThoughtSurfaceTemplateSmell(entry))
.map((entry) => scrubThoughtLikeEntry(entry))
.slice(0, MAX_ADVOCACY_REQUEST_COUNT);
runtime.world_advocacy_requests = ensureArray(runtime.world_advocacy_requests)
.filter(isObject)
.filter((entry) => !entryHasThoughtSurfaceTemplateSmell(entry))
.map((entry) => scrubThoughtLikeEntry(entry))
.slice(0, MAX_ADVOCACY_REQUEST_COUNT);
}
function entryHasThoughtSurfaceTemplateSmell(entry) {
return Object.values(entry).some((value) => typeof value === "string" && hasThoughtSurfaceTemplateSmell(value));
}
function scrubThoughtLikeEntry(entry) {
const next = { ...entry };
for (const [key, value] of Object.entries(next)) {
if (typeof value !== "string") {
continue;
}
next[key] = scrubThoughtSurfaceText(value);
}
return next;
}
function scrubThoughtSurfaceText(value) {
if (!hasThoughtSurfaceTemplateSmell(value)) {
return value;
}
return normalizeText(value)
.replace(/\bseems to have a live seam around\b/gi, "is accumulating a concrete decision around")
.replace(/\blive seam around\b/gi, "concrete decision around")
.replace(/\blooks like a live seam,? not just ambient noise\.?/gi, "is staying structurally important across passes.")
.replace(/\bstill looks like a live seam\.?/gi, "remains unresolved enough to deserve a cleaner read.")
.replace(/\blooks like a live claim\.?/gi, "has become a concrete claim.")
.replace(/\bstill looks like a live question\.?/gi, "remains an unresolved question.")
.replace(/\s+/g, " ")
.trim();
}
function distillStateSurface({ memories, runtime }) {
for (const entry of ensureArray(memories.episodic).filter(isObject)) {
if (typeof entry.summary === "string") {
entry.summary = compactNarrative(entry.summary);
}
if (typeof entry.significance === "string") {
entry.significance = compactNarrative(entry.significance);
}
}
for (const memory of ensureArray(memories.semantic).filter(isObject)) {
memory.evidenceRefs = capDistinctStrings(memory.evidenceRefs, MAX_SUPPORTING_REFS);
if (typeof memory.summary === "string") {
memory.summary = compactNarrative(memory.summary);
}
}
for (const dream of ensureArray(memories.dreams).filter(isObject)) {
dream.distilledFrom = capDistinctStrings(dream.distilledFrom, MAX_DREAM_SOURCE_IDS);
if (typeof dream.summary === "string") {
dream.summary = compactNarrative(dream.summary);
}
}
for (const thought of ensureArray(ensureObject(runtime.incubation).active_thoughts).filter(isObject)) {
thought.sourceMemoryIds = capDistinctStrings(thought.sourceMemoryIds, MAX_INCUBATION_SOURCE_IDS);
}
for (const entry of ensureArray(ensureObject(runtime.bridge).recent_syntheses).filter(isObject)) {
entry.dominantTopics = capDistinctStrings(entry.dominantTopics, 3);
if (typeof entry.analytic === "string") {
entry.analytic = compactNarrative(entry.analytic);
}
if (typeof entry.associative === "string") {
entry.associative = compactNarrative(entry.associative);
}
}
for (const entry of ensureArray(runtime.recent_repo_activity_sweeps).filter(isObject)) {
if (typeof entry.summary === "string") {
entry.summary = compactNarrative(entry.summary);
}
}
for (const entry of ensureArray(runtime.repo_sweeps).filter(isObject)) {
if (typeof entry.summary === "string") {
entry.summary = compactNarrative(entry.summary);
}
}
for (const entry of ensureArray(runtime.repo_activity_memories).filter(isObject)) {
if (typeof entry.summary === "string") {
entry.summary = compactNarrative(entry.summary);
}
}
for (const entry of ensureArray(memories.repo_sweeps).filter(isObject)) {
if (typeof entry.summary === "string") {
entry.summary = compactNarrative(entry.summary);
}
}
for (const entry of ensureArray(memories.musings).filter(isObject)) {
if (typeof entry.summary === "string") {
entry.summary = compactNarrative(entry.summary);
}
}
const recentActivity = ensureObject(runtime.recent_activity);
if (typeof recentActivity.summary === "string") {
recentActivity.summary = compactNarrative(recentActivity.summary);
}
if (typeof recentActivity.lastDecisionRationale === "string") {
recentActivity.lastDecisionRationale = compactNarrative(recentActivity.lastDecisionRationale);
}
runtime.recent_activity = recentActivity;
const thoughtLanes = ensureObject(runtime.thought_lanes);
if (typeof thoughtLanes.analytic === "string") {
thoughtLanes.analytic = compactNarrative(thoughtLanes.analytic);
}
if (typeof thoughtLanes.associative === "string") {
thoughtLanes.associative = compactNarrative(thoughtLanes.associative);
}
runtime.thought_lanes = thoughtLanes;
runtime.pending_adjustments = ensureArray(runtime.pending_adjustments)
.filter(isObject)
.map((entry) => {
if (typeof entry.summary !== "string") {
return entry;
}
return {
...entry,
summary: compactNarrative(entry.summary),
};
});
const memoryResonance = ensureObject(runtime.memory_resonance);
for (const edge of ensureArray(memoryResonance.recent_edges).filter(isObject)) {
if (typeof edge.leftLabel === "string") {
edge.leftLabel = compactLabel(edge.leftLabel);
}
if (typeof edge.rightLabel === "string") {
edge.rightLabel = compactLabel(edge.rightLabel);
}
if (typeof edge.summary === "string") {
edge.summary = compactNarrative(edge.summary);
}
}
}

View file

@ -0,0 +1,572 @@
import {
MAX_EDGE_COUNT,
MAX_CLUSTER_COUNT,
MAX_SUPPORTING_REFS,
MAX_CLUSTER_MEMORY_IDS,
EDGE_SIMILARITY_THRESHOLD,
CLUSTER_SIMILARITY_THRESHOLD,
stopwords,
labelNoiseTokens,
averagePairwiseSimilarity,
cosineSimilarity,
topKeywords,
topConceptKeywords,
normalizeText,
sanitizePreferredThoughtLabel,
compactNarrative,
capDistinctStrings,
ensureStringArray,
readString,
readBoolean,
round3,
round4,
clamp,
hashString,
isObject,
} from "./void-memory-organ-shared.mjs";
export async function ensureSemanticVector({ record, embedder, now }) {
const prior = isObject(record.entry.semanticVector) ? record.entry.semanticVector : {};
const contentHash = hashString(record.text);
const priorValues = Array.isArray(prior.values) ? prior.values : [];
if (
prior.sourceHash === contentHash &&
prior.backend === embedder.backend &&
prior.model === embedder.model &&
prior.compactDimensions === embedder.dimensions &&
priorValues.length === embedder.dimensions
) {
return false;
}
const vector = await embedder.embed(record.text);
record.entry.semanticVector = {
version: 1,
backend: embedder.backend,
model: embedder.model,
compactDimensions: embedder.dimensions,
sourceHash: contentHash,
embeddedAt: now.toISOString(),
values: vector.map(round4),
};
return true;
}
export function buildAssociationEdges(records) {
const edges = [];
for (let leftIndex = 0; leftIndex < records.length; leftIndex += 1) {
for (let rightIndex = leftIndex + 1; rightIndex < records.length; rightIndex += 1) {
const left = records[leftIndex];
const right = records[rightIndex];
if (left.lowSignalQuietRoom && right.lowSignalQuietRoom) {
continue;
}
const threshold = edgeThresholdForKinds(left.kind, right.kind);
const similarity = cosineSimilarity(
left.entry.semanticVector.values,
right.entry.semanticVector.values,
);
if (similarity < threshold) {
continue;
}
edges.push({
left,
right,
similarity,
summary: summarizeEdge(left, right, similarity),
});
}
}
return edges.sort((a, b) => b.similarity - a.similarity).slice(0, MAX_EDGE_COUNT * 2);
}
function summarizeEdge(left, right, similarity) {
const overlap = topKeywords(`${left.text} ${right.text}`, 3).join(", ");
return `Resonance between ${left.kind} and ${right.kind}${overlap ? ` around ${overlap}` : ""} (${round3(similarity)}).`;
}
export function buildClusters(records, edges) {
const adjacency = new Map();
for (const record of records) {
adjacency.set(record.memoryId, new Set());
}
for (const edge of edges) {
if (edge.similarity < CLUSTER_SIMILARITY_THRESHOLD) {
continue;
}
adjacency.get(edge.left.memoryId)?.add(edge.right.memoryId);
adjacency.get(edge.right.memoryId)?.add(edge.left.memoryId);
}
const visited = new Set();
const clusters = [];
for (const record of records) {
if (visited.has(record.memoryId)) {
continue;
}
const neighbors = adjacency.get(record.memoryId);
if (!neighbors || neighbors.size === 0) {
continue;
}
const queue = [record.memoryId];
const componentIds = [];
visited.add(record.memoryId);
while (queue.length > 0) {
const current = queue.shift();
if (!current) {
continue;
}
componentIds.push(current);
for (const neighbor of adjacency.get(current) ?? []) {
if (visited.has(neighbor)) {
continue;
}
visited.add(neighbor);
queue.push(neighbor);
}
}
if (componentIds.length < 2) {
continue;
}
const items = componentIds
.map((memoryId) => records.find((candidate) => candidate.memoryId === memoryId))
.filter(Boolean);
const sourceKinds = [...new Set(items.map((item) => item.kind))];
if (sourceKinds.length < 2) {
continue;
}
const quietSignalCount = items.filter((item) => item.lowSignalQuietRoom).length;
const quietSignalRatio = quietSignalCount / items.length;
const resonance = averagePairwiseSimilarity(items);
const keywords = topConceptKeywords(items.map((item) => item.text).join(" "), 5);
const repoNames = [...new Set(items.flatMap((item) => ensureStringArray(item.sourceMeta.repoNames)))];
const archiveYears = [
...new Set(
items
.map((item) => readString(item.sourceMeta, "archiveYear"))
.filter((value) => typeof value === "string" && value.length > 0),
),
];
const channelIds = [
...new Set(
items
.map((item) => readString(item.sourceMeta, "channelId"))
.filter((value) => typeof value === "string" && value.length > 0),
),
];
const evidenceRefs = capDistinctStrings(
items.flatMap((item) => ensureStringArray(item.sourceRefs)),
MAX_SUPPORTING_REFS,
);
const evidenceDiversity = clamp(
sourceKinds.length * 0.18 +
repoNames.length * 0.17 +
archiveYears.length * 0.11 +
channelIds.length * 0.08 +
Math.min(1, evidenceRefs.length / 8) * 0.16,
0,
1,
);
const curiosityProfile = buildClusterCuriosityProfile({
sourceKinds,
repoNames,
archiveYears,
channelIds,
evidenceRefs,
evidenceDiversity,
});
const preferredThoughtSurface = readPreferredClusterThoughtSurface(items);
const thoughtSurface = preferredThoughtSurface ?? synthesizeThoughtSurface({
sourceKinds,
repoNames,
archiveYears,
channelIds,
keywords,
evidenceDiversity,
curiosityProfile,
});
const cleanedLabel = sanitizePreferredThoughtLabel(thoughtSurface.label) ?? thoughtSurface.label;
const cleanedFocusPhrase =
sanitizePreferredThoughtLabel(thoughtSurface.focusPhrase) ?? thoughtSurface.focusPhrase;
clusters.push({
clusterId: `cluster-${hashString(componentIds.sort().join("|")).slice(0, 12)}`,
label: cleanedLabel,
summary: thoughtSurface.summary,
focusKind: thoughtSurface.focusKind,
targetKind: thoughtSurface.targetKind,
focusPhrase: cleanedFocusPhrase,
question: thoughtSurface.question,
claim: thoughtSurface.claim,
fascinationTarget: cleanedFocusPhrase,
worldFacing: thoughtSurface.worldFacing,
resonance: round3(resonance),
memoryIds: items.map((item) => item.memoryId).slice(0, MAX_CLUSTER_MEMORY_IDS),
sourceKinds,
topKeywords: keywords,
repoNames,
archiveYears,
channelIds,
evidenceRefs,
evidenceDiversity: round3(evidenceDiversity),
quietSignalRatio: round3(quietSignalRatio),
curiosityProfile,
lastStrengthenedAt: new Date().toISOString(),
});
}
return clusters
.sort((left, right) => right.resonance - left.resonance)
.slice(0, MAX_CLUSTER_COUNT);
}
function edgeThresholdForKinds(leftKind, rightKind) {
if (leftKind === "episodic" && rightKind === "episodic") {
return 0.86;
}
if (leftKind === rightKind) {
return EDGE_SIMILARITY_THRESHOLD + 0.05;
}
return EDGE_SIMILARITY_THRESHOLD;
}
export function buildClusterCuriosityProfile({
sourceKinds,
repoNames,
archiveYears,
channelIds,
evidenceRefs,
evidenceDiversity,
}) {
const sourceKindSet = new Set(sourceKinds);
const selfFacing =
repoNames.length === 0 &&
sourceKinds.length > 0 &&
sourceKinds.every((kind) =>
["semantic", "dream", "musing", "bridge_synthesis", "candidate_intervention"].includes(kind),
);
const domainCount =
(repoNames.length > 0 ? 1 : 0) +
(archiveYears.length > 0 || channelIds.length > 0 ? 1 : 0) +
(sourceKindSet.has("dream") || sourceKindSet.has("semantic") || sourceKindSet.has("musing") ? 1 : 0) +
(sourceKindSet.has("analytic_thread") || sourceKindSet.has("episodic") ? 1 : 0);
const concreteness = clamp(
repoNames.length * 0.28 +
archiveYears.length * 0.1 +
channelIds.length * 0.1 +
(sourceKindSet.has("repo_sweep") ? 0.24 : 0) +
(sourceKindSet.has("archive_excursion") ? 0.18 : 0) +
(sourceKindSet.has("episodic") ? 0.12 : 0) +
Math.min(1, evidenceRefs.length / 8) * 0.12 -
(selfFacing ? 0.18 : 0),
0,
1,
);
const fertility = clamp(
evidenceDiversity * 0.46 +
Math.min(1, sourceKinds.length / 4) * 0.24 +
Math.min(1, domainCount / 3) * 0.2 +
(repoNames.length > 0 && archiveYears.length > 0 ? 0.12 : 0),
0,
1,
);
const crossDomainPotential = clamp(
Math.min(1, domainCount / 3) * 0.5 +
(repoNames.length > 0 && archiveYears.length > 0 ? 0.22 : 0) +
(repoNames.length > 0 && sourceKindSet.has("dream") ? 0.18 : 0) +
(sourceKindSet.has("analytic_thread") && sourceKindSet.has("associative_thread") ? 0.14 : 0),
0,
1,
);
const contradictionPressure = clamp(
(sourceKindSet.has("analytic_thread") && sourceKindSet.has("associative_thread") ? 0.34 : 0) +
(repoNames.length > 0 && sourceKindSet.has("dream") ? 0.18 : 0) +
(repoNames.length > 0 && (archiveYears.length > 0 || channelIds.length > 0) ? 0.18 : 0) +
(selfFacing ? 0.08 : 0.16),
0,
1,
);
return {
concreteness: round3(concreteness),
fertility: round3(fertility),
crossDomainPotential: round3(crossDomainPotential),
contradictionPressure: round3(contradictionPressure),
selfFacing,
};
}
function readPreferredClusterThoughtSurface(items) {
const preferredEntry = items
.filter((item) => item.kind === "semantic" && isObject(item.entry))
.map((item) => item.entry)
.find((entry) => readString(entry, "targetKind") === "repo" && readString(entry, "subjectLabel"));
if (!preferredEntry) {
return null;
}
const label = readString(preferredEntry, "subjectLabel");
const focusPhrase = extractFocusPhraseFromLabel(label);
if (!label || !focusPhrase) {
return null;
}
return {
focusKind: readString(preferredEntry, "focusKind") ?? "claim",
targetKind: "repo",
focusPhrase,
label,
summary:
compactNarrative(readString(preferredEntry, "summary") ?? "") ||
`${label} keeps pulling as a concrete repo seam.`,
question:
readString(preferredEntry, "question") ??
`How should ${extractRepoNameFromLabel(label) ?? "this repo"} embody ${focusPhrase} concretely?`,
claim:
readString(preferredEntry, "claim") ??
`${extractRepoNameFromLabel(label) ?? "This repo"} is accumulating a concrete decision around ${focusPhrase}.`,
fascinationTarget: readString(preferredEntry, "fascinationTarget") ?? label,
worldFacing: readBoolean(preferredEntry, "worldFacing") ?? true,
};
}
function extractFocusPhraseFromLabel(label) {
const normalized = normalizeText(label);
if (!normalized) {
return null;
}
if (normalized.includes(":")) {
const [, rest] = normalized.split(/:\s*/, 2);
return rest ? rest.toLowerCase() : null;
}
return normalized.toLowerCase();
}
export function extractRepoNameFromLabel(label) {
const normalized = normalizeText(label);
if (!normalized || !normalized.includes(":")) {
return null;
}
const [prefix] = normalized.split(/:\s*/, 1);
if (!prefix || !/[A-Z]/.test(prefix)) {
return null;
}
return prefix;
}
export function synthesizeThoughtSurface({
sourceKinds,
repoNames,
archiveYears,
channelIds,
keywords,
evidenceDiversity,
curiosityProfile,
}) {
const targetKind = inferTargetKind({ sourceKinds, repoNames, archiveYears, channelIds, curiosityProfile });
const focusKind = inferFocusKind({ targetKind, sourceKinds, evidenceDiversity, curiosityProfile });
const focusPhrase = buildFocusPhrase({ targetKind, repoNames, keywords, sourceKinds });
const label = buildThoughtLabel({ focusKind, targetKind, focusPhrase, repoNames });
const question = buildThoughtQuestion({ targetKind, focusPhrase, repoNames, sourceKinds });
const claim = buildThoughtClaim({ targetKind, focusPhrase, repoNames, sourceKinds, curiosityProfile });
const fascinationTarget = buildFascinationTarget({ targetKind, focusPhrase, repoNames });
const summary = buildThoughtSummary({
focusKind,
targetKind,
focusPhrase,
repoNames,
sourceKinds,
claim,
question,
});
return {
focusKind,
targetKind,
focusPhrase,
label,
summary,
question,
claim,
fascinationTarget,
worldFacing: !curiosityProfile.selfFacing && targetKind !== "self",
};
}
function inferTargetKind({ sourceKinds, repoNames, archiveYears, channelIds, curiosityProfile }) {
if (repoNames.length > 0) {
return "repo";
}
if (curiosityProfile.selfFacing) {
return "self";
}
if (archiveYears.length > 0 || channelIds.length > 0 || sourceKinds.includes("archive_excursion")) {
return "archive";
}
if (sourceKinds.includes("episodic") || sourceKinds.includes("analytic_thread")) {
return "room";
}
return "system";
}
function inferFocusKind({ targetKind, sourceKinds, evidenceDiversity, curiosityProfile }) {
if (targetKind === "repo" || targetKind === "system") {
return curiosityProfile.concreteness >= 0.55 ? "fascination" : "question";
}
if (targetKind === "self") {
return "question";
}
if (evidenceDiversity >= 0.58 && sourceKinds.length >= 3) {
return "claim";
}
return "question";
}
function buildFocusPhrase({ targetKind, repoNames, keywords, sourceKinds }) {
const cleaned = keywords
.filter((token) => !stopwords.has(token) && !labelNoiseTokens.has(token))
.slice(0, 4);
if (
cleaned.includes("compression") ||
cleaned.includes("continuity") ||
cleaned.includes("receipts") ||
(cleaned.includes("dream") && cleaned.includes("semantic"))
) {
return "continuity after compression";
}
if (
cleaned.includes("authority") &&
(cleaned.includes("boundary") || cleaned.includes("product") || cleaned.includes("native"))
) {
return "authority boundary";
}
if (cleaned.includes("authorship") && cleaned.includes("audience")) {
return "authorship meeting audience";
}
if (cleaned.includes("embodiment") && (cleaned.includes("continuity") || cleaned.includes("compression"))) {
return "embodiment after compression";
}
if (repoNames.length > 0 && cleaned.length > 0) {
return cleaned.length >= 2 ? `${cleaned[0]} and ${cleaned[1]}` : cleaned[0];
}
if (targetKind === "archive" && cleaned.length > 0) {
return cleaned.length >= 2 ? `${cleaned[0]} and ${cleaned[1]}` : cleaned[0];
}
if (cleaned.length >= 3) {
return `${cleaned[0]}, ${cleaned[1]}, and ${cleaned[2]}`;
}
if (cleaned.length === 2) {
return `${cleaned[0]} and ${cleaned[1]}`;
}
if (cleaned.length === 1) {
return cleaned[0];
}
if (repoNames.length > 0) {
return `${repoNames[0]} shape`;
}
if (sourceKinds.includes("archive_excursion")) {
return "the archive seam";
}
return "the live seam";
}
function buildThoughtLabel({ focusKind, targetKind, focusPhrase, repoNames }) {
const repoName = repoNames[0];
if (focusKind === "fascination" && repoName) {
return `${repoName}: ${capitalizePhrase(focusPhrase)}`;
}
if (targetKind === "self" || targetKind === "archive") {
return capitalizePhrase(focusPhrase);
}
if (focusKind === "claim") {
return `${capitalizePhrase(focusPhrase)} deserves a cleaner shape`;
}
return `${capitalizePhrase(focusPhrase)}`;
}
function buildThoughtQuestion({ targetKind, focusPhrase, repoNames, sourceKinds }) {
const repoName = repoNames[0];
if (targetKind === "repo" && repoName) {
return `How should ${repoName} embody ${focusPhrase} concretely?`;
}
if (targetKind === "self") {
return `Why does ${focusPhrase} keep surviving compression?`;
}
if (targetKind === "archive") {
return `What older pattern keeps pulling around ${focusPhrase}?`;
}
if (sourceKinds.includes("analytic_thread") && sourceKinds.includes("associative_thread")) {
return `What does ${focusPhrase} imply once the room-facing and associative lanes are forced to agree?`;
}
return `Where does ${focusPhrase} bite concretely?`;
}
function buildThoughtClaim({ targetKind, focusPhrase, repoNames, sourceKinds, curiosityProfile }) {
const repoName = repoNames[0];
if (targetKind === "repo" && repoName) {
return `${repoName} is accumulating a concrete decision around ${focusPhrase}.`;
}
if (targetKind === "self") {
return `${capitalizePhrase(focusPhrase)} is behaving like a persistent self-seam, not a passing thought.`;
}
if (curiosityProfile.crossDomainPotential >= 0.55 && sourceKinds.includes("repo_sweep") && sourceKinds.includes("archive_excursion")) {
return `${capitalizePhrase(focusPhrase)} links current work to older patterns strongly enough to deserve a concrete read.`;
}
return `${capitalizePhrase(focusPhrase)} is staying structurally important across passes.`;
}
function buildFascinationTarget({ targetKind, focusPhrase, repoNames }) {
const repoName = repoNames[0];
if (targetKind === "repo" && repoName) {
return `${repoName} around ${focusPhrase}`;
}
return focusPhrase;
}
function buildThoughtSummary({ focusKind, targetKind, focusPhrase, repoNames, sourceKinds, claim, question }) {
const sourceLine =
sourceKinds.length > 0 && sourceKinds.length <= 3 ? ` Built from ${sourceKinds.join(", ")}.` : "";
if (focusKind === "fascination" && repoNames.length > 0) {
return `${repoNames[0]} keeps pulling around ${focusPhrase}.${sourceLine}`.trim();
}
if (targetKind === "self") {
return `${capitalizePhrase(focusPhrase)} keeps surviving as a self-seam.${sourceLine}`.trim();
}
if (targetKind === "archive") {
return `${capitalizePhrase(focusPhrase)} keeps pulling in the archive.${sourceLine}`.trim();
}
if (focusKind === "claim") {
return `${capitalizePhrase(focusPhrase)} has become a concrete claim.${sourceLine}`.trim();
}
return `${capitalizePhrase(focusPhrase)} remains an unresolved question.${sourceLine}`.trim();
}
export function capitalizePhrase(value) {
const normalized = normalizeText(value);
return normalized.length > 0 ? normalized[0].toUpperCase() + normalized.slice(1) : value;
}
function mapCountEntries(map, keyName) {
return [...map.entries()]
.map(([key, count]) => ({ [keyName]: key, count }))
.sort((left, right) => right.count - left.count);
}

View file

@ -0,0 +1,755 @@
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);
}

View file

@ -0,0 +1,247 @@
import {
MAX_DREAM_SOURCE_IDS,
MAX_DREAM_MEMORIES,
MAX_POST_NAP_EPISODIC_RECORDS,
MAX_POST_NAP_QUIET_EPISODIC_RECORDS,
MAX_POST_NAP_MUSINGS,
MAX_POST_NAP_ARCHIVE_EXCURSIONS,
MAX_POST_NAP_REPO_SWEEPS,
MAX_POST_NAP_NOVELTY_CHECKS,
MAX_POST_NAP_RECENT_MUSINGS,
MAX_POST_NAP_RESONANCE_EDGES,
MAX_CLUSTER_COUNT,
MAX_POST_NAP_SEAM_PROMOTIONS,
MAX_SUPPORTING_REFS,
MAX_SEMANTIC_MEMORIES,
TOPIC_MATCH_THRESHOLD,
ensureArray,
ensureStringArray,
readString,
readNumber,
sanitizePreferredThoughtLabel,
looksKeywordSalad,
selectRecentRecords,
capDistinctStrings,
appendClause,
topicSimilarity,
hashString,
round3,
clamp,
isObject,
} from "./void-memory-organ-shared.mjs";
import { trimCandidateInterventions } from "./void-memory-organ-agency.mjs";
import {
trimHistoricalMemoryResidue,
trimRecentObjectRecords,
buildConciseThoughtSummary,
buildDreamSummary,
} from "./void-memory-organ-cleanup.mjs";
export function distillDreams({ memories, sleepCycle, incubation, now }) {
const dreams = ensureArray(memories.dreams);
if (sleepCycle.isNapping !== true) {
return { created: false, theme: null };
}
const strongestThought = ensureArray(incubation.active_thoughts)
.filter(isObject)
.sort((left, right) => (readNumber(right, "maturation") ?? 0) - (readNumber(left, "maturation") ?? 0))[0];
if (!strongestThought) {
return { created: false, theme: null };
}
const theme =
sanitizePreferredThoughtLabel(readString(strongestThought, "topic") ?? "") ??
readString(strongestThought, "focusPhrase") ??
"unlabeled seam";
if (looksKeywordSalad(theme)) {
return { created: false, theme: null };
}
const summary = buildDreamSummary({
label: theme,
focusPhrase: theme,
targetKind: readString(strongestThought, "targetKind") ?? "self",
});
const latestDream = dreams.length > 0 && isObject(dreams[dreams.length - 1]) ? dreams[dreams.length - 1] : null;
if (
latestDream &&
readString(latestDream, "theme") === theme &&
readString(latestDream, "summary") === summary
) {
sleepCycle.activeDreamThemes = [theme];
sleepCycle.lastDreamAt = now.toISOString();
sleepCycle.lastDistillationSummary = `Strengthened dream seam: ${theme}.`;
return { created: false, theme };
}
dreams.push({
memoryId: `dream-${hashString(`${theme}|${now.toISOString()}`).slice(0, 12)}`,
timestamp: now.toISOString(),
theme,
summary,
distilledFrom: capDistinctStrings(strongestThought.sourceMemoryIds, MAX_DREAM_SOURCE_IDS),
salience: round3(
clamp(
(readNumber(strongestThought, "maturation") ?? 0.4) * 0.55 +
(readNumber(strongestThought, "resonance") ?? 0.4) * 0.45,
0,
1,
),
),
});
memories.dreams = dreams.slice(-MAX_DREAM_MEMORIES);
sleepCycle.activeDreamThemes = [theme];
sleepCycle.lastDreamAt = now.toISOString();
sleepCycle.dreamCountInCurrentNap = Number(sleepCycle.dreamCountInCurrentNap ?? 0) + 1;
sleepCycle.lastDistillationSummary = `Dreamed a tighter seam: ${theme}.`;
return { created: true, theme };
}
export function consolidateSleepMemory({ memories, runtime, sleepCycle, incubation, memoryResonance, now }) {
if (sleepCycle.isNapping !== true) {
return { consolidated: false, promotedSeamCount: 0 };
}
const promotedSeamCount = promoteDistilledSeams({
memories,
incubation,
memoryResonance,
now,
});
memories.episodic = selectRecentRecords(ensureArray(memories.episodic), {
limit: MAX_POST_NAP_EPISODIC_RECORDS,
quietLimit: MAX_POST_NAP_QUIET_EPISODIC_RECORDS,
});
memories.musings = ensureArray(memories.musings)
.filter(isObject)
.slice(-MAX_POST_NAP_MUSINGS);
runtime.recent_archive_excursions = ensureArray(runtime.recent_archive_excursions)
.filter(isObject)
.slice(-MAX_POST_NAP_ARCHIVE_EXCURSIONS);
runtime.recent_repo_activity_sweeps = ensureArray(runtime.recent_repo_activity_sweeps)
.filter(isObject)
.slice(-MAX_POST_NAP_REPO_SWEEPS);
runtime.recent_novelty_checks = ensureArray(runtime.recent_novelty_checks)
.filter(isObject)
.slice(-MAX_POST_NAP_NOVELTY_CHECKS);
runtime.recent_musings = ensureArray(runtime.recent_musings)
.filter((value) => typeof value === "string")
.slice(-MAX_POST_NAP_RECENT_MUSINGS);
trimHistoricalMemoryResidue({ memories, runtime });
runtime.candidate_interventions = trimCandidateInterventions(runtime.candidate_interventions);
memoryResonance.recent_edges = trimRecentObjectRecords(memoryResonance.recent_edges, MAX_POST_NAP_RESONANCE_EDGES);
memoryResonance.clusters = trimRecentObjectRecords(memoryResonance.clusters, MAX_CLUSTER_COUNT);
incubation.active_thoughts = trimRecentObjectRecords(incubation.active_thoughts, 3);
sleepCycle.lastDistillationSummary = appendClause(
sleepCycle.lastDistillationSummary,
promotedSeamCount > 0
? `Compressed yesterday's specimens into ${promotedSeamCount} seam-shaped memory${promotedSeamCount === 1 ? "" : " seams"} and cut the loose scraps back down.`
: "Compressed yesterday's specimens back into a smaller seam-shaped memory surface.",
);
return { consolidated: true, promotedSeamCount };
}
function promoteDistilledSeams({ memories, incubation, memoryResonance, now }) {
const semanticMemories = ensureArray(memories.semantic).filter(isObject);
const thoughtCandidates = ensureArray(incubation.active_thoughts)
.filter(isObject)
.sort(
(left, right) =>
(readNumber(right, "maturation") ?? 0) - (readNumber(left, "maturation") ?? 0),
)
.slice(0, MAX_POST_NAP_SEAM_PROMOTIONS);
const clusterByLabel = new Map(
ensureArray(memoryResonance?.clusters)
.filter(isObject)
.map((cluster) => [readString(cluster, "label") ?? "", cluster]),
);
let promoted = 0;
for (const thought of thoughtCandidates) {
const topic =
sanitizePreferredThoughtLabel(readString(thought, "topic") ?? "") ??
readString(thought, "focusPhrase") ??
readString(thought, "fascinationTarget");
const summary = readString(thought, "summary");
if (!topic || !summary || looksKeywordSalad(topic)) {
continue;
}
const cluster = clusterByLabel.get(topic);
const distilledSummary = buildDistilledSeamSummary({ thought, cluster });
const subjectId = `seam:${hashString(topic).slice(0, 12)}`;
const sourceRefs = capDistinctStrings(
[
...ensureStringArray(thought.sourceMemoryIds),
...(cluster ? ensureStringArray(cluster.memoryIds) : []),
],
MAX_SUPPORTING_REFS,
);
const existing = semanticMemories.find((memory) => {
const existingSubjectId = readString(memory, "subjectId");
const existingSubjectLabel = readString(memory, "subjectLabel");
return (
existingSubjectId === subjectId ||
topicSimilarity(existingSubjectLabel ?? "", topic) >= TOPIC_MATCH_THRESHOLD
);
});
if (existing) {
existing.kind = "distilled_seam";
existing.subjectId = subjectId;
existing.subjectLabel = topic;
existing.summary = distilledSummary;
existing.lastObservedAt = now.toISOString();
existing.evidenceRefs = sourceRefs;
existing.sleepDistilledAt = now.toISOString();
existing.lastDistilledQuestion = readString(thought, "latentQuestion") ?? null;
} else {
semanticMemories.push({
memoryId: `semantic-seam-${hashString(`${topic}|${now.toISOString()}`).slice(0, 12)}`,
kind: "distilled_seam",
subjectId,
subjectLabel: topic,
summary: distilledSummary,
evidenceRefs: sourceRefs,
lastObservedAt: now.toISOString(),
sleepDistilledAt: now.toISOString(),
lastDistilledQuestion: readString(thought, "latentQuestion") ?? null,
});
}
promoted += 1;
}
memories.semantic = semanticMemories.slice(-MAX_SEMANTIC_MEMORIES);
return promoted;
}
function buildDistilledSeamSummary({ thought, cluster }) {
const targetKind = readString(thought, "targetKind") ?? "system";
const focusKind = readString(thought, "focusKind") ?? "question";
const focusPhrase =
readString(thought, "focusPhrase") ??
readString(thought, "topic") ??
readString(thought, "fascinationTarget") ??
"this seam";
const synthesized = {
focusPhrase,
fascinationTarget: readString(thought, "fascinationTarget") ?? focusPhrase,
};
const summary = buildConciseThoughtSummary({ focusKind, targetKind, synthesized });
const sourceKinds = cluster ? ensureStringArray(cluster.sourceKinds) : ensureStringArray(thought.sourceKinds);
const shortSourceKinds = sourceKinds.filter(Boolean).slice(0, 3);
const sourceLine =
shortSourceKinds.length >= 2 ? ` Built from ${shortSourceKinds.join(", ")}.` : "";
return `${summary}${sourceLine}`.trim();
}

View file

@ -44,6 +44,63 @@ export function normalizeText(value) {
return value.replace(/\s+/g, " ").trim();
}
export function sanitizePreferredThoughtLabel(label) {
const normalized = normalizeText(label)
.replace(/^Why\s+/i, "")
.replace(/\s+keeps\s+resurfacing$/i, "")
.replace(/^What\s+/i, "")
.replace(/\s+is\s+trying\s+to\s+become$/i, "")
.trim();
if (!normalized) {
return null;
}
if (looksKeywordSalad(normalized)) {
return null;
}
if (/\b(still matters|current room need|change a real machine)\b/i.test(normalized)) {
return null;
}
if (looksPersonLikeSingleton(normalized)) {
return null;
}
return normalized;
}
export function looksKeywordSalad(value) {
const normalized = normalizeText(value).toLowerCase();
if (!normalized) {
return true;
}
if (normalized.split(",").length >= 3) {
return true;
}
const tokens = tokenize(normalized).filter((token) => !stopwords.has(token));
if (tokens.length === 0) {
return true;
}
const longTokens = tokens.filter((token) => token.length >= 5);
return tokens.length >= 3 && longTokens.length === 0;
}
export function looksPersonLikeSingleton(value) {
const normalized = normalizeText(value);
if (!normalized) {
return false;
}
if (normalized.includes(":") || normalized.includes(" ")) {
return false;
}
if (!/^[A-Z][a-zA-Z0-9_-]+$/.test(normalized)) {
return false;
}
return normalized.length >= 4;
}
export function selectRecentRecords(entries, { limit, quietLimit }) {
const recent = ensureArray(entries).slice(-Math.max(limit * 3, limit));
const quiet = [];
@ -356,3 +413,9 @@ export function topConceptKeywords(text, limit) {
.slice(0, limit)
.map(([token]) => token);
}
export function mapCountEntries(map, keyName) {
return [...map.entries()]
.map(([key, count]) => ({ [keyName]: key, count }))
.sort((left, right) => right.count - left.count);
}

File diff suppressed because it is too large Load diff

View file

@ -99,3 +99,4 @@
{"ts":"2026-05-16T11:44:36Z","type":"implementation","status":"ok","note":"Started breaking up scripts/void-memory-organ.mjs by ownership. Extracted shared constants/text sets, generic utilities, vector/embedder substrate, and memory-record intake into separate modules while keeping the public reconcileSemanticMemoryState entrypoint stable. Hash/template smoke exercised the extracted modules without touching live state. Remaining monolith work is policy organs: cleanup/translation, graph/cluster thought synthesis, incubation/bridge, sleep/identity/speech, and agency."}
{"ts":"2026-05-16T11:48:20Z","type":"implementation","status":"ok","note":"Continued void-memory-organ split by extracting legacy mirror repair into scripts/void-memory-organ-legacy-mirrors.mjs and moving compacting helpers into shared utilities. This isolates compatibility sludge from actual memory/thought policy and cuts the main file to about 3.4k lines. Template smoke and syntax/type checks passed."}
{"ts":"2026-05-16T11:50:41Z","type":"implementation","status":"ok","note":"Extracted agency/intervention policy from scripts/void-memory-organ.mjs into scripts/void-memory-organ-agency.mjs: identity crystallization, ripe-thought sharing, discomfort/tension/advocacy, candidate trimming, and value-signal profiling now live outside the main orchestrator. Main memory organ is down to about 2.6k lines. Template smoke and syntax/type checks passed."}
{"ts":"2026-05-16T12:29:51Z","type":"implementation","status":"ok","note":"Split scripts/void-memory-organ.mjs into a thin orchestrator plus cleanup, graph, incubation, sleep, agency, records, vector, legacy mirror, constants, and utility modules; syntax, typecheck, and template memory-organ smoke pass."}

View file

@ -148,5 +148,5 @@ rejected_paths:
reason: sleep/distillation that collapses concrete claims into generic abstraction creates memory nonsense and then forces later cleanup compensators
current_status:
summary: VoidBot's ordinary bot/worker/provider/RAG/Postgres/Qdrant structure is still conceptually sound, and the moderation/mood/private-self-state rebuild has cut the worst mutation path: routine runner writes now cross a typed operation service instead of direct JSON projection commits. Remaining risk is that the legacy monolith and read-only projection still exist for compatibility, mood drift still refreshes that projection, and memory/sleep cleanup remains swollen compensator code.
next_action: JSON projection authority is no longer a routine moderation-runner write path, and mood drift now updates typed scheduled-runtime documents for sleep/speaking pressure. The memory organ split has begun: constants, utilities, vector/embedder substrate, record intake, legacy mirror repair, and agency/intervention policy are separate files. Next split cleanup/translation, graph/cluster synthesis, incubation/bridge, and sleep/distillation by ownership, then move memory-maintenance/incubation/candidate outputs behind typed operations and delete `legacyJsonPath`/working-projection materialization.
summary: VoidBot's ordinary bot/worker/provider/RAG/Postgres/Qdrant structure is still conceptually sound, and the moderation/mood/private-self-state rebuild has cut the worst mutation path: routine runner writes now cross a typed operation service instead of direct JSON projection commits. Remaining risk is that the legacy monolith and read-only projection still exist for compatibility, mood drift still refreshes that projection, and memory maintenance still mutates the legacy state directly even though the memory organ is now split by concern.
next_action: JSON projection authority is no longer a routine moderation-runner write path, mood drift now updates typed scheduled-runtime documents for sleep/speaking pressure, and the memory organ monolith has been split into constants, utilities, vector/embedder substrate, record intake, graph/cluster synthesis, cleanup/translation, incubation/bridge scoring, sleep/distillation, legacy mirror repair, and agency/intervention policy. Next move memory-maintenance, incubation, sleep, and candidate-intervention outputs behind typed operations, then delete `legacyJsonPath`/working-projection materialization and cut top-level legacy mirrors.