archipellabs-runtime
An async runtime for distributing work over Redis — Playwright sessions, API calls, SSE streams, timed waits — without losing control of how many run at once.
These tasks spend most of their time waiting (on a network call, a browser, a timer), so you want many of them going at once. Two easy approaches both break:
- A new task per event: a spike starts thousands at once and falls over — too many browsers, too many open connections.
- One at a time: safe, but a single slow call holds up everything behind it. All that waiting happens in sequence instead of together.
A service is the middle ground: a fixed budget of workers (max_slots, say
20) sharing the work. Up to 20 run together — enough to overlap the waiting — and
never more. Each service has its own budget, so a slow batch of browser sessions
cannot hog the workers your API calls need.
Services talk to each other with three verbs and nothing else. No registry, no service discovery, no configuration tying them together: a producer in one process and a consumer in another agree because they share a string.
Distribution name
archipellabs-runtime; it imports asruntime.
Install
pip install archipellabs-runtime # requires Python 3.12+ and a Redis server
Quickstart
import os
from runtime import App, Service
pricing = Service("pricing", max_slots=20)
@pricing.action("pricing.quote") # one executant, returns a value
async def quote(ctx, params):
return {"total": round(19.99 * params["qty"], 2)}
@pricing.event("order.placed") # every subscriber gets a copy
async def note_order(ctx, params):
print(f"order {params['id']}")
@pricing.every("500ms") # runs on a schedule
async def tick(ctx):
total = await ctx.call("pricing.quote", qty=3)
print(f"quote: {total}")
app = App(redis=os.environ.get("REDIS_URL", "redis://localhost:6379/0"))
app.include(pricing)
app.start() # blocking; logs the topology first
The three verbs
| Cardinality | Reply | Caller | |
|---|---|---|---|
await ctx.call(action, **params) |
exactly one executant | the return value, awaited | coupled in time |
await ctx.dispatch(action, **params) |
exactly one executant | a task id | not coupled |
await ctx.emit(event, **params) |
every subscriber | none | not coupled |
Every message can carry a ttl — a deadline that travels with it, and that any
call or dispatch the handler makes inherits and can only narrow — and a
delay. Failures cross the wire as typed errors and are re-raised in the caller.
Actions can declare a pydantic model for their params and get validation before
the handler runs.
await ctx.call("pricing.quote", ttl="2s", qty=3)
await ctx.dispatch("report.build", delay="30s", month="2026-07")
await ctx.emit("order.placed", id="o1")
Prior art
The semantics are lifted from Moleculer — actions,
call, a propagated context, typed errors, distributed timeouts. What is not taken
is its protocol: no registry, no heartbeats, no discovery, because Redis Streams
already do that. dispatch is the one addition, where Moleculer folds decoupled
work into a balanced emit.
doc/index.md has the argument; doc/limits.md has what it costs.
Shared resources
A service's lifespan opens what its handlers share — a browser, a DB pool, an
API client — once at boot, and closes it on shutdown.
from contextlib import asynccontextmanager
@asynccontextmanager
async def store(config):
db = await connect(config["dsn"])
try:
yield {"db": db} # → ctx.resources["db"]
finally:
await db.close()
warehouse = Service("warehouse", max_slots=8, lifespan=store)
@warehouse.action("order.fulfil")
async def fulfil(ctx, params):
await ctx.resources["db"].fulfil(params["id"])
app.include(warehouse, config={"dsn": "postgres://…"})
Documentation
Full documentation is in doc/:
| concepts | services, the three verbs, choosing between them |
| messages | the envelope, correlation, deadlines, errors, params |
| internals | the keyspace, a message's life, what runs in a process |
| operations | budgets, runtime switches, deployment, App knobs |
| limits | what this deliberately does not do — read before deploying |
Runnable examples in examples/:
minimal— one service, one action, one producerrpc—call, typed errors, params validation, a ttl expiryevents— fan-out to independent subscribersjobs—dispatch+task_status, adelay, and a runtime switchorders— two processes, lifespans on both sidespoisson— a service that consumes and producesplaywright— a heavy, isolated cost profile
Upgrading from 0.2? The API is replaced — see CHANGELOG.md.
Deployment
The same App runs in one process, split by role, or scaled to N replicas with no
code change. App(namespace=...) isolates environments sharing one Redis;
include(enabled=False) leaves a service out of a process entirely, and runtime
switches
pause one that is mounted.
Development
uv sync --extra dev
uv run python -m pytest # auto-detects a backend
RUNTIME_TEST_BACKEND=fake uv run python -m pytest # CI runs both legs
RUNTIME_TEST_BACKEND=real uv run python -m pytest # needs a Redis on :6379
uv run python -m pytest --cov=runtime --cov-report=term-missing
uv run mypy && uv run ruff check .
License
MIT — see LICENSE.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file archipellabs_runtime-0.3.2.tar.gz.
File metadata
- Download URL: archipellabs_runtime-0.3.2.tar.gz
- Upload date:
- Size: 54.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
297736c5252c0b85f038ca84c9da305b2ad2130c80b0af5631711a2745f27676
|
|
| MD5 |
a6dc8708eea7a54f84b5f2e927d63cb0
|
|
| BLAKE2b-256 |
587244226545d9bfaa3226a5e97372422407000033cc863bb2c7fc79c52325cf
|
Provenance
The following attestation bundles were made for archipellabs_runtime-0.3.2.tar.gz:
Publisher:
publish.yml on archipellabs/runtime
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
archipellabs_runtime-0.3.2.tar.gz -
Subject digest:
297736c5252c0b85f038ca84c9da305b2ad2130c80b0af5631711a2745f27676 - Sigstore transparency entry: 2292144800
- Sigstore integration time:
-
Permalink:
archipellabs/runtime@dd71d21b5e223dbec915c253fc94d6824afa0572 -
Branch / Tag:
refs/tags/v0.3.2 - Owner: https://github.com/archipellabs
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@dd71d21b5e223dbec915c253fc94d6824afa0572 -
Trigger Event:
release
-
Statement type:
File details
Details for the file archipellabs_runtime-0.3.2-py3-none-any.whl.
File metadata
- Download URL: archipellabs_runtime-0.3.2-py3-none-any.whl
- Upload date:
- Size: 41.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
aa2d2bf606416f09567af1ea5a429dd2389cb174a40d707b11c03b7d45ccffdb
|
|
| MD5 |
cc1c1c291b3db0ab8e69d3123668becf
|
|
| BLAKE2b-256 |
62935a24727bc419310c60e888672a990ba3ed8e2099cd11146bf5c45b986990
|
Provenance
The following attestation bundles were made for archipellabs_runtime-0.3.2-py3-none-any.whl:
Publisher:
publish.yml on archipellabs/runtime
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
archipellabs_runtime-0.3.2-py3-none-any.whl -
Subject digest:
aa2d2bf606416f09567af1ea5a429dd2389cb174a40d707b11c03b7d45ccffdb - Sigstore transparency entry: 2292144834
- Sigstore integration time:
-
Permalink:
archipellabs/runtime@dd71d21b5e223dbec915c253fc94d6824afa0572 -
Branch / Tag:
refs/tags/v0.3.2 - Owner: https://github.com/archipellabs
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@dd71d21b5e223dbec915c253fc94d6824afa0572 -
Trigger Event:
release
-
Statement type: