Agents and functions
This page documents every platformctl command that works on agents and functions: what it does, its flags, and what its output looks like. Agents and functions share one command surface — a function is deployed with functions deploy, then managed with the same status, logs, invoke, and delete verbs as an agent.
The everyday verbs sit at the top level, where muscle memory expects them. Everything else — revisions, traffic, compute config, environment, stored source, conversations, the chat widget — lives under platformctl agents, documented in the agents group below.
All commands here honor the global flags (--api, --project, -o). Management commands (everything except invoke) need a credential; invoke works without one by default.
deploy and functions deploy build a container image and push it to a repository in your project's own Crusoe Cloud registry, so a project with no stored credential is refused before anything is built:
this 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 fixes it once with platformctl crusoe-cloud connect. A project that is already connected needs nothing new.
Command summary
| Command | What it does | Credential |
|---|---|---|
platformctl deploy <dir> | Package, upload, build, and start an agent | Required |
platformctl functions deploy <dir> | Same, for a one-file function | Required |
platformctl list | List all agents and functions | Required |
platformctl status <agent> | Show one agent's full state | Required |
platformctl invoke <agent> <message> | Send a message, print the reply | Not required (by default) |
platformctl memorize <agent> --session <id> | Save a session to long-term memory | Required |
platformctl secrets set <agent> KEY=VALUE... | Add, update, or clear an agent's secrets | Required |
platformctl logs <agent> | Read logs (live, follow, or persisted) | Required |
platformctl delete <agent> | Delete an agent or function | Required |
platformctl demo | Narrated end-to-end walkthrough | Required (it deploys) |
And the agents group, all of which require a credential:
| Command | What it does |
|---|---|
platformctl agents redeploy | Rebuild from the source the platform already has |
platformctl agents revisions | List revisions and the traffic each one serves |
platformctl agents set-traffic | Pin which revisions serve traffic — the rollback control |
platformctl agents config get / set | Scaling bounds, per-instance resources, request timeout |
platformctl agents env get / set | Plain (non-secret) environment variables |
platformctl agents secrets get | Which secret keys are set — key names only, never values |
platformctl agents spec | The live service spec, as YAML |
platformctl agents metrics | Live instances, per-revision replicas, readiness |
platformctl agents users list | The end users who have talked to the agent |
platformctl agents sessions list / get / delete | Browse and delete conversations |
platformctl agents files list / get / put / delete | Read and edit the stored source |
platformctl agents embed get / enable / disable / rotate-key | The embeddable chat widget |
platformctl deploy
Packages a directory of agent code, uploads it, and waits until the agent is running.
platformctl deploy <dir> [--name <name>] [--framework adk|langgraph|crewai]
| Flag | Default | What it does |
|---|---|---|
--name | The directory's base name | The agent's name. Must be a lowercase DNS label matching ^[a-z]([a-z0-9-]{0,61}[a-z0-9])?$: start with a lowercase letter, end with a lowercase letter or digit (never a hyphen), use only a-z, 0-9, and - in between, at most 63 characters. |
--framework | Auto-detected | Which agent library your code uses: adk, langgraph, or crewai. Auto-detection: a crew.py file means crewai, a graph.py file means langgraph, anything else means adk. |
What happens: the CLI tars the directory's contents in memory (symlinks preserved, entries relative to the directory), uploads via POST /v1/agents with a 2-minute request timeout, prints the accepted build id, then polls the agent's state every 2 seconds for up to 5 minutes, printing each transition.
Example:
platformctl deploy examples/agents/research-buddy --name research-buddy
You should see:
packaging examples/agents/research-buddy...
uploading research-buddy (4.2 KiB, framework=adk)...
build b-1a2b3c accepted
state: -> building
state: building -> deploying
state: deploying -> ready
research-buddy is ready at https://research-buddy-xxxx.apps.codyhill.dev
If the build or deploy fails:
research-buddy failed to build/deploy (see `platformctl logs research-buddy`)
If it does not become ready in time:
timed out after 5m0s waiting for research-buddy (last state: building)
Scripting a deploy
deploy is the one command whose stdout is not clean machine output. The progress narration above — packaging ..., uploading ..., build ... accepted, and every state: ... -> ... line — is printed to stdout, ahead of the result. Only the final result honors -o. So the obvious pipeline fails:
platformctl deploy ./my-agent -o json | jq -r .url
You should see:
jq: parse error: Invalid numeric literal at line 1, column 10
jq chokes on packaging ./my-agent..., the first line. -o json also pretty-prints the result across several lines, so "take the last line" does not rescue it either.
Use the legacy --json flag for scripting instead. It prints the result as one compact line, which survives the narration because you can simply take the last line:
URL=$(platformctl deploy ./my-agent --json | tail -n 1 | jq -r .public_url)
echo "$URL"
You should see:
https://my-agent-xxxxxx.apps.codyhill.dev
The --json object is {"name","state":"ready","ready":true,"image","url","public_url"}. state is the word for where the deploy got to — building, deploying, ready, or failed — and ready is the plain boolean beside it, the same field spelled the same way on every resource the platform returns, so a script branches on ready and shows state. public_url is the address a person or an outside caller uses. url is the platform-internal address, callable only from workloads running on the platform — that is the one a Pub/Sub push subscription needs. The same applies to platformctl functions deploy, which shares this code path.
--json is hidden from --help, so you will not find it by reading the CLI's own usage text. It still works on every command, and for deploy it is the flag you want.
Other commands send their asides to stderr — the pubsub pull --ack confirmation does, for one. The deploy narration does not, so redirecting stderr will not clean it up. Take the last line, as above.
There is no ignore mechanism. A stray .venv/ or node_modules/ ships with your code and can blow the 100 MiB upload cap. Deploy from a clean directory.
Notes:
- Upload cap: 100 MiB by default (the server's
MAX_UPLOAD_BYTESsetting), plus 1 MiB slack for form fields. - An invalid name returns
400with:missing or invalid 'name' (must be a lowercase DNS label). (Commands that address an existing agent by name —status,invoke,logs,delete— returninvalid agent name (must be a lowercase DNS label)instead.) - Auto-detection can surprise you: a stray
crew.pyin an ADK agent directory deploys it as CrewAI. Pass--frameworkto be explicit. - The
202response from upload carries no state — the state comes only from the polling that follows.
See also: deploying agents and the agent quickstart.
platformctl functions deploy
Deploys a one-file HTTP function. A function is a directory with a single handler file that exposes one function the platform calls for each request.
platformctl functions deploy <dir> [--name <name>] [--runtime python]
| Flag | Default | What it does |
|---|---|---|
--name | The directory's base name | The function's name. Same lowercase-DNS-label rule as agents. |
--runtime | auto-detected, else python | One of python, nodejs, go, ruby. When the flag is absent the CLI reads your directory's handler file: handler.js means nodejs, handler.go means go, handler.rb means ruby, and anything else means python. |
Functions ride the same upload path as agents — POST /v1/agents — with the same polling, output, and 100 MiB cap as deploy. The CLI sends framework=function and the language in a separate runtime form field, which is what the API expects. The hyphenated tokens function-nodejs, function-go, and function-ruby are internal names the server derives for itself; sending one as framework is a 400.
An unrecognized --runtime value reaches the server's runtime check:
unsupported 'runtime': one of "python", "nodejs", "go", "ruby"
An invalid framework reports itself separately:
unsupported 'framework': one of "adk", "langgraph", "crewai", "function", "container"
Example:
mkdir hello-http
cat > hello-http/handler.py <<'EOF'
def handle(event):
return {"echo": event.get("message", "")}
EOF
platformctl functions deploy ./hello-http --name hello-http
You should see the same build ... accepted and state: transitions as an agent deploy, ending with:
hello-http is ready at https://hello-http-xxxx.apps.codyhill.dev
Notes:
- The progress narration goes to stdout here too, so scripting a deploy applies unchanged to functions.
- Agents and functions share one flat name space per project — a function cannot reuse an agent's name.
- Redeploying means running
functions deployagain, orplatformctl agents redeploy <name>to rebuild from the source the platform already stored. A function rebuilds with its own runtime template, not the agent one: the platform records what a name was created as and recovers it on redeploy. - Everything in the agents group addresses a function by name too. It is the same route family underneath, so
agents config set,agents env set,agents revisions,agents set-traffic, andagents filesall work on a function exactly as they do on an agent.
See also: the function quickstart and runtimes.
platformctl list
Lists every agent and function in the project. The CLI follows pagination to the end, so you always see everything, not just the first 50.
platformctl list
Example:
platformctl list
You should see:
NAME STATE URL
research-buddy ready https://research-buddy-xxxxxx.apps.codyhill.dev
The URL column shows the address you can actually call. An agent that has not been published has none, and the column reads private rather than an address that would fail.
If nothing is deployed: no agents deployed. With -o json or -o yaml you get the full agent objects, including the platform-internal url alongside public_url.
platformctl status
Shows one agent's or function's full state.
platformctl status <agent>
The default output is a generic field/value table. Use -o json for the same object as JSON:
platformctl status research-buddy -o json
You should see a JSON object with name, framework, state, ready, url, image, message, created_at, and updated_at, plus public_url and external_url once the agent has an address of each kind, and kind (agent or function) and runtime (a function's language) when the API reports them.
The state values are building, deploying, ready, and failed — that is the word to read — and ready is the boolean beside it, true only while the state is ready. Two fields, because they answer different questions: state says which stage the agent is in, ready says whether you can call it, and ready is spelled and typed identically on every resource the platform returns, so one script can ask it of all of them. When the state is failed, the message field carries the last error. The one field the CLI does not decode is latest_revision — read it from platformctl agents revisions, GET /v1/agents/{name}, or the console.
platformctl invoke
Sends one message to a deployed agent or function and prints the reply. This is the one command that works without a credential (the platform's data plane is open by default; an administrator can close it with the server-side INVOKE_AUTH_REQUIRED=true setting).
platformctl invoke <agent> <message> [--session <id>] [--memorize]
| Flag | Default | What it does |
|---|---|---|
--session | none — the server starts a new session | Continue an existing conversation. A session is one ongoing conversation, identified by a session_id; the platform stores its history so follow-up questions have context. |
--memorize | off | Also save this exchange to the agent's long-term memory immediately. |
The client waits up to 5 minutes — generous on purpose, because an idle agent has to cold-start.
Example:
platformctl invoke research-buddy "My boat is a Mastercraft Maristar 245. Compute 2**32 in python."
You should see:
4294967296 ...
(session: 9f2c...)
tool_call: run_python ...
The answer comes first, then the session id, then one tool_call: <name> <summary> line per tool the agent used. Continue the conversation by passing the session id back:
platformctl invoke research-buddy "What boat do I have?" --session 9f2c...
With the legacy --json flag, the machine-readable shape is {"session_id","response","tool_calls":[names]}.
Functions are invoked the same way — platformctl invoke hello-http "ping" — because functions share the invoke path.
platformctl invoke waits for the complete answer. Live token-by-token streaming (POST /v1/agents/<name>/invoke/stream, NDJSON) is available through the API and the example Python client, not the Go CLI. See invoking agents.
platformctl memorize
Copies a session's conversation into the agent's long-term memory, so future new sessions can recall it.
platformctl memorize <agent> --session <id>
| Flag | Default | What it does |
|---|---|---|
--session | none — required | The session to save. Missing it fails with: --session is required. |
Unlike invoke, memorize always requires a credential — it writes to the agent's memory.
Example:
platformctl memorize research-buddy --session 9f2c...
You should see the server's own message if it returns one, otherwise:
memorized session 9f2c... for research-buddy
See also: agent memory and sessions.
platformctl secrets set
Adds or updates secrets on an agent or function. Secrets become environment variables in the workload, and each change rolls a new revision.
platformctl secrets set <agent> KEY=VALUE [KEY=VALUE...]
Example:
platformctl secrets set research-buddy DEMO_TOKEN=abc123
You should see:
set 1 secret(s) for research-buddy
A malformed pair fails with:
invalid KEY=VALUE pair: "DEMO_TOKEN"
Notes:
secrets setmerges. It callsPATCH /v1/agents/<name>/secretswith only the keys you name and never touches keys you did not name — so it can never wipe the platform-managedMODEL_API_KEY.- An empty value clears the key.
platformctl secrets set research-buddy DEMO_TOKEN=removesDEMO_TOKENrather than storing an empty string. The server converts a blank value into a removal on purpose: a stored empty key would makeagents secrets getreport the secret as configured while the workload authenticated with nothing, and an emptyMODEL_API_KEYspecifically would shadow the platform default. Blank means "unset", not "set to nothing". - Reading back is by key name only.
platformctl agents secrets get <name>lists the keys; nothing — not this CLI, not the API behind it — returns an agent secret's value. See agents secrets get. - Your value reaches the platform through your shell's argument list, so it lands in shell history. For anything you would rather not leave there, keep the value in the project secret store instead, whose write commands read from a file or stdin for exactly that reason.
platformctl secrets set <agent> KEY=VALUE writes an agent environment secret: one value, attached to one agent, no version history.
Every other secrets subcommand — list, show, put, versions, reveal, delete, issue-token — operates on the project secret store: versioned values owned by the project that many agents can bind. Those are documented on MCP servers and secrets.
See also: secrets and environment variables.
platformctl logs
Reads an agent's or function's logs.
platformctl logs <agent> [-f|--follow] [--history]
| Flag | Default | What it does |
|---|---|---|
-f, --follow | off | Tail the running instance's log stream. Runs with no timeout — stop it with Ctrl-C. |
--history | off | Print persisted log lines that survive scale-to-zero and revision rollouts. Always the newest 500 lines — see the limitation below. |
The two flags are mutually exclusive:
--follow and --history are mutually exclusive: --history reads persisted logs, --follow tails a running instance
Example:
platformctl logs research-buddy --history
You should see one line per persisted entry, formatted as <ts> <stream> <message>, or:
no persisted logs
An agent that has scaled to zero has nothing for plain logs or --follow to read. Use --history to see what it logged before it went idle.
Log history is capped at 500 lines
--history always returns the newest 500 lines, and nothing lets you change that from the CLI. The CLI calls GET /v1/agents/{name}/logs/history with no query string, so it always gets the server's default window: the 500 most recent persisted lines, with no time filter. There is no --limit flag and no --since flag.
That matters when you are chasing "why did it fail an hour ago?" on a chatty agent. The CLI prints 500 lines and stops. It does not tell you that older lines exist, and there is no way from the CLI to widen the window or aim it at the hour you care about.
The endpoint itself accepts both knobs, so use curl when you need them. Set CAI_API to https://api.codyhill.dev and CAI_TOKEN to an API key or session token (see API authentication):
# The 5000 newest lines, instead of 500
curl -s "$CAI_API/v1/agents/research-buddy/logs/history?limit=5000" \
-H "Authorization: Bearer $CAI_TOKEN"
# Only lines written since a moment in time (RFC 3339, UTC)
curl -s "$CAI_API/v1/agents/research-buddy/logs/history?since=2026-08-12T14:00:00Z" \
-H "Authorization: Bearer $CAI_TOKEN"
You should see a JSON object whose lines array holds one entry per log line, oldest first:
{
"agent": "research-buddy",
"lines": [
{
"ts": "2026-08-12T14:01:03.117Z",
"revision": "research-buddy-00003",
"instance": "research-buddy-00003-deployment-6b7f9-xk2lp",
"stream": "stdout",
"message": "harness started, agent loaded"
}
],
"count": 1,
"note": "persisted history - survives scale-to-zero and revision rollouts"
}
Both parameters can be combined. An unparseable since is ignored rather than rejected, so double-check the format if the window looks wrong — it must be RFC 3339, like 2026-08-12T14:00:00Z.
See also: logs and the limits reference.
platformctl delete
Deletes an agent or function.
platformctl delete <agent>
Example:
platformctl delete research-buddy
You should see:
deleted research-buddy
platformctl demo
Runs a narrated end-to-end walkthrough of the golden path. It takes no flags.
platformctl demo
It runs five narrated steps: deploy the example agent if needed, invoke it in a new session, ask a follow-up in the same session, memorize the session, then recall the memory in a fresh session.
demo must be run from inside the platform repository — it looks for examples/agents/research-buddy in the current directory or a parent (walking up to 8 levels). Otherwise it fails with:
could not find examples/agents/research-buddy under the current directory or any parent (run `platformctl demo` from inside crusoe-ai-platform)
The agents group
platformctl agents carries the rest of an agent's lifecycle: rebuilding from stored source, pinning traffic to a revision, the compute envelope, environment variables, the live spec, runtime signals, which secret keys are set, the stored source files, the conversations the agent has held, and the embeddable chat widget.
Every subcommand takes the agent's name and honors the same global flags as the top-level verbs. All of them address functions too, by the same name.
platformctl agents redeploy
Rebuilds an agent from the source the platform already holds. No tarball, no local directory — this is the deploy you run after agents files put, after a browser edit, or after a build that failed for a transient reason.
platformctl agents redeploy <name>
Example:
platformctl agents redeploy research-buddy
You should see:
agent research-buddy
build_id b-4d5e6f
files 7
note rebuilding from the stored source
The build runs asynchronously. The build_id is the handle; platformctl status research-buddy is where it finishes.
An agent whose source the platform never received cannot be rebuilt:
no source is stored for this agent - deploy it once from the CLI or upload files first
platformctl agents revisions
Lists an agent's immutable revisions, newest first, with the share of traffic each one serves.
platformctl agents revisions <name>
Every deploy, redeploy, config change, and secret change produces a revision. The TRAFFIC column is read from live routing, so this is also the answer to "did my set-traffic take effect yet?".
platformctl agents revisions research-buddy
You should see:
NAME GENERATION TRAFFIC READY REPLICAS
research-buddy-00003 3 100% yes 1
research-buddy-00002 2 0% yes 0
research-buddy-00001 1 0% yes -
READY answers one question — is this revision serving right now? — and answers it yes or no. It is worth saying what it is not any more: a revision's ready used to be the platform's own condition word, the string True, False, or Unknown, while ready everywhere else was a boolean. Same name, opposite failure mode, because the string "False" is a non-empty value that a script reads as true. It is a boolean now, like every other ready on the platform.
The third answer did not disappear, it moved: -o json carries state beside it, reading ready, not_ready, or unknown. unknown means nothing has reported on that revision yet; not_ready is a verdict the platform actually reached, and its reason and message say what it was. A revision serving no traffic with no replicas is the normal resting state on a scale-to-zero platform, not a fault.
If nothing has been deployed: no revisions - this agent has not been deployed yet.
-o json and -o yaml carry more per revision than the table shows: the image, state, the failure reason and message, the creation time, and the revision's own URL and tag.
platformctl agents set-traffic
Pins the share of traffic each revision serves. This is the rollback control.
platformctl agents set-traffic <name> <revision>=<percent> [<revision>=<percent>...]
The targets are positional arguments, not flags, and the percentages must sum to 100. Revision names come from platformctl agents revisions. A trailing % is accepted, so =100 and =100% both work.
# pin a rollback: send everything to the previous revision
platformctl agents set-traffic research-buddy research-buddy-00002=100
# canary: 10% of traffic to a named revision, the rest to whatever is newest
platformctl agents set-traffic research-buddy latest=90 research-buddy-00006=10
# back to following new deploys
platformctl agents set-traffic research-buddy latest=100
You should see the split that was accepted:
REVISION PERCENT
research-buddy-00002 100%
latest is not a revision name. It is the "newest revision" target — a split that keeps following new deploys, which is the state a freshly deployed agent is in. Naming a revision pins it there instead. (@latest is accepted as well; it is the spelling this command used to require, kept working for existing scripts.)
A malformed target fails before anything is sent:
invalid traffic target "research-buddy-00002": write REVISION=PERCENT, e.g. research-buddy-00002=100, or latest=100 for the newest revision
Pass the complete intended state every time. Sending one target sets the whole split to that one target — there is no "and leave the others where they were", because a merge would leave the remaining percentages undefined.
Two things worth knowing before you rely on this in an incident:
- It moves traffic, not source. Pinning an older revision is a valid way to mitigate a bad rollout, but the stored source is unchanged, so the next
deployoragents redeployrebuilds forward and takes the traffic back. - It is not instant. The platform reprograms routing within a few seconds.
platformctl agents revisions <name>is where the live answer shows up — the command's own output is the split that was accepted, not proof it is serving yet.
See also: traffic and revisions.
platformctl agents config
Reads and sets the per-agent compute envelope: scaling bounds, per-instance concurrency, container resources, and the request timeout.
platformctl agents config get <name>
platformctl agents config set <name> [flags]
| Flag | What it does |
|---|---|
--min-scale | Minimum running instances; 0 lets the agent scale to zero when idle |
--max-scale | Maximum running instances; 0 means unbounded |
--concurrency | Concurrent requests one instance handles; 0 means the platform default |
--timeout | Per-request time budget, in seconds |
--cpu | CPU request per instance, e.g. 500m (empty removes it) |
--memory | Memory request per instance, e.g. 1Gi (empty removes it) |
--cpu-limit | CPU limit per instance, e.g. 1 (empty removes it) |
--memory-limit | Memory limit per instance, e.g. 2Gi (empty removes it) |
Only the flags you pass are sent, so setting scaling does not reset resources and vice versa. Passing no flag at all is an error rather than a no-op:
nothing to set: pass at least one of --min-scale, --max-scale, --concurrency, --timeout, --cpu, --memory, --cpu-limit, --memory-limit
platformctl agents config set research-buddy --min-scale 1 --max-scale 10
platformctl agents config set research-buddy --cpu 500m --memory 1Gi
platformctl agents config set research-buddy --timeout 600 --concurrency 8
config get prints only what is actually overridden, so an unset value reads as absent rather than as a zero the agent is running with:
min_scale 1
max_scale 10
container_concurrency 8
timeout_seconds 600
requests.cpu 500m
requests.memory 1Gi
An agent with nothing set runs on the platform defaults, and says so:
no compute overrides - this agent runs on the platform defaults
Notes:
--max-scale 0and--concurrency 0are real settings, not "unset" —0maxima mean unbounded,0concurrency means the platform default.config getlabels them:max_scale 0 (unbounded).- A resource flag reads before it writes. The resource envelope is one object on the server and a write replaces the whole block, so when any resource flag is passed the CLI fetches the current envelope first and changes only the entries you named. That is what keeps
--cpu 500mfrom silently dropping a memory request set earlier. It costs one extra request, and only when a resource flag is used. - Passing an empty value removes one entry:
--memory ""stops asking for a memory floor. - The change arrives as a new revision. Scaling, resources, and timeout are resolved per revision, so a revision already running cannot be altered underneath it.
- The configuration is stored durably and re-applied by every later rebuild, so it survives a redeploy rather than reverting.
platformctl agents env
Reads and sets the agent's plain, non-secret environment variables.
platformctl agents env get <name>
platformctl agents env set <name> KEY=VALUE [KEY=VALUE...] [--remove KEY]
| Flag | What it does |
|---|---|
--remove | Environment variable to unset. Repeatable. |
platformctl agents env set research-buddy LOG_LEVEL=debug REGION=us-east
platformctl agents env set research-buddy --remove LOG_LEVEL
env get prints the variables as key/value rows:
LOG_LEVEL debug
REGION us-east
With nothing set: no environment variables.
Notes:
- These values are readable back. That is the whole difference from secrets, and it is exactly why anything credential-shaped belongs in
platformctl secrets setinstead. - The update is a merge: variables you do not name keep their current values, so there is no need to resupply the whole environment to change one entry.
- Setting or removing one rolls a new revision, because environment is resolved per revision.
- Passing neither a pair nor a
--removefails with:nothing to do: pass KEY=VALUE pairs to set, --remove KEY to unset, or both.
platformctl agents secrets get
Lists the secret key names an agent runs with. Never the values.
platformctl agents secrets get <name>
platformctl agents secrets get research-buddy
You should see one key per line:
DEMO_TOKEN
MODEL_API_KEY
With none set: no secrets configured.
This answers "is MODEL_API_KEY set?" without anything being able to read what it is set to — values are not returned by this command, and not by the API behind it either. There is deliberately no agents secrets set: writing a secret is platformctl secrets set <agent> KEY=VALUE, on the service that holds the value.
platformctl agents spec
Prints the agent's live service object as YAML — the full spec, exactly as the platform holds it. This is where "what is actually deployed?" is answerable when the summary views disagree with reality.
platformctl agents spec <name>
The default output is the YAML document itself, so it redirects straight to a file. -o json and -o yaml carry the envelope instead (the agent name, the document, and the note about sanitizing).
The spec is sanitized before it leaves the platform: internal bookkeeping fields are stripped, and any environment value whose name looks like a credential is redacted with its key kept, rendered as ***REDACTED***. It never contains a secret value.
platformctl agents metrics
Shows what the platform can honestly say about an agent right now.
platformctl agents metrics <name>
platformctl agents metrics research-buddy
You should see a readiness block, a per-revision table, and the platform's own note:
ready true
ready_since 2026-08-12T14:01:07Z
instances 1
latest_ready_revision research-buddy-00003
target_concurrency platform default
REVISION REPLICAS LATEST_READY LATEST_CREATED
research-buddy-00003 1 yes yes
These are live counts read from the platform right now: running instances, per-revision replicas, and the current revision's readiness. Historical charts - request rate, latency percentiles, instances over time - aren't available yet.
That last paragraph is not a caveat this page added. It is the server's own response text, printed with the numbers, because request rate, error rate, and latency percentiles need a metrics backend the platform does not run yet. ready is a plain true or false here, the same field every resource carries; when it is false, the block also carries state — not_ready when the platform reached that verdict, unknown when nothing has reported on the agent yet — with the reason and message explaining it.
platformctl agents users list
Lists the end users an agent has held conversations with, most recently active first.
platformctl agents users list <name>
platformctl agents users list research-buddy
You should see:
USER_ID LAST_SEEN SESSIONS
alice@example.com 2026-08-12T13:58:02Z 4
anon-7f21c9 2026-08-11T09:12:44Z 1
With none: no users have talked to this agent yet.
This is the entry point to the sessions browser — sessions are indexed per user, so agents sessions list needs a user id, and this is the only place to get one.
A user id is whatever the caller passed when it invoked the agent: an application's own account id, an email, an anonymous handle. The platform does not mint it and cannot tell you who it belongs to. The SESSIONS count is how many of that user's conversations the platform still holds — transcripts expire, so a user who talked to the agent long ago can be listed with none left.
platformctl agents sessions
Browses and deletes the conversations an agent has held.
platformctl agents sessions list <name> --user <user-id>
platformctl agents sessions get <name> <session-id>
platformctl agents sessions delete <name> <session-id>
--user is required on list. There is no "every session" view, by design — it is the same boundary that keeps one end user's history out of another's. Pair the two commands:
platformctl agents users list research-buddy
platformctl agents sessions list research-buddy --user alice@example.com
You should see:
SESSION_ID LAST_UPDATE EVENTS PREVIEW
9f2c4a71 2026-08-12T13:58:02Z 6 My boat is a Mastercraft Maristar 245. Compute 2**…
5b18d0e3 2026-08-11T17:22:10Z 2 What boat do I have?
With none: no sessions for this user. The CLI follows pagination to the end, so you see every session that user has, not just the first page.
sessions get returns the full turn-by-turn transcript. Its events are a nested array of the runtime's own event objects, so the default table can only summarize the top-level fields — read it with -o json or -o yaml to see the turns themselves.
sessions delete removes the transcript and the session's place in the owning user's index, and prints:
deleted session 9f2c4a71 from agent research-buddy
The owning user is resolved from the session itself, so you do not name it. The platform keeps no copy, which makes this the erasure path for a "delete my data" request.
A transcript carries whatever the end user typed and whatever the agent's tools were called with. Treat sessions get output — and anywhere you pipe it — accordingly.
See also: sessions.
platformctl agents files
Reads and edits the source the platform builds an agent from — the same files the console's editor shows.
platformctl agents files list <name>
platformctl agents files get <name> <path>
platformctl agents files put <name> <path> --from <localfile>
platformctl agents files delete <name> <path>
platformctl agents files list research-buddy
You should see:
PATH SIZE UPDATED
agent.py 2.1 KiB 2026-08-12T13:40:11Z
requirements.txt 48 B 2026-08-12T13:40:11Z
tools/search.py 1.4 KiB 2026-08-12T13:40:11Z
With none: no files stored for this agent.
files get prints the file itself, unadorned, so it redirects straight to disk. -o json and -o yaml carry the API's object instead (path, content, updated_at):
platformctl agents files get research-buddy agent.py > agent.py
files put writes one file, creating or replacing it. The content comes from --from, which names a local file or - to read stdin:
platformctl agents files put research-buddy agent.py --from ./agent.py
sed 's/gpt-4/gpt-4o/' agent.py | platformctl agents files put research-buddy agent.py --from -
--from is required — there is no --content flag to paste source into.
Three rules the CLI enforces before anything is sent:
- The path must be relative and clean.
agent.pyandtools/search.pyare fine; a leading/, a..segment, a backslash, or a path over 512 characters is refused withinvalid file path "/agent.py": it must be relative and in clean form, e.g. agent.py or tools/search.py. This is checked client-side on purpose: a non-clean path never reaches the handler, because the request path gets cleaned and answered with a redirect that downgrades the write to a read — so without the check, aputwould print a file object as though the write had landed, and the next redeploy would quietly build the old source. - The content must be valid UTF-8. The store is text: source crosses the wire inside a JSON string, and every invalid byte would be silently substituted, stored mangled, and handed back by the next
files get. So a binary file is refused up front with./logo.png is not valid UTF-8 text: the agent file API carries source as JSON text, so binary files cannot be stored(the name isstdinwhen the content came from--from -). An image or a compiled artifact belongs in the agent's own dependencies, not here. - A write is saved, not live.
files putreturnsnote saved - redeploy the agent for this to take effect. Runplatformctl agents redeploy <name>once the edits are in place.
The platform stores the latest version only — no history, no rollback. Anything worth keeping belongs in your own version control. files delete is immediate and prints deleted tools/search.py from agent research-buddy.
See also: files and the editor.
platformctl agents embed
Manages the chat widget an agent can be embedded as on someone else's website.
platformctl agents embed get <name>
platformctl agents embed enable <name> [flags]
platformctl agents embed disable <name>
platformctl agents embed rotate-key <name>
enable flag | What it does |
|---|---|
--allow-origin | Origin allowed to embed the widget, e.g. https://example.com. Repeatable. Empty means any origin. |
--title | Heading shown at the top of the chat window |
--subtitle | Line under the heading |
--greeting | First message the widget shows before the visitor types |
--launcher-text | Label on the button that opens the widget |
--accent-color | Accent colour as a 6-digit hex value, e.g. #f9743a |
--position | Corner the widget sits in: right (default) or left |
platformctl agents embed enable research-buddy \
--title "Research Buddy" \
--allow-origin https://example.com \
--allow-origin https://www.example.com
embed get prints the configuration, including the key:
accent_color #f9743a
allowed_origins https://example.com, https://www.example.com
embed_key emb_7c1f9a2b3d4e5f60
enabled true
greeting Ask me anything.
launcher_text Chat
position right
subtitle -
title Research Buddy
updated_at 2026-08-12T14:07:11Z
An agent that has never been set up reports the off default rather than an error.
Enabling a widget mints a public embed key. It is an identifier, not a secret — it sits in the HTML of every page the widget is pasted into, and embed get will show it again. What actually guards the widget is --allow-origin, so set it before you hand the snippet out. An empty allowlist means any origin, which is fine for a demo and wrong in production. Passing a single empty value (--allow-origin="") is how you go back to that from a configured list.
Notes on the three writes:
enablemerges. The API replaces the whole configuration on write, so this command reads the current one first and changes only the flags you actually passed.embed enablewith no flags turns the widget on without blanking a title or greeting set from the console.disableremoves everything, key included. It does not merely flip a switch. Any snippet already pasted onto a page stops working immediately, and enabling the widget again mints a new key, so those snippets have to be updated. To change the appearance without losing the key, runenableinstead.rotate-keyshows the new key once, in its own line. Every snippet already on a page carries the old key and stops working the moment the command returns:
new embed key: emb_a91c4f7e28b60d35
update the widget snippet on every page that embeds research-buddy - the previous key no longer works
rotate-key needs a widget to already exist; otherwise: no embed widget is configured for this agent yet.
Working with sessions
Sessions are created through invoke and browsed through agents sessions:
- Invoke without
--sessionand the server mints a new session id, printed as(session: <id>). - Pass that id back with
--sessionto continue the conversation. - Use
memorize(orinvoke --memorize) to promote a session into long-term memory. - Use
agents users listto find a user id, thenagents sessions list --userto see that user's conversations,sessions getto read one, andsessions deleteto erase one.
There is no top-level platformctl sessions group — the browsing commands hang off agents, and listing always starts from a user id. See sessions for how session storage works.
Console/API only
Two agent capabilities still have no CLI command:
| Capability | Where to do it |
|---|---|
| Streaming invoke (NDJSON) | API — see invoking agents |
| Bind a project secret to an agent's environment variable | Console, or the API — see secrets and environment variables |
One more thing is a console convenience rather than a missing capability: the console builds the ready-to-paste widget <script> snippet for you, from its own origin. platformctl agents embed get gives you the embed key and configuration the snippet needs, but not the snippet markup itself.
For the underlying endpoints, see the agents API reference.
Related pages
- Agents overview — what an agent is and what the platform wires in for you.
- Deploy an agent — the directory layout and the state polling behind
agents deploy. - Invoke an agent — request and response shapes, including streaming.
- Functions overview — the one-file handler model behind
functions deploy. - Agents API reference — every route these commands call.
- CLI overview — endpoint resolution, credentials, and output formats.