Run an Agent Completion nightly from CI with curl and a spend cap

A 25-line bash job that starts a Sume Agent Completion with a cap and an idempotency key, polls the receipt, and fails the build unless the run completed.

5 min readSume
All posts

A CI job can start a Sume Agent Completion, wait for it, and exit non-zero unless the run completed. The pieces are POST /v1/agent/completions with a required generation_spend_cap_usd, an Idempotency-Key that is stable per pipeline run, and a poll of GET /v1/agent-runs/{id} until next_action is no longer poll_status.

The key must carry agent_completions:write and agent_completions:read. Keys created before Agent Completions shipped do not have them and return 403 insufficient_scope; create a new key and store it as a CI secret. Service-account keys cannot create these runs.

The script

This uses curl and jq. Set SUME_API_KEY from your CI secret store and RUN_KEY to something stable for the pipeline run, such as the commit sha plus the job name. It caps spend at $2 and stops waiting after 40 polls of 15 seconds, which is ten minutes. A timeout of the poll does not cancel the run, so the script cancels it explicitly.

set -euo pipefail
API=https://api.sume.com/v1
H=(-H "Authorization: Bearer $SUME_API_KEY" -H "Content-Type: application/json")
RUN=$(curl -sS -X POST "$API/agent/completions" "${H[@]}" \
  -H "Idempotency-Key: nightly-$RUN_KEY" \
  -d '{"instruction":"Summarize yesterday release notes in 5 bullets.",
       "generation_spend_cap_usd":2}')
ID=$(echo "$RUN" | jq -er '.data.id')
for i in $(seq 1 40); do
  R=$(curl -sS "$API/agent-runs/$ID" "${H[@]}")
  [ "$(echo "$R" | jq -r '.data.next_action')" != "poll_status" ] && break
  sleep 15
done
STATUS=$(echo "$R" | jq -r '.data.status')
if [ "$STATUS" != "completed" ]; then
  curl -sS -X POST "$API/agent-runs/$ID/cancel" "${H[@]}" >/dev/null || true
  echo "run $ID ended as $STATUS" >&2
  exit 1
fi
echo "$R" | jq -r '.data.output.text'

Why each line is there

The Idempotency-Key makes a retried pipeline step return the original receipt (idempotency_hit: true) instead of starting and billing a second run. Reusing the key with a different instruction returns 409 idempotency_conflict, so change the key when you change the prompt.

The cap is not optional. generation_spend_cap_usd has no default, and a missing value is a 400 invalid_request. In the chat UI an approval prompt protects the wallet; a backend caller has none, so the cap is the control.

  • The statuses are queued, processing, completed, failed and canceled.
  • A completed run puts the last text in output.text and generated media in output.images, output.videos, output.audio and output.files.
  • GET /v1/agent-runs lists runs newest first, useful for a morning audit.

Failure handling

Poll failures that are transport errors should be retried by the loop, not turned into a failed build. If you would rather not poll at all, send communication.webhook_url and let a receiver record the single agent.run.terminal event, then have CI read the stored result. Keep the poll as a backup, because the docs treat delivery as an optimization and not the only recovery path.

Hardening the job

Add a wall-clock limit on the CI step itself, so that a hung runner does not outlive the poll loop. Store the run id as a build artifact. If the step is retried by the CI system, the same RUN_KEY returns the original receipt through the idempotency key, so the second attempt reads the first run instead of starting another.

For structured output add an output_schema object to the request and have the final line assert a field with jq -e. For heavier work, switch to a webhook: send communication.webhook_url and let a small receiver write the result where CI can read it. Keep the poll as a fallback.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume