mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-29 06:31:32 +00:00
refactor(session-log): delete _save_session_log and all callers
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.
This commit is contained in:
parent
c29b4f55d9
commit
ce26785187
8 changed files with 6 additions and 109 deletions
|
|
@ -1454,7 +1454,6 @@ def run_conversation(
|
|||
}
|
||||
messages.append(continue_msg)
|
||||
agent._session_messages = messages
|
||||
agent._save_session_log(messages)
|
||||
restart_with_length_continuation = True
|
||||
break
|
||||
|
||||
|
|
@ -3086,7 +3085,6 @@ def run_conversation(
|
|||
if not agent.quiet_mode:
|
||||
agent._vprint(f"{agent.log_prefix}↻ Codex response incomplete; continuing turn ({agent._codex_incomplete_retries}/3)")
|
||||
agent._session_messages = messages
|
||||
agent._save_session_log(messages)
|
||||
continue
|
||||
|
||||
agent._codex_incomplete_retries = 0
|
||||
|
|
@ -3411,7 +3409,6 @@ def run_conversation(
|
|||
|
||||
# Save session log incrementally (so progress is visible even if interrupted)
|
||||
agent._session_messages = messages
|
||||
agent._save_session_log(messages)
|
||||
|
||||
# Continue loop for next response
|
||||
continue
|
||||
|
|
@ -3578,7 +3575,6 @@ def run_conversation(
|
|||
interim_msg["_thinking_prefill"] = True
|
||||
messages.append(interim_msg)
|
||||
agent._session_messages = messages
|
||||
agent._save_session_log(messages)
|
||||
continue
|
||||
|
||||
# ── Empty response retry ──────────────────────
|
||||
|
|
@ -3712,7 +3708,6 @@ def run_conversation(
|
|||
}
|
||||
messages.append(continue_msg)
|
||||
agent._session_messages = messages
|
||||
agent._save_session_log(messages)
|
||||
continue
|
||||
|
||||
codex_ack_continuations = 0
|
||||
|
|
|
|||
66
run_agent.py
66
run_agent.py
|
|
@ -1176,7 +1176,6 @@ class AIAgent:
|
|||
self._drop_trailing_empty_response_scaffolding(messages)
|
||||
self._apply_persist_user_message_override(messages)
|
||||
self._session_messages = messages
|
||||
self._save_session_log(messages)
|
||||
self._flush_messages_to_session_db(messages, conversation_history)
|
||||
|
||||
def _drop_trailing_empty_response_scaffolding(self, messages: List[Dict]) -> None:
|
||||
|
|
@ -1516,71 +1515,6 @@ class AIAgent:
|
|||
content = re.sub(r'(</think>)\n+', r'\1\n', content)
|
||||
return content.strip()
|
||||
|
||||
def _save_session_log(self, messages: List[Dict[str, Any]] = None):
|
||||
"""
|
||||
Save the full raw session to a JSON file.
|
||||
|
||||
Stores every message exactly as the agent sees it: user messages,
|
||||
assistant messages (with reasoning, finish_reason, tool_calls),
|
||||
tool responses (with tool_call_id, tool_name), and injected system
|
||||
messages (compression summaries, todo snapshots, etc.).
|
||||
|
||||
REASONING_SCRATCHPAD tags are converted to <think> blocks for consistency.
|
||||
Overwritten after each turn so it always reflects the latest state.
|
||||
"""
|
||||
messages = messages or self._session_messages
|
||||
if not messages:
|
||||
return
|
||||
|
||||
try:
|
||||
# Clean assistant content for session logs
|
||||
cleaned = []
|
||||
for msg in messages:
|
||||
if msg.get("role") == "assistant" and msg.get("content"):
|
||||
msg = dict(msg)
|
||||
msg["content"] = self._clean_session_content(msg["content"])
|
||||
cleaned.append(msg)
|
||||
|
||||
# Guard: never overwrite a larger session log with fewer messages.
|
||||
# This protects against data loss when --resume loads a session whose
|
||||
# messages weren't fully written to SQLite — the resumed agent starts
|
||||
# with partial history and would otherwise clobber the full JSON log.
|
||||
if self.session_log_file.exists():
|
||||
try:
|
||||
existing = json.loads(self.session_log_file.read_text(encoding="utf-8"))
|
||||
existing_count = existing.get("message_count", len(existing.get("messages", [])))
|
||||
if existing_count > len(cleaned):
|
||||
logging.debug(
|
||||
"Skipping session log overwrite: existing has %d messages, current has %d",
|
||||
existing_count, len(cleaned),
|
||||
)
|
||||
return
|
||||
except Exception:
|
||||
pass # corrupted existing file — allow the overwrite
|
||||
|
||||
entry = {
|
||||
"session_id": self.session_id,
|
||||
"model": self.model,
|
||||
"base_url": self.base_url,
|
||||
"platform": self.platform,
|
||||
"session_start": self.session_start.isoformat(),
|
||||
"last_updated": datetime.now().isoformat(),
|
||||
"system_prompt": self._cached_system_prompt or "",
|
||||
"tools": self.tools or [],
|
||||
"message_count": len(cleaned),
|
||||
"messages": cleaned,
|
||||
}
|
||||
|
||||
atomic_json_write(
|
||||
self.session_log_file,
|
||||
entry,
|
||||
indent=2,
|
||||
default=str,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
if self.verbose_logging:
|
||||
logging.warning(f"Failed to save session log: {e}")
|
||||
|
||||
def interrupt(self, message: str = None) -> None:
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -74,7 +74,6 @@ class _Codex401ThenSuccessAgent(run_agent.AIAgent):
|
|||
self._cleanup_task_resources = lambda task_id: None
|
||||
self._persist_session = lambda messages, history=None: None
|
||||
self._save_trajectory = lambda messages, user_message, completed: None
|
||||
self._save_session_log = lambda messages: None
|
||||
|
||||
def _try_refresh_codex_client_credentials(self, *, force: bool = True) -> bool:
|
||||
type(self).refresh_attempts += 1
|
||||
|
|
|
|||
|
|
@ -110,8 +110,6 @@ class TestFlushDeduplication:
|
|||
db = SessionDB(db_path=db_path)
|
||||
|
||||
agent = self._make_agent(db)
|
||||
# Stub out _save_session_log to avoid file I/O
|
||||
agent._save_session_log = MagicMock()
|
||||
|
||||
conversation_history = [{"role": "user", "content": "old"}]
|
||||
messages = list(conversation_history) + [
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ def _make_agent(monkeypatch, api_mode, provider, response_fn):
|
|||
kw.update(skip_context_files=True, skip_memory=True, max_iterations=4)
|
||||
super().__init__(*a, **kw)
|
||||
self._cleanup_task_resources = self._persist_session = lambda *a, **k: None
|
||||
self._save_trajectory = self._save_session_log = lambda *a, **k: None
|
||||
self._save_trajectory = lambda *a, **k: None
|
||||
|
||||
def run_conversation(self, msg, conversation_history=None, task_id=None):
|
||||
self._interruptible_api_call = lambda kw: response_fn()
|
||||
|
|
|
|||
|
|
@ -9,11 +9,7 @@ def _agent_with_stubbed_persistence():
|
|||
agent._persist_user_message_override = None
|
||||
agent._session_db = None
|
||||
agent._session_messages = []
|
||||
agent.saved_session_logs = []
|
||||
agent.flushed_session_db_messages = []
|
||||
agent._save_session_log = lambda messages: agent.saved_session_logs.append(
|
||||
[m.copy() for m in messages]
|
||||
)
|
||||
agent._flush_messages_to_session_db = lambda messages, conversation_history=None: (
|
||||
agent.flushed_session_db_messages.append([m.copy() for m in messages])
|
||||
)
|
||||
|
|
@ -60,7 +56,7 @@ def test_persist_session_strips_trailing_empty_recovery_scaffolding():
|
|||
assert messages == [
|
||||
{"role": "user", "content": "run the task"},
|
||||
]
|
||||
assert agent.saved_session_logs[-1] == messages
|
||||
assert agent.flushed_session_db_messages[-1] == messages
|
||||
assert all(not msg.get("_empty_recovery_synthetic") for msg in messages)
|
||||
|
||||
|
||||
|
|
@ -77,7 +73,7 @@ def test_persist_session_keeps_unmarked_terminal_empty_response():
|
|||
{"role": "user", "content": "run the task"},
|
||||
{"role": "assistant", "content": "(empty)"},
|
||||
]
|
||||
assert agent.saved_session_logs[-1] == messages
|
||||
assert agent.flushed_session_db_messages[-1] == messages
|
||||
|
||||
|
||||
def test_persist_session_strips_marked_terminal_empty_sentinel():
|
||||
|
|
@ -94,5 +90,5 @@ def test_persist_session_strips_marked_terminal_empty_sentinel():
|
|||
AIAgent._persist_session(agent, messages, conversation_history=[])
|
||||
|
||||
assert messages == [{"role": "user", "content": "continue"}]
|
||||
assert agent.saved_session_logs[-1] == messages
|
||||
assert agent.flushed_session_db_messages[-1] == messages
|
||||
assert all(not msg.get("_empty_terminal_sentinel") for msg in messages)
|
||||
|
|
|
|||
|
|
@ -1901,7 +1901,6 @@ class TestExecuteToolCalls:
|
|||
agent._interruptible_api_call = _fake_api_call
|
||||
agent._persist_session = lambda *args, **kwargs: None
|
||||
agent._save_trajectory = lambda *args, **kwargs: None
|
||||
agent._save_session_log = lambda *args, **kwargs: None
|
||||
|
||||
captured = io.StringIO()
|
||||
agent._print_fn = lambda *args, **kw: print(*args, file=captured, **kw)
|
||||
|
|
@ -4300,22 +4299,6 @@ class TestSafeWriter:
|
|||
assert inner.getvalue() == "test"
|
||||
|
||||
|
||||
class TestSaveSessionLogAtomicWrite:
|
||||
def test_uses_shared_atomic_json_helper(self, agent, tmp_path):
|
||||
agent.session_log_file = tmp_path / "session.json"
|
||||
messages = [{"role": "user", "content": "hello"}]
|
||||
|
||||
with patch("run_agent.atomic_json_write", create=True) as mock_atomic_write:
|
||||
agent._save_session_log(messages)
|
||||
|
||||
mock_atomic_write.assert_called_once()
|
||||
call_args = mock_atomic_write.call_args
|
||||
assert call_args.args[0] == agent.session_log_file
|
||||
payload = call_args.args[1]
|
||||
assert payload["session_id"] == agent.session_id
|
||||
assert payload["messages"] == messages
|
||||
assert call_args.kwargs["indent"] == 2
|
||||
assert call_args.kwargs["default"] is str
|
||||
|
||||
|
||||
# ===================================================================
|
||||
|
|
@ -5103,12 +5086,9 @@ class TestPersistUserMessageOverride:
|
|||
{"role": "assistant", "content": "Hi!"},
|
||||
]
|
||||
|
||||
with patch.object(agent, "_save_session_log") as mock_save:
|
||||
agent._persist_session(messages, [])
|
||||
agent._persist_session(messages, [])
|
||||
|
||||
assert messages[0]["content"] == "Hello there"
|
||||
saved_messages = mock_save.call_args.args[0]
|
||||
assert saved_messages[0]["content"] == "Hello there"
|
||||
first_db_write = agent._session_db.append_message.call_args_list[0].kwargs
|
||||
assert first_db_write["content"] == "Hello there"
|
||||
|
||||
|
|
|
|||
|
|
@ -54,7 +54,6 @@ def _build_agent(monkeypatch):
|
|||
agent._cleanup_task_resources = lambda task_id: None
|
||||
agent._persist_session = lambda messages, history=None: None
|
||||
agent._save_trajectory = lambda messages, user_message, completed: None
|
||||
agent._save_session_log = lambda messages: None
|
||||
return agent
|
||||
|
||||
|
||||
|
|
@ -75,7 +74,6 @@ def _build_copilot_agent(monkeypatch, *, model="gpt-5.4"):
|
|||
agent._cleanup_task_resources = lambda task_id: None
|
||||
agent._persist_session = lambda messages, history=None: None
|
||||
agent._save_trajectory = lambda messages, user_message, completed: None
|
||||
agent._save_session_log = lambda messages: None
|
||||
return agent
|
||||
|
||||
|
||||
|
|
@ -335,7 +333,6 @@ def test_build_api_kwargs_codex_clamps_minimal_effort(monkeypatch):
|
|||
agent._cleanup_task_resources = lambda task_id: None
|
||||
agent._persist_session = lambda messages, history=None: None
|
||||
agent._save_trajectory = lambda messages, user_message, completed: None
|
||||
agent._save_session_log = lambda messages: None
|
||||
|
||||
kwargs = agent._build_api_kwargs(
|
||||
[
|
||||
|
|
@ -365,8 +362,7 @@ def test_build_api_kwargs_codex_preserves_supported_efforts(monkeypatch):
|
|||
agent._cleanup_task_resources = lambda task_id: None
|
||||
agent._persist_session = lambda messages, history=None: None
|
||||
agent._save_trajectory = lambda messages, user_message, completed: None
|
||||
agent._save_session_log = lambda messages: None
|
||||
|
||||
|
||||
kwargs = agent._build_api_kwargs(
|
||||
[
|
||||
{"role": "system", "content": "sys"},
|
||||
|
|
@ -594,7 +590,6 @@ def _build_xai_oauth_agent(monkeypatch):
|
|||
agent._cleanup_task_resources = lambda task_id: None
|
||||
agent._persist_session = lambda messages, history=None: None
|
||||
agent._save_trajectory = lambda messages, user_message, completed: None
|
||||
agent._save_session_log = lambda messages: None
|
||||
return agent
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue