A durable, multi-worker task orchestration platform built on PostgreSQL. Tasks are submitted over HTTP, persisted in Postgres, and executed by workers that claim work concurrently. The worker can route prompts to local or cloud Ollama models, records execution cost and duration, and exposes operational metrics through the API.
The design bet is that Postgres is the coordinator. Claiming,
deduplication, crash recovery, and poison-task protection are all expressed as
SQL against a single tasks table using row locks, constraints, leases, and
bounded counters. No Redis, no message broker, no separate lock service.
- Durable state machine: task state (
PENDING,RUNNING,SUCCEEDED,FAILED) lives in Postgres, so a process crash does not lose queued work. - Concurrent claiming: workers use
SELECT ... FOR UPDATE SKIP LOCKEDto fan out across pending tasks without claiming the same row. - Idempotent submission: the optional
Idempotency-Keyheader deduplicates retried submissions via a unique constraint andON CONFLICT. - Retries with exponential backoff: transient execution failures are
retried up to
retry.max_retry, using2 ** retry_countseconds of backoff. - Crash recovery: running tasks hold a lease renewed by a heartbeat thread;
the reaper returns stale
RUNNINGtasks toPENDING. - Poison-task protection:
delivery_countlimits how many times a task can be handed to workers, even if workers keep crashing mid-task. - Adaptive routing: workers choose
localorcloudexecution based on prompt length and queue depth, then persist the selected route. - Cost and duration metrics: completed tasks store backend cost and
execution duration;
/metricssummarizes queue state, retries, reclaimed work, duration, and cost by route.
- Python
- FastAPI
- PostgreSQL
- psycopg2
- Ollama-compatible generation API
- TOML configuration
| Path | Responsibility |
|---|---|
main.py |
FastAPI app with task submission, task lookup, and metrics endpoints |
scheduler.py |
Worker loop: reclaim stale work, claim, route, execute, heartbeat, retry |
db.py |
Postgres connection handling, task submission, status lookup, metrics queries |
config.py |
Loads runtime settings from config.toml |
config.toml |
Retry limits, routing thresholds, model names, cost rates, Ollama URL |
schema.sql |
tasks table and hot-path index definition |
DESIGN.md |
Architecture notes and design tradeoffs |
DEBUGGING_NOTES.md |
Debugging record for concurrency and timing issues |
docs/architecture.png |
Architecture diagram |
Connection parameters are hardcoded for local development; the password comes from the DB_PASSWORD env var (defaults to devpass) — secrets stay out of code and config files.
- Host:
localhost - Port:
5432 - Database:
orchestrator - User:
postgres - Password:
DB_PASSWORD, defaulting todevpass
Application settings live in config.toml:
[retry]
max_retry = 3
max_delivery = 3
[routing]
prompt_threshold = 100
pending_threshold = 5
[models]
local = "llama3.2:latest"
cloud = "qwen3:14b"
[cost]
local_per_token = "0.00001"
cloud_per_token = "0.0001"
[ollama]
url = "http://localhost:11434"
timeout_seconds = 60Routing rules:
- Prompts longer than
routing.prompt_thresholduse thecloudmodel. - If pending queue depth is above
routing.pending_threshold, new work uses thecloudmodel. - Otherwise, work uses the
localmodel.
Start Postgres (schema is created automatically on first run):
docker compose up -dMake sure Ollama is running and the configured models are available:
ollama pull llama3.2:latest
ollama pull qwen3:14bInstall Python dependencies in your preferred environment:
pip install -r requirements.txtStart the API:
uvicorn main:app --reloadStart a worker in another shell:
python scheduler.pySubmit a task:
curl -X POST http://localhost:8000/tasks \
-H "Idempotency-Key: demo-1" \
-H "Content-Type: application/json" \
-d '{"prompt": "Write one sentence about durable queues."}'Check task status:
curl http://localhost:8000/tasks/1Check metrics:
curl http://localhost:8000/metricspython -m pytestUnit tests cover decide_route — both routing signals, threshold boundaries, and defensive handling of missing keys.
Submits a prompt task.
Request body:
{
"prompt": "hello"
}Optional header:
Idempotency-Key: demo-1Response:
{
"task_id": 1,
"status": "PENDING"
}Returns task status and result when available.
Response:
{
"task_id": 1,
"status": "SUCCEEDED",
"result": {
"text": "..."
}
}Missing tasks currently return:
{
"error": "not found"
}Returns aggregate operational metrics.
Example shape:
{
"by_status": {
"PENDING": 2,
"SUCCEEDED": 10
},
"avg_retry": 0.2,
"reclaimed_count": 1,
"avg_duration_seconds": 3.41,
"cost_by_route": {
"local": "0.15818000",
"cloud": "0.03400000"
}
}./demo.shThe script submits a long task, kills the worker mid-execution (SIGKILL), waits for the lease to expire (~35s), starts a fresh worker, and shows the final state: delivery_count=2, retry_count=0 — the task was delivered twice but never failed. A worker crash is redelivery, not task failure.
- Submit a task that takes long enough to run through the worker.
- Kill the worker process while the task is
RUNNING. - Watch the heartbeat stop renewing
lease_expires_at. - After the lease expires, the reaper returns the task to
PENDING. - Start a fresh worker and confirm it reclaims and completes the task.
This demonstrates the lease + heartbeat + reaper recovery loop. The system is at-least-once: a recovered task may execute again, so task effects should be idempotent when the execution side effect matters.
Read DESIGN.md for the deeper architecture discussion:
- why Postgres is used as the coordinator,
- how
SKIP LOCKEDenables concurrent workers, - how idempotency is enforced,
- why
retry_countanddelivery_countare separate, - how leases and heartbeats recover crashed workers,
- and what limitations remain in this iteration.
