Skip to main content

Quickstart: deploy your first agent

In about five minutes you'll deploy a real AI agent, talk to it, and clean it up. The agent can run Python code in an isolated sandbox, and it remembers what you told it earlier in the same conversation.

Every step below is shown three ways — platformctl, curl, and the console. Pick whichever tab suits you and stay in it; the tabs are the same work through three different doors. Your choice follows you across every page in these docs.

If anything goes wrong, jump to If something breaks at the bottom — the common failures and their exact error messages are listed there.

Before you begin

  • A platform account and a selected project — see Create an account.

  • platformctl tab: the CLI built and signed in — see Install the CLI.

  • curl tab: a token and the API address in your shell:

    export CAI_API=https://api.codyhill.dev
    export CAI_TOKEN="<your session token or API key>"
  • Console tab: nothing else. A browser is enough.

  • A Crusoe Cloud access key (an access key ID and its secret key) for step 1, and the admin role in your project so you can save it. Create the key in Crusoe Cloud — the platform never mints one for you. If someone has already connected your project, you need neither.

  • A model (inference) API key for your project, because the agent's deploy is refused without one. There is no platform-wide model key: every project brings its own Crusoe Managed Inference key, saved once under Project Settings in the console (or PUT /v1/projects/{id}/inference). If your project already has one — check on that page — you need nothing here.

Step 1: Connect your Crusoe Cloud account

Your agent is built into a container image — a self-contained bundle of your code and everything it needs to run — and that image is stored in a repository in your own Crusoe Cloud Registry. Your account, your quota, your bill, your retention policy, and no other customer's images sitting next to yours. That is why the platform needs a Crusoe Cloud credential for your project before it can build anything, and why a deploy without one is refused up front rather than failing halfway through a build.

This is a one-time step per project. If your project is already connected, nothing here changes for you — skip to step 2.

Check first — if this says you are connected, go to step 2:

platformctl crusoe-cloud show

An unconnected project answers:

not connected: this project has no Crusoe Cloud credential.
connect one with 'platformctl crusoe-cloud connect --access-key-id <id>'.

So connect it. Put the secret key in a shell variable (or a file), because it is read from standard input and never from a flag:

read -rs CRUSOE_SECRET_KEY # paste the secret key; it is not echoed
printf %s "$CRUSOE_SECRET_KEY" | platformctl crusoe-cloud connect --access-key-id 'CRUSOEEXAMPLEKEYID'

You should see:

connected - the credential was accepted by Crusoe Cloud and stored.
crusoe cloud project: ml-platform
project id: b6f1a0c2-1f2e-4a55-9a4e-2c0a7f8d3e11
region: us-east1-a

There is deliberately no --secret-key flag: a secret passed as an argument lands in your shell history and in the process table. Pipe it in, as above, or point at a file with --secret-key-file ./secret-key. Read it back with --secret-key-file - if you prefer to be explicit about standard input.

Two things that occasionally bite: if your key can reach more than one Crusoe Cloud project the command is refused until you add --cc-project-id <id>, and the command needs to know which platform project it is acting on — pass --project, set $CAI_PROJECT, or run platformctl config set-project.

You do not create a repository yourself. The platform creates one per workload the first time it builds it, so there is nothing to pre-create here.

The secret key is checked against Crusoe Cloud before it is stored, so a typo fails here rather than as a mystery three commands later — and once stored it is never returned by any read. Full detail, including what happens when you disconnect, is in Crusoe Cloud integration.

Step 2: Write the agent

An agent is a directory with one entry file in it. Which file, and what it must define, is the only thing that differs between frameworks — the platform runs all three unmodified.

ADK is Google's open-source Agent Development Kit. Your directory needs an agent.py defining root_agent.

mkdir my-first-agent && cat > my-first-agent/agent.py <<'EOF'
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_first_agent",
model=foundry_model(),
instruction="Use run_python for math and search_memory to recall facts.",
tools=[run_python, search_memory],
)
EOF
  • root_agent is the name the platform looks for. It must be defined at module level — the top level of the file, not inside a function.
  • foundry_model() returns the platform-managed model. Swap models later via configuration, not code.
  • run_python and search_memory come from crusoe_adk.tools.

You don't need a requirements.txt. Each framework's base image — the prebuilt starting point your agent is built on top of — already ships the framework, the Crusoe helpers, and these tools.

Two of those helpers are worth naming now, because every agent uses them:

Step 3: Deploy it

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

You should see:

packaging ./my-first-agent...
uploading my-first-agent (1.2 KiB, framework=adk)...
build 2f6f1c3a-... accepted
state: -> building
state: building -> deploying
state: deploying -> ready
my-first-agent is ready at https://my-first-agent-ab12cd.apps.codyhill.dev

The framework= on the upload line is whichever framework you wrote in step 2 — the CLI works it out from the entry file in your folder: crew.py means CrewAI, graph.py means LangGraph, anything else means ADK. Force it with --framework adk|crewai|langgraph.

The CLI polls every 2 seconds and waits up to 5 minutes.

Step 4: Talk to it

