infra/ shared layer · not a domain with entities ├── *.py thin client, Redis pools (durable + cache) ├── worker/ SAQ app, task registration by lane ├── scheduler/ singleton cron service ├── rate_limit.py Lua window / counter primitive └── lifecycle.py heartbeat · retry · reaping helpers
achilles/infra/ — where it's natural
to import it from
(from achilles.infra.worker import …).
One backend image runs in different roles. The API scales
separately; workers are split across
three lanes with
different concurrency, and the scheduler and cache are separate
services.
| Service | Role | Lane / command | Replicas |
|---|---|---|---|
api |
HTTP · response streaming and the notification push channel (SSE) | uvicorn | ≥ 1, scales separately |
worker-interactive |
background tasks on the live-request path | SAQ · interactive | ≥ 1, moderate concurrency |
worker-background |
heavy deferred runs | SAQ · background | ≥ 1, high concurrency |
worker-agents |
autonomous agent runs | SAQ · agents | ≥ 1, bounded |
scheduler |
cron service · singleton | SAQ cron | exactly 1 |
redis-durable |
queues, task state, locks | Redis | 1 |
redis-cache |
ephemeral query cache | Redis | 1 |
Splitting queues and cache into two Redis instances — so that memory-based cache eviction doesn't touch the durable queues.