Observability

Broadcast usage, errors, and memory events to external systems.

Overview

BrainstormRouter's observability engine broadcasts structured events to external destinations in real time. Every API call, guardrail match, quality signal, and memory operation can be forwarded to your analytics stack.

Event types

TypeFires when
usageEvery completion (tokens, cost, latency, model, provider)
auditMemory operations (append, replace, delete, pin, evict)
qualityTool-call accuracy, Thompson sampling rewards
guardrailContent safety matches (PII, jailbreak, policy)
errorProvider errors, timeouts, circuit breaker trips

Destination types

TypeConfigUse case
webhook{ url }Any HTTP endpoint
otlp{ endpoint }OpenTelemetry collectors, Langfuse
datadog{ api_key }Datadog Events API
splunk{ hec_url, hec_token }Splunk HTTP Event Collector
custom{ url }Custom HTTP with full control

Setup

// Add a webhook destination
const { destination } = await client.observability.addDestination({
  name: "Slack Alerts",
  type: "webhook",
  config: { url: "https://hooks.slack.com/services/..." },
  event_types: ["error", "guardrail"],
});

// Enable broadcasting
await client.observability.setEnabled(true);

// Test it
await client.observability.testDestination(destination.id);
curl
curl -X POST https://api.brainstormrouter.com/v1/observability/destinations \
  -H "Authorization: Bearer br_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Datadog",
    "type": "datadog",
    "config": {"api_key": "dd_..."},
    "event_types": ["usage", "error"]
  }'

Batching

Configure batch delivery to reduce HTTP overhead:

await client.observability.addDestination({
  name: "Analytics",
  type: "webhook",
  config: { url: "https://analytics.example.com/ingest" },
  batch: {
    max_size: 100, // flush after 100 events
    flush_interval_ms: 5000, // or every 5 seconds
  },
});

Event filtering

Each destination can subscribe to specific event types. A webhook that only receives error events won't get noisy usage events.

Memory events (unique to BrainstormRouter)

Unlike other gateways, BrainstormRouter broadcasts memory operations — when the agent appends, replaces, or evicts a memory entry, that event flows through the observability pipeline. This enables:

  • Tracking what the agent learns over time
  • Alerting on PII entering memory
  • Compliance audit trails via external SIEM
  • Sleep-time refinement monitoring

Telemetry & EDR integration

Security events from streaming guardrails flow through a structured pipeline to your SIEM/EDR:

%%{init: {'theme': 'dark', 'themeVariables': {'primaryColor': '#d97706', 'lineColor': '#9494a8', 'primaryTextColor': '#e8e8ee'}}}%%
flowchart LR
    SGE["StreamingGuardrailEvaluator\nstreaming-guardrails.ts"] -->|Verdict| Event["Structured Security Event\ntype, severity, action, actor"]
    Event --> Format{"Export Format"}
    Format -->|CEF| CEF["toCef()\nCEF:0|BrainstormRouter|..."]
    Format -->|JSON| ECS["toSiemJson()\nECS-aligned JSON"]
    CEF --> Splunk["Splunk / ArcSight\nQRadar"]
    ECS --> Elastic["Elastic / Datadog\nCustom Webhook"]
    Event --> PG["Postgres\nsecurity_events table"]
    PG --> Batch["exportBatch()\nSeverity-filtered"]
    Batch --> Splunk
    Batch --> Elastic

The SIEM export module (src/security/siem-export.ts) supports two formats:

  • CEF (Common Event Format) — toCef() produces ArcSight-compatible strings with severity mapping, actor identification, and action details
  • ECS JSON (Elastic Common Schema) — toSiemJson() produces structured JSON with @timestamp, event.kind, event.category, event.severity, and observer.* fields

Batch export filters by minimum severity (debug=0, info=3, warn=6, error=8, critical=10) so your SIEM receives only actionable events.

Event categories

Event prefixCategoryExample
auth.*authenticationAPI key validation failure
config.*configurationHot config reload
tls.*networkTLS enforcement violation
guardrail.*processPII detected in streaming output
schema.*changeSchema inference update

Managing destinations

// List all destinations
const { destinations } = await client.observability.destinations();

// Update a destination
await client.observability.updateDestination("dest-uuid", {
  enabled: false,
});

// Remove a destination
await client.observability.removeDestination("dest-uuid");