Skip to content

feat: tell a quiet arXiv inbox apart from a broken one - #60

Merged
Jammy2211 merged 3 commits into
mainfrom
claude/inbox-board-staleness-signal-onzg34
Aug 25, 2026
Merged

Jammy2211 merged 3 commits into
mainfrom
claude/inbox-board-staleness-signal-onzg34

Conversation

@Jammy2211

Copy link
Copy Markdown
Collaborator

Summary

Closes #58.

The board rendered an empty arXiv inbox as nothing waiting — the nightly arXiv digest fills this for two opposite states: arXiv genuinely announced no strong-lensing papers, or the digest ran and the filing failed so the papers were lost. On 2026-08-25 it was the second, and nothing on the page said so. arxiv_papers.yml already killed this ambiguity on the Slack side — empty days still post a heartbeat, so silence in #papers always means a broken run. The board had no equivalent.

inbox_actions.py now owns a last digest: <date> line in arxiv-inbox.md, rewritten on every digest run whether or not papers arrive. One line does both jobs the prompt scoped: dated today over an empty inbox it is the quiet-day marker, making absence positive evidence; days old it is the "last filled" signal. Being replaced rather than accumulated, the sweep has nothing to age out — which is what an explicit per-day marker line would have cost.

The board now reads:

nothing waiting — the last digest ran 2026-08-25 and found nothing
nothing waiting — ⚠ no digest since 2026-08-19 (4 weekdays) — the nightly filing may be broken
nothing waiting — the nightly arXiv digest fills this — no run recorded yet

Two design calls worth a reviewer's attention.

The warning is computed in the browser, not at render time. knowledge_board.yml republishes on pushes to arxiv-inbox.md — which is precisely what stops happening when filing breaks. The page then freezes at its last good render, so a warning baked in server-side would sit at "0 weekdays" forever and never fire in the one failure it exists for. The rendered date is a fact and survives the freeze; the verdict is recomputed against the reader's clock. A page that was already stale when rendered warns without JS too.

The threshold is 2 weekdays, not 1 day. The digest fires at a nominal 02:00 UTC and GitHub cron only ever jitters later, so a board rendered early can see yesterday's stamp with nothing wrong. Weekdays rather than days because the digest is Mon–Fri: a Monday board reading Friday's stamp is three calendar days but zero missed runs.

A missing stamp reads neutrally (no run recorded yet), never as a warning — spawn.py empties arxiv-inbox.md for the template repos, so a freshly spawned Memory has never run a digest and has nothing to be late for.

The first commit on this branch is unrelated: main has been red on make validate and make test since 498e1a8 committed a tracked .claude/ without adding it to ALLOWED_TOP_DIRS. It is one line, kept as its own commit so it can be read or reverted separately.

The companion PyAutoMind PR makes the digest write the stamp on quiet days. Merge this one first — arxiv_papers.yml calls inbox_actions.py stamp, so the other way round leaves a window where the nightly run fails on a subcommand that does not exist yet.

API Changes

New public surface in scripts/inbox_actions.py, all additive — last_digest(), set_last_digest(), weekdays_since(), is_stale(), the INBOX_STALE_WEEKDAYS constant and a stamp subcommand. board.py's snapshot gains one key, inbox_last_digest. Nothing was removed, renamed, or had its signature changed; the inbox line format, the 7-day window and all four transitions are untouched. See full details below.

Test Plan

  • make test — 92 passing, 23 new (was 69)
  • make validate — structure and citation metadata both green
  • All four render states checked by hand: quiet / stale / never-run / populated, on both the markdown and HTML renderers
  • The shipped JS (extracted from board.py, not retyped) cross-checked against the Python weekday arithmetic over 30 consecutive stamp dates, weekends included — no mismatches
  • Rehearsed end to end against a real checkout with the digest's shell blocks extracted verbatim from arxiv_papers.yml: quiet day/same date (no commit), quiet day/new date (heartbeat commit), paper day (paper lands below the stamp), quiet day sweeping lapsed papers
Full API Changes (for automation & release notes)

