From c7fc39bde0cf57a3271181df5f3ca5f121a6418b Mon Sep 17 00:00:00 2001 From: dmahan93 Date: Thu, 12 Mar 2026 05:51:31 -0700 Subject: [PATCH] feat: include session ID in system prompt via --pass-session-id flag MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds --pass-session-id CLI flag. When set, the agent's system prompt includes the session ID: Conversation started: Sunday, March 08, 2026 06:32 PM Session ID: 20260308_183200_abc123 Usage: hermes --pass-session-id hermes chat --pass-session-id Implementation threads the flag as a proper parameter through the full chain (main.py → cli.py → run_agent.py) rather than using an env var, avoiding collisions in multi-agent/multitenant setups. Based on PR #726 by dmahan93, reworked to use instance parameter instead of HERMES_PASS_SESSION_ID environment variable. Co-authored-by: dmahan93 --- cli.py | 28 ++++++++++++++++++---------- hermes_cli/main.py | 17 +++++++++++++++-- run_agent.py | 19 ++++++++++++++++--- 3 files changed, 49 insertions(+), 15 deletions(-) diff --git a/cli.py b/cli.py index 7f2b2394a..54013b0eb 100755 --- a/cli.py +++ b/cli.py @@ -416,7 +416,7 @@ from model_tools import get_tool_definitions, get_toolset_for_tool # Extracted CLI modules (Phase 3) from hermes_cli.banner import ( cprint as _cprint, _GOLD, _BOLD, _DIM, _RST, - VERSION, HERMES_AGENT_LOGO, HERMES_CADUCEUS, COMPACT_BANNER, + VERSION, RELEASE_DATE, HERMES_AGENT_LOGO, HERMES_CADUCEUS, COMPACT_BANNER, get_available_skills as _get_available_skills, build_welcome_banner, ) @@ -993,7 +993,7 @@ def build_welcome_banner(console: Console, model: str, cwd: str, tools: List[dic # Wrap in a panel with the title outer_panel = Panel( layout_table, - title=f"[bold {_title_c}]{_agent_name} {VERSION}[/]", + title=f"[bold {_title_c}]{_agent_name} v{VERSION} ({RELEASE_DATE})[/]", border_style=_border_c, padding=(0, 2), ) @@ -1099,6 +1099,7 @@ class HermesCLI: compact: bool = False, resume: str = None, checkpoints: bool = False, + pass_session_id: bool = False, ): """ Initialize the Hermes CLI. @@ -1113,6 +1114,7 @@ class HermesCLI: verbose: Enable verbose logging compact: Use compact display mode resume: Session ID to resume (restores conversation history from SQLite) + pass_session_id: Include the session ID in the agent's system prompt """ # Initialize Rich console self.console = Console() @@ -1194,6 +1196,7 @@ class HermesCLI: cp_cfg = {"enabled": cp_cfg} self.checkpoints_enabled = checkpoints or cp_cfg.get("enabled", False) self.checkpoint_max_snapshots = cp_cfg.get("max_snapshots", 50) + self.pass_session_id = pass_session_id # Ephemeral system prompt: env var takes precedence, then config self.system_prompt = ( @@ -1511,6 +1514,7 @@ class HermesCLI: thinking_callback=self._on_thinking, checkpoints_enabled=self.checkpoints_enabled, checkpoint_max_snapshots=self.checkpoint_max_snapshots, + pass_session_id=self.pass_session_id, ) # Apply any pending title now that the session exists in the DB if self._pending_title and self._session_db: @@ -3120,8 +3124,8 @@ class HermesCLI: level = "none (disabled)" else: level = rc.get("effort", "medium") - display_state = "on" if self.show_reasoning else "off" - _cprint(f" {_GOLD}Reasoning effort: {level}{_RST}") + display_state = "on ✓" if self.show_reasoning else "off" + _cprint(f" {_GOLD}Reasoning effort: {level}{_RST}") _cprint(f" {_GOLD}Reasoning display: {display_state}{_RST}") _cprint(f" {_DIM}Usage: /reasoning {_RST}") return @@ -3133,14 +3137,16 @@ class HermesCLI: self.show_reasoning = True if self.agent: self.agent.reasoning_callback = self._on_reasoning - _cprint(f" {_GOLD}Reasoning display: ON{_RST}") - _cprint(f" {_DIM}Model thinking will be shown during and after each response.{_RST}") + save_config_value("display.show_reasoning", True) + _cprint(f" {_GOLD}✓ Reasoning display: ON (saved){_RST}") + _cprint(f" {_DIM} Model thinking will be shown during and after each response.{_RST}") return if arg in ("hide", "off"): self.show_reasoning = False if self.agent: self.agent.reasoning_callback = None - _cprint(f" {_GOLD}Reasoning display: OFF{_RST}") + save_config_value("display.show_reasoning", False) + _cprint(f" {_GOLD}✓ Reasoning display: OFF (saved){_RST}") return # Effort level change @@ -3155,9 +3161,9 @@ class HermesCLI: self.agent = None # Force agent re-init with new reasoning config if save_config_value("agent.reasoning_effort", arg): - _cprint(f" {_GOLD}Reasoning effort set to '{arg}' (saved to config){_RST}") + _cprint(f" {_GOLD}✓ Reasoning effort set to '{arg}' (saved to config){_RST}") else: - _cprint(f" {_GOLD}Reasoning effort set to '{arg}' (session only){_RST}") + _cprint(f" {_GOLD}✓ Reasoning effort set to '{arg}' (session only){_RST}") def _on_reasoning(self, reasoning_text: str): """Callback for intermediate reasoning display during tool-call loops.""" @@ -4544,7 +4550,7 @@ class HermesCLI: # Check for commands if isinstance(user_input, str) and user_input.startswith("/"): - print(f"\n⚙️ {user_input}") + _cprint(f"\n⚙️ {user_input}") if not self.process_command(user_input): self._should_exit = True # Schedule app exit @@ -4652,6 +4658,7 @@ def main( worktree: bool = False, w: bool = False, checkpoints: bool = False, + pass_session_id: bool = False, ): """ Hermes Agent CLI - Interactive AI Assistant @@ -4757,6 +4764,7 @@ def main( compact=compact, resume=resume, checkpoints=checkpoints, + pass_session_id=pass_session_id, ) # Inject worktree context into agent's system prompt diff --git a/hermes_cli/main.py b/hermes_cli/main.py index 480aba7bf..781535350 100644 --- a/hermes_cli/main.py +++ b/hermes_cli/main.py @@ -51,7 +51,7 @@ os.environ.setdefault("MSWEA_SILENT_STARTUP", "1") import logging -from hermes_cli import __version__ +from hermes_cli import __version__, __release_date__ from hermes_constants import OPENROUTER_BASE_URL logger = logging.getLogger(__name__) @@ -495,6 +495,7 @@ def cmd_chat(args): "resume": getattr(args, "resume", None), "worktree": getattr(args, "worktree", False), "checkpoints": getattr(args, "checkpoints", False), + "pass_session_id": getattr(args, "pass_session_id", False), } # Filter out None values kwargs = {k: v for k, v in kwargs.items() if v is not None} @@ -1484,7 +1485,7 @@ def cmd_config(args): def cmd_version(args): """Show version.""" - print(f"Hermes Agent v{__version__}") + print(f"Hermes Agent v{__version__} ({__release_date__})") print(f"Project: {PROJECT_ROOT}") # Show Python version @@ -1895,6 +1896,12 @@ For more help on a command: default=False, help="Bypass all dangerous command approval prompts (use at your own risk)" ) + parser.add_argument( + "--pass-session-id", + action="store_true", + default=False, + help="Include the session ID in the agent's system prompt" + ) subparsers = parser.add_subparsers(dest="command", help="Command to run") @@ -1966,6 +1973,12 @@ For more help on a command: default=False, help="Bypass all dangerous command approval prompts (use at your own risk)" ) + chat_parser.add_argument( + "--pass-session-id", + action="store_true", + default=False, + help="Include the session ID in the agent's system prompt" + ) chat_parser.set_defaults(func=cmd_chat) # ========================================================================= diff --git a/run_agent.py b/run_agent.py index cce83f6b6..0d6fe5458 100644 --- a/run_agent.py +++ b/run_agent.py @@ -233,6 +233,7 @@ class AIAgent: fallback_model: Dict[str, Any] = None, checkpoints_enabled: bool = False, checkpoint_max_snapshots: int = 50, + pass_session_id: bool = False, ): """ Initialize the AI Agent. @@ -287,6 +288,7 @@ class AIAgent: self.ephemeral_system_prompt = ephemeral_system_prompt self.platform = platform # "cli", "telegram", "discord", "whatsapp", etc. self.skip_context_files = skip_context_files + self.pass_session_id = pass_session_id self.log_prefix_chars = log_prefix_chars self.log_prefix = f"{log_prefix} " if log_prefix else "" # Store effective base URL for feature detection (prompt caching, reasoning, etc.) @@ -1483,9 +1485,10 @@ class AIAgent: from hermes_time import now as _hermes_now now = _hermes_now() - prompt_parts.append( - f"Conversation started: {now.strftime('%A, %B %d, %Y %I:%M %p')}" - ) + timestamp_line = f"Conversation started: {now.strftime('%A, %B %d, %Y %I:%M %p')}" + if self.pass_session_id and self.session_id: + timestamp_line += f"\nSession ID: {self.session_id}" + prompt_parts.append(timestamp_line) platform_key = (self.platform or "").lower().strip() if platform_key in PLATFORM_HINTS: @@ -2442,6 +2445,16 @@ class AIAgent: """ reasoning_text = self._extract_reasoning(assistant_message) + # Fallback: extract inline blocks from content when no structured + # reasoning fields are present (some models/providers embed thinking + # directly in the content rather than returning separate API fields). + if not reasoning_text: + content = assistant_message.content or "" + think_blocks = re.findall(r'(.*?)', content, flags=re.DOTALL) + if think_blocks: + combined = "\n\n".join(b.strip() for b in think_blocks if b.strip()) + reasoning_text = combined or None + if reasoning_text and self.verbose_logging: preview = reasoning_text[:100] + "..." if len(reasoning_text) > 100 else reasoning_text logging.debug(f"Captured reasoning ({len(reasoning_text)} chars): {preview}")