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:
28
cli.py
28
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 <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
|
||||
|
||||
@@ -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)
|
||||
|
||||
# =========================================================================
|
||||
|
||||
19
run_agent.py
19
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 <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}")
|
||||
|
||||
Reference in New Issue
Block a user