Added

  • inbox_actions.INBOX_STALE_WEEKDAYS — weekdays of silence before the board calls a digest broken (2)
  • inbox_actions.last_digest(text) — the date of the last recorded digest run, or None
  • inbox_actions.set_last_digest(inbox_text, date) — record a run, replacing any existing stamp
  • inbox_actions.weekdays_since(stamp, today) — weekdays elapsed, skipping weekends
  • inbox_actions.is_stale(stamp, today) — the staleness verdict; a missing stamp is never stale
  • inbox_actions.STAMP_RE, inbox_actions.SEPARATOR — the stamp grammar and the header separator
  • scripts/inbox_actions.py stamp — CLI subcommand, prints stamped:<date>
  • board.collect() → snapshot["inbox_last_digest"], carried through --json
  • board._inbox_freshness(), board._inbox_empty_note() — internal render helpers

Changed Behaviour

  • board.py's inbox section now carries a freshness clause in both the populated and empty branches; the empty branch's wording is no longer a fixed string
  • The rendered HTML carries data-last-digest / data-stale-weekdays / data-stale-text and a freshness() script that recomputes the verdict client-side

Migration

None — every addition is new surface. Existing callers of papers(), append(), sweep(), remove() and add_to_queue() are unaffected, and an inbox with no stamp parses exactly as before.

Generated by the PyAutoLabs agent workflow.


Generated by Claude Code

claude added 2 commits August 25, 2026 13:17
498e1a8 committed .claude/ (the session-start hook + settings) without adding
it to ALLOWED_TOP_DIRS, so `make validate` and `make test` have both been red
on main ever since — validate_structure only considers git-tracked top-level
names, and .claude/ is tracked.

Unrelated to the inbox work on this branch; committed separately so it can be
read, or reverted, on its own.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ex9YARe5kDjsT3Ug7LKQ1r
The board rendered an empty inbox as "nothing waiting — the nightly arXiv
digest fills this" for two opposite states: arXiv genuinely announced no
strong-lensing papers, or the digest ran and the filing failed so the papers
were lost. On 2026-08-25 it was the second, and nothing on the page said so.
arxiv_papers.yml already killed this ambiguity on the Slack side — empty days
still post a heartbeat, so silence in #papers always means a broken run. The
board had no equivalent.

inbox_actions.py now owns a `last digest: <date>` line in arxiv-inbox.md,
rewritten on every digest run whether or not papers arrive. One line does both
jobs the prompt scoped: dated today over an empty inbox it is the quiet-day
marker, making absence positive evidence; days old it is the "last filled"
signal. Being replaced rather than accumulated, the sweep has nothing to age
out — which is what the explicit per-day marker line would have cost.

The staleness verdict is computed in the reader's browser, not at render time.
knowledge_board.yml republishes on pushes to arxiv-inbox.md, which is exactly
what stops happening when filing breaks: the page freezes at its last good
render, so a warning baked in server-side would sit at "0 weekdays" forever
and never fire in the one failure it exists for. The date is a fact and
survives the freeze; the verdict is recomputed against the reader's clock. A
page that was already stale when rendered warns without JS too.

Threshold is 2 weekdays, in inbox_actions.py beside INBOX_WINDOW_DAYS. Not 1:
the digest fires at a nominal 02:00 UTC and GitHub cron only ever jitters
later, so a board rendered early can see yesterday's stamp with nothing wrong.
Weekdays, not days, because the digest is Mon-Fri — a Monday board reading
Friday's stamp is three days but zero missed runs.

A missing stamp reads neutrally ("no run recorded yet"), never as a warning:
spawn.py empties arxiv-inbox.md for the template repos, so a freshly spawned
Memory has never run a digest and has nothing to be late for.

Seeds the stamp at 2026-08-25, the day the backfill dispatch filed the two
papers now in the inbox.

23 new tests (92 total, was 69), covering the three empty states, the weekday
arithmetic across a weekend, and the render/browser handshake. The shipped JS
was separately cross-checked against the Python for 30 consecutive stamp dates.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ex9YARe5kDjsT3Ug7LKQ1r
main landed the same .claude/ allowlist fix in #59 (93a0b6b) while this branch
was in flight. Same one-line change, different comment wording — resolved in
main's favour, so 05aa803 here now contributes nothing and the file is
byte-identical to main. Kept as a merge rather than a rebase: the branch is
pushed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ex9YARe5kDjsT3Ug7LKQ1r
@Jammy2211
Jammy2211 merged commit 963388a into main Aug 25, 2026
1 check passed
@Jammy2211
Jammy2211 deleted the claude/inbox-board-staleness-signal-onzg34 branch August 25, 2026 15:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: distinguish a quiet arXiv inbox from a broken one

2 participants