/** * Panic Response Layer — behavioral destabilization detection. * * Separate from EpistemicLease (freshness = epistemic authority decay). * Panic = observable behavioral instability: oscillation, trajectory bursts, * repeated stale-depth-3 persistence. * * State file: .openlore/panic-state.json (atomic writes, fail-open reads). * Hook consumer: `openlore panic-check` reads this file before every agent tool call. */ export type PanicLevel = 0 | 1 | 2 | 3 | 4; export interface PanicState { schemaVersion: 1; panicScore: number; panicLevel: PanicLevel; updatedAt: string; lastOrientAt: string; lastHookInterventionAt?: string; recentOrientCount: number; localityConfidence: number; interventionCountSinceStable: number; triggers: string[]; /** ISO — upward signals suppressed until this timestamp after an orient() recovery. */ panicRecoverySuppressionUntil?: string; /** ISO — start of the Gryph query window for the panic-check hook path. Advanced on each intervention write. */ gryphWindowStart?: string; agentId?: string; sessionId?: string; /** Monotonically increasing write counter. Used for CAS by concurrent writers (Gryph poll vs MCP). */ revision: number; } export interface PanicCheckOutput { decision: 'allow' | 'warn'; severity?: 'elevated' | 'panic' | 'scope' | 'critical'; message?: string; } export declare function applyPanicHysteresis(current: PanicLevel, score: number, staleDepth: number): PanicLevel; export declare function defaultPanicState(): PanicState; /** * Reads panic state. Fails open on all error paths: * missing file, parse error, wrong schema version, expired/invalid session. * * panic-state.json is a hand-editable on-disk file, so every field is treated as * untrusted: numeric fields are coerced and clamped so a garbage value (NaN, a * string, an out-of-range level) can't poison scoring or index off the end of * SEVERITY_MAP/DIRECTIVE_MESSAGES, and a non-parseable `updatedAt` (NaN age) is * treated as expired rather than letting a zombie state survive forever. */ export declare function readPanicState(directory: string): PanicState; /** * Atomically writes panic state. POSIX rename(2) is atomic on same filesystem. * Bumps revision on every write — callers sync their own revision counter from the return value. * Never throws — must not crash the hot path. * Returns the new revision written (or the existing revision if write failed). */ export declare function writePanicState(directory: string, state: PanicState): number; export declare const LOCK_ATTEMPTS_DAEMON = 12; /** * Compare-and-swap write across concurrent writers (Gryph poll vs MCP vs hook). * Serialized by an exclusive cross-process lock so the read-check-write is atomic against OTHER * PROCESSES (not just within one event loop). Returns false if on-disk revision !== expectedRevision * (stale read → caller retries), the lock could not be acquired, or the write failed. * `maxAttempts` lets the background daemon use a short budget (LOCK_ATTEMPTS_DAEMON). */ export declare function casWritePanicState(directory: string, expectedRevision: number, state: PanicState, maxAttempts?: number): boolean; /** * Atomically (cross-process) record a hook intervention: re-read the freshest state under the lock, * bump interventionCountSinceStable, merge the given fields, and persist. Returns the new count. * This prevents concurrent panic-check processes from losing increments (last-writer-wins on a * non-locked read-modify-write under-counts the advisory→directive escalation gate). */ export declare function recordHookInterventionLocked(directory: string, fields: { lastHookInterventionAt: string; gryphWindowStart?: string; }, fallbackCount: number): number; /** * Locked read-modify-write for panic-state.json: under the cross-process lock, read the * FRESHEST on-disk state, apply `mutate(fresh)`, persist with a bumped revision, and return * the written state. This is the serialization primitive the MCP writer uses so it cannot * clobber a concurrent panic-check hook (`recordHookInterventionLocked`) or gryph daemon * (`casWritePanicState`) write — in particular the cross-process `interventionCountSinceStable` * counter stays monotonic (the MCP path previously read-then-wrote via an unlocked * `writePanicState`, racing the hook's locked increment). * * Because the read and write happen under the same lock, no separate CAS retry is needed — * the lock guarantees no other writer interleaves. Fails open: if the lock cannot be acquired * the mutation is applied to a fresh read and written best-effort (unlocked), degrading to the * prior behavior rather than blocking the hot path. Never throws. */ export declare function mutatePanicStateLocked(directory: string, mutate: (fresh: PanicState) => PanicState): PanicState; /** * Builds the structured output for the panic-check CLI hook consumer. * Always exits 0 — severity encoded in payload, not exit code. * Applies per-level cooldown: no-ops if intervention fired recently. */ export declare function buildPanicCheckOutput(state: PanicState): PanicCheckOutput; /** Advisory MCP response injection begins at L2 — the documented advisory-injection floor and the * same threshold the accuracy gate/calibration measure (L1 is "elevated": tracked, not intervened on). * Keeping L1 silent avoids nagging on a weak signal and matches `advisory (surface a signal at L2+)`. */ export declare const PANIC_INJECTION_MIN_LEVEL = 2; /** * Returns panic signal text for MCP tool response injection, or null below the injection floor. * Appended after result (not prepended) to preserve JSON structure. */ export declare function getPanicSignalText(state: PanicState): string | null; //# sourceMappingURL=panic-response.d.ts.map