Skip to content

refactor(sessions): salvage #29182 + opt-in JSON snapshot writer#29278

Merged
teknium1 merged 8 commits into
mainfrom
hermes/hermes-5db05717
May 20, 2026
Merged

refactor(sessions): salvage #29182 + opt-in JSON snapshot writer#29278
teknium1 merged 8 commits into
mainfrom
hermes/hermes-5db05717

Conversation

@teknium1

Copy link
Copy Markdown
Contributor

Salvage of #29182. The default ~/.hermes/sessions/session_{sid}.json snapshot writer is gone; users with external tooling that consumes those files can opt back in via sessions.write_json_snapshots: true.

Changes (vs #29182)

  • All six original commits from @yoniebans cherry-picked onto current main (authorship preserved).
  • New: sessions.write_json_snapshots (default False) — DEFAULT_CONFIG entry in hermes_cli/config.py. Gates the writer per-agent.
  • Restored AIAgent._save_session_log + _clean_session_content as gated methods. When the flag is off the call is a fast no-op; when on, behavior matches pre-refactor(session-log): stop writing per-session JSON snapshots #29182 (atomic write, truncation guard, REASONING_SCRATCHPAD → think tag normalization).
  • Re-derive the target path from agent.session_id on each call so /branch and /compress re-points happen automatically — no need to restore the explicit re-point bookkeeping at call sites that the original PR removed.
  • Wire the single call site in _persist_session (the cleanup-on-exit hook). The 7 intra-turn calls refactor(session-log): stop writing per-session JSON snapshots #29182 deleted are NOT restored — they were redundant writes within the same turn that doubled disk I/O without adding any persistence guarantee _persist_session does not already provide.
  • TestNoSessionJsonSnapshotTestSessionJsonSnapshotOptIn: pins behavior (default off → no file, opt-in true → file written, no-op method on default agents, logs_dir retained unconditionally for request_dump_*.json).
  • Docs updated (CONTRIBUTING.md, bundled hermes-agent skill).
  • AUTHOR_MAP fix (separate commit): jonny@nousresearch.com was mapped to jquesnelle, but that email belongs to @yoniebans (GitHub id 5584832, name "jonny"). Jeffrey Quesnelle commits as emozilla@nousresearch.com. Verified across all 60 historical commits from that email — every one was a yoniebans commit being mis-credited.

Verified premise of #29182

  • _save_session_log + session_log_file had zero non-self readers across the codebase (trajectory, batch_runner, mcp_serve, plugins all clean).
  • hermes_state.py::_remove_session_files cleans up {sid}.json / {sid}.jsonl (gateway format, no session_ prefix) — different files from the agent's session_{sid}.json, so no cleanup-path regression.
  • The truncation guard PR fix(session): add /resume CLI handler, session log truncation guard, reopen_session API #3315 added is also gated — only relevant when the user opts in.
  • On Teknium's box this was ~20K files / 6.2GB (PR estimated ~950 / ~500MB; heavy users much worse).

Validation

Result
tests/run_agent/ 1374 passed, 3 skipped
tests/agent/ + tests/cli/ 3939 passed
tests/hermes_cli/ -k config 555 passed
E2E default (empty config.yaml) _session_json_enabled=False, no session_*.json written
E2E opt-in (sessions.write_json_snapshots: true) _session_json_enabled=True, session_{sid}.json written with correct shape

Closes #29182. Credit to @yoniebans for the original refactor.

yoniebans and others added 8 commits May 20, 2026 03:21
state.db now stores every message field the JSON snapshot stored. Removed
the method, all 7 call-sites, and ~13 test stubs that suppressed its file I/O.
Body is in git history if it ever needs to come back.
The attribute no longer exists; nothing to re-point.
Only caller was the removed _save_session_log. Also removes the unused
convert_scratchpad_to_think and has_incomplete_scratchpad imports from
run_agent.py (both still used elsewhere via their own imports).
…tespace

Adds TestNoSessionJsonSnapshot to lock the contract that session_log_file
attribute, _save_session_log method, and the per-session JSON snapshot
writer are gone. logs_dir is retained for request_dump_*.json.

Also cleans up stray trailing whitespace in test_run_agent_codex_responses
introduced when the _save_session_log stub line was deleted.
The email "jonny@nousresearch.com" belongs to @yoniebans (GitHub id
5584832, display name "jonny"), not to Jeffrey Quesnelle (@jquesnelle,
id 687076, who commits as emozilla@nousresearch.com).  Verified across
all 60 historical commits on the repo authored from this email — every
one of them was a yoniebans commit being mis-credited to jquesnelle in
the changelog.

Surfaced while salvaging PR #29182 (yoniebans's session-log refactor).
PR #29182 deleted the per-session JSON snapshot writer outright because
state.db is canonical and the snapshots had no in-tree consumer.  Some
users have external tooling that reads `~/.hermes/sessions/session_{sid}.json`
directly, so reintroduce the writer behind a config flag that defaults
to off.

- Add `sessions.write_json_snapshots` (default False) to DEFAULT_CONFIG
- Restore `AIAgent._save_session_log` + `_clean_session_content` as
  gated methods.  When the flag is off the call is a fast no-op; when
  on, the writer behaves as before (atomic write, truncation guard
  preserved, REASONING_SCRATCHPAD → think tag normalization)
- Re-derive the target path from `agent.session_id` on each call so
  `/branch` and `/compress` re-points happen automatically — no need
  to restore the explicit re-point bookkeeping at call sites
- Wire the single call site in `_persist_session` (the cleanup-on-exit
  hook).  Did NOT restore the 7 intra-turn calls the original PR deleted
  — those were redundant writes within the same turn that doubled disk
  I/O without adding any persistence guarantee `_persist_session` does
  not already provide
- Read the flag once at agent init via `load_config()`, cache as
  `agent._session_json_enabled`
- Update `TestNoSessionJsonSnapshot` → `TestSessionJsonSnapshotOptIn`
  to pin behavior: default off (no file), opt-in true (file written),
  no-op method on default agents, logs_dir retained unconditionally
- Update CONTRIBUTING.md and the bundled `hermes-agent` skill to
  document the flag and its default
@github-actions

Copy link
Copy Markdown
Contributor

🔎 Lint report: hermes/hermes-5db05717 vs origin/main

ruff

Total: 0 on HEAD, 0 on base (➖ 0)

🆕 New issues: none

✅ Fixed issues: none

Unchanged: 0 pre-existing issues carried over.

ty (type checker)

Total: 8963 on HEAD, 8974 on base (✅ -11)

🆕 New issues (1):

Rule Count
unresolved-attribute 1
First entries
run_agent.py:1544: [unresolved-attribute] unresolved-attribute: Object of type `Self@_save_session_log` has no attribute `logs_dir`

✅ Fixed issues (7):

Rule Count
invalid-assignment 4
unresolved-attribute 1
invalid-argument-type 1
unsupported-operator 1
First entries
cli.py:6507: [invalid-assignment] invalid-assignment: Object of type `Unknown` is not assignable to attribute `session_log_file` on type `AIAgent & <Protocol with members 'session_log_file'> & <Protocol with members 'logs_dir'> & ~AlwaysFalsy`
tests/cron/test_codex_execution_paths.py:77: [invalid-assignment] invalid-assignment: Object of type `(messages) -> None` is not assignable to attribute `_save_session_log` of type `def _save_session_log(self, messages: list[dict[str, Any]] = None) -> Unknown`
run_agent.py:1575: [unresolved-attribute] unresolved-attribute: Object of type `Self@_save_session_log` has no attribute `session_log_file`
tests/run_agent/test_run_agent.py:559: [invalid-argument-type] invalid-argument-type: Argument to function `AIAgent._clean_session_content` is incorrect: Expected `str`, found `None`
cli.py:6508: [unsupported-operator] unsupported-operator: Operator `/` is not supported between objects of type `object` and `str`
tests/run_agent/test_context_token_tracking.py:55: [invalid-assignment] invalid-assignment: Object of type `(...) -> None` is not assignable to attribute `_save_session_log` of type `def _save_session_log(self, messages: list[dict[str, Any]] = None) -> Unknown`
tests/run_agent/test_run_agent_codex_responses.py:597: [invalid-assignment] invalid-assignment: Object of type `(messages) -> None` is not assignable to attribute `_save_session_log` of type `def _save_session_log(self, messages: list[dict[str, Any]] = None) -> Unknown`

Unchanged: 4724 pre-existing issues carried over.

Diagnostics are surfaced as warnings — this check never fails the build.

@alt-glitch alt-glitch added type/refactor Code restructuring, no behavior change comp/agent Core agent loop, run_agent.py, prompt builder comp/cli CLI entry point, hermes_cli/, setup wizard P2 Medium — degraded but workaround exists labels May 20, 2026
@teknium1 teknium1 merged commit eeb747d into main May 20, 2026
20 of 21 checks passed
@teknium1 teknium1 deleted the hermes/hermes-5db05717 branch May 20, 2026 18:44
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 comp/cli CLI entry point, hermes_cli/, setup wizard P2 Medium — degraded but workaround exists type/refactor Code restructuring, no behavior change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants