Resonate http service design python
Skill resonatehq/resonate-skills/resonate-http-service-design-python
Agent skills for building with Resonate — durable execution for long-running, crash-safe workflows.
npx -y skills add resonatehq/resonate-skills --skill resonate-http-service-design-pythonAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 5 stars5 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Design HTTP services in Python that use Resonate durable functions behind FastAPI / Flask / Django route handlers — routing patterns, workflow boundaries, RPC to specialized worker groups, webhook-driven promise resolution. Use when building or refactoring Python HTTP APIs that trigger durable workflows.
The file declares its own license as Apache-2.0. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.
SKILL.md
9.6 KB, as published. Nobody here has run it
Resonate HTTP Service Design — Python
Overview
Route handlers in a Python web framework (FastAPI, Flask, Django) are the entrypoint for durable workflows; they start or await invocations via the Resonate client. Actual business logic runs in worker processes that register durable functions. Downstream services (a DB worker, a search worker) expose their own durable functions and are invoked via RPC.
This skill covers the shape of that split: what runs where, how routes interact with workers, and how webhooks resolve durable promises.
Architecture model
client → HTTP routes (FastAPI/Flask)
→ Resonate client (r.run / r.rpc / r.promises.resolve)
→ worker group A (business logic)
→ ctx.rpc → worker group B (specialized, e.g. DB)
- HTTP server: FastAPI/Flask/Django app handling routes; maps HTTP requests to Resonate invocations
- Worker service: a process (same or different host) with
r.register(...)-ed functions; connects to the Resonate server - Resonate server: durable promise store + coordination hub (Rust v0.9.x, single port 8001)
Rules
- Route handlers are ephemeral. Use Client APIs (
r.run,r.rpc,r.promises.*). Never use Context APIs in a route handler. - Durable functions run in workers.
async defwithawait ctx.run/rpc+r.register(fn). - All external effects (DB, HTTP, filesystem) run inside
ctx.runenvelopes — never at the top of a durable function, never at module import time. - Stable invocation IDs. Derive from request inputs (
f"order:{order_id}") — notuuid.uuid4()at route-handler time unless you persist the ID in the response so the client can poll.
Route design patterns
1. Submit-and-poll (async HTTP)
Client submits a job, gets an ID, polls for status:
from __future__ import annotations
import os, time
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from resonate.resonate import Resonate
app = FastAPI()
r = Resonate(url=os.environ.get("RESONATE_URL", "http://localhost:8001"))
class JobSubmission(BaseModel):
order_id: str
items: list[str]
@app.post("/jobs")
async def submit_job(body: JobSubmission) -> dict:
job_id = f"order:{body.order_id}"
# Fire and forget — workflow runs durably in the background
r.options(target="order-workers").rpc(job_id, "process_order", body.order_id, body.items)
return {"job_id": job_id, "status": "started"}
@app.get("/jobs/{job_id}")
async def get_job(job_id: str) -> dict:
try:
record = await r.promises.get(job_id)
state = record.state if hasattr(record, "state") else "pending"
if state == "resolved":
return {"job_id": job_id, "status": "done", "result": record.value}
elif state == "rejected":
return {"job_id": job_id, "status": "failed", "reason": record.value}
else:
return {"job_id": job_id, "status": "running"}
except Exception:
raise HTTPException(status_code=404, detail="job not found")
2. Submit-and-callback (webhook resolves promise)
Client submits; workflow blocks on ctx.promise(); external system resolves via webhook:
from resonate.types import Value
# Worker
async def approve_then_ship(ctx, order_id: str) -> dict:
approval = ctx.promise()
approval_id = await approval.id()
await ctx.run(notify_approver, order_id, approval_id)
decision = await approval
if not decision.get("approved"):
return {"status": "rejected"}
await ctx.run(ship_order, order_id)
return {"status": "shipped"}
# Route — start the workflow
@app.post("/orders/{order_id}/approve-and-ship")
async def start(order_id: str) -> dict:
r.options(target="order-workers").rpc(
f"order:{order_id}", "approve_then_ship", order_id
)
return {"status": "pending_approval"}
# Route — webhook resolves the durable promise
@app.post("/webhooks/approval/{order_id}")
async def approval_webhook(order_id: str, payload: dict) -> dict:
await r.promises.resolve(
f"approval:{order_id}",
Value(data={"approved": payload.get("approved", False)}),
)
return {"ok": True}
3. Synchronous HTTP wrapping a fast workflow
If the workflow completes quickly, block on it and return the result directly:
@app.post("/price-quote")
async def price_quote(body: QuoteRequest) -> dict:
quote_id = f"quote:{body.sku}:{body.qty}"
result = await r.options(target="pricing-workers").rpc(
quote_id,
"compute_quote",
body.sku,
body.qty,
).result()
return {"quote_id": quote_id, "price": result["price"]}
r.rpc(...) returns a handle; calling .result() and awaiting it blocks the route handler. Keep timeouts tight — a slow worker holds the HTTP request open.
Worker-side lifecycle
A worker process:
- Instantiates
Resonate(url=..., group=...) - Registers its durable functions
- Stays alive (via its own event loop or a
while Truesleep)
# workers/order_worker.py
from __future__ import annotations
import asyncio, os
from typing import TYPE_CHECKING
from resonate.resonate import Resonate
if TYPE_CHECKING:
from resonate.context import Context
r = Resonate(url=os.environ.get("RESONATE_URL", "http://localhost:8001"), group="order-workers")
async def process_order(ctx: Context, order_id: str, items: list[str]) -> dict:
await ctx.run(validate_order, order_id, items)
await ctx.run(charge_payment, order_id)
await ctx.run(create_shipment, order_id)
return {"order_id": order_id, "status": "done"}
r.register(process_order)
if __name__ == "__main__":
async def main() -> None:
try:
# Block until Ctrl-C; SDK long-polls for work
await asyncio.Event().wait()
finally:
await r.stop()
asyncio.run(main())
Separating worker groups
For larger services, split specialization across worker groups:
# db worker group
r_db = Resonate(url=url, group="db")
async def query_orders(ctx: Context, user_id: str) -> list:
db = ctx.get_dependency(psycopg.Connection)
return db.execute("SELECT * FROM orders WHERE user_id = %s", (user_id,)).fetchall()
r_db.register(query_orders)
# business worker group
r_biz = Resonate(url=url, group="business")
async def place_order(ctx: Context, user_id: str, cart: list[str]) -> dict:
# Reach into the DB group via RPC
history = await ctx.options(target="db").rpc("query_orders", user_id)
# ... business logic ...
return {"user_id": user_id, "order_id": "..."}
r_biz.register(place_order)
Each group runs in its own process (or cluster of processes). RPC routes through Resonate's promise store with the server handling fair dispatch.
Combining FastAPI + in-process worker
Small services often put the HTTP server and the worker in the same process:
from __future__ import annotations
import asyncio, os
from fastapi import FastAPI
from resonate.resonate import Resonate
app = FastAPI()
r = Resonate(url=os.environ.get("RESONATE_URL", "http://localhost:8001"))
async def process_order(ctx, order_id: str) -> dict:
await ctx.run(charge_payment, order_id)
return {"order_id": order_id, "status": "done"}
r.register(process_order)
@app.post("/orders/{order_id}")
async def start(order_id: str) -> dict:
r.run(f"order:{order_id}", process_order, order_id)
return {"status": "started"}
@app.on_event("shutdown")
async def shutdown() -> None:
await r.stop()
Because r.register registers in the same process, both RPC-routing and in-process r.run(...) work. For higher throughput, move workers to dedicated processes.
Distinct Python idioms
- FastAPI is the leading modern web framework; patterns use FastAPI
async defroute handlers. - Pydantic models (
BaseModel) for request bodies — idiomatic + validates at parse time. r.run(...)is SYNC — it returns a handle immediately; the actual async work is awaitinghandle.result(). You can fire-and-forget by not awaiting the handle.r.promises.*are allasync— alwaysawaitthem in async route handlers.Value(data=...)wraps the payload forr.promises.resolve/reject.
Common gotchas
- Don't use Context APIs in a route handler.
ctxonly exists inside ar.register()-ed function body. A route handler gets aResonateclient, not aContext. - Don't rely on FastAPI request lifecycle for workflow timing. A durable function can outlive an HTTP request by hours or days. Return a job ID and let the client poll.
- Always
await r.stop()on shutdown. Register a shutdown handler (@app.on_event("shutdown")) to drain in-flight work gracefully. - Don't forget
target. When the route handler callsr.rpc(...), specify.options(target="group-name")so the invocation routes to a worker group.
Related skills
resonate-basic-ephemeral-world-usage-python—Resonate,r.run,r.rpc,r.options(target=), promise managementresonate-basic-durable-world-usage-python—r.register,ctx.run,ctx.rpc,ctx.promiseresonate-human-in-the-loop-pattern-python— webhook-driven promise resolution (pattern 2 in this skill, expanded)resonate-saga-pattern-python— multi-step workflows suitable for pattern 3 (sync HTTP)
Gives 0 of the 12 instructions most design frontend skills give
Counted across 1,170 of the 1,878 authors here whose files we hold, read 2026-08-06
- use css variables for color consistencyin 73 of 1170, across 24 files
- match implementation complexity to the aesthetic visionin 70 of 1170, across 20 files
- commit to one bold aesthetic direction before codingin 70 of 1170, across 25 files
- add atmospheric background effects and texturesin 58 of 1170, across 10 files
- use unexpected spatial compositions and layoutsin 55 of 1170, across 7 files
- implement real working codein 55 of 1170, across 7 files
- vary themes and aesthetics across different designsin 48 of 1170, across 7 files
- launch chromium in headless modein 47 of 1170, across 4 files
- close the browser when donein 47 of 1170, across 4 files
- run provided scripts with help flag firstin 47 of 1170, across 4 files
- use descriptive selectors for elementsin 47 of 1170, across 4 files
- wait for network idle statein 46 of 1170, across 3 files
Said here and by no other author read
- run external effects inside ctx.run envelopes
- derive durable invocation ids from request inputs
- await r.promises methods in route handlers
- call await r.stop on shutdown
- specify target when calling r.rpc in routes
- keep timeouts tight for synchronous routes
Grouped from the skills themselves: near-identical wordings counted once, and counted by distinct author, so one author publishing three of these counts once.