Skip to content

Queue work for the desk and retire the paid API jobs - #500

Merged
JesperDramsch merged 8 commits into
mainfrom
ai-agent/exciting-tesla-g7fn0r-4
Oct 15, 2026
Merged

JesperDramsch merged 8 commits into
mainfrom
ai-agent/exciting-tesla-g7fn0r-4

Conversation

@JesperDramsch

@JesperDramsch JesperDramsch commented Oct 14, 2026 •

Copy link
Copy Markdown
Owner

Fourth PR in the desk stack, now based on main (#499 is merged). GitHub Actions stops calling a paid API: the jobs that used one either collect work for the desk into a queue or keep only their deterministic part. After this PR, no workflow references ANTHROPIC_API_KEY.

The desk queue

utils/desk_queue.py reads and writes the queue on an orphan desk-queue branch, which is never merged. There is one JSON file per series under queue/, keyed by the slug of the series name, and a README.md that init writes from the module, so the format is documented in one place. Each item gets a stable id from its source, kind, URL and payload, so the same finding queued twice is one item. When the desk removes an item, its id moves to the file's handled list. A writer that finds the same thing again (the import asks the same standing questions on every run) then skips it, while a finding that has changed gets a new id. A file keeps its 50 newest items and 500 handled ids, and payload strings are cut at 20,000 characters and flagged. A broken file is moved to .bad, so it can't stop the writers. The module uses only the standard library, so any job can run it.

.github/actions/desk-queue-write is the one place that writes to the queue. It creates the branch when it is missing, adds records with desk_queue.py add --jsonl, and retries a rejected push from a fresh copy of the branch, never with force. I tested it against a local remote: creating the branch, a repeat (no-op), an append, a push race (rejected once, then landed on top), a rejected record (a workflow warning, while the valid one still lands), and a re-add after remove (reported as handled).

Writers

  • check-conference-update.yml turns a changedetection.io event into one queue record, with no triage in front. The payload is read from the event file, so page text never reaches the shell, and a long diff can't hit the environment size limit. The old pipeline (Haiku triage, two agent jobs, apply-filters) goes. Over 254 events the triage had 38% precision and 58% recall.
  • canonical-source-update.yml: the import stays deterministic. In CI, query_yes_no still takes the default answer. When the caller passes context, it also records the question: conference, year, field, both values and the source. With DESK_QUEUE_OUT set, the record goes to the queue, where the enricher answers it from the live site. The JSON-lines writer is shared with the link checker in tidy_conf/queue_out.py. A queue problem can't hold back the data merge (continue-on-error). The job also gets actions: write, which its existing gh workflow run call to start the enrichment always needed.
  • redirect-scan.yml (new, Sundays) runs the link check in report-only mode. It queues every link that now ends on another site, the case lychee can't tell apart from a healthy 200.

Retired

  • scheduled-extension-checker.yml: the worklist checks every deadline 1, 3 and 7 days after it passes.
  • The full mode of utils/enrich_tba.py and its workflow input. The deterministic link extraction stays, along with the code only it uses. The confidence handling, which only the API results could reach, is removed. Review items now end in "(kept existing)".
  • The remaining link extraction no longer visits cancelled editions (7d6c428), so it can't fill a sponsor or finaid link for an event that won't happen. Editions with cfp: None stay in: the job only fills Bluesky, YouTube, Mastodon, sponsor and finaid links, and an edition with no public CfP still happens. On the current data the only change is PyCon Estonia 2026.

Needs your call

  • Merge order: merge the stack bottom-up. The desk only starts once you've done the Routine steps in the UI. Between merging this PR and that, changedetection events, merge questions and redirects pile up in desk-queue without being lost (at most 50 per series).
  • Secret: ANTHROPIC_API_KEY can be deleted from the repository secrets once this PR is on main.

Checks

  • An adversarial review found two should-fix issues: handled items came back on every run, and a broken queue file stopped the canonical merge. It also found five nits. All seven are fixed in b3705a6, and each has a test where it can.
  • The routine review's two nits are fixed in 87bc32e:
    • The manual run's example name, "PyCon US 2027", was queued with year null under its own file, since a year counted only before a changedetection suffix. A trailing year from 1900 to 2099 with no suffix now counts too, so it lands in queue/pycon-us.json with year 2027. Watch titles keep their suffix and read as before.
    • A comment next to the merge prompts says each prompt's text is part of its queued question's id, so rewording one queues every standing question again, once.
  • Tests: the pinned uv run gives 1175 passed, 0 failed.
  • uvx ruff@0.4.4 check utils tests: clean.
  • pre-commit run on every changed file: passes.
  • actionlint on all workflows: clean.
  • python utils/sort_yaml.py --skip_links leaves _data/ unchanged.

🤖 Generated with Claude Code

https://claude.ai/code/session_01QzyF2ciB5jhvcTVpj4ErMr

@JesperDramsch
JesperDramsch added this pull request to stack #502 October 14, 2026 14:19
@JesperDramsch
JesperDramsch force-pushed the ai-agent/exciting-tesla-g7fn0r-4 branch from 8eb3353 to 9de3168 Compare October 15, 2026 07:12
Base automatically changed from ai-agent/exciting-tesla-g7fn0r-3 to main October 15, 2026 10:28
@JesperDramsch
JesperDramsch force-pushed the ai-agent/exciting-tesla-g7fn0r-4 branch from 98fcd66 to 6c0061b Compare October 15, 2026 10:28
The desk queue lives on an orphan desk-queue branch: one JSON file per
series under queue/, keyed by the slug of the series name, plus a README
that init writes from the module so the format is documented in one
place. GitHub Actions jobs add records with "add", from JSON lines or
from options; the desk removes handled items with "remove".

Each item gets a stable id from its source, kind, URL and payload, so
the same finding queued twice is one item. A file keeps its 50 newest
items and long payload strings are cut at 20,000 characters, flagged
with "truncated". The module uses only the standard library, so any job
can run it without the data tooling.

clean_watch_title and the name lookup move here from desk_worklist.py,
which now imports them, so the queue file key and the worklist's series
matching read names the same way.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QzyF2ciB5jhvcTVpj4ErMr
check-conference-update.yml ran a paid-API triage on every changed
page, then two agent jobs that edited the data. The triage passed 26%
of events at 38% precision and 58% recall, and never saw the data, so
it flagged dates already recorded and dropped extensions and moved
dates. The workflow now validates the payload and writes it to the
desk-queue branch unchanged; the desk judges it on its next run with
the data and the live site in hand.

The payload is read from the event file rather than interpolated or
passed through the environment, so page text cannot reach the shell
and a long diff cannot exceed the environment size limit.

The new composite action desk-queue-write is the one place that writes
to the queue: it creates the orphan branch when missing, adds records
with utils/desk_queue.py, and retries a rejected push from a fresh copy
of the branch, never with force. Ids are stable, so a retry adds nothing
twice.

scheduled-extension-checker.yml goes: the desk's worklist checks every
deadline 1, 3 and 7 days after it passes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QzyF2ciB5jhvcTVpj4ErMr
The TBA enrichment keeps its deterministic link extraction: social
accounts, sponsor pages and financial aid pages found on each
conference's homepage. Deadlines, dates and time zones are now
researched by the desk, so the full mode, its prompt and its API calls
are removed, together with the --level option and the confidence
handling only the API results needed.

The workflow always runs the deterministic enrichment and no longer
needs the API key. The canonical source workflow triggers it without a
level input.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QzyF2ciB5jhvcTVpj4ErMr
The import asks a person to settle conflicts it cannot resolve: which of
two values to keep, whether a later deadline is an extension, whether two
similar names are one series. In CI no one is at a terminal, so
query_yes_no took the default and the question was lost.

query_yes_no now takes an optional context. Without a terminal it still
returns the default, so the import stays deterministic, and it also
records the question with the conference, year, field, both values and
the source. With DESK_QUEUE_OUT set, the record is written as a JSON
line, and canonical-source-update.yml adds the file to the desk queue,
where the enricher answers it from the live site.

The JSON-lines writer moves into tidy_conf/queue_out.py, shared with the
link checker's redirect records.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QzyF2ciB5jhvcTVpj4ErMr
The link check now records a link that ends on a different site instead
of counting a 200 there as healthy, but nothing ran it on a schedule.
redirect-scan.yml runs it every Sunday with DESK_QUEUE_OUT set and adds
the records to the desk queue. The desk decides each one: a series that
moved gets its new link, a lapsed or hijacked domain an archived copy.

The scan is report-only. The link check's other edits stay with the
writers that commit data.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QzyF2ciB5jhvcTVpj4ErMr
The canonical import asks the same standing questions on every run (an
end date one day apart, "Brasil" and "Brazil"), and the redirect scan
finds the same redirects every week. Ids only deduplicated while an item
was queued, so once the desk had handled one, the next run queued it
again. desk_queue.py remove now records each id in its file's "handled"
list, the last 500 per file, and add reports a handled id instead of
queueing it. A changed finding has a new id and is queued as usual. For
a redirect the id leaves out the status and the final URL's query and
fragment, which can change between checks.

A queue file that is not valid JSON stopped every writer bound for it,
and under set -e the canonical merge was never committed. add now moves
such a file to <key>.json.bad and starts a new one; remove and list skip
it. The composite action turns these messages and rejected records into
workflow warnings, and the canonical workflow's queue step can no longer
hold back the merge.

Also from review:
- the follow-up "Is this an extension?" question is no longer queued:
  the cfp question before it carries both deadlines, while its record
  gave the YAML cfp as the cfp_ext value;
- the redirect scan's link check has a step timeout below the job's, so
  a slow scan still queues what it found;
- canonical-source-update gets actions: write, which gh workflow run
  needs to start the TBA enrichment;
- the action matches refs/heads/desk-queue exactly.

desk.md step 8 now removes items only through desk_queue.py remove.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QzyF2ciB5jhvcTVpj4ErMr
find_tba_conferences picked every entry whose cfp is in TBA_WORDS,
which also holds "cancelled", so the job visited the homepage of a
cancelled edition and could fill its sponsor or finaid link. It now
leaves cancelled editions out. Entries with cfp None stay in: since
the API mode is gone, the job only fills Bluesky, YouTube, Mastodon,
sponsor and finaid links, and an edition with no public CfP still
happens. On the current data the only change is PyCon Estonia 2026.
The new tests fail on the previous code for the cancelled cases.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QzyF2ciB5jhvcTVpj4ErMr
@JesperDramsch
JesperDramsch force-pushed the ai-agent/exciting-tesla-g7fn0r-4 branch from 6c0061b to 7d6c428 Compare October 15, 2026 10:40
@JesperDramsch
JesperDramsch marked this pull request as ready for review October 15, 2026 10:49

Copy link
Copy Markdown
Owner Author

Routine review

1. Request

No linked issue, so the request is the PR body. It asks to stop every paid-API call from GitHub Actions by:

  • adding the desk queue (utils/desk_queue.py, .github/actions/desk-queue-write);
  • turning check-conference-update.yml into a single queue writer;
  • queueing the canonical import's unanswered merge questions (interactive_merge.py, utils.py, queue_out.py, both importers, canonical-source-update.yml);
  • adding a weekly redirect-scan.yml;
  • retiring scheduled-extension-checker.yml and the full mode of enrich_tba.py / enrich-tba-conferences.yml;
  • leaving cancelled editions out of the TBA link enrichment (7d6c428).

2. Checked against

  • Date: Thu Oct 15 10:49:51 UTC 2026 (date).
  • No external facts are relied on. unverified: the PR body's figure for the retired triage (38% precision, 58% recall over 254 events). It comes from repository history I did not re-derive.

3. Needs your decision

  1. Merge order and the gap before the desk Routine is live. Once this is on main, changedetection signals, merge questions and redirects pile up on desk-queue, with at most 50 items per series, until you set up the Routine in the UI. Recommendation: merge, then set up the Routine the same day, so no busy series hits the 50-item cap and silently drops older signals.
  2. Deleting ANTHROPIC_API_KEY. On this head, no workflow, utils/ file, .claude/ file or pixi.toml refers to ANTHROPIC_API_KEY, enrichment_level, --level, apply-filters or scheduled-extension-checker (grep). Recommendation: delete the secret after merge.

4. Findings

Important: None.

Nits:

  • .github/workflows/check-conference-update.yml:21: the manual input's example is "PyCon US 2027". clean_watch_title strips a year only when the title ends in - Main/- CfP, so that example becomes ("PyCon US 2027", None) and is queued to queue/pycon-us-2027.json with year: null (checked with a one-line script). Suggest the example "PyCon US 2027 - Main", or making the year part of the suffix pattern optional.
  • utils/tidy_conf/utils.py record_question: the question's id hashes the full prompt text, which includes the YAML and remote values. That is correct as the PR intends: a changed value is a new finding. It also means any later rewording of a prompt in interactive_merge.py re-queues every standing question once. A comment next to the prompts would warn whoever edits them.

5. Tests

  • Invariant tests: tests/test_desk_queue.py covers the queue's invariants on every source and kind it names: stable ids, a re-add is a no-op, handled ids stay handled, the 50-item and 20,000-character caps, and a broken file moved to .bad. tests/test_merge_questions.py covers the question path for the place, cfp and fuzzy-name questions, and checks that agreeing sources ask nothing. tests/test_enrich_tba.py covers every TBA word, plus None, the cancelled cases and a dated cfp.
  • Fix in 7d6c428, new test against base and head:
    • Base (origin/main utils/enrich_tba.py, run with this PR's tests/test_enrich_tba.py): 2 failed, 6 passed in 0.18s. The failures are FAILED tests/test_enrich_tba.py::test_cancelled_and_dated_conferences_are_left_out[Cancelled] and FAILED tests/test_enrich_tba.py::test_cancelled_and_dated_conferences_are_left_out[cancelled], each failing with Found 1 conferences with TBA CFP.
    • Change: 8 passed in 0.06s.
  • Change, full suite (REVIEW.md's pinned uv run --python 3.11 … pytest tests/ -q -x -p no:cacheprovider): 1171 passed, 5 skipped, 3 xfailed, 2 xpassed, 153 warnings in 50.29s.
  • Lint (uvx ruff@0.4.4 check utils tests): All checks passed!
  • Data (… python utils/sort_yaml.py --skip_links): exit 0, and git diff --stat -- _data is empty.
  • JS (npm ci && npm test): Tests: 423 passed, 423 total across 15 suites. No static/js/ changes.

6. Bounds

  • Route: the diff and PR came from the REST API. The head was cloned at refs/pull/500/head, and git rev-parse matched 7d6c428eaf9f280abd18645fe63f0317b61722e7.
  • Read in full: utils/desk_queue.py, .github/actions/desk-queue-write/action.yml, check-conference-update.yml, redirect-scan.yml, the diffs of canonical-source-update.yml, enrich-tba-conferences.yml, .claude/routines/desk.md, interactive_merge.py, utils.py, links.py, queue_out.py, both importers and desk_worklist.py.
  • Skimmed: the 884-line removal from enrich_tba.py (checked only that nothing still calls the removed code), tests/test_desk_queue.py and the deleted workflows.
  • Not run:
    • the composite action and workflows. There is no Actions runner or actionlint here, so the branch-creation, push-retry and continue-on-error paths rest on reading the code and on the PR's own local-remote test;
    • pre-commit.ci - pr. I did not fetch its status;
    • pixi run links (network, and up to 150 min).
  • Pre-existing: none noticed.

Generated by Claude Code

…shes

From the routine review of #500:

- The manual check-conference-update run asks for a name such as
  "PyCon US 2027", but clean_watch_title took a year only from a
  changedetection suffix (" 2027 - Main"), so that input was queued with
  year null under queue/pycon-us-2027.json. A trailing year from 1900 to
  2099 with no suffix now counts too, so it goes to queue/pycon-us.json
  with year 2027. Watch titles keep their suffix and are read as before;
  "Python 3000" and "PyCon 2027 Online" keep their names.
- A comment next to the merge prompts in interactive_merge.py says that
  each prompt's text is part of its queued question's id, so rewording
  one queues every standing question again, once.

The new tests fail on the previous code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QzyF2ciB5jhvcTVpj4ErMr
@JesperDramsch JesperDramsch added the Review Ready Runs the PR review Routine; the Routine removes it after posting label Oct 15, 2026

Copy link
Copy Markdown
Owner Author

Routine review

Re-review of #500 at 87bc32e. The earlier review at 7d6c428 raised no Important findings, so this one checks only what changed in 87bc32e and, under the contract's re-review rule, leaves out Nits.

1. Request

No linked issue, so the request is the PR body. It is the same as last round:

  • the desk queue (utils/desk_queue.py, .github/actions/desk-queue-write);
  • check-conference-update.yml as a queue writer;
  • queued merge questions from the canonical import;
  • the weekly redirect-scan.yml;
  • retiring scheduled-extension-checker.yml and the full mode of enrich_tba.py;
  • leaving cancelled editions out of the TBA link enrichment.

This round adds 87bc32e, which addresses the earlier review's two Nits:

  • clean_watch_title now reads a trailing year;
  • a comment in interactive_merge.py explains what a question id hashes.

2. Checked against

  • Date: Thu Oct 15 11:19:10 UTC 2026 (date).
  • No external facts are relied on.

3. Needs your decision

Both questions from the earlier review still apply, and this push does not change them:

  1. Merge order and the gap before the desk Routine is live. Recommendation: set up the Routine on the day you merge, so no series reaches the 50-item cap.
  2. Deleting ANTHROPIC_API_KEY. Recommendation: delete it once this is on main.

4. Findings

Important: None. The earlier review had none to resolve, and 87bc32e adds none.

What I checked for the new TRAILING_YEAR rule (utils/desk_queue.py:64):

  • Where it applies. clean_watch_title now also feeds record_names (desk_queue.py:208), _queue_names and _queue_year (desk_worklist.py:418,430) and the changedetection step (check-conference-update.yml:93).
  • Effect on names. It strips a name only when the name ends in a year from 1900 to 2099. No conference value in _data/*.yml and no key or variation in titles.yml ends that way (grep), so no tracked series name gets cut short.
  • Edge cases. "Python 3000" and "PyCon 2027 Online" keep their names, and the new tests pin both.

5. Tests

  • Invariant tests:
    • tests/test_desk_worklist.py::TestCleanWatchTitle covers both new cases: a trailing year, and one with trailing whitespace. It also covers the two shapes that must not count: a number outside the range and a year in the middle of the name.
    • tests/test_desk_queue.py::test_hand_typed_year_is_not_part_of_the_series checks that "PyCon US 2027" is queued to queue/pycon-us.json.
  • Change, full suite (REVIEW.md's pinned uv run --python 3.11 … pytest tests/ -q -x -p no:cacheprovider): 1175 passed, 5 skipped, 3 xfailed, 2 xpassed, 153 warnings in 58.40s.
  • Lint (uvx ruff@0.4.4 check utils tests): All checks passed!
  • Data (… python utils/sort_yaml.py --skip_links): exit 0, and git diff --stat -- _data is empty.
  • Not rerun: the base-vs-change run. 87bc32e is a behaviour change that came from a review Nit, not a bug fix.

6. Bounds

  • Route:
    • The PR came from the REST API. I cloned the head at refs/pull/500/head, and git rev-parse matched 87bc32efd1112bfe10d8bade7c43b7fb999bf6fa.
    • I diffed this round as 7d6c428..87bc32e.
  • Read in full: that diff (4 files), and the call sites of clean_watch_title in desk_queue.py, desk_worklist.py and check-conference-update.yml.
  • Not reread: the rest of the PR, which the earlier review covered at 7d6c428.
  • Not run:
    • the composite action and workflows: there is no Actions runner here;
    • npm test: no static/js/ or tests/frontend/ changes;
    • pre-commit.ci - pr: I did not fetch its status;
    • pixi run links.
  • Pre-existing: none noticed.

Generated by Claude Code

@JesperDramsch JesperDramsch removed the Review Ready Runs the PR review Routine; the Routine removes it after posting label Oct 15, 2026
@JesperDramsch
JesperDramsch merged commit ce5df9f into main Oct 15, 2026
11 checks passed
@JesperDramsch
JesperDramsch deleted the ai-agent/exciting-tesla-g7fn0r-4 branch October 15, 2026 11:30
JesperDramsch pushed a commit that referenced this pull request Oct 15, 2026
#500 landed on main as the squash commit ce5df9f, whose tree is the
old #500 head 87bc32e. Conflicts keep this branch's side, so the
branch's diff against main is #501's own change (87bc32e..dd5a329).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CztuQqAuxRRJLkKiQYasnS
JesperDramsch pushed a commit that referenced this pull request Oct 15, 2026
Brings in #500 and #501. The writers push with GITHUB_TOKEN, which
cannot push this merge because main changed files under
.github/workflows/, so it is merged by hand once.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CztuQqAuxRRJLkKiQYasnS
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants