Python 3.14.8 Agent Completions: spend cap, schema, asyncio.run

Start an Agent Completion from Python 3.14.8 with asyncio.run, a required generation_spend_cap_usd, an output_schema and an Idempotency-Key, then poll the run.

5 min readSume
All posts

Short answer

POST https://api.sume.com/v1/agent/completions returns 202 with an agrun_ receipt, not a chat reply, and it fails with 400 invalid_request unless you send generation_spend_cap_usd. Wrap the create and the poll in asyncio.run(main()); top-level await is not available in a script. Python 3.14.8 was released on 30 September 2026 according to the release page.

The Agent Completions docs describe the receipt, the scopes and the errors. This post wires them into a runnable script using only the standard library, with blocking calls pushed to threads by asyncio.to_thread.

Request fields that matter

Send exactly one of instruction or messages. The cap has no default, because an unattended agent has no interactive spend prompt and the cap is the substitute.

Agent Completion create fields (as of 2026-10-03)
FieldRequiredNote
instruction or messagesone ofnever both; assistant turns are rejected
generation_spend_cap_usdyesmost you will spend on this one run
output_schemanobinds the run's output to your schema
attachmentsnoup to 30 images
communication.webhook_urlnopublic HTTPS URL notified at the terminal status

The script

The key needs agent_completions:write to create and agent_completions:read to poll. Keys created before Agent Completions shipped lack both and fail with 403 insufficient_scope; scopes cannot be added to an existing key, so create a new one. Service-account keys cannot create completions at all.

import asyncio, json, os, urllib.request

H = {"Authorization": "Bearer " + os.environ["SUME_API_KEY"], "Content-Type": "application/json"}

def call(method, url, payload=None, extra=None):
    data = json.dumps(payload).encode() if payload else None
    req = urllib.request.Request(url, data=data, method=method, headers={**H, **(extra or {})})
    with urllib.request.urlopen(req, timeout=30) as res:
        return json.load(res)["data"]

SCHEMA = {"name": "caption", "schema": {"type": "object", "additionalProperties": False,
          "properties": {"caption": {"type": "string"}}, "required": ["caption"]}}

async def main():
    run = await asyncio.to_thread(call, "POST", "https://api.sume.com/v1/agent/completions", {
        "instruction": "Write a 12-word caption for a matte black bottle on marble.",
        "generation_spend_cap_usd": 1,
        "output_schema": SCHEMA,
    }, {"Idempotency-Key": "caption-2026-10-03-001"})
    url = "https://api.sume.com/v1/agent-runs/" + run["id"]
    while run["status"] in ("queued", "processing"):
        await asyncio.sleep(3)
        run = await asyncio.to_thread(call, "GET", url)
    print(run["status"], json.dumps(run["output"]))

asyncio.run(main())

Reading the finished run

A completed run fills output. With an output_schema, the output is parsed against it after the run completes. Generated media comes back as durable media.sume.com HTTPS URLs in output.images, output.videos, output.audio and output.files. Run states match Action runs: queued, processing, completed, failed, canceled.

To stop a run in flight call POST /v1/agent-runs/{id}/cancel. A 404 agent_run_not_found means the id is unknown, belongs to another account, or is an Action or Format run id, which does not resolve on this route.

What Sume does and does not do

Sume replays an Idempotency-Key with the original receipt and idempotency_hit: true, and returns 409 idempotency_conflict if the payload differs. Every completion runs in a fresh thread.

Sume does not stream Agent Completions, does not offer an OpenAI-compatible choices[] response, does not continue a prior thread with thread_id, and accepts image attachments only. For push delivery set communication.webhook_url and verify the agent.run.terminal event.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume