Extract person-shaped rumination organ

This commit is contained in:
Meta Vens 2026-07-16 05:29:00 +01:00
commit c0cf87520b
7 changed files with 273 additions and 37 deletions

View file

@ -0,0 +1,21 @@
export function projectRelativeChronology<T>(value: T, observedAt: Date): T {
return projectValue(value, observedAt) as T;
}
function projectValue(value: unknown, observedAt: Date, key = ""): unknown {
if (Array.isArray(value)) return value.map((entry) => projectValue(entry, observedAt));
if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).map(([entryKey, entry]) => [entryKey, projectValue(entry, observedAt, entryKey)]));
if (typeof value === "string" && /(?:at|timestamp)$/i.test(key) && Number.isFinite(Date.parse(value))) return relativeTime(value, observedAt);
return value;
}
function relativeTime(value: string, observedAt: Date): string {
const difference = observedAt.getTime() - Date.parse(value);
const future = difference < 0;
const minutes = Math.max(0, Math.round(Math.abs(difference) / 60_000));
const phrase = (amount: number, unit: string) => future ? `in ${amount} ${unit}${amount === 1 ? "" : "s"}` : `${amount} ${unit}${amount === 1 ? "" : "s"} ago`;
if (minutes < 60) return phrase(minutes, "minute");
const hours = Math.round(minutes / 60);
if (hours < 24) return phrase(hours, "hour");
return phrase(Math.round(hours / 24), "day");
}

View file

@ -1,4 +1,5 @@
import { voidSelfStateOperationSchema, type VoidSelfStateOperation, type VoidSelfStateTypedProjection } from "@voidbot/core";
import { projectRelativeChronology } from "./relative-chronology-projector.js";
const ALLOWED_OPERATIONS = new Set([
"merge_incubation_support", "queue_candidate_intervention", "retire_candidate_intervention",
@ -39,7 +40,7 @@ export function projectVoidMemoryMaintenanceContext(input: {
const activeAgency = state.agencyPressure.pressures.filter((entry) => ["active", "cooling", "ready_to_act"].includes(entry.status));
const activeCandidates = state.candidateInterventions.interventions.filter((entry) => entry.status === "queued" || entry.status === "deferred");
const maintenancePressure = activeShortTerm.length + state.thoughtMemory.incubation.length + activeAgency.length + activeCandidates.length;
const relative = <T>(value: T): T => projectRelativeChronology(value, input.observedAt) as T;
const relative = <T>(value: T): T => projectRelativeChronology(value, input.observedAt);
return {
mode: sleeping ? "sleep_maintenance" : "awake_memory_maintenance",
operationTimestamp: input.observedAt.toISOString(),
@ -85,19 +86,3 @@ export function parseVoidMemoryMaintenanceOperations(outputText: string): VoidSe
return operation;
});
}
function projectRelativeChronology(value: unknown, observedAt: Date, key = ""): unknown {
if (Array.isArray(value)) return value.map((entry) => projectRelativeChronology(entry, observedAt));
if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).map(([entryKey, entry]) => [entryKey, projectRelativeChronology(entry, observedAt, entryKey)]));
if (typeof value === "string" && /(?:At|Timestamp)$/.test(key) && Number.isFinite(Date.parse(value))) return relativeTime(value, observedAt);
return value;
}
function relativeTime(value: string, observedAt: Date): string {
const differenceMs = observedAt.getTime() - Date.parse(value);
if (!Number.isFinite(differenceMs)) return "unknown time";
const future = differenceMs < 0;
const minutes = Math.max(0, Math.round(Math.abs(differenceMs) / 60_000));
const amount = minutes < 60 ? `${minutes} minute${minutes === 1 ? "" : "s"}` : minutes < 1_440 ? `${Math.round(minutes / 60)} hour${Math.round(minutes / 60) === 1 ? "" : "s"}` : `${Math.round(minutes / 1_440)} day${Math.round(minutes / 1_440) === 1 ? "" : "s"}`;
return future ? `in ${amount}` : `${amount} ago`;
}

View file

@ -1,5 +1,6 @@
import { voidSelfStateOperationSchema, type VoidSelfStateOperation, type VoidSelfStateTypedProjection } from "@voidbot/core";
import { z } from "zod";
import { projectRelativeChronology } from "./relative-chronology-projector.js";
const ALLOWED_MODERATION_TAGS = new Set(["moderation:instaban", "moderation:strike", "moderation:case_only"]);
@ -30,7 +31,7 @@ export function projectVoidModerationHeartbeatContext(input: {
enforcementMode: string;
rules: string;
}): VoidModerationHeartbeatContext {
const relative = <T>(value: T): T => projectRelativeChronology(value, input.observedAt) as T;
const relative = <T>(value: T): T => projectRelativeChronology(value, input.observedAt);
return {
mode: "moderation_heartbeat",
operationTimestamp: input.observedAt.toISOString(),
@ -112,22 +113,3 @@ function validateOperation(entry: unknown, index: number, state: VoidSelfStateTy
if (moderationTags.length !== 1) throw new Error("Moderation heartbeat open cases must include exactly one moderation classification tag.");
return operation;
}
function projectRelativeChronology(value: unknown, observedAt: Date, key = ""): unknown {
if (Array.isArray(value)) return value.map((entry) => projectRelativeChronology(entry, observedAt));
if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).map(([entryKey, entry]) => [entryKey, projectRelativeChronology(entry, observedAt, entryKey)]));
if (typeof value === "string" && /(?:at|timestamp)$/i.test(key) && Number.isFinite(Date.parse(value))) return relativeTime(value, observedAt);
return value;
}
function relativeTime(value: string, observedAt: Date): string {
const difference = observedAt.getTime() - Date.parse(value);
const future = difference < 0;
const minutes = Math.max(0, Math.round(Math.abs(difference) / 60_000));
const phrase = (amount: number, unit: string) => future ? `in ${amount} ${unit}${amount === 1 ? "" : "s"}` : `${amount} ${unit}${amount === 1 ? "" : "s"} ago`;
if (minutes < 60) return phrase(minutes, "minute");
const hours = Math.round(minutes / 60);
if (hours < 24) return phrase(hours, "hour");
const days = Math.round(hours / 24);
return phrase(days, "day");
}

View file

@ -0,0 +1,53 @@
import { applyVoidSelfStateOperation, loadVoidSelfStateTypedDocuments } from "@voidbot/core";
import { buildVoidRuminationPrompt, parseVoidRuminationOperations, projectVoidRuminationContext } from "./void-rumination-projector.js";
export type VoidRuminationResult =
| { status: "skipped"; reason: "already_attempted_this_pressure" | "napping_without_pressure"; runnerId?: string }
| { status: "ok"; runnerId: string; appliedOperationCount: number; queuedCandidateIds: string[] };
export async function runVoidRumination(input: {
statePath: string;
observedAt?: Date;
pendingMentions?: unknown[];
recentHistory?: unknown;
repoActivity?: unknown;
recentRoomLinks?: unknown;
recentConversationTarget?: unknown;
publicSpeechTarget?: unknown;
doctrine: string;
rules: string;
voice: string;
}, dependencies: {
loadState?: typeof loadVoidSelfStateTypedDocuments;
applyOperation?: typeof applyVoidSelfStateOperation;
projectText: (prompt: string) => Promise<string>;
}): Promise<VoidRuminationResult> {
const loadState = dependencies.loadState ?? loadVoidSelfStateTypedDocuments;
const applyOperation = dependencies.applyOperation ?? applyVoidSelfStateOperation;
const state = await loadState({ canonicalPath: input.statePath });
const observedAt = input.observedAt ?? new Date();
const context = projectVoidRuminationContext({ ...input, state, observedAt });
const hasExternalPressure = (input.pendingMentions?.length ?? 0) > 0 || hasObservedItems(input.recentHistory) || hasObservedItems(input.repoActivity) || hasObservedItems(input.recentRoomLinks);
const hasInternalPressure = context.speechPressureObligationIds.length > 0 || state.moderationCursor.openCases.some((entry) => ["pending", "watching"].includes(entry.status)) || state.candidateInterventions.interventions.some((entry) => entry.status === "queued");
if (state.scheduledRuntime.sleepCycle.isNapping && !hasExternalPressure && !hasInternalPressure) return { status: "skipped", reason: "napping_without_pressure" };
const runnerId = `void-rumination-attempt:${context.pressureFingerprint}`;
if (state.scheduledRuntime.lastRuns.some((run) => run.runner === runnerId)) return { status: "skipped", reason: "already_attempted_this_pressure", runnerId };
await applyOperation({ canonicalPath: input.statePath }, {
operation: "record_scheduled_run",
run: { runner: runnerId, ranAt: observedAt.toISOString(), summary: "Started one bounded person-shaped rumination attempt for this pressure fingerprint." },
});
const operations = parseVoidRuminationOperations({ outputText: await dependencies.projectText(buildVoidRuminationPrompt(context)), state });
for (const operation of operations) await applyOperation({ canonicalPath: input.statePath }, operation);
return {
status: "ok", runnerId, appliedOperationCount: operations.length,
queuedCandidateIds: operations.flatMap((operation) => operation.operation === "queue_candidate_intervention" ? [operation.intervention.interventionId] : []),
};
}
function hasObservedItems(value: unknown): boolean {
if (Array.isArray(value)) return value.length > 0;
if (!value || typeof value !== "object") return false;
const record = value as Record<string, unknown>;
for (const key of ["messages", "items", "commits", "repos"]) if (Array.isArray(record[key]) && record[key].length > 0) return true;
return false;
}

