Format run stuck on processing: stalled or slow? Read events_url

A Format run can show processing for a long time. The last at value on events_url is the progress clock: if it stops moving for minutes, the run is stalled.

5 min readSume
All posts

Read GET /v1/format-runs/{run_id}/events and look at at on the last entry. The docs call it the progress clock of the run. While it keeps moving, the run is slow. If it does not move for several minutes, the run is stalled, and Sume finalizes it at the bounds it publishes. status: "processing" alone cannot tell you which of the two you have.

This matters for long-form video, where the docs describe 15 to 30 minutes of work. A run that sits on processing for twenty minutes is normal. A run whose clock froze is a different problem, and you want to see it before the deadline.

What the timeline returns

The events route returns a data array of phase entries. Each entry has at, phase, status and duration_ms. There are three phases, and they follow the life of a run.

PhaseWhat it covers
preparingAll the work before the agent gets the run
runningThe agent doing the recipe, where most of the time goes
finalizingTeardown and output harvest

Each entry also has a status of pending, running, done, warning, error or skipped. duration_ms is filled when the phase measured itself, and it is null for a phase that is still running. Consecutive entries with the same phase and status are merged into one, and the at of the merged entry keeps increasing. That merge is why the last entry's at works as a clock.

Measure the silence

The sample reads events_url from the receipt, so you never build the path yourself. It returns the minutes since the last at. A failed timeline read returns null and tells you nothing about the run, because a 429 or 5xx on a read is a read failure only. Pass it SUME_API_KEY in the environment. With a run id as the first argument it reads that run's timeline; with no argument it shows the action and agent case.

const key = process.env.SUME_API_KEY;
if (!key) throw new Error("set SUME_API_KEY");

// Minutes since the progress clock (`at` on the last timeline entry) moved.
// Only Format runs have an events route; action and agent receipts say events_url: null.
export async function silentMinutes(run) {
  if (!run.events_url) return null;
  const res = await fetch(run.events_url, { headers: { "x-api-key": key } });
  if (!res.ok) return null; // a failed timeline read says nothing about the run
  const { data } = await res.json();
  const last = data.at(-1);
  return last ? (Date.now() - Date.parse(last.at)) / 60_000 : null;
}

const base = process.env.SUME_API_BASE_URL ?? "https://api.sume.com/v1";
const run = process.argv[2]
  ? { events_url: `${base}/format-runs/${process.argv[2]}/events` }
  : { events_url: null };
console.log(await silentMinutes(run));

Action and agent runs have no timeline

Only Format runs have an events route. Receipts of action runs and agent completions report events_url: null, and the SDK helpers ignore timeline: true for those families. For them, status_url and the terminal webhook are all you have. The same holds for the log-stream idea: the timeline is a phase timeline, not a log feed. Sume does not publish agent output, tool calls or sandbox internals there, and the docs say it will not in the future. There is also no push channel for progress.

What to do with the number

  • Silent for a few minutes while expires_at is still in the future: log it with the run id and keep polling. Do not cancel yet; a cancel does not refund generation that already finished.
  • Silent and expires_at close: stop polling at expires_at. After that deadline Sume force-finalizes the run as failed, and the receipt carries the error.
  • Silent on a run that is still queued: read queue.state instead. runtime_unavailable means nothing claimed the run, and retry_after_seconds says how long to back off.
  • Reading the timeline on every poll doubles your read requests for the same run. Reads have their own budget, 40 times the write budget, so creates are safe, but poll the timeline at a slower rhythm than status.

Where it fits

Use status for the decision "is it done", and the events clock for the decision "is it moving". The Runs and results page has the lifecycle, the expires_at rule and the cancel semantics. If you only care about the final answer, skip the timeline and wait for the single terminal webhook.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume