Integrate WAMP Cloud
Run one task through the public API, then use its Session and Run IDs to follow work or recover after a disconnect. No SDK is required. The example uses an empty workspace; repository access is a separate grant.
Before the first request
Section titled “Before the first request”You need an organization with compute configured and an App installation granted
wamp.cloud.sessions:create, wamp.cloud.turns:submit and
wamp.cloud.sessions:read. A backend serving one organization can use an
administrator-issued installation API key. A product installed by multiple
organizations exchanges its signed App assertion for a short-lived installation
token. Authentication covers both.
Set WAMP_API to your Cloud API origin and WAMP_TOKEN to that installation
credential. This deployment uses https://api.vampikez.fun; the first-party
browser workspace is at cloud.vampikez.fun.
If Account and Cloud use separate origins, token exchange goes to Account and
these /v1 calls go to Cloud. Keep installation credentials on your backend.
A discovery response separates execution capacity from repository authority. This excerpt shows available compute with no private repository grant:
{ "capabilities": { "environments": [{25 collapsed lines
"id": "managed", "label": "WAMP managed sandbox", "availability": { "state": "available" }, "default": true, "runtimes": ["wamp", "pi"], "workspace": { "checkpoint": "portable_tree", "maxCheckpointBytes": 268435456 } }], "repositories": { "sources": [ { "kind": "public_https", "available": true }, { "kind": "github_repository_grant", "available": false, "discovery": "/v1/repositories" } ], "publications": [ { "kind": "github_draft_pull_request", "available": false }, { "kind": "github_direct_branch_push", "available": false } ], "merges": [{ "kind": "github_pull_request_merge", "available": false, "strategies": ["merge", "squash", "rebase"], "modes": ["now", "when_ready"] }] } }}Run a task
Section titled “Run a task”- Check compute availability. Read
capabilities.environments[].availability. Unavailable compute requires organization configuration. A new native Session uses the organization’s resolved agent slot model whenmodelis omitted; if no slot is available, creation returns400 cloud_agent_model_unavailable. - Persist the command before sending it. Session and Turn IDs are
caller-owned UUIDs. The example writes
cloud-command.jsononce and reuses it on reruns, so an ambiguous network failure does not create duplicate work. In your backend, store the same information in your database. - Submit and observe. Creation with
initialTurncommits both the Session and its queued Run. The Turn ID is also the Run ID. Poll until terminal or awaiting input, with a local deadline; reaching that deadline does not cancel Cloud work.
Requires curl, jq and uuidgen. Export WAMP_TOKEN before running.
#!/usr/bin/env bashset -euo pipefailexport WAMP_API="${WAMP_API:-https://api.vampikez.fun}": "${WAMP_TOKEN:?Set WAMP_TOKEN to your installation credential}"
if [ ! -f cloud-command.json ]; then CAPS=$(curl --fail-with-body --max-time 30 -sS "$WAMP_API/v1/capabilities" \ -H "Authorization: Bearer $WAMP_TOKEN") printf '%s' "$CAPS" | jq -e \ '.capabilities.environments | any(.availability.state == "available")' >/dev/null SESSION_ID=$(uuidgen | tr 'A-Z' 'a-z') TURN_ID=$(uuidgen | tr 'A-Z' 'a-z') # This local demo runs one process; use a database transaction in production. jq -n --arg a "$WAMP_API" --arg s "$SESSION_ID" --arg t "$TURN_ID" \ '{apiOrigin:$a, sessionId:$s, body:{initialTurn:{id:$t, message:"Create hello.txt containing hello, then summarize what you did."}}}' \ > cloud-command.jsonfi
SAVED_API=$(jq -er '.apiOrigin' cloud-command.json)if [ "$SAVED_API" != "$WAMP_API" ]; then echo "Saved command belongs to a different API origin" >&2 exit 1fiSESSION_ID=$(jq -er '.sessionId' cloud-command.json)TURN_ID=$(jq -er '.body.initialTurn.id' cloud-command.json)BODY=$(jq -c '.body' cloud-command.json)curl --fail-with-body --max-time 30 -sS -X PUT "$WAMP_API/v1/sessions/$SESSION_ID" \ -H "Authorization: Bearer $WAMP_TOKEN" \ -H 'Content-Type: application/json' -d "$BODY" | jq '{session, initialTurn}'
# Follow the ordered Event log. Save the last id you handled before reconnecting.curl -N --fail-with-body -sS \ "$WAMP_API/v1/sessions/$SESSION_ID/events/stream?after=0" \ -H "Authorization: Bearer $WAMP_TOKEN" \ -H 'Accept: text/event-stream'Run with a TypeScript runner on Node 22 or later. Export WAMP_TOKEN first.
import { randomUUID } from 'node:crypto';import { readFile, writeFile } from 'node:fs/promises';
const api = process.env.WAMP_API ?? 'https://api.vampikez.fun';const token = process.env.WAMP_TOKEN;if (!token) throw new Error('Set WAMP_TOKEN to your installation credential');
async function call(path: string, method = 'GET', body?: unknown) { const response = await fetch(`${api}${path}`, { method, headers: { Authorization: `Bearer ${token}`, ...(body === undefined ? {} : { 'Content-Type': 'application/json' }), }, body: body === undefined ? undefined : JSON.stringify(body), signal: AbortSignal.timeout(30_000), }); const value = await response.json(); if (!response.ok) { throw new Error(`${response.status} ${value.error}; retry-after=${ response.headers.get('Retry-After') ?? 'none'}`); } return value;}
async function main() { let command; try { command = JSON.parse(await readFile('cloud-command.json', 'utf8')); } catch (error) { if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error; const { capabilities } = await call('/v1/capabilities'); const available = capabilities.environments.some( (environment: { availability: { state: string } }) => environment.availability.state === 'available', ); if (!available) throw new Error('Configure organization compute access'); command = { apiOrigin: api, sessionId: randomUUID(), body: { initialTurn: { id: randomUUID(), message: 'Create hello.txt containing hello, then summarize what you did.', } }, }; // Exclusive creation prevents this demo from overwriting another command. await writeFile('cloud-command.json', JSON.stringify(command), { flag: 'wx' }); } if (command.apiOrigin !== api) throw new Error('Saved command belongs to a different API origin'); const sessionPath = `/v1/sessions/${command.sessionId}`; const runId = command.body.initialTurn.id; console.log(await call(sessionPath, 'PUT', command.body)); const response = await fetch(`${api}${sessionPath}/events/stream?after=0`, { headers: { Authorization: `Bearer ${token}`, Accept: 'text/event-stream' }, }); if (!response.ok || !response.body) { throw new Error(`Event stream refused: ${response.status} ${await response.text()}`); } const reader = response.body.getReader(); const decoder = new TextDecoder(); while (true) { const { done, value } = await reader.read(); if (done) break; process.stdout.write(decoder.decode(value, { stream: true })); }}
main().catch(error => { console.error(error); process.exitCode = 1; });Both examples reuse the saved command. To start independent work, save a new command under a new application record; do not change the body under an existing ID. Never reuse the file across organizations.
Handle the outcome
Section titled “Handle the outcome”| Run state | Next action |
|---|---|
queued, dispatching, running |
Continue observing. A closed Event stream leaves the Run active; reconnect from the saved cursor |
awaiting |
Fetch open Interactions and submit a new Turn with replyTo.interactionId |
completed |
Read safe message events and retained Artifacts |
failed, cancelled, crashed |
Inspect stopReason, lastError and the result summary before retrying work |
The Session’s phase is a presentation projection. The Run’s status and the
ordered event log are the outcome authority. A file in the workspace is not
necessarily a retained public Artifact; the agent must present it for retention.
See Artifacts.
To continue the same conversation, generate and persist another Turn ID and
send PUT /v1/sessions/{sessionId}/turns/{turnId} with message. Optional agent
selection belongs to that Turn; Agents explains runtime,
model and settings. If the workspace has expired, read Session continuation
first. Follow a run covers event cursors and interaction
replies in both languages.
Add repository delivery
Section titled “Add repository delivery”Connect a repository before creating the Session if the task needs an existing checkout. A Cloud capability is not repository authority; private GitHub access requires a repository grant.
After work finishes, fetch the repository review and publish its exact revision. Choose a pull request or direct branch delivery. Persist Publication and Merge IDs like Turn IDs. Publication success and exact-head PR merge are separate commands and must be observed separately.
Move the example into a backend
Section titled “Move the example into a backend”Drive Cloud from a backend covers tenant mapping,
credential refresh and durable state. Add bounded network retries with jitter:
repeat the exact command after an ambiguous failure, honor Retry-After on
429/503, back off on a 502, 503 or 504 with no JSON code when the
request is safe to repeat, and resolve 409 by its machine code. Bound the
retries in time, not attempts, so they outlast a release’s brief
503 service_unavailable. The session quota has no timed
retry; archive completed Sessions to free it. Errors and
Limits contain the individual policies.
Page Events from your last committed exclusive cursor and deduplicate by event ID. Keep credentials and customer content out of logs. Archive only when no further commands are needed: archive is irreversible, although reads remain available.