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

0open issues
0open PRs
6items grouped
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

No open issues.

Open PRs0

No open pull requests.

Fixes on record

Merged to main. A stamp counts the issues a fix is linked to.

  • #3315 fix(session): add /resume CLI handler, session log truncation guard, reopen_session API

    merged to main

    files: cli.py, hermes_state.py, run_agent.py

    Show 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.00 means 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 Lab

At 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.

For your agent

The same facts, typed.