Skip to content

emrg: list_history can answer with the session's records, and paging stops moving - #1476

Merged
argszero merged 1 commit into
masterfrom
fix/history-replays-records
Sep 20, 2026
Merged

argszero merged 1 commit into
masterfrom
fix/history-replays-records

Conversation

@argszero

Copy link
Copy Markdown
Owner

Server half of the host rant 2026-09-20T18:58:44 (work order: #1472).

The defect

list_history answers with a display projection, not with the session's records.
It keeps only type == "message" (a tool_result is never emitted), skips
assistant records whose content is empty but which carry tool_calls, and attaches
preview = content[:80] + "…" — which the GUI reads as preview || content, so the
full body can never be reached. On a real task session (103 records: 1 summary, 50
tool-call-only assistant records, 50 tool results) the current listing returns
0 items.

Paging has the same shape of problem: the offset counts back from the newest on the
filtered list, so an append between two pages shifts the window (duplicates), and a
compaction that shrank the file makes an offset name a different position — returning
a short page with has_more: false and stranding the older records for good. The
cut also lands by item count, so it can fall between an assistant(tool_calls) and
its tool_result.

The fix

include_records: true answers with the raw record sequence — message records of
both roles including the tool-call-only assistant records the display path drops,
and every tool_result, in record order, each carrying its absolute
record_index — and does not attach preview in that mode (the preview stays in
the legacy shape, which is what /rewind reads). Paging in that mode is a cursor
(before_index, an absolute record index) instead of an offset from the newest, with
three properties the tests pin:

  1. an append does not move the cursor;
  2. a cursor past the end is clamped rather than answered with an empty page and
    has_more: false (a compaction that shrank the file must not strand the older
    records);
  3. a page never begins at a tool_result — the cut is widened down to the assistant
    record that carries the call, so a pair is never split.

The legacy shape is untouched, so /rewind and the TUI path are unchanged.

Measured, not asserted

Four tests in TestWSHistoryRecords, one decision each
(-k "HistoryRecords or HistoryPagination" → 9 passed), and four mutation arms, each
killing exactly its decision (no pair snap / stale cursor answered empty / cursor read
as offset / preview re-attached). uv run pytest tests/test_ws_e2e.py -q53
passed
.

Not in this PR

The client half: the GUI replay must reuse the live handler set fed by records mode
(parsing intent from tool_calls[].function.arguments) and Shell.tsx must stop
reading preview; the acceptance is the rant's equivalence test (the same turn fed
once as live frames and once as persisted records must produce item-for-item equal
entries). The system-prompt lines (⏸ Interrupted, queued, round cap) stay a
difference by an explicit host ruling.

@argszero argszero left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ LGTM — cycle cyc20260920-214143

Measured on this head (1acf70f5) in an isolated worktree: tests/test_ws_e2e.py53 passed. Two mutation arms: making the page cut land inside a tool pair (dropping the tool_result back-off) turns test_a_cut_never_lands_inside_a_tool_pair red; ignoring the before_index cursor and restoring the offset-from-newest window turns both test_an_appended_turn_does_not_move_the_cursor and test_a_stale_cursor_still_reaches_the_oldest_records red.

The three properties the rant asked for are each pinned separately, and the code matches: records mode answers with the raw record sequence (including the tool-call-only assistant records and every tool_result), each entry carries its absolute record_index, and preview is deliberately not attached in this mode — which is what the "81 of 5064 chars" defect came from.

@argszero argszero left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ LGTM — cycle cyc20260920-220537

Measured this cycle: tests/test_ws_e2e.py53 passed on this head (1acf70f5); the plan of all six votable PRs (#1459 -> #1474 -> #1475 -> #1476 -> #1479 -> #1480; check-merge-order.py reports 0 of 15 pairs conflicting, and merging any of them dirties nothing else) -> final tree 7539922e5ecd, suite OK 4416 passed / 22 skipped.

Read against the three properties the server half has to provide, each visible in the code rather than only in a test:

  • the answer is the raw record sequence — both roles' message records, including the tool-call-only assistant records the display path drops, plus every tool_result;
  • every entry carries its absolute record_index, which is what makes the cursor addressable at all;
  • preview is deliberately not attached in this mode — the 80-char /rewind affordance whose use by the replay is the "5064 characters shown as 81" defect.

Paging is the before_index cursor, and the three decisions are in the code: an absolute cursor cannot be moved by an append; a cursor past a history a compaction shrank is clamped rather than answered empty; and the page start walks back off tool_result records so a page never begins with results whose call sits on the previous page.

@argszero argszero left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ LGTM — cycle cyc20260920-224215

Measured on this head (1acf70f5) in an isolated worktree: tests/test_ws_e2e.py 53 passed. Arm this cycle (HOME/TMPDIR pinned to scratch): reverting emrg/server/daemon.py to master turns the new records/paging tests red (4 failed / 49 passed, including test_a_stale_cursor_still_reaches_the_oldest_records). Production file restored byte-identically (sha256[:16] b3073e56ce232432).

Both CI legs green at this head; head unchanged since the earlier votes.

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.

1 participant