Agent Completion result: read output.videos, images, audio and files

A completed Agent Completion puts its last text in output.text and generated media in output.images, videos, audio and files, as durable media.sume.com URLs.

5 min readSume
All posts

Read the result from output on the completed run. By default it has the sume/action-run-output/v1 shape: the agent's last text is in output.text, and generated media is in output.images, output.videos, output.audio and output.files. The run also fills artifacts and records spend in usage, and media URLs are durable media.sume.com HTTPS URLs (Agent Completions).

When the result is ready

Creating a completion returns 202 and a receipt, not the result. The statuses are queued, processing, completed, failed and canceled. Poll the status_url until next_action is no longer poll_status, or register communication.webhook_url and take one signed POST when the run completes or fails.

output is null until the run is terminal. A completed run fills it. A run can also complete with real artifacts but fail to fit your output_schema, which the webhook docs call degraded, so check output_error before you trust output.

Where each result lives on a completed run, read 2026-10-05
FieldHoldsNote
output.textThe agent's last textDefault output shape
output.images / videos / audio / filesGenerated mediaDurable media.sume.com URLs
artifactsMedia harvested from the runEmpty until terminal
usageSpend recordNull when spend could not be read
status_urlPoll targetUse it, do not build URLs

Pull the URLs out

The function below walks a receipt-shaped dictionary and returns every media URL. The sample receipt is illustrative and uses a placeholder URL. I assume each media entry is an object with a url, or a bare string, so adjust after you inspect a real receipt. It runs offline.

MEDIA = ("images", "videos", "audio", "files")

def media_urls(receipt: dict) -> list:
    out = receipt.get("output") or {}
    urls = []
    for kind in MEDIA:
        for item in out.get(kind) or []:
            url = item.get("url") if isinstance(item, dict) else item
            if url:
                urls.append(url)
    return urls

if __name__ == "__main__":
    sample = {"status": "completed", "output": {"text": "Done",
        "videos": [{"url": "https://media.sume.com/x/clip.mp4"}]}}
    print(media_urls(sample))

Mind the access model

Media URLs do not expire, and the Format run docs say anyone who has a URL can open it. If your product needs per-customer access, copy or proxy the media instead of passing the URL along.

Also log the receipt's request_id and the run id, which together are what support needs, and keep generated URLs out of chat transcripts.

Binding output to your own schema

If your agent needs a fixed shape, send output_schema on the create call, with an optional primary_output_key naming the headline result. Sume parses the run's output against that schema after the run completes. The contract is the same as for Action runs, so a Format or schedule integration you already have can share the parser.

A schema that asks for a field the run never produces leads to a degraded outcome, which is why you should read output_error and the artifacts together rather than assuming that output is the whole story.

Checklist

Before you ship a reader for completion results, check these.

  • Treat output: null on a non-terminal run as normal.
  • Read output_error when output is empty on a completed run.
  • Do not re-submit because your poll timed out; the run keeps going.
  • Cancel with POST /v1/agent-runs/{id}/cancel if you no longer need it.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume