Alerts & Channels

An alert is a (threshold, channel) pair. The SDK fires the channel when an agent crosses the threshold. Four notification channels ship in the box: email, Slack, PagerDuty, and webhook. A separate kill switch can halt the agent outright, covered at the end.

How alerts work #

A threshold is a fraction in (0.0, 1.0], so 0.70 fires when usage reaches 70% of the budget. After every LLM call the engine recomputes the usage ratio and dispatches any threshold that was just crossed.

A threshold does not have to track cost. The engine measures three dimensions, and each alert is bound to one of them so the backend evaluates the right axis. Cost comes from your Budget (daily, monthly, or total), total token usage comes from max_tokens_per_run, and elapsed runtime comes from max_runtime_seconds.

DimensionComes from
costBudget.daily / .monthly / .total
tokens_totalmax_tokens_per_run
durationmax_runtime_seconds

Because a channel is bound to a single dimension, a channel watching cost will not fire on token usage. To alert on both, register the channel twice, once per dimension. To keep a busy agent from flooding you, each (budget, threshold, channel) combination fires at most once every five minutes within a budget period.

ChannelType #

EnumString valueBehavior
ChannelType.EMAIL"email"Transactional email, sent by the AgentKavach backend.
ChannelType.SLACK"slack"POST to a Slack incoming webhook. Block Kit message with a text fallback.
ChannelType.PAGERDUTY"pagerduty"PagerDuty Events API v2 trigger.
ChannelType.WEBHOOK"webhook"POST JSON to a URL. Optional HMAC-SHA256 signing.
python
from agentkavach import AgentKavach, ChannelType

Configuring channels #

python
from agentkavach import AgentKavach, Budget, ChannelType

guard = AgentKavach(
    provider="openai",
    api_key="ak_prod_...",     # AgentKavach key
    llm_key="sk-...",          # OpenAI key
    agent_name="research-bot",
    budget=Budget.daily(50),
    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..."),
    ],
)

Build a list of ChannelConfig objects with AgentKavach.channel() and pass them to the channels= argument.

ℹ️ Misconfigurations fail at startup

Every channel is validated the moment you construct it. A missing recipient, a bad webhook URL, or a threshold outside (0.0, 1.0] raises a ValueError right away, with a message naming the problem. This is standard Python for an argument that has the right type but an unusable value, and it means you catch the mistake on the first run rather than the night an alert silently fails to fire.

Email #

python
AgentKavach.channel(ChannelType.EMAIL, threshold=0.80, to="oncall@acme.com")

You provide only the recipient address. The AgentKavach backend sends the message through Resend using its own key, so there is nothing else to configure and no email credential of your own to manage.

ParameterTypeRequiredDefaultDescription
channel_typeChannelType | strYesChannelType.EMAIL or "email".
thresholdfloatYesTrigger ratio in (0.0, 1.0].
tostrYesRecipient email address.

Slack #

The channel posts a Block Kit message to a Slack incoming webhook. Each message includes a plain-text text fallback so legacy webhooks render something useful.

ParameterTypeRequiredDefaultDescription
channel_typeChannelType | strYesChannelType.SLACK or "slack".
thresholdfloatYesTrigger ratio in (0.0, 1.0].
webhook_urlstrYesSlack incoming webhook URL.
templatedictNoOptional override with a 'text' key and optional 'blocks'. Supports template variables (see below).

Get a webhook URL #

  1. Open api.slack.com/apps and click Create New App, then From scratch.
  2. Pick the workspace, then enable Incoming Webhooks in the left sidebar.
  3. Click Add New Webhook to Workspace, choose a channel, and copy the URL.
  4. Store the URL in .env as AGENTKAVACH_SLACK_WEBHOOK_URL and pass it via webhook_url=os.environ["AGENTKAVACH_SLACK_WEBHOOK_URL"].

⚠️ Treat the URL as a secret

Anyone with the webhook URL can post to your channel. Store it in environment variables or a secret manager. Never commit it.

Custom template

python
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 #

python
AgentKavach.channel(
    ChannelType.PAGERDUTY,
    threshold=0.95,
    routing_key=os.environ["AGENTKAVACH_PAGERDUTY_ROUTING_KEY"],
)

The channel triggers an incident through the PagerDuty Events API v2. Severity maps from the threshold: warning below 90%, error from 90% to 99%, critical at 100%.

ParameterTypeRequiredDefaultDescription
channel_typeChannelType | strYesChannelType.PAGERDUTY or "pagerduty".
thresholdfloatYesTrigger ratio in (0.0, 1.0].
routing_keystrYesEvents API v2 integration key (32-char hex).

Create the routing key #

  1. In PagerDuty, open Services, Service Directory, then New Service.
  2. Pick an escalation policy, then add an Events API v2 integration.
  3. Copy the integration key from the integration row.
  4. Store it as AGENTKAVACH_PAGERDUTY_ROUTING_KEY and pass it via routing_key=os.environ[...].

ℹ️ Integration key is the routing key

PagerDuty calls the same 32-character hex string Integration Key in the UI and routing_key in the API. AgentKavach uses routing_key to match the Events API v2 spec.

Webhook #

python
AgentKavach.channel(
    ChannelType.WEBHOOK,
    threshold=0.80,
    url="https://api.acme.com/budget-alerts",
    secret=os.environ["AGENTKAVACH_WEBHOOK_SECRET"],
)

The channel POSTs a JSON payload to your URL. Provide a secret to receive an HMAC-SHA256 signature in the X-AgentKavach-Signature header.

ParameterTypeRequiredDefaultDescription
channel_typeChannelType | strYesChannelType.WEBHOOK or "webhook".
thresholdfloatYesTrigger ratio in (0.0, 1.0].
urlstrYesEndpoint to POST the JSON payload to.
secretstrNo""HMAC-SHA256 secret. When set, the signature is sent in X-AgentKavach-Signature.

Verifying the signature

python
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)

Template variables #

By default each channel formats its own message. When you want different wording, pass a template to the channel (as shown in the Slack example above) and reference any of the variables below with Python str.format() syntax, for example {agent_name} hit {pct}% of its {period} budget. Unknown names are left untouched, so a typo never crashes an alert.

VariableExample
{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
{resets_at}2026-05-30 00:00 UTC
{dashboard_url}https://agentkavach.com/dashboard/agents/...

Credentials #

AgentKavach never reads channel credentials from the environment. Give each channel its credential explicitly when you build the client, or define it in your YAML config. Reading from os.environ in your own code is encouraged, for example webhook_url=os.environ["AGENTKAVACH_SLACK_WEBHOOK_URL"]; the SDK simply will not do it for you.

⚠️ Never commit secrets

Webhook URLs, routing keys, and signing secrets all grant the ability to post into your systems. Store them in .env or a secret manager. Never in source.

Kill switch #

The kill switch is not a notification channel. The four channels above tell someone that spend is climbing; the kill switch acts on it by stopping the agent. You arm it by giving the client an on_kill callback and a kill threshold, usually 1.0. When usage reaches that point the SDK invokes your callback and then refuses to run again: every later guard.create() on that instance raises BudgetExceededError immediately.

python
import sys
from agentkavach import AgentKavach, Budget, ChannelType

def emergency_stop():
    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),
    ],
)

⚠️ A killed instance does not resume

Once the kill fires, that AgentKavach instance stays killed for its lifetime. Construct a fresh instance to start again, for example at the next budget period.

Dashboard Kill button

The Kill button on the Agents page records a kill on the backend. The SDK picks it up on its next config sync, and on the agent's next guard.create() it invokes the agent's on_kill callback and then raises BudgetExceededError. This is the same stop signal as a budget kill, which is why it runs the same callback and surfaces the same exception.

⚠️ The button stops the agent on its next call

The kill takes effect on the agent's next LLM call, not the instant you click. To make it terminate the process immediately, define an on_kill callback that exits, for example sys.exit(1). Without one, the agent stops making LLM calls but any surrounding non-LLM work continues until it finishes on its own.