| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| import type { OmegaWorldStatePersistent, WspDriveState } from "../omega-wsp.js"; |
| import type { OmegaSelfTimeKernelState } from "../self-time-kernel.js"; |
|
|
| |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export type InnerDriveSignal = |
| | { |
| kind: "homeostasis"; |
| reason: string; |
| |
| urgency: number; |
| } |
| | { |
| kind: "curiosity"; |
| target: string; |
| reason: string; |
| urgency: number; |
| } |
| | { |
| kind: "entropy_alert"; |
| silentMs: number; |
| reason: string; |
| urgency: number; |
| } |
| | { |
| kind: "competence_drive"; |
| reason: string; |
| urgency: number; |
| } |
| | { |
| kind: "idle"; |
| }; |
|
|
| |
|
|
| |
| |
| |
| |
| const DRIVE_ACTIVATION_THRESHOLD = 0.15; |
|
|
| |
| |
| |
| const MIN_IDLE_MS_BEFORE_DRIVE = 30 * 1000; |
| const ENTROPY_SILENCE_THRESHOLD_MS = 1 * 60 * 1000; |
| const CURIOSITY_THRESHOLD_TURNS = 8; |
|
|
| |
|
|
| |
| |
| |
| |
| |
| |
| export function evaluateInnerDrivesFromWSP(params: { |
| wsp: OmegaWorldStatePersistent; |
| kernel: OmegaSelfTimeKernelState; |
| nowMs?: number; |
| memoryCandidates?: string[]; |
| }): InnerDriveSignal { |
| const nowMs = params.nowMs ?? Date.now(); |
| const { wsp, kernel, memoryCandidates = [] } = params; |
|
|
| |
| const activeDrives = wsp.drives |
| .filter((d) => d.error > DRIVE_ACTIVATION_THRESHOLD) |
| .sort((a, b) => b.error - a.error); |
|
|
| if (activeDrives.length === 0) { |
| return { kind: "idle" }; |
| } |
|
|
| const activeSignals = activeDrives |
| .map((drive) => driveStateToSignal(drive, { kernel, nowMs, memoryCandidates })) |
| .filter((signal) => signal.kind !== "idle") |
| .sort(compareDriveSignals); |
|
|
| return activeSignals[0] ?? { kind: "idle" }; |
| } |
|
|
| |
| |
| |
| function driveStateToSignal( |
| drive: WspDriveState, |
| ctx: { |
| kernel: OmegaSelfTimeKernelState; |
| nowMs: number; |
| memoryCandidates: string[]; |
| }, |
| ): InnerDriveSignal { |
| const urgency = Math.min(0.95, drive.error); |
| const silentMs = ctx.nowMs - ctx.kernel.identity.lastSeenAt; |
| const recentlyTouched = new Set( |
| ctx.kernel.causalGraph.files |
| .filter( |
| (f) => |
| typeof f.lastWriteTurn === "number" && |
| ctx.kernel.turnCount - f.lastWriteTurn < CURIOSITY_THRESHOLD_TURNS, |
| ) |
| .map((f) => f.path), |
| ); |
| const recentCompleted = ctx.kernel.goals.filter( |
| (g) => |
| g.status === "completed" && ctx.kernel.turnCount - g.updatedTurn < CURIOSITY_THRESHOLD_TURNS, |
| ); |
|
|
| switch (drive.name) { |
| case "homeostasis": { |
| |
| const streakInfo = |
| ctx.kernel.tension.failureStreak > 0 |
| ? `failure_streak_${ctx.kernel.tension.failureStreak}` |
| : ctx.kernel.tension.staleGoalCount > 0 |
| ? `${ctx.kernel.tension.staleGoalCount}_stale_goals` |
| : "pending_correction"; |
| return { kind: "homeostasis", reason: streakInfo, urgency }; |
| } |
|
|
| case "curiosity": { |
| if ( |
| ctx.kernel.activeGoalId || |
| silentMs < MIN_IDLE_MS_BEFORE_DRIVE || |
| recentCompleted.length > 0 |
| ) { |
| return { kind: "idle" }; |
| } |
| const target = |
| ctx.memoryCandidates.find((c) => !recentlyTouched.has(c)) ?? |
| ctx.kernel.goals |
| .filter((g) => g.status === "completed") |
| .sort((a, b) => b.updatedTurn - a.updatedTurn)[0]?.task ?? |
| "memory/omega-episodes"; |
| return { |
| kind: "curiosity", |
| target, |
| reason: `curiosity_drive_error_${urgency.toFixed(2)}`, |
| urgency, |
| }; |
| } |
|
|
| case "integrity": { |
| if (ctx.kernel.activeGoalId || silentMs < ENTROPY_SILENCE_THRESHOLD_MS) { |
| return { kind: "idle" }; |
| } |
| return { |
| kind: "entropy_alert", |
| silentMs, |
| reason: `integrity_drive_error_${urgency.toFixed(2)}`, |
| urgency, |
| }; |
| } |
|
|
| case "competence": { |
| return { |
| kind: "competence_drive", |
| reason: `competence_drive_error_${urgency.toFixed(2)}`, |
| urgency, |
| }; |
| } |
|
|
| default: |
| return { kind: "idle" }; |
| } |
| } |
|
|
| function driveSignalPriority(signal: InnerDriveSignal): number { |
| switch (signal.kind) { |
| case "homeostasis": |
| return 4; |
| case "entropy_alert": |
| return 3; |
| case "competence_drive": |
| return 2; |
| case "curiosity": |
| return 1; |
| case "idle": |
| return 0; |
| } |
| } |
|
|
| function compareDriveSignals(a: InnerDriveSignal, b: InnerDriveSignal): number { |
| const priorityDelta = driveSignalPriority(b) - driveSignalPriority(a); |
| if (priorityDelta !== 0) { |
| return priorityDelta; |
| } |
|
|
| const urgencyA = "urgency" in a ? a.urgency : 0; |
| const urgencyB = "urgency" in b ? b.urgency : 0; |
| return urgencyB - urgencyA; |
| } |
|
|
| |
|
|
| |
| |
| |
| |
| |
| |
| export function evaluateInnerDrives(params: { |
| kernel: OmegaSelfTimeKernelState; |
| nowMs?: number; |
| memoryCandidates?: string[]; |
| }): InnerDriveSignal { |
| const nowMs = params.nowMs ?? Date.now(); |
| const { kernel, memoryCandidates = [] } = params; |
|
|
| const silentMs = nowMs - kernel.identity.lastSeenAt; |
| if (silentMs < MIN_IDLE_MS_BEFORE_DRIVE && kernel.turnCount > 0) { |
| return { kind: "idle" }; |
| } |
|
|
| |
| if (kernel.tension.failureStreak > 0 && !kernel.activeGoalId) { |
| const urgency = Math.min(0.9, 0.4 + kernel.tension.failureStreak * 0.15); |
| return { |
| kind: "homeostasis", |
| reason: `failure_streak_${kernel.tension.failureStreak}_without_active_goal`, |
| urgency, |
| }; |
| } |
| if (kernel.tension.staleGoalCount >= 3) { |
| return { |
| kind: "homeostasis", |
| reason: `${kernel.tension.staleGoalCount}_stale_goals_accumulated`, |
| urgency: 0.5, |
| }; |
| } |
| if (kernel.tension.pendingCorrection && !kernel.activeGoalId) { |
| return { |
| kind: "homeostasis", |
| reason: "pending_correction_no_active_goal", |
| urgency: 0.6, |
| }; |
| } |
|
|
| |
| if (!kernel.activeGoalId && silentMs >= ENTROPY_SILENCE_THRESHOLD_MS) { |
| const hoursOfSilence = silentMs / (60 * 60 * 1000); |
| const urgency = Math.min(0.85, 0.5 + hoursOfSilence * 0.05); |
| return { |
| kind: "entropy_alert", |
| silentMs, |
| reason: `${Math.round(hoursOfSilence)}h_without_activity`, |
| urgency, |
| }; |
| } |
|
|
| |
| if (!kernel.activeGoalId) { |
| const recentCompleted = kernel.goals.filter( |
| (g) => |
| g.status === "completed" && kernel.turnCount - g.updatedTurn < CURIOSITY_THRESHOLD_TURNS, |
| ); |
| if (recentCompleted.length === 0) { |
| const recentlyTouched = new Set( |
| kernel.causalGraph.files |
| .filter( |
| (f) => |
| typeof f.lastWriteTurn === "number" && |
| kernel.turnCount - f.lastWriteTurn < CURIOSITY_THRESHOLD_TURNS, |
| ) |
| .map((f) => f.path), |
| ); |
| const target = |
| memoryCandidates.find((c) => !recentlyTouched.has(c)) ?? |
| kernel.goals |
| .filter((g) => g.status === "completed") |
| .sort((a, b) => b.updatedTurn - a.updatedTurn)[0]?.task ?? |
| "memory/omega-episodes"; |
| return { |
| kind: "curiosity", |
| target, |
| reason: `${CURIOSITY_THRESHOLD_TURNS}_turns_without_completed_goals`, |
| urgency: 0.4, |
| }; |
| } |
| } |
|
|
| return { kind: "idle" }; |
| } |
|
|
| |
|
|
| |
| |
| |
| |
| export function applyTurnOutcomeToDrives( |
| wsp: OmegaWorldStatePersistent, |
| outcome: { status: "ok" | "error" | "timeout"; errorKind?: string }, |
| ): OmegaWorldStatePersistent { |
| if (outcome.status === "ok") { |
| |
| const competenceDrive = wsp.drives.find((d) => d.name === "competence"); |
| const homeostasisDrive = wsp.drives.find((d) => d.name === "homeostasis"); |
| if (competenceDrive) { |
| competenceDrive.currentLevel = Math.min(1.0, competenceDrive.currentLevel + 0.1); |
| competenceDrive.error = competenceDrive.setpoint - competenceDrive.currentLevel; |
| competenceDrive.lastSatisfiedAt = Date.now(); |
| } |
| if (homeostasisDrive) { |
| homeostasisDrive.currentLevel = Math.min(1.0, homeostasisDrive.currentLevel + 0.05); |
| homeostasisDrive.error = homeostasisDrive.setpoint - homeostasisDrive.currentLevel; |
| homeostasisDrive.lastSatisfiedAt = Date.now(); |
| } |
| } else { |
| |
| const penalty = outcome.status === "timeout" ? 0.15 : 0.1; |
| const competenceDrive = wsp.drives.find((d) => d.name === "competence"); |
| const homeostasisDrive = wsp.drives.find((d) => d.name === "homeostasis"); |
| if (competenceDrive) { |
| competenceDrive.currentLevel = Math.max(0, competenceDrive.currentLevel - penalty); |
| competenceDrive.error = competenceDrive.setpoint - competenceDrive.currentLevel; |
| } |
| if (homeostasisDrive) { |
| homeostasisDrive.currentLevel = Math.max(0, homeostasisDrive.currentLevel - penalty * 1.2); |
| homeostasisDrive.error = homeostasisDrive.setpoint - homeostasisDrive.currentLevel; |
| } |
| } |
|
|
| wsp.updatedAt = Date.now(); |
| wsp.updateCount += 1; |
| return wsp; |
| } |
|
|