ERGENEKON Engine Docs

ERGENEKON is a deterministic record-and-replay debugger for distributed Node.js systems. It intercepts all I/O at runtime, stores a byte-perfect recording, and lets you replay it locally β€” without a network, database, or external dependencies.

🎁 Launch offer: All tiers (Pro + Enterprise) are free during our launch period. No credit card required.

Package Overview

PackagePurposeInstall
@ergenekon/probe Express middleware β€” intercepts all I/O in production npm install @ergenekon/probe
@ergenekon/collector HTTP ingest server β€” receives and stores recordings npm install @ergenekon/collector
@ergenekon/replay Replay engine β€” deterministic re-execution from recordings npm install @ergenekon/replay
@ergenekon/core Shared types, HLC clock, ULID, license validation Installed as dependency
@ergenekon/cli Command-line tool β€” inspect, replay, export sessions npm install -g @ergenekon/cli

⚑ Quick Start

Get from zero to your first recorded replay in under 2 minutes.

  1. Install the packages
    npm install @ergenekon/probe @ergenekon/collector
  2. Start the Collector

    The Collector runs as a separate HTTP server on port 4380.

    npx ergenekon-collector --port 4380 --storage ./recordings
  3. Add the Probe to your Express app
    TypeScriptimport { ErgenekonProbe } from '@ergenekon/probe';
    import express from 'express';
    
    const app = express();
    const probe = new ErgenekonProbe({
      serviceName: 'checkout-service',
      collectorUrl: 'http://localhost:4380',
    });
    
    app.use(probe.middleware()); // Must be first middleware
    
    app.get('/api/orders', async (req, res) => {
      // Your existing code β€” untouched
      const orders = await db.query('SELECT * FROM orders');
      res.json(orders);
    });
    
    app.listen(3000);
  4. Make a request β€” it's recorded automatically
    curl http://localhost:3000/api/orders
  5. View and replay the session
    npx ergenekon sessions       # list recordings
    npx ergenekon replay <id>   # replay and verify

Note: The probe must be added before any other middleware so it can intercept the full request lifecycle.


πŸ”„ How It Works

ERGENEKON operates on a simple principle: intercept all sources of non-determinism at I/O boundaries, record them, and replay them exactly during debugging.

The Recording Pipeline

Incoming Request
     β”‚
     β–Ό
 β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
 β”‚  PROBE (your service)                                β”‚
 β”‚                                                      β”‚
 β”‚  β‘  HTTP middleware wraps the request                 β”‚
 β”‚  β‘‘ All I/O interceptions installed:                  β”‚
 β”‚    β€’ Date.now(), Math.random(), crypto.randomUUID()  β”‚
 β”‚    β€’ http.request(), fetch()                         β”‚
 β”‚    β€’ fs.readFile(), fs.writeFile()                   β”‚
 β”‚    β€’ dns.lookup(), dns.resolve()                     β”‚
 β”‚    β€’ pg.query(), mysql2.execute()                    β”‚
 β”‚    β€’ setTimeout(), setInterval()                     β”‚
 β”‚  β‘’ Each call β†’ ErgenekonEvent (ULID + HLC timestamp) β”‚
 β”‚  β‘£ Events buffered in SpillBuffer                    β”‚
 β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                    β”‚ POST /sessions
                                    β–Ό
                        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                        β”‚  COLLECTOR SERVER   β”‚
                        β”‚                     β”‚
                        β”‚  Rate limiting      β”‚
                        β”‚  SHA-256 checksum   β”‚
                        β”‚  Durable fsync writeβ”‚
                        β”‚  REST API           β”‚
                        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                  β”‚
                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                    β”‚  REPLAY ENGINE             β”‚
                    β”‚                            β”‚
                    β”‚  Loads recording session   β”‚
                    β”‚  Mocks all I/O from events β”‚
                    β”‚  Re-executes your code     β”‚
                    β”‚  Verifies byte-for-byte    β”‚
                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Event Types

