Skip to content

fix(memory/hindsight): flush buffered turns on session exit - #45

Open
hashbender wants to merge 1 commit into
mainfrom
mirror/pr-55936
Open

fix(memory/hindsight): flush buffered turns on session exit#45
hashbender wants to merge 1 commit into
mainfrom
mirror/pr-55936

Conversation

@hashbender

Copy link
Copy Markdown
Owner

Problem

When retain_every_n_turns > 1, sync_turn() only enqueues a retain every Nth turn. Turns 1..N-1 accumulate in _session_turns and are silently discarded on exit (Ctrl-D, /quit, /exit).

The plugin already handles this for session switches — on_session_switch() flushes buffered turns before clearing (it explicitly calls out this data-loss class in its docstring). But the exit path goes through on_session_end() + shutdown():

  • on_session_end() — base class implementation is a no-op (Hindsight plugin does not override it)
  • shutdown() — only drains already-queued retains from the writer thread; does not flush buffered-but-unqueued turns from _session_turns

Fix

Adds on_session_end() to HindsightMemoryProvider that flushes the unretained delta through the same writer queue. Since on_session_end is called before shutdown_all in the exit sequence (cli.py:1049run_agent.py:3141), the flush is enqueued first, then shutdown()'s sentinel follows — the writer processes the flush before exiting (FIFO).

Refactor

The flush logic is extracted into a shared _enqueue_buffered_flush() helper. Both on_session_end and on_session_switch now call it, removing 36 lines of duplication. on_session_switch shrinks from 127 to 82 lines.

The helper handles: metadata building, lineage tags, content serialization, the _flush closure, and writer-queue routing. The caller handles:

  • Which turns to send: on_session_end sends only the unretained delta (avoids append-mode duplicates); on_session_switch sends all turns (it clears the buffer anyway)
  • When to resolve document_id: on_session_switch must resolve before session rotation; on_session_end uses current state

Design choices

  • No-op when retain_every_n_turns=1 (default): _session_turns is empty at exit because every turn was already retained via sync_turn. The first guard (if not self._session_turns: return) makes the patch zero-risk for the default config.
  • Delta-only in append mode: on_session_end slices from _last_retained_turn_count to avoid duplicating turns the server already has. In overwrite mode (legacy servers), it resends everything.
  • Best-effort: failures are caught and logged as warnings — same as on_session_switch. No crash or data corruption if the API is down.
  • Does not clear _session_turns: the provider is being torn down, not rotated. No need to reset state that's about to be destroyed.

Verification

All 116 existing Hindsight plugin tests pass, including all 5 TestSessionSwitchBufferFlush tests that verify the refactored on_session_switch still flushes correctly.

116 passed in 1.76s

Not covered by this patch

The atexit hook (_atexit_shutdown, line 1143) calls shutdown() directly, bypassing on_session_end. This is a pre-existing safety-net limitation for unclean interpreter exits — fixing it would require registering the flush in the atexit handler, which is a separate change.


Mirror-of: NousResearch#55936
NousResearch#55936

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