Hermes cron no-agent script: poll a Sume bulk queue quietly
A Hermes cron script that prints nothing while a Sume bulk queue runs and one line when every item is terminal. Includes the Python, 404 and 429 cases.

Use a Hermes cron job in script-only mode: the script asks Sume for the queue, prints nothing while items are still running, and prints one summary line when the queue is done. Hermes delivers a script's stdout as written and treats empty output as a silent tick, so no model is called and no message is sent until there is something to say.
This is the right shape for bulk runs because the queue has no webhook of its own. Polling is the only queue-level signal, and a poll that costs no tokens can run every few minutes all night.
How does script-only mode behave?
According to Hermes's cron page, setting no_agent skips the language model entirely. The script's stdout goes out verbatim, an empty stdout is a silent tick, and a script has a default timeout of 3600 seconds. Each run is still a separate process, so the script must keep its own state, such as the queue id.
| Sume answer | Why | |
|---|---|---|
| Queue status queued or running | Nothing | Silent tick, nothing to report |
| 429 rate_limited | Nothing | The next tick tries again; reads have their own, larger budget |
| 404 format_run_queue_not_found | One error line | Wrong key, wrong owner or a mistyped id; it will not fix itself |
| completed, counts.failed is 0 | One summary line | Every item is terminal and none failed |
| completed, counts.failed above 0 | Summary plus failed rows | Completed only means every item is terminal |
What is the script?
Save the queue id from the 202 at submit time into a file the script can read. The script removes the file once it has reported, so later ticks stay silent.
import os, pathlib, requests
f = pathlib.Path.home() / ".sume-queue"
if not f.exists():
raise SystemExit(0)
qid = f.read_text().strip()
r = requests.get(
f"https://api.sume.com/v1/format-run-queues/{qid}",
headers={"Authorization": "Bearer " + os.environ["SUME_API_KEY"]},
timeout=30,
)
if r.status_code == 429:
raise SystemExit(0)
if r.status_code != 200:
print(f"Sume queue check failed: HTTP {r.status_code} {r.text[:200]}")
raise SystemExit(0)
q = r.json()["data"]
if q["status"] != "completed":
raise SystemExit(0)
c = q["counts"]
print(f"Queue {qid} done: {c['completed']} completed, {c['failed']} failed, {c['canceled']} canceled.")
for item in q["items"]:
if item["status"] == "failed":
print(f" item {item['index']}: {(item['error'] or {}).get('code')} run {item['run_id']}")
f.unlink()Why print the failed rows?
The queue item's error is a coarse code such as format_run_failed. The reason lives on the child run, so the line includes the run_id for you to open at GET /v1/format-runs/{run_id}. An item that never started has run_id: null, and its error carries the create failure instead. Both cases are in the printed line.
How often should it tick?
Hermes accepts interval schedules such as every 30m and cron expressions, and its scheduler wakes every 60 seconds, so anything under a minute is not meaningful. A bulk queue of 100 items at a concurrency of 4 is a job of hours, not seconds; a check every 10 minutes tells you about completion soon enough, and each check is a single cheap read.
Pick the interval from how long one child run takes and how many rows wait behind the window. If a person is waiting on the first result, poll the first child directly with GET /v1/format-runs/{run_id}/status instead. This script answers a different question: is the whole batch finished, and did anything fail.
What can go wrong?
Create the job disabled and start it with a manual run once the queue id file exists. Hermes supports creating a job paused and running one on demand, which avoids a first tick that finds no file.
- A canceled child counts under
canceled, notfailed, so a person who stopped one row does not trigger a failure line but still shows in the summary. - If a check fails, the script prints the error and keeps the file, so the next tick checks again. That is deliberate: an outage that clears should not lose the queue id. The cost is one error line per tick until it does.
- The queue id is not recoverable from Sume: there is no list-queues endpoint. Losing the file means listing the Format's runs with
GET /v1/formats/{handle}/{slug}/runsand checking each receipt. - Polling is a read, and reads have a budget forty times the write budget on every plan, so a poll every five minutes is nowhere near it.
Sources
Related posts
More in Agents
- What is an AI video agent? One that talks, or one that makes
An AI video agent can mean a live on-camera persona like a Tavus PAL, or an agent that makes videos for you. Which one Sume is, and how to call it from code.
- Run the Sume video agent from your backend with Agent Completions
POST /v1/agent/completions runs the same agent as the Sume Agents chat, with tools and media generation, and returns an async run receipt you poll or webhook.
- Safe automation for AI agents that call paid APIs
Keep agents read-only by default, keep secrets out of logs, and on hosted MCP send an idempotency_key, preview with dry_run, and cap with max_spend_usd.
- Scheduled AI video agent runs: cron, API triggers, and receipts
A Sume schedule is a saved Agents automation that runs on a cron cadence and returns a run receipt. Author it in the dashboard; start and monitor runs by API.
Written by Sume