---
description: "Stream AW4: the live collectors page rebuilt as a control room (start, pause, resume and stop as page actions on a cooperative control field the loop reads between requests, a heartbeat per request, the pace per source floored at the host's robots crawl delay, discover and recheck modes, percent and ETA from counters on the run row, container memory and per-collector RSS, the opening hours collectors in their own group, one line per collector with ticking numbers, and the W2 live test plan)"
---

You are running **Stream AW4** of the Duty Free Professor away run of 19 to 20 September 2026, as
an autonomous session. The design is in the brief (adopted from the design panel in AW4.1); do not
redesign it. Where the brief is silent, choose the option that keeps the collector slower and the
page honest (a stalled run says stalled), and write the choice in your handoff.

**Start by checking your ground.** `pwd` must be
`/srv/apps/dutyfreeprofessor/.claude/worktrees/collectors-live-page-plan-8951e2` on branch
`claude/collectors-live-page-plan-8951e2`; `git log --oneline -1` must show `05941b4` or later.
If either is wrong, stop and say so. `.venv-dev` and `main/web/node_modules` are symlinked here.

Read first, in this order: your brief `.logs/planning/streams/AW4-live-page.md`, the away plan
`.logs/planning/streams/AWAY-2026-09-19.md` (sections 1, 2, 4 and 7 bind you),
`.logs/planning/streams/OVERNIGHT-RULES.md`, `agents.md`, `brief.md`, then the docs the tasks
need: `main/docs/RUNBOOK.md` (Collecting, Deploy, Migrations, the dev database),
`main/docs/COLLECTORS.md` (How collection works, Robots policy), `main/docs/ACCOUNTS.md`
(Permissions and levels, The route policy), `main/docs/VOCABULARY.md`,
`/srv/projects/standards/react.md` and `frontend.md`. Then the code the brief names:
`services/ingest.py` (`run_collector`, `held_listing_refs`, `is_stuck`, `process_boot`),
`collectors/base.py` (the skip sink), `collectors/fetch.py` (`fetch`, `render`, `render_wait`),
`collectors/robots.py` (`check_allowed`, `delay_for`), `collector_view.py` (`live`, `_story`),
`routers/collectors.py`, `routers/sources.py`, `services/access.py` (`PERMISSION`), `cli.py`
(`cmd_collect`, `cmd_status`, `backfill stuck_runs`), `services/hours/registry.py` and
`catalog_queries.airport_hours_out`, `web/src/pages/CollectorsPage.tsx` (`LiveTab`,
`useLiveJson`), `.logs/runs/sweep-2026-09-19b/{admit-queue,guard,finish}.sh`, and the tests that
pin them (`tests/test_writer.py`, `test_collector_view.py`, `test_route_inventory.py`).

Then work the tasks in order, AW4.1 to AW4.5, one at a time, each verified, committed with the
`AW4:` prefix. Register the tasks on `/plan` first, one per task:

```
python3 main/scripts/plan-set.py AW4.1 todo "Stream AW4" --add --stream AW4 --stream-title "AW4 · The live collectors page" --title "Adopt the recorded design"
python3 main/scripts/plan-set.py AW4.2 todo "Stream AW4" --add --stream AW4 --title "The control plane: migration, the hook, pace, the routes"
python3 main/scripts/plan-set.py AW4.3 todo "Stream AW4" --add --stream AW4 --title "Progress, counters, modes, memory, hours: the live read"
python3 main/scripts/plan-set.py AW4.4 todo "Stream AW4" --add --stream AW4 --title "The page"
python3 main/scripts/plan-set.py AW4.5 todo "Stream AW4" --add --stream AW4 --title "The W2 verification plan, and the documents"
```

then `python3 main/scripts/plan-set.py AW4.n doing "Stream AW4"` when you start one and
`python3 main/scripts/plan-set.py AW4.n done "Stream AW4" --note "<what proves it>"` when it is
verified. Rehearse the migration (up, down, up) on `dfp-devdb` (`127.0.0.1:5433`) holding a fresh
restore of the newest dump in `backups/`, and report what that copy produced. A bug you fix
becomes a test in `main/tests/` written as the real record that failed, with what it cost in the
docstring. `main/check.sh` must print "checks passed" before each commit; one task, one commit.

**Hard boundaries beyond the usual.** You never deploy and never write to staging's or production's
database: the migration rides with deploy D3, which the orchestrator runs; no backfill follows it.
You never run a collection: window W2 is the orchestrator's, after D3, from the plan you write in
AW4.5; the only network you may use is one `robots.txt` read per host to test code. The
screenshots run against the SPA dev server and a local API on a restored copy with seeded run
rows, never against staging. You never edit the collectors' parsing or `registry.py`, `keying.py`,
`normalize.py`, `merges.py`, `identity.py`, `decisions/*`, `services/hours/*`,
`routers/sources.py`, `docker-compose.yml`, `.env`, `.app.env`, `main/check.sh`, the vendored
packs, `test_route_inventory.py` or `test_access.py`. A GET never writes; every write is a POST
that audits the actor; the pace can never go below the host's robots crawl delay; a refused
source is never started from the page (`SOURCE_REFUSED` is final; only a recorded decision by
rian lifts it). Never a bare "product" in code, labels or prose: product
variant, product line, brand, listing. Client-facing copy has no em dashes and never the words
"cheap" or "free". Import a component's stylesheet in the component.

Tell rian as you go with `srv-gw notify --from "AW4" "<one line>"`: on start (`AW4 starting: 5
tasks, the live collectors page`), after each task with the count (`AW4.2 done (2/5): ...`), on
blocked, and at hand-back. Never put a secret or a tokened URL in a message. If the command fails,
note it in the handoff and carry on.

When you are blocked on something only rian can decide, do not stop: add it to the running list
(`python3 main/scripts/items.py add --kind decide --by "Stream AW4" --blocks AW4.n --weight
costly|blocking --title ... --detail ... --option ... --assumption ...`) with the options, the
assumption you are proceeding under, and what you would have to redo, then carry on with
everything that does not depend on it. The brief's "Questions for rian" are already assumed; raise
them as items only if the work turns one critical.

If a task turns out to need more than about three hours, split it, record the split with a note,
and keep the part that lands. When the brief is done, prepend the handoff entry to
`.logs/handoff.md` (25 lines or fewer, state not summary: the migration id, the six routes and
their codes, the W2 plan's path and its first command, the freeze marker's path, what the
screenshots showed at 1440 and 390, what you need from rian), add the CHANGELOG line, run the
project `/checkpoint`, send the hand-back line, and stop. The orchestrator deploys and opens W2;
you do not.

```
/goal Stream AW4 is complete: every task AW4.1 to AW4.5 is done on /plan, or blocked with a note saying why; main/check.sh has printed "checks passed" on the final tree; every finished task has its own commit prefixed "AW4:"; the migration was rehearsed up, down, up on dfp-devdb, its down_revision is the head alembic heads printed before it was written, alembic heads prints exactly one head on the final tree, and its id is in the handoff; POST /api/collectors/{slug}/start, pause, resume, stop, pace and mode exist under sources.manage and GET /api/collectors/live returns the typed LiveOut with memory, per-collector state, counters, percent, ETA, pace with its floor, and the hours group; .logs/runs/window-2026-09-19-w2-live-page.md holds the W2 sequence; nothing was deployed, staging's database was never written, no collection was run. Or stop after 120 turns.
```
