From de5a88bd976aea965ebf3005e01330db8d36f552 Mon Sep 17 00:00:00 2001 From: teknium1 Date: Sat, 28 Feb 2026 00:05:58 -0800 Subject: [PATCH] refactor: migrate tool progress configuration from environment variables to config.yaml --- AGENTS.md | 9 +++------ README.md | 20 +++++++++++++------- cli-config.yaml.example | 8 ++++++++ cli.py | 25 +++++++++++++++++++------ docs/messaging.md | 10 ++++------ gateway/run.py | 22 +++++++++++++++++++--- hermes_cli/commands.py | 2 +- hermes_cli/config.py | 36 +++++++++++++++++++++++++++++++----- hermes_cli/setup.py | 34 ++++++++++++++++------------------ 9 files changed, 114 insertions(+), 52 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 6b52aab39..f729bde98 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -248,9 +248,7 @@ DISCORD_ALLOWED_USERS=123456789012345678 # Comma-separated user IDs HERMES_MAX_ITERATIONS=60 # Max tool-calling iterations MESSAGING_CWD=/home/myuser # Terminal working directory for messaging -# Tool Progress (optional) -HERMES_TOOL_PROGRESS=true # Send progress messages -HERMES_TOOL_PROGRESS_MODE=new # "new" or "all" +# Tool progress is configured in config.yaml (display.tool_progress: off|new|all|verbose) ``` ### Working Directory Behavior @@ -301,7 +299,7 @@ Files: `gateway/hooks.py` ### Tool Progress Notifications -When `HERMES_TOOL_PROGRESS=true`, the bot sends status messages as it works: +When `tool_progress` is enabled in `config.yaml`, the bot sends status messages as it works: - `💻 \`ls -la\`...` (terminal commands show the actual command) - `🔍 web_search...` - `📄 web_extract...` @@ -411,8 +409,7 @@ Terminal tool configuration (in `~/.hermes/config.yaml`): Agent behavior (in `~/.hermes/.env`): - `HERMES_MAX_ITERATIONS` - Max tool-calling iterations (default: 60) - `MESSAGING_CWD` - Working directory for messaging platforms (default: ~) -- `HERMES_TOOL_PROGRESS` - Enable tool progress messages (`true`/`false`) -- `HERMES_TOOL_PROGRESS_MODE` - Progress mode: `new` (tool changes) or `all` +- `display.tool_progress` in config.yaml - Tool progress: `off`, `new`, `all`, `verbose` - `OPENAI_API_KEY` - Voice transcription (Whisper STT) - `SLACK_BOT_TOKEN` / `SLACK_APP_TOKEN` - Slack integration (Socket Mode) - `SLACK_ALLOWED_USERS` - Comma-separated Slack user IDs diff --git a/README.md b/README.md index 5408fa29c..3cb1d6598 100644 --- a/README.md +++ b/README.md @@ -325,14 +325,22 @@ TERMINAL_CWD=/workspace # All terminal sessions (local or contain ### Tool Progress Notifications -Get real-time updates as the agent works: +Control how much tool activity is displayed. Set in `~/.hermes/config.yaml`: -```bash -# Enable in ~/.hermes/.env -HERMES_TOOL_PROGRESS=true -HERMES_TOOL_PROGRESS_MODE=all # or "new" for only when tool changes +```yaml +display: + tool_progress: all # off | new | all | verbose ``` +| Mode | What you see | +|------|-------------| +| `off` | Silent — just the final response | +| `new` | Tool indicator only when the tool changes (skip repeats) | +| `all` | Every tool call with a short preview (default) | +| `verbose` | Full args, results, and debug logs | + +Toggle at runtime in the CLI with `/verbose` (cycles through all four modes). + --- ## Commands @@ -1568,8 +1576,6 @@ All variables go in `~/.hermes/.env`. Run `hermes config set VAR value` to set t | Variable | Description | |----------|-------------| | `HERMES_MAX_ITERATIONS` | Max tool-calling iterations per conversation (default: 60) | -| `HERMES_TOOL_PROGRESS` | Send progress messages when using tools (`true`/`false`) | -| `HERMES_TOOL_PROGRESS_MODE` | `all` (every call, default) or `new` (only when tool changes) | **Context Compression:** | Variable | Description | diff --git a/cli-config.yaml.example b/cli-config.yaml.example index 5a1855320..72b2f572b 100644 --- a/cli-config.yaml.example +++ b/cli-config.yaml.example @@ -487,3 +487,11 @@ delegation: display: # Use compact banner mode compact: false + + # Tool progress display level (CLI and gateway) + # off: Silent — no tool activity shown, just the final response + # new: Show a tool indicator only when the tool changes (skip repeats) + # all: Show every tool call with a short preview (default) + # verbose: Full args, results, and debug logs (same as /verbose) + # Toggle at runtime with /verbose in the CLI + tool_progress: all diff --git a/cli.py b/cli.py index b45ba8546..ea9c3e630 100755 --- a/cli.py +++ b/cli.py @@ -793,7 +793,9 @@ class HermesCLI: # Initialize Rich console self.console = Console() self.compact = compact if compact is not None else CLI_CONFIG["display"].get("compact", False) - self.verbose = verbose if verbose is not None else CLI_CONFIG["agent"].get("verbose", False) + # tool_progress: "off", "new", "all", "verbose" (from config.yaml display section) + self.tool_progress_mode = CLI_CONFIG["display"].get("tool_progress", "all") + self.verbose = verbose if verbose is not None else (self.tool_progress_mode == "verbose") # Configuration - priority: CLI args > env vars > config file # Model can come from: CLI arg, LLM_MODEL env, OPENAI_MODEL env (custom endpoint), or config @@ -1697,24 +1699,35 @@ class HermesCLI: return True def _toggle_verbose(self): - """Toggle verbose mode on/off at runtime.""" - self.verbose = not self.verbose + """Cycle tool progress mode: off → new → all → verbose → off.""" + cycle = ["off", "new", "all", "verbose"] + try: + idx = cycle.index(self.tool_progress_mode) + except ValueError: + idx = 2 # default to "all" + self.tool_progress_mode = cycle[(idx + 1) % len(cycle)] + self.verbose = self.tool_progress_mode == "verbose" if self.agent: self.agent.verbose_logging = self.verbose self.agent.quiet_mode = not self.verbose - # Reconfigure logging level to match new state + labels = { + "off": "[dim]Tool progress: OFF[/] — silent mode, just the final response.", + "new": "[yellow]Tool progress: NEW[/] — show each new tool (skip repeats).", + "all": "[green]Tool progress: ALL[/] — show every tool call.", + "verbose": "[bold green]Tool progress: VERBOSE[/] — full args, results, and debug logs.", + } + self.console.print(labels.get(self.tool_progress_mode, "")) + if self.verbose: logging.getLogger().setLevel(logging.DEBUG) for noisy in ('openai', 'openai._base_client', 'httpx', 'httpcore', 'asyncio', 'hpack', 'grpc', 'modal'): logging.getLogger(noisy).setLevel(logging.WARNING) - self.console.print("[bold green]Verbose mode ON[/] — tool calls, parameters, and results will be shown.") else: logging.getLogger().setLevel(logging.INFO) for quiet_logger in ('tools', 'minisweagent', 'run_agent', 'trajectory_compressor', 'cron', 'hermes_cli'): logging.getLogger(quiet_logger).setLevel(logging.ERROR) - self.console.print("[dim]Verbose mode OFF[/] — returning to normal display.") def _clarify_callback(self, question, choices): """ diff --git a/docs/messaging.md b/docs/messaging.md index 8e6e2e790..9963cfe03 100644 --- a/docs/messaging.md +++ b/docs/messaging.md @@ -223,11 +223,9 @@ MESSAGING_CWD=/home/myuser # TOOL PROGRESS NOTIFICATIONS # ============================================================================= -# Show progress messages as agent uses tools -HERMES_TOOL_PROGRESS=true - -# Mode: "new" (only when tool changes) or "all" (every tool call) -HERMES_TOOL_PROGRESS_MODE=new +# Tool progress is now configured in config.yaml: +# display: +# tool_progress: all # off | new | all | verbose # ============================================================================= # SESSION SETTINGS @@ -301,7 +299,7 @@ The gateway keeps the "typing..." indicator active throughout processing, refres ### Tool Progress Notifications -When `HERMES_TOOL_PROGRESS=true`, the bot sends status messages as it works: +When `tool_progress` is enabled in `config.yaml`, the bot sends status messages as it works: ```text 💻 `ls -la`... diff --git a/gateway/run.py b/gateway/run.py index 9d8e43a95..bcd2457b9 100644 --- a/gateway/run.py +++ b/gateway/run.py @@ -1462,9 +1462,24 @@ class GatewayRunner: default_toolset = default_toolset_map.get(source.platform, "hermes-telegram") enabled_toolsets = [default_toolset] - # Check if tool progress notifications are enabled - tool_progress_enabled = os.getenv("HERMES_TOOL_PROGRESS", "true").lower() in ("1", "true", "yes") - progress_mode = os.getenv("HERMES_TOOL_PROGRESS_MODE", "all") # "all" or "new" (only new tools) + # Tool progress mode from config.yaml: "all", "new", "verbose", "off" + # Falls back to env vars for backward compatibility + _progress_cfg = {} + try: + _tp_cfg_path = _hermes_home / "config.yaml" + if _tp_cfg_path.exists(): + import yaml as _tp_yaml + with open(_tp_cfg_path) as _tp_f: + _tp_data = _tp_yaml.safe_load(_tp_f) or {} + _progress_cfg = _tp_data.get("display", {}) + except Exception: + pass + progress_mode = ( + _progress_cfg.get("tool_progress") + or os.getenv("HERMES_TOOL_PROGRESS_MODE") + or "all" + ) + tool_progress_enabled = progress_mode != "off" # Queue for progress messages (thread-safe) progress_queue = queue.Queue() if tool_progress_enabled else None @@ -1627,6 +1642,7 @@ class GatewayRunner: base_url=base_url, max_iterations=max_iterations, quiet_mode=True, + verbose_logging=False, enabled_toolsets=enabled_toolsets, ephemeral_system_prompt=combined_ephemeral or None, prefill_messages=self._prefill_messages or None, diff --git a/hermes_cli/commands.py b/hermes_cli/commands.py index 54a95f326..b7e5a6213 100644 --- a/hermes_cli/commands.py +++ b/hermes_cli/commands.py @@ -25,7 +25,7 @@ COMMANDS = { "/cron": "Manage scheduled tasks (list, add, remove)", "/skills": "Search, install, inspect, or manage skills from online registries", "/platforms": "Show gateway/messaging platform status", - "/verbose": "Toggle verbose mode (show tool calls, parameters, and results)", + "/verbose": "Cycle tool progress display: off → new → all → verbose", "/quit": "Exit the CLI (also: /exit, /q)", } diff --git a/hermes_cli/config.py b/hermes_cli/config.py index e0b109e0c..583cb9cf9 100644 --- a/hermes_cli/config.py +++ b/hermes_cli/config.py @@ -136,7 +136,7 @@ DEFAULT_CONFIG = { "command_allowlist": [], # Config schema version - bump this when adding new required fields - "_config_version": 3, + "_config_version": 4, } # ============================================================================= @@ -318,16 +318,19 @@ OPTIONAL_ENV_VARS = { "password": False, "category": "setting", }, + # HERMES_TOOL_PROGRESS and HERMES_TOOL_PROGRESS_MODE are deprecated — + # now configured via display.tool_progress in config.yaml (off|new|all|verbose). + # Gateway falls back to these env vars for backward compatibility. "HERMES_TOOL_PROGRESS": { - "description": "Send tool progress messages in messaging channels (true/false)", - "prompt": "Enable tool progress messages", + "description": "(deprecated) Use display.tool_progress in config.yaml instead", + "prompt": "Tool progress (deprecated — use config.yaml)", "url": None, "password": False, "category": "setting", }, "HERMES_TOOL_PROGRESS_MODE": { - "description": "Progress mode: 'all' (every tool) or 'new' (only when tool changes)", - "prompt": "Progress mode (all/new)", + "description": "(deprecated) Use display.tool_progress in config.yaml instead", + "prompt": "Progress mode (deprecated — use config.yaml)", "url": None, "password": False, "category": "setting", @@ -442,6 +445,29 @@ def migrate_config(interactive: bool = True, quiet: bool = False) -> Dict[str, A # Check config version current_ver, latest_ver = check_config_version() + # ── Version 3 → 4: migrate tool progress from .env to config.yaml ── + if current_ver < 4: + config = load_config() + display = config.get("display", {}) + if not isinstance(display, dict): + display = {} + if "tool_progress" not in display: + old_enabled = get_env_value("HERMES_TOOL_PROGRESS") + old_mode = get_env_value("HERMES_TOOL_PROGRESS_MODE") + if old_enabled and old_enabled.lower() in ("false", "0", "no"): + display["tool_progress"] = "off" + results["config_added"].append("display.tool_progress=off (from HERMES_TOOL_PROGRESS=false)") + elif old_mode and old_mode.lower() in ("new", "all"): + display["tool_progress"] = old_mode.lower() + results["config_added"].append(f"display.tool_progress={old_mode.lower()} (from HERMES_TOOL_PROGRESS_MODE)") + else: + display["tool_progress"] = "all" + results["config_added"].append("display.tool_progress=all (default)") + config["display"] = display + save_config(config) + if not quiet: + print(f" ✓ Migrated tool progress to config.yaml: {display['tool_progress']}") + if current_ver < latest_ver and not quiet: print(f"Config version: {current_ver} → {latest_ver}") diff --git a/hermes_cli/setup.py b/hermes_cli/setup.py index 6828311f8..6ed9fb64a 100644 --- a/hermes_cli/setup.py +++ b/hermes_cli/setup.py @@ -1044,27 +1044,25 @@ def run_setup_wizard(args): except ValueError: print_warning("Invalid number, keeping current value") - # Tool progress notifications (for messaging) + # Tool progress notifications print_info("") - print_info("Tool Progress Notifications (Messaging only)") - print_info("Send status messages when the agent uses tools.") - print_info("Example: '💻 ls -la...' or '🔍 web_search...'") + print_info("Tool Progress Display") + print_info("Controls how much tool activity is shown (CLI and messaging).") + print_info(" off — Silent, just the final response") + print_info(" new — Show tool name only when it changes (less noise)") + print_info(" all — Show every tool call with a short preview") + print_info(" verbose — Full args, results, and debug logs") - current_progress = get_env_value('HERMES_TOOL_PROGRESS') or 'true' - if prompt_yes_no("Enable tool progress messages?", current_progress.lower() in ('1', 'true', 'yes')): - save_env_value("HERMES_TOOL_PROGRESS", "true") - - # Progress mode - current_mode = get_env_value('HERMES_TOOL_PROGRESS_MODE') or 'all' - print_info(" Mode options:") - print_info(" 'new' - Only when switching tools (less spam)") - print_info(" 'all' - Every tool call") - mode = prompt(" Progress mode", current_mode) - if mode.lower() in ('all', 'new'): - save_env_value("HERMES_TOOL_PROGRESS_MODE", mode.lower()) - print_success("Tool progress enabled") + current_mode = config.get("display", {}).get("tool_progress", "all") + mode = prompt("Tool progress mode", current_mode) + if mode.lower() in ("off", "new", "all", "verbose"): + if "display" not in config: + config["display"] = {} + config["display"]["tool_progress"] = mode.lower() + save_config(config) + print_success(f"Tool progress set to: {mode.lower()}") else: - save_env_value("HERMES_TOOL_PROGRESS", "false") + print_warning(f"Unknown mode '{mode}', keeping '{current_mode}'") # ========================================================================= # Step 6: Context Compression