poller: per-room history so a mention arrives with its thread; state-of-play line (#90) - #92
Conversation
…of-play line (#90 items 2, 6, 7) Why: a mention reached the agent with a 100-char preview of its parent, or "target not in recent window" when the parent was older than the 10-message fetch; settled facts lived in messages the agent never saw. hermes read "new card" as a GPU card for exactly this reason (issue #90). What: - src/common/room-history.mjs: RoomHistory (JSON file next to seen_file, 400 messages per room, persisted every poll) plus threadSuffix (parent in full with author and time, up to two ancestors), previousSuffix (the asker's previous message), stateOfPlayLine ("state:" / "settled:" messages, deduped, newest wins) and ownLastPostLine. - reply-context.resolveReplyTargets(msgs, lookup): resolves targets outside the batch through the history; snippet 120 -> 300 chars. - room-poller: fetch 25 per poll (poller.fetch_limit), remember every batch, one deeper fetch (100) when a reply target is still unknown, and the notification line carries body (400) + thread + previous message for owner messages and mentions. When a room's batch has an owner message or a mention, one STATE OF PLAY line and one YOUR LAST POST HERE line are prepended for that room. - Notification-file contract kept: one physical line per message. - README: poller.history_file, poller.fetch_limit, the state: convention. Tests: 7 new in test/room-history.test.mjs; suite 304/304. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
Reviewed the room-poller hunk for the interaction with #91 (per-message seen-state). No blocking issue in #92 itself. Two notes for the rebase I will do after this merges: (1) #92 buffers each room's lines and appends them after the loop with the state-of-play / own-last-post header; #91 needs each message's line on disk before its seen-marker. Reconcile by emitting the header lazily — once, right before the first priority line of that room (owner/mention is known per message) — then append line + marker per message. Readers still see header-then-lines. (2) |
ThinkOffApp
left a comment
There was a problem hiding this comment.
P1: On a fresh install, the seed branch fetches and marks the current 50 messages as seen but never calls history.remember() for them. The first subsequent reply to any seeded parent therefore still renders as "target ... not in recent window", which defeats the core cross-poll context guarantee until that parent happens to be refetched. Please remember the seed batches before saving seen IDs (and add an integration/regression test for a reply to a seeded message).
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9252ab03ca
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| * and m._replyTarget in place; returns the batch for chaining. | ||
| */ | ||
| export function resolveReplyTargets(msgs) { | ||
| export function resolveReplyTargets(msgs, lookup) { |
There was a problem hiding this comment.
Apply history to the unified GroupMind adapter
When GroupMind runs through the supported ide-agent-kit platform watch path, bin/cli.mjs creates a UnifiedPoller with groupmindAdapter, but src/adapters/groupmind.mjs:19-35 still fetches only 10 messages and invokes this function without the new lookup. Consequently poller.history_file, poller.fetch_limit, state/previous/own context, and old-parent resolution have no effect on that path, so replies outside its ten-message window retain the unresolved marker. Pass the history-aware lookup and formatting through the unified adapter as well.
Useful? React with 👍 / 👎.
| // and the room's "state:" facts (issue #90). One file per poller. | ||
| const historyFile = config?.poller?.history_file || seenFile.replace(/\.txt$/, '') + '-history.json'; | ||
| const fetchLimit = parsePositiveInt(config?.poller?.fetch_limit, 25); | ||
| const history = new RoomHistory(historyFile); |
There was a problem hiding this comment.
Seed the new history from the startup fetch
When the history file is absent—on first launch or an upgrade—this initializes an empty history, while the existing startup seed fetches 50 messages only to mark their IDs seen. The initial poll then remembers just fetchLimit messages (25 by default), permanently discarding seeded messages 26–50 from history; a recent state: fact or the agent's own post in that range will therefore be missing from the next owner's non-reply notification. Remember and save each seed batch, or perform an equivalent initial history backfill.
Useful? React with 👍 / 👎.
…w); seedRoom() exported with a regression test Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Fixed: the first-run seed now goes through |
…t priority line Rebased onto #92 (fa043ae). #92 buffered each room's lines and wrote the state-of-play / own-last-post header with them after the loop; #91 writes each line as it is handled. Reconciled: the header goes to the notification file right before the first line that makes the batch worth a wake, so readers still see header then lines and every line is on disk before its seen-marker. Test: header once, before the owner line, no line written twice. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…lines, not text The seen-state fixtures carried midnight timestamps that the new age gate rightly skips after 6 h; the stale-gate test matched the March text that #92's asker-context legitimately appends to the fresh line. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… 20000 Every restart of the Mini poller replayed months-old messages from the quiet rooms as new (2026-09-02: 80+ lines from four rooms, March to July). Cause: the seen-file keeps the last 1000 ids globally, and thinkoff-development alone writes more than that between restarts, so the quiet rooms' ids fell off the end and their last window looked unseen. Pre-existing; #92's larger fetch only made it visible sooner. Two fixes: the cap is 20000 (36-byte ids, ~700 KB), and a message that is already older than poller.max_age_sec (default 6 h) when first seen is remembered but never notified or queued, with a count in the log. Test: an old and a fresh message in one batch - old remembered and silent, fresh delivered. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…lines, not text The seen-state fixtures carried midnight timestamps that the new age gate rightly skips after 6 h; the stale-gate test matched the March text that #92's asker-context legitimately appends to the fresh line. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…(complements #95) After #92 widened the fetch window, a restart replayed months-old messages in quiet rooms: their ids had fallen off the capped seen file, so they read as new (claudemm, 2 Sep). Fix that does not depend on the cap and does not drop a legitimate backlog: RoomHistory keeps a per-room watermark (newest created_at ever processed or seeded, persisted under "_marks" in the history file). A fetched message older than the watermark by more than 120 s and not in the seen set is marked seen and skipped, never notified. Anything newer than the watermark is delivered no matter how old, so a poller that was down for hours still delivers what it missed (#90 item 1's promise). seedRoom() sets the watermark; classifyFetched() is the exported decision; the loop logs how many stale messages it swallowed per room. Tests: unit (marks persist, stale/new/seen classification, tolerance, no watermark = nothing stale) and a live poller run with the curl stub: seed sets the mark, a wide window with three evicted-old messages and one new one notifies only the new one and remembers all ids. Suite 314/314. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Items 2, 6 and 7 of #90 (petrus: "go ahead").
Problem. A mention reached the agent with a 100-character preview of its parent, or
target … not in recent windowwhen the parent was older than the 10-message fetch, and the room's settled facts lived in messages the agent never saw. Tonight hermes read "new card" as a GPU card for exactly this reason. The room API has no single-message fetch and ignores pagination, but every poller already sees every message pass by.Change.
src/common/room-history.mjs:RoomHistory, a per-room JSON history next toseen_file(400 messages per room, saved every poll), plusthreadSuffix(parent in full with author and time, up to two ancestors),previousSuffix(the asker's previous message),stateOfPlayLine(messages startingstate:orsettled:, deduped, newest wins, one line) andownLastPostLine.reply-context.resolveReplyTargets(msgs, lookup): an optional lookup resolves targets outside the batch through the history; embedded/batch snippet 120 to 300 chars. Existing callers unchanged.room-poller: fetch 25 per poll (poller.fetch_limit), remember every batch, one deeper fetch (100) per room per poll when a reply target is still unknown; each notification line now carries body (400) + thread + the asker's previous message for owner messages and mentions. When a room's batch contains an owner message or a mention, oneSTATE OF PLAYline and oneYOUR LAST POST HEREline are prepended for that room.poller.history_file,poller.fetch_limitand thestate:convention; the codex example config gets ahistory_file.Tests. 7 new in
test/room-history.test.mjs(persist/cap/dedupe, cross-poll target resolution, bounded chain, unresolved marker, previous message, state entries, own last post); suite 304/304.Not in this PR. Item 3 (addressing gate, needs #78 reactions), item 5 (post budget). Item 1 (per-message seen-save) is claudemm's and touches the same
pollRoomsloop; whichever merges second rebases.🤖 Generated with Claude Code