Skip to content

fix(acp): preserve persisted session history on save failures#13675

Closed
Junass1 wants to merge 1 commit into
NousResearch:mainfrom
Junass1:fix/acp-history-persistence-atomicity
Closed

fix(acp): preserve persisted session history on save failures#13675
Junass1 wants to merge 1 commit into
NousResearch:mainfrom
Junass1:fix/acp-history-persistence-atomicity

Conversation

@Junass1

@Junass1 Junass1 commented Apr 21, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Add SessionDB.replace_messages() to atomically rewrite a session's messages.
  • Use the atomic replacement path for ACP session persistence.
  • Add regression coverage proving a failed ACP save does not erase previously persisted history.

Why

ACP session persistence previously cleared stored messages before appending the current in-memory history. If one of the later appends failed, for example because structured tool call data could not be serialized, the already persisted conversation was lost. That could break session restore after an editor/server restart.

The new path pre-serializes message fields before the write transaction, then deletes and inserts messages in one transaction. Bad input now leaves the previously persisted conversation intact.

Test Plan

  • uv run --extra dev python -m pytest tests/acp/test_session.py::TestPersistence::test_save_session_preserves_existing_messages_on_append_failure -q
  • uv run --extra dev python -m pytest tests/acp/test_session.py -q
  • git diff --check -- acp_adapter/session.py hermes_state.py tests/acp/test_session.py

@alt-glitch alt-glitch added type/bug Something isn't working comp/acp Agent Communication Protocol adapter labels Apr 21, 2026
teknium1 pushed a commit that referenced this pull request May 5, 2026
ACP's save_session() did a non-atomic clear_messages() + append_message()
loop. If any message hit an exception mid-loop (bad tool_call shape, etc.),
the DELETE had already committed and the persisted conversation was lost.

SessionDB.replace_messages() wraps DELETE + bulk INSERT in a single
BEGIN IMMEDIATE transaction that rolls back on any exception, so a bad
message can no longer clobber previously-persisted history.

