diff --git a/.env.example b/.env.example index 49f68d2..7440287 100644 --- a/.env.example +++ b/.env.example @@ -17,6 +17,7 @@ VOID_PUBLIC_ROOM_CHANNEL_ID= VOID_PUBLIC_ROOM_PERSONA_NAME=Void VOID_PUBLIC_ROOM_PERSONA_AVATAR_URL= VOID_CANDIDATE_DELIVERY_ENABLED=false +VOID_PHYSIOLOGY_INTERVAL_MINUTES=5 VOID_RUMINATION_DAEMON_ENABLED=false VOID_RUMINATION_DAEMON_INTERVAL_MINUTES=15 STATE_STORAGE_BACKEND=postgres diff --git a/apps/persona-scheduler/src/index.ts b/apps/persona-scheduler/src/index.ts index 4d59921..3eb90eb 100644 --- a/apps/persona-scheduler/src/index.ts +++ b/apps/persona-scheduler/src/index.ts @@ -24,7 +24,7 @@ let timer: NodeJS.Timeout | undefined; let lastPhysiologyAt = 0; let lastModerationAt = 0; let lastRuminationAt = 0; -const physiologyIntervalMs = Math.max(5, Number(process.env.VOIDBOT_MOOD_INTERVAL_MINUTES) || 5) * 60_000; +const physiologyIntervalMs = config.voidPhysiology.intervalMinutes * 60_000; const moderationIntervalMs = config.voidModerationDaemon.intervalMinutes * 60_000; const ruminationIntervalMs = config.voidRuminationDaemon.intervalMinutes * 60_000; diff --git a/apps/persona-scheduler/src/initiative-engine.ts b/apps/persona-scheduler/src/initiative-engine.ts index 6ae8fc6..038b5db 100644 --- a/apps/persona-scheduler/src/initiative-engine.ts +++ b/apps/persona-scheduler/src/initiative-engine.ts @@ -2,8 +2,8 @@ import { createHash } from "node:crypto"; export interface InitiativeParticipant { identityId: string; - participantKind: "repo_face" | "native_persona" | "system_agent"; - turnKind: "repo_face_rumination" | "void_moderation"; + participantKind: "repo_face" | "native_persona"; + turnKind: "repo_face_rumination"; repoName: string; displayName: string; initiativeSpeed: number; @@ -94,7 +94,7 @@ export function reconcileParticipants(input: ReconcileParticipantsInput): Initia return input.specs.map((spec, index) => { const current = existingById.get(spec.id); - const hasChannel = spec.participantKind === "system_agent" || Boolean(spec.allowedChannelIds[0] || input.defaultChannelId); + const hasChannel = Boolean(spec.allowedChannelIds[0] || input.defaultChannelId); const speed = initiativeSpeedFor(spec, input.speedOverrides) * spec.channelSpeedMultiplier; const groups = initiativeGroupsFor(spec); const heat = heatFor(spec, groups, input.globalHeat, input.heatOverrides); diff --git a/apps/persona-scheduler/src/persona-scheduler-runner.ts b/apps/persona-scheduler/src/persona-scheduler-runner.ts index 41aa5fb..6d0a0cc 100644 --- a/apps/persona-scheduler/src/persona-scheduler-runner.ts +++ b/apps/persona-scheduler/src/persona-scheduler-runner.ts @@ -89,7 +89,7 @@ export async function runPersonaSchedulerTick(input: { const pressure = await readSemanticPressure({ candidates: participantSpecs.flatMap((spec) => { const participant = state.participants.find((entry) => entry.identityId === spec.id); - if (!spec.identity || spec.participantKind === "system_agent" || participant?.status !== "active" || participant.currentLoad >= 1 || completedThisTick.has(participant.identityId) || restStates.get(participant.identityId)?.isNapping === true) return []; + if (!spec.identity || participant?.status !== "active" || participant.currentLoad >= 1 || completedThisTick.has(participant.identityId) || restStates.get(participant.identityId)?.isNapping === true) return []; return [{ identityId: participant.identityId, interruptThreshold: participant.interruptThreshold, affinityText: renderPersonaInitiativeAffinity(spec), allowedChannelIds: spec.allowedChannelIds }]; }), messages: idleCooling.observedHumanMessages, diff --git a/apps/persona-scheduler/src/state-store.ts b/apps/persona-scheduler/src/state-store.ts index 5cc8064..1a488d6 100644 --- a/apps/persona-scheduler/src/state-store.ts +++ b/apps/persona-scheduler/src/state-store.ts @@ -272,8 +272,8 @@ function isInitiativeParticipant(value: unknown): value is InitiativeParticipant if (!isRecord(value)) return false; return ( typeof value.identityId === "string" && - ["repo_face", "native_persona", "system_agent"].includes(String(value.participantKind)) && - ["repo_face_rumination", "void_moderation"].includes(String(value.turnKind)) && + ["repo_face", "native_persona"].includes(String(value.participantKind)) && + value.turnKind === "repo_face_rumination" && typeof value.repoName === "string" && typeof value.displayName === "string" && ["active", "blocked", "withdrawn", "offscreen"].includes(String(value.status)) && diff --git a/packages/config/src/index.ts b/packages/config/src/index.ts index 95a884a..db6f25d 100644 --- a/packages/config/src/index.ts +++ b/packages/config/src/index.ts @@ -140,6 +140,7 @@ const envSchema = z.object({ VOID_MODERATION_DAEMON_INTERVAL_MINUTES: z.coerce.number().int().min(5).default(15), VOID_MODERATION_ENFORCEMENT_MODE: z.string().trim().min(1).default("log_only"), VOID_CANDIDATE_DELIVERY_ENABLED: booleanFromEnv.default(false), + VOID_PHYSIOLOGY_INTERVAL_MINUTES: z.coerce.number().int().min(5).default(5), VOID_RUMINATION_DAEMON_ENABLED: booleanFromEnv.default(false), VOID_RUMINATION_DAEMON_INTERVAL_MINUTES: z.coerce.number().int().min(5).default(15), VOID_PUBLIC_ROOM_PERSONA_NAME: z.string().trim().min(1).default("Void"), @@ -256,6 +257,7 @@ export interface AppConfig { }; voidModerationDaemon: { enabled: boolean; intervalMinutes: number; enforcementMode: string }; voidCandidateDelivery: { enabled: boolean; personaName: string; personaAvatarUrl?: string }; + voidPhysiology: { intervalMinutes: number }; voidRuminationDaemon: { enabled: boolean; intervalMinutes: number }; repoFaceHeartbeats: { enabled: boolean; @@ -556,6 +558,7 @@ export function loadConfig(env: NodeJS.ProcessEnv = process.env): AppConfig { personaName: parsed.VOID_PUBLIC_ROOM_PERSONA_NAME, personaAvatarUrl: parsed.VOID_PUBLIC_ROOM_PERSONA_AVATAR_URL, }, + voidPhysiology: { intervalMinutes: parsed.VOID_PHYSIOLOGY_INTERVAL_MINUTES }, voidRuminationDaemon: { enabled: parsed.VOID_RUMINATION_DAEMON_ENABLED, intervalMinutes: parsed.VOID_RUMINATION_DAEMON_INTERVAL_MINUTES, diff --git a/state/evidence.jsonl b/state/evidence.jsonl index 8484791..6de64ac 100644 --- a/state/evidence.jsonl +++ b/state/evidence.jsonl @@ -453,3 +453,4 @@ {"ts":"2026-07-16T04:58:31Z","type":"architecture","status":"ok","note":"Cut the obsolete Windows Void rumination authority: deleted the 1,762-line runner, task installer, PowerShell-only rumination/moderation fixtures, CTB system-agent launcher, and dead moderation-lock observers; removed both Void organs from Local Orchestrator and removed legacy config/scripts. Persona initiative now registers only real Persona identities. Consolidated resident-organ smoke and TypeScript build pass, and deployed b09267e proved both model cadences cold before the cut."} {"ts":"2026-07-16T05:03:14Z","type":"architecture","status":"ok","note":"Cut the remaining local Void sleep/memory authority: deleted mood task/simulator, PowerShell memory runner, shared shell chronology projector, obsolete prompt, and shell-only fixtures. Resident physiology and nap-deduped memory maintenance are now the only owners. Direct Void Discord mentions now use the normal bot prompt/job lane rather than impossible built-in-CTB attention; live scheduler state had 22 registered Personas, zero Void pending mentions, and zero total pending debt before rollout."} {"ts":"2026-07-16T05:07:08Z","type":"architecture","status":"ok","note":"Deleted the final Windows omnibus cadence, installer, and hidden launcher; removed startup auto-install, watchdog fallback ownership, and orchestrator provider witness. Unregistered the five disabled Starfire task husks for Local Orchestrator, mood, moderation rumination, repo Face heartbeats, and Bifrost dispatch. Swarm publication remains Ygg-resident, health belongs to Idunn, and Bifrost dispatch belongs to Bifrost."} +{"ts":"2026-07-16T05:10:08Z","type":"architecture","status":"ok","note":"Completion audit removed the final scheduler type loophole: system_agent and void_moderation are no longer valid participant/turn kinds, so legacy state cannot deserialize a built-in Void CTB writer. Physiology cadence is now a typed VOID_PHYSIOLOGY_INTERVAL_MINUTES config owned by the resident daemon rather than a raw legacy mood environment name."}