Pipelines & Worker — the pipeline axis
The sample proves the full loop: API creates a job document → worker polls → pipeline family runs → job reaches a terminal status.
The jobs slice
domain/jobs/ is an ordinary CRUD slice; its model is the durable state
machine the worker drives:
POST /jobs with {"name": "...", "kind": "appointment_reminder",
"payload": {"appointment_id": 1}} enqueues work; GET /jobs?status=...
inspects the queue.
The appointment_reminder family
Each stage is a small, independently testable unit. The loader
(pipelines/shared/loader.py) wires the root through hexa.build and merges
resources/patterns/*.yaml into the stage-injected registry:
To add a family: copy appointment_reminder/, replace the stages, call
register(kind, variant, root_cls) — see
Pipeline Scaffold.
The worker
worker.py is the process that turns
documents into execution:
- poll
job_svc.search({"status": "pending"})onWORKER_POLL_SECONDS - claim a job:
status → processing - dispatch through the
HANDLERStable —HANDLERS[job.kind](job, resources) - on success
status → done; on exceptionstatus → failed,retry_count += 1,error_messagecaptured; jobs atWORKER_MAX_RETRIESare skipped
The worker shares the composition root: from init import init gives it the
same wired services the API uses — one place to add a dependency.
Same process model in production
API and worker are separate processes (see docker-compose.yaml: api,
worker, mongo). Any in-memory progress bus is per-process; to surface
live progress through the API, swap the bus adaptor for a broker-backed
one — see Worker & Progress.
Tests
tests/unit/test_appointment_reminder_pipeline.py— stage units + the wired root against a fake service.tests/unit/test_worker.py— dispatch, done/failed transitions, retry exhaustion, using an in-memory job service double and a cancelled poll task.