Event TypeWhen CapturedTier
http_request_in / http_response_outEvery incoming HTTP request/responseCommunity
http_request_out / http_response_inOutbound fetch/http.request callsCommunity
timestampDate.now(), new Date(), performance.now()Community
randomMath.random()Community
timer_set / timer_firesetTimeout / setIntervalCommunity
errorUncaught exceptions, unhandled rejectionsCommunity
db_query / db_resultPostgreSQL, MySQL2, MongoDB queriesPro
fs_read / fs_writeFile system operationsPro
dns_lookupDNS resolution callsPro

βš™οΈ Probe Configuration

TypeScriptconst probe = new ErgenekonProbe({
  // REQUIRED
  serviceName:  'payment-service',

  // Collector endpoint (default: http://localhost:4380)
  collectorUrl: process.env.ERGENEKON_COLLECTOR_URL,

  // Disable in test environments
  enabled: process.env.NODE_ENV !== 'test',

  // Record every request (1.0 = 100%, 0.1 = 10%) β€” Pro tier
  samplingRate: 1.0,

  // Auto-redact these headers in all events
  redactHeaders: ['authorization', 'cookie', 'x-api-key'],

  // Max body size to capture (bytes) β€” default 512KB
  maxBodyBytes: 524288,

  // Advanced sampling config β€” Pro tier
  sampling: {
    strategy: 'reservoir',   // 'deterministic' | 'reservoir' | 'adaptive'
    rate: 0.1,              // sample 10% of requests
    reservoirSize: 100,     // keep 100 samples per minute
    alwaysRecord: [          // patterns that always record
      'POST /api/payments',
      'DELETE *',
    ],
  },
});

Full Configuration Reference

OptionTypeDefaultDescription
serviceNamestringrequiredUnique name for this service in recordings
collectorUrlstringhttp://localhost:4380URL of the Collector server
enabledbooleantrueDisable to turn off all instrumentation
samplingRatenumber1.0Fraction of requests to record (0–1.0)
redactHeadersstring[]['authorization', 'cookie', 'x-api-key']Headers redacted before storage
maxBodyBytesnumber524288Max request/response body size (bytes)
samplingSamplingConfigsee aboveAdvanced sampling configuration Pro

🎲 Smart Sampling

Pro Sampling lets you record a representative subset of requests β€” reducing overhead to near zero in high-traffic environments.

Strategies

StrategyDescriptionBest For
deterministicRecord every N-th request (consistent, predictable)Load testing validation
reservoirReservoir sampling β€” statistically uniform over time windowsProduction monitoring
adaptiveAuto-adjusts rate based on error signals and anomaliesHigh-traffic APIs
TypeScriptconst probe = new ErgenekonProbe({
  serviceName: 'api-gateway',
  sampling: {
    strategy: 'adaptive',
    rate: 0.05,             // baseline 5%
    alwaysRecord: [        // always record these β€” regardless of rate
      'POST /api/checkout',
      'POST /api/auth/*',
      'DELETE *',
    ],
    neverRecord: [         // never record health checks / metrics
      'GET /health',
      'GET /metrics',
    ],
  },
});

πŸ”’ PII Redaction

Pro ERGENEKON includes a deep redaction engine that recursively walks recorded payloads and scrubs sensitive fields before they are written to disk.

Design guarantee: The redaction engine never mutates the original object β€” it always returns a deep copy. Your application data is never modified at runtime.

TypeScriptimport { redactDeep } from '@ergenekon/probe';

const redacted = redactDeep(payload, {
  // Redact these field names (case-insensitive)
  fieldNames: ['password', 'ssn', 'creditCard', 'apiKey'],

  // Redact nested paths (supports * wildcard)
  pathPatterns: ['user.*.secret', 'payment.card.*'],

  // Auto-detect emails, JWTs, credit card numbers, SSNs
  autoDetect: true,

  // Custom redactor function
  customRedactor: (field, value, path) => {
    if (path.includes('billing')) return '[BILLING_REDACTED]';
    return undefined; // undefined = use default behavior
  },

  replacement: '[REDACTED]',
  maxDepth: 20,
});

