Concepts
Spaces, runs, once, limits, slots, budgets, queues, gather, sleep and together, and what runstate guarantees for each.
runstate is a small set of coordination building blocks. Your agents call them; runstate stores the result durably and applies the same rules to every caller. This page defines the terms the guides use.
Mechanism, not policy
Section titled “Mechanism, not policy”runstate decides nothing about what your agents should do. It records and enforces the things they need to agree on: whether a piece of work already has a result, how many units of a limit are left, how many slots are free, whether a budget can cover a reservation, whether a run is still going. Prioritisation and what counts as a good result stay in your code.
That split is also why runstate works with any framework: you wrap the few functions where agents would otherwise conflict, and call them as before.
The vocabulary
Section titled “The vocabulary”| Word | Meaning |
|---|---|
| run | One execution of your swarm. Every call belongs to one. |
| once | Work done once per run, with the result shared. |
| limit | A shared rate quota: N units per window. |
| slots | At most N things at the same time. |
| budget | Money that work reserves before it starts. |
| queue | Work handed to worker processes. |
| gather | Fan work out and stop when enough is done. |
| sleep | A durable timer. |
| together | Wait until N workers arrive. |
A space is a project or environment inside your organization, for example production and staging. Every SDK client works in one space (RUNSTATE_SPACE_ID), and API keys are created per space. Plan limits are counted across the whole organization, so splitting work into more spaces never buys more capacity.
A run is one execution of your swarm: a research job, an eval batch, a nightly pipeline. Every call belongs to a run.
- Runs have names. Every process joins the run named by
RUNSTATE_RUN(defaultif unset), or the one you pick withrunstate.run(name). The first call creates the run; every other process with the same name joins it. onceresults and queue jobs belong to the run. When a run is stopped or finished, or its deadline passes, the next call with the same name starts a fresh run, with fresh results.- A run can have child runs (
runstate.child()). Stopping a run stops its children. - Stopping a run (
runstate.stop()) means nothing new starts anywhere in it. Running work sees its signal abort and can wind down.runstate.finish()marks a run as done.
You never handle run ids. The SDK joins runs by name.
once wraps a function so that, within a run, it runs once for the same arguments. The first caller runs it. Every other caller with the same arguments, in any process, waits and gets the same result. If the function fails, the next caller tries again, up to attempts. If the process running it dies, another caller takes over.
The arguments are hashed into a key, so the TypeScript and Python SDKs agree on it.
Limit and slots
Section titled “Limit and slots”- A limit is a shared quota of units per time window, for example 60 search calls per minute. Every worker draws from it. When the window is used up, callers wait in a fair queue on the server.
runstate.limit(name, fn)wraps a function;runstate.take(name)takes units without one. - Slots cap how many things run at the same time, for example five browsers. A caller holds a slot while its function runs and gives it back afterwards, also on error. If the process dies, the slot comes back when its lease lapses.
Budget
Section titled “Budget”A budget is an exact-decimal spend ceiling. runstate.budget(name, fn, { reserve, cost }) holds reserve before the call and doesn’t call the function if the budget can’t cover it. After the call it charges the real cost and releases the rest.
Queue, gather, sleep and together
Section titled “Queue, gather, sleep and together”- A queue hands work to worker processes. You define it once with a handler, import it on both sides, call it like a function from producers, and run
runstate.serve()in workers. Each job has one recorded result. - gather submits a list of inputs to a queue and returns once enough results are accepted, then cancels the rest.
- sleep is a durable timer in the run. A process that restarts waits only for the time that is left.
- together returns once a given number of workers have arrived under the same name.
Leases
Section titled “Leases”Work in progress is always leased: a once call, a slot and a queue job all expire unless the holder renews them. The SDKs renew automatically while your function runs.
When a lease lapses, the work becomes available again, so a crashed process can’t hold it forever. If a slow process comes back after someone else took its work over, runstate rejects its result, and the SDK raises LeaseLostError and aborts context().signal.
Workers
Section titled “Workers”Every process has a worker label, <hostname>-<pid> by default. It shows in the console next to the work the process holds. Set worker in the client options to use a stable name such as the pod name.
Guarantees and their boundaries
Section titled “Guarantees and their boundaries”runstate is precise about what it promises:
- One recorded result. A
oncecall and a queue job each end with exactly one result, even when several workers race. The work itself may run more than once if a process crashes after doing it but before recording the result, so external side effects should be safe to repeat. - Late owners are rejected. After a lease is lost, the old holder can’t record a result.
- Limits apply to work that goes through runstate. Limits, slots and budgets are a hard ceiling for every call your agents route through runstate. An agent that ignores runstate is not stopped by it.
- Stopping stops new work. It does not kill processes; running work sees the signal and can finish or give up.
- Waiting is fair. Waiting for limit units and slots happens in a queue on the server.
Everything else, such as process memory, prompts and model calls, stays in your code and your infrastructure.
Other languages
Section titled “Other languages”The SDKs are thin layers over the runstate HTTP API, which is available to any language. See the HTTP API reference. The API uses its own resource names; the SDKs and these guides use the words above.