Clarify typed self-state projection boundary
This commit is contained in:
parent
cb63ef45e9
commit
2f3aaa8f17
6 changed files with 216 additions and 193 deletions
|
|
@ -26,7 +26,7 @@ Git history and smoke artifacts carry routine proof. `state/evidence.jsonl` shou
|
|||
- CultCache-backed self-state uses the `.cc` extension. The active store path is `.voidbot/private/void-self-state.cc`.
|
||||
- The smallest coherent target is one polymorphic CultCache-backed state authority with typed document kinds plus typed mutation tools: agents propose small operations, and the state service validates/applies them. Whole-state JSON editing is rejected as a durable mutation path.
|
||||
- Commit-one boundary naming is now in code at `packages/core/src/void-self-state-domain.ts`: it declares the global typed document kinds and strict operation payload schemas. Runtime behavior has not moved yet.
|
||||
- Read-only projection is now in `packages/core/src/void-self-state-projection.ts`. `loadVoidSelfState` uses that typed projection for nap/reply-mode context while leaving the old richer prompt summary intact until typed documents explicitly own those details.
|
||||
- Read-only projection is now owned by `packages/core/src/void-self-state-projection.ts`. `loadVoidSelfState` is a thin loader that reads typed `.cc` documents and delegates the agent-facing summary plus nap/reply-mode projection to that module.
|
||||
- Typed operations are applied through `packages/core/src/void-self-state-service.ts` and `scripts/void-self-state.mjs apply-operation`. The service now writes typed documents only; it no longer registers, reads, updates, or mirrors the old moderation monolith during routine operations.
|
||||
- `scripts/export-recent-repo-activity.mjs` reads repo cursors from the typed `.cc` store. Legacy `.json` cursor arguments are only mapped to a sibling `.cc` path when needed by old invocation surfaces.
|
||||
- `scripts/run-void-moderator-rumination.ps1` has been rebuilt as a typed rumination runner. It builds a typed context packet, loads `prompts/void-moderator-rumination.md`, runs Codex rumination, and applies only the JSON operation payloads that come back through `scripts/void-self-state.mjs`. Cursor and receipt bookkeeping remain parent-owned.
|
||||
|
|
|
|||
|
|
@ -42,6 +42,10 @@ This note is the source-grounded description of how the live VoidBot stack is sh
|
|||
- Profile synthesis, disposition, psychological read, inferred traits, interaction dimensions, and response-guidance construction.
|
||||
- `packages/core/src/context-builder.ts`
|
||||
- request context assembly, including interaction-memory/profile attachment, shared Void self-state projection, and any precomputed situational social read.
|
||||
- `packages/core/src/void-self-state-loader.ts`
|
||||
- thin typed `.cc` self-state loader that delegates read-side rendering to the projection module.
|
||||
- `packages/core/src/void-self-state-projection.ts`
|
||||
- read-side projection authority for typed self-state: renders the agent-facing summary and compact nap/reply-mode projection from typed documents without touching legacy state.
|
||||
- `packages/core/src/situational-social-read.ts`
|
||||
- quick Ollama sidecar inferer for ephemeral room-reading scaffolding built from the current prompt, recent room transcript, and longer-horizon interaction memory.
|
||||
- `packages/providers/src/owner-codex-provider.ts`
|
||||
|
|
|
|||
|
|
@ -176,9 +176,10 @@ Verdict: stop feature work and rebuild this foundation. The rest of the machine
|
|||
|
||||
### Commit 2: Add Read-Only Typed Projections
|
||||
|
||||
- Build a self-state service that reads current canonical state and renders the same summaries used by direct replies.
|
||||
- Keep JSON projection for the old runner.
|
||||
- Verification: compare rendered summary against current loader output on a fixture.
|
||||
- Landed path: `packages/core/src/void-self-state-projection.ts` owns typed `.cc` document to agent-facing summary/projection rendering.
|
||||
- `packages/core/src/void-self-state-loader.ts` only loads typed documents and delegates read-side projection.
|
||||
- No JSON projection is needed for the live typed runner.
|
||||
- Verification: projection smoke loads a temp `.cc` store and confirms mode plus transient room context render.
|
||||
|
||||
### Commit 3: Add Typed Mutation CLI
|
||||
|
||||
|
|
|
|||
|
|
@ -4,11 +4,7 @@ import {
|
|||
type VoidSelfStateContext,
|
||||
} from "@voidbot/shared";
|
||||
|
||||
import {
|
||||
type VoidScheduledRuntime,
|
||||
type VoidThoughtMemory,
|
||||
} from "./void-self-state-domain";
|
||||
import { type VoidSelfStateTypedProjection } from "./void-self-state-projection";
|
||||
import { buildVoidSelfStateContext } from "./void-self-state-projection";
|
||||
import { loadVoidSelfStateTypedDocuments } from "./void-self-state-service";
|
||||
|
||||
export interface LoadVoidSelfStateOptions {
|
||||
|
|
@ -20,9 +16,12 @@ export async function loadVoidSelfState(
|
|||
statePath: string,
|
||||
options: LoadVoidSelfStateOptions = {},
|
||||
): Promise<VoidSelfStateContext | undefined> {
|
||||
let typedProjection: VoidSelfStateTypedProjection;
|
||||
try {
|
||||
typedProjection = await loadVoidSelfStateTypedDocuments({ canonicalPath: statePath });
|
||||
const typedProjection = await loadVoidSelfStateTypedDocuments({ canonicalPath: statePath });
|
||||
return buildVoidSelfStateContext(typedProjection, {
|
||||
sourcePath: statePath,
|
||||
...options,
|
||||
});
|
||||
} catch (error) {
|
||||
const code = (error as NodeJS.ErrnoException).code;
|
||||
|
||||
|
|
@ -32,186 +31,4 @@ export async function loadVoidSelfState(
|
|||
|
||||
throw error;
|
||||
}
|
||||
|
||||
const projection = buildVoidSelfStateProjection(typedProjection);
|
||||
|
||||
return {
|
||||
sourcePath: statePath,
|
||||
loadedAt: new Date().toISOString(),
|
||||
summary: renderTypedVoidSelfStateSummary(typedProjection, options),
|
||||
projection,
|
||||
};
|
||||
}
|
||||
|
||||
function renderTypedVoidSelfStateSummary(
|
||||
state: VoidSelfStateTypedProjection,
|
||||
options: LoadVoidSelfStateOptions,
|
||||
): string {
|
||||
const profile = state.selfProfile;
|
||||
const topValues = state.selfProfile.values
|
||||
.sort((left, right) => right.priority - left.priority)
|
||||
.slice(0, 4)
|
||||
.map((value) => value.label);
|
||||
const memories = state.thoughtMemory.memories
|
||||
.slice(-6)
|
||||
.reverse()
|
||||
.map((memory) => renderTypedMemory(memory));
|
||||
const incubation = state.thoughtMemory.incubation
|
||||
.filter((thread) => thread.status !== "retired")
|
||||
.sort((left, right) => right.maturation - left.maturation)
|
||||
.slice(0, 4)
|
||||
.map((thread) => renderTypedIncubationThread(thread));
|
||||
const openCases = state.moderationCursor.openCases
|
||||
.filter((entry) => !["answered", "resolved", "closed", "retired", "dropped"].includes(entry.status))
|
||||
.slice(-4)
|
||||
.reverse()
|
||||
.map((entry) => `- ${entry.summary}${entry.authorName ? ` [from ${entry.authorName}]` : ""}`);
|
||||
const interventions = state.candidateInterventions.interventions
|
||||
.filter((entry) => entry.status === "queued" || entry.status === "deferred")
|
||||
.slice(-3)
|
||||
.reverse()
|
||||
.map((entry) => `- ${entry.summary}: ${entry.draft}`);
|
||||
|
||||
return [
|
||||
`- Identity: ${profile.publicName}${profile.publicDescription ? ` - ${profile.publicDescription}` : ""}`,
|
||||
profile.privateNotes.length > 0
|
||||
? `- Private notes: ${profile.privateNotes.slice(0, 4).join(" | ")}`
|
||||
: "- Private notes: none recorded.",
|
||||
topValues.length > 0
|
||||
? `- Highest-priority values: ${topValues.join(" | ")}`
|
||||
: "- Highest-priority values: none recorded.",
|
||||
renderTypedSleepCycleSummary(state.scheduledRuntime, state.thoughtMemory),
|
||||
renderTypedSpeakingPressureSummary(state.scheduledRuntime),
|
||||
openCases.length > 0
|
||||
? ["- Outstanding room obligations:", ...openCases].join("\n")
|
||||
: "- Outstanding room obligations: none recorded.",
|
||||
memories.length > 0
|
||||
? ["- Typed memory:", ...memories].join("\n")
|
||||
: "- Typed memory: none recorded.",
|
||||
incubation.length > 0
|
||||
? ["- Incubating threads:", ...incubation].join("\n")
|
||||
: "- Incubating threads: none recorded.",
|
||||
interventions.length > 0
|
||||
? ["- Candidate interventions:", ...interventions].join("\n")
|
||||
: "- Candidate interventions: none recorded.",
|
||||
renderTransientRoomLog(options),
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
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("; ")})` : ""}`;
|
||||
}
|
||||
|
||||
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}`;
|
||||
}
|
||||
|
||||
function renderTypedSleepCycleSummary(
|
||||
runtime: VoidScheduledRuntime,
|
||||
thoughtMemory: VoidThoughtMemory,
|
||||
): string {
|
||||
const sleepCycle = runtime.sleepCycle;
|
||||
const dreams = thoughtMemory.memories
|
||||
.filter((memory) => memory.kind === "dream_residue")
|
||||
.slice(-2)
|
||||
.reverse()
|
||||
.map((memory) => `${memory.target.label ?? memory.target.id}: ${memory.summary}`);
|
||||
const headline = sleepCycle.isNapping
|
||||
? `- Sleep cycle: 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.";
|
||||
const dreamLine =
|
||||
dreams.length > 0
|
||||
? `- Recent dream residue: ${dreams.join(" | ")}`
|
||||
: "- Recent dream residue: none recorded.";
|
||||
|
||||
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));
|
||||
|
||||
return `- Speaking pressure: ${parts.join(", ")}`;
|
||||
}
|
||||
|
||||
function renderTransientRoomLog(options: LoadVoidSelfStateOptions): string {
|
||||
const recentMessages = (options.recentMessages ?? []).slice(-10);
|
||||
const guildName = options.guildContext?.guildName ?? options.guildContext?.guildId ?? "(direct/unknown)";
|
||||
const channelName =
|
||||
options.guildContext?.channelName ?? options.guildContext?.channelId ?? "(unknown channel)";
|
||||
|
||||
if (recentMessages.length === 0) {
|
||||
return [
|
||||
"- Current room snapshot:",
|
||||
`- Current room: guild=${guildName}; channel=${channelName}`,
|
||||
"- No recent chat messages were captured in this snapshot.",
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
const lines = recentMessages.map(
|
||||
(message) =>
|
||||
`- [${message.timestamp}] ${message.authorName}: ${collapseWhitespace(message.content || "(no text content)")}`,
|
||||
);
|
||||
|
||||
return [
|
||||
"- Current room snapshot:",
|
||||
`- Current room: guild=${guildName}; channel=${channelName}`,
|
||||
...lines,
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
function buildVoidSelfStateProjection(
|
||||
typedState: VoidSelfStateTypedProjection,
|
||||
): VoidSelfStateContext["projection"] {
|
||||
const sleepCycle = typedState.scheduledRuntime.sleepCycle;
|
||||
const dreamSummaries = typedState.thoughtMemory.memories
|
||||
.filter((memory) => memory.kind === "dream_residue")
|
||||
.slice(-3)
|
||||
.reverse()
|
||||
.map((memory) => memory.summary);
|
||||
const isNapping = sleepCycle.isNapping === true;
|
||||
const napStartedAt = sleepCycle.currentNapStartedAt;
|
||||
const napEndsAt = sleepCycle.currentNapEndsAt;
|
||||
const nextNapAt = sleepCycle.nextNapStartsAt;
|
||||
|
||||
return {
|
||||
mode: isNapping ? "napping" : "awake",
|
||||
effortCeiling: isNapping ? "minimal" : "normal",
|
||||
napStartedAt: isNapping ? napStartedAt : undefined,
|
||||
napEndsAt: isNapping ? napEndsAt : undefined,
|
||||
nextNapAt,
|
||||
activeDreamThemes: sleepCycle.activeDreamThemes,
|
||||
recentDreamSummaries: dreamSummaries,
|
||||
replyDirective: isNapping
|
||||
? "You are in a scheduled nap. Reply in brief, low-effort, half-dreaming mutters instead of doing full attentive service-work."
|
||||
: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
function collapseWhitespace(value: string): string {
|
||||
return value.replace(/\s+/g, " ").trim();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,9 @@
|
|||
import {
|
||||
type GuildContext,
|
||||
type SourceMessage,
|
||||
type VoidSelfStateContext,
|
||||
} from "@voidbot/shared";
|
||||
|
||||
import {
|
||||
type VoidCandidateInterventions,
|
||||
type VoidModerationCursor,
|
||||
|
|
@ -22,6 +28,13 @@ export interface VoidSelfStateTypedProjection {
|
|||
candidateInterventions: VoidCandidateInterventions;
|
||||
}
|
||||
|
||||
export interface VoidSelfStateProjectionOptions {
|
||||
sourcePath: string;
|
||||
loadedAt?: string;
|
||||
recentMessages?: SourceMessage[];
|
||||
guildContext?: GuildContext;
|
||||
}
|
||||
|
||||
export function createEmptyVoidSelfState(
|
||||
options: { createdAt?: string } = {},
|
||||
): VoidSelfStateTypedProjection {
|
||||
|
|
@ -80,3 +93,190 @@ export function createEmptyVoidSelfState(
|
|||
}),
|
||||
};
|
||||
}
|
||||
|
||||
export function buildVoidSelfStateContext(
|
||||
typedState: VoidSelfStateTypedProjection,
|
||||
options: VoidSelfStateProjectionOptions,
|
||||
): VoidSelfStateContext {
|
||||
return {
|
||||
sourcePath: options.sourcePath,
|
||||
loadedAt: options.loadedAt ?? new Date().toISOString(),
|
||||
summary: renderVoidSelfStateSummary(typedState, options),
|
||||
projection: buildVoidSelfStateProjection(typedState),
|
||||
};
|
||||
}
|
||||
|
||||
export function renderVoidSelfStateSummary(
|
||||
state: VoidSelfStateTypedProjection,
|
||||
options: Pick<VoidSelfStateProjectionOptions, "guildContext" | "recentMessages"> = {},
|
||||
): string {
|
||||
const profile = state.selfProfile;
|
||||
const topValues = state.selfProfile.values
|
||||
.sort((left, right) => right.priority - left.priority)
|
||||
.slice(0, 4)
|
||||
.map((value) => value.label);
|
||||
const memories = state.thoughtMemory.memories
|
||||
.slice(-6)
|
||||
.reverse()
|
||||
.map((memory) => renderTypedMemory(memory));
|
||||
const incubation = state.thoughtMemory.incubation
|
||||
.filter((thread) => thread.status !== "retired")
|
||||
.sort((left, right) => right.maturation - left.maturation)
|
||||
.slice(0, 4)
|
||||
.map((thread) => renderTypedIncubationThread(thread));
|
||||
const openCases = state.moderationCursor.openCases
|
||||
.filter((entry) => !["answered", "resolved", "closed", "retired", "dropped"].includes(entry.status))
|
||||
.slice(-4)
|
||||
.reverse()
|
||||
.map((entry) => `- ${entry.summary}${entry.authorName ? ` [from ${entry.authorName}]` : ""}`);
|
||||
const interventions = state.candidateInterventions.interventions
|
||||
.filter((entry) => entry.status === "queued" || entry.status === "deferred")
|
||||
.slice(-3)
|
||||
.reverse()
|
||||
.map((entry) => `- ${entry.summary}: ${entry.draft}`);
|
||||
|
||||
return [
|
||||
`- Identity: ${profile.publicName}${profile.publicDescription ? ` - ${profile.publicDescription}` : ""}`,
|
||||
profile.privateNotes.length > 0
|
||||
? `- Private notes: ${profile.privateNotes.slice(0, 4).join(" | ")}`
|
||||
: "- Private notes: none recorded.",
|
||||
topValues.length > 0
|
||||
? `- Highest-priority values: ${topValues.join(" | ")}`
|
||||
: "- Highest-priority values: none recorded.",
|
||||
renderTypedSleepCycleSummary(state.scheduledRuntime, state.thoughtMemory),
|
||||
renderTypedSpeakingPressureSummary(state.scheduledRuntime),
|
||||
openCases.length > 0
|
||||
? ["- Outstanding room obligations:", ...openCases].join("\n")
|
||||
: "- Outstanding room obligations: none recorded.",
|
||||
memories.length > 0
|
||||
? ["- Typed memory:", ...memories].join("\n")
|
||||
: "- Typed memory: none recorded.",
|
||||
incubation.length > 0
|
||||
? ["- Incubating threads:", ...incubation].join("\n")
|
||||
: "- Incubating threads: none recorded.",
|
||||
interventions.length > 0
|
||||
? ["- Candidate interventions:", ...interventions].join("\n")
|
||||
: "- Candidate interventions: none recorded.",
|
||||
renderTransientRoomLog(options),
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
export function buildVoidSelfStateProjection(
|
||||
typedState: VoidSelfStateTypedProjection,
|
||||
): VoidSelfStateContext["projection"] {
|
||||
const sleepCycle = typedState.scheduledRuntime.sleepCycle;
|
||||
const dreamSummaries = typedState.thoughtMemory.memories
|
||||
.filter((memory) => memory.kind === "dream_residue")
|
||||
.slice(-3)
|
||||
.reverse()
|
||||
.map((memory) => memory.summary);
|
||||
const isNapping = sleepCycle.isNapping === true;
|
||||
const napStartedAt = sleepCycle.currentNapStartedAt;
|
||||
const napEndsAt = sleepCycle.currentNapEndsAt;
|
||||
const nextNapAt = sleepCycle.nextNapStartsAt;
|
||||
|
||||
return {
|
||||
mode: isNapping ? "napping" : "awake",
|
||||
effortCeiling: isNapping ? "minimal" : "normal",
|
||||
napStartedAt: isNapping ? napStartedAt : undefined,
|
||||
napEndsAt: isNapping ? napEndsAt : undefined,
|
||||
nextNapAt,
|
||||
activeDreamThemes: sleepCycle.activeDreamThemes,
|
||||
recentDreamSummaries: dreamSummaries,
|
||||
replyDirective: isNapping
|
||||
? "You are in a scheduled nap. Reply in brief, low-effort, half-dreaming mutters instead of doing full attentive service-work."
|
||||
: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
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("; ")})` : ""}`;
|
||||
}
|
||||
|
||||
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}`;
|
||||
}
|
||||
|
||||
function renderTypedSleepCycleSummary(
|
||||
runtime: VoidScheduledRuntime,
|
||||
thoughtMemory: VoidThoughtMemory,
|
||||
): string {
|
||||
const sleepCycle = runtime.sleepCycle;
|
||||
const dreams = thoughtMemory.memories
|
||||
.filter((memory) => memory.kind === "dream_residue")
|
||||
.slice(-2)
|
||||
.reverse()
|
||||
.map((memory) => `${memory.target.label ?? memory.target.id}: ${memory.summary}`);
|
||||
const headline = sleepCycle.isNapping
|
||||
? `- Sleep cycle: 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.";
|
||||
const dreamLine =
|
||||
dreams.length > 0
|
||||
? `- Recent dream residue: ${dreams.join(" | ")}`
|
||||
: "- Recent dream residue: none recorded.";
|
||||
|
||||
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));
|
||||
|
||||
return `- Speaking pressure: ${parts.join(", ")}`;
|
||||
}
|
||||
|
||||
function renderTransientRoomLog(
|
||||
options: Pick<VoidSelfStateProjectionOptions, "guildContext" | "recentMessages">,
|
||||
): string {
|
||||
const recentMessages = (options.recentMessages ?? []).slice(-10);
|
||||
const guildName = options.guildContext?.guildName ?? options.guildContext?.guildId ?? "(direct/unknown)";
|
||||
const channelName =
|
||||
options.guildContext?.channelName ?? options.guildContext?.channelId ?? "(unknown channel)";
|
||||
|
||||
if (recentMessages.length === 0) {
|
||||
return [
|
||||
"- Current room snapshot:",
|
||||
`- Current room: guild=${guildName}; channel=${channelName}`,
|
||||
"- No recent chat messages were captured in this snapshot.",
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
const lines = recentMessages.map(
|
||||
(message) =>
|
||||
`- [${message.timestamp}] ${message.authorName}: ${collapseWhitespace(message.content || "(no text content)")}`,
|
||||
);
|
||||
|
||||
return [
|
||||
"- Current room snapshot:",
|
||||
`- Current room: guild=${guildName}; channel=${channelName}`,
|
||||
...lines,
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
function collapseWhitespace(value: string): string {
|
||||
return value.replace(/\s+/g, " ").trim();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -105,3 +105,4 @@
|
|||
{"ts":"2026-05-16T17:39:43Z","type":"implementation","status":"ok","note":"Took legacy scheduled self-state writers offline and rebuilt the active mood/moderation entry scripts around typed-only CultCache state at .voidbot/private/void-self-state.cc. The typed service no longer registers, reads, or writes the legacy moderation document; mood updates sleep/speaking pressure only, and moderation runner is cursor-only until the typed phase machine is rebuilt."}
|
||||
{"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."}
|
||||
|
|
|
|||
Loading…
Reference in a new issue