Skip to content

A long chat is no longer read in full on every prompt - #11

Merged
TheFabFab merged 1 commit into
documentor-pgfrom
fix/bounded-history-lazy-catalogue
Oct 22, 2026
Merged

TheFabFab merged 1 commit into
documentor-pgfrom
fix/bounded-history-lazy-catalogue

Conversation

@TheFabFab

Copy link
Copy Markdown
Owner

Every prompt read the whole session three times. On a long chat that dominated the opencode process's memory and time.

What changes

  • Prompt loop history. MessageV2.filterCompactedEffect pages newest first and stops at the newest compaction. Before, MessageV2.stream() loaded every message on each model step, and filterCompacted then threw most of them away. Upstream v1.19.3 has the same eager read. Both the paged read and the sync filterCompacted run one walk, compactedScanner, so they cannot drift apart.
  • prune walks through a new Session.scanNewestFirst and stops where its loop already stopped (newest summary, first cleared output), instead of loading the session first.
  • ACP usage_update reads the newest 20 messages and sends no cost. The total needed every message over the in-process HTTP API on each prompt. cost is optional in ACP, and DocuMentor reads only used and size (AcpChunkMapper.handleUsageUpdate).

Measured

A compiled --single build, driven over ACP stdio the way DocuMentor's StdioAgentClient does, against a fake OpenAI-compatible LLM and local Postgres 18. The synthetic chat has 2,979 messages and 68 MB of part JSON with a compaction near the end. Fresh process, session/resume, then 3 prompts, two runs each:

before this PR + DocuMentor's trimmed catalogue
peak RSS per prompt 994–1,383 MB 356–398 MB
time per prompt 4.0–6.3 s 0.2–0.4 s

The history changes alone, measured separately, took the peak from 1.04–1.64 GB to 0.58–0.69 GB, the same as a short chat. The rest comes from the models catalogue: the DocuMentor PR gives each chat process a catalogue cut to the model policy's providers, about 200 MB per process.

Tests

  • test/pg/bounded-history.test.ts (new) holds the paged read to the full read (filterCompacted(stream())) for five cases: no compaction, a compaction without a tail, a retained tail that crosses a page boundary, two compactions, and an unfinished summary. It counts the MessageV2.page spans each reads, and checks that scanNewestFirst stops where its visitor stops.
  • Upstream's prune tests now exercise the paged walk.
  • Local run, as root: bun test test/session test/acp test/pg gives 637 pass, 0 fail.

Not in this PR

Expanding the models catalogue lazily inside Provider was tried and measured, then left out. It saved only 40–80 MB: 23 providers with built-in loaders still expand (2,546 models), and the core v2 catalogue (core/src/plugin/models-dev.ts) still writes all 9,142 models. A trimmed OPENCODE_MODELS_PATH shrinks all three consumers at once, so it lives in DocuMentor.

🤖 Generated with Claude Code

https://claude.ai/code/session_0148RPMT9kUkZ6BL4TtebZJe

Three readers loaded every message and part of a session on each prompt:
the prompt loop's history (MessageV2.stream, on every model step, before
filterCompacted kept only what follows the newest compaction), prune
after each turn, and the ACP usage update, which fetched the whole
session over HTTP to sum its cost. On a chat with 65 MB of history a
prompt peaked at 1.0-1.6 GB and took 4-6 s.

- filterCompactedEffect pages newest first through
  MessageV2.scanNewestFirst and stops where filterCompacted stops; both
  run the same walk (compactedScanner).
- prune walks through Session.scanNewestFirst and stops at the newest
  summary or the first cleared output, as its loop already did.
- The usage update reads the newest 20 messages and sends no cost. ACP
  makes cost optional and DocuMentor reads only used and size.

test/pg/bounded-history.test.ts holds the paged read to the full one for
sessions without a compaction, with one, with a retained tail across a
page boundary, with two, and with an unfinished summary, and counts the
pages each reads.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0148RPMT9kUkZ6BL4TtebZJe
@github-actions

Copy link
Copy Markdown

This PR doesn't fully meet our contributing guidelines and PR template.

What needs to be fixed:

  • PR description is missing required template sections. Please use the PR template.

Please edit this PR description to address the above within 72 hours, or it will be automatically closed.

If you believe this was flagged incorrectly, please let a maintainer know.

@github-actions

Copy link
Copy Markdown

Hey! Your PR title A long chat is no longer read in full on every prompt doesn't follow conventional commit format.

Please update it to start with one of:

  • feat: or feat(scope): new feature
  • fix: or fix(scope): bug fix
  • docs: or docs(scope): documentation changes
  • chore: or chore(scope): maintenance tasks
  • refactor: or refactor(scope): code refactoring
  • test: or test(scope): adding or updating tests

Where scope is the package name (e.g., app, desktop, opencode).

See CONTRIBUTING.md for details.

@TheFabFab
TheFabFab merged commit 9c2ab82 into documentor-pg Oct 22, 2026
8 of 14 checks passed
@TheFabFab
TheFabFab deleted the fix/bounded-history-lazy-catalogue branch October 22, 2026 11:20
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.

1 participant