Skip to content

fix(run_agent): clamp _last_flushed_db_idx after scaffolding drop (#31507)#31517

Open
0xsir0000 wants to merge 1 commit into
NousResearch:mainfrom
0xsir0000:fix/flush-idx-overshoot-31507
Open

fix(run_agent): clamp _last_flushed_db_idx after scaffolding drop (#31507)#31517
0xsir0000 wants to merge 1 commit into
NousResearch:mainfrom
0xsir0000:fix/flush-idx-overshoot-31507

Conversation

@0xsir0000

Copy link
Copy Markdown
Contributor

Summary

_persist_session runs _drop_trailing_empty_response_scaffolding before _flush_messages_to_session_db. If an earlier intermediate flush advanced _last_flushed_db_idx to a length that included the scaffolding tail, the drop leaves the index strictly greater than len(messages). The next flush computes flush_from = max(start_idx, _last_flushed_db_idx) >= len(messages), slices messages[flush_from:] to empty, and silently writes nothing — including any final assistant response appended after the drop. CLI sessions that reuse the same AIAgent across turns are the common trigger, which is why state.db loses assistant rows while the WebUI mirror keeps the full transcript.

Fix

Resetting flush_from to start_idx (as the report suggested) would re-flush messages already persisted, so instead clamp _last_flushed_db_idx down to len(messages) right after the scaffolding drop. New messages appended on the next turn are then flushed exactly once — preserving the #860 dedup invariant.

  • run_agent.py: clamp _last_flushed_db_idx after _drop_trailing_empty_response_scaffolding
  • tests/run_agent/test_empty_response_recovery_persistence.py: two new regression tests (one direct, one end-to-end against a real SessionDB); existing stub fixture updated to seed the new attribute it now reads

Test plan

  • pytest tests/run_agent/test_empty_response_recovery_persistence.py — 5 passed (2 existing + 3 new)
  • pytest tests/run_agent/test_860_dedup.py tests/run_agent/test_compression_persistence.py tests/run_agent/test_message_sequence_repair.py tests/run_agent/test_413_compression.py — 40 passed (dedup / compression / repair invariants unchanged)
  • pytest tests/cli/test_branch_command.py tests/cli/test_cli_new_session.py — 23 passed (other _last_flushed_db_idx consumers unaffected)

Fixes #31507

…usResearch#31507)

`_persist_session` runs `_drop_trailing_empty_response_scaffolding`
before `_flush_messages_to_session_db`. If an earlier intermediate flush
advanced `_last_flushed_db_idx` to a length that included the
scaffolding tail, the drop leaves the index strictly greater than
`len(messages)`. The next flush computes
`flush_from = max(start_idx, _last_flushed_db_idx) >= len(messages)`,
slices `messages[flush_from:]` to an empty list, and silently writes
nothing — including any final assistant response appended after the
drop. CLI sessions that reuse the same `AIAgent` across turns are the
common trigger, which is why state.db is missing assistant rows while
the WebUI mirror keeps the full transcript.

Resetting `flush_from` to `start_idx` (as the report suggested) would
re-flush messages already persisted, so instead clamp
`_last_flushed_db_idx` to `len(messages)` right after the drop. New
messages appended in the next turn are then flushed exactly once.

Tests cover both the direct invariant (clamp happens, flush is invoked)
and an end-to-end SessionDB scenario where the post-drop assistant
response must land in the DB.

Fixes NousResearch#31507
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/agent Core agent loop, run_agent.py, prompt builder P1 High — major feature broken, no workaround type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

State.db loses assistant responses when _drop_trailing_empty_response_scaffolding causes _last_flushed_db_idx to overshoot len(messages)

2 participants