Flow control is an opt-in feature and is disabled by default.
Its configuration and APIs may change in future releases.
Why flow control
Flow control gives you a lever over concurrent work, which helps with:- Cost control: Cap how much expensive work runs at once. This is especially valuable for AI agents, where each concurrent invocation can translate directly into model or API spend. A concurrency limit puts a ceiling on that cost.
- Endpoint protection: Keep a burst of invocations from overwhelming a downstream service, database, or third-party API by bounding how many hit it concurrently.
- Fairness: Invocations flow through a scheduler that decides who goes next, so Restate ensures fairness between invocations running on the same partition.
What Restate supports today
Restate’s flow control primitives are built on a scheduler that decides which invocation runs next. The first capability built on this scheduler is concurrency limits: the maximum number of invocations that may run concurrently for a given scope. More flow-control capabilities will follow in later releases, all expressed through the same scope-based model. Planned follow-ups include throttling and rate limits, invocation priorities, and finite queue (backlog) limits.Scopes
A scope is a namespace for concurrency control. Every invocation can carry a scope, and concurrency limits are applied per scope: all invocations sharing the same scope draw from the same concurrency budget. You choose what a scope represents. For example, you might scope by:- A tenant or customer, to give each one a fair share of capacity.
- A downstream dependency, to bound how many invocations hit it at once.
- A class of work, such as
checkoutorai-agent, to cap how much of it runs concurrently.
[a-zA-Z0-9_.-], non-empty, and at most 36 characters long.
Limit keys
Within a scope, a limit key gives you a finer, hierarchical level of concurrency control. Where a scope is a single namespace, a limit key subdivides that namespace into up to two nested levels, so you can cap concurrency per subgroup without creating a separate scope for each one. A limit key has one or two levels separated by/:
tenant1targets a single level (L1) under the scope.tenant1/user42targets two levels (L1 and L2) under the scope.
tenant1/user42 invocation draws from the scope budget, the tenant1 (L1) budget, and the tenant1/user42 (L2) budget simultaneously, and is admitted only once all of them have a free slot.
The effective limit is therefore the strictest of the matching rules.
A limit key always requires a scope. Restate rejects an invocation that carries a limit key but no scope.
A limit key only influences concurrency. It is not part of an invocation’s identity: two calls to the same target with the same scope and object key but different limit keys still address the same resource instance (for example, the same Virtual Object). The limit key never routes to a different object.
[a-zA-Z0-9_.-], non-empty, and at most 36 characters long.
Enabling flow control
Flow control is disabled by default. Enable it in your server configuration:restate.toml
Configuring concurrency limits
Concurrency limits are defined in a cluster-wide rule book. A rule pairs a pattern, which selects the scopes it applies to, with a set of limits. The only limit available today isconcurrency: the maximum number of invocations that may run concurrently for a matching scope.
A pattern is a /-separated path that mirrors the scope and limit key hierarchy: scope, scope/l1, or scope/l1/l2.
Each component is either an exact value or the wildcard *:
| Pattern | Matches |
|---|---|
* | Any scope. Acts as a default for scopes without their own rule. |
checkout | One specific scope. |
checkout/* | Any L1 limit key under the checkout scope. |
checkout/premium | The L1 limit key premium under checkout. |
checkout/premium/* | Any L2 limit key under checkout/premium. |
*/premium | The L1 limit key premium under any scope. |
scope/l1 rule limits the L1 counter, and a scope/l1/l2 rule limits the L2 counter.
An invocation carrying a two-level limit key is checked against all three levels at once, each against its own most specific matching rule.
When several patterns match the same level, the most specific one wins.
An exact component beats a wildcard, and specificity is ranked from the scope down: scope first, then L1, then L2.
So checkout/premium takes precedence over checkout/*, which takes precedence over */premium.
Manage rules dynamically with the restate rules CLI commands.
set is idempotent: it creates a rule if it doesn’t exist, or merges into the existing values, preserving fields you don’t touch.
restate rules --help for the full set of options.
Applying concurrency limits
To make an invocation count against a scope’s concurrency limit, send it through a scoped ingress endpoint under the reserved/restate/scope/ prefix:
{key} segment for Virtual Objects and Workflows; omit it for basic Services.
For example, to invoke checkout of OrderService under the checkout scope:
concurrency and held in their queue until a slot frees up.
Adding a limit key
To also place an invocation under a limit key, pass it with thelimit-key query parameter or the x-restate-limit-key header.
Separate the two levels with /:
scope/{scopeKey} segment is rejected.
Invocations sent through the non-scoped endpoints (
/restate/call/... and /restate/send/...) are not subject to any scope-based limit.
See HTTP invocation for the full set of ingress endpoints.Example: a multi-tenant inference platform
Say the scope is an organization, L1 is a team, and L2 is a user. One rule book covers every organization through wildcards:team/user as the limit key:
- acme (scope): 10000, shared by the whole organization.
- growth (L1): 1000, shared by everyone on the team.
- alice (L2): 10, hers alone.
admins team instead (limit-key=admins/alice), the L1 budget would be unlimited, but she would still be capped at 10 by the */*/* rule. If we want individual admins to have higher limits (say 100), we can add a rule on */admins/*.
Observing flow control
When flow control is enabled, several SQL system tables let you inspect the scheduler, queues, and concurrency limits directly:| Table | What it shows |
|---|---|
sys_rules | The configured rule book: one row per rule with its pattern, concurrency limit, description, disabled flag, version, and last-modified time. |
sys_user_limits | Concurrency counters at every level (scope, L1, and L2): the scope and limit-key components, the hierarchy level, current usage, configured limit, available capacity, and the matching rule pattern. |
sys_vqueues | One row per entry across all queue stages, with its status, attempt counters, and lifecycle timestamps. |
sys_vqueue_meta | Aggregate statistics per queue: scope, service name, per-stage entry counts, and timing averages. |
sys_scheduler | Real-time scheduler state for each queue’s head entry: queue depth, scheduler status, and what it is blocked on. |