import type { CacheRegistry, CacheFootprint } from './cache-registry.js'; import type { PauseController } from './pause-controller.js'; /** * Effective system memory in MB: the smaller of physical RAM and the cgroup * memory limit that applies to THIS PROCESS. The process's own cgroup is * resolved from /proc/self/cgroup and every ancestor directory up to the * cgroup root is checked (the effective limit is the MINIMUM along the chain) *, a daemon run under systemd MemoryMax= has its limit at * /sys/fs/cgroup/system.slice/.service/memory.max, which a root-only * read misses entirely. Root-path reads remain as the final fallback (a * container with a private cgroup namespace reports `0::/`, which the walk * covers anyway). Without this, a limited daemon budgets off physical RAM and * every tier sits ABOVE the kernel kill line: the kernel OOM-kills it while * the governor still reports 'normal'. */ export declare function resolveEffectiveSystemRamMb(readFile?: (path: string) => string, physicalRamMb?: number): number; export type MemoryTier = 'normal' | 'elevated' | 'high' | 'critical'; /** One memory sample. Heap fields are best-effort (bun:jsc where available). */ export interface MemorySample { readonly rssBytes: number; readonly heapUsedBytes: number; readonly heapTotalBytes?: number | undefined; } export type MemorySampler = () => MemorySample; /** Owner-confirmed governor configuration. */ export interface MemoryGovernorConfig { /** Budget in MB. 0 or negative ⇒ auto: min(25% of system RAM, 4096). */ readonly budgetMb: number; readonly elevatedPct: number; readonly highPct: number; readonly criticalPct: number; readonly tripwireRateMbPerSec: number; readonly tripwireSustainSec: number; /** * Absolute-RSS backstop, as a percent of the EFFECTIVE KILL CEILING, the * own-cgroup memory limit where one applies, else physical RAM (default 90). * The backstop exists to beat the kernel/cgroup OOM killer, so it anchors to * the line the kernel actually kills at, NOT to the budget: the default * budget deliberately caps at 4096MB, and a large-but-stable working set * legitimately above the budget (mmap'd sqlite pages, a big heap graph, * not registered caches, not reclaimable by flush) is the critical tier's * job (refuse expensive work, stay alive), never an exit condition. When RSS * holds at/above hardLimitPct% of the ceiling for the tripwire sustain * window, the governor takes the SAME graceful exit + receipt path as the * rate tripwire, catching the slow leak the rate condition is blind to, * without false-firing on healthy hosts with room to spare. */ readonly hardLimitPct?: number | undefined; /** Sampling cadence in ms (default 5000). */ readonly sampleIntervalMs?: number | undefined; } /** Injectable collaborators (all default to real implementations). */ export interface MemoryGovernorDeps { readonly caches: CacheRegistry; readonly pauses: PauseController; readonly sampler?: MemorySampler | undefined; readonly now?: (() => number) | undefined; readonly gc?: (() => void) | undefined; readonly resolveSystemRamMb?: (() => number) | undefined; /** Emit the ops attention event (tier change to critical, tripwire). */ readonly emitOps?: ((event: MemoryPressureEvent) => void) | undefined; /** Persist a tripwire receipt so a supervisor sees why the daemon exited. */ readonly writeReceipt?: ((receipt: MemoryTripwireReceipt) => void) | undefined; /** * Graceful shutdown hook run BEFORE the tripwire exit, the daemon * composition wires the same work its signal handlers do (session/store * snapshots, inhibitor release). Bounded by a 10s ceiling so a wedged hook * cannot pin a leaking daemon alive. */ readonly shutdown?: ((receipt: MemoryTripwireReceipt) => Promise | void) | undefined; /** Perform the final exit (default process.exit(1)). Injected in tests. */ readonly exit?: ((receipt: MemoryTripwireReceipt) => void) | undefined; } /** The ops attention event payload the governor hands to {@link MemoryGovernorDeps.emitOps}. */ export interface MemoryPressureEvent { readonly tier: MemoryTier; readonly previousTier: MemoryTier; readonly rssMb: number; readonly heapMb: number; readonly budgetMb: number; readonly usedPct: number; readonly tripwire?: { readonly rateMbPerSec: number; readonly sustainedSec: number; readonly action: 'exit'; } | undefined; readonly note?: string | undefined; } /** One recorded tier transition, oldest-first, carried in the exit receipt. */ export interface MemoryTierTransition { readonly at: number; readonly tier: MemoryTier; } /** Written to disk when the governor exits under memory pressure. */ export interface MemoryTripwireReceipt { readonly kind: 'memory-leak-tripwire'; /** * Which backstop fired: 'rate-tripwire', post-flush growth exceeded * tripwireRateMbPerSec for the sustain window (a FAST leak); 'hard-limit', * RSS held at/above hardLimitPct% of the effective kill ceiling (own-cgroup * limit or physical RAM) for the sustain window regardless of rate (a SLOW * leak the rate tripwire is structurally blind to, caught just before the * kernel/cgroup OOM killer would act). */ readonly trigger: 'rate-tripwire' | 'hard-limit'; readonly at: number; readonly rssMb: number; readonly budgetMb: number; readonly rateMbPerSec: number; readonly sustainedSec: number; readonly heap: MemorySample; readonly topCaches: readonly CacheFootprint[]; /** Tier ladder history (oldest-first) so the receipt shows how RSS climbed. */ readonly tierHistory: readonly MemoryTierTransition[]; readonly note: string; } /** The ops.memory verb payload, the full governor snapshot. */ export interface MemoryGovernorSnapshot { readonly tier: MemoryTier; readonly budgetMb: number; readonly rssMb: number; readonly heapUsedMb: number; readonly heapTotalMb?: number | undefined; readonly usedPct: number; readonly refusingExpensiveWork: boolean; readonly caches: readonly CacheFootprint[]; readonly pausedJobs: readonly string[]; readonly tripwire: { readonly armed: boolean; readonly sustainedSec: number; readonly rateMbPerSec: number; }; readonly thresholds: { readonly elevatedPct: number; readonly highPct: number; readonly criticalPct: number; }; } /** Structured refusal returned when the governor is at the critical tier. */ export interface ExpensiveWorkDecision { readonly allowed: boolean; readonly tier: MemoryTier; readonly reason?: string | undefined; } export declare class MemoryGovernor { private readonly caches; private readonly pauses; private readonly sampler; private readonly now; private readonly gc; private readonly emitOps; private readonly writeReceipt; private readonly shutdown; private readonly exit; private readonly sampleIntervalMs; private readonly budgetMb; private readonly effectiveCeilingMb; private readonly elevatedBytes; private readonly highBytes; private readonly criticalBytes; private readonly hardLimitBytes; private readonly hardLimitPct; private readonly tripwireRateBytesPerSec; private readonly tripwireSustainMs; private tier; private refusing; private timer; private exited; private tierHistory; private hardLimitOverSince; private tripwireArmed; private tripwireSamples; private tripwireOverSince; private lastRateMbPerSec; constructor(config: MemoryGovernorConfig, deps: MemoryGovernorDeps); /** Begin interval sampling. Idempotent. The timer is unref'd so it never pins the loop. */ start(): void; /** Stop sampling. */ stop(): void; /** The current tier. */ currentTier(): MemoryTier; /** * Consult the governor before starting expensive work. At the critical tier * the daemon refuses with an honest structured outcome rather than piling on * more allocation. */ admitExpensiveWork(label?: string): ExpensiveWorkDecision; /** The full governor snapshot served by the ops.memory verb. */ snapshot(): MemoryGovernorSnapshot; /** Take one sample and apply tier actions + tripwire. Exposed for deterministic tests. */ sampleOnce(): void; /** Append a bounded tier-transition entry for the exit receipt's history. */ private recordTierTransition; /** * Absolute-RSS backstop anchored to the EFFECTIVE KILL CEILING (own-cgroup * limit or physical RAM), not the budget. The rate tripwire is structurally * blind to any leak slower than tripwireRateMbPerSec, so a slow leak would * ride all the way to a kernel OOM kill with no exit and no receipt; when * RSS holds at/above hardLimitPct% of the ceiling for the sustain window, * take the SAME graceful exit path the rate tripwire uses. A stable working * set above the BUDGET but below the ceiling never fires this, that is the * critical tier's stay-alive posture, not an exit condition. */ private checkHardLimit; private tierFor; private applyTier; private armTripwire; private disarmTripwire; private checkTripwire; private fireExit; private emitPressure; private receipt; } //# sourceMappingURL=memory-governor.d.ts.map