feat: include session ID in system prompt via --pass-session-id flag

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 <dmahan93@users.noreply.github.com>
This commit is contained in:
dmahan93
2026-03-12 05:51:31 -07:00
committed by teknium1
parent 92e9809c86
commit c7fc39bde0
3 changed files with 49 additions and 15 deletions

28
cli.py
View File

@@ -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 <none|low|medium|high|xhigh|show|hide>{_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

View File

@@ -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)
# =========================================================================

View File

@@ -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 <think> 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'<think>(.*?)</think>', 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}")