hermes - ✅(Solved) Fix messages.timestamp is always DB write-time, lost during fork/compress/branch [1 pull requests, 1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
NousResearch/hermes-agent#28841Fetched 2026-05-20 04:01:33
View on GitHub
Comments
0
Participants
1
Timeline
8
Reactions
0
Author
Participants
Timeline (top)
labeled ×6cross-referenced ×2

Root Cause

  • append_message() always binds time.time() to the timestamp column. No caller can supply an explicit value.
  • replace_messages() assigns a single time.time() base to every message. The msg["timestamp"] field, which is already present in the dict when loaded from get_messages(), is never read.
  • _flush_messages_to_session_db() and append_to_transcript() call append_message() without forwarding the message's existing timestamp field.

Fix Action

Fixed

PR fix notes

PR #28977: fix: preserve message timestamps across fork/compress/branch

Description (problem / solution / changelog)

Summary

Fixes #28841 — messages.timestamp is always set to DB write-time (time.time()), silently discarding original timestamps when sessions are forked, compressed, or branched via /retry, /undo, or /compress.

Root Cause

Two sites in hermes_state.py:

  1. append_message() (L1492): hardcodes time.time() — no way to pass an explicit timestamp.
  2. replace_messages() (L1537/1575): sets now_ts = time.time() once and stamps every row with that single value, ignoring any existing msg["timestamp"].

Fix

hermes_state.py (+3/-1)

  • append_message(): add optional timestamp: float = None parameter. Uses explicit value when provided, falls back to time.time().
  • replace_messages(): per-message msg.get("timestamp", now_ts) instead of uniform now_ts.

Caller updates (+4 lines)

Four callers that copy existing messages during fork/branch now forward timestamp=msg.get("timestamp"):

  • run_agent.py_flush_messages_to_session_db
  • cli.py/branch handler
  • gateway/run.py — gateway branch handler
  • tui_gateway/server.py — TUI branch handler

Two remaining callers (gateway/session.py, gateway/mirror.py) handle live messages where the default time.time() is correct — no change needed.

Testing

6 new tests in tests/test_hermes_state.py:

  • test_append_message_explicit_timestamp — explicit timestamp stored correctly
  • test_append_message_default_timestamp — default falls back to time.time()
  • test_replace_messages_preserves_timestamps — per-message timestamps retained
  • test_replace_messages_falls_back_to_now — missing timestamps default to now_ts
  • test_replace_messages_mixed_timestamps — explicit and default in same batch
  • test_fork_preserves_timestamps — end-to-end fork scenario

All 219 tests in test_hermes_state.py pass (6 new + 213 existing, zero regressions).

Backward Compatibility

Fully backward compatible:

  • timestamp parameter defaults to None → existing callers unchanged
  • replace_messages() falls back to now_ts for messages without timestamps
  • No schema migration needed — the timestamp column already exists

Co-Authored-By: zccyman [email protected]

Changed files

  • .dev-workflow/code-graph.db (added, +0/-0)
  • .dev-workflow/experiences.jsonl (added, +1/-0)
  • analyzed_issues.json (added, +62/-0)
  • cli.py (modified, +1/-0)
  • gateway/run.py (modified, +1/-0)
  • hermes_state.py (modified, +3/-2)
  • run_agent.py (modified, +1/-0)
  • tests/test_hermes_state.py (modified, +106/-0)
  • tui_gateway/server.py (modified, +1/-0)
RAW_BUFFERClick to expand / collapse

messages.timestamp in the SQLite database is always set to time.time() at INSERT time. There is no way for callers to supply an explicit timestamp, and the message dict's existing timestamp field is silently ignored during bulk rewrites.

As a result, when a session is forked (/branch), compressed, or rewritten (/retry//undo), every message gets a new DB write-time timestamp. A conversation that started at 05:00 can show every message timestamped at 08:00 — the time of the last compress or branch.

Downstream tooling that reads messages.timestamp — transcript export scripts, session analytics, /insights, and anything consuming the messages table — gets misleading wall-clock times.

To reproduce

  1. Start a conversation at time T0.
  2. Run /compress or /branch.
  3. Query messages.timestamp from SQLite for the child session.
  4. All timestamps now reflect the compress/branch time, not the original conversation time.

Root cause

  • append_message() always binds time.time() to the timestamp column. No caller can supply an explicit value.
  • replace_messages() assigns a single time.time() base to every message. The msg["timestamp"] field, which is already present in the dict when loaded from get_messages(), is never read.
  • _flush_messages_to_session_db() and append_to_transcript() call append_message() without forwarding the message's existing timestamp field.

Fix (minimal)

  1. hermes_state.py: Add optional timestamp: float = None parameter to append_message(). Read msg.get("timestamp") in replace_messages() per-message.
  2. 6 callers (run_agent.py, cli.py, gateway/run.py, tui_gateway/server.py, gateway/session.py, gateway/mirror.py): Forward msg.get("timestamp") to append_message(). Each change is +1 line.
  3. tests/test_hermes_state.py: 8 new tests verifying explicit timestamps, fallback behavior, mixed usage, and fork chain preservation.

Are you willing to submit a PR for this?

  • I'd like to fix this myself and submit a PR

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

hermes - ✅(Solved) Fix messages.timestamp is always DB write-time, lost during fork/compress/branch [1 pull requests, 1 participants]