# Deploy an Agent SDK project

Deploying an Agent SDK 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](#self-host-securely) 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 `agent-sdk`.

```text
cd my-agent
agent-sdk login
agent-sdk whoami
agent-sdk validate
agent-sdk deploy
```

The deploy command waits until the runtime is running and prints the stable
deployment URL. Open [Deployed Agents](https://cursor.com/dashboard/deployed-agents) to see the agent and manage it.

`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:

```text
agent-sdk deploy \
  --repo https://github.com/acme/agents \
  --ref main \
  --path agents/weather \
  --slug weather-agent
```

The source repository must be available through the team's repository
integration, and a nested `--path` must contain an installable Agent SDK
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](/docs/guides/agent-to-agent.md) 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](/docs/reference/memory.md) journal,
which `agent-sdk 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; `agent-sdk info --dir .` shows the combined list and what each mount
adds. Deploy the agent, then set each value at the hidden prompt.
The next task uses the new value. You do not deploy again to apply
that value.

```ts
// bot/agent.ts
import { defineAgent } from "@grok/agent-sdk";

export default defineAgent({
  hosting: {
    egressDomains: ["api.weather.example.com"],
    secretNames: ["WEATHER_API_KEY"],
  },
});
```

```text
agent-sdk deploy --dir . --slug weather-agent
agent-sdk secret set weather-agent WEATHER_API_KEY
agent-sdk secret list weather-agent
```

Commit secret names, never values. `secret set` accepts names and reads
values without echoing them; `secret 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](/docs/reference/connections.md#credentials-in-headers-and-env).

`agent-sdk deploy` runs the top-level code in your files one time to build
the snapshot. No secrets exist at that time. The snapshot keeps those
values.

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](/docs/guides/github.md#other-hosts) 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](/docs/guides/mcp-oauth.md) covers OAuth-backed MCP
servers on a hosted agent.

Your team egress policy limits `hosting.egressDomains`. `agent-sdk deploy` fails
when a requested host is outside that policy, and the bot that is already
running keeps the policy from its last successful deploy. A bot that lists
no egress hosts receives only the platform base. Architecture v2 stores
that decision on the release, from the hosts the release itself declares.
The server reads those hosts from the engine once the release is healthy.
It does not use a host list sent by the CLI, the portal, or MCP. An older
client that omits the optional field is marked the same way. A new release
with no stored set for that team does not start. On a shared release each
installing team has its own stored set. A release written before this change
keeps its previous behavior.

A PlanetScale Postgres host (`*.pg.psdb.cloud`) has no port in the
declaration. Under a restricted team mode it is a snapshot key for ports
5432 and 6432, using the team `CIDR:port` entries that cover every
resolved address. An address outside those entries blocks the deploy. The
snapshot does not pin the addresses from that lookup.

| Team egress mode | What `agent-sdk deploy` allows |
| --- | --- |
| No mode set | Every host in `hosting.egressDomains`. |
| Allow all network access | Every host in `hosting.egressDomains`. The bot stays limited to that list. |
| Defaults + team allowlist | The platform default list, plus the team list. |
| Team allowlist only | The team list. |

The platform base is always allowed. A team list cannot remove it. The base
is a small set of exact hosts and a few wildcards the pod needs to install
and run: Cursor API hosts, npm and Yarn registries, Slack, Origin, and the
artifact and Agent Store buckets. `agent-sdk deploy --json` includes those hosts
on `egress.effective`, together with the hosts the team policy allowed.
`agent-sdk deployment --json` includes `egressAllowedDomains`, the hosts from
`hosting.egressDomains` that the last successful deploy allowed.

A team entry `*.example.com` allows `example.com`, any subdomain, and a
narrower wildcard such as `*.api.example.com`. A bot entry `*.example.com`
does not add `example.com`. List the apex on the bot when the bot calls it.

A raw TCP host such as `db.example.com:5432` is allowed only when the team
list has a public IPv4 `CIDR:port` entry that contains every address that
name resolves to, on the same port. Private addresses and IPv6 are refused.
When deploy blocks hosts, the error names each host, the team mode, and two
fixes: ask a team admin to add the hosts (admins get the settings link
directly), or remove the hosts from `hosting.egressDomains` and deploy
again.

## Use the hosted agent

### Verify a hosted turn

After `deploy` reports `running`, message the deployment from the CLI:

```bash
agent-sdk session create --slug weather-agent \
  --message "Forecast for Paris" --wait
```

The deployment accepts the turn as a delivery and creates the session when
the turn starts. `--wait` polls the delivery until it names the session,
then prints the `sessionId`. Without `--wait`, the output has a
`deliveryId` and `workflowId` but no `sessionId` yet. Add `--read-only` to
check a deployment without side effects: read tools run and write tools are
stubbed.

You can also find the session in the deployment's recent output, which
prints a `==> ses_... (http) <==` header for each session, or open the
deployment in
[Deployed Agents](https://cursor.com/dashboard/deployed-agents) and select **Sessions**:

```bash
agent-sdk logs --prod --once --slug weather-agent
```

Then follow the turn:

```bash
agent-sdk session get ses_... --prod --slug weather-agent
```

The command lists each hosted run with its status and log lines. When the
deployment stored process output, a `Process output:` section prints that
output. `agent-sdk logs --prod` prints the same process output that [Deployed Agents](https://cursor.com/dashboard/deployed-agents) shows
for a persistent deployment, under `==> process-output <==`. Pass the session
ID to also print that session's process output under `Session process output:`
and a `tool <name>` line for each tool call. While the turn is still working, the session command 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 see which tools the bot called, print the hosted events:

```bash
agent-sdk session get ses_... --prod --slug weather-agent --view events
```

Each tool call is an `actions.requested` or `action.result` event. The tool
name is `toolName`.

To continue the same conversation, pass `--session ses_...` to
`session create`. `agent-sdk session get ses_... --prod --slug weather-agent --web`
opens the session in the [Deployed Agents](https://cursor.com/dashboard/deployed-agents) playground. Hosted deployments don't
support `agent-sdk chat`, `tool call`, `--view cost`, or `mcp`; use
`session create`, `logs --prod`, `session list --prod`, and
`session get --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:

```bash
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](/docs/reference/channels.md#route-responses).

To confirm the work started, find the new session with
`agent-sdk logs --prod` and follow it with `agent-sdk session get --prod`, as in
[Verify a hosted turn](#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,
`agent-sdk deployment 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](/docs/guides/webhooks.md).

## Update the deployment

Push the new ref and deploy the same slug. The stable URL remains while
the new generation replaces the previous one.

```text
agent-sdk deploy --dir . --slug weather-agent
```

A 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 `agent-sdk deployment get weather-agent` to inspect a rollout.
The [CLI reference](/docs/reference/cli.md#deploy) 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](/docs/reference/schedules.md#reminders).

## Self-host securely

Run Agent SDK 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.

```text
export CURSOR_API_KEY="<cursor-api-key>"
export AGENT_SDK_BEARER_TOKEN="$(openssl rand -hex 32)"

agent-sdk serve \
  --dir /srv/agents \
  --host 127.0.0.1 \
  --port 3000 \
  --state-root /var/lib/agent-sdk \
  --bearer-token "$AGENT_SDK_BEARER_TOKEN"
```

Before exposing the host:

- Put TLS or an authenticating proxy in front of the loopback server.

- Keep `--bearer-token` on shared hosts. Use `--allow-anonymous` only
  behind a proxy that authenticates every caller.

- Mount `--state-root` on 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](/docs/reference/channels.md#auth-policies) covers
per-caller and channel-specific auth.

## Observability

Use `agent-sdk logs --prod` for a hosted session's process output, and
`agent-sdk session get <sessionId> --prod` for its runs. Add `--view events` to
print the tool calls. [OpenTelemetry](/docs/guides/opentelemetry.md) covers traces and
metrics, and
[session traces](/docs/reference/sessions.md#inspect-a-saved-event-stream)
cover a local conversation.

## Related

- [Early-alpha migration](/docs/guides/migrate.md): move a hosted architecture
  v1 project to v2 through a reviewed pull request
- [CLI reference](/docs/reference/cli.md#deploy): inspect, update, stop,
  delete, and rotate deployments
- [GitHub](/docs/guides/github.md): Cursor-event repositories and direct
  webhook delivery
- [Slack](/docs/guides/slack.md): provision a dedicated hosted Slack app
- [Host MCP OAuth](/docs/guides/mcp-oauth.md): reach OAuth-backed MCP
  servers from a hosted agent
- [Troubleshooting](/docs/troubleshooting.md): diagnose failed or stale
  deployments
