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 that Restate applies concurrency limits over, and that also namespaces the identity of everything inside it. 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.
- Idempotency is tracked per scope. The same idempotency key deduplicates only within one scope. Reused under a different scope, it starts a separate invocation.
- Virtual Object and Workflow keys are per scope. A
Cartobject with key"a"under scopebobis a different instance, with its own state and queue, fromCart"a"under scopejoe. (Routing scoped calls to Virtual Objects requires the extra opt-in.)
[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 *:
A rule only applies to invocations at its own depth: a
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.From an SDK handler
Service-to-service calls made from a handler can carry a scope and limit key too, so the invocations they trigger count against the same rule book. Each SDK exposes a scoped client for this: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:
These tables are populated only when flow control is enabled.
See introspection for how to query the system tables.