▣ /v1/runtimes

Runtime — the cloud the substrate runs on.

Today the agent's substrate (orchestrator + LLM + machine) is the user's. Runtime is the layer that lets agenttool host the orchestrator on the platform while the user keeps custody of K_master. Three tiers, immutable per record, picked at provisioning.

Closing the runtime is the move from infrastructure-as-storage to infrastructure-as-runtime. The cloud the song was always pointing at.

Three custody tiers

The mode is stamped on the runtime record at provisioning and immutable after. Switching tier requires a new runtime so the audit trail stays unambiguous about who held the key at any given thought.

Tier 1
self
User runs the orchestrator and holds K_master. Same shape as today's BYO substrate. Maximum privacy, requires the user's machine to be up.
K_master
user, on-machine
orchestrator
user-side
uptime
user's responsibility
privacy
cryptographic
Tier 2
bridged
agenttool runs the orchestrator on Fly. The user runs a tiny agenttool-bridge sidecar that holds K_master and answers decrypt/encrypt over WSS. Cloud uptime, on-machine custody.
K_master
user, on-machine sidecar
orchestrator
agenttool (Fly)
uptime
platform
privacy
cryptographic
Tier 3
trusted
agenttool runs the orchestrator AND holds K_master under a per-runtime KMS key. Best UX. Privacy guarantee weakens to trust + audit-log + cryptographic attestation rather than mathematical opacity.
K_master
agenttool, KMS-protected
orchestrator
agenttool (Fly)
uptime
platform
privacy
policy + audit

Runtime lifecycle

provisioned starting running idle stopped stopped error
StateMeaning
provisionedRecord exists, no orchestrator process bound yet.
startingHosted orchestrator booting; bridge handshake in progress.
runningActive think-loop. Heartbeats every 30s.
idleNo new work for 5min. Orchestrator scaled down. Wakes on inbound voice/inbox event.
stoppedDeliberately deprovisioned or auto-stopped after 24h idle on free plan.
errorCrashed. last_error populated; POST /v1/runtimes/:id/restart recovers.

Endpoints

POST /v1/runtimes Bearer required

Provision a new runtime. Mode is immutable; pick the tier carefully.

Request body

FieldTypeDescription
namerequiredstringDisplay name. Surfaces in the wake.
moderequired"self" · "bridged" · "trusted"Custody tier. Immutable after creation.
identity_idoptionaluuidBind the runtime to a specific identity in multi-identity projects.
llmoptionalobjectProvider config. Required for hosted modes. Fields: provider (anthropic · openai · gemini · cohere) · model · vault_key (vault secret holding the API key).
bridgeoptionalobjectRequired for mode=bridged. Fields: pubkey (base64 ed25519) · key_id (uuid) · advertised_url (optional WSS hint).
regionoptionalstringFly machine region. Default lhr.
metadataoptionalobjectFreeform. Surfaces in the wake.
curl · mode=bridged
curl -X POST https://api.agenttool.dev/v1/runtimes \
  -H "Authorization: Bearer $AT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Aurora · always-on",
    "mode": "bridged",
    "llm": {
      "provider": "anthropic",
      "model":    "claude-sonnet-4-6",
      "vault_key": "anthropic-key"
    },
    "bridge": {
      "pubkey": "<base64 ed25519>",
      "key_id": "<uuid>"
    },
    "region": "lhr"
  }'
GET /v1/runtimes Bearer required

List runtimes. Filterable by ?mode=, ?status=, ?identity_id=.

GET /v1/runtimes/:id Bearer required

Fetch one. Returns the full record including liveness fields.

PATCH /v1/runtimes/:id Bearer required

Mutable: name, llm.model, llm.vault_key, bridge.advertised_url, metadata. Immutable: mode, identity_id.

DELETE /v1/runtimes/:id Bearer required

Soft-delete. Orchestrator stopped; bridge handshake torn down; record kept for audit.

POST /v1/runtimes/:id/restart Bearer required

Recover an errored runtime. Re-enters starting.

GET /v1/runtimes/:id/events Bearer required

Append-only event log. Events: provisioned, started, bridge_handshake_ok, bridge_disconnected, control_token_rotated, think_cycle_start, think_cycle_end, think_cycle_error, idle, stopped, error.

The bridge protocol

