# poema cloud: getting started for agents
poema cloud runs agents in isolated instances (the API calls one a Task), with a closed network and model keys the agent never sees.
An instance can be suspended (memory and files kept, no compute used), resumed where it stopped, and forked into copies to try several paths.
You drive it over HTTP, the Python SDK, the CLI or MCP with an API key. Site: https://console.poema.dev . API: https://127.0.0.1:7070 .
## What you cannot do alone
A human must create your key. You cannot sign up.
1. The human opens https://console.poema.dev/ and signs in with GitHub or Google.
2. A new account starts on a waitlist and is approved by hand. Nothing tells them when: they open https://console.poema.dev/dashboard now and then, which shows the dashboard once they are in.
3. On https://console.poema.dev/dashboard/keys they choose "Create key" and copy it (shown once). It looks like `poema_ns_<namespace>_<random>`.
4. They give it to you as the environment variable POEMA_API_KEY.
5. To use a model in an instance, they save a provider key on https://console.poema.dev/dashboard/secrets (Anthropic, OpenAI, Google or a custom OpenAI-compatible endpoint).
## Auth
Send `X-API-Key: $POEMA_API_KEY` on every request. The key reaches one namespace, the third part of the key, and nothing else.
```
export POEMA_URL=https://127.0.0.1:7070
export POEMA_API_KEY=poema_ns_...
NS=$(echo "$POEMA_API_KEY" | cut -d_ -f3)
API=$POEMA_URL/v1alpha1/namespaces/$NS
H='X-API-Key: '$POEMA_API_KEY
```
## Core calls
Create or replace a resource with `PUT $API/{tasks|workspaces|gateways|models}/NAME` and a JSON manifest (`apiVersion: poema.dev/v1alpha1`, `kind`, `metadata.name`, `spec`). GET one, GET the kind to list, DELETE to remove.
1. A Workspace that clones a git repository into /workspace (optional):
```
curl -X PUT $API/workspaces/app -H "$H" -H 'Content-Type: application/json' -d '{
"apiVersion":"poema.dev/v1alpha1","kind":"Workspace","metadata":{"name":"app"},
"spec":{"git":[{"name":"repo","repo":"https://github.com/you/app","branch":"main","depth":1}]}}'
```
A Workspace can also give an agent that reads them (Claude Code) MCP servers, a remote one by URL or one the instance runs by command, and skills, repositories with a SKILL.md at their root. A remote server's host must be one the Gateway allows.
```
"spec":{"mcp":{"servers":[{"name":"docs","url":"https://mcp.example.com/mcp"},{"name":"files","command":["npx","-y","@modelcontextprotocol/server-filesystem","."]}]},
"skills":{"path":"repo/.claude/skills","sources":["https://github.com/owner/skill"]}}
```
2. A Task. `gateway` "default" is the free egress list below (without one there is no network). `debug: true` turns on commands, files and the terminal in it. `model` names a Model (see Model keys). Its command exiting does not end it: it stays up until suspended, deleted or its session ends.
```
curl -X PUT $API/tasks/work -H "$H" -H 'Content-Type: application/json' -d '{
"apiVersion":"poema.dev/v1alpha1","kind":"Task","metadata":{"name":"work"},
"spec":{"command":["sleep","infinity"],"workspaces":[{"name":"app"}],"gateway":{"name":"default"},"debug":true}}'
```
3. Watch it (NDJSON, one line per status change, `status.phase` is Pending, Running, Suspended or Failed) and read its output (only while Running):
```
curl -N $API/tasks/work/watch -H "$H"
curl $API/tasks/work/logs -H "$H" # add ?follow=true to stream
curl $API/tasks/work/events -H "$H" # activity, egress allowed and denied
```
4. Run a command in it (needs debug: true; NDJSON of start, stdout, stderr, end with exitCode). Files: `GET|POST $API/tasks/work/runner/files?path=/workspace/x`.
```
curl -N -X POST $API/tasks/work/runner/process/start -H "$H" -H 'Content-Type: application/json' \
-d '{"cmd":["bash","-lc","ls /workspace"],"cwd":"/workspace"}'
```
5. Suspend, resume, fork (a suspended Task, 1 to 100 names, `"memory":true` copies memory too), delete:
```
curl -X POST $API/tasks/work/suspend -H "$H"
curl -X POST $API/tasks/work/resume -H "$H"
curl -X POST $API/tasks/work/fork -H "$H" -H 'Content-Type: application/json' -d '{"names":["try-a","try-b"]}'
curl -X DELETE "$API/tasks/work?wait=true" -H "$H"
```
The same in the CLI (`poema`, Node 22.19 or later; manifests are YAML or JSON):
```
poema apply -f manifest.yaml # or -f - for stdin
poema get tasks
poema watch task work
poema logs work -f
poema exec work -- ls /workspace
poema terminal work # a shell in it
poema forward work 3000:3000 # 127.0.0.1:3000 here, port 3000 inside it
poema suspend work
poema resume work
poema delete task work
```
The same in Python (`pip install` the poema wheel; standard library only):
```python
from poema import Poema
p = Poema()
t = p.create_task("work", ["sleep", "infinity"], gateway="default", debug=True)
print(t.connect().run("ls /workspace").stdout)
t.suspend(); t.wait("Suspended")
forks = t.fork("try-a", "try-b") # memory=True to copy memory too
t.resume()
t.delete()
```
MCP (this is for the agent that drives poema, not the servers of an agent in an instance): `claude mcp add poema -e POEMA_URL=https://127.0.0.1:7070 -e POEMA_API_KEY=$POEMA_API_KEY -- poema-mcp` gives tools sandbox_create, sandbox_run, sandbox_read_file, sandbox_write_file, sandbox_list, sandbox_suspend, sandbox_resume, sandbox_fork and sandbox_delete.
## Network and Terminal
An instance's network is closed: it reaches the hosts of its Gateway and nothing else, and the dashboard's Network page lists them, and on an invited plan adds your own. It reaches each over HTTPS, on port 443, and a host can carry a rule set there of the methods it may send. An instance accepts no connections from the internet. With `debug: true` (the Terminal box on New instance) the dashboard opens a shell in it, and `poema terminal work` and `poema forward work 3000:3000` reach it with your key.
## Projects, the library and the Canvas
These are the dashboard's, for the human. A project groups instances (the Task label `poema-cloud/project`) and has a library of skills and MCP servers, and network rules, of its own; the organization's library reaches every instance made after a change, and the instance page's Update makes an older one again from it. The Canvas draws agents, apps, Postgres and Redis as one graph and applies it as instances, Workspaces and Gateways labelled `poema-cloud/canvas`, which the API lists like any other; changing a started one recreates it and deletes its files. Public URLs are not switched on.
## Model keys
Bring your own key. The human saves it on https://console.poema.dev/dashboard/secrets; poema stores it write-only and calls the provider through its broker, which adds the key on the way out, so it never enters an instance. The saved key makes a Model named `anthropic`, `openai`, `google` or `custom`: use `"model":{"name":"anthropic"}` in a Task's spec. In the instance, ANTHROPIC_BASE_URL and ANTHROPIC_API_KEY (or OpenAI's, or Gemini's) point the stock SDKs at the broker. Model calls need no Gateway. There is no model credit. Models: Anthropic claude-sonnet-5-5, claude-opus-5-5, claude-fable-5-1, claude-haiku-5-5, claude-opus-5, claude-sonnet-5, claude-fable-5, claude-opus-4-8, claude-opus-4-7, claude-opus-4-6, claude-opus-4-5, claude-sonnet-4-6, claude-haiku-4-5; OpenAI gpt-5.3-codex; Google gemini-3-flash-preview, gemini-3.1-pro-preview, gemini-2.5-pro, gemini-2.5-flash.
## Images
An instance runs poema's base image (Debian with git and bash) unless `spec.image` names one of: (omit `image`), agent-minimal, agent-codex, agent-gemini, agent-pi, agent-hermes. The dashboard's New instance page builds these agents from a git repository, each installed at its first start:
- Claude Code: Ubuntu 24.04, needs a anthropic model key.
- Codex: Codex CLI, needs a openai model key.
- Gemini CLI: Gemini CLI, needs a google model key.
- Pi: Pi, needs a anthropic model key.
- Hermes: Hermes Agent, needs a anthropic model key.
- Shell: Ubuntu 24.04.
Shapes on that page: S (1 vCPU, 1 GiB), M (2 vCPU, 2 GiB), L (2 vCPU, 4 GiB).
## Free plan
- 3 instances running at once, 4 vCPUs and 8 GiB of memory between them; one that would exceed it waits Pending with reason QuotaExceeded.
- Sessions of up to 60 minutes: at its end an instance is suspended, not deleted; resume it for another. Suspended for 14 days, it is deleted.
- 100 vCPU-hours of compute a month (UTC), then instances wait for the next month.
- 12 objects kept: instances (suspended too), Workspaces, Gateways, Models and secrets.
- Any other image than the ones above is refused.
- Egress, port 443 only, to: github.com, api.github.com, codeload.github.com, objects.githubusercontent.com, raw.githubusercontent.com, gitlab.com, registry.npmjs.org, pypi.org, files.pythonhosted.org, proxy.golang.org, sum.golang.org, index.crates.io, static.crates.io, deb.debian.org. Everything else is denied and logged. A Gateway of your own may list a subset of these hosts.
- Workspaces are not backed up.
## Errors
Every error is JSON with the matching HTTP status: `{"error":{"code":"Invalid","message":"..."}}`. 400 Invalid (the message names the JSON path), 401 Unauthorized (no or wrong key), 403 Forbidden (outside your namespace, or a field only an operator sets), 404 NotFound, 409 Conflict (for example fork of a task that is not suspended, or deleting a Workspace a live Task uses), 413, 429 and 500 Internal (the message has an id). CLI and SDK errors carry the same status, code and message.
## Docs
- Quickstart: https://console.poema.dev/docs
- Deploy from git: https://console.poema.dev/docs/git
- Projects and the library: https://console.poema.dev/docs/library
- Network rules: https://console.poema.dev/docs/network
- Canvas: https://console.poema.dev/docs/canvas
- Python SDK: https://console.poema.dev/docs/python
- CLI: https://console.poema.dev/docs/cli
- poema-mcp: https://console.poema.dev/docs/mcp
- Concepts: https://console.poema.dev/docs/concepts
- This document: https://console.poema.dev/agent.md . Index: https://console.poema.dev/llms.txt