Deploy a BDK project
Deploying a Bot Development Kit (BDK) project from a pushed Git ref creates a stable service. Cursor-managed hosting builds the selected ref and operates the runtime; self-hosting gives you control of the network, process, and filesystem.
Choose managed hosting or self-hosting
| Option | Use it when | You manage |
|---|---|---|
| Cursor-managed hosting | You want the shortest path from Git to a running agent | Source, declared egress, secrets, and agent behavior |
| Self-hosting | You need your own network, proxy, filesystem, or process controls | The Node process, TLS, auth, secrets, state, monitoring, and upgrades |
The rest of this guide starts with managed hosting. The self-host recipe uses the same project and HTTP API.
Deploy from Git
Commit and push the exact ref you want to run, then sign in, validate,
and deploy. The preferred CLI is bdk.
cd my-agentbdk loginbdk whoamibdk validatebdk deployThe deploy command waits until the runtime is running and prints the stable deployment URL.
deploy reads from Git; it does not upload your working tree. Inside a
checkout, it infers the HTTPS origin, current ref, agent path, and slug.
Pass them explicitly when the checkout is not the source you want:
bdk deploy \ --repo https://github.com/acme/agents \ --ref main \ --path agents/weather \ --slug weather-agentThe source repository must be available through the team's repository
integration, and a nested --path must contain an installable BDK
project.
Connect deployed peers
A team-owned deployment can call another agent deployed by the same team with
defineConnection({ agent: "weather-agent" }). Deploy the specialist and the
caller; Cursor-managed hosting resolves the slug and authenticates the call,
so you don't configure a peer URL, header, or secret.
Redeploy the caller after adding or changing a peer connection. An agent last deployed before hosted peer connections became available also needs one redeploy. See Peer agents for delegation, session affinity, and local development.
Privacy Mode
A hosted deployment stores a snapshot of its source repository, along with the sessions, KV, files, and logs the agent creates for your team. Whether managed hosting is available depends on the privacy setting that applies to you, which is your team's setting when the team enforces one:
| Privacy setting | Managed hosting |
|---|---|
| Privacy Mode | Available. Cursor stores deployment data the same way it does without Privacy Mode, and the agent's model requests get the same Privacy Mode handling as your other Cursor requests. |
| Privacy Mode (Legacy) | Not available. deploy, a hosted session, and an eval run all refuse. |
deploy refuses because a deployment stores a snapshot of your
repository. A hosted session refuses, and Cursor does not store the
message. An eval run refuses because it stores prompts and replies.
Switch to Privacy Mode, or ask a team admin when your team enforces
this setting.
The stored data includes the memory journal,
which bdk init turns on. The journal holds user messages and the
agent's replies. With Privacy Mode, Cursor stores it only to run your
agent and doesn't train on it.
Add egress and secrets
Declare the external hosts and secret names the agent needs. Mounted
extensions add the ones they declare, so list only what your own code
calls; bdk info --dir . shows the combined list and what each mount
adds. After the
deployment exists, set each value at the hidden prompt and follow the
CLI or dashboard instruction to redeploy.
// bot/agent.tsimport { defineAgent } from "@cursor/bdk";export default defineAgent({ hosting: { egressDomains: ["api.weather.example.com"], secretNames: ["WEATHER_API_KEY"], },});bdk secrets set weather-agent WEATHER_API_KEYbdk secrets list weather-agentbdk deploy --dir . --slug weather-agentCommit secret names, never values. secrets set accepts names and reads
values without echoing them; secrets list returns names and creation
times only. Do not pass NAME=VALUE, print a secret to verify it, or
put names beginning with reserved CURSOR_ in secretNames.
A hosted deployment receives its secrets per request, after its modules
are loaded, so code that reads process.env while a module is still
loading sees nothing. Read a secret inside the handler, tool, or callback
that needs it, and give an MCP connection its credential as a ${NAME}
template, which resolves when the connection opens. See
credentials in headers and env.
SCM webhook channels go one step further and name their secrets in the
channel definition, as { secretName: "GITHUB_WEBHOOK_SECRET" }, so the
platform can verify each delivery's signature before any compute starts.
validate rejects a literal or getter there. The
GitHub guide shows the named form.
Enter egress hosts without schemes or paths. An HTTPS host needs no port;
add one, as clickhouse.example.com:9440, only for a protocol the proxy
reads no hostname from. The
Host MCP OAuth guide covers OAuth-backed MCP
servers on a hosted agent.
Use the hosted agent
Verify a hosted turn
After deploy reports running, message the deployment from the CLI:
bdk run --prod --slug weather-agent \ --message "Forecast for Paris"The command returns as soon as the deployment accepts the turn, so its
output has a deliveryId and workflowId but no sessionId yet; the
session is created when the turn starts. Add --dry-run to check a
deployment without side effects: read tools run and write tools are
stubbed.
Find the new session in the deployment's recent output, which prints a
==> ses_... (http) <== header for each session, or open the deployment
from Deployed Agents in the dashboard
and select Sessions:
bdk logs --prod --once --slug weather-agentThen follow the turn:
bdk session ses_... --prod --slug weather-agentThe command lists each hosted run with its status and log lines. While
the turn is still working, it reports how many events have arrived and
prints Turn running; runs may lag behind events. Rerun it until the
latest run shows a final status such as finished. A high-effort turn
can take several minutes, and its run can show no logs until it ends, so
a growing event count is the progress signal.
To continue the same conversation, pass --session ses_... to
run --prod. bdk playground --prod --slug weather-agent --session ses_...
opens the session in the dashboard playground. Hosted deployments don't
support bdk chat, call, sessions, cost, or mcp; use
run --prod, logs --prod, and session --prod instead.
Send work to the deployment
A hosted agent wakes from its channels: Slack mentions and DMs, SCM events, and custom channel routes. External HTTP clients call a custom route on the stable alias URL and send the alias token on every request:
curl -X POST "$AGENT_ALIAS_URL/v1/channels/tickets/ticket" \ -H "X-Agent-Alias-Token: $AGENT_ALIAS_TOKEN" \ -H 'content-type: application/json' \ -d '{"ticketId":"ENG-142","message":"CSV export returns 500"}'The deployment answers 202 Accepted with an empty body as soon as the
request arrives, then runs the route's handler, so the caller never
receives a session ID or a result. Deliver results from the channel, as
described in Route responses.
To confirm the work started, find the new session with
bdk logs --prod and follow it with bdk session --prod, as in
Verify a hosted turn.
Protect the alias token
The first deploy prints the alias token once. Store it outside terminal
transcripts; it cannot be retrieved later. If it is lost or exposed,
bdk rotate-token weather-agent replaces it and invalidates the
old value.
deployment --json can include short-lived access headers, so treat
that output as a credential too.
For a custom webhook whose provider cannot send the alias token, use
publicEndpoint() only when the channel verifies the provider
signature. That opens the custom channel path, not the built-in session
or tool APIs. See Webhooks.
Update the deployment
Push the new ref and deploy the same slug. The stable URL remains while the new generation replaces the previous one.
bdk deploy --dir . --slug weather-agentA redeploy replaces its source, its watched repositories, and any egress
domains supplied through CLI flags. Pass every --cursor-events-repo value
again; without the flag, the deployment watches its GitHub source repository.
Keep source-controlled egress in the hosting block when possible.
Use bdk deployment weather-agent to inspect a rollout.
The CLI reference owns --no-wait,
multi-agent deployment, stop, and delete behavior.
Keep state across replacements
Managed hosting persists framework-owned session, KV, and file state
across normal runtime replacements. Files written elsewhere on the
runtime's local filesystem are disposable; use ctx.host.kv or
ctx.host.files for durable application state.
On architecture v2, the default, reminders take a prompt or a named handler. See Reminders.
Self-host securely
Run BDK under Node 22.13 or newer, never Bun. One supervised process can serve every agent under a directory while a persistent state root keeps sessions across restarts.
export CURSOR_API_KEY="<cursor-api-key>"export BDK_BEARER_TOKEN="$(openssl rand -hex 32)"bdk serve \ --dir /srv/agents \ --host 127.0.0.1 \ --port 3000 \ --state-root /var/lib/bdk \ --bearer-token "$BDK_BEARER_TOKEN"Before exposing the host:
-
Put TLS or an authenticating proxy in front of the loopback server.
-
Keep
--bearer-tokenon shared hosts. Use--allow-anonymousonly behind a proxy that authenticates every caller. -
Mount
--state-rooton a persistent volume outside the agent repository and back it up. -
Inject credentials at startup instead of baking them into an image.
-
Run the process under a supervisor and enable schedules on exactly one serving process per project.
Containers use the same command with --host 0.0.0.0, a mounted state
volume, and an authenticating network boundary. The
Channels reference covers
per-caller and channel-specific auth.
Observability
Use bdk logs --prod and bdk session <sessionId> --prod for a hosted
session's runs and logs, OpenTelemetry for traces and metrics, and
session traces
for a local conversation.
Related
- Early-alpha migration: move a hosted architecture v1 project to v2 through a reviewed pull request
- CLI reference: inspect, update, stop, delete, and rotate deployments
- GitHub: Cursor-event repositories and direct webhook delivery
- Slack: provision a dedicated hosted Slack app
- Host MCP OAuth: reach OAuth-backed MCP servers from a hosted agent
- Troubleshooting: diagnose failed or stale deployments