Hooks
Hooks observe session events after they are recorded and run side effects such
as updating metrics or sending alerts. They never change the turn, prompt, or
reply. Author them under bot/hooks/ with defineHook from
@cursor/bdk/hooks.
Meter token usage
With OpenTelemetry configured, this hook adds a live turn's reported input and output tokens to your counters. Eval runs stay quiet, so the dashboard reflects live traffic instead of the test suite.
// bot/hooks/usage.tsimport { defineHook } from "@cursor/bdk/hooks";export default defineHook({ events: { async "turn.completed"(event, ctx) { if (ctx.session.purpose === "eval") { return; } if (event.data.usage === undefined) { return; } const { inputTokens, outputTokens } = event.data.usage; ctx.host.otel.increment("acme.tokens.input", inputTokens); ctx.host.otel.increment("acme.tokens.output", outputTokens); }, },});Alert on failure
Set PAGER_WEBHOOK_URL to your pager's webhook. When a live turn fails, this
hook posts the agent, session, and failure message there. Eval runs and
interrupted turns stay quiet, so tests and preemptions do not page anyone.
// bot/hooks/page-on-failure.tsimport { defineHook } from "@cursor/bdk/hooks";export default defineHook({ events: { async "turn.failed"(event, ctx) { if (ctx.session.purpose === "eval") { return; } if (event.data.message === "turn interrupted") { return; } const pagerUrl = process.env.PAGER_WEBHOOK_URL; if (pagerUrl === undefined) { return; } await fetch(pagerUrl, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ agent: ctx.agent.name, session: ctx.session.id, channel: ctx.channel.id, message: event.data.message, }), signal: AbortSignal.timeout(5_000), }); }, },});Events / when hooks run
Use event names from the session event vocabulary. A hook receives each matching event after it is recorded, and the model does not wait for the handler.
Within one session, handlers run one at a time. A slow handler delays later
handlers for that session, but it does not delay the model or handlers for
other sessions. Hooks also fire for evals, so check
ctx.session.purpose === "eval" before metering or paging. A restart does not
replay recorded events into hooks.
When not to use a hook
| Want | Use instead |
|---|---|
| Add context before the model | instructions.md, skills, or workspaceFiles |
| Deliver to Slack or a PR | Channel events or packs |
| Gate behavior | Evals |
To act on a turn's decision, have the model record it with an authored
tool, then act on it from a turn.completed hook.
Cursor Agent hooks in
.cursor/hooks.json are a different product. They can observe, block, or
modify the local agent loop.
Related
- Hooks reference: payloads, context, and discovery
- Sessions: stream events: event vocabulary and payload sequence
- OpenTelemetry: export traces and custom metrics
- Channels: events: deliver replies back to Slack, source control, or another surface