Auto-detected Patterns

  • Credit card numbers (Luhn-valid 13–19 digit sequences)
  • JSON Web Tokens (three-part base64url strings)
  • Email addresses
  • US Social Security Numbers (XXX-XX-XXXX format)
  • Private keys (PEM header detection)

πŸ“‘ Running the Collector

Standalone (Development)

# Start with defaults (port 4380, ./recordings directory)
npx @ergenekon/collector

# Custom port and storage path
npx @ergenekon/collector --port 4380 --storage /var/ergenekon/recordings

# With license
ERGENEKON_LICENSE_KEY='...' npx @ergenekon/collector

Programmatic (Node.js)

TypeScriptimport { CollectorServer } from '@ergenekon/collector';

const server = new CollectorServer({
  port: 4380,
  storageDir: '/var/ergenekon/recordings',
});

await server.start();
console.log('Collector listening on :4380');

Defense Layers

LayerBehavior
Body limitHard 16MB limit per request body β€” returns 413 on overflow
Rate limitingToken bucket: 100 requests/min per IP β€” returns 429
Event capMax 1,000,000 events per session β€” prevents index explosion
ChecksumSHA-256 checksum per session β€” tamper detection on read
Durable writefsync before ACK β€” no data loss on process crash

🌐 Collector REST API

All endpoints served on http://localhost:4380 (default).

MethodEndpointDescription
POST/sessionsIngest a batch of recording sessions from the Probe
GET/sessionsList all recorded sessions (newest first)
GET/sessions/:idRetrieve a specific session by ID
DELETE/sessions/:idDelete a session
GET/healthHealth check β€” returns uptime, session count
GET/statsCollector statistics (events, sessions, storage)

List Sessions

GET /sessions

Response:
{
  "sessions": [
    {
      "id": "01HWXYZ...",
      "traceId": "abc123",
      "serviceName": "checkout-service",
      "startTime": "2026-04-15T18:00:00Z",
      "durationMs": 234,
      "eventCount": 47,
      "nodeVersion": "v22.0.0"
    }
  ],
  "total": 1
}

Get Session Detail

GET /sessions/01HWXYZ...

Response:
{
  "id": "01HWXYZ...",
  "traceId": "abc123",
  "events": [
    {
      "id": "01HWXYZ001",
      "type": "http_request_in",
      "timestamp": { "wallTime": 1712345678901, "logical": 0 },
      "data": { "method": "POST", "url": "/api/checkout", "headers": {...} }
    },
    ...
  ]
}

πŸ“Š Dashboard

The Time-Travel Debugger dashboard gives you a visual interface to browse recordings, inspect events, and debug issues.

Connect to Your Collector

Open the dashboard and enter your collector URL in the connection panel:

# Option 1: Use the UI
1. Open ergenekon-dashboard.vercel.app
2. Enter http://localhost:4380 in the Collector field
3. Click Connect

# Option 2: URL parameter
https://ergenekon-dashboard.vercel.app?collector=http://localhost:4380

# Option 3: Self-host the dashboard
cd dashboard-demo && npx serve .

Features

  • Live Mode β€” Polls your collector every 5s for new recordings
  • Session List β€” Browse all recorded sessions with method, path, status
  • Event Timeline β€” Scrub through events with the timeline slider
  • Event Inspector β€” View full request/response data, DB queries, etc.
  • Keyboard Shortcuts β€” ← β†’ navigate, Space play/pause, ? help
  • Search β€” Filter sessions by path, method, or service name
  • Auto-reconnect β€” Saves your collector URL in localStorage

πŸ’» CLI Reference

npx ergenekon <command> [options]

# Or install globally:
npm install -g @ergenekon/cli
ergenekon <command>

Commands

CommandDescription
ergenekon sessionsList all recorded sessions, sorted by timestamp
ergenekon inspect <id>Show full session details β€” metadata, event breakdown, timing
ergenekon timeline <id>Print color-coded ASCII event timeline to stdout
ergenekon trace <traceId>Show all sessions sharing a distributed trace ID
ergenekon replay <id>Replay a session and print verification report
ergenekon export <id> [file]Export session to JSON or binary .prdx format
ergenekon import <file>Import a .json or .prdx session into the Collector
ergenekon statsShow Collector statistics (sessions, events, uptime)
ergenekon watchLive-tail new recording sessions (streams updates)
ergenekon healthCheck Collector health and connectivity
ergenekon session pin <id>Pin a session to prevent TTL eviction

Environment Variables for CLI

VariableDefaultDescription
ERGENEKON_COLLECTOR_URLhttp://localhost:4380Collector server URL

Examples

# List sessions from a remote Collector
ERGENEKON_COLLECTOR_URL=http://prod-collector:4380 ergenekon sessions

# Show color timeline of session events
ergenekon timeline 01HWXYZ...

# Export for offline debugging
ergenekon export 01HWXYZ... ./debug-session.prdx

# Replay and compare output
ergenekon replay 01HWXYZ...
# βœ… PERFECT REPLAY β€” BYTE-FOR-BYTE IDENTICAL
# βœ“ Date.now() β†’ 1712345678901  (deterministic)
# βœ“ Math.random() β†’ 0.73421847  (deterministic)
# βœ“ Response body β†’ identical    (byte-for-byte)

βͺ Replay Engine

The replay engine re-runs your application code against a recorded session, mocking all I/O with the captured values β€” so you can step through the exact execution path that caused a production bug.

Programmatic Replay

TypeScriptimport { MockLayer } from '@ergenekon/replay';
import { importSessionJSON } from '@ergenekon/core';

const session = await importSessionJSON('./session.json');
const mock = new MockLayer(session);

// Install mocks before running your code
mock.install();

try {
  // Run your handler with the recorded request
  await yourRequestHandler(session.events[0].data);
} finally {
  mock.uninstall();
  const report = mock.verificationReport();
  console.log(report); // pass/fail per event
}

What "Deterministic" Means

During replay, every I/O call returns the exact value captured during recording:

CallProduction valueReplay returns
Date.now()17123456789011712345678901 ← exact
Math.random()0.734218470.73421847 ← exact
db.query()[{id:42, name:"Δ°lhan"}][{id:42, name:"Δ°lhan"}] ← exact
fetch(url){"status":200, body:"..."}{"status":200, body:"..."} ← exact
fs.readFile()<Buffer 68 65 6c 6c 6f><Buffer 68 65 6c 6c 6f> ← exact

πŸ”‘ License System

Licenses are Ed25519-signed JWT tokens validated entirely offline β€” no network call at startup or runtime.

License Discovery (auto, in order)

  1. ERGENEKON_LICENSE_KEY env var β€” inline JSON token
  2. ERGENEKON_LICENSE env var β€” path to license file
  3. .ergenekon-license.json in the current working directory
  4. ~/.ergenekon-license.json in the home directory

No license found? The system falls back to Community mode gracefully β€” it never crashes or throws. All Community features remain available.

License File Format

JSON{
  "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9...",
  "tier": "pro",
  "licensee": "Acme Corp",
  "issuedAt": "2026-04-15T00:00:00Z",
  "expiresAt": "2027-04-15T00:00:00Z"
}

Setting via Environment (recommended for production)

ERGENEKON_LICENSE_KEY='{"token":"eyJ...","tier":"pro"}' node app.js

Feature Tiers

