How to Debug Connection Cooldowns and Model Lockout States in OmniRoute

To debug connection cooldowns and model lockout states in OmniRoute, inspect the Domain Lockout Policy at src/domain/lockoutPolicy.ts, which tracks failed attempts in a hybrid in-memory/SQLite cache and exposes checkLockout, recordFailedAttempt, and clearAllLockouts for runtime inspection.

OmniRoute implements connection-rate throttling and model lockout protection through a centralized Domain Lockout Policy. When troubleshooting why a client receives 429 or 403 errors, you need to understand how the system tracks failed authentication attempts and manages cooldown windows. This guide shows you how to debug connection cooldowns and model lockout states in OmniRoute by examining the state lifecycle, inspecting the hybrid storage layer, and using the exposed debugging utilities.

Understanding the Domain Lockout Architecture

OmniRoute isolates lockout logic inside src/domain/lockoutPolicy.ts. This module tracks failed authentication or request attempts per identifier—such as IP address, API key, or username—and determines when to enforce a cooldown.

The system uses a two-tier storage strategy:

  • In-memory cache: A JavaScript Map named lockoutCache provides fast lookups for active lockout states.
  • SQLite persistence: The module falls back to src/lib/db/domainState.ts for durable storage, ensuring lockout states survive process restarts.

When debugging, you can interact with either layer directly using the exported functions from the lockout policy module.

Debugging the Lockout State Lifecycle

The lockout mechanism follows a predictable lifecycle. Understanding these six phases helps you identify why a request is being rejected.

State Loading and Cache Behavior

When checkLockout is invoked, it first attempts to retrieve a cached lockout record from the in-memory lockoutCache. If the cache misses, the function loads the persisted record from SQLite via loadLockoutState (lines 41‑49).

import { checkLockout } from '@/domain/lockoutPolicy';

const identifier = '192.168.1.42'; // IP, API key, or username
const result = checkLockout(identifier);

if (result.locked) {
  console.log(
    `Locked out – ${result.remainingMs! / 1000}s remaining (attempts: ${result.attempts})`
  );
} else {
  console.log('No lockout – safe to proceed');
}

Expiration Handling and Cleanup

If a record exists and lockedUntil is in the future, the function returns locked: true together with the remaining cooldown duration in remainingMs (lines 86‑92).

When the lockout window has elapsed, the module automatically clears lockedUntil and the attempt list, persisting the refreshed state back to the database (lines 95‑99).

Attempt Counting and Pruning

Each failed attempt is recorded via recordFailedAttempt, which stores timestamped entries in state.attempts. The policy prunes attempts older than attemptWindowMs (default 5 minutes) before evaluating the threshold.

Lockout Trigger Conditions

When the number of recent attempts reaches maxAttempts (default 5), the service calculates the lockout expiry and sets lockedUntil = Date.now() + lockoutDurationMs (default 15 minutes). It then returns a lockout response (lines 133‑138).

Policy Engine Integration

The src/domain/policyEngine.ts orchestrator invokes checkLockout early in the request pipeline (line 52). If the client is locked, the engine aborts the request with a 429 or 403 error before reaching the model layer.

Practical Debugging Techniques

Use these runtime utilities to inspect and manipulate lockout states during development.

Checking Current Lockout Status

To verify whether a specific identifier is currently locked out, call checkLockout directly from your debugging console or test script:

import { checkLockout } from '@/domain/lockoutPolicy';

const identifier = '192.168.1.42';
const result = checkLockout(identifier);

if (result.locked) {
  console.log(
    `Locked out – ${result.remainingMs! / 1000}s remaining (attempts: ${result.attempts})`
  );
} else {
  console.log('No lockout – safe to proceed');
}

(See checkLockout definition at lines 80‑92.)

Resetting All Lockouts

During development or integration testing, you may need to clear all active lockouts without waiting for expiration. The clearAllLockouts function removes every entry from both the in-memory cache and the SQLite database:

import { clearAllLockouts } from '@/domain/lockoutPolicy';

// Remove every entry from the in-memory cache and DB
clearAllLockouts();
console.log('All lockouts cleared – next request will be allowed');

(See clearAllLockouts near the end of src/domain/lockoutPolicy.ts.)

Simulating Failed Attempts

To test the lockout trigger threshold manually, use recordFailedAttempt to programmatically register failures:

import { recordFailedAttempt, checkLockout } from '@/domain/lockoutPolicy';

const id = 'my-api-key';
for (let i = 0; i < 6; i++) {
  recordFailedAttempt(id);
}
const status = checkLockout(id);
console.log(status); // { locked: true, remainingMs: … }

(See recordFailedAttempt implementation in src/domain/lockoutPolicy.ts.)

Inspecting the SQLite Persistence Layer

Because lockout state is durably stored in SQLite via src/lib/db/domainState.ts, you can inspect the raw database for debugging. The module provides three low-level helpers:

  • loadLockoutState(identifier): Retrieves the persisted record.
  • saveLockoutState(identifier, state): Writes the current state to disk.
  • deleteLockoutState(identifier): Removes the record entirely.

You can query the underlying database directly using SQLite CLI tools to verify that lockedUntil timestamps and attempt arrays match your expectations.

Summary

  • OmniRoute centralizes lockout logic in src/domain/lockoutPolicy.ts, using a hybrid in-memory/SQLite cache.
  • State lifecycle: Check cache → load from DB → validate lockedUntil → prune old attempts → count recent failures → trigger lockout if threshold exceeded.
  • Debugging utilities: Use checkLockout to inspect status, clearAllLockouts to wipe all states, and recordFailedAttempt to simulate failures.
  • Integration point: The src/domain/policyEngine.ts invokes checkLockout at line 52, returning 429/403 for locked clients.
  • Persistence: Raw state is accessible via src/lib/db/domainState.ts for direct database inspection.

Frequently Asked Questions

How do I check if a specific IP is currently locked out in OmniRoute?

Call checkLockout(identifier) from the src/domain/lockoutPolicy.ts module, passing the IP address as the identifier. The function returns an object with locked: boolean and remainingMs: number indicating the cooldown time left.

Where is the lockout state stored in OmniRoute?

OmniRoute uses a two-tier system: an in-memory Map called lockoutCache for fast access, and SQLite for persistence. The SQLite operations are handled in src/lib/db/domainState.ts via loadLockoutState and saveLockoutState.

How do I clear all active lockouts without restarting the server?

Import clearAllLockouts from src/domain/lockoutPolicy.ts and invoke it. This function clears both the in-memory cache and the SQLite database, immediately allowing all previously locked identifiers to make requests.

What are the default thresholds for triggering a lockout?

By default, OmniRoute allows 5 failed attempts within a 5-minute window (attemptWindowMs). When the sixth attempt occurs, the system imposes a 15-minute lockout (lockoutDurationMs). These values are configurable in the lockout policy configuration.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →