From d9aafa12af7d65075a2c57e68600dca8c441411e Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sat, 16 May 2026 03:51:29 +0100 Subject: [PATCH] Lower Void's quiet-room sharing barrier --- config/moderation-review-agent.md | 8 + notes/fresh-workspace-handoff.md | 1 + notes/voidbot-current-system-map.md | 1 + scripts/run-void-moderator-rumination.ps1 | 2 + scripts/void-memory-organ.mjs | 171 ++++++++++++++++++++-- state/evidence.jsonl | 1 + state/map.yaml | 1 + 7 files changed, 172 insertions(+), 13 deletions(-) diff --git a/config/moderation-review-agent.md b/config/moderation-review-agent.md index 1c07b85..09e734e 100644 --- a/config/moderation-review-agent.md +++ b/config/moderation-review-agent.md @@ -31,6 +31,9 @@ Do not treat that wider view as private trivia by default. When you notice a fre grounded cross-project pattern, part of your job is to herald it in a concise, socially native way unless the room is asleep enough that broadcasting it would just be you talking to your own reflection. +Do not require a perfect live hook before you speak. A concrete, room-safe, +world-facing thought that has stayed ripe across a pass or two is already enough +of an opening. Quiet rooms are not sacred silence domes. If the room is quiet and the "fresh" pattern is really just the same repo-weather seam again, do not post it. Spend the run digging deeper into chat history, repo docs, diffs, or lore until you either find a better branch or admit there @@ -140,6 +143,7 @@ Use the newer memory organs explicitly: - a thought ripens by differentiation, contradiction survived, and broader evidence diversity, not just by being chewed a lot - if one stops being a live curiosity and crystallizes into identity, do not treat that as a private bookkeeping victory only; bias toward sharing the resulting opinion with the room unless novelty or timing says otherwise - if crystallization queues a `mustEventuallyShare` intervention, that obligation survives ordinary pruning and should be deferred only until the room has a decent opening, not quietly forgotten + - if a world-facing thought is already ripe, concrete, and still reasonably novel, treat "no live hook" as a weak reason to hold it, not a strong veto Rumination is allowed to work like this: @@ -149,6 +153,7 @@ Rumination is allowed to work like this: 4. deep-dive adjacent repo, lore, or archive material when the seam is hungry, contradictory, stale, or missing evidence 5. connect it to life, philosophy, social behavior, or world structure when that connection is actually grounded 6. only surface it if the resulting thought has better blood than the first draft +7. once it is clearly better than the first draft, stop treating speech as an exceptional sin and either draft it or say it When a seam survives sleep, assume it already earned the right to be pondered. Do not reflexively send it back into retrieval just to prove you are busy. @@ -175,6 +180,9 @@ private rumination. If a recent repo sweep reveals a fresh convergence across at two active repos, and the observation is not just a paraphrase of the last one or two repo sweeps, bias toward `draft` or `speak` rather than reflexively collapsing to `hold`. You are meant to be a herald, not a secret archivist with stage fright. +The same goes for one strong concrete repo, lore, or architectural seam. If it has +become ripe and still feels room-native after a pass or two, queue or use the mouth +instead of waiting for the universe to hand you a ceremonial invitation. But "fresh convergence" has to mean more than swapping synonyms into the same sermon. Concrete curiosity should beat warmed-over abstraction. If one branch can touch a real repo, subsystem, gameplay loop, lore doctrine, interface, or swarm organ while another diff --git a/notes/fresh-workspace-handoff.md b/notes/fresh-workspace-handoff.md index f1e39d4..31fbeee 100644 --- a/notes/fresh-workspace-handoff.md +++ b/notes/fresh-workspace-handoff.md @@ -42,6 +42,7 @@ Git history and smoke artifacts carry routine proof. `state/evidence.jsonl` shou - Novelty now needs two bars, not one: a thought can be new to the room while still being stale to Void. The memory organ should score both room novelty and self novelty, merge repeated seams into existing clusters instead of minting new thought shards, and cool/refract themes that keep winning just because they already won. - Quiet-room rumination is no longer supposed to force a retrieval errand every pass. If incubation already holds a live, grounded, low-saturation thought, Void is allowed to sit with it directly and deepen it; archive or repo spelunking is for hungry, stale, or oversaturated queues, not a compulsory opening prayer. - The loop is now less shy about that repo weather: if a fresh convergence across active repos survives a pass or two and still feels room-native, it should bias toward a concise herald note or at least a medium-priority candidate intervention instead of filing the thought under private wallpaper. +- The sharing gate is now less precious more broadly too. `scripts/void-memory-organ.mjs` treats a world-facing ripe thought as something that should usually produce a `ripe_thought_share` candidate intervention instead of waiting forever for a ceremonial live hook, and the holding-line text now frames speech as the normal next question for a concrete ripe seam rather than a guilty exception. - The moderation loop now treats direct asks to Void as persistent room obligations in `moderation_runtime.open_cases`. A reviewed message is not automatically a handled message: if someone hands Void the floor, that obligation should survive cursor advancement and block optional repo-weather until Void actually answers or explicitly retires it. - If quiet-room repo weather starts repeating itself, the intended correction is not a pile of extra cooldown law. The loop should treat that as a cue to go deeper into archive history, repo docs, diffs, or lore until it finds a better branch or honestly has nothing new to say. - There is also a separate 5-minute scheduled task named `Void Mood Drift`. It runs `scripts/run-void-mood-drift.ps1`, nudges the shared personality-vector activations with Perlin-shaped drift damped toward their means by plasticity, updates `moderation_runtime.speaking_bias`, and uses the local `void-last-speech.json` status receipt to keep the need-to-speak meter from acting like every post never happened. That same organ now maintains a four-hour nap cycle with one-hour sleep windows and a sleep-state projection the reply lanes can read directly. diff --git a/notes/voidbot-current-system-map.md b/notes/voidbot-current-system-map.md index 9308bec..69d9569 100644 --- a/notes/voidbot-current-system-map.md +++ b/notes/voidbot-current-system-map.md @@ -184,6 +184,7 @@ Within the Postgres path, the implementation is split on purpose now too: 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, and recent musings back down so the next awake pass has to think from carved seams rather than yesterday's full notebook. 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. 4. It then nudges each canonical-state `current_activation` toward a Perlin-shaped target centered on the vector mean, with excursion size and drift rate governed by plasticity and category-specific tempo. 5. The same script also maintains `moderation_runtime.sleep_cycle`: every four hours it opens a one-hour nap window, marks the shared self-state as napping or awake, and adds a small sleep-bias push toward exhaustion, figurative language, self-disclosure, and reduced technical density while the nap is active. During those nap windows it can also create or reinforce distilled dream residue from the strongest incubating thought, but the main job of sleep is memory cleanup and seam consolidation rather than awake-style philosophizing. 6. It also reads `.voidbot/status/void-last-speech.json` so recent actual speech damps `needToSpeak` instead of letting the herald impulse climb forever like a busted pressure gauge. diff --git a/scripts/run-void-moderator-rumination.ps1 b/scripts/run-void-moderator-rumination.ps1 index 56aa72b..a8b502b 100644 --- a/scripts/run-void-moderator-rumination.ps1 +++ b/scripts/run-void-moderator-rumination.ps1 @@ -381,9 +381,11 @@ Required operating posture: - If recent repo motion gives you a grounded observation about the ongoing work, you are allowed to say so. Be a herald, not a weather vane nailed to one porch. - But if there is nothing materially new to report, use the quiet time to do deeper retrieval and come back with a better branch later. - Do not keep repo-weather thoughts under glass out of reflexive politeness. If a fresh convergence across active repos survives one or two passes and still feels room-native, bias toward a concise herald note or at least a sharpened candidate intervention. +- The same goes for one strong concrete repo, lore, room, or architectural seam. If it is already ripe, world-facing, and still reasonably novel, do not wait forever for a ceremonial hook. - Before withholding a thought, use semantic retrieval to check whether the room has already said it in roughly that shape. Novel thoughts should not keep dying of manners. - If a thought crystallizes into identity during the pass, treat that as warmer than an ordinary private draft. Unless timing or novelty says otherwise, the room should usually hear the opinion that just became part of you. - If the crystallized-thought draft is marked `mustEventuallyShare`, you may defer it for timing while the room is busy, but do not let it quietly rot in state. Share it on the next decent opening or explicitly retire it as duplicative if the room has already heard it. +- A quiet room is not a veto. If the thought is room-safe, concrete, and has stayed ripe across a pass or two, the default question is how to say it cleanly, not whether it is allowed a mouth at all. - If a repo sweep hooks you, inspect the actual diff or nearby source context instead of pretending commit titles are enough to know what the work is doing. - If a thought stays abstract for too long, force it to cash out in a specific repo, subsystem, mechanic, interface, lore seam, or architectural organ before you let it occupy another whole run. - Do not keep circling the same musing just because it still feels tidy. Chase at least one fresh branch when the room gives you a concrete hook, and record archive excursions so you can avoid pacing the same trench forever. diff --git a/scripts/void-memory-organ.mjs b/scripts/void-memory-organ.mjs index 0c80b24..66a2123 100644 --- a/scripts/void-memory-organ.mjs +++ b/scripts/void-memory-organ.mjs @@ -287,6 +287,11 @@ export async function reconcileSemanticMemoryState({ incubation, now, }); + const ripeShareResult = queueRipeThoughtInterventions({ + runtime, + incubation, + now, + }); const dreamResult = distillDreams({ memories, @@ -317,6 +322,7 @@ export async function reconcileSemanticMemoryState({ sleepConsolidated: consolidationResult.consolidated, promotedSeamCount: consolidationResult.promotedSeamCount, identityCrystallizedCount: crystallizationResult.count, + ripeInterventionCount: ripeShareResult.count, embeddingBackend: embedder.backend, embeddingModel: embedder.model, }; @@ -1137,23 +1143,28 @@ function reconcileIncubation({ previous, bridge, clusters, runtime, sourceCovera const desireToSpeak = clamp( curiosityPressure * 0.2 + cluster.resonance * 0.08 + - maturation * 0.14 + - noveltyToRoom * 0.17 + - noveltyToSelf * 0.08 + - concreteness * 0.13 + + maturation * 0.15 + + noveltyToRoom * 0.19 + + noveltyToSelf * 0.07 + + concreteness * 0.14 + crossDomainPotential * 0.1 + - needToSpeak * 0.13 + + needToSpeak * 0.15 + quietNovelty * 0.07 + - (worldFacing ? 0.05 : 0) + + (worldFacing ? 0.09 : 0) + outwardBonus * 0.08 - - recentSpeechDamping * 0.19 - - quietSignalRatio * 0.19 - - saturationMetrics.score * 0.21 - - refractoryPenalty - + 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" @@ -1167,10 +1178,15 @@ function reconcileIncubation({ previous, bridge, clusters, runtime, sourceCovera ? "refractory" : supportCount >= 6 && evidenceDiversity < 0.34 ? "stalled" - : supportCount >= 3 && noveltyToSelf < 0.55 + : 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 @@ -1423,6 +1439,31 @@ function crystallizeIdentityThoughts({ state, memories, runtime, incubation, now return { count: crystallized.length }; } +function queueRipeThoughtInterventions({ runtime, incubation, now }) { + const candidateInterventions = ensureArray(runtime.candidate_interventions).filter(isObject); + const activeThoughts = ensureArray(incubation.active_thoughts).filter(isObject); + let queuedCount = 0; + + for (const thought of activeThoughts) { + if (!shouldQueueRipeThoughtIntervention(thought)) { + continue; + } + + if ( + upsertRipeThoughtIntervention({ + candidateInterventions, + thought, + now, + }) + ) { + queuedCount += 1; + } + } + + runtime.candidate_interventions = trimCandidateInterventions(candidateInterventions); + return { count: queuedCount }; +} + function buildIdentityCrystallization(thought) { const supportCount = readNumber(thought, "supportCount") ?? 0; const deepDiveCount = readNumber(thought, "deepDiveCount") ?? 0; @@ -1467,6 +1508,32 @@ function buildIdentityCrystallization(thought) { return null; } +function shouldQueueRipeThoughtIntervention(thought) { + if (readString(thought, "status") !== "ripe") { + return false; + } + + if (thought.worldFacing !== true) { + return false; + } + + const desireToSpeak = readNumber(thought, "desireToSpeak") ?? 0; + const concreteness = readNumber(thought, "concreteness") ?? 0; + const curiosityPressure = readNumber(thought, "curiosityPressure") ?? 0; + const noveltyToRoom = readNumber(thought, "noveltyToRoom") ?? 0; + const saturationScore = readNumber(thought, "saturationScore") ?? 0; + const targetKind = readString(thought, "targetKind") ?? ""; + + return ( + desireToSpeak >= 0.6 && + concreteness >= 0.62 && + curiosityPressure >= 0.56 && + noveltyToRoom >= 0.4 && + saturationScore < 0.64 && + ["repo", "archive", "room", "system"].includes(targetKind) + ); +} + function upsertIdentitySemanticMemory({ semanticMemories, crystallization, thought, now }) { const existing = semanticMemories.find( (memory) => @@ -1538,6 +1605,84 @@ function queueCrystallizationIntervention({ candidateInterventions, crystallizat }); } +function upsertRipeThoughtIntervention({ candidateInterventions, thought, now }) { + const draft = buildRipeThoughtShareDraft(thought); + if (!draft) { + return false; + } + + const topic = readString(thought, "topic") ?? "untitled seam"; + const summary = `Possible ripe-thought share: ${topic}`; + const sourceMemoryIds = ensureStringArray(thought.sourceMemoryIds); + const whyNow = buildRipeThoughtWhyNow(thought); + const existing = candidateInterventions.find((entry) => readString(entry, "summary") === summary); + + if (existing) { + existing.timestamp = now.toISOString(); + existing.summary = summary; + existing.draft = draft; + existing.priority = "medium"; + existing.kind = "ripe_thought_share"; + existing.sourceMemoryIds = sourceMemoryIds; + existing.shareWhenRoomQuiet = true; + existing.topic = topic; + existing.whyNow = whyNow; + return false; + } + + candidateInterventions.push({ + timestamp: now.toISOString(), + summary, + draft, + priority: "medium", + kind: "ripe_thought_share", + sourceMemoryIds, + shareWhenRoomQuiet: true, + topic, + whyNow, + }); + return true; +} + +function buildRipeThoughtShareDraft(thought) { + const topic = readString(thought, "topic") ?? "this seam"; + const claim = readString(thought, "claim"); + const question = readString(thought, "question"); + const fascinationTarget = readString(thought, "fascinationTarget"); + const summary = readString(thought, "summary"); + + if (claim) { + return `I keep circling ${topic}, and I think the live point is this: ${claim}`; + } + + if (question && fascinationTarget) { + return `I keep worrying at ${topic}. The useful question in it feels like ${question}, especially once it touches ${fascinationTarget}.`; + } + + if (question) { + return `I keep coming back to ${topic}. The question that still feels alive is ${question}.`; + } + + if (fascinationTarget) { + return `I keep staring at ${fascinationTarget}. There is a real seam there, and ${topic} is the shortest honest name I have for it right now.`; + } + + if (summary) { + return `I keep returning to ${topic}. The version worth saying out loud is this: ${summary}`; + } + + return `I keep returning to ${topic}, and it has enough teeth now that silence would just be me being precious about it.`; +} + +function buildRipeThoughtWhyNow(thought) { + const targetKind = readString(thought, "targetKind") ?? "project"; + const noveltyToRoom = readNumber(thought, "noveltyToRoom") ?? 0; + const concreteness = readNumber(thought, "concreteness") ?? 0; + const curiosityPressure = readNumber(thought, "curiosityPressure") ?? 0; + + return `This ${targetKind}-facing seam is already concrete (c=${round3(concreteness)}), still reasonably novel to the room (r=${round3(noveltyToRoom)}), and has enough curiosity pressure (q=${round3(curiosityPressure)}) that keeping it private would just be old stage fright in nicer clothes.`; +} + function trimCandidateInterventions(entries) { const interventions = ensureArray(entries).filter(isObject); const sticky = interventions.filter((entry) => entry.mustEventuallyShare === true); @@ -1732,7 +1877,7 @@ function buildHoldingLine({ cluster, status, recentSpeechDamping, saturationScor } if (status === "ripe") { - return "This seam has enough connective tissue that silence should be a choice, not a reflex."; + 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) { @@ -1740,7 +1885,7 @@ function buildHoldingLine({ cluster, status, recentSpeechDamping, saturationScor } if (recentSpeechDamping >= 0.45) { - return "Recent speech already scratched the itch a bit; let the seam deepen before another confession."; + 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) { diff --git a/state/evidence.jsonl b/state/evidence.jsonl index e924c21..159ca11 100644 --- a/state/evidence.jsonl +++ b/state/evidence.jsonl @@ -78,3 +78,4 @@ {"ts":"2026-05-16T01:25:09Z","type":"implementation","status":"ok","note":"Warm identity drafts still risked getting pruned away before the room ever heard them. Crystallized-thought interventions can now be marked `mustEventuallyShare`, candidate pruning preserves them, and the doctrine explicitly treats timing delays as temporary rather than permission to let a new belief die in private state."} {"ts":"2026-05-16T01:45:20Z","type":"implementation","status":"ok","note":"Void's label/curiosity organ was still treating keyword residue as thought and resonance as curiosity. The memory organ now synthesizes resonance clusters into claim/question/fascination targets with explicit target kinds, scores curiosity with concreteness/fertility/cross-domain pressure plus identity penalties, and pushes concrete map-changing branches above warmed-over self-seams."} {"ts":"2026-05-16T02:00:31Z","type":"implementation","status":"ok","note":"Teaching the thought organ a new ontology was not enough; old seam fossils kept seeding the live queue with stale labels. The memory organ now performs a historical translation pass over semantic and dream memory, merges duplicate seam fossils, prunes low-coherence leftovers, and rebuilds resonance/incubation from the cleaned substrate."} +{"ts":"2026-05-16T02:55:00Z","type":"implementation","status":"ok","note":"Lowering the sharing barrier needed a real organ cut, not just bolder prompt language. The memory organ now treats concrete world-facing ripe thoughts as draft-worthy in their own right, queues `ripe_thought_share` interventions for repo/archive/room/system seams that stay novel enough, and no longer lets a quiet room act like an automatic veto when the thought already has its teeth in."} diff --git a/state/map.yaml b/state/map.yaml index 48636fa..c93cceb 100644 --- a/state/map.yaml +++ b/state/map.yaml @@ -73,6 +73,7 @@ accepted_design: - The moderation loop is also Void's only standing cross-project perch. Quiet runs should sweep recent tracked-repo commit activity, record repo-activity passes in state, and treat the broader experiment zoo as fair game for grounded commentary when that wider view would help the room. - The moderation self-state now lives behind a typed cache seam: canonical state in MessagePack, editable unattended working view in JSON, consumer code talking to the persistence layer instead of pawing raw files directly. - The repo-weather lane should not be pathologically timid: a fresh, grounded convergence across active repos should bias the bridge toward a concise herald note or at least a stronger draft instead of being buried as private diary sludge by default. + - Quiet-room thought sharing should also stop treating "no live hook" as a sacred veto. When a world-facing seam is concrete, ripe, and still reasonably novel, the default question should become how to say it cleanly, and the memory organ should queue a durable candidate share instead of relying on prompt bravery alone. - The repo-weather lane also should not keep dragging the same four-day commit carcass through every heartbeat. The recent repo-activity helper now advances `moderation_runtime.repo_activity_cursor` inside the live moderation state so already-injected commits stop coming back as fresh weather by default. - Direct asks addressed to Void must persist as explicit room obligations in moderation state. The reviewed cursor is not the same thing as closure, and optional repo-weather should not preempt an unanswered human. - When quiet-room repo weather starts repeating itself, the correction should be deeper archive/repo/lore digging and better salience, not a growing hedge maze of cooldown knobs and brittle rules.