The OpenClaw runtime is calling the context engine's dispose() method after every conversation turn, rather than only at session end.
Expected behavior
dispose() should be called once when the session ends, as a final opportunity to flush any remaining buffered episodes to the backend. The lifecycle should be:
assemble() — inject context before inference
afterTurn() — buffer the new episode
compact() — called when context window fills up (ingests buffered episodes)
dispose() — called once at session end to flush remaining buffer
Actual behavior
dispose() is being called after every turn. This means:
- Buffered episodes are ingested immediately instead of being batched — each turn triggers
backend.ingest() for just 1 episode, defeating the buffering strategy
injectedItemIds is cleared every turn — the deduplication set that prevents re-injecting already-seen context items is wiped, so the same context can be injected repeatedly in subsequent assemble() calls
- Compaction never sees buffered messages — by the time
compact() runs, the buffer is always empty because dispose() already flushed it
Impact
- Excessive API calls to the backend (1 ingest per turn instead of batched)
- Context deduplication is broken — previously injected items may be re-injected
SlidingWindowEngine's compaction strategy is effectively bypassed since it never has buffered messages to work with
Relevant code
dispose() implementation: src/engine/default.ts:42-49, src/engine/sliding-window.ts:84-91
- Engine lifecycle base class:
src/engine/base.ts
- Plugin registration:
src/index.ts:53
The OpenClaw runtime is calling the context engine's
dispose()method after every conversation turn, rather than only at session end.Expected behavior
dispose()should be called once when the session ends, as a final opportunity to flush any remaining buffered episodes to the backend. The lifecycle should be:assemble()— inject context before inferenceafterTurn()— buffer the new episodecompact()— called when context window fills up (ingests buffered episodes)dispose()— called once at session end to flush remaining bufferActual behavior
dispose()is being called after every turn. This means:backend.ingest()for just 1 episode, defeating the buffering strategyinjectedItemIdsis cleared every turn — the deduplication set that prevents re-injecting already-seen context items is wiped, so the same context can be injected repeatedly in subsequentassemble()callscompact()runs, the buffer is always empty becausedispose()already flushed itImpact
SlidingWindowEngine's compaction strategy is effectively bypassed since it never has buffered messages to work withRelevant code
dispose()implementation:src/engine/default.ts:42-49,src/engine/sliding-window.ts:84-91src/engine/base.tssrc/index.ts:53