Alerts & Channels
Alerts fire when an agent crosses a configured threshold. Each alert is a (threshold, channel) pair. The SDK ships five channel types: email, Slack, PagerDuty, webhook, and a built-in kill switch.
How Alerts Work #
The threshold is a float in (0.0, 1.0]; 0.70 means “fire when usage hits 70% of the budget”. After every LLM call, the engine compares the new usage ratio to every configured threshold and dispatches alerts that just crossed their line.
Evaluable budget types
A threshold can apply to any of the dimensions the engine tracks. The SDK syncs the budget type with each alert so the backend evaluates the right axis.
| Budget type | Source |
|---|---|
cost | Budget.daily / .monthly / .total |
tokens_total | max_tokens_per_run |
duration | max_runtime_seconds |
ℹ️ One firing per (threshold, channel, type)
(budget_key, threshold, channel) tuple prevents alert storms within a single budget period.ChannelType #
| Enum | String value | Description |
|---|---|---|
ChannelType.EMAIL | "email" | Resend transactional email |
ChannelType.SLACK | "slack" | POST to a Slack incoming webhook (Block Kit message + text fallback) |
ChannelType.PAGERDUTY | "pagerduty" | PagerDuty Events API v2 trigger |
ChannelType.WEBHOOK | "webhook" | POST JSON to a URL with optional HMAC-SHA256 signing |
ChannelType.KILL | "kill" | Invoke the on_kill callback; block subsequent calls in the run |
from agentkavach import AgentKavach, ChannelTypeConfiguring channels #
Build a list of ChannelConfig objects with the AgentKavach.channel() factory and pass it via the channels= argument:
from agentkavach import AgentKavach, Budget, ChannelType
guard = AgentKavach(
provider="openai",
api_key="ak_prod_...", # your AgentKavach key
llm_key="sk-...", # your OpenAI key
agent_name="research-bot",
budget=Budget.daily(50),
on_kill=lambda: None,
channels=[
AgentKavach.channel(ChannelType.EMAIL, threshold=0.50, to="team@acme.com"),
AgentKavach.channel(ChannelType.SLACK, threshold=0.80, webhook_url="https://hooks.slack.com/..."),
AgentKavach.channel(ChannelType.PAGERDUTY, threshold=0.95, routing_key="R0..."),
AgentKavach.channel(ChannelType.KILL, threshold=1.0),
],
)ℹ️ Channel-type validation
ChannelConfig is validated at construction. Missing required fields (e.g. webhook_url for Slack, routing_key for PagerDuty) raise ValueError immediately so you find misconfigurations at startup, not at 2 AM.Email #
Email is delivered through Resend. Two modes:
- Backend-dispatched (default): pass only
to=. The hosted backend sends the message using its own Resend key. No SDK-side key required. - SDK-dispatched: pass
api_key=(or setRESEND_API_KEYin the environment). The SDK sends the message directly.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
channel_type | ChannelType | str | Yes | — | ChannelType.EMAIL or "email". |
threshold | float | Yes | — | Trigger ratio in (0.0, 1.0]. |
to | str | Yes | — | Recipient email address. |
api_key | str | No | "" | Resend API key for SDK-dispatched mode. Leave empty to use backend dispatch. |
AgentKavach.channel(ChannelType.EMAIL, threshold=0.80, to="oncall@acme.com")Slack #
Posts a Block Kit message to a Slack incoming webhook. Every message also includes a plain-text text fallback so legacy webhooks still render something useful.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
channel_type | ChannelType | str | Yes | — | ChannelType.SLACK or "slack". |
threshold | float | Yes | — | Trigger ratio in (0.0, 1.0]. |
webhook_url | str | Yes | — | Slack incoming webhook URL. |
template | dict | No | — | Optional override dict with a 'text' key and optional 'blocks'. Supports template variables (see below). |
Get a webhook URL #
- Open api.slack.com/apps and click Create New App → From scratch.
- Pick the workspace, then enable Incoming Webhooks in the left sidebar.
- Click Add New Webhook to Workspace, choose a channel, and copy the URL.
- Store the URL in
.envasAGENTKAVACH_SLACK_WEBHOOK_URLand pass it viawebhook_url=os.environ["AGENTKAVACH_SLACK_WEBHOOK_URL"].
⚠️ Treat the URL as a secret
Custom template
AgentKavach.channel(
ChannelType.SLACK,
threshold=0.70,
webhook_url=os.environ["AGENTKAVACH_SLACK_WEBHOOK_URL"],
template={
"text": ":rotating_light: {agent_name} at {pct}% of {period} {budget_type} limit",
},
)PagerDuty #
Triggers an incident through the PagerDuty Events API v2. Severity is auto-mapped from the threshold: warning below 90%, error from 90% to 99%, critical at 100%.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
channel_type | ChannelType | str | Yes | — | ChannelType.PAGERDUTY or "pagerduty". |
threshold | float | Yes | — | Trigger ratio in (0.0, 1.0]. |
routing_key | str | Yes | — | Events API v2 integration key (32-char hex). |
Create the routing key #
- In PagerDuty, go to Services → Service Directory → New Service.
- Pick an escalation policy, then add an Events API v2 integration.
- Copy the integration key from the integration row.
- Store it as
AGENTKAVACH_PAGERDUTY_ROUTING_KEYand pass it viarouting_key=os.environ[...].
ℹ️ Integration key == routing key
routing_key in the API. AgentKavach uses routing_key for consistency with the Events API v2 spec.AgentKavach.channel(
ChannelType.PAGERDUTY,
threshold=0.95,
routing_key=os.environ["AGENTKAVACH_PAGERDUTY_ROUTING_KEY"],
)Webhook #
POSTs a JSON payload to any URL. Provide a secret to receive an HMAC-SHA256 signature in the X-AgentKavach-Signature header.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
channel_type | ChannelType | str | Yes | — | ChannelType.WEBHOOK or "webhook". |
threshold | float | Yes | — | Trigger ratio in (0.0, 1.0]. |
url | str | Yes | — | Endpoint to POST the JSON payload to. |
secret | str | No | "" | HMAC-SHA256 secret. When set, the signature is sent in X-AgentKavach-Signature. |
AgentKavach.channel(
ChannelType.WEBHOOK,
threshold=0.80,
url="https://api.acme.com/budget-alerts",
secret=os.environ["AGENTKAVACH_WEBHOOK_SECRET"],
)Verifying the signature
import hmac
import hashlib
def verify(body: bytes, signature: str, secret: str) -> bool:
expected = hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()
return hmac.compare_digest(f"sha256={expected}", signature)Kill switch #
The kill channel invokes the on_kill callback you registered on the AgentKavach instance, then sets an internal flag that causes every subsequent guard.create() call to raise BudgetExceededError immediately.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
channel_type | ChannelType | str | Yes | — | ChannelType.KILL or "kill". |
threshold | float | Yes | — | Trigger ratio. Typically 1.0. |
import sys
from agentkavach import AgentKavach, Budget, ChannelType
def emergency_stop():
# Called once when the kill threshold is crossed.
print("agent killed by budget")
sys.exit(1)
guard = AgentKavach(
provider="openai",
api_key="ak_prod_...",
llm_key="sk-...",
agent_name="expensive-agent",
budget=Budget.daily(10),
on_kill=emergency_stop,
channels=[
AgentKavach.channel(ChannelType.KILL, threshold=1.0),
],
)⚠️ Kill is sticky for the instance
AgentKavach instance cannot resume. Construct a new instance (e.g. on the next budget period) if you need to keep going.Dashboard Stop button #
The Stop button on the Agents page records a kill event on the backend. The next time the SDK syncs config (on instance construction), it picks up the kill state. If your process is already running, the SDK enforces the kill on its next post-flight cycle and future guard.create() calls raise BudgetExceededError.
⚠️ The button does not terminate processes
on_kill callback that actually stops the process (e.g. sys.exit(1)). Otherwise the run keeps executing non-LLM work until it finishes on its own.Template variables #
Templates use Python str.format() substitution. Available variables:
| Variable | Example |
|---|---|
{agent_name} | research-bot |
{pct} | 82 |
{spent} / {spent_fmt} | 4.13 / $4.13 |
{budget} / {budget_fmt} | 5.00 / $5.00 |
{remaining} / {remaining_fmt} | 0.87 / $0.87 |
{period} | daily |
{budget_type} | cost |
{severity} | warning / error / critical |
{dashboard_url} | https://app.agentkavach.com/... |
Environment variables #
When you construct AgentKavach without an explicit channels= list, the SDK reads these environment variables and registers any channel whose credentials are present:
| Variable | Channel |
|---|---|
AGENTKAVACH_SLACK_WEBHOOK_URL | Slack |
AGENTKAVACH_PAGERDUTY_ROUTING_KEY | PagerDuty |
AGENTKAVACH_WEBHOOK_URL / AGENTKAVACH_WEBHOOK_SECRET | Webhook |
RESEND_API_KEY + AGENTKAVACH_ALERT_EMAIL | Email (SDK-dispatched) |
⚠️ Never commit secrets
.env or your secret manager, never in source.