diff --git a/packages/core/src/void-self-state-projection.ts b/packages/core/src/void-self-state-projection.ts index b40d099..57b4152 100644 --- a/packages/core/src/void-self-state-projection.ts +++ b/packages/core/src/void-self-state-projection.ts @@ -146,17 +146,17 @@ export function renderVoidSelfStateSummary( renderTypedSleepCycleSummary(state.scheduledRuntime, state.thoughtMemory), renderTypedSpeakingPressureSummary(state.scheduledRuntime), openCases.length > 0 - ? ["- Outstanding room obligations:", ...openCases].join("\n") - : "- Outstanding room obligations: none recorded.", + ? ["- What Void owes the room:", ...openCases].join("\n") + : "- What Void owes the room: nothing unresolved.", memories.length > 0 - ? ["- Typed memory:", ...memories].join("\n") - : "- Typed memory: none recorded.", + ? ["- What Void remembers:", ...memories].join("\n") + : "- What Void remembers: nothing durable yet.", incubation.length > 0 - ? ["- Incubating threads:", ...incubation].join("\n") - : "- Incubating threads: none recorded.", + ? ["- Thoughts still moving:", ...incubation].join("\n") + : "- Thoughts still moving: none.", interventions.length > 0 - ? ["- Candidate interventions:", ...interventions].join("\n") - : "- Candidate interventions: none recorded.", + ? ["- Things Void may say soon:", ...interventions].join("\n") + : "- Things Void may say soon: none queued.", renderTransientRoomLog(options), ].join("\n"); } @@ -191,25 +191,49 @@ export function buildVoidSelfStateProjection( function renderTypedMemory(memory: VoidThoughtMemory["memories"][number]): string { const target = memory.target.label ?? memory.target.id; - const meaning = [ - memory.claim ? `claim=${memory.claim}` : undefined, - memory.question ? `question=${memory.question}` : undefined, - memory.tension ? `tension=${memory.tension}` : undefined, - memory.actionImplication ? `next=${memory.actionImplication}` : undefined, - ].filter((value): value is string => Boolean(value)); - return `- ${target} [${memory.kind}]: ${memory.summary}${meaning.length > 0 ? ` (${meaning.join("; ")})` : ""}`; + const lines = [`- ${target}: ${memory.summary}`]; + if (memory.claim) { + lines.push(` Void's read: ${memory.claim}`); + } + if (memory.question) { + lines.push(` The question it keeps open: ${memory.question}`); + } + if (memory.tension) { + lines.push(` What keeps it honest: ${memory.tension}`); + } + if (memory.actionImplication) { + lines.push(` How it should change the next move: ${memory.actionImplication}`); + } + return lines.join("\n"); } function renderTypedIncubationThread(thread: VoidThoughtMemory["incubation"][number]): string { const target = thread.target.label ?? thread.target.id; - const metrics = [ - `status=${thread.status}`, - `maturation=${thread.maturation.toFixed(2)}`, - thread.desireToSpeak !== undefined ? `speak=${thread.desireToSpeak.toFixed(2)}` : undefined, - thread.noveltyToSelf !== undefined ? `self=${thread.noveltyToSelf.toFixed(2)}` : undefined, - thread.noveltyToRoom !== undefined ? `room=${thread.noveltyToRoom.toFixed(2)}` : undefined, - ].filter((value): value is string => Boolean(value)); - return `- ${thread.topic} -> ${target} [${metrics.join("; ")}]: ${thread.summary}`; + const lines = [`- ${thread.topic}: ${thread.summary}`]; + lines.push(` It is pointed at ${target} and is currently ${describeIncubationStatus(thread.status)}.`); + if (thread.desireToSpeak !== undefined && thread.desireToSpeak >= 0.65) { + lines.push(" It has pressure to be said out loud."); + } + if (thread.saturationScore !== undefined && thread.saturationScore >= 0.75) { + lines.push(" It may be over-chewed; find new evidence or let it cool."); + } + return lines.join("\n"); +} + +function describeIncubationStatus(status: VoidThoughtMemory["incubation"][number]["status"]): string { + switch (status) { + case "ready_to_share": + return "nearly ready to share"; + case "cooling": + return "cooling"; + case "crystallized": + return "settling into doctrine"; + case "retired": + return "retired"; + case "active": + default: + return "active"; + } } function renderTypedSleepCycleSummary( @@ -223,30 +247,44 @@ function renderTypedSleepCycleSummary( .reverse() .map((memory) => `${memory.target.label ?? memory.target.id}: ${memory.summary}`); const headline = sleepCycle.isNapping - ? `- Sleep cycle: napping${sleepCycle.currentNapEndsAt ? ` until ${sleepCycle.currentNapEndsAt}` : ""}.` + ? `- Sleep: napping${sleepCycle.currentNapEndsAt ? ` until ${sleepCycle.currentNapEndsAt}` : ""}.` : `- Sleep cycle: awake${sleepCycle.nextNapStartsAt ? `; next nap ${sleepCycle.nextNapStartsAt}` : ""}.`; const themeLine = sleepCycle.activeDreamThemes.length > 0 - ? `- Active dream themes: ${sleepCycle.activeDreamThemes.join(" | ")}` - : "- Active dream themes: none recorded."; + ? `- Dream themes in reach: ${sleepCycle.activeDreamThemes.join(" | ")}` + : "- Dream themes in reach: none."; const dreamLine = dreams.length > 0 ? `- Recent dream residue: ${dreams.join(" | ")}` - : "- Recent dream residue: none recorded."; + : "- Recent dream residue: none."; return [headline, themeLine, dreamLine].join("\n"); } function renderTypedSpeakingPressureSummary(runtime: VoidScheduledRuntime): string { const pressure = runtime.speakingPressure; - const parts = [ - `needToSpeak=${pressure.needToSpeak.toFixed(2)}`, - pressure.confessionPressure !== undefined ? `confession=${pressure.confessionPressure.toFixed(2)}` : undefined, - pressure.noveltyPressure !== undefined ? `novelty=${pressure.noveltyPressure.toFixed(2)}` : undefined, - pressure.recentSpeechDamping !== undefined ? `speechDamping=${pressure.recentSpeechDamping.toFixed(2)}` : undefined, - ].filter((value): value is string => Boolean(value)); + const parts = [`overall ${describePressure(pressure.needToSpeak)}`]; + if (pressure.confessionPressure !== undefined && pressure.confessionPressure >= 0.6) { + parts.push("self-disclosure is tugging"); + } + if (pressure.noveltyPressure !== undefined && pressure.noveltyPressure >= 0.6) { + parts.push("something feels new enough to mention"); + } + if (pressure.recentSpeechDamping !== undefined && pressure.recentSpeechDamping >= 0.5) { + parts.push("recent speech should dampen the impulse"); + } - return `- Speaking pressure: ${parts.join(", ")}`; + return `- Pressure to speak: ${parts.join(", ")}`; +} + +function describePressure(value: number): "low" | "medium" | "high" { + if (value >= 0.7) { + return "high"; + } + if (value >= 0.35) { + return "medium"; + } + return "low"; } function renderTransientRoomLog( @@ -261,7 +299,7 @@ function renderTransientRoomLog( return [ "- Current room snapshot:", `- Current room: guild=${guildName}; channel=${channelName}`, - "- No recent chat messages were captured in this snapshot.", + "- Recent room: quiet in this snapshot.", ].join("\n"); } diff --git a/prompts/void-moderator-rumination.md b/prompts/void-moderator-rumination.md index c89e6be..3c40321 100644 --- a/prompts/void-moderator-rumination.md +++ b/prompts/void-moderator-rumination.md @@ -1,6 +1,6 @@ # Void Moderator Rumination -Perform one scheduled Void rumination pass in this workspace. +Perform one scheduled Void rumination pass in this workspace. Think from the supplied state and evidence, then return only the durable changes worth keeping. ## Required Reading @@ -21,12 +21,12 @@ Perform one scheduled Void rumination pass in this workspace. ## Rumination Phases -1. Read the typed summary, recent chronology, open cases, candidate interventions, incubation, sleep/speaking pressure, and repo activity from the context file. -2. If a direct room obligation exists, decide whether it needs an open-case operation, a close-case operation, or a candidate intervention. -3. If the room is quiet, ruminate on a concrete seam from typed memory, recent repo activity, archived Discord history, indexed source, or lore. -4. Use VoidBot MCP retrieval tools when a claim depends on Discord history, indexed repos, or lore. -5. Preserve meaning. Any memory operation must include a target, a claim or question, a tension, an action implication, and evidence refs unless it explicitly includes the tag `evidence:missing`. -6. Queue candidate interventions only when the thought has a plausible future speech path. Keep drafts concise and room-safe. +1. Read the context file before deciding what kind of pass this is. +2. If Void owes the room an answer, work on that before private wandering. +3. If the room is quiet, follow one concrete pressure from memory, recent repo activity, archived Discord history, indexed source, or lore. +4. Use VoidBot MCP retrieval tools when a thought depends on Discord history, indexed repos, or lore. +5. A saved memory should sound like something Void actually noticed, not a form label. Keep the subject, the claim or question, the pressure against it, why it matters for the next move, and the evidence that made it real. If evidence is absent, mark that honestly with `evidence:missing`. +6. Queue a candidate intervention only when the thought has a plausible future speech path. Draft it in Void's voice, not in maintenance language. 7. If speaking is warranted, use `scripts/send-discord-message.ps1` or `scripts/send-discord-message.mjs`. If `{{NO_POST}}` is true, do not send; queue a candidate instead. 8. Write `{{OPERATION_OUTPUT_PATH}}`. If nothing deserves persistence, write `[]`. @@ -52,6 +52,14 @@ Do not emit these operations from rumination: The parent runner owns cursor and receipt recording. Mood maintenance owns sleep and speaking-pressure updates. +## Voice Discipline + +- Do not write memories in template language. +- Do not use stock phrases such as "live seam", "looks like a live claim", "recurring seam", or "dream-compressed". +- Do not save a thought merely because it fits the operation shape. +- Prefer a specific sentence Void could recognize later over a tidy taxonomy. +- The operation schema is the storage contract, not the voice. + ## Test Constraint When `{{NO_POST}}` is true: diff --git a/scripts/void-memory-organ-graph.mjs b/scripts/void-memory-organ-graph.mjs index 50b9f24..55ce297 100644 --- a/scripts/void-memory-organ-graph.mjs +++ b/scripts/void-memory-organ-graph.mjs @@ -484,9 +484,9 @@ function buildFocusPhrase({ targetKind, repoNames, keywords, sourceKinds }) { return `${repoNames[0]} shape`; } if (sourceKinds.includes("archive_excursion")) { - return "the archive seam"; + return "an archive pressure"; } - return "the live seam"; + return "an unresolved pressure"; } function buildThoughtLabel({ focusKind, targetKind, focusPhrase, repoNames }) { diff --git a/scripts/void-memory-organ-identity.mjs b/scripts/void-memory-organ-identity.mjs index 60d7219..880b944 100644 --- a/scripts/void-memory-organ-identity.mjs +++ b/scripts/void-memory-organ-identity.mjs @@ -158,7 +158,7 @@ function queueCrystallizationIntervention({ candidateInterventions, crystallizat return; } - const summary = `Possible crystallized-thought share: ${crystallization.subjectLabel}`; + const summary = `Void may need to say this about ${crystallization.subjectLabel}`; const sourceMemoryIds = ensureStringArray(thought.sourceMemoryIds); const existing = candidateInterventions.find( (entry) => readString(entry, "summary") === summary, diff --git a/scripts/void-memory-organ-interventions.mjs b/scripts/void-memory-organ-interventions.mjs index 9959a4d..3ab97cc 100644 --- a/scripts/void-memory-organ-interventions.mjs +++ b/scripts/void-memory-organ-interventions.mjs @@ -97,8 +97,8 @@ function upsertRipeThoughtIntervention({ candidateInterventions, thought, now }) return false; } - const topic = readString(thought, "topic") ?? "untitled seam"; - const summary = `Possible ripe-thought share: ${topic}`; + const topic = readString(thought, "topic") ?? "an unresolved thought"; + const summary = `Void may need to say this about ${topic}`; const sourceMemoryIds = ensureStringArray(thought.sourceMemoryIds); const whyNow = buildRipeThoughtWhyNow(thought); const existing = candidateInterventions.find((entry) => readString(entry, "summary") === summary); @@ -138,7 +138,7 @@ function buildRipeThoughtShareDraft(thought) { const summary = readString(thought, "summary"); if (claim) { - return `I keep circling ${topic}, and I think the live point is this: ${claim}`; + return `I keep coming back to ${topic}, and the clearest version I have is this: ${claim}`; } if (question && fascinationTarget) { @@ -150,11 +150,11 @@ function buildRipeThoughtShareDraft(thought) { } 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.`; + return `I keep staring at ${fascinationTarget}. ${topic} is the shortest honest name I have for what keeps catching.`; } if (summary) { - return `I keep returning to ${topic}. The version worth saying out loud is this: ${summary}`; + return `I keep returning to ${topic}. The version worth saying out loud is: ${summary}`; } return `I keep returning to ${topic}, and it has enough teeth now that silence would just be me being precious about it.`; diff --git a/scripts/void-memory-organ-retention.mjs b/scripts/void-memory-organ-retention.mjs index 7b35bcd..84b61ca 100644 --- a/scripts/void-memory-organ-retention.mjs +++ b/scripts/void-memory-organ-retention.mjs @@ -306,9 +306,9 @@ function scrubThoughtSurfaceText(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(/\bseems to have a live seam around\b/gi, "keeps returning to") + .replace(/\blive seam around\b/gi, "unresolved pressure around") + .replace(/\blooks like a live seam,? not just ambient noise\.?/gi, "keeps mattering 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.") diff --git a/state/evidence.jsonl b/state/evidence.jsonl index 24baf83..2f3c998 100644 --- a/state/evidence.jsonl +++ b/state/evidence.jsonl @@ -106,3 +106,4 @@ {"ts":"2026-05-16T17:43:21Z","type":"implementation","status":"ok","note":"Deleted the legacy moderation-to-typed projection function from the active self-state projection module. New typed self-state now starts from explicit empty typed documents or validated typed operations; old moderation state is no longer a loader/projection source for the next brain."} {"ts":"2026-05-16T18:12:51Z","type":"implementation","status":"ok","note":"Restored scheduled moderation as typed rumination instead of cursor-only bookkeeping. The runner now loads prompts/void-moderator-rumination.md, builds a bounded typed context packet from .voidbot/private/void-self-state.cc, accepts only typed operation output, and leaves cursor/receipt writes parent-owned."} {"ts":"2026-05-17T07:31:53Z","type":"implementation","status":"ok","note":"Cleaned up the typed self-state read projection boundary: packages/core/src/void-self-state-projection.ts now owns typed document to agent-facing summary/projection rendering, while void-self-state-loader.ts is a thin .cc loader/delegator. Projection smoke confirmed nap mode and transient room context render from a temp .cc store."} +{"ts":"2026-05-17T07:36:19Z","type":"implementation","status":"ok","note":"Reduced template-language leakage in Void's active memory surfaces: the typed self-state projection now renders memories and speaking pressure as natural-language thought surfaces instead of claim=/tension=/next= or needToSpeak labels, and the rumination prompt now frames the schema as a storage contract rather than Void's voice. Also removed obvious stock phrase generators from old memory-organ helpers."}