Skip to main content

Command Palette

Search for a command to run...

BDK

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

WantUse instead
Add context before the modelinstructions.md, skills, or workspaceFiles
Deliver to Slack or a PRChannel events or packs
Gate behaviorEvals

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.