Skip to content
Go To Dashboard

Runtime limits

These are the limits the cloud runtime enforces on a deployed agent today. Local runs (sapiom_dev_agents_run_local) behave differently; see Test locally.

LimitValueWhat happens when it is reached
Total run durationNo limit—
One step attempt15 minutes, or the step’s timeoutMsThe attempt fails and the step is retried
Attempts per step3, counting the firstThe run fails
Delay between attempts30 seconds, then 5 minutes; at most 10 minutesA longer retry({ delayMs }) is shortened to 10 minutes
One continuous unpaused stretch of a run4 hoursThe sandbox is shut down, stopping any work still running in it
Pause durationNo limit; the pause’s timeoutMs if setThe run fails. It does not resume
Concurrent runsNo per-organization limit—
Events API (POST /v1/workflows/events)300 per minute per tenant; 600 per minute per source IP429 with Retry-After
Unmatched events60 per minute per tenant429 with Retry-After
Webhook trigger URL120 per minute per hook; 600 per minute per source IP429 with Retry-After
Request bodyEvents API 256 KiB; webhook trigger 1 MiBRequest rejected

The sections below explain each limit.

A run has no overall time limit. A run that keeps moving between steps, or waits in a pause, can last as long as it needs to.

Each step attempt has a deadline. The default is 15 minutes. Set timeoutMs on the step to give it a different deadline:

const render = defineStep({
name: "render",
next: ["publish"],
timeoutMs: 30 * 60 * 1000, // 30 minutes
async run(input, ctx) {
// ...
},
});

When an attempt passes its deadline, it is recorded as a failed attempt and the step is retried. The runtime checks deadlines once a minute, so a timed-out attempt can be failed up to about a minute after its deadline.

A step runs in a sandbox. The sandbox is created with a 4-hour lifetime that is never extended, and the steps of a run reuse it until the run pauses. A pause releases the sandbox, and the resume starts a fresh one with a fresh 4 hours. Keep any continuous stretch of work between pauses under 4 hours. For longer work, launch it and pause until it finishes; see Long-running steps: pause & resume.

A step gets 3 attempts in total, counting the first. An attempt is used up when the step throws, times out, or returns retry(...). After the third failed attempt, the run fails.

Between attempts the runtime waits 30 seconds before the second attempt and 5 minutes before the third. A step that returns retry({ delayMs }) sets its own wait instead, capped at 10 minutes.

A pause has no maximum duration. A pauseUntilSignal(...) without timeoutMs waits until a matching signal arrives, for as long as that takes. A paused run releases its sandbox about a minute after pausing and uses no compute while it waits.

With timeoutMs, the pause records a deadline. If no matching signal arrives by then, the run fails with a pause-timeout error (PauseTimeoutError). The runtime does not resume the run, does not call the resume step, and does not supply a fallback payload. The check runs once a minute, so the failure lands up to about a minute after the deadline. A signal sent after the run has failed matches nothing and returns { "matched": 0 }.

Failed runs, including pause timeouts, emit sapiom.run.failed, so a watchdog agent can react to them.

Sapiom does not limit how many runs an organization has in progress at once. Runs from every tenant share the runtime’s worker capacity, so under heavy load a run can wait in a queue before its next step starts. Paused runs do not use worker capacity.

Event and webhook triggers start runs through the same shared queue. The ingress rate limits below bound how quickly one tenant or one hook can add to it.

Both ingress routes are rate limited over a one-minute window. A rejected request answers 429 with a Retry-After header in seconds. Back off for that long and resend with the same event id (events API) or the same X-Sapiom-Event-Id (webhooks); a repeat of an accepted delivery is deduplicated, so the retry is safe.

RouteLimitScope
POST /v1/workflows/events300 requests per minutePer tenant
60 unmatched events per minutePer tenant. Counts events that no active trigger subscribes to
600 requests per minutePer source IP
POST to a webhook trigger URL120 requests per minutePer hook URL
600 requests per minutePer source IP

The per-IP limit is checked first. Every process behind one NAT egress address shares it.

Body size limits: the events API accepts up to 256 KiB per request, and a webhook trigger URL accepts up to 1 MiB.

If the webhook rate limiter itself is unavailable, the hook URL answers 503 with Retry-After: 5 instead of accepting the delivery unchecked. Retry it the same way.

See Triggers for the request formats, signing, and receipts.