View file

@ -0,0 +1,127 @@
import { createHash } from "node:crypto";
import { voidSelfStateOperationSchema, type VoidSelfStateOperation, type VoidSelfStateTypedProjection } from "@voidbot/core";
import { projectRelativeChronology } from "./relative-chronology-projector.js";
const ALLOWED_OPERATIONS = new Set([
"upsert_open_case", "close_open_case", "record_short_term_memory", "merge_incubation_support",
"upsert_agency_pressure", "retire_agency_pressure", "upsert_affect_need", "retire_affect_need",
"upsert_social_bond", "retire_social_bond", "upsert_status_read", "retire_status_read",
"update_mood_dimensions", "queue_candidate_intervention", "retire_candidate_intervention",
]);
export interface VoidRuminationContext {
mode: "person_shaped_rumination";
operationTimestamp: string;
pressureFingerprint: string;
selfProfile: unknown;
affect: unknown;
memories: unknown[];
shortTermMemories: unknown[];
incubation: unknown[];
agencyPressure: unknown[];
speechPressureObligationIds: string[];
candidateInterventions: unknown[];
speechReceipts: unknown[];
openCases: unknown[];
scheduledRuntime: unknown;
pendingMentions: unknown[];
recentHistory: unknown;
repoActivity: unknown;
recentRoomLinks: unknown;
recentConversationTarget: unknown;
publicSpeechTarget: unknown;
doctrine: string;
rules: string;
voice: string;
}
export function projectVoidRuminationContext(input: {
state: VoidSelfStateTypedProjection;
observedAt: Date;
pendingMentions?: unknown[];
recentHistory?: unknown;
repoActivity?: unknown;
recentRoomLinks?: unknown;
recentConversationTarget?: unknown;
publicSpeechTarget?: unknown;
doctrine: string;
rules: string;
voice: string;
}): VoidRuminationContext {
const obligationIds = speechPressureObligationIds(input.state);
const fingerprint = fingerprintVoidRuminationPressure({
state: input.state,
pendingMentions: input.pendingMentions ?? [], recentHistory: input.recentHistory ?? null,
repoActivity: input.repoActivity ?? null, recentRoomLinks: input.recentRoomLinks ?? null,
});
const relative = <T>(value: T): T => projectRelativeChronology(value, input.observedAt);
return {
mode: "person_shaped_rumination", operationTimestamp: input.observedAt.toISOString(), pressureFingerprint: fingerprint,
selfProfile: relative(input.state.selfProfile), affect: relative(input.state.faceAffect),
memories: relative(input.state.thoughtMemory.memories), shortTermMemories: relative(input.state.thoughtMemory.shortTerm),
incubation: relative(input.state.thoughtMemory.incubation), agencyPressure: relative(input.state.agencyPressure.pressures),
speechPressureObligationIds: obligationIds, candidateInterventions: relative(input.state.candidateInterventions.interventions),
speechReceipts: relative(input.state.speechReceipts.recentReceipts), openCases: relative(input.state.moderationCursor.openCases),
scheduledRuntime: relative(input.state.scheduledRuntime), pendingMentions: relative(input.pendingMentions ?? []),
recentHistory: relative(input.recentHistory ?? null), repoActivity: relative(input.repoActivity ?? null),
recentRoomLinks: relative(input.recentRoomLinks ?? null), recentConversationTarget: input.recentConversationTarget ?? null,
publicSpeechTarget: input.publicSpeechTarget ?? null, doctrine: input.doctrine, rules: input.rules, voice: input.voice,
};
}
export function buildVoidRuminationPrompt(context: VoidRuminationContext): string {
return [
"You are Void's private person-shaped rumination organ. Think as a participant with values and relationships, not as a maintenance report.",
"Return only a JSON array of complete typed Void self-state operation objects. No markdown, commentary, tools, file writes, or direct transport calls.",
`Allowed operations: ${[...ALLOWED_OPERATIONS].join(", ")}.`,
"New memories must be short-term and meaning-preserving. Queue speech only as candidate interventions; the parent delivery organ owns transport and receipts.",
"A reply candidate must retain the source channel and message in deliveryTarget. publicSpeechTarget is only for unanchored public artifacts or herald speech.",
"For every speechPressureObligationId, queue a live candidate tagged source_pressure:<id>, retire the pressure, or upsert it with cooling/resolved status. Silent [] is invalid while obligations exist.",
"Use operationTimestamp for fresh exact timestamps. Relative chronology is evidence only.",
"If no anchored state change or sayable pressure exists, return [].",
"Rumination context:", JSON.stringify(context, null, 2),
].join("\n\n");
}
export function parseVoidRuminationOperations(input: { outputText: string; state: VoidSelfStateTypedProjection }): VoidSelfStateOperation[] {
const trimmed = input.outputText.trim();
const candidate = trimmed.match(/^```(?:json)?\s*([\s\S]*?)\s*```$/i)?.[1]?.trim() ?? trimmed;
let raw: unknown;
try { raw = JSON.parse(candidate); } catch (error) { throw new Error(`Void rumination output is not valid JSON: ${error instanceof Error ? error.message : String(error)}`); }
if (!Array.isArray(raw)) throw new Error("Void rumination output must be a JSON array.");
const operations = raw.map((entry, index) => {
const operation = voidSelfStateOperationSchema.parse(entry);
if (!ALLOWED_OPERATIONS.has(operation.operation)) throw new Error(`Void rumination operation ${index + 1} is not allowed: ${operation.operation}.`);
return operation;
});
assertSpeechPressureResolved(input.state, operations);
return operations;
}
export function fingerprintVoidRuminationPressure(input: { state: VoidSelfStateTypedProjection; pendingMentions: unknown[]; recentHistory: unknown; repoActivity: unknown; recentRoomLinks: unknown }): string {
const state = input.state;
const pressure = {
affect: state.faceAffect, shortTerm: state.thoughtMemory.shortTerm, incubation: state.thoughtMemory.incubation,
agency: state.agencyPressure.pressures, candidates: state.candidateInterventions.interventions,
openCases: state.moderationCursor.openCases, receipts: state.speechReceipts.recentReceipts.slice(-16),
pendingMentions: input.pendingMentions, recentHistory: input.recentHistory, repoActivity: input.repoActivity, recentRoomLinks: input.recentRoomLinks,
};
return createHash("sha256").update(JSON.stringify(pressure)).digest("hex").slice(0, 24);
}
function speechPressureObligationIds(state: VoidSelfStateTypedProjection): string[] {
return state.agencyPressure.pressures
.filter((pressure) => ["active", "ready_to_act"].includes(pressure.status) && pressure.intensity >= 0.55)
.filter((pressure) => !state.candidateInterventions.interventions.some((candidate) => ["queued", "spoken"].includes(candidate.status) && candidate.tags.includes(`source_pressure:${pressure.pressureId}`)))
.map((pressure) => pressure.pressureId);
}
function assertSpeechPressureResolved(state: VoidSelfStateTypedProjection, operations: VoidSelfStateOperation[]): void {
for (const pressureId of speechPressureObligationIds(state)) {
const resolved = operations.some((operation) =>
(operation.operation === "queue_candidate_intervention" && operation.intervention.status === "queued" && operation.intervention.tags.includes(`source_pressure:${pressureId}`))
|| (operation.operation === "retire_agency_pressure" && operation.pressureId === pressureId)
|| (operation.operation === "upsert_agency_pressure" && operation.pressure.pressureId === pressureId && ["cooling", "resolved", "retired"].includes(operation.pressure.status)));
if (!resolved) throw new Error(`Active advocacy pressure '${pressureId}' requires a candidate intervention or explicit cooling/retirement.`);
}
}

