Skip to content

Bug: User messages stored twice in state.db when agent and gateway both write to SQLite #42039

@UniGood

Description

@UniGood

Description

User messages from platform adapters (confirmed on Feishu, likely affects all platforms) are stored twice in the messages table of state.db. One entry has platform_message_id, the other doesn't.

Root Cause

There are two independent write paths using two different SessionDB instances that both connect to the same SQLite file:

  1. Agent's _flush_messages_to_session_db() (run_agent.py:1533)

    • Called during run_conversation() via _post_run_persist (line 1473)
    • Uses the SessionDB instance passed from GatewayRunner._session_db (line 13804)
    • Does not include platform_message_id in the write
    • Writes first (during agent execution)
  2. Gateway's session_store.append_to_transcript() (gateway/run.py:8899)

    • Called after run_conversation() returns
    • Uses SessionStore._db — a separate SessionDB instance (created at session.py:705)
    • Includes platform_message_id from event.message_id
    • Writes second (after agent returns)

The skip_db mechanism

There is a skip_db mechanism at line 8879-8901 designed to prevent this:

agent_persisted = self._session_db is not None
# ...
self.session_store.append_to_transcript(
    session_entry.session_id, entry,
    skip_db=agent_persisted,
)

This works for the normal path (else branch at line 8874). However, there are two fallback paths that completely bypass skip_db:

# Path 1: agent_failed_early (line 8844-8855) — NO skip_db
_user_entry = {"role": "user", "content": message_text, "timestamp": ts}
if event.message_id:
    _user_entry["message_id"] = str(event.message_id)
self.session_store.append_to_transcript(session_entry.session_id, _user_entry)

# Path 2: not new_messages fallback (line 8861-8873) — NO skip_db
if not new_messages:
    _user_entry = {"role": "user", "content": message_text, "timestamp": ts}
    if event.message_id:
        _user_entry["message_id"] = str(event.message_id)
    self.session_store.append_to_transcript(session_entry.session_id, _user_entry)

When either fallback triggers, the gateway writes the user message unconditionally, regardless of whether the agent already persisted it.

Reproduction

  1. Send a message via any platform (confirmed with Feishu)
  2. Query state.db for the session's messages:
SELECT id, role, content, platform_message_id, timestamp
FROM messages
WHERE session_id = '<session_id>' AND role = 'user'
ORDER BY timestamp;
  1. Observe duplicate user entries — one with platform_message_id, one without.

Evidence

From session 20260608_155640_bba43481:

id=32737 role=user ts=1780914776.291 platform_message_id=None     content="明白了"
id=32738 role=user ts=1780914781.082 platform_message_id=om_xxx   content="明白了"

id=32679 role=user ts=1780912724.451 platform_message_id=None     content="是的"
id=32680 role=user ts=1780912736.180 platform_message_id=om_xxx   content="是的"

id=32657 role=user ts=1780911959.371 platform_message_id=None     content="嗯呢"
id=32658 role=user ts=1780911974.459 platform_message_id=om_xxx   content="嗯呢"

Pattern: 7 duplicate pairs in this session alone. The entry without platform_message_id always comes first (from agent), the one with it comes 5-24 seconds later (from gateway).

Suggested Fix

Add skip_db to both fallback paths in gateway/run.py:

# Fix for agent_failed_early path (line 8844)
agent_persisted = self._session_db is not None
self.session_store.append_to_transcript(
    session_entry.session_id, _user_entry,
    skip_db=agent_persisted,
)

# Fix for not new_messages fallback (line 8861)
agent_persisted = self._session_db is not None
self.session_store.append_to_transcript(
    session_entry.session_id, _user_entry,
    skip_db=agent_persisted,
)
if response:
    self.session_store.append_to_transcript(
        session_entry.session_id,
        {"role": "assistant", "content": response, "timestamp": ts},
        skip_db=agent_persisted,
    )

Environment

  • Hermes Agent version: fork of NousResearch/hermes-agent (synced with main as of 2026-06-08)
  • Platform: Feishu (likely affects all platforms)
  • Model: xiaomi/mimo-v2.5-pro
  • OS: Ubuntu 24.04

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Medium — degraded but workaround existscomp/gatewayGateway runner, session dispatch, deliverytype/bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions