Skip to main content

Deploy an agent

Deploying is how your code becomes a running agent. This page covers what goes in your directory, how the upload works from both the CLI and raw HTTP, how to watch a build, how redeploys work, and what every deploy error actually means.

Before you begin

  • You have an account. Accounts are created by an administrator or through an invitation link — ask your administrator if you don't have one. See create an account.
  • You have the CLI installed. See install the CLI.
  • You are signed in. Deploying is a management action and always requires credentials (invoking an agent, by contrast, does not by default):
platformctl login --email you@example.com
  • Your project is connected to Crusoe Cloud. Your image is built into a repository in your own Crusoe Cloud Registry, so a project with no credential cannot deploy — the first deploy is refused before anything is built. A project admin connects it once, and nothing on this page changes for a project that already is. See connect your Crusoe Cloud account, or Crusoe Cloud integration for the full reference.
Where the API lives

The raw HTTP examples below read the API address from CAI_API and a token from CAI_TOKEN. Set CAI_API=https://api.codyhill.dev. platformctl defaults to that address and needs neither variable set.

What a deploy does

When you deploy, the platform:

  1. Packages your directory into a .tar.gz archive (the CLI and the console do this for you).
  2. Builds a container image from it: your code is layered onto the framework's base image, and your requirements.txt is installed.
  3. Pushes the image to a repository in your own Crusoe Cloud Registry — the storage images are pulled from when a container starts. The repository is created for you on the first build of each workload and named cai-<project-short>-<workload>, so you never pre-create one. The image's tag, its readable label, is a hash of your source: a short fingerprint computed from the file contents. The running service is pinned to the image digest instead, a fingerprint of the built image itself, written like registry.us-east1-a.ccr.crusoecloudcompute.com/cai-ab12cd-my-agent@sha256:.... Pinning by digest means the exact bytes you built are the exact bytes that run. Someone can move a tag to point at different bytes; nobody can move a digest.
  4. Rolls out a new revision — an immutable snapshot of the image plus its settings, frozen the moment it is created — and routes traffic to it once it is healthy.

The agent's state walks buildingdeployingready, or ends at failed. Deploying again with the same name is a redeploy: same flow, new revision, and the agent keeps its URL.

Directory layout per framework

Your directory needs one entry file, named for the framework, plus an optional requirements.txt. The CLI auto-detects which framework you meant from that file name: crew.py means CrewAI, graph.py means LangGraph, anything else defaults to ADK. Force it with --framework adk|langgraph|crewai.

my-agent/
agent.py # required: defines a module-level `root_agent`
requirements.txt # optional
from google.adk.agents import Agent

from crusoe_adk.foundry import foundry_model
from crusoe_adk.tools import run_python, search_memory

root_agent = Agent(
name="my_agent",
model=foundry_model(),
instruction="Use run_python for math and search_memory to recall facts.",
tools=[run_python, search_memory],
)

Full contract and the built-in tools: build agents with ADK.

You can add other .py files and import them from the entry file. If you include a requirements.txt, its packages are installed at build time; a package that cannot be installed fails the build, not the running agent, and the reason is readable in the agent's message field.

Deploy

A name must be a lowercase DNS label — the kind of name allowed in a web address, so lowercase letters, digits, and hyphens only, up to 63 characters. It is permanent for the life of the agent.

--name defaults to the directory's name:

platformctl deploy ./my-agent --name my-agent

You should see:

packaging ./my-agent...
uploading my-agent (1.2 KiB, framework=adk)...
build 2f6f2f6e-8a1e-4c3b-9d2a-1b2c3d4e5f6a accepted
state: -> building
state: building -> deploying
state: deploying -> ready
my-agent is ready at https://my-agent-x7k2q.apps.codyhill.dev

The CLI checks the state every 2 seconds for up to 5 minutes, and packages the tarball for you. On failure it prints my-agent failed to build/deploy (see 'platformctl logs my-agent'). For a build failure, though, run platformctl status my-agent first — that is where the build output lands, in the message field described below.

