HTTP acceptance and Python completion are separate events
An asynchronous Python execution API returns a job record before the script necessarily finishes. In cpuOS, POST /v1/jobs returns HTTP 202 with an accepted or replayed job. Store its ID, request parameters and application intent before letting another workflow depend on its answer. A successful HTTP response proves that the request was handled, not that a calculation produced a correct result.
Use the Python execution tutorial for the complete standard-library HTTP client and known arithmetic fixture. This guide focuses on lifecycle decisions that surround that client: waiting budgets, ambiguous submission, cancellation races and deliberate new attempts. The current pilot runs trusted Python 3.13 or Node 24 code on your Docker worker. Code and outputs pass through the hosted control plane.
Persist one submission intent before making the POST
Keep the workspace API key in your submitting application. An intent record should contain a stable Idempotency-Key, the exact source, resource settings and eventually the returned job ID. Replaying unchanged normalized parameters with the same key recovers the same workspace job. Changed parameters with that key produce HTTP 409 idempotency_conflict. A fresh key requests another execution.
{ "name": "Synthetic order totals", "template": "python", "code": "import json\n\n# Synthetic orders, with prices represented as integer cents.\norders = [\n {\"sku\": \"A\", \"unit_cents\": 1200, \"quantity\": 2},\n {\"sku\": \"B\", \"unit_cents\": 500, \"quantity\": 3},\n {\"sku\": \"A\", \"unit_cents\": 1200, \"quantity\": 1},\n]\nby_sku = {}\nfor order in orders:\n revenue = order[\"unit_cents\"] * order[\"quantity\"]\n by_sku[order[\"sku\"]] = by_sku.get(order[\"sku\"], 0) + revenue\nprint(json.dumps({\n \"rows\": len(orders),\n \"units\": sum(order[\"quantity\"] for order in orders),\n \"revenue_cents\": sum(by_sku.values()),\n \"by_sku\": by_sku,\n}, sort_keys=True))", "vcpu": 1, "memoryMb": 512, "timeoutSeconds": 30}Send the intent key in the Idempotency-Key header, rather than adding it as a JSON field. The jobs contract rejects extra body fields. Retain the key when a connection fails before a response arrives: losing the response does not prove the server rejected the submission. Do not create a new key merely because an HTTP client timed out.
Poll status with an application waiting budget
Scroll horizontally to see every column.
| Job state | Application decision |
|---|---|
| queued | Keep the job ID and wait for eligible worker capacity. |
| running | Continue bounded polling. Output is returned after execution. |
| completed | Check exitCode, error, outputTruncated and the result contract. |
| failed | Inspect the reason; decide whether a new attempt is appropriate. |
| cancelled | Record an unsuccessful attempt and stop normal result processing. |
Read GET /v1/jobs/:id until a terminal state arrives. Set a separate client waiting budget that includes requests, queue delay and retry waits. The job's execution timeout cannot stand in for that budget. The published Python client uses time.monotonic for elapsed time, so wall-clock adjustments do not alter its waiting calculation.
The workspace rate limit is shared across API keys and clients. Polling one job every two seconds already uses roughly 30 requests per minute, before submissions or other operations. Coordinate polling centrally for several jobs and respect Retry-After after HTTP 429. A longer interval increases result discovery latency; it does not slow the script itself.
Cancellation can race with normal completion
Use DELETE /v1/jobs/:id when the application no longer needs unfinished work. cpuOS records cancellation for queued work and informs the worker of running cancellation through its polling loop. Do not interpret the API response as instantaneous process termination. A terminal job stays terminal: if completion or failure won the race, DELETE returns that existing result.
Stopping a web request, terminating your client or pressing Ctrl-C does not automatically cancel an accepted server job. The tutorial attempts cancellation when it knows the ID. If the cancellation request fails, keep that ID and inspect the dashboard or API. Recover an uncertain submission with its original intent key before deciding which job should be cancelled.
Separate request recovery from a new computation attempt
A request retry recovers the same intent after a transport problem. A computation retry requests a new execution after reviewing a failed or cancelled attempt. Idempotent replay returns the existing job, including terminal failure; it does not rerun Python. Keep a parent task identifier in your application so several approved attempts can be compared without treating them as distinct business tasks.
// Application policy, not a cpuOS SDK. Persist the intent key and payload.function nextAction({ transportUncertain = false, httpStatus, errorCode, status }) { if (transportUncertain) return "recover-same-intent" if (errorCode === "idempotency_conflict") return "investigate" if (errorCode === "no_online_nodes" || errorCode === "queue_full" || httpStatus === 429) return "wait-for-capacity" if (status === "queued" || status === "running") return "poll-existing-job" if (status === "completed") return "validate-result" if (status === "failed" || status === "cancelled") return "review-before-new-attempt" return "investigate"}const decisions = [ nextAction({ transportUncertain: true }), nextAction({ httpStatus: 429, errorCode: "queue_full" }), nextAction({ status: "completed" }), nextAction({ status: "failed" }),]console.log(JSON.stringify(decisions))This policy names the next decision without executing it. wait-for-capacity requires checking worker readiness or queue pressure, while review-before-new-attempt requires inspecting the failure. Add a maximum attempt count and a reason for each new key. Validate results separately with the structured stdout protocol.
Resolve queue and worker failures before resubmitting
Scroll horizontally to see every column.
| Evidence | Useful response |
|---|---|
| 503 no_online_nodes | Connect an eligible recent-heartbeat Docker worker; verify CPU and memory. |
| 429 queue_full | Reduce application submissions or cancel unnecessary queued jobs. |
| failed / worker_lost | Inspect the interrupted attempt and worker before authorizing another run. |
| failed / queue_expired | Review worker availability and the application's waiting policy. |
| completed / invalid stdout | Fix the output or domain contract; request acceptance did not establish correctness. |
Jobs run with networking disabled; package installation, uploaded files and persistent sessions are unavailable in this pilot. Keep the submitted task small and self-contained. Follow worker operations for readiness and interrupted attempts, then use bounded batches when one application task needs several independent executions.
Return checked tool evidence to the model
When an open model requests a tool, your application authorizes the proposed arguments, submits the job and waits for a checked result. Return compact structured evidence with an explicit failure outcome when the task did not succeed. gpuOS tool calling explains the model side of that loop; LangChain and n8n show HTTP workflow wiring.