Purge semantic memory sludge
This commit is contained in:
parent
ba5b7c392b
commit
03038a2b62
5 changed files with 151 additions and 9 deletions
|
|
@ -63,6 +63,7 @@ Git history and smoke artifacts carry routine proof. `state/evidence.jsonl` shou
|
|||
- The memory organ's thought labels and curiosity budget have been cut away from keyword compost. Resonance clusters and incubating thoughts are now supposed to name actual claims, questions, or fascination targets, and curiosity should favor concrete map-changing terrain over whichever seam already feels warm.
|
||||
- The live moderation state has now had one real translation pass too, not just new code draped over old fossils. Legacy slash-label seam memories and dream themes are rewritten through the new thought synthesizer, obvious duplicates get merged or pruned, and the active queue is rebuilt from the cleaned substrate instead of the old keyword graveyard.
|
||||
- That cleanup is now harsher and less sentimental. Sleep-side consolidation trims semantic memory harder, cuts dream memory down, throws away excess repo/archive/runtime residue, and explicitly keeps the freshest surviving records after sort instead of accidentally preserving the oldest fossils in the file.
|
||||
- The semantic purge also learned to distrust generic doctrine sludge. `recent-preoccupation` and `quiet-room-status` are not durable thought kinds, person-singleton labels do not get to crystallize into doctrine, and boilerplate like `looks like a live seam` is now treated as template smell across both semantic memory and runtime drafts/opinions.
|
||||
- Values and personality activations now have a real autonomy path. `scripts/void-memory-organ.mjs` can turn sustained value-backed pressure into `moderation_runtime.discomforts`, `active_tensions`, `self_advocacy_requests`, and `world_advocacy_requests`, and it can spend those through existing `candidate_interventions` instead of leaving the vectors as decorative weather.
|
||||
- The important distinction is outward too, not just inward. Self-directed tensions can ask for prompt/memory/runtime surgery, while world-directed tensions can turn repo/lore/room opinions into real requests or first-person interventions when the same objection survives more than one pass.
|
||||
- The intended shape now is: quiet runs can follow one seam for several passes, connect repo motion to lore or philosophy if the evidence supports it, and only surface with the juiciest version once the incubation queue says the thought has actually grown teeth.
|
||||
|
|
|
|||
|
|
@ -183,7 +183,7 @@ Within the Postgres path, the implementation is split on purpose now too:
|
|||
Cluster labeling is no longer supposed to be a slash-joined keyword graveyard. The organ now tries to synthesize each cluster into a claim, question, or fascination target with an explicit target kind such as repo, archive, room, self, or system, and uses concreteness/fertility/cross-domain pressure to decide whether the thought deserves more curiosity.
|
||||
That same pass now also computes a value-signal profile from canonical values plus current activations and uses it to decide when a thought has become a real objection instead of a pretty salience spike. Sustained pressure becomes `moderation_runtime.discomforts` and `active_tensions`; if it persists, the organ can emit `self_advocacy_requests` about Void's own wiring or `world_advocacy_requests` about repos, lore, or room dynamics, then queue matching candidate interventions so those opinions have a route to speech.
|
||||
That same organ now also performs historical cleanup on the live memory surface before it re-embeds anything: old slash-label seam memories and dream themes are translated into the new thought surface, obvious duplicates are merged, and stale low-coherence seam fossils get pruned so the active queue is rebuilt from cleaner material instead of inheriting every older representational sin forever.
|
||||
When the sleep cycle says Void is napping, that same organ becomes deliberately lossy: it promotes a few stronger incubating seams into semantic memory, then trims raw episodic receipts, repo sweeps, archive excursions, novelty checks, recent syntheses, resonance edges, and other runtime byproducts back down so the next awake pass has to think from carved seams rather than yesterday's full notebook. That cleanup now also explicitly prefers the freshest surviving records after sort; otherwise the machine just embalms old fossils and calls it memory hygiene.
|
||||
When the sleep cycle says Void is napping, that same organ becomes deliberately lossy: it promotes a few stronger incubating seams into semantic memory, then trims raw episodic receipts, repo sweeps, archive excursions, novelty checks, recent syntheses, resonance edges, and other runtime byproducts back down so the next awake pass has to think from carved seams rather than yesterday's full notebook. That cleanup now also explicitly prefers the freshest surviving records after sort; otherwise the machine just embalms old fossils and calls it memory hygiene. The same purge path now drops bogus semantic kinds such as `recent-preoccupation` and `quiet-room-status`, refuses person-singleton labels as doctrine, and treats `looks like a live seam` boilerplate as state poison rather than a valid thought surface.
|
||||
The same organ can also crystallize an over-chewed private seam into identity: once a thought has survived enough passes to become durable self-doctrine rather than live curiosity, it can be retired from the active queue, copied into `identity.private_notes`, echoed as a canonical value, preserved as one `identity_seam` semantic memory, and queued as a warmer `identity_crystallization` candidate intervention so the room has a chance to hear it. Those intervention records can be marked `mustEventuallyShare`, which ordinary pruning is supposed to preserve until the room gets a decent opening.
|
||||
Ordinary ripe world-facing thoughts now get a similar but lighter bridge out of private state: if a thought is concrete, repo/archive/room/system-facing, still reasonably novel to the room, and ripe enough to speak, the memory organ queues a `ripe_thought_share` candidate intervention so the unattended loop does not have to reinvent the same courage from scratch every pass.
|
||||
The doctrine is also less forgiving of faux-cosmic repo weather now: if two repos only rhyme at slogan altitude, the loop is supposed to keep them separate and lead with concrete particulars instead of forcing one grand law over both.
|
||||
|
|
|
|||
|
|
@ -23,8 +23,8 @@ const MAX_POST_NAP_SEAM_PROMOTIONS = 3;
|
|||
const MAX_POST_NAP_RESONANCE_EDGES = 12;
|
||||
const MAX_ARCHIVE_EXCURSION_MEMORIES = 6;
|
||||
const MAX_REPO_SWEEP_MEMORIES = 8;
|
||||
const MAX_SEMANTIC_MEMORIES = 18;
|
||||
const MAX_DREAM_MEMORIES = 6;
|
||||
const MAX_SEMANTIC_MEMORIES = 14;
|
||||
const MAX_DREAM_MEMORIES = 5;
|
||||
const MAX_RUNTIME_REPO_SWEEPS = 4;
|
||||
const MAX_RUNTIME_REPO_ACTIVITY_MEMORIES = 4;
|
||||
const MAX_RUNTIME_ARCHIVE_EXCURSIONS = 4;
|
||||
|
|
@ -367,6 +367,10 @@ function normalizeSemanticMemories({ memories, now }) {
|
|||
const kept = [];
|
||||
|
||||
for (const memory of semanticMemories) {
|
||||
if (shouldDropSemanticMemory(memory)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (readString(memory, "kind") === "identity_seam") {
|
||||
kept.push(memory);
|
||||
continue;
|
||||
|
|
@ -559,6 +563,10 @@ function sanitizePreferredThoughtLabel(label) {
|
|||
return null;
|
||||
}
|
||||
|
||||
if (looksPersonLikeSingleton(normalized)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
|
|
@ -660,9 +668,9 @@ function buildConciseThoughtSummary({ focusKind, targetKind, synthesized }) {
|
|||
return `${focus} keeps surviving compression as a self-seam.`;
|
||||
}
|
||||
if (focusKind === "claim") {
|
||||
return `${focus} looks like a live claim rather than ambient noise.`;
|
||||
return `${focus} has hardened into a concrete claim.`;
|
||||
}
|
||||
return `${focus} still looks like a live seam.`;
|
||||
return `${focus} remains unresolved enough to deserve a cleaner read.`;
|
||||
}
|
||||
|
||||
function buildDreamSummary(normalized) {
|
||||
|
|
@ -738,6 +746,10 @@ function pruneHistoricalSeamMemories({ memories }) {
|
|||
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);
|
||||
|
|
@ -792,16 +804,26 @@ 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;
|
||||
}
|
||||
|
|
@ -837,6 +859,36 @@ function shouldPurgeThoughtLikeMemory({ entry, normalized, preferredLabel }) {
|
|||
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) {
|
||||
|
|
@ -848,6 +900,10 @@ function hasThoughtSurfaceTemplateSmell(value) {
|
|||
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")
|
||||
|
|
@ -870,6 +926,20 @@ function looksKeywordSalad(value) {
|
|||
return tokens.length >= 3 && longTokens.length === 0;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
function pruneDreamMemories({ memories }) {
|
||||
const dreams = ensureArray(memories.dreams)
|
||||
.filter(isObject)
|
||||
|
|
@ -921,12 +991,81 @@ function trimHistoricalMemoryResidue({ memories, runtime }) {
|
|||
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 });
|
||||
}
|
||||
|
||||
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 compareMemoryFreshness(left, right) {
|
||||
const leftTime = parseIsoTimestamp(readString(left, "lastObservedAt") ?? readString(left, "timestamp") ?? "") ?? 0;
|
||||
const rightTime = parseIsoTimestamp(readString(right, "lastObservedAt") ?? readString(right, "timestamp") ?? "") ?? 0;
|
||||
|
|
@ -1997,7 +2136,7 @@ function buildAgencyInterventionDraft({ thought, domain, targetLabel }) {
|
|||
if (domain === "self") {
|
||||
return `I do not like what ${topic} is doing to my own wiring right now. ${opinion} I want that cut cleaner.`;
|
||||
}
|
||||
return `I have an opinion about ${targetLabel}. ${opinion} I think it wants a cleaner decision, not just another quiet pass.`;
|
||||
return `I have an opinion about ${targetLabel}. ${opinion} I think it needs a cleaner decision.`;
|
||||
}
|
||||
|
||||
function findPriorAgencyEntry(entries, candidate) {
|
||||
|
|
@ -3272,7 +3411,7 @@ function buildThoughtClaim({ targetKind, focusPhrase, repoNames, sourceKinds, cu
|
|||
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)} looks like a live seam, not just ambient noise.`;
|
||||
return `${capitalizePhrase(focusPhrase)} is staying structurally important across passes.`;
|
||||
}
|
||||
|
||||
function buildFascinationTarget({ targetKind, focusPhrase, repoNames }) {
|
||||
|
|
@ -3296,9 +3435,9 @@ function buildThoughtSummary({ focusKind, targetKind, focusPhrase, repoNames, so
|
|||
return `${capitalizePhrase(focusPhrase)} keeps pulling in the archive.${sourceLine}`.trim();
|
||||
}
|
||||
if (focusKind === "claim") {
|
||||
return `${capitalizePhrase(focusPhrase)} looks like a live claim.${sourceLine}`.trim();
|
||||
return `${capitalizePhrase(focusPhrase)} has become a concrete claim.${sourceLine}`.trim();
|
||||
}
|
||||
return `${capitalizePhrase(focusPhrase)} still looks like a live question.${sourceLine}`.trim();
|
||||
return `${capitalizePhrase(focusPhrase)} remains an unresolved question.${sourceLine}`.trim();
|
||||
}
|
||||
|
||||
function topConceptKeywords(text, limit) {
|
||||
|
|
|
|||
|
|
@ -83,3 +83,4 @@
|
|||
{"ts":"2026-05-16T04:56:01Z","type":"implementation","status":"ok","note":"The recent repo-weather smell came from prompt shape as much as thought shape. The moderation doctrine now tells Void to lead with concrete particulars from each repo before announcing any shared law, and to keep reports separate when the convergence only exists at slogan altitude instead of pretending every pair of commits wants to become one philosophy lecture."}
|
||||
{"ts":"2026-05-16T05:24:00Z","type":"implementation","status":"ok","note":"Void's values and activations now have an operational will-path instead of living as ornamental schema. The memory organ computes a value-signal profile, turns sustained pressure into `discomforts` and `active_tensions`, graduates persistent self-facing objections into `self_advocacy_requests`, graduates persistent repo/lore/room objections into `world_advocacy_requests`, and feeds those through the existing candidate-intervention queue so opinions can actually ask for change."}
|
||||
{"ts":"2026-05-16T05:29:00Z","type":"implementation","status":"ok","note":"The real moderation-memory cleanup bug was twofold: templated thought surfaces were leaving slogan-slop behind, and several trim paths sorted newest-first then sliced from the end, preserving the oldest fossils. The memory organ now translates legacy seam/dream residue into the live ontology, rejects the worst template smells, trims repo/archive/runtime residue aggressively during ordinary normalization and naps, and keeps the freshest surviving records instead of embalming yesterday's carcasses."}
|
||||
{"ts":"2026-05-16T05:46:00Z","type":"implementation","status":"ok","note":"A second cleanup pass showed the memory organ was still preserving generic `system/question` fossils and runtime drafts with phrases like `looks like a live seam`, including junk such as `Metacrat still looks like a live seam.` The organ now treats that boilerplate as template smell, drops `recent-preoccupation` and `quiet-room-status` semantic memories, rejects person-singleton labels as doctrine, and rewrites stale runtime drafts/opinions during manual or sleep-driven reconciliation so naps stop regenerating the same nonsense."}
|
||||
|
|
|
|||
|
|
@ -123,6 +123,7 @@ landed_baseline:
|
|||
- A second scheduled task now runs the plain Node mood-drift script every 5 minutes, updating the shared self-state's activations plus speaking-bias meter, damping expression pressure based on actual recent speech receipts, and maintaining the four-hour nap clock that the moderation loop and direct reply prompts both consume.
|
||||
- That mood-drift organ now also reconciles compact semantic vectors for persistent memories and runtime seeds, maintains a small resonance graph plus incubation queue in the shared self-state, and leaves stronger dream residue during nap windows instead of only nudging activations.
|
||||
- Memory cleanup now has to be meaningfully destructive. Sleep-side consolidation should translate old thought fossils into the live claim/question/fascination surface, drop templated slogan-slop, trim repo/archive/runtime residue down to a small recent evidence surface, and keep the freshest surviving records instead of accidentally embalming the oldest ones after a sort.
|
||||
- Durably weird semantic kinds are guilty until proven useful. `recent-preoccupation`, `quiet-room-status`, person-singleton labels, and `live seam` boilerplate are state poison, not doctrine, and the memory organ should cut them during ordinary cleanup rather than preserving them for another nap to admire.
|
||||
|
||||
rejected_paths:
|
||||
- name: transcript_only_continuity
|
||||
|
|
|
|||
Loading…
Reference in a new issue