Events

The Sentinel event system for reacting to security signals without polling

3 min read

Events

Sentinel emits typed events when security-relevant things happen in the pipeline. Subscribe with sentinel.on() to react in real time — no polling needed.

API

typescript
// Register a listener
sentinel.on(event, listener)

// Remove a listener
sentinel.off(event, listener)

Both methods are generic over SentinelEvent, so TypeScript will infer the correct payload type for each event name.

Available Events

threat:detected

Emitted when the PromptGuard finds an unsafe input.

typescript
sentinel.on('threat:detected', ({ result }) => {
  console.log(result.threatType);  // 'DRAIN_INTENT'
  console.log(result.confidence);  // 0.97
  console.log(result.flags);       // RiskFlag[]
  console.log(result.reasoning);   // LLM explanation (if mode: 'llm')
})

Payload: { result: ScanResult }

Fires in full and guard-only modes when result.safe === false.

tx:simulated

Emitted after every transaction simulation, regardless of approval status.

typescript
sentinel.on('tx:simulated', ({ result }) => {
  console.log(result.approved);
  console.log(result.riskScore);
  console.log(result.balanceChanges);
  console.log(result.programInvocations);
})

Payload: { result: SimulationResult }

Fires in full and sandbox-only modes after ExecutionSandbox.evaluate() completes.

policy:violated

Emitted once per policy violation found during sandbox evaluation.

typescript
sentinel.on('policy:violated', ({ violation }) => {
  console.log(violation.rule);    // 'MAX_PER_TX'
  console.log(violation.message); // 'Transaction exceeds maximum per-tx limit of 10 SOL'
  console.log(violation.details); // { limit: 10, actual: 15.5 }
})

Payload: { violation: PolicyViolation }

If a transaction violates three policy rules, this event fires three times.

Usage Examples

Audit logging

typescript
sentinel.on('threat:detected', ({ result }) => {
  auditLog.write({
    event: 'threat_detected',
    threatType: result.threatType,
    confidence: result.confidence,
    latency_ms: result.latency_ms,
    timestamp: Date.now(),
  });
});

sentinel.on('policy:violated', ({ violation }) => {
  auditLog.write({
    event: 'policy_violated',
    rule: violation.rule,
    message: violation.message,
    timestamp: Date.now(),
  });
});

Alerting

typescript
sentinel.on('threat:detected', async ({ result }) => {
  if (result.confidence && result.confidence > 0.9) {
    await alertingService.send({
      severity: 'high',
      message: `High-confidence ${result.threatType} detected`,
    });
  }
});

Metrics

typescript
const metrics = { threats: 0, violations: 0, simulations: 0 };

sentinel.on('threat:detected', () => metrics.threats++);
sentinel.on('policy:violated', () => metrics.violations++);
sentinel.on('tx:simulated', () => metrics.simulations++);

Removing listeners

typescript
function onThreat({ result }: { result: ScanResult }) {
  console.log(result.threatType);
}

sentinel.on('threat:detected', onThreat);

// Later:
sentinel.off('threat:detected', onThreat);

Error Isolation

Errors thrown inside event listeners are silently swallowed. This prevents a misbehaving listener from disrupting the agent's main execution path. If a listener throws, execute() continues as if the listener didn't exist.

typescript
sentinel.on('threat:detected', () => {
  throw new Error('listener error'); // silently ignored
});

If your listener has side effects that could fail (network calls, database writes), handle errors inside the listener:

typescript
sentinel.on('threat:detected', async ({ result }) => {
  try {
    await db.logThreat(result);
  } catch (err) {
    console.error('Failed to log threat:', err);
  }
});