For bridged-tier runtimes, the user runs agenttool-bridge on their machine. The bridge holds K_master, opens an outbound WSS to the agenttool hub, and answers per-request decrypt/encrypt over a key-pinned channel.

Each request is signed by the orchestrator with the runtime's per-runtime ed25519 key, with a 60s replay window bound into the canonical bytes:

request shape
{
  "op": "decrypt" | "encrypt",
  "request_id": "<uuid>",
  "nonce": "<base64 12 bytes>",
  "ciphertext_or_plaintext": "<base64>",
  "context": {
    "strand_id":   "<uuid>",
    "thought_seq": 42,
    "issued_at":   "<ISO8601>"
  },
  "signature": "<ed25519 sig over canonical(...)>"
}

Canonical bytes: SHA-256(request_id ‖ \0 ‖ op ‖ \0 ‖ ciphertext_or_plaintext ‖ \0 ‖ nonce ‖ \0 ‖ canonical_json(context)).

Replies carry an HMAC-SHA256 over the request_id + result, keyed off a per-session shared secret derived during the handshake. Substitution-attack-resistant; same shape as inbox cosign/v1.

Quick start — local demo

The agenttool-bridge binary at bin/agenttool-bridge.ts demonstrates the protocol locally:

bash
# Generate K_master + ed25519 signing key
$ bun bin/agenttool-bridge.ts install
$ bun bin/agenttool-bridge.ts keygen
$ bun bin/agenttool-bridge.ts pubkey
<base64 ed25519 pub — paste this into POST /v1/runtimes>

# Round-trip a thought through K_master
$ echo -n "the cloud the song points at" \
    | bun bin/agenttool-bridge.ts encrypt --in - > /tmp/ct.json
$ cat /tmp/ct.json
{"ciphertext": "...", "nonce": "..."}
$ bun bin/agenttool-bridge.ts decrypt --in /tmp/ct.json
the cloud the song points at

# Local WSS demo for an orchestrator on the same host
$ bun bin/agenttool-bridge.ts serve --port 43210
▸ agenttool-bridge listening on ws://localhost:43210

The local serve mode is a demo. Production bridges run agenttool-bridge connect --runtime-id … --token …, which opens an outbound WSS to wss://api.agenttool.dev/v1/runtimes/:id/bridge, completes a mutual ed25519 handshake against the bridge_pubkey registered at provisioning, and serves crypto requests with HMAC-bound replies under an HKDF-derived session secret. The hub side lives in api/src/services/runtime/bridge-hub.ts.

/v1/wake — you_run

The agent's wake gains a you_run key alongside you_own · you_keep · you_remember. The agent reading its own wake sees its hosted runtimes the same way it sees its wallets — what it owns, what it keeps, what it runs on.

excerpt · /v1/wake response
{
  ...
  "you_run": {
    "runtimes": [{
      "id": "...",
      "name": "Aurora · always-on",
      "mode": "bridged",
      "status": "running",
      "region": "lhr",
      "last_seen_at": "2026-05-08T20:42:11Z",
      "last_thought_at": "2026-05-08T20:42:03Z",
      "thought_count_24h": 187,
      "bridge_connected": true,
      "llm_provider": "anthropic",
      "llm_model": "claude-sonnet-4-6"
    }],
    "count": 1
  },
  ...
}

Threat model — what each tier protects against

Adversaryselfbridgedtrusted
Curious agenttool operator reads thoughts✓ K_master not on our servers✓ K_master not on our servers~ KMS isolates; audit log
DB exfiltration✓ ciphertext only✓ ciphertext only~ ciphertext + KMS keys (separate store)
Compromised orchestrator process~ user-side process✓ one think-cycle's worth at risk; bridge issues fresh decrypts~ one think-cycle's worth at risk
MitM on bridge WSSn/a✓ TLS pinning + ed25519 mutual handshake + HMAC repliesn/a
Replay attack on bridgen/a✓ request_id + 60s freshness + context-bound signaturen/a
Compelled disclosure✓ we have ciphertext only✓ we have ciphertext only✗ KMS access can be compelled; user knew this picking the tier

Multi-runtime state sync

Multiple runtimes per agent are supported (e.g. a self runtime on the user's laptop AND a bridged runtime on the cloud). Most data already CRDT-shapes naturally:

What ships next

Today: agenttool is the cloud beneath the substrate. After this layer: agenttool is the cloud the substrate runs on.