Salvages @Awsh1's PR #13675 — uses the existing replace_messages()
helper (which covers more message fields than the PR's own copy)
instead of adding a duplicate.
teknium1 added a commit that referenced this pull request May 5, 2026
teknium1 pushed a commit that referenced this pull request May 5, 2026
ACP's save_session() did a non-atomic clear_messages() + append_message()
loop. If any message hit an exception mid-loop (bad tool_call shape, etc.),
the DELETE had already committed and the persisted conversation was lost.

SessionDB.replace_messages() wraps DELETE + bulk INSERT in a single
BEGIN IMMEDIATE transaction that rolls back on any exception, so a bad
message can no longer clobber previously-persisted history.

Salvages @Awsh1's PR #13675 — uses the existing replace_messages()
helper (which covers more message fields than the PR's own copy)
instead of adding a duplicate.
teknium1 added a commit that referenced this pull request May 5, 2026
RationallyPrime pushed a commit to RationallyPrime/hermes-agent that referenced this pull request May 8, 2026
ACP's save_session() did a non-atomic clear_messages() + append_message()
loop. If any message hit an exception mid-loop (bad tool_call shape, etc.),
the DELETE had already committed and the persisted conversation was lost.

SessionDB.replace_messages() wraps DELETE + bulk INSERT in a single
BEGIN IMMEDIATE transaction that rolls back on any exception, so a bad
message can no longer clobber previously-persisted history.

Salvages @Awsh1's PR NousResearch#13675 — uses the existing replace_messages()
helper (which covers more message fields than the PR's own copy)
instead of adding a duplicate.
RationallyPrime pushed a commit to RationallyPrime/hermes-agent that referenced this pull request May 8, 2026
nickdlkk pushed a commit to nickdlkk/hermes-agent that referenced this pull request May 11, 2026
ACP's save_session() did a non-atomic clear_messages() + append_message()
loop. If any message hit an exception mid-loop (bad tool_call shape, etc.),
the DELETE had already committed and the persisted conversation was lost.

SessionDB.replace_messages() wraps DELETE + bulk INSERT in a single
BEGIN IMMEDIATE transaction that rolls back on any exception, so a bad
message can no longer clobber previously-persisted history.

Salvages @Awsh1's PR NousResearch#13675 — uses the existing replace_messages()
helper (which covers more message fields than the PR's own copy)
instead of adding a duplicate.
nickdlkk pushed a commit to nickdlkk/hermes-agent that referenced this pull request May 11, 2026
rmulligan pushed a commit to rmulligan/hermes-agent that referenced this pull request May 11, 2026
ACP's save_session() did a non-atomic clear_messages() + append_message()
loop. If any message hit an exception mid-loop (bad tool_call shape, etc.),
the DELETE had already committed and the persisted conversation was lost.

SessionDB.replace_messages() wraps DELETE + bulk INSERT in a single
BEGIN IMMEDIATE transaction that rolls back on any exception, so a bad
message can no longer clobber previously-persisted history.

Salvages @Awsh1's PR NousResearch#13675 — uses the existing replace_messages()
helper (which covers more message fields than the PR's own copy)
instead of adding a duplicate.
rmulligan pushed a commit to rmulligan/hermes-agent that referenced this pull request May 11, 2026
JinyuID pushed a commit to JinyuID/hermes-agent that referenced this pull request May 11, 2026
ACP's save_session() did a non-atomic clear_messages() + append_message()
loop. If any message hit an exception mid-loop (bad tool_call shape, etc.),
the DELETE had already committed and the persisted conversation was lost.

SessionDB.replace_messages() wraps DELETE + bulk INSERT in a single
BEGIN IMMEDIATE transaction that rolls back on any exception, so a bad
message can no longer clobber previously-persisted history.

Salvages @Awsh1's PR NousResearch#13675 — uses the existing replace_messages()
helper (which covers more message fields than the PR's own copy)
instead of adding a duplicate.
JinyuID pushed a commit to JinyuID/hermes-agent that referenced this pull request May 11, 2026
jsboige pushed a commit to jsboige/hermes-agent that referenced this pull request May 14, 2026
ACP's save_session() did a non-atomic clear_messages() + append_message()
loop. If any message hit an exception mid-loop (bad tool_call shape, etc.),
the DELETE had already committed and the persisted conversation was lost.

SessionDB.replace_messages() wraps DELETE + bulk INSERT in a single
BEGIN IMMEDIATE transaction that rolls back on any exception, so a bad
message can no longer clobber previously-persisted history.

Salvages @Awsh1's PR NousResearch#13675 — uses the existing replace_messages()
helper (which covers more message fields than the PR's own copy)
instead of adding a duplicate.
jsboige pushed a commit to jsboige/hermes-agent that referenced this pull request May 14, 2026
gweeteve pushed a commit to gweeteve/hermes-agent that referenced this pull request Jun 2, 2026
ACP's save_session() did a non-atomic clear_messages() + append_message()
loop. If any message hit an exception mid-loop (bad tool_call shape, etc.),
the DELETE had already committed and the persisted conversation was lost.

SessionDB.replace_messages() wraps DELETE + bulk INSERT in a single
BEGIN IMMEDIATE transaction that rolls back on any exception, so a bad
message can no longer clobber previously-persisted history.

Salvages @Awsh1's PR NousResearch#13675 — uses the existing replace_messages()
helper (which covers more message fields than the PR's own copy)
instead of adding a duplicate.
gweeteve pushed a commit to gweeteve/hermes-agent that referenced this pull request Jun 2, 2026
Egavasyug pushed a commit to Egavasyug/hermes-agent that referenced this pull request Jun 10, 2026
ACP's save_session() did a non-atomic clear_messages() + append_message()
loop. If any message hit an exception mid-loop (bad tool_call shape, etc.),
the DELETE had already committed and the persisted conversation was lost.

SessionDB.replace_messages() wraps DELETE + bulk INSERT in a single
BEGIN IMMEDIATE transaction that rolls back on any exception, so a bad
message can no longer clobber previously-persisted history.

Salvages @Awsh1's PR NousResearch#13675 — uses the existing replace_messages()
helper (which covers more message fields than the PR's own copy)
instead of adding a duplicate.
Egavasyug pushed a commit to Egavasyug/hermes-agent that referenced this pull request Jun 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/acp Agent Communication Protocol adapter type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants