Large/long-running sessions can cause high memory usage during prompt execution because the compacted-history path hydrates the full session, including old tool outputs that are later discarded.
The relevant path appears to be:
// packages/opencode/src/session/message-v2.ts
filterCompactedEffect(sessionID) {
return filterCompacted(yield* stream(sessionID))
}
stream(sessionID) pages through all messages and hydrates all parts before filterCompacted(...) reduces the history. For sessions with large persisted tool outputs, this loads much more data than the model turn ultimately needs.
I reproduced this locally using a copied database and this sanitized profiler script:
https://gist.github.com/dsbrgg/b5229cccb1c816e204cb7e10e54d5f93
The script only reports memory usage, message/part counts, part type totals, and approximate serialized part size. It does not print session contents, tool output contents, heap snapshots, logs, or database data.
Observed on one large compacted session:
full stream:
messages: 3963
parts: 13932
approx part JSON: 197 MB
filterCompactedEffect result:
messages: 134
parts: 450
approx part JSON: ~1 MB
Despite returning a small compacted result, filterCompactedEffect still paid the full-session hydration cost first.
Profiler output:
stream mode:
RSS: 249 MB -> 547 MB, then 1162 MB after stats
heapUsed: 36 MB -> 532 MB
elapsed: 1291ms
messages: 3963
parts: 13932
approxPartJsonMB: 197
filter mode:
RSS: 254 MB -> 627 MB
heapUsed: 36 MB -> 531 MB
elapsed: 1290ms
returned messages: 134
returned parts: 450
approxPartJsonMB: ~1
page100 mode:
RSS: 247 MB -> 364 MB
elapsed: 61ms
messages: 100
parts: 335
This shows up as RSS spikes in active long sessions, especially where historical tool parts are large.
A possible fix is to make filterCompactedEffect two-phase:
- Load message rows and only small control parts needed for compaction decisions, such as compaction/subtask parts.
- Run compaction filtering.
- Hydrate full parts only for retained message IDs.
This keeps stream(sessionID) behavior unchanged for callers that really need full history, but avoids hydrating discarded large tool outputs during prompt execution.
Plugins
rtk-ai, ponytail
OpenCode version
1.17.10 via Homebrew; reproduced against local source 1.17.13
Steps to reproduce
- Use or create a long session with many historical tool parts, including large tool outputs.
- Continue the session and send another prompt.
- Observe prompt execution memory while
MessageV2.filterCompactedEffect(sessionID) runs.
- Compare current behavior to a bounded page load.
I used the sanitized profiler script here:
https://gist.github.com/dsbrgg/b5229cccb1c816e204cb7e10e54d5f93
Example commands:
OPENCODE_DB=/path/to/copied.db \
bun --expose-gc run profile-opencode-session-hydration.ts <sessionID> stream
OPENCODE_DB=/path/to/copied.db \
bun --expose-gc run profile-opencode-session-hydration.ts <sessionID> filter
OPENCODE_DB=/path/to/copied.db \
bun --expose-gc run profile-opencode-session-hydration.ts <sessionID> page100
Expected signal:
stream hydrates all historical messages/parts.
filter returns a compacted subset but currently still pays the full hydration cost first.
page100 shows bounded loading is much cheaper.
Screenshot and/or share link
N/A
Operating System
macOS
Terminal
Alacritty
Large/long-running sessions can cause high memory usage during prompt execution because the compacted-history path hydrates the full session, including old tool outputs that are later discarded.
The relevant path appears to be:
stream(sessionID)pages through all messages and hydrates all parts beforefilterCompacted(...)reduces the history. For sessions with large persisted tool outputs, this loads much more data than the model turn ultimately needs.I reproduced this locally using a copied database and this sanitized profiler script:
https://gist.github.com/dsbrgg/b5229cccb1c816e204cb7e10e54d5f93
The script only reports memory usage, message/part counts, part type totals, and approximate serialized part size. It does not print session contents, tool output contents, heap snapshots, logs, or database data.
Observed on one large compacted session:
Despite returning a small compacted result,
filterCompactedEffectstill paid the full-session hydration cost first.Profiler output:
This shows up as RSS spikes in active long sessions, especially where historical tool parts are large.
A possible fix is to make
filterCompactedEffecttwo-phase:This keeps
stream(sessionID)behavior unchanged for callers that really need full history, but avoids hydrating discarded large tool outputs during prompt execution.Plugins
rtk-ai, ponytail
OpenCode version
1.17.10 via Homebrew; reproduced against local source 1.17.13
Steps to reproduce
MessageV2.filterCompactedEffect(sessionID)runs.I used the sanitized profiler script here:
https://gist.github.com/dsbrgg/b5229cccb1c816e204cb7e10e54d5f93
Example commands:
Expected signal:
streamhydrates all historical messages/parts.filterreturns a compacted subset but currently still pays the full hydration cost first.page100shows bounded loading is much cheaper.Screenshot and/or share link
N/A
Operating System
macOS
Terminal
Alacritty