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
// 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.
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.
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.
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
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
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
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
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.
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:
sentinel.on('threat:detected', async ({ result }) => {
try {
await db.logThreat(result);
} catch (err) {
console.error('Failed to log threat:', err);
}
});