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
| Package | Purpose | Install |
|---|---|---|
@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.
-
Install the packages
npm install @ergenekon/probe @ergenekon/collector -
Start the Collector
The Collector runs as a separate HTTP server on port 4380.
npx ergenekon-collector --port 4380 --storage ./recordings -
Add the Probe to your Express app
TypeScript
import { 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); -
Make a request β it's recorded automatically
curl http://localhost:3000/api/orders -
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 Type | When Captured | Tier |
|---|---|---|
http_request_in / http_response_out | Every incoming HTTP request/response | Community |
http_request_out / http_response_in | Outbound fetch/http.request calls | Community |
timestamp | Date.now(), new Date(), performance.now() | Community |
random | Math.random() | Community |
timer_set / timer_fire | setTimeout / setInterval | Community |
error | Uncaught exceptions, unhandled rejections | Community |
db_query / db_result | PostgreSQL, MySQL2, MongoDB queries | Pro |
fs_read / fs_write | File system operations | Pro |
dns_lookup | DNS resolution calls | Pro |
βοΈ 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
| Option | Type | Default | Description |
|---|---|---|---|
serviceName | string | required | Unique name for this service in recordings |
collectorUrl | string | http://localhost:4380 | URL of the Collector server |
enabled | boolean | true | Disable to turn off all instrumentation |
samplingRate | number | 1.0 | Fraction of requests to record (0β1.0) |
redactHeaders | string[] | ['authorization', 'cookie', 'x-api-key'] | Headers redacted before storage |
maxBodyBytes | number | 524288 | Max request/response body size (bytes) |
sampling | SamplingConfig | see above | Advanced 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
| Strategy | Description | Best For |
|---|---|---|
deterministic | Record every N-th request (consistent, predictable) | Load testing validation |
reservoir | Reservoir sampling β statistically uniform over time windows | Production monitoring |
adaptive | Auto-adjusts rate based on error signals and anomalies | High-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
| Layer | Behavior |
|---|---|
| Body limit | Hard 16MB limit per request body β returns 413 on overflow |
| Rate limiting | Token bucket: 100 requests/min per IP β returns 429 |
| Event cap | Max 1,000,000 events per session β prevents index explosion |
| Checksum | SHA-256 checksum per session β tamper detection on read |
| Durable write | fsync before ACK β no data loss on process crash |
π Collector REST API
All endpoints served on http://localhost:4380 (default).
| Method | Endpoint | Description |
|---|---|---|
POST | /sessions | Ingest a batch of recording sessions from the Probe |
GET | /sessions | List all recorded sessions (newest first) |
GET | /sessions/:id | Retrieve a specific session by ID |
DELETE | /sessions/:id | Delete a session |
GET | /health | Health check β returns uptime, session count |
GET | /stats | Collector 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
| Command | Description |
|---|---|
| ergenekon sessions | List 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 stats | Show Collector statistics (sessions, events, uptime) |
| ergenekon watch | Live-tail new recording sessions (streams updates) |
| ergenekon health | Check Collector health and connectivity |
| ergenekon session pin <id> | Pin a session to prevent TTL eviction |
Environment Variables for CLI
| Variable | Default | Description |
|---|---|---|
ERGENEKON_COLLECTOR_URL | http://localhost:4380 | Collector 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:
| Call | Production value | Replay returns |
|---|---|---|
Date.now() | 1712345678901 | 1712345678901 β exact |
Math.random() | 0.73421847 | 0.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)
ERGENEKON_LICENSE_KEYenv var β inline JSON tokenERGENEKON_LICENSEenv var β path to license file.ergenekon-license.jsonin the current working directory~/.ergenekon-license.jsonin 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
| Feature | Community | Pro | Enterprise |
|---|---|---|---|
| HTTP interceptor | β | β | β |
| Globals (Date, Math, timers) | β | β | β |
| Error tracking | β | β | β |
| CLI tool (11 commands) | β | β | β |
| Time-Travel UI | β | β | β |
| Session retention | 24 hours | 30 days | Unlimited |
| 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
| Variable | Component | Description |
|---|---|---|
ERGENEKON_LICENSE_KEY | Probe, Collector | Inline JSON license token (highest priority) |
ERGENEKON_LICENSE | Probe, Collector | Path to .ergenekon-license.json file |
ERGENEKON_COLLECTOR_URL | Probe, CLI | URL of the Collector server (default: http://localhost:4380) |
ERGENEKON_SIGNING_KEY | License Server | Ed25519 private key for generating license tokens |
STRIPE_SECRET_KEY | License Server | Stripe API secret key for payment processing |
STRIPE_WEBHOOK_SECRET | License Server | Stripe webhook signing secret |
PORT | Collector, License Server | Override HTTP listen port |
Never commit ERGENEKON_SIGNING_KEY or STRIPE_SECRET_KEY to source control. Use secret managers (AWS Secrets Manager, Vault, k8s Secrets).