Background Work
Background tasks
Section titled “Background tasks”Use run_in_background() for fire-and-forget async tasks. There is no Celery and no Redis requirement; the task runs on the current event loop and exceptions are logged.
from stk.tasks import run_in_background
async def send_email(user_id): ...
await run_in_background(send_email(user.id))For tasks that need their own DB session, run_with_session opens one, commits on success, and rolls back on error:
from stk.tasks import run_with_session
async def cleanup_old_records(session): await session.execute(...)
await run_with_session(cleanup_old_records)Tasks live only as long as the process. Work that must survive a restart belongs in a table that a scheduled job drains, not in run_in_background.
uv run stk cleanup-sessions deactivates expired sessions and deletes rows older than 30 days. Run it from cron or a systemd timer.
WebSockets
Section titled “WebSockets”Signed-in pages connect to /ws automatically through layoutMixin. Push a message to one user with broadcast:
from stk.websocket import broadcast
await broadcast({"type": "notification", "title": "Export ready", "to": "/exports"}, user_id=user.id)There is no broadcast-to-everyone: every message names its recipient. {type: 'notification'} messages land in the notification bell; extend _onWsMessage in layout.html for your own types. Activity.register() stores audit events and does not broadcast.
The endpoint enforces:
- an
OriginmatchingSTK_PUBLIC_URL(or the request host), overridable withSTK_WS_ALLOWED_ORIGINS - a signed-in session, rechecked every
STK_WS_AUTH_INTERVALseconds (5), so logout or deactivation closes the socket - at most
STK_WS_CONNECTIONS_PER_USER(5) andSTK_WS_MAX_CONNECTIONS(1000) connections - at most
STK_WS_MESSAGE_LIMIT(60) client messages per minute, each up toSTK_WS_MESSAGE_SIZE(65536) bytes - a send queue of
STK_WS_QUEUE_SIZE(32); a client that falls behind is disconnected
These are app config values: set them on your Config class, not in .env.
Connected clients are tracked in process memory, so broadcast reaches only sockets held by the same worker. With several uvicorn workers, a message sent from one worker misses users connected to another. Run one worker, or fan out through Redis pub/sub if you need more.

