Overview
The knowledge board renders an empty arXiv inbox as nothing waiting — the nightly arXiv digest fills this. — the same line 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 exact ambiguity on the Slack side — empty
days still post a heartbeat, so silence in #papers always means a broken run.
The board has no equivalent. This task gives it one.
Split out of complete/2026/08/arxiv-inbox-pat-scope.md, which fixed the filing
bug (PR PyAutoMind#317 makes a denied push redden the run) but deliberately left
this open: that half only helps someone reading CI, and this half is for the
person reading the board.
Plan
- Give
arxiv-inbox.md a single last digest: <YYYY-MM-DD> line, owned by
scripts/inbox_actions.py like every other part of the inbox format. No new
state file.
- One line covers both options the prompt listed: a stamp dated today over an
empty inbox is the quiet-day marker (absence becomes positive evidence);
a stamp four days old is the last filled: signal. Because it is replaced
rather than accumulated, the sweep never has to age it out.
- Make the digest stamp on quiet days too. Today the inbox step is skipped
whenever the fetch returns zero papers, so a stamp would go stale on exactly
the days it exists to prove were quiet. This is the PyAutoMind leg.
- Render the date on the board, and compute the staleness warning in the
reader's browser, not at render time (see the trap below).
- Warn after 2 weekdays with no digest.
Detailed implementation plan
Affected Repositories
- PyAutoMemory (primary) — the stamp and the board
- PyAutoMind — the workflow leg that writes the stamp on quiet days
The prompt scopes this to PyAutoMemory alone. The PyAutoMind leg is not
scope creep: a stamp that only updates on days papers arrive cannot
distinguish a quiet day from a broken one, which is the entire point.
Branch Survey
| Repository |
Current Branch |
Dirty? |
| ./PyAutoMemory |
main |
clean |
| ./PyAutoMind |
claude/inbox-board-staleness-signal-onzg34 |
clean |
Suggested branch: claude/inbox-board-staleness-signal-onzg34 (both repos —
the session's designated branch; no worktree, this is a web-github run)
The load-bearing trap: a render-time warning can never fire
knowledge_board.yml re-renders on pushes to arxiv-inbox.md, plus a weekly
Monday cron. When filing breaks there is no push — so the published page freezes
at its last good render, and a staleness warning computed server-side would be
frozen with it, showing "0 days since last digest" forever. It would be absent
in precisely the failure it exists for.
So the split is:
- the absolute date is rendered into the page — honest even when frozen;
- the warning is computed from it client-side, at view time.
The weekly cron re-renders the page anyway; the JS makes it honest in between.
Implementation Steps
PyAutoMemory/scripts/inbox_actions.py
INBOX_STALE_WEEKDAYS = 2, beside INBOX_WINDOW_DAYS, with the reasoning:
the digest is nominally 02:00 UTC Mon–Fri and GitHub cron only ever jitters
later (0–3 h), so a single missed weekday is reachable without anything
being wrong; two is a pattern.
STAMP_RE = ^last digest:\s*(\d{4}-\d{2}-\d{2})\s*$.
last_digest(text) -> str | None.
set_last_digest(text, date) -> str — replaces the stamp in place, else
inserts directly after the --- separator.
weekdays_since(stamp, today) -> int.
stamp subcommand, printing stamped:<date> like the other one-token
subcommands.
append's insertion point is unchanged: STAMP_RE cannot match
INBOX_LINE_RE, and the stamp sits above the papers under every ordering
(append inserts after the last paper line, or at EOF when there are none).
PyAutoMemory/scripts/board.py
collect() gains inbox_last_digest; carried through --json.
- Markdown: a freshness clause in both branches — populated
(2 waiting · last digest 2026-08-25 · lapse after 7 days) and empty
(nothing waiting — last digest ran 2026-08-25 and found nothing).
- HTML: the same, wrapped in
<time datetime=… data-stale-weekdays=2>.
_EXTRA_JS: compute the weekday age at view time and swap in
⚠ no digest for N weekdays — filing may be broken.
- A missing stamp reads neutrally (
no digest run recorded yet), not as
a warning: spawn.py maps arxiv-inbox.md to EMPTY, so a freshly
spawned template legitimately has no stamp and has never run a digest.
PyAutoMemory/arxiv-inbox.md — document the stamp in the format block.
PyAutoMind/.github/workflows/arxiv_papers.yml
- The
count == '0' heartbeat step also writes arxiv_survivors.json as
{"papers": []} — exactly what the Claude step already writes on its
empty day, so every path produces the file.
- Drop
if: count != '0' from the inbox step and add the stamp call, so
every weekday run stamps. The existing warn-and-exit-0 guard on a
missing survivors file stays as the real-failure signal.
- Unchanged: the step still runs after the Slack POST, so "a cross-repo
failure must never cost the morning's post" holds.
- Deliberate side effect: one commit per weekday to PyAutoMemory, which
re-renders the board daily. That is the heartbeat.
- Tests —
tests/test_inbox_actions.py (stamp round-trip, insertion point,
replacement not accumulation, weekday arithmetic across a weekend, stamp is
not parsed as a paper) and tests/test_board.py (both render branches,
missing stamp, the date reaching the HTML).
Not provable in CI
arxiv_papers.yml cannot be fired from a branch that edits it —
claude-code-action refuses to run when the workflow file differs from the
default branch's copy, and then exits success (recorded trap, cost a wasted
dispatch in arxiv-inbox-pat-scope). That leg gets bash -n, a YAML parse and
reasoned review only; the first genuine exercise is the scheduled run on main.
Pre-existing failure to clear first
PyAutoMemory@main is currently red on both make validate and make test:
commit 498e1a8 added a tracked .claude/ directory without adding it to
ALLOWED_TOP_DIRS in scripts/validate_structure.py. Unrelated to this task,
but any PR is red on arrival until it is fixed. One line, in its own commit.
Key Files
PyAutoMemory/scripts/inbox_actions.py — single owner of the inbox line
format, the window and every transition; the stamp joins them
PyAutoMemory/scripts/board.py — renders the inbox above the reading queue
PyAutoMemory/arxiv-inbox.md — the inbox itself, and its format block
PyAutoMind/.github/workflows/arxiv_papers.yml — the nightly digest
PyAutoMemory/scripts/validate_structure.py — the pre-existing red
Original Prompt
Click to expand starting prompt
The knowledge board's empty inbox should distinguish quiet from broken
Type: feature
Target: PyAutoMemory
Repos:
- PyAutoMemory
Difficulty: small
Autonomy: supervised
Priority: normal
Status: formalised
Filed: 2026-08-25
Split out of complete/2026/08/arxiv-inbox-pat-scope.md, which fixed the filing
bug but deliberately left this open.
The gap
The board renders an empty arXiv inbox as:
arXiv inbox (suggested overnight — un-acted papers lapse after 7 days)
nothing waiting — the nightly arXiv digest fills this.
That line is shown for two completely different states:
- arXiv genuinely announced no strong-lensing papers; or
- the digest ran and the filing failed, so papers were lost.
On 2026-08-25 it was the second, and nothing on the page said so. The reader has
no way to tell without opening GitHub Actions.
Why this is a known-solved problem here
arxiv_papers.yml already fixed the identical ambiguity on the Slack side. From
its header comment (user, 2026-07-13):
Empty days STILL post a short "no new strong-lensing papers today" heartbeat —
so silence in #papers always means a broken run, never a genuinely empty one.
The board has no equivalent. The digest's own invariant stops at Slack and does
not reach the surface the paper-management feature actually asks the human to
use.
Task
Give the inbox a freshness signal so an empty board is readable. Options, in
rough order of cost:
last filled: line — the board renders the date of the most recent
successful append/sweep. An empty inbox dated today is quiet; one dated
four days ago is suspect. Cheapest, no new state if it can be derived from
arxiv-inbox.md's git history or a marker the digest writes.
- Explicit quiet-day marker — the digest writes a dated "no papers" line on
genuinely empty days, exactly mirroring the Slack heartbeat. Makes absence
positive evidence, at the cost of a line the sweep must then age out.
- Freshness warning — the board flags an inbox with no write in N weekdays.
Strongest signal, needs a threshold nobody has tuned yet.
Prefer whichever needs no new state file. Note that scripts/inbox_actions.py
is the single owner of the line format, the window and every transition — a
marker line must go through it, not through board.py or a workflow's shell.
Note on the sibling failure mode
PR PyAutoMind#317 made the workflow fail loudly when the push is denied, so a
future filing failure reddens the run. This task covers the other half: the
board should be honest even when nobody is reading CI.
Overview
The knowledge board renders an empty arXiv inbox as
nothing waiting — the nightly arXiv digest fills this.— the same line 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.ymlalready killed this exact ambiguity on the Slack side — emptydays still post a heartbeat, so silence in
#papersalways means a broken run.The board has no equivalent. This task gives it one.
Split out of
complete/2026/08/arxiv-inbox-pat-scope.md, which fixed the filingbug (PR PyAutoMind#317 makes a denied push redden the run) but deliberately left
this open: that half only helps someone reading CI, and this half is for the
person reading the board.
Plan
arxiv-inbox.mda singlelast digest: <YYYY-MM-DD>line, owned byscripts/inbox_actions.pylike every other part of the inbox format. No newstate file.
empty inbox is the quiet-day marker (absence becomes positive evidence);
a stamp four days old is the
last filled:signal. Because it is replacedrather than accumulated, the sweep never has to age it out.
whenever the fetch returns zero papers, so a stamp would go stale on exactly
the days it exists to prove were quiet. This is the PyAutoMind leg.
reader's browser, not at render time (see the trap below).
Detailed implementation plan
Affected Repositories
The prompt scopes this to PyAutoMemory alone. The PyAutoMind leg is not
scope creep: a stamp that only updates on days papers arrive cannot
distinguish a quiet day from a broken one, which is the entire point.
Branch Survey
Suggested branch:
claude/inbox-board-staleness-signal-onzg34(both repos —the session's designated branch; no worktree, this is a
web-githubrun)The load-bearing trap: a render-time warning can never fire
knowledge_board.ymlre-renders on pushes toarxiv-inbox.md, plus a weeklyMonday cron. When filing breaks there is no push — so the published page freezes
at its last good render, and a staleness warning computed server-side would be
frozen with it, showing "0 days since last digest" forever. It would be absent
in precisely the failure it exists for.
So the split is:
The weekly cron re-renders the page anyway; the JS makes it honest in between.
Implementation Steps
PyAutoMemory/scripts/inbox_actions.pyINBOX_STALE_WEEKDAYS = 2, besideINBOX_WINDOW_DAYS, with the reasoning:the digest is nominally 02:00 UTC Mon–Fri and GitHub cron only ever jitters
later (0–3 h), so a single missed weekday is reachable without anything
being wrong; two is a pattern.
STAMP_RE = ^last digest:\s*(\d{4}-\d{2}-\d{2})\s*$.last_digest(text) -> str | None.set_last_digest(text, date) -> str— replaces the stamp in place, elseinserts directly after the
---separator.weekdays_since(stamp, today) -> int.stampsubcommand, printingstamped:<date>like the other one-tokensubcommands.
append's insertion point is unchanged:STAMP_REcannot matchINBOX_LINE_RE, and the stamp sits above the papers under every ordering(
appendinserts after the last paper line, or at EOF when there are none).PyAutoMemory/scripts/board.pycollect()gainsinbox_last_digest; carried through--json.(
2 waiting · last digest 2026-08-25 · lapse after 7 days) and empty(
nothing waiting — last digest ran 2026-08-25 and found nothing).<time datetime=… data-stale-weekdays=2>._EXTRA_JS: compute the weekday age at view time and swap in⚠ no digest for N weekdays — filing may be broken.no digest run recorded yet), not asa warning:
spawn.pymapsarxiv-inbox.mdtoEMPTY, so a freshlyspawned template legitimately has no stamp and has never run a digest.
PyAutoMemory/arxiv-inbox.md— document the stamp in the format block.PyAutoMind/.github/workflows/arxiv_papers.ymlcount == '0'heartbeat step also writesarxiv_survivors.jsonas{"papers": []}— exactly what the Claude step already writes on itsempty day, so every path produces the file.
if: count != '0'from the inbox step and add thestampcall, soevery weekday run stamps. The existing warn-and-exit-0 guard on a
missing survivors file stays as the real-failure signal.
failure must never cost the morning's post" holds.
re-renders the board daily. That is the heartbeat.
tests/test_inbox_actions.py(stamp round-trip, insertion point,replacement not accumulation, weekday arithmetic across a weekend, stamp is
not parsed as a paper) and
tests/test_board.py(both render branches,missing stamp, the date reaching the HTML).
Not provable in CI
arxiv_papers.ymlcannot be fired from a branch that edits it —claude-code-actionrefuses to run when the workflow file differs from thedefault branch's copy, and then exits success (recorded trap, cost a wasted
dispatch in
arxiv-inbox-pat-scope). That leg getsbash -n, a YAML parse andreasoned review only; the first genuine exercise is the scheduled run on main.
Pre-existing failure to clear first
PyAutoMemory@mainis currently red on bothmake validateandmake test:commit
498e1a8added a tracked.claude/directory without adding it toALLOWED_TOP_DIRSinscripts/validate_structure.py. Unrelated to this task,but any PR is red on arrival until it is fixed. One line, in its own commit.
Key Files
PyAutoMemory/scripts/inbox_actions.py— single owner of the inbox lineformat, the window and every transition; the stamp joins them
PyAutoMemory/scripts/board.py— renders the inbox above the reading queuePyAutoMemory/arxiv-inbox.md— the inbox itself, and its format blockPyAutoMind/.github/workflows/arxiv_papers.yml— the nightly digestPyAutoMemory/scripts/validate_structure.py— the pre-existing redOriginal Prompt
Click to expand starting prompt
The knowledge board's empty inbox should distinguish quiet from broken
Type: feature
Target: PyAutoMemory
Repos:
Difficulty: small
Autonomy: supervised
Priority: normal
Status: formalised
Filed: 2026-08-25
Split out of
complete/2026/08/arxiv-inbox-pat-scope.md, which fixed the filingbug but deliberately left this open.
The gap
The board renders an empty arXiv inbox as:
That line is shown for two completely different states:
On 2026-08-25 it was the second, and nothing on the page said so. The reader has
no way to tell without opening GitHub Actions.
Why this is a known-solved problem here
arxiv_papers.ymlalready fixed the identical ambiguity on the Slack side. Fromits header comment (user, 2026-07-13):
The board has no equivalent. The digest's own invariant stops at Slack and does
not reach the surface the paper-management feature actually asks the human to
use.
Task
Give the inbox a freshness signal so an empty board is readable. Options, in
rough order of cost:
last filled:line — the board renders the date of the most recentsuccessful
append/sweep. An empty inbox dated today is quiet; one datedfour days ago is suspect. Cheapest, no new state if it can be derived from
arxiv-inbox.md's git history or a marker the digest writes.genuinely empty days, exactly mirroring the Slack heartbeat. Makes absence
positive evidence, at the cost of a line the sweep must then age out.
Strongest signal, needs a threshold nobody has tuned yet.
Prefer whichever needs no new state file. Note that
scripts/inbox_actions.pyis the single owner of the line format, the window and every transition — a
marker line must go through it, not through
board.pyor a workflow's shell.Note on the sibling failure mode
PR PyAutoMind#317 made the workflow fail loudly when the push is denied, so a
future filing failure reddens the run. This task covers the other half: the
board should be honest even when nobody is reading CI.