View file

@ -0,0 +1,10 @@
import type { loadConfig } from "@voidbot/config";
import { requestBoundedTextCompletion } from "./bounded-text-completion-actuator.js";
export async function projectVoidRuminationOperations(input: { prompt: string; config: ReturnType<typeof loadConfig>; model?: string }, dependencies: { fetch?: typeof fetch; readText?: (path: string) => Promise<string> } = {}): Promise<string> {
return requestBoundedTextCompletion({
prompt: input.prompt, config: input.config, purpose: "Void person-shaped rumination",
model: input.model ?? process.env.VOID_RUMINATION_MODEL,
maxCompletionTokens: Number(process.env.VOID_RUMINATION_MAX_COMPLETION_TOKENS) || 3072,
}, dependencies);
}

View file

@ -89,6 +89,9 @@ import { readVoidModerationEvidence } from "../apps/persona-scheduler/dist/void-
import { runVoidModerationHeartbeat } from "../apps/persona-scheduler/dist/void-moderation-heartbeat-organ.js";
import { projectVoidModerationOperations } from "../apps/persona-scheduler/dist/void-moderation-text-actuator.js";
import { runVoidModerationEnforcement } from "../apps/persona-scheduler/dist/void-moderation-enforcement-organ.js";
import { buildVoidRuminationPrompt, parseVoidRuminationOperations, projectVoidRuminationContext } from "../apps/persona-scheduler/dist/void-rumination-projector.js";
import { runVoidRumination } from "../apps/persona-scheduler/dist/void-rumination-organ.js";
import { projectVoidRuminationOperations } from "../apps/persona-scheduler/dist/void-rumination-text-actuator.js";
import { projectGamecultPersonaState } from "../apps/persona-scheduler/dist/persona-standard-state-projector.js";
import { projectPersonaStatePacket } from "../apps/persona-scheduler/dist/persona-state-packet-projector.js";
import { extractLastPersonaProjectionMessage, isRetryablePersonaProjectionFailure } from "../apps/persona-scheduler/dist/persona-text-projection-actuator.js";
@ -924,6 +927,51 @@ assert.equal(bannedUserId, "human-1", "policy enforcement lowers an instaban thr
assert.equal(moderationEnforcement.actions[0]?.status, "instaban_applied", "successful sanction closes the typed case with an enforcement result");
const repeatedEnforcement = await runVoidModerationEnforcement({ statePath: "void-self-state.cc", mode: "policy", botToken: "token", guildId: "guild", observedAt: physiologyObservedAt }, { loadState: async () => moderationState });
assert.equal(repeatedEnforcement.evaluatedCaseCount, 0, "resolved moderation debt cannot be sanctioned twice on later cadences");
const ruminationState = createEmptyVoidSelfState({ createdAt: "2026-07-15T00:00:00.000Z" });
ruminationState.scheduledRuntime.sleepCycle.isNapping = false;
ruminationState.agencyPressure.pressures.push({
pressureId: "pressure-1", kind: "world_advocacy_request", status: "active", target: { kind: "room", id: "aquarium" },
summary: "A direct room claim deserves an answer.", claim: "The reply belongs in its source room.", tension: "Silence would leave the claim unanswered.",
actionImplication: "Queue a source-anchored reply.", intensity: 0.8, anchorRefs: [{ ref: "discord:message-1" }], evidenceRefs: [], sourceMemoryIds: [],
createdAt: physiologyObservedAt.toISOString(), updatedAt: physiologyObservedAt.toISOString(), tags: [],
} as never);
const ruminationContext = projectVoidRuminationContext({ state: ruminationState, observedAt: physiologyObservedAt, pendingMentions: [{ messageId: "message-1", channelId: "room-1" }], recentHistory: { messages: [{ id: "message-1", timestamp: "2026-07-16T02:55:00.000Z" }] }, doctrine: "Participate honestly.", rules: "Fixture rules.", voice: "Dry and direct." });
const laterRuminationContext = projectVoidRuminationContext({ state: ruminationState, observedAt: new Date("2026-07-16T04:00:00.000Z"), pendingMentions: [{ messageId: "message-1", channelId: "room-1" }], recentHistory: { messages: [{ id: "message-1", timestamp: "2026-07-16T02:55:00.000Z" }] }, doctrine: "Participate honestly.", rules: "Fixture rules.", voice: "Dry and direct." });
assert.equal(ruminationContext.pressureFingerprint, laterRuminationContext.pressureFingerprint, "wall-clock projection cannot manufacture a fresh rumination intent");
assert.match(buildVoidRuminationPrompt(ruminationContext), /source_pressure:<id>/, "rumination prompt projects explicit speech-pressure accountability");
assert.throws(() => parseVoidRuminationOperations({ outputText: "[]", state: ruminationState }), /requires a candidate/, "strong advocacy pressure cannot be silently acknowledged");
assert.throws(() => parseVoidRuminationOperations({ outputText: JSON.stringify([{ operation: "propose_memory_distillation", proposal: {} }]), state: ruminationState }), /invalid|not allowed/i, "awake rumination cannot annex sleep-owned durable-memory policy");
const ruminationCandidate = {
operation: "queue_candidate_intervention", intervention: {
interventionId: "candidate-1", kind: "world_advocacy", status: "queued", target: { kind: "room", id: "room-1" },
summary: "Answer the source-room claim.", draft: "This answer stays with the conversation that made it necessary.",
deliveryTarget: { channelId: "room-1", replyToMessageId: "message-1" }, priority: 0.8, mustEventuallyShare: true,
createdAt: physiologyObservedAt.toISOString(), updatedAt: physiologyObservedAt.toISOString(), tags: ["source_pressure:pressure-1"],
},
};
const parsedRumination = parseVoidRuminationOperations({ outputText: JSON.stringify([ruminationCandidate]), state: ruminationState });
assert.equal(parsedRumination[0]?.operation, "queue_candidate_intervention", "person-shaped rumination crosses the typed operation boundary without delivering speech");
let ruminationModelCalls = 0;
const ruminationDependencies = {
loadState: async () => ruminationState,
projectText: async () => { ruminationModelCalls += 1; return ruminationModelCalls === 1 ? JSON.stringify([ruminationCandidate]) : "[]"; },
applyOperation: async (_store: unknown, operation: { operation: string; run?: { runner: string; ranAt: string; summary: string }; intervention?: unknown }) => {
if (operation.operation === "record_scheduled_run" && operation.run) ruminationState.scheduledRuntime.lastRuns.push(operation.run);
if (operation.operation === "queue_candidate_intervention" && operation.intervention) {
const index = ruminationState.candidateInterventions.interventions.findIndex((entry) => entry.interventionId === (operation.intervention as { interventionId: string }).interventionId);
if (index >= 0) ruminationState.candidateInterventions.interventions[index] = operation.intervention as never;
else ruminationState.candidateInterventions.interventions.push(operation.intervention as never);
}
return ruminationState;
},
};
const ruminated = await runVoidRumination({ statePath: "void-self-state.cc", observedAt: physiologyObservedAt, pendingMentions: [{ messageId: "message-1", channelId: "room-1" }], recentHistory: { messages: [{ id: "message-1", timestamp: "2026-07-16T02:55:00.000Z" }] }, doctrine: "Participate honestly.", rules: "Fixture rules.", voice: "Dry and direct." }, ruminationDependencies as never);
assert.equal(ruminated.status, "ok", "the rumination coordinator applies one bounded pressure pass");
const repeatedRumination = await runVoidRumination({ statePath: "void-self-state.cc", observedAt: new Date("2026-07-16T04:00:00.000Z"), pendingMentions: [{ messageId: "message-1", channelId: "room-1" }], recentHistory: { messages: [{ id: "message-1", timestamp: "2026-07-16T02:55:00.000Z" }] }, doctrine: "Participate honestly.", rules: "Fixture rules.", voice: "Dry and direct." }, ruminationDependencies as never);
assert.equal(repeatedRumination.status, "ok", "a meaning-bearing state change may authorize the next rumination pressure fingerprint");
const settledRumination = await runVoidRumination({ statePath: "void-self-state.cc", observedAt: new Date("2026-07-16T05:00:00.000Z"), pendingMentions: [{ messageId: "message-1", channelId: "room-1" }], recentHistory: { messages: [{ id: "message-1", timestamp: "2026-07-16T02:55:00.000Z" }] }, doctrine: "Participate honestly.", rules: "Fixture rules.", voice: "Dry and direct." }, ruminationDependencies as never);
assert.equal(settledRumination.status, "skipped", "an unchanged settled pressure cannot become a timer-driven inference loop");
assert.equal(ruminationModelCalls, 2, "rumination cadence follows changed pressure rather than elapsed clock");
const maintainedState = structuredClone(physiologyState);
let memoryModelCalls = 0;
const memoryMaintenanceDependencies = {
@ -955,6 +1003,16 @@ const memoryModelOutput = await projectVoidMemoryOperations({ prompt: "Maintain
assert.equal(memoryModelOutput, "[]", "the portable memory actuator returns only model text to the operation parser");
assert.equal(memoryRequestBody?.model, "memory-model", "the memory actuator uses one explicit configured model and bounded completion budget");
assert.equal(memoryRequestBody?.tools, undefined, "memory maintenance cannot acquire filesystem or side-effect tools through the model actuator");
let ruminationRequestBody: Record<string, unknown> | undefined;
const ruminationModelOutput = await projectVoidRuminationOperations({ prompt: "Ruminate from typed pressure.", config: { openAiApi: { baseUrl: "https://model.invalid/v1", apiKey: "secret", model: "rumination-model", timeoutMs: 1000, authHeader: "Authorization", maxCompletionTokens: 512 } } as never }, {
fetch: async (_url, init) => {
ruminationRequestBody = JSON.parse(String(init?.body));
return new Response(JSON.stringify({ choices: [{ message: { content: "[]" } }] }), { status: 200 });
},
});
assert.equal(ruminationModelOutput, "[]", "the rumination actuator returns only model text to the typed parser");
assert.equal(ruminationRequestBody?.model, "rumination-model", "rumination uses one bounded configured text model");
assert.equal(ruminationRequestBody?.tools, undefined, "person-shaped rumination cannot acquire transport or filesystem tools");
let moderationRequestBody: Record<string, unknown> | undefined;
const moderationModelOutput = await projectVoidModerationOperations({ prompt: "Review moderation evidence.", config: { openAiApi: { baseUrl: "https://model.invalid/v1", apiKey: "secret", model: "moderation-model", timeoutMs: 1000, authHeader: "Authorization", maxCompletionTokens: 512 } } as never }, {
fetch: async (_url, init) => {