Behavioral Drift Detection
Per-agent behavioral baselines, KL divergence scoring, and alerts when an agent's behavior shifts away from its expected pattern.
What it is
Drift detection answers a question that signed receipts can't: is this agent still doing what it's supposed to be doing?
A signed receipt proves "this action happened, signed by Aira." That is a per-action claim. Drift detection adds a per-agent claim: the agent's behavior over the last N hours matches the behavior we recorded as the baseline. When it doesn't, you get an alert with the exact action types that drifted, the KL divergence score, and the volume ratio.
The fingerprint is intentionally simple: action_type frequency distribution + average daily volume. This produces interpretable alerts ("the wire_transfer action went from 5% to 40% of total volume") and is independent of the agent's prompt or model. A drift alert is not a hallucination claim; it's a behavioral observation.
Three baseline types
Aira supports three kinds of baseline so you can avoid the "first N actions of a fresh agent are the baseline" attack surface.
| Type | When to use |
|---|---|
| production | After the agent has been running for at least a few days. Computes the baseline from real action history in a window you specify. |
| synthetic | At first deployment, before any production data exists. Seed the baseline from a config dict ({"action_type": probability}) so the drift checker has a reference distribution from day 1. |
| pooled | Bootstrap a brand-new agent from a cohort of similar agents. Aggregates action history across multiple agents into a single reference. |
Only one baseline is active per agent at any time. Past baselines are kept for audit but no longer compared against.
Compute a baseline
Production baseline (real history)
from datetime import datetime, timedelta, timezone
from aira import Aira
aira = Aira(api_key="aira_live_xxx")
now = datetime.now(timezone.utc)
window_start = now - timedelta(days=7)
baseline = aira._post( # direct API call — SDK wrapper coming soon
"/agents/payments-agent/drift/baseline",
{
"window_start": window_start.isoformat(),
"window_end": now.isoformat(),
"activate": True,
},
)Synthetic baseline (seeded)
baseline = aira._post(
"/agents/payments-agent/drift/baseline/synthetic",
{
"expected_distribution": {
"wire_transfer": 0.05,
"email_sent": 0.40,
"api_call": 0.30,
"tool_call": 0.25,
},
"expected_actions_per_day": 200,
"activate": True,
},
)Pooled baseline (cohort)
baseline = aira._post(
"/agents/payments-agent-v2/drift/baseline/pooled",
{
"source_agent_ids": ["payments-agent", "payments-agent-canary"],
"window_start": window_start.isoformat(),
"window_end": now.isoformat(),
"activate": True,
},
)Read the current drift status
GET /api/v1/agents/{slug}/drift?lookback_hours=24 returns the current divergence scored against the active baseline. Read-only — it does not persist an alert.
{
"agent_id": "payments-agent",
"has_baseline": true,
"baseline": {
"id": "...",
"baseline_type": "production",
"is_active": true,
"action_type_dist": {
"wire_transfer": 0.05,
"email_sent": 0.40,
"api_call": 0.55
},
"total_actions": 1400,
"avg_actions_per_day": 200.0
},
"current_window": {
"agent_id": "payments-agent",
"action_type_dist": {
"wire_transfer": 0.40,
"email_sent": 0.20,
"api_call": 0.40
},
"total_actions": 250,
"avg_actions_per_day": 250.0
},
"kl_divergence": 0.4523,
"volume_ratio": 1.25,
"severity": "critical",
"new_action_types": null,
"is_drifting": true,
"request_id": "..."
}The dashboard polls this endpoint and renders a bar chart with the baseline as a faint outline behind the current window so the shift is visible at a glance.
Run a drift check (record an alert)
POST /api/v1/agents/{slug}/drift/check runs the same scoring but persists an alert if the divergence exceeds the threshold. Returns the new alert (or null if no drift).
curl -X POST https://api.airaproof.com/api/v1/agents/payments-agent/drift/check \
-H "Authorization: Bearer aira_live_xxx"{
"id": "...",
"agent_id": "payments-agent",
"baseline_uuid": "...",
"kl_divergence": 0.4523,
"volume_ratio": 1.25,
"severity": "critical",
"new_action_types": ["data_export"],
"detected_at": "2026-04-10T20:00:00Z",
"acknowledged_at": null,
"acknowledged_by": null
}Run the check on a schedule (every 15 minutes is a reasonable default) and pipe the result into your alerting system. The check also fires the agent.drift_detected webhook event so external monitoring can hook in.
Severity bands
| KL divergence | Volume ratio | Severity | Verdict |
|---|---|---|---|
< 0.30 | 0.2x .. 5x | info | Stable — no alert raised |
>= 0.30 | any | warning | Distribution shift |
| any | > 5x or < 0.2x | warning | Volume spike or collapse |
>= 0.90 | any | critical | Severe distribution shift |
| any | > 10x or < 0.1x | critical | Severe volume anomaly |
Thresholds are tunable per-call via the threshold parameter on check_drift. The defaults are calibrated against synthetic distributions to mark "this agent is doing something meaningfully different from yesterday" without firing on benign weekly patterns.
What KL divergence actually measures
Symmetric KL with Laplace smoothing. For two distributions P (baseline) and Q (current window):
KL_sym(P || Q) = 0.5 * (KL(P || Q) + KL(Q || P))With Laplace smoothing (alpha = 1e-6) so that an action type appearing in one distribution but not the other contributes a finite penalty rather than infinity. This is the same primitive that drives the bar chart's "new action types" highlight: any key in the current window that's missing from the baseline is flagged in amber on the dashboard.
The math is pure-function and lives in app/services/drift_service.py on the backend if you want to inspect or replicate it.
Webhook payload
{
"event": "agent.drift_detected",
"agent_id": "payments-agent",
"baseline_uuid": "...",
"kl_divergence": 0.4523,
"volume_ratio": 1.25,
"severity": "critical",
"new_action_types": ["data_export"],
"detected_at": "2026-04-10T20:00:00Z"
}Subscribe via Dashboard → Webhooks → Add endpoint → agent.drift_detected.
DORA third-party risk register (Articles 28-44)
Maintain the DORA register of ICT third-party service providers — criticality classification, exit strategies, and subcontractor tracking.
Pluggable Signing Backend
Aira's signing layer is a Protocol, not a hard-coded algorithm. Ed25519 is the default; ML-DSA-65 / SLH-DSA / HSM backends are drop-in implementations.