Send a message that makes the agent do arithmetic. It should reach for the sandbox rather than guess.

platformctl invoke my-first-agent "My favorite number is 42. Compute 2**32 in python."

You should see:

2**32 is 4294967296.
(session: 3f2c8a1e-9b7d-4e21-a6c0-5d8f13b2e470)
tool_call: run_python called with args={'code': 'print(2**32)'}

Copy that session id — you need it in step 5.

Three things happened. The model decided to use a tool. run_python executed real Python in an isolated sandbox. And the platform minted a session id — the handle for this conversation.

Step 5: Prove it remembers

Ask a follow-up in the same session. Nothing in your new message mentions 42, so the only way the agent can answer is from the stored conversation.

platformctl invoke my-first-agent "What's my favorite number?" \
--session 3f2c8a1e-9b7d-4e21-a6c0-5d8f13b2e470

You should see: a reply mentioning 42. The platform stored turn 1 and replayed it to the model for turn 2. That's sessions working. Start a turn without the session id and each invoke begins a brand-new conversation.

Step 6: Look under the hood (optional)

platformctl status my-first-agent

You should see: the agent's name, framework, state (ready), address, and image. The address is the agent's public URL, or private if you have not published it. The one field the table does not show is latest_revision — the next command lists revisions properly anyway.

To list this agent's revisions — immutable snapshots of your code and settings, one per deploy — and the share of traffic each one serves:

platformctl agents revisions my-first-agent

And to read logs:

platformctl logs my-first-agent --history

--history shows persisted logs, which survive even when the agent has scaled to zero.

Clean up

Optional — skip this if you want to keep going.

platformctl delete my-first-agent

You should see:

deleted my-first-agent

Delete is narrower than it looks: it removes the running agent, not the conversations it stored or anything it memorized. See what delete leaves behind before you rely on it to remove data.

What just happened

  1. Build. The platform packaged your folder and built it into a container image. The image is layered on top of your framework's managed harness, a small web server that runs your code. The finished image went into your own Crusoe Cloud Registry, into a repository the platform created for this agent and named cai-<project-short>-<workload> — so it sits in your account, under your quota, alongside your other repositories.
  2. Deploy. The image became a serverless service in your project with a stable invoke endpoint, pulling from that repository with a credential minted from the Crusoe Cloud key you connected in step 1.
  3. Wire-up. Sessions (conversation history), the memory bank, the sandbox, and the model were injected automatically — zero configuration from you.
  4. Scale-to-zero. A few minutes after your last message, the agent's instances drop to zero. The next invoke cold-starts it, so expect that first reply after an idle period to be slower than the ones that follow.

Notice what did not change between the three framework tabs in step 2: steps 3 through 6 are identical. Once deployed, an agent answers the same HTTP API no matter which framework wrote it — callers cannot tell them apart.

If something breaks

SymptomCause and fix
409this project cannot deploy yet: ... no Crusoe Cloud credential is mapped to this projectYou skipped step 1. Nothing was built and your source was not touched. Connect the project, then deploy again.
409this project has no inference credentialThe project has no model API key, and the deploy was refused before building anything. Save the project's Crusoe Managed Inference key under Project Settings in the console (or PUT /v1/projects/{id}/inference), then deploy again.
State is failed after deployThe build or startup failed. Run platformctl status my-first-agent — the tail of the build output lands in the message field. In the console it is on the Overview tab.
Error mentions could not import 'root_agent'Your agent.py doesn't define a module-level root_agent. Match the code above exactly. The CrewAI and LangGraph equivalents are a missing crew and a missing graph.
invalid agent name (must be a lowercase DNS label)A DNS label is one piece of a hostname, the part between dots. Names allow only lowercase letters, digits, and hyphens, starting with a letter, up to 63 characters.
The reply is a model authentication errorThe project's model key is wrong or was rotated without a redeploy. Check the key under Project Settings, then redeploy the agent so it picks the new value up — running instances read it at cold start. A per-agent MODEL_API_KEY overrides the project's key if set — see Secrets and env.
401this platform requires authentication to invoke agents (INVOKE_AUTH_REQUIRED=true)You invoked without a credential. Sign in (platformctl login) or send Authorization: Bearer $CAI_TOKEN — invoking is gated on this install, and the error names the setting in case your install differs.
Turn 2 forgot turn 1You didn't pass the session id, so each invoke started a fresh conversation.

More in troubleshooting agents.

Next steps

  • Crusoe Cloud integration — the rest of what the credential you connected in step 1 unlocks: object-storage buckets, your repository list, and the models your account can serve.
  • Make it reachable by your users — publish the agent with CAI_EXPOSE_EXTERNAL, because agents are private until you do.
  • Deploy your first function — a plain HTTPS endpoint in one file, in any of four languages.
  • Sessions — how conversations persist, and the user_id model.
  • Memory — promote a conversation into long-term memory that new sessions can recall.
  • Tools — write your own tools beyond run_python.
  • Framework guides with the full contract: ADK, CrewAI, LangGraph.
  • Embedded chat widget — put this agent on your own website with one script tag.