FeatureCommunityProEnterprise
HTTP interceptorβœ…βœ…βœ…
Globals (Date, Math, timers)βœ…βœ…βœ…
Error trackingβœ…βœ…βœ…
CLI tool (11 commands)βœ…βœ…βœ…
Time-Travel UIβœ…βœ…βœ…
Session retention24 hours30 daysUnlimited
Database interceptor (pg, mysql2)βŒβœ…βœ…
FS interceptorβŒβœ…βœ…
DNS interceptorβŒβœ…βœ…
Smart sampling engineβŒβœ…βœ…
Deep PII redactionβŒβœ…βœ…
Binary .prdx exportβŒβœ…βœ…
Distributed multi-service replayβŒβœ…βœ…
SSO / SAMLβŒβŒβœ…
RBAC access controlβŒβŒβœ…
On-premise deployment supportβŒβŒβœ…
SLA 99.9% + dedicated supportβŒβŒβœ…

🐳 Docker Deployment

Docker Compose (Recommended)

docker-compose.ymlversion: '3.9'
services:
  ergenekon-collector:
    image: node:22-alpine
    working_dir: /app
    command: npx @ergenekon/collector --port 4380 --storage /data
    ports:
      - "4380:4380"
    volumes:
      - ergenekon-data:/data
    environment:
      - ERGENEKON_LICENSE_KEY=${ERGENEKON_LICENSE_KEY}
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:4380/health"]
      interval: 30s
      timeout: 5s
      retries: 3

volumes:
  ergenekon-data:
docker compose up -d
docker compose logs -f ergenekon-collector

Standalone Dockerfile

DockerfileFROM node:22-alpine
WORKDIR /app
RUN npm install -g @ergenekon/collector
EXPOSE 4380
VOLUME /data
CMD ["ergenekon-collector", "--port", "4380", "--storage", "/data"]

☸️ Kubernetes Deployment

collector-deployment.yamlapiVersion: apps/v1
kind: Deployment
metadata:
  name: ergenekon-collector
  namespace: observability
spec:
  replicas: 1
  selector:
    matchLabels:
      app: ergenekon-collector
  template:
    metadata:
      labels:
        app: ergenekon-collector
    spec:
      containers:
        - name: collector
          image: node:22-alpine
          command: ["npx", "@ergenekon/collector"]
          args: ["--port", "4380", "--storage", "/data"]
          ports:
            - containerPort: 4380
          env:
            - name: ERGENEKON_LICENSE_KEY
              valueFrom:
                secretKeyRef:
                  name: ergenekon-license
                  key: key
          volumeMounts:
            - name: recordings
              mountPath: /data
          resources:
            requests:
              memory: "128Mi"
              cpu: "100m"
            limits:
              memory: "512Mi"
              cpu: "500m"
          livenessProbe:
            httpGet:
              path: /health
              port: 4380
            initialDelaySeconds: 5
            periodSeconds: 15
      volumes:
        - name: recordings
          persistentVolumeClaim:
            claimName: ergenekon-pvc
---
apiVersion: v1
kind: Service
metadata:
  name: ergenekon-collector
  namespace: observability
spec:
  selector:
    app: ergenekon-collector
  ports:
    - port: 4380
      targetPort: 4380

Secret for License Key

kubectl create secret generic ergenekon-license \
  --from-literal=key='{"token":"eyJ...","tier":"enterprise"}' \
  -n observability

πŸ”§ Environment Variables

VariableComponentDescription
ERGENEKON_LICENSE_KEYProbe, CollectorInline JSON license token (highest priority)
ERGENEKON_LICENSEProbe, CollectorPath to .ergenekon-license.json file
ERGENEKON_COLLECTOR_URLProbe, CLIURL of the Collector server (default: http://localhost:4380)
ERGENEKON_SIGNING_KEYLicense ServerEd25519 private key for generating license tokens
STRIPE_SECRET_KEYLicense ServerStripe API secret key for payment processing
STRIPE_WEBHOOK_SECRETLicense ServerStripe webhook signing secret
PORTCollector, License ServerOverride HTTP listen port

Never commit ERGENEKON_SIGNING_KEY or STRIPE_SECRET_KEY to source control. Use secret managers (AWS Secrets Manager, Vault, k8s Secrets).