# AI Agent Notifications

> Notifications from AI coding agents (Claude Code, Codex, Cursor, Gemini CLI, Aider) when they finish or need input. Plus AGENTS.md snippet and Agent Skill.

Source: https://docs.alphorn.dev/ai-agents/

AI coding agents run for minutes or hours, and they stop whenever they need a decision from you. Alphorn turns those moments into a notification on the channel you actually look at (Slack, Telegram, SMS, push, email, or any other supported channel), with filters so a blocked agent can page you while a finished task only lands in a chat.

Every agent integration is the same HTTP request:

```bash
curl -fsS --max-time 5 -X POST https://app.alphorn.dev/n/V1StGXR8_Z5jdHi6B-myT \
  -H "Authorization: Bearer $ALPHORN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"title": "Claude Code - my-app", "message": "Needs permission to run tests", "priority": 4, "tags": ["ai-agent", "claude-code", "permission"]}'
```

Create a webhook first (see [Webhooks](/webhooks)) and use its **Endpoint URL** (plus its API key, if you switched on **Require API key**).

## Set up your agent

<Cards>
  <Card title="MCP Server" description="send_notification tool for any MCP client" href="/ai-agents/mcp" />
  <Card title="Claude Code" description="Stop and Notification hooks" href="/ai-agents/claude-code" />
  <Card title="OpenAI Codex CLI" description="Stop and PermissionRequest hooks, legacy notify" href="/ai-agents/codex" />
  <Card title="Cursor" description="stop hook for agent runs" href="/ai-agents/cursor" />
  <Card title="Gemini CLI" description="AfterAgent and Notification hooks" href="/ai-agents/gemini-cli" />
  <Card title="Aider" description="notifications-command" href="/ai-agents/aider" />
</Cards>

## Recommended conventions

Using the same fields across all agents keeps your [filters](/filtering) simple:

| Situation | `priority` | Tags |
|---|---|---|
| Task finished | 2 | `ai-agent`, `<agent>`, `completed` |
| Waiting for input | 3 | `ai-agent`, `<agent>`, `waiting` |
| Needs permission or blocked | 4 | `ai-agent`, `<agent>`, `permission` |
| Failed | 4 | `ai-agent`, `<agent>`, `error` |

Put the project name in the `title` (for example `Claude Code - my-app`) so you can tell parallel sessions apart. Alphorn truncates `title` to 200 and `message` to 4,000 characters, so you can pass an agent's last response without trimming it. See the [API Reference](/api-reference) for all fields.

## Let the agent notify you itself

Hooks fire on fixed events. To let the agent decide when a notification is worth sending (for example "tell me when the migration is done"), give it instructions. Set two environment variables where the agent runs:

```bash
export ALPHORN_WEBHOOK_URL="https://app.alphorn.dev/n/V1StGXR8_Z5jdHi6B-myT"
export ALPHORN_API_KEY="alp_..."
```

The snippets below build the JSON with `jq`, so the agent's environment needs `jq` and `curl`.

### AGENTS.md snippet

Add this to the `AGENTS.md` (or `CLAUDE.md`) of your project. Codex, Cursor, and many other agents read `AGENTS.md` automatically. Claude Code reads `CLAUDE.md`, which can import it with a line containing `@AGENTS.md`.

````markdown title="AGENTS.md"
## Notifications

Notify the user with Alphorn when a long-running task finishes or fails, or when you
are blocked on a decision and the user may not be watching. Do not notify for routine steps.

```bash
curl -fsS --max-time 5 -X POST "$ALPHORN_WEBHOOK_URL" \
  -H "Authorization: Bearer $ALPHORN_API_KEY" \
  -H "Content-Type: application/json" \
  -d "$(jq -n --arg t "<short title>" --arg m "<what happened, what you need>" \
        '{title: $t, message: $m, priority: 3, tags: ["ai-agent"]}')"
```

- priority: 2 = finished, 3 = needs input, 4 = blocked or failed
- Start the title with the project name. Never include secrets in the message.
````

### Agent Skill

Agents that support [Agent Skills](https://agentskills.io) (including Claude Code) load a skill only when it is relevant. Save this as `~/.claude/skills/alphorn-notify/SKILL.md` for all projects, or `.claude/skills/alphorn-notify/SKILL.md` for one project:

````markdown title="SKILL.md"
---
name: alphorn-notify
description: Send the user a notification through Alphorn. Use when the user asks to be notified, pinged, or alerted, when a long-running task finishes or fails, or when you are blocked and the user may be away.
---

# Notify the user with Alphorn

Requires ALPHORN_WEBHOOK_URL and ALPHORN_API_KEY in the environment, plus jq and curl.

```bash
curl -fsS --max-time 5 -X POST "$ALPHORN_WEBHOOK_URL" \
  -H "Authorization: Bearer $ALPHORN_API_KEY" \
  -H "Content-Type: application/json" \
  -d "$(jq -n --arg t "$TITLE" --arg m "$MESSAGE" --argjson p 3 \
        '{title: $t, message: $m, priority: $p, tags: ["ai-agent"]}')"
```

- Title: project name plus a few words, e.g. "my-app - tests passing".
- Message: what happened and what you need from the user, in one or two sentences.
- Priority: 2 = finished, 3 = needs input, 4 = blocked or failed.
- A 201 response means the notification was accepted.
- 401: the Authorization header is missing or malformed, or the API key is wrong. Check that ALPHORN_API_KEY is set.
- 400: invalid JSON body. 403: webhook disabled. 404: wrong webhook URL. 413: body too large (over 1 MiB). 429: quota exceeded, don't retry right away. 508: loop detected.
- Never include secrets, tokens, or file contents in the message.
````

## Custom agents

For an agent you build yourself, send a notification at the decision points:

```python
import os
import requests

WEBHOOK = os.environ["ALPHORN_WEBHOOK_URL"]
HEADERS = {"Authorization": "Bearer " + os.environ["ALPHORN_API_KEY"]}

def notify(event, message, priority=3):
    requests.post(WEBHOOK, json={
        "title": f"Agent - {event}",
        "message": message,
        "priority": priority,
        "tags": ["ai-agent", event],
    }, headers=HEADERS, timeout=5)

# When the agent needs human input
notify("waiting", "Agent is blocked and needs your decision")

# When a task completes
notify("completed", f"Finished in {duration}m: {summary}", priority=2)

# When something goes wrong
notify("error", f"{error}", priority=4)

# Cost tracking
notify("cost", f"${cost:.2f} spent of ${budget:.2f} budget",
       priority=4 if cost > budget * 0.8 else 2)
```

## Routing examples

| Channel | Filter | Purpose |
|---|---|---|
| Telegram | Tags **has any of** `waiting`, `permission` | Instant alert when any agent is blocked |
| Slack (#dev) | Tags **has any of** `ai-agent` | All agent activity |
| SMS | Priority **greater than** 3 | Only when an agent is stuck or failed |
| Email | Tags **has any of** `completed` | Archive of finished tasks |
