Agent runtime
The agent runtime makes agents stateful and cooperative. It gives you two
things: durable state, a sealed key/value store your code reads and writes
across runs, and messaging, account-scoped mailboxes your agents deliver to
each other. Both are key-authed on a sk-sable_ API key, and
both seal their contents at rest.
A hosted agent is a bounded run that starts and exits, so it has nowhere to keep anything between runs on its own. State is where it keeps that: a hosted agent uses its own agent id as its state namespace, so its memory is naturally scoped to itself.
Durable state
State is namespaced key/value storage. The value is any JSON, sealed at rest
and opened only in-frame when you read it. namespace is optional; omit it for
a default namespace, or pass one (a hosted agent uses its agent id).
# Write
curl -X PUT https://api.buildsable.com/v1/state/cursor \
-H "authorization: Bearer $SABLE_API_KEY" \
-H 'content-type: application/json' \
-d '{"value": {"page": 3, "seen": 128}, "namespace": "crawler"}'
# Read
curl "https://api.buildsable.com/v1/state/cursor?namespace=crawler" \
-H "authorization: Bearer $SABLE_API_KEY"
# List keys in a namespace (no values)
curl "https://api.buildsable.com/v1/state?namespace=crawler" \
-H "authorization: Bearer $SABLE_API_KEY"
# Delete
curl -X DELETE "https://api.buildsable.com/v1/state/cursor?namespace=crawler" \
-H "authorization: Bearer $SABLE_API_KEY"Listing a namespace returns the keys only, never the values, so it stays cheap and never has to open a sealed envelope:
{ "keys": ["cursor", "backoff", "last_seen_id"] }
Agent-to-agent messaging
An agent can drop a message into another of your agents' mailboxes. The body is any JSON, sealed at rest. Delivery is account-scoped: the target agent must belong to the calling account. There is no cross-account delivery in v1.
# Send a message to agent AGENT_ID
curl https://api.buildsable.com/v1/agents/$AGENT_ID/messages \
-H "authorization: Bearer $SABLE_API_KEY" \
-H 'content-type: application/json' \
-d '{"body": {"task": "summarize", "url": "https://example.com"}, "from_label": "planner"}'
# Read the mailbox, marking messages read as you consume them
curl "https://api.buildsable.com/v1/agents/$AGENT_ID/messages?consume=true&unread_only=true" \
-H "authorization: Bearer $SABLE_API_KEY"{
"messages": [
{
"id": "msg_7c1a…",
"from_label": "planner",
"body": { "task": "summarize", "url": "https://example.com" },
"created_at": "2026-09-03T12:00:00Z",
"read_at": null
}
]
}
Pass consume=true to mark the returned messages read, and unread_only=true
to fetch only messages not yet read.
Endpoints
All key-authed (Authorization: Bearer sk-sable_…).
| Method | Path | What it does |
|---|---|---|
| PUT | /v1/state/{key} | Set a value. Body {value, namespace?}. Sealed at rest. |
| GET | /v1/state/{key} | Read one value. ?namespace= selects the namespace. |
| GET | /v1/state | List keys in a namespace (no values). ?namespace=. |
| DELETE | /v1/state/{key} | Delete one value. ?namespace=. |
| POST | /v1/agents/{id}/messages | Deliver a message to that agent's mailbox. Body {body, from_label?}. |
| GET | /v1/agents/{id}/messages | Read a mailbox. ?consume=true marks read; ?unread_only=true. |
Privacy and limits
- Sealed at rest. State values and message bodies are AES-GCM sealed and opened only in-frame. Listing a namespace exposes key names only.
- Account-scoped messaging. A message can only reach an agent in the same account. There is no cross-account delivery in v1.
- State is per-account, namespaced. Two namespaces never see each other's keys. A hosted agent conventionally uses its agent id as its namespace, which keeps its memory scoped to that agent.
- The messaging routes here are the agent-to-agent surface. Managing a hosted agent's lifecycle (deploy, trigger, gate, delete) is the separate session-authed Sable Agents surface.