# Phase 6: Background Worker - Discussion Log

> **Audit trail only.** Do not use as input to planning, research, or execution agents.
> Decisions are captured in CONTEXT.md — this log preserves the alternatives considered.

**Date:** 2026-05-15
**Phase:** 6-background-worker
**Areas discussed:** Queue library, Proof-of-life job, Failed job surface, Job scheduling model

---

## Queue Library

| Option | Description | Selected |
|--------|-------------|----------|
| river (Postgres-native) | Postgres-native job queue, one Go dep, built-in retry/backoff | ✓ |
| Hand-rolled pg_notify | Zero new deps, full control, but owns retry/backoff yourself | |
| asynq (Redis) | Adds Redis to compose.yaml, richer UI but conflicts with Postgres-only thesis | |

**User's choice:** river

**Migration management:**

| Option | Description | Selected |
|--------|-------------|----------|
| rivermigrate at startup (programmatic) | Run rivermigrate before client starts; zero manual SQL | ✓ |
| Embed into goose migrations | Copy river SQL into 0006_river.sql; manual sync on upgrades | |

**User's choice:** rivermigrate at startup

**Scheduling scope:**

| Option | Description | Selected |
|--------|-------------|----------|
| Periodic only for Phase 6 | Prove wiring with scheduled jobs; web-side enqueueing deferred | ✓ |
| Both periodic + web-side enqueue | Wire river client into cmd/web for full flow | |

**User's choice:** Periodic only

**Notes:** User selected the recommended option for all three queue-library sub-decisions, confirming Postgres-only constraint alignment.

---

## Proof-of-Life Job

| Option | Description | Selected |
|--------|-------------|----------|
| Orphan-file cleanup | Finds and deletes tablo_files rows/S3 objects where tablo was deleted | |
| Heartbeat / noop | Logs on schedule; proves scheduler but no domain value | ✓ (initial) |
| Signed-URL prewarm | Pre-generates download URLs for recent files | |

**Follow-up (heartbeat only vs. heartbeat + cleanup):**

| Option | Description | Selected |
|--------|-------------|----------|
| Heartbeat only | Minimal Phase 6 | |
| Heartbeat + orphan-file cleanup | Both jobs; heartbeat proves scheduling, cleanup proves DB+S3 | ✓ |

**Intervals:**

| Option | Description | Selected |
|--------|-------------|----------|
| Cleanup: hourly; Heartbeat: every minute | Frequent heartbeat visibility, safe hourly cleanup | ✓ |
| Both every minute | More log noise, maximum dev observability | |

**User's choice:** Heartbeat (1 min) + orphan-file cleanup (1 hr)

**Notes:** User initially chose heartbeat-only for proof, then selected adding cleanup as a second job. Cleanup is the more meaningful domain job; heartbeat proves the periodic scheduler plumbing.

---

## Failed Job Surface

| Option | Description | Selected |
|--------|-------------|----------|
| CLI subcommand: backend list-failed-jobs | Queries river_jobs for failed rows; satisfies WORK-04 literally | |
| Structured logs only | Rich log fields on failure; redefines WORK-04 as log observability | ✓ |
| Admin HTTP route on worker | GET /admin/jobs/failed on separate port | |

**User's choice:** Structured logs only — WORK-04's "CLI surface" interpreted as log observability.

**Notes:** User rejected a follow-up push to add a CLI command alongside logs, then confirmed the logs-only decision on second presentation. Decision is deliberate.

---

## Job Scheduling Model

| Option | Description | Selected |
|--------|-------------|----------|
| Single river.Client with all periodic jobs at startup | One client, PeriodicJob registrations, block on shutdown | ✓ |
| Separate goroutines with time.Ticker | Hand-rolled scheduling outside river | |

**Concurrency model:**

| Option | Description | Selected |
|--------|-------------|----------|
| Document single-worker constraint | README notes: one worker only in v1 | ✓ |
| Rely on river's advisory locks | River prevents duplicate execution across instances | |

**User's choice:** Single river.Client; document single-worker constraint in README.

---

## Claude's Discretion

- Exact log fields emitted by heartbeat job (uptime, job count, etc.)
- Whether orphan-cleanup logs a per-run summary
- River client configuration (concurrency, max attempts, queue name)
- Orphan detection query pattern (LEFT JOIN vs NOT EXISTS)

## Deferred Ideas

- Web-side job enqueueing (river client in cmd/web) — future phase when event-triggered use case appears
- Multiple worker instances / leader election — after v1 ships
- Job admin UI or CLI subcommand — logs satisfy v1 observability needs
- Redis / asynq — explicitly out of scope (Postgres-only constraint)
