← Back to Learn
monitoringtutorialguardrails

Extending Sentinel with custom monitors

Authensor

Sentinel's default monitors track action rates, denial rates, and tool distribution. For domain-specific monitoring, you can add custom monitors that track metrics unique to your application.

When to add custom monitors

Add custom monitors when you need to track:

  • Spending velocity: How fast the agent is spending money
  • Data access volume: How much data the agent is reading
  • Communication frequency: How many messages the agent is sending
  • Geographic access patterns: Which regions the agent is accessing data from
  • Response latency changes: Whether tool responses are taking longer

Custom monitor structure

A custom monitor has:

  • name: Identifier for the monitor
  • extract: Function that extracts a metric value from a receipt
  • detector: EWMA, CUSUM, or both
  • alert: Callback when an anomaly is detected

Adding custom monitors

const guard = createGuard({
  policy,
  sentinel: {
    enabled: true,
    customMonitors: [
      {
        name: 'spending_rate',
        extract: (receipt) => {
          if (receipt.tool === 'payment.send') {
            return receipt.args.amount;
          }
          return 0;
        },
        detector: {
          ewma: { alpha: 0.3, sigmaThreshold: 2.5 },
          cusum: { slack: 50, threshold: 500 },
        },
        aggregate: 'sum',         // Sum values in the window
        windowSize: 3600_000,     // 1-hour window
      },
      {
        name: 'data_read_volume',
        extract: (receipt) => {
          if (receipt.tool === 'file.read' && receipt.action === 'allow') {
            return receipt.args.size || 1;
          }
          return 0;
        },
        detector: {
          ewma: { alpha: 0.5, sigmaThreshold: 3.0 },
        },
        aggregate: 'sum',
        windowSize: 60_000,       // 1-minute window
      },
      {
        name: 'external_comms',
        extract: (receipt) => {
          if (['email.send', 'slack.post', 'http.request'].includes(receipt.tool)) {
            return 1;
          }
          return 0;
        },
        detector: {
          cusum: { slack: 1, threshold: 10 },
        },
        aggregate: 'count',
        windowSize: 300_000,      // 5-minute window
      },
    ],
    onAlert: (alert) => {
      if (alert.monitor === 'spending_rate') {
        notifyFinanceTeam(alert);
      } else if (alert.monitor === 'external_comms') {
        notifySecurityTeam(alert);
      }
    }
  }
});

Monitor lifecycle

  1. Baseline period: The monitor collects data without alerting. This establishes the normal range.
  2. Active monitoring: After the baseline period, the monitor compares each new value to the baseline using the configured detector.
  3. Alert: When the detector threshold is exceeded, the onAlert callback fires.
  4. Reset: After an alert, the detector can reset to the current value or continue accumulating.

Testing custom monitors

Write unit tests to verify that your monitors detect the anomalies you care about:

test('spending monitor detects rapid spending', () => {
  const sentinel = createSentinel({ customMonitors: [spendingMonitor] });

  // Simulate normal spending
  for (let i = 0; i < 100; i++) {
    sentinel.record({ tool: 'payment.send', args: { amount: 10 }, action: 'allow' });
  }

  // Simulate abnormal spending
  const alerts = [];
  sentinel.onAlert = (a) => alerts.push(a);

  for (let i = 0; i < 10; i++) {
    sentinel.record({ tool: 'payment.send', args: { amount: 500 }, action: 'allow' });
  }

  expect(alerts.length).toBeGreaterThan(0);
  expect(alerts[0].monitor).toBe('spending_rate');
});

Performance

Custom monitors add a small amount of processing to each receipt. Each monitor performs one metric extraction and one statistical update per receipt. For most deployments, even 10 custom monitors add negligible overhead (under 100 microseconds per receipt).

Keep learning

Explore more guides on AI agent safety, prompt injection, and building secure systems.

View All Guides