fix(agent): clamp and rewind session flush cursor after repair_message_sequence compaction (#44837)#45260
Merged
Merged
Conversation
…he cursor Follow-up to the #44837 clamp: a min() clamp only fixes cursor overshoot past the new end of the list. When repair_message_sequence drops/merges messages at indexes below the cursor, the clamp leaves the cursor pointing past unflushed rows and the turn-end flush silently skips them. Extract repair_message_sequence_with_cursor(): snapshot the flushed prefix by object identity before repair, then recompute the cursor as the count of surviving flushed messages. Falls back to the clamp when no snapshot is available. Keeps the safety guard in _flush_messages_to_session_db. Adds targeted tests for overshoot, before-cursor compaction, no-repair, bare-agent, and the flush guard.
Contributor
🔎 Lint report:
|
| Rule | Count |
|---|---|
unresolved-attribute |
2 |
First entries
run_agent.py:2891: [unresolved-attribute] unresolved-attribute: Object of type `Self@get_credits_spent_micros` has no attribute `_credits_session_start_micros`
tests/run_agent/test_credits_notices_toggle.py:76: [unresolved-attribute] unresolved-attribute: Unresolved attribute `_credits_session_start_micros` on type `AIAgent`
✅ Fixed issues (1):
| Rule | Count |
|---|---|
invalid-assignment |
1 |
First entries
tests/run_agent/test_credits_notices_toggle.py:76: [invalid-assignment] invalid-assignment: Object of type `None` is not assignable to attribute `_credits_session_start_micros` of type `int`
Unchanged: 5703 pre-existing issues carried over.
Diagnostics are surfaced as warnings — this check never fails the build.
Contributor
|
Verified — the cursor repair logic is correct and well-tested. Checked:
CI: all checks passing. LGTM. |
Willhong
pushed a commit
to Willhong/hermes-agent
that referenced
this pull request
Jun 13, 2026
…nt drops (NousResearch#43936) The session-DB flush in `_flush_messages_to_session_db` was position-based (`messages[max(start_idx, _last_flushed_db_idx):]`). It assumes `messages == conversation_history + this turn's new messages`, which breaks two ways, both reproduced live: 1. Overlapping turns on the cached agent corrupt the shared `_last_flushed_db_idx` (it indexes the turn-local `messages` array) → the earlier completed turn flushes an empty slice and its delivered assistant row is never written. 2. `repair_message_sequence` compacts `messages` in place below `len(conversation_history)`, so `start_idx > len(messages)` permanently → self-reinforcing drop loop. The merged NousResearch#44837 fix (NousResearch#45260) clamps `_last_flushed_db_idx` to `len(messages)` but leaves `start_idx = len(conversation_history)` unbounded, so shape 2 is still an empty slice. No index arithmetic survives the two arrays diverging. Replace the positional slice with identity-based dedup: stamp each persisted dict with `_db_persisted`, recognize this turn's `conversation_history` entries by object identity (`id()`) and stamp instead of re-write. A message is written exactly once regardless of how the arrays shift; the NousResearch#860 and NousResearch#31507 guards hold by construction. `_db_persisted` is underscore-prefixed and already stripped from API payloads. The compression split clears the stamps so the surviving messages re-write into the new session row. `_last_flushed_db_idx` is kept updated for legacy readers but no longer decides what is written. Complementary to NousResearch#43962 (backfills delivered text after an interrupt in turn_finalizer.py); this prevents the drop at the flush layer and also covers the non-interrupt repair-shrink path. Regression: tests/run_agent/test_identity_flush.py (5, incl. an explicit test that the NousResearch#44837 clamp still drops where identity flush persists) + test_message_sequence_repair, test_860_dedup, test_compression_persistence all GREEN.
2 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Salvages PR #44870 by @kyssta-exe (fixes #44837) onto current main and widens the fix: the flush cursor is now recomputed exactly after
repair_message_sequence()compacts the message list, not just clamped to the new length.A
min()clamp only catches cursor overshoot past the new end. When repair drops/merges messages at indexes below the cursor, the clamp leaves it pointing past unflushed rows and the turn-end flush silently skips the assistant/tool chain — the malformed-transcript loop reported in #44837 ("agent dementia" looping).Changes
agent/agent_runtime_helpers.py: newrepair_message_sequence_with_cursor()— snapshots the flushed prefix by object identity before repair, recomputes the cursor as the count of surviving flushed messages; falls back to the clamp when no snapshot is availableagent/conversation_loop.py: pre-API repair now goes through the cursor-aware helper (cherry-picked clamp from fix(agent): clamp flush cursor after repair_message_sequence compacts messages (#44837) #44870, then refactored)run_agent.py: safety guard in_flush_messages_to_session_dbclamping a stale cursor (from fix(agent): clamp flush cursor after repair_message_sequence compacts messages (#44837) #44870, unchanged)tests/run_agent/test_message_sequence_repair.py: 5 new tests — overshoot clamp, before-cursor rewind, no-repair no-op, bare agent, flush-guardValidation
Contributor commit cherry-picked with authorship preserved. Merge with rebase.
Infographic