bckground builds developer tools for engineering resilient software. Tourbillon, our durable execution framework, gives developers the right tools to deliver business critical processes in any environment.
Tourbillon makes your code resilient to failures — without asking you to think about them. Write the happy path.
Open source · Self-hosted · Public beta Networks drop. Processes crash. Cloud providers go down. At scale, these aren't edge cases — they're certainties.
Conventional queues, cron jobs, and retried HTTP calls leave your business logic scattered across logs, dead-letter queues, and manual runbooks. Every failure becomes an incident.
You end up writing the same defensive boilerplate repeatedly: checkpoints, idempotency keys, state machines, retry logic, timeout handling. Infrastructure code that has nothing to do with your actual problem.
Tourbillon handles the primitives so your team can focus on what actually matters.
Infrastructure fails — your workflow doesn't. Tourbillon persists the result of every function call as it completes. On failure, it replays your code, skipping calls it already completed, and resumes from exactly where it left off.
Every function call result is persisted immediately. Your code is unchanged — it just works, whether it runs once or is replayed across different workers and deployments.
Define workflows as plain functions; no idempotency boilerplating. Tourbillon injects durability at the infrastructure layer, invisibly, with observability conventions built-in.
Tracing is built-in; no additional code is needed to get your telemetry from the start. OpenTelemetry traces and logs, for each operation in your workflow, are shipped to your existing observability platform.
Your executions are tied directly to the git revision it was triggered with; no surprises at runtime, even if your execution is long lived.
There's no SDK to install or keep in version lockstep - Tourbillon services are HTTP & gRPC native - reducing the overhead in integrating with your existing systems.
Define your workflow as a plain function. Tourbillon automatically persists every function call result — on failure, completed calls are skipped and execution resumes exactly where it left off. No retries to configure, no idempotency to reason about.
load("error", "codes")
load("fs", "fs")
load("http", "http")
load("json", "json")
load("openapi", "openapi")
def _prompt(client, model, content)!:
response = client.chat.create_chat_completion(
model = model,
messages = [{"role": "user", "content": content}],
)
if response.status_code != http.STATUS_OK:
return codes.INTERNAL(message = "prompt failed")
return response
def _content(body)!:
body = json.decode(body)
if len(body["choices"]) == 0:
return codes.INTERNAL(message = "No content")
reply = ""
for choice in body["choices"]:
reply += choice["message"]["content"]
return reply
def Poem(ctx, input)!:
client = openapi.client(
spec = fs.read_all("//openai/specs/openapi.yml"),
base_url = input.llm_url,
http_client = _http_client,
)
prompt = "Can you write me a poem about {topic}".format(topic = input.topic)
resp = try _prompt(client, input.model, prompt)
poem = try _content(resp.body)
pkg = proto.package("openai", contracts = ctx.contracts)
return pkg.PoemResponse(poem = poem) load("http", "http")
load("time", "time")
_client = http.client(timeout = time.SECOND * 30)
def BookTrip(ctx, input)!:
flight = try _reserve_flight(_client, input.flight)
errdefer cancel_flight()
hotel = try _reserve_hotel(_client, input.hotel)
errdefer cancel_hotel()
payment = try _charge(_client, input.payment, input.total)
errdefer cancel_payment()
pkg = proto.package("travel", contracts=ctx.contracts)
return pkg.Booking(
"flight_id": flight["id"],
"hotel_id": hotel["id"],
"transaction_id": payment["id"],
) load("error", "codes")
load("http", "http")
load("json", "json")
load("retry", "retry")
load("time", "time")
_retry = retry.with_max_retries(5, retry.with_capped_duration(
time.SECOND * 30,
retry.exponential(time.SECOND),
))
_client = http.client(
timeout = time.SECOND * 30,
retry_strategy = _retry,
)
def Checkout(ctx, input)!:
# Create the order in the Checkout service.
checkout_resp = _post_json(
_client, input.checkout_url,
{"customer_id": input.customer_id, "items": input.items},
)
if checkout_resp.status_code != http.STATUS_CREATED:
return codes.INTERNAL(message = "checkout failed")
order = json.decode(checkout_resp.body)
# Charge the customer via the Payments service.
payment_resp = _post_json(
_client, input.payments_url,
{"order_id": order["id"], "amount": order["total"],
"payment_method": input.payment_method},
)
if payment_resp.status_code != http.STATUS_OK:
return errors.INTERNAL(message = "payment failed")
payment = json.decode(payment_resp.body)
pkg = proto.package("checkout", contracts=ctx.contracts)
return pkg.CheckoutResponse(
"order_id": order["id"],
"payment_id": payment["id"],
) load("error", "codes")
load("http", "http")
load("json", "json")
_client = http.client(timeout = time.SECOND * 30)
def SyncOrders(ctx, input)!:
# Pull every order updated since the last successful run.
resp = _get_json(
_client,
input.source_url + "?since=" + input.cursor,
)
if resp.status_code != http.STATUS_OK:
return codes.INTERNAL_ERROR
page = json.decode(resp.body)
for order in page["orders"]:
row = _transform(order)
_submit_upsert(_client, "orders", row, key = "id")
pkg = proto.package("sync", contracts=ctx.contracts)
return pkg.SyncResult(
"synced": len(page["orders"]),
"next_cursor": page["cursor"],
) Everything you need to build durable workflows with Tourbillon — guides, API reference, and examples.