Skip to main content

Invoke an agent

This page shows you how to talk to a deployed agent: the invoke request and response shapes, streaming, calling the agent's public URL, and what to expect from cold starts.

Before you begin

  • You have a deployed agent in ready state. If not, start with deploy an agent.
  • For curl examples you need the API endpoint in $CAI_API. The platform is in alpha and has no public API hostname yet — ask your administrator for the endpoint, or port-forward:
kubectl -n cai-system port-forward svc/agent-engine-api 8080:8080 &
export CAI_API=http://localhost:8080
Invoking is open by default

Talking to an agent needs no credential by default — the data plane is open (your administrator can close it by setting INVOKE_AUTH_REQUIRED=true on the platform). Managing an agent (deploy, logs, secrets, delete) always requires signing in.

The invoke endpoint

POST /v1/agents/{name}/invoke — send one message, get one reply.

  • Auth: none required by default. Optional Authorization: Bearer token.
  • Request body: JSON, at most 1 MiB.
  • Add ?project=<slug> if the same agent name exists in more than one project.

Request fields

FieldTypeRequiredDefaultNotes
messagestringyesThe user's message. Blank or missing returns 400 message is required.
session_idstringnoplatform-minted UUIDReuse it to continue a conversation. Anonymous callers who pick their own must use at least 24 characters.
user_idstringnoderived from the session idMust match ^[A-Za-z0-9_.:@-]{1,128}$. Omit it and the platform derives a stable one.
memorizebooleannofalseAlso commit this session to the memory bank. Requires authentication.

Response fields

FieldTypeNotes
session_idstringAlways filled — save it to continue the conversation.
user_idstringAlways filled — the caller identity the session belongs to.
outputstringThe agent's answer.
reasoningstringThe agent's thinking text, when the model produced any.
tool_callsarrayOne entry per tool the agent used: {"name": "run_python", "summary": "called with args={...}"}.
eventsarrayThe raw framework events for the turn (verbose; useful for debugging).

Invoke with platformctl

platformctl invoke my-agent "My boat is a Mastercraft Maristar 245. Compute 2**32 in python."

You should see:

2**32 is 4294967296.
(session: 3f2c8a1e-9d41-4c1b-a2f7-0b1c2d3e4f5a)
tool_call: run_python called with args={'code': 'print(2**32)'}

Continue the same conversation by passing the session id back:

platformctl invoke my-agent "What boat do I have?" --session 3f2c8a1e-9d41-4c1b-a2f7-0b1c2d3e4f5a

Flags: --session <id> reuses a conversation, --memorize also writes it to the memory bank (needs platformctl login first). There is no --user flag — the platform derives the user id from the session. With -o json the CLI prints {"session_id", "response", "tool_calls"}. The CLI waits up to 5 minutes for a reply.

Invoke with curl

curl -s -X POST "$CAI_API/v1/agents/my-agent/invoke" \
-H 'content-type: application/json' \
-d '{"message":"Compute 2**32 in python."}'

You should see:

{
"session_id": "3f2c8a1e-9d41-4c1b-a2f7-0b1c2d3e4f5a",
"user_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"output": "2**32 is 4294967296.",
"reasoning": "",
"tool_calls": [
{"name": "run_python", "summary": "called with args={'code': 'print(2**32)'}"}
],
"events": ["..."]
}

Session id rules for anonymous callers

If you omit session_id, the platform mints a UUID and returns it — this is the easy path. If an unauthenticated caller supplies their own session_id, it must be at least 24 characters long, or the call fails with 400:

session_id chosen by an unauthenticated caller must be at least 24 characters of unguessable randomness (or omit it and the platform will generate one). A short, guessable id would let anyone else read this conversation.

Authenticated callers are exempt. If you omit user_id, the platform derives the same one from the session id every time, so multi-turn conversations just work. See sessions for the full story.

The memorize flag

"memorize": true asks the platform to also commit the session to the agent's long-term memory bank. Anonymous callers get 401:

memorize requires authentication: it writes durable memory that later callers read back. Invoke without 'memorize', then call POST /v1/agents/{name}/sessions/{id}/memorize with a session token.

See long-term memory.

Streaming

POST /v1/agents/{name}/invoke/stream takes the exact same request body and validation rules, but responds with application/x-ndjson: one JSON object per line, in turn order.

curl -sN -X POST "$CAI_API/v1/agents/my-agent/invoke/stream" \
-H 'content-type: application/json' \
-d '{"message":"Compute 2**32 in python."}'

You should see (one object per line):

{"type":"thinking", "seq":1, "text":"<delta>"}
{"type":"output", "seq":2, "text":"<delta>"}
{"type":"block_end", "seq":3, "kind":"output"}
{"type":"tool_call", "name":"run_python", "args":{"code":"print(2**32)"}}
{"type":"tool_result","name":"run_python", "result":"4294967296\n"}
{"type":"done", "session_id":"...", "user_id":"..."}

Line types: thinking (reasoning delta), output (answer delta), block_end (a thinking or output block finished), tool_call, tool_result, done (carries the final session_id and user_id), and error with a message field if the turn fails.

Two extras worth knowing:

  • Minted ids are also returned as response headers X-CAI-Session-Id and X-CAI-User-Id, so you can capture them before the stream ends.
  • You get token-level deltas when the model supports streaming; otherwise the first "delta" is the whole text.

Invoke via the agent's public URL

Agents are private by default: the deployed service is cluster-local, and the control-plane routes above are the only way to reach it. GET /v1/agents/{name} still returns public_url — the address the agent would have, https://<name>-<project-short>.apps.codyhill.dev — but nothing serves that host until you publish the agent by setting CAI_EXPOSE_EXTERNAL=true on its env:

curl -s -X PATCH "$CAI_API/v1/agents/my-agent/env" \
-H "Authorization: Bearer $CAI_TOKEN" -H 'content-type: application/json' \
-d '{"set": {"CAI_EXPOSE_EXTERNAL": "true"}}'

This rolls a new revision. Once the address is live over a valid certificate, external_url is filled in and the agent serves POST /invoke and POST /invoke/stream on that host with the same body:

export AGENT_URL="$(curl -s -H "Authorization: Bearer $CAI_TOKEN" "$CAI_API/v1/agents/my-agent" | jq -r .public_url)"
curl -s -X POST "$AGENT_URL/invoke" \
-H 'content-type: application/json' \
-d '{"message":"Hello!"}'

Use the public URL for plain invocation — for example from a webhook or a frontend. For anything that touches session management or memory (browsing transcripts, deleting sessions, memorize), go through the control-plane routes under $CAI_API/v1/agents/{name}/... instead: those surfaces on the agent's own URL are locked to the platform's internal credential and will refuse you.

The agent also serves two open helper endpoints on its public URL: GET /healthz (returns {"status": "ok"}) and GET /debug/config (non-secret config: the resolved model, framework, and whether a model key is present — handy for troubleshooting).

Cold starts

Idle agents scale to zero — they run no machines at all until the next request arrives, and you pay nothing to keep them around. The first invoke after an idle period triggers a cold start: the platform schedules a pod, starts the harness, and imports your code before answering. Expect that first reply to take noticeably longer than warm requests. The whole invoke, cold start included, must finish inside the invoke timeout (60 seconds by default), so a slow-importing agent can time out on its first call and succeed on the second. See autoscaling and scale-to-zero.

Limits

LimitValue
Request body1 MiB
Response body (buffered by the control plane)32 MiB — larger replies fail with 502
Invoke timeout60 s default (platform-configurable via INVOKE_TIMEOUT_SECONDS)
Anonymous self-chosen session_idat least 24 characters
session_id charsetNot enforced on invoke, but session ids used with the memorize and session-browsing routes must match ^[A-Za-z0-9_.-]{1,128}$ (a path id outside that returns 400 invalid session id) — so stay inside it
user_id charset^[A-Za-z0-9_.:@-]{1,128}$

Status codes

All errors use the envelope {"error": "<message>", "request_id": "<id>"}.

CodeMeaningExample message
400Bad requestmessage is required
401Auth neededthe memorize message above, or when the platform runs with INVOKE_AUTH_REQUIRED=true
404No such agent (or not visible to you)unknown agent: my-agent
409Ambiguous namethe agent name my-agent exists in more than one project; add ?project=<slug> to say which one, or sign in so it resolves within your project
502The agent failed or timed outinvoke agent my-agent: ...

Next steps