# Deploy an Agent SDK project

Deploy a pushed Git ref as a stable service on Cursor-managed hosting, or
self-host it when you need your own network or process controls.

## Deploy from Git

Commit and push the ref you want to run, then deploy it:

```bash
cd my-agent
agent-sdk login
agent-sdk validate
agent-sdk deploy
```

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

`deploy` builds the pushed ref, so uncommitted changes don't ship. Pass the source when the checkout is not the one you want:

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

### Connect deployed peers

To call another agent your team deployed, add
`defineConnection({ agent: "weather-agent" })` to the caller and redeploy
it. See [Peer agents](https://cursor.com/docs/agent-sdk/guides/agent-to-agent.md).

### Privacy Mode

Managed hosting stores your repository and the agent's data, so it depends
on your privacy setting:

| 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.                                                                                                               |

Switch to Privacy Mode, or ask a team admin when your team enforces the
setting.

## Add egress and secrets

Declare the hosts and secret names your code calls. `agent-sdk info --dir .`
shows them combined with what mounted extensions add.

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

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

After you deploy, set each value at the hidden prompt. The next task uses
the new value without another deploy.

```bash
agent-sdk secret set weather-agent WEATHER_API_KEY
agent-sdk secret list weather-agent
```

Commit secret names, never values. Don't pass `NAME=VALUE` to `secret set`
or use a name that starts with `CURSOR_`.

Hosted secrets arrive after your modules load, so read each one inside the
handler, tool, or callback that needs it. Give an MCP connection a
[`${NAME}` template](https://cursor.com/docs/agent-sdk/reference/connections.md#credentials-in-headers-and-env)
instead.

SCM channels take each credential as a named secret, such as
`{ secretName: "GITHUB_WEBHOOK_SECRET" }`. The channel reads the environment
variable with that name locally and the deployment secret on Cursor hosting.
On Cursor hosting, Cursor rejects a badly signed delivery before it reaches your agent. `validate`
fails on a literal or a getter.

Write each egress host as a lowercase hostname with no scheme or path, such as `api.example.com`, and add a port such as `clickhouse.example.com:9440` only for raw TCP. `agent-sdk deploy` rejects any other form, and the error quotes that host.

Your team's egress policy limits which hosts you can list in
`hosting.egressDomains`. If you list a host that the policy blocks,
`agent-sdk deploy` fails and the error names that host. A deployment that
is already running keeps the policy from its last successful deploy. Ask a
team admin to add the host, or remove it and deploy again.

| 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.                                                             |

No team list can remove the platform base, which includes Cursor's API and
the npm registry. `agent-sdk deploy --json` lists every allowed host under
`egress.effective`.

A team entry `*.example.com` allows `example.com`, every subdomain, and
narrower wildcards such as `*.api.example.com`. An agent entry
`*.example.com` skips the apex, so list `example.com` too when the agent
calls it.

A raw TCP host such as `db.example.com:5432` needs a team `CIDR:port` entry
that covers every public IPv4 address it resolves to. If your team
restricts egress, a PlanetScale Postgres host (`*.pg.psdb.cloud`) needs a
team `CIDR:port` entry for ports 5432 and 6432.

## Use the hosted agent

### Verify a hosted turn

```bash
agent-sdk session create --slug weather-agent --message "Forecast for Paris" --wait
agent-sdk session get ses_... --prod --slug weather-agent
agent-sdk session get ses_... --prod --slug weather-agent --view events
agent-sdk logs --prod --slug weather-agent
```

Without `--wait`, the command returns before the session exists, so the
output has no `sessionId` yet. A long turn can show `running` with no logs
for several minutes. If the event count keeps going up, the turn is still
working.

### Send work to the deployment

Call a custom route on the stable alias URL with the alias token:

```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"}'
```

See [Route responses](https://cursor.com/docs/agent-sdk/reference/channels.md#route-responses) for what the
caller gets back. To confirm the work started, run
`agent-sdk session list --prod --slug weather-agent`.

### Protect the alias token

The first deploy prints the alias token once. If it leaks or you lose it, run `agent-sdk deployment rotate-token weather-agent`. Output from `deployment --json` can include access headers, so guard it the same way.

Use [`publicEndpoint()`](https://cursor.com/docs/agent-sdk/guides/webhooks.md) only for a webhook provider
that can't send the token and whose signature the channel verifies.

## Update the deployment

Push the new ref and deploy the same slug. Each redeploy replaces the
watched repositories, so pass every `--cursor-events-repo` value again, or
the deployment watches only its GitHub source repository. Run
`agent-sdk deployment get weather-agent` to check a rollout.

## Keep state across replacements

Sessions, `ctx.host.kv`, and `ctx.host.files` survive restarts and
redeploys. Anything else the agent writes to disk can disappear.

## Self-host securely

Run Agent SDK under Node 22.13 or newer, never Bun. One process can serve
every agent under a directory:

```bash
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 you expose the host:

- Put TLS or an authenticating proxy in front of the server.
- Keep `--bearer-token` unless a proxy authenticates every caller.
- Keep `--state-root` on a backed-up persistent volume outside the
  repository.
- Turn on schedules in only one process per project.

In a container, use `--host 0.0.0.0` behind an authenticating network
boundary.


---

## Sitemap

[Overview of all docs pages](/llms.txt)
