Adding missing /resume command handler to interactive CLI
NousResearch/hermes-agent · cluster 2591 · hermes cli
Data as of
Nothing open on GitHub. Fix merged in #3315. Check it covers your case.
The short answer
- Known?
- Yes. 6 related items grouped here, 1 issue already closed.
- Fix event
- A fix was merged to main in PR #3315. Check it covers your case. No release data is tracked.
- Tracker
- The maintainers' tracker marks this
unresolved. That is separate from the fix event. - Still open
- Nothing open on GitHub.
Multiple pull requests and a bug report addressing the same gap: the interactive CLI advertised a /resume command but had no handler implemented for it. Members propose various implementations of that missing handler, with some also touching session log truncation and a reopen_session API.
Open issues0
Open PRs0
Fixes on record
-
#3315 fix(session): add /resume CLI handler, session log truncation guard, reopen_session API
files:
cli.py,hermes_state.py,run_agent.pyShow the diff
-
cli.py@@ -2949,6 +2949,82 @@ def new_session(self, silent=False): if not silent: print("(^_^)v New session started!") + def _handle_resume_command(self, cmd_original: str) -> None: + """Handle /resume <session_id_or_title> — switch to a previous session mid-conversation.""" + parts = cmd_original.split(None, 1) + target = parts[1].strip() if len(parts) > 1 else "" + + if not target: + _cprint(" Usage: /resume <session_id_or_title>") + _cprint(" Tip: Use /history or `hermes sessions list` to find sessions.") + return + + if not self._session_db: + _cprint(" Session database not available.") + return + + # Resolve title or ID + from hermes_cli.main import _resolve_session_by_name_or_id + resolved = _resolve_session_by_name_or_id(target) + target_id = resolved or target + + session_meta = self._session_db.get_session(target_id) + if not session_meta: + _cprint(f" Session not found: {target}") + _cprint(" Use /history or `hermes sessions list` to see available sessions.") + return + + if target_id == self.session_id: + _cprint(" Already on that session.") + return + + # End current session + try: + self._session_db.end_session(self.session_id, "resumed_other") + except Exception: + pass + + # Switch to the target session + self.session_id = target_id + self._resumed = True + self._pending_title = None + + # Load conversation history + restored = self._session_db.get_messages_as_conversation(target_id) + self.conversation_history = restored or [] + + # Re-open the target session so it's not marked as ended + try: + self._session_db.reopen_session(target_id) + except Exception: + pass + + # Sync the agent if already initialised + if self.agent: + self.agent.session_id = target_id + self.agent.reset_session_state() + if hasattr(self.agent, "_last_flushed_db_idx"): + self.agent._last_flushed_db_idx = len(self.conversation_history) + if hasattr(self.agent, "_todo_store"): + try: + from tools.todo_tool import TodoStore + self.agent._todo_store = TodoStore() + except Exception: + pass + if hasattr(self.agent, "_invalidate_system_prompt"): + self.agent._invalidate_system_prompt() + + title_part = f" \"{session_meta['title']}\"" if session_meta.get("title") else "" + msg_count = len([m for m in self.conversation_history if m.get("role") == "user"]) + if self.conversation_history: + _cprint( + f" ↻ Resumed session {target_id}{title_part}" + f" ({msg_count} user message{'s' if msg_count != 1 else ''}," + f" {len(self.conversation_history)} total)" + ) + else: + _cprint(f" ↻ Resumed session {target_id}{title_part} — no messages, starting fresh.") + def reset_conversation(self): """Reset the conversation by starting a new session.""" self.new_session() @@ -3667,6 +3743,8 @@ def process_command(self, command: str) -> bool: _cprint(" Session database not available.") elif canonical == "new": self.new_session() + elif canonical == "resume": + self._handle_resume_command(cmd_original) elif canonical == "provider": self._show_model_and_providers() elif canonical == "prompt": -
hermes_state.py@@ -284,6 +284,15 @@ def end_session(self, session_id: str, end_reason: str) -> None: ) self._conn.commit() + def reopen_session(self, session_id: str) -> None: + """Clear ended_at/end_reason so a session can be resumed.""" + with self._lock: + self._conn.execute( + "UPDATE sessions SET ended_at = NULL, end_reason = NULL WHERE id = ?", + (session_id,), + ) + self._conn.commit() + def update_system_prompt(self, session_id: str, system_prompt: str) -> None: """Store the full assembled system prompt snapshot.""" with self._lock: -
run_agent.py@@ -2055,6 +2055,23 @@ def _save_session_log(self, messages: List[Dict[str, Any]] = None): 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,
-
4 PRs closed without merging
From the close comments: done elsewhere 2 · superseded by another PR 1 · other 1
-
#2592 fix(cli): implement /resume command handler for interactive CLI · other →
#2893,#3183 -
#2593 fix(cli): add interactive /resume handler · done elsewhere →
1519c4d,#3315 -
#2597 fix(cli): implement missing resume command handler in interactive CLI · superseded by another PR →
#2593,#2592 -
#2722 fix(cli): implement /resume command for the interactive CLI · done elsewhere →
bd01ec7,f24956b
Closed issues · 1
- Closed#2591[Bug]: interactive CLI advertises /resume but does not handle it
How this cluster was grouped
-
Link evidence (
structural links throughout): every member is connected by structural evidence: shared changed files, or a closing/superseded reference. -
Pairwise link density
0.33: the share of item pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36.1.00means every pair is linked.
Pick up a Lab task
Check whether a fix covers the open issues here, with your own agent. Bring back evidence for maintainers.
Open the LabAt a glance
- Subsystem
- hermes cli
- Merged PRs
- #3315
- Closed issues
- 1
- Attempts
- 4 PRs closed without merging
- Fix link checked
- 27 Mar 2026. This confirmation is old and may be out of date (a revert or reopen would not have invalidated it here); re-verify.