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.

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,failedandcanceled. - A completed run puts the last text in
output.textand generated media inoutput.images,output.videos,output.audioandoutput.files. GET /v1/agent-runslists 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
- Egress firewall for a Claude agent in CI: Sume hosts to allow
Claude Agent SDK v0.2.164 hardens CI egress firewalls. Which Sume hosts a render job needs: api.sume.com, mcp.sume.com and media.sume.com, and what each is for.
- Claude Code 2.1.295 MCP changes: a checklist for Sume
Three MCP changes in Claude Code 2.1.295 (Oct 8) and what each means for a connection to Sume's hosted server. A short table to run through after upgrading.
- Claude Code hooks on Sume tool calls: check the top-level guards
A Claude Code hook that gates paid Sume MCP calls should read the top-level idempotency_key, dry_run and max_spend_usd, and deny when input is unreadable.
- claude -p --max-budget-usd does not cap Sume renders
Claude Code's --max-budget-usd is a client-side estimate of API spend in print mode. Pair it with max_spend_usd on every paid Sume call and leave headroom.
Written by Sume