Skip to main content

Deploy an agent

This page covers everything about getting agent code onto the platform: what goes in your directory, how the upload works (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
Where the API lives

There is no public API hostname yet — this platform is in alpha. Set CAI_API to the endpoint your administrator gives you, or leave it unset and platformctl opens a temporary connection to the platform for you. The raw HTTP examples below assume CAI_API and a token in CAI_TOKEN.

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 the platform's registry. The image tag is a content hash of your source, and the running service is pinned to the image digest (a fingerprint like localhost:30500/my-agent@sha256:...). Pinning by digest means the exact bytes you built are the exact bytes that run — a tag can be moved, a digest cannot.
  4. Rolls out a new revision (an immutable snapshot of image + settings) and routes traffic to it once it is healthy.

The agent's status 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:

FrameworkRequired fileIt must defineOptional
ADKagent.pya module-level root_agentrequirements.txt, other .py files
LangGraphgraph.pya module-level graph (a compiled graph)requirements.txt, other .py files
CrewAIcrew.pya module-level crewrequirements.txt, other .py files

The CLI auto-detects the framework from the entry file: crew.py means CrewAI, graph.py means LangGraph, anything else defaults to ADK. You can force it with --framework adk|langgraph|crewai.

Each framework guide has a complete example: ADK, LangGraph, CrewAI.

Deploy with the CLI

  1. Deploy the directory. --name defaults to the directory's name; names must be a lowercase DNS label (letters, digits, hyphens; max 63 characters) and are permanent for the agent's life:
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
status: -> building
status: building -> deploying
status: deploying -> ready
my-agent is ready at http://my-agent.cai-p-x7k2q.svc.cluster.local

The CLI polls status every 2 seconds for up to 5 minutes. On failure it prints my-agent failed to build/deploy (see 'platformctl logs my-agent') — but for build failures, check platformctl status my-agent first: the build output is in the message field (see below).

  1. Confirm it:
platformctl status my-agent

Deploy over raw HTTP

The API endpoint is POST /v1/agents. It accepts multipart/form-data (the standard file-upload encoding) with these fields:

FieldTypeRequiredDefaultNotes
nametextyesLowercase DNS label, max 63 characters. Immutable.
codefileyesA .tar.gz of your agent directory.
frameworktextnoadkOne of adk, langgraph, crewai, function.
runtimetextnopythonFunctions only (python, nodejs, go, ruby). Sending it with an agent framework is a 400.
configtextnoA JSON string with the same shape as PATCH /v1/agents/{name}/config, applied to the first revision.

Package and upload:

tar -czf my-agent.tar.gz -C my-agent .
curl -s -X POST "$CAI_API/v1/agents" \
-H "Authorization: Bearer $CAI_TOKEN" \
-F "name=my-agent" \
-F "framework=adk" \
-F "code=@my-agent.tar.gz"

You should see (HTTP 202 — the build runs in the background):

{"agent": "my-agent", "build_id": "2f6f2f6e-8a1e-4c3b-9d2a-1b2c3d4e5f6a"}

The upload is capped at 100 MiB by default. All errors use the standard envelope {"error": "<message>", "request_id": "<id>"}.

The config field

Use config to set scaling, resources, or the request timeout on the very first revision, instead of patching afterwards. Omitted fields keep platform defaults:

curl -s -X POST "$CAI_API/v1/agents" \
-H "Authorization: Bearer $CAI_TOKEN" \
-F "name=my-agent" \
-F "code=@my-agent.tar.gz" \
-F 'config={"scaling":{"min_scale":0,"max_scale":5,"container_concurrency":10},"timeout_seconds":300}'

The shape also accepts resources with requests and limits for CPU and memory (for example {"requests":{"cpu":"100m","memory":"256Mi"}}). Setting max_scale to 0 means unbounded. You can change all of this later with PATCH /v1/agents/{name}/config — every change rolls a new revision. See traffic and revisions.

Poll build status

Poll GET /v1/agents/{name} until status is ready or failed:

curl -s -H "Authorization: Bearer $CAI_TOKEN" "$CAI_API/v1/agents/my-agent"

You should see:

{
"name": "my-agent",
"status": "ready",
"kind": "agent",
"framework": "adk",
"image": "localhost:30500/my-agent@sha256:9f2c1a...",
"url": "http://my-agent.cai-p-x7k2q.svc.cluster.local",
"public_url": "https://my-agent-x7k2q.apps.codyhill.dev",
"latest_revision": "my-agent-00001",
"message": "",
"owner": "you@example.com"
}
  • url is the agent's internal cluster address, and out of the box it is the only address that serves anything. New agents are private by default: the platform marks them cluster-local, so the authenticated control plane is the only way to invoke them. See invoke your agent for how to publish one deliberately.
  • public_url is the address the agent would have if you published it (https://<name>-<project-short>.apps.codyhill.dev). It is returned even while the agent is private, so treat it as a reservation, not a live endpoint. external_url appears only once that address genuinely answers over a valid certificate.
  • message carries the last error, and this 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

From your machine: run platformctl deploy again with the same name. Same build flow, new revision, same URL.

From stored source: every deploy also stores your source files (best-effort) so you can edit them in the browser — see files and the editor. Saving a file does not change the running agent; the save response says so: saved - redeploy the agent for this to take effect. To rebuild from what is stored, no tarball needed:

curl -s -X POST -H "Authorization: Bearer $CAI_TOKEN" \
"$CAI_API/v1/agents/my-agent/redeploy"

You should see:

{"agent": "my-agent", "build_id": "8c41...", "files": 2, "note": "rebuilding from the stored source"}

If nothing is stored yet, you get 400: no source is stored for this agent - deploy it once from the CLI or upload files first.

From the console: the deploy dialog at https://console.codyhill.dev offers three input modes — write code in the browser (with starter templates per framework), upload files or a folder, or upload a ready .tar.gz. The browser refuses binary files and names the offending path; use the .tar.gz mode for those.

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; a stale build claim is reclaimable 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"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 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 more that is not an HTTP error: a deploy can build fine and then hang at deploying. If the project hit its service quota after the build started, the status message 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 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

This removes the running service, the agent's secrets and settings, and its record.

Next steps