Confirm it:

platformctl status my-agent

Poll the build state

The state walks buildingdeployingready, or ends at failed. A plain ready boolean travels beside it, true exactly when the state is ready.

platformctl deploy already polls for you and exits when the state settles. To check afterwards:

platformctl status my-agent

The table shows name, framework, state, ready, address, and image — and, on a failure, the message field described below. Add -o json for the raw response.

The address column shows the agent's public URL, or private when the agent has not been published — never the internal hostname, which would look like an address you could call but is not. The one field the CLI does not decode is latest_revision; for that, run platformctl agents revisions my-agent or read the curl tab's response.

The full response is:

{
"name": "my-agent",
"state": "ready",
"ready": true,
"kind": "agent",
"framework": "adk",
"image": "registry.us-east1-a.ccr.crusoecloudcompute.com/cai-ab12cd-my-agent@sha256:9f2c1a...",
"url": "http://<private-hostname>",
"public_url": "https://my-agent-x7k2q.apps.codyhill.dev",
"latest_revision": "my-agent-00001",
"owner": "you@example.com"
}
  • state is the agent's own vocabulary — building, deploying, ready, failed — and is the field to show a person. ready is the boolean beside it, true only while the state is ready, and is the field to branch on in a script. Every resource on the platform publishes the same pair, so one habit works everywhere.
  • url is the agent's internal address, reachable only from inside the platform. Out of the box it is the only address that serves anything. New agents are private by default. The one way in is the control plane — the platform's own management API, the thing platformctl and the console talk to — and it requires you to sign in. See invoke your agent for how to publish an agent deliberately.
  • public_url is the address the agent would have if you published it: https://<name>-<project-short>.apps.codyhill.dev, where <project-short> is your project short id. The platform returns it even while the agent is private, so treat it as a reservation rather than a live endpoint. A separate field, external_url, appears only once that address genuinely answers over a valid TLS certificate.
  • message carries the last error. It is omitted entirely when there is nothing to say, which is why the healthy response above has no message at all. When it is there, it matters more than it looks:
The message field is the build log

There is no separate build-log endpoint. When a build fails, the tail of the actual build output — the real pip error, the real syntax error — lands in the agent's message field, truncated at 4,000 characters with the suffix ...(truncated). Read it with platformctl status my-agent or in the console's failure panel before anything else.

Redeploy

Deploying again with the same name is a redeploy: same flow, new revision, same URL.

Every deploy also keeps a copy of your source files so you can read and edit them later — see files and the editor. The copy is best-effort, meaning the platform tries but does not guarantee it. Saving a file does not change the running agent, and the save response says so: saved - redeploy the agent for this to take effect.

Run the same command again:

platformctl deploy ./my-agent --name my-agent

This uploads your local directory, so it is the right choice whenever your machine holds the truth.

Limits

LimitValue
Code upload100 MiB (platform default)
Stored source (editor)1 MiB per file, 200 files per agent, 64 MiB expanded archive
Agent namelowercase DNS label, max 63 characters, immutable
Concurrent buildsone per agent; if a build crashes, its claim on the agent is released after 30 minutes

Full platform limits: limits reference.

Deploy errors, verbatim

These are the real error strings the API returns, so you can search for them.

StatusErrorWhat it means and what to do
400missing or invalid 'name' (must be a lowercase DNS label)Fix the name: lowercase letters, digits, hyphens; must start with a letter; max 63 characters.
400unsupported 'framework': one of "adk", "langgraph", "crewai", "function", "container"Typo in the framework field.
400missing 'code' file field: ...The code part was absent or not a file field.
400'runtime' only applies to framework=functionDrop the runtime field for agents.
400invalid 'config' field: ...The config value is not valid JSON of the expected shape.
409this project cannot deploy yet: its container images are built into your own Crusoe Cloud container registry, and no Crusoe Cloud credential is mapped to this project. A project admin sets one with PUT /v1/projects/{id}/crusoe-cloud ... then deploy again. The repository itself is created for you on the first deploy - there is nothing to pre-create. Nothing was built and your stored source was not touched.The project has no Crusoe Cloud connection. A project admin connects it — platformctl crusoe-cloud connect, or Project Settings in the console — then you deploy again. See connect your Crusoe Cloud account.
409this deploy could not be attached to a project, and images are built into the project's own Crusoe Cloud container registry - so there is no registry to push to. Deploy with a project-scoped credential, or name the project explicitly with ?project=<slug>, then deploy again. Nothing was built.The request resolved to no project, so there is no credential to look up. Sign in with a credential that belongs to a project, or add ?project=<slug> to the request.
409this project is at its service limit (N / M): deploying needs at least one more service and cannot proceed. Delete an agent or function, or ask an admin to raise the project's service quota, then deploy.Your project has a quota on how many services it can run. Delete something (platformctl delete <agent>) or ask a project admin to raise the quota.
409a build is already in progress for agent my-agentSomeone (or a script) is already deploying this agent. Wait for it, or wait 30 minutes for a stale claim to expire.
403an agent named my-agent already exists and has no recorded owner, so only a project admin can redeploy itThe agent was deployed without a signed-in owner. A project admin can adopt it by redeploying it.
404unknown agent: my-agentEither the agent doesn't exist, or it belongs to someone else — the API deliberately doesn't tell you which.

One failure is not an HTTP error at all: a deploy can build fine and then hang at deploying. That happens when the project hits its service quota after the build already started. The agent's message field explains it: This project is at its service limit (N / M), so the new revision cannot get a network route yet - .... The fix is the same as for the 409 above.

For runtime failures after a successful deploy (crash loops, missing root_agent, model auth), see troubleshooting.

Clean up

platformctl delete my-agent

You should see:

deleted my-agent

What delete removes, and what it leaves behind

Delete is narrower than most people expect. It tears down the agent's runtime and configuration, and it does not touch the data the agent produced.

Removed by deleteLeft behind
The running service and its public addressThe agent's conversations (sessions), stored in MemoryStore under keys like sess:<project-short>:my-agent:<session-id>
The per-agent Secret
The environment, compute and memory-policy settings
The agent's long-term memory and knowledge — the VectorDB collections mem_<project-short>_my-agent and know_<project-short>_my-agent
The agent's record, and with it the source files you stored, its build records, and its saved log history
Deleting an agent does not delete its data

If you have been asked to remove a customer's data, or you are handing a project over, deleting the agent is not enough. Its sessions - the transcripts - survive, and there is no single command that removes them all.

What the agent remembered does go with it: deleting the agent deletes its memory and knowledge collections. To erase one person's memories while the agent stays, use platformctl agents memory forget <agent> --user <id> --yes (see Long-term memory).

Sessions are kept indefinitely unless your administrator configured an expiry.

Removing an agent's data before you delete it

Do this first, while the agent is still running. The session routes read through the agent itself, so they stop working the moment it is gone.

Sessions are indexed per end user. There is no "every session" view, by design — it is the same boundary that keeps one end user's history out of another's. So the walk is always: list the users, then list each user's sessions, then delete them one at a time. There is no bulk-delete route.

platformctl agents users list my-agent
platformctl agents sessions list my-agent --user 7c9e6679-7425-40de-944b-e07fc1f90ae7
platformctl agents sessions delete my-agent 3f2c8a1e-9d41-4c1b-a2f7-0b1c2d3e4f5a

The owning user is resolved from the session itself, so sessions delete does not need --user.

A transcript can carry whatever the end user typed and whatever the agent's tools were called with. Treat it as customer data. See sessions for the full list-and-read surface.

  1. Memory bank collection cleanup. Memory bank collections (mem_<project-short>_<agent>) are platform-managed collections. To remove a memory bank collection and its stored entries, request your platform administrator to remove the mem_<project-short>_<agent> collection directly from the underlying vector store.

Only after that is done should you delete the agent.

Next steps