2025-09-10 00:43:55 -07:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
"""
|
|
|
|
|
Toolsets Module
|
|
|
|
|
|
|
|
|
|
This module provides a flexible system for defining and managing tool aliases/toolsets.
|
|
|
|
|
Toolsets allow you to group tools together for specific scenarios and can be composed
|
|
|
|
|
from individual tools or other toolsets.
|
|
|
|
|
|
|
|
|
|
Features:
|
|
|
|
|
- Define custom toolsets with specific tools
|
|
|
|
|
- Compose toolsets from other toolsets
|
|
|
|
|
- Built-in common toolsets for typical use cases
|
|
|
|
|
- Easy extension for new toolsets
|
|
|
|
|
- Support for dynamic toolset resolution
|
|
|
|
|
|
|
|
|
|
Usage:
|
|
|
|
|
from toolsets import get_toolset, resolve_toolset, get_all_toolsets
|
|
|
|
|
|
|
|
|
|
# Get tools for a specific toolset
|
|
|
|
|
tools = get_toolset("research")
|
|
|
|
|
|
|
|
|
|
# Resolve a toolset to get all tool names (including from composed toolsets)
|
|
|
|
|
all_tools = resolve_toolset("full_stack")
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
from typing import List, Dict, Any, Set, Optional
|
|
|
|
|
|
|
|
|
|
|
refactor: deduplicate toolsets, unify async bridging, fix approval race condition, harden security
- Replace 4 copy-pasted messaging platform toolsets with shared _HERMES_CORE_TOOLS list
- Consolidate 5 ad-hoc async-bridging patterns into single _run_async() in model_tools.py
- Removes deprecated get_event_loop()/set_event_loop() calls
- Makes all tool handlers self-protecting regardless of caller's event loop state
- RL handler refactored from if/elif chain to dispatch dict
- Fix exec approval race condition: replace module-level globals with thread-safe
per-session tools/approval.py (submit_pending, pop_pending, approve_session, is_approved)
- Session A approving "rm" no longer approves it for all other sessions
- Fix config deep merge: user overriding tts.elevenlabs.voice_id no longer clobbers
tts.elevenlabs.model_id; migration detection now recurses to arbitrary depth
- Gateway default-deny: unauthenticated users denied unless GATEWAY_ALLOW_ALL_USERS=true
- Add 10 dangerous command patterns: rm --recursive, bash -c, python -e, curl|bash,
xargs rm, find -delete
- Sanitize gateway error messages: users see generic message, full traceback goes to logs
2026-02-21 18:28:49 -08:00
|
|
|
# Shared tool list for CLI and all messaging platform toolsets.
|
|
|
|
|
# Edit this once to update all platforms simultaneously.
|
|
|
|
|
_HERMES_CORE_TOOLS = [
|
|
|
|
|
# Web
|
|
|
|
|
"web_search", "web_extract",
|
|
|
|
|
# Terminal + process management
|
|
|
|
|
"terminal", "process",
|
|
|
|
|
# File manipulation
|
|
|
|
|
"read_file", "write_file", "patch", "search_files",
|
|
|
|
|
# Vision + image generation
|
|
|
|
|
"vision_analyze", "image_generate",
|
|
|
|
|
# MoA
|
|
|
|
|
"mixture_of_agents",
|
|
|
|
|
# Skills
|
|
|
|
|
"skills_list", "skill_view", "skill_manage",
|
|
|
|
|
# Browser automation
|
|
|
|
|
"browser_navigate", "browser_snapshot", "browser_click",
|
|
|
|
|
"browser_type", "browser_scroll", "browser_back",
|
|
|
|
|
"browser_press", "browser_close", "browser_get_images",
|
|
|
|
|
"browser_vision",
|
|
|
|
|
# Text-to-speech
|
|
|
|
|
"text_to_speech",
|
|
|
|
|
# Planning & memory
|
|
|
|
|
"todo", "memory",
|
|
|
|
|
# Session history search
|
|
|
|
|
"session_search",
|
|
|
|
|
# Clarifying questions
|
|
|
|
|
"clarify",
|
|
|
|
|
# Code execution + delegation
|
|
|
|
|
"execute_code", "delegate_task",
|
|
|
|
|
# Cronjob management
|
|
|
|
|
"schedule_cronjob", "list_cronjobs", "remove_cronjob",
|
2026-02-22 20:44:15 -08:00
|
|
|
# Cross-platform messaging (gated on gateway running via check_fn)
|
|
|
|
|
"send_message",
|
2026-02-25 19:34:25 -05:00
|
|
|
# Honcho user context (gated on honcho being active via check_fn)
|
|
|
|
|
"query_user_context",
|
2026-02-28 13:32:48 +03:00
|
|
|
# Home Assistant smart home control (gated on HASS_TOKEN via check_fn)
|
2026-03-03 05:16:53 -08:00
|
|
|
"ha_list_entities", "ha_get_state", "ha_list_services", "ha_call_service",
|
refactor: deduplicate toolsets, unify async bridging, fix approval race condition, harden security
- Replace 4 copy-pasted messaging platform toolsets with shared _HERMES_CORE_TOOLS list
- Consolidate 5 ad-hoc async-bridging patterns into single _run_async() in model_tools.py
- Removes deprecated get_event_loop()/set_event_loop() calls
- Makes all tool handlers self-protecting regardless of caller's event loop state
- RL handler refactored from if/elif chain to dispatch dict
- Fix exec approval race condition: replace module-level globals with thread-safe
per-session tools/approval.py (submit_pending, pop_pending, approve_session, is_approved)
- Session A approving "rm" no longer approves it for all other sessions
- Fix config deep merge: user overriding tts.elevenlabs.voice_id no longer clobbers
tts.elevenlabs.model_id; migration detection now recurses to arbitrary depth
- Gateway default-deny: unauthenticated users denied unless GATEWAY_ALLOW_ALL_USERS=true
- Add 10 dangerous command patterns: rm --recursive, bash -c, python -e, curl|bash,
xargs rm, find -delete
- Sanitize gateway error messages: users see generic message, full traceback goes to logs
2026-02-21 18:28:49 -08:00
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
2025-09-10 00:43:55 -07:00
|
|
|
# Core toolset definitions
|
|
|
|
|
# These can include individual tools or reference other toolsets
|
|
|
|
|
TOOLSETS = {
|
|
|
|
|
# Basic toolsets - individual tool categories
|
|
|
|
|
"web": {
|
|
|
|
|
"description": "Web research and content extraction tools",
|
2026-01-29 06:10:24 +00:00
|
|
|
"tools": ["web_search", "web_extract"],
|
2025-09-10 00:43:55 -07:00
|
|
|
"includes": [] # No other toolsets included
|
|
|
|
|
},
|
|
|
|
|
|
2026-01-29 06:10:24 +00:00
|
|
|
"search": {
|
|
|
|
|
"description": "Web search only (no content extraction/scraping)",
|
|
|
|
|
"tools": ["web_search"],
|
|
|
|
|
"includes": []
|
|
|
|
|
},
|
|
|
|
|
|
2025-09-10 00:43:55 -07:00
|
|
|
"vision": {
|
|
|
|
|
"description": "Image analysis and vision tools",
|
|
|
|
|
"tools": ["vision_analyze"],
|
|
|
|
|
"includes": []
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
"image_gen": {
|
|
|
|
|
"description": "Creative generation tools (images)",
|
|
|
|
|
"tools": ["image_generate"],
|
|
|
|
|
"includes": []
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
"terminal": {
|
Add background process management with process tool, wait, PTY, and stdin support
New process registry and tool for managing long-running background processes
across all terminal backends (local, Docker, Singularity, Modal, SSH).
Process Registry (tools/process_registry.py):
- ProcessSession tracking with rolling 200KB output buffer
- spawn_local() with optional PTY via ptyprocess for interactive CLIs
- spawn_via_env() for non-local backends (runs inside sandbox, never on host)
- Background reader threads per process (Popen stdout or PTY)
- wait() with timeout clamping, interrupt support, and transparent limit reporting
- JSON checkpoint to ~/.hermes/processes.json for gateway crash recovery
- Module-level singleton shared across agent loop, gateway, and RL
Process Tool (model_tools.py):
- 7 actions: list, poll, log, wait, kill, write, submit
- Paired with terminal in all toolsets (CLI, messaging, RL)
- Timeout clamping with transparent notes in response
Terminal Tool Updates (tools/terminal_tool.py):
- Replaced nohup background mode with registry spawn (returns session_id)
- Added workdir parameter for per-command working directory
- Added check_interval parameter for gateway auto-check watchers
- Added pty parameter for interactive CLI tools (Codex, Claude Code)
- Updated TERMINAL_TOOL_DESCRIPTION with full background workflow docs
- Cleanup thread now respects active background processes (won't reap sandbox)
Gateway Integration (gateway/run.py, session.py, config.py):
- Session reset protection: sessions with active processes exempt from reset
- Default idle timeout increased from 2 hours to 24 hours
- from_dict fallback aligned to match (was 120, now 1440)
- session_key env var propagated to process registry for session mapping
- Crash recovery on gateway startup via checkpoint probe
- check_interval watcher: asyncio task polls process, delivers updates to platform
RL Safety (environments/):
- tool_context.py cleanup() kills background processes on episode end
- hermes_base_env.py warns when enabled_toolsets is None (loads all tools)
- Process tool safe in RL via wait() blocking the agent loop
Also:
- Added ptyprocess as optional dependency (in pyproject.toml [pty] extra + [all])
- Fixed pre-existing bug: rl_test_inference missing from TOOL_TO_TOOLSET_MAP
- Updated AGENTS.md with process management docs and project structure
- Updated README.md terminal section with process management overview
2026-02-17 02:51:31 -08:00
|
|
|
"description": "Terminal/command execution and process management tools",
|
|
|
|
|
"tools": ["terminal", "process"],
|
2025-09-10 00:43:55 -07:00
|
|
|
"includes": []
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
"moa": {
|
|
|
|
|
"description": "Advanced reasoning and problem-solving tools",
|
|
|
|
|
"tools": ["mixture_of_agents"],
|
|
|
|
|
"includes": []
|
|
|
|
|
},
|
|
|
|
|
|
2026-01-30 07:39:55 +00:00
|
|
|
"skills": {
|
2026-02-19 18:25:53 -08:00
|
|
|
"description": "Access, create, edit, and manage skill documents with specialized instructions and knowledge",
|
|
|
|
|
"tools": ["skills_list", "skill_view", "skill_manage"],
|
2026-01-30 07:39:55 +00:00
|
|
|
"includes": []
|
|
|
|
|
},
|
|
|
|
|
|
2026-01-29 06:10:24 +00:00
|
|
|
"browser": {
|
|
|
|
|
"description": "Browser automation for web interaction (navigate, click, type, scroll, iframes, hold-click) with web search for finding URLs",
|
|
|
|
|
"tools": [
|
|
|
|
|
"browser_navigate", "browser_snapshot", "browser_click",
|
|
|
|
|
"browser_type", "browser_scroll", "browser_back",
|
|
|
|
|
"browser_press", "browser_close", "browser_get_images",
|
|
|
|
|
"browser_vision", "web_search"
|
|
|
|
|
],
|
|
|
|
|
"includes": []
|
|
|
|
|
},
|
|
|
|
|
|
2026-02-02 08:26:42 -08:00
|
|
|
"cronjob": {
|
2026-02-21 12:46:18 -08:00
|
|
|
"description": "Cronjob management tools - schedule, list, and remove automated tasks",
|
2026-02-02 08:26:42 -08:00
|
|
|
"tools": ["schedule_cronjob", "list_cronjobs", "remove_cronjob"],
|
|
|
|
|
"includes": []
|
|
|
|
|
},
|
|
|
|
|
|
2026-02-03 23:41:26 -08:00
|
|
|
"rl": {
|
|
|
|
|
"description": "RL training tools for running reinforcement learning on Tinker-Atropos",
|
|
|
|
|
"tools": [
|
|
|
|
|
"rl_list_environments", "rl_select_environment",
|
|
|
|
|
"rl_get_current_config", "rl_edit_config",
|
|
|
|
|
"rl_start_training", "rl_check_status",
|
|
|
|
|
"rl_stop_training", "rl_get_results",
|
2026-02-04 10:36:01 -08:00
|
|
|
"rl_list_runs", "rl_test_inference"
|
2026-02-03 23:41:26 -08:00
|
|
|
],
|
|
|
|
|
"includes": []
|
|
|
|
|
},
|
|
|
|
|
|
2026-02-05 03:49:46 -08:00
|
|
|
"file": {
|
|
|
|
|
"description": "File manipulation tools: read, write, patch (with fuzzy matching), and search (content + files)",
|
2026-02-20 02:43:57 -08:00
|
|
|
"tools": ["read_file", "write_file", "patch", "search_files"],
|
2026-02-05 03:49:46 -08:00
|
|
|
"includes": []
|
|
|
|
|
},
|
|
|
|
|
|
2026-02-12 10:05:08 -08:00
|
|
|
"tts": {
|
|
|
|
|
"description": "Text-to-speech: convert text to audio with Edge TTS (free), ElevenLabs, or OpenAI",
|
|
|
|
|
"tools": ["text_to_speech"],
|
|
|
|
|
"includes": []
|
|
|
|
|
},
|
|
|
|
|
|
2026-02-17 17:02:33 -08:00
|
|
|
"todo": {
|
|
|
|
|
"description": "Task planning and tracking for multi-step work",
|
|
|
|
|
"tools": ["todo"],
|
|
|
|
|
"includes": []
|
|
|
|
|
},
|
|
|
|
|
|
2026-02-19 00:57:31 -08:00
|
|
|
"memory": {
|
|
|
|
|
"description": "Persistent memory across sessions (personal notes + user profile)",
|
|
|
|
|
"tools": ["memory"],
|
|
|
|
|
"includes": []
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
"session_search": {
|
|
|
|
|
"description": "Search and recall past conversations with summarization",
|
|
|
|
|
"tools": ["session_search"],
|
|
|
|
|
"includes": []
|
|
|
|
|
},
|
|
|
|
|
|
2026-02-19 20:06:14 -08:00
|
|
|
"clarify": {
|
|
|
|
|
"description": "Ask the user clarifying questions (multiple-choice or open-ended)",
|
|
|
|
|
"tools": ["clarify"],
|
|
|
|
|
"includes": []
|
|
|
|
|
},
|
|
|
|
|
|
2026-02-19 23:23:43 -08:00
|
|
|
"code_execution": {
|
|
|
|
|
"description": "Run Python scripts that call tools programmatically (reduces LLM round trips)",
|
|
|
|
|
"tools": ["execute_code"],
|
|
|
|
|
"includes": []
|
|
|
|
|
},
|
|
|
|
|
|
2026-02-20 03:15:53 -08:00
|
|
|
"delegation": {
|
|
|
|
|
"description": "Spawn subagents with isolated context for complex subtasks",
|
|
|
|
|
"tools": ["delegate_task"],
|
|
|
|
|
"includes": []
|
|
|
|
|
},
|
2026-02-25 19:34:25 -05:00
|
|
|
|
|
|
|
|
"honcho": {
|
|
|
|
|
"description": "Honcho AI-native memory for persistent cross-session user modeling",
|
|
|
|
|
"tools": ["query_user_context"],
|
|
|
|
|
"includes": []
|
|
|
|
|
},
|
2026-02-28 13:32:48 +03:00
|
|
|
|
|
|
|
|
"homeassistant": {
|
|
|
|
|
"description": "Home Assistant smart home control and monitoring",
|
2026-03-03 05:16:53 -08:00
|
|
|
"tools": ["ha_list_entities", "ha_get_state", "ha_list_services", "ha_call_service"],
|
2026-02-28 13:32:48 +03:00
|
|
|
"includes": []
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
2025-09-10 00:43:55 -07:00
|
|
|
# Scenario-specific toolsets
|
|
|
|
|
|
|
|
|
|
"debugging": {
|
|
|
|
|
"description": "Debugging and troubleshooting toolkit",
|
Add background process management with process tool, wait, PTY, and stdin support
New process registry and tool for managing long-running background processes
across all terminal backends (local, Docker, Singularity, Modal, SSH).
Process Registry (tools/process_registry.py):
- ProcessSession tracking with rolling 200KB output buffer
- spawn_local() with optional PTY via ptyprocess for interactive CLIs
- spawn_via_env() for non-local backends (runs inside sandbox, never on host)
- Background reader threads per process (Popen stdout or PTY)
- wait() with timeout clamping, interrupt support, and transparent limit reporting
- JSON checkpoint to ~/.hermes/processes.json for gateway crash recovery
- Module-level singleton shared across agent loop, gateway, and RL
Process Tool (model_tools.py):
- 7 actions: list, poll, log, wait, kill, write, submit
- Paired with terminal in all toolsets (CLI, messaging, RL)
- Timeout clamping with transparent notes in response
Terminal Tool Updates (tools/terminal_tool.py):
- Replaced nohup background mode with registry spawn (returns session_id)
- Added workdir parameter for per-command working directory
- Added check_interval parameter for gateway auto-check watchers
- Added pty parameter for interactive CLI tools (Codex, Claude Code)
- Updated TERMINAL_TOOL_DESCRIPTION with full background workflow docs
- Cleanup thread now respects active background processes (won't reap sandbox)
Gateway Integration (gateway/run.py, session.py, config.py):
- Session reset protection: sessions with active processes exempt from reset
- Default idle timeout increased from 2 hours to 24 hours
- from_dict fallback aligned to match (was 120, now 1440)
- session_key env var propagated to process registry for session mapping
- Crash recovery on gateway startup via checkpoint probe
- check_interval watcher: asyncio task polls process, delivers updates to platform
RL Safety (environments/):
- tool_context.py cleanup() kills background processes on episode end
- hermes_base_env.py warns when enabled_toolsets is None (loads all tools)
- Process tool safe in RL via wait() blocking the agent loop
Also:
- Added ptyprocess as optional dependency (in pyproject.toml [pty] extra + [all])
- Fixed pre-existing bug: rl_test_inference missing from TOOL_TO_TOOLSET_MAP
- Updated AGENTS.md with process management docs and project structure
- Updated README.md terminal section with process management overview
2026-02-17 02:51:31 -08:00
|
|
|
"tools": ["terminal", "process"],
|
2026-02-05 03:49:46 -08:00
|
|
|
"includes": ["web", "file"] # For searching error messages and solutions, and file operations
|
2025-09-10 00:43:55 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
|
|
"safe": {
|
|
|
|
|
"description": "Safe toolkit without terminal access",
|
|
|
|
|
"tools": ["mixture_of_agents"],
|
2026-02-20 23:23:32 -08:00
|
|
|
"includes": ["web", "vision", "image_gen"]
|
2026-02-02 08:26:42 -08:00
|
|
|
},
|
|
|
|
|
|
|
|
|
|
# ==========================================================================
|
refactor: deduplicate toolsets, unify async bridging, fix approval race condition, harden security
- Replace 4 copy-pasted messaging platform toolsets with shared _HERMES_CORE_TOOLS list
- Consolidate 5 ad-hoc async-bridging patterns into single _run_async() in model_tools.py
- Removes deprecated get_event_loop()/set_event_loop() calls
- Makes all tool handlers self-protecting regardless of caller's event loop state
- RL handler refactored from if/elif chain to dispatch dict
- Fix exec approval race condition: replace module-level globals with thread-safe
per-session tools/approval.py (submit_pending, pop_pending, approve_session, is_approved)
- Session A approving "rm" no longer approves it for all other sessions
- Fix config deep merge: user overriding tts.elevenlabs.voice_id no longer clobbers
tts.elevenlabs.model_id; migration detection now recurses to arbitrary depth
- Gateway default-deny: unauthenticated users denied unless GATEWAY_ALLOW_ALL_USERS=true
- Add 10 dangerous command patterns: rm --recursive, bash -c, python -e, curl|bash,
xargs rm, find -delete
- Sanitize gateway error messages: users see generic message, full traceback goes to logs
2026-02-21 18:28:49 -08:00
|
|
|
# Full Hermes toolsets (CLI + messaging platforms)
|
|
|
|
|
#
|
|
|
|
|
# All platforms share the same core tools. Messaging platforms add
|
2026-02-22 20:44:15 -08:00
|
|
|
# All platforms share the same core tools (including send_message,
|
|
|
|
|
# which is gated on gateway running via its check_fn).
|
2026-02-02 08:26:42 -08:00
|
|
|
# ==========================================================================
|
|
|
|
|
|
|
|
|
|
"hermes-cli": {
|
|
|
|
|
"description": "Full interactive CLI toolset - all default tools plus cronjob management",
|
refactor: deduplicate toolsets, unify async bridging, fix approval race condition, harden security
- Replace 4 copy-pasted messaging platform toolsets with shared _HERMES_CORE_TOOLS list
- Consolidate 5 ad-hoc async-bridging patterns into single _run_async() in model_tools.py
- Removes deprecated get_event_loop()/set_event_loop() calls
- Makes all tool handlers self-protecting regardless of caller's event loop state
- RL handler refactored from if/elif chain to dispatch dict
- Fix exec approval race condition: replace module-level globals with thread-safe
per-session tools/approval.py (submit_pending, pop_pending, approve_session, is_approved)
- Session A approving "rm" no longer approves it for all other sessions
- Fix config deep merge: user overriding tts.elevenlabs.voice_id no longer clobbers
tts.elevenlabs.model_id; migration detection now recurses to arbitrary depth
- Gateway default-deny: unauthenticated users denied unless GATEWAY_ALLOW_ALL_USERS=true
- Add 10 dangerous command patterns: rm --recursive, bash -c, python -e, curl|bash,
xargs rm, find -delete
- Sanitize gateway error messages: users see generic message, full traceback goes to logs
2026-02-21 18:28:49 -08:00
|
|
|
"tools": _HERMES_CORE_TOOLS,
|
2026-02-02 08:26:42 -08:00
|
|
|
"includes": []
|
2026-02-02 19:01:51 -08:00
|
|
|
},
|
|
|
|
|
|
|
|
|
|
"hermes-telegram": {
|
2026-02-03 10:46:23 -08:00
|
|
|
"description": "Telegram bot toolset - full access for personal use (terminal has safety checks)",
|
2026-02-22 20:44:15 -08:00
|
|
|
"tools": _HERMES_CORE_TOOLS,
|
2026-02-02 19:01:51 -08:00
|
|
|
"includes": []
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
"hermes-discord": {
|
2026-02-11 04:44:30 +00:00
|
|
|
"description": "Discord bot toolset - full access (terminal has safety checks via dangerous command approval)",
|
2026-02-22 20:44:15 -08:00
|
|
|
"tools": _HERMES_CORE_TOOLS,
|
2026-02-02 19:01:51 -08:00
|
|
|
"includes": []
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
"hermes-whatsapp": {
|
|
|
|
|
"description": "WhatsApp bot toolset - similar to Telegram (personal messaging, more trusted)",
|
2026-02-22 20:44:15 -08:00
|
|
|
"tools": _HERMES_CORE_TOOLS,
|
Add messaging platform enhancements: STT, stickers, Discord UX, Slack, pairing, hooks
Major feature additions inspired by OpenClaw/ClawdBot integration analysis:
Voice Message Transcription (STT):
- Auto-transcribe voice/audio messages via OpenAI Whisper API
- Download voice to ~/.hermes/audio_cache/ on Telegram/Discord/WhatsApp
- Inject transcript as text so all models can understand voice input
- Configurable model (whisper-1, gpt-4o-mini-transcribe, gpt-4o-transcribe)
Telegram Sticker Understanding:
- Describe static stickers via vision tool with JSON-backed cache
- Cache keyed by file_unique_id avoids redundant API calls
- Animated/video stickers get emoji-based fallback description
Discord Rich UX:
- Native slash commands (/ask, /reset, /status, /stop) via app_commands
- Button-based exec approvals (Allow Once / Always Allow / Deny)
- ExecApprovalView with user authorization and timeout handling
Slack Integration:
- Full SlackAdapter using slack-bolt with Socket Mode
- DMs, channel messages (mention-gated), /hermes slash command
- File attachment handling with bot-token-authenticated downloads
DM Pairing System:
- Code-based user authorization as alternative to static allowlists
- 8-char codes from unambiguous alphabet, 1-hour expiry
- Rate limiting, lockout after failed attempts, chmod 0600 on data
- CLI: hermes pairing list/approve/revoke/clear-pending
Event Hook System:
- File-based hook discovery from ~/.hermes/hooks/
- HOOK.yaml + handler.py per hook, sync/async handler support
- Events: gateway:startup, session:start/reset, agent:start/step/end
- Wildcard matching (command:* catches all command events)
Cross-Channel Messaging:
- send_message agent tool for delivering to any connected platform
- Enables cron job delivery and cross-platform notifications
Human-Like Response Pacing:
- Configurable delays between message chunks (off/natural/custom)
- HERMES_HUMAN_DELAY_MODE env var with min/max ms settings
Warm Injection Message Style:
- Retrofitted image vision messages with friendly kawaii-consistent tone
- All new injection messages (STT, stickers, errors) use warm style
Also: updated config migration to prompt for optional keys interactively,
bumped config version, updated README, AGENTS.md, .env.example,
cli-config.yaml.example, install scripts, pyproject.toml, and toolsets.
2026-02-15 21:38:59 -08:00
|
|
|
"includes": []
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
"hermes-slack": {
|
|
|
|
|
"description": "Slack bot toolset - full access for workspace use (terminal has safety checks)",
|
2026-02-22 20:44:15 -08:00
|
|
|
"tools": _HERMES_CORE_TOOLS,
|
2026-02-02 19:01:51 -08:00
|
|
|
"includes": []
|
|
|
|
|
},
|
|
|
|
|
|
2026-02-28 13:32:48 +03:00
|
|
|
"hermes-homeassistant": {
|
|
|
|
|
"description": "Home Assistant bot toolset - smart home event monitoring and control",
|
|
|
|
|
"tools": _HERMES_CORE_TOOLS,
|
|
|
|
|
"includes": []
|
|
|
|
|
},
|
|
|
|
|
|
2026-02-02 19:01:51 -08:00
|
|
|
"hermes-gateway": {
|
|
|
|
|
"description": "Gateway toolset - union of all messaging platform tools",
|
|
|
|
|
"tools": [],
|
2026-02-28 13:32:48 +03:00
|
|
|
"includes": ["hermes-telegram", "hermes-discord", "hermes-whatsapp", "hermes-slack", "hermes-homeassistant"]
|
2025-09-10 00:43:55 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_toolset(name: str) -> Optional[Dict[str, Any]]:
|
|
|
|
|
"""
|
|
|
|
|
Get a toolset definition by name.
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
name (str): Name of the toolset
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
Dict: Toolset definition with description, tools, and includes
|
|
|
|
|
None: If toolset not found
|
|
|
|
|
"""
|
|
|
|
|
# Return toolset definition
|
|
|
|
|
return TOOLSETS.get(name)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def resolve_toolset(name: str, visited: Set[str] = None) -> List[str]:
|
|
|
|
|
"""
|
|
|
|
|
Recursively resolve a toolset to get all tool names.
|
|
|
|
|
|
|
|
|
|
This function handles toolset composition by recursively resolving
|
|
|
|
|
included toolsets and combining all tools.
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
name (str): Name of the toolset to resolve
|
|
|
|
|
visited (Set[str]): Set of already visited toolsets (for cycle detection)
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
List[str]: List of all tool names in the toolset
|
|
|
|
|
"""
|
|
|
|
|
if visited is None:
|
|
|
|
|
visited = set()
|
|
|
|
|
|
2025-10-03 13:45:29 +00:00
|
|
|
# Special aliases that represent all tools across every toolset
|
|
|
|
|
# This ensures future toolsets are automatically included without changes.
|
|
|
|
|
if name in {"all", "*"}:
|
|
|
|
|
all_tools: Set[str] = set()
|
|
|
|
|
for toolset_name in get_toolset_names():
|
|
|
|
|
# Use a fresh visited set per branch to avoid cross-branch contamination
|
|
|
|
|
resolved = resolve_toolset(toolset_name, visited.copy())
|
|
|
|
|
all_tools.update(resolved)
|
|
|
|
|
return list(all_tools)
|
|
|
|
|
|
2025-09-10 00:43:55 -07:00
|
|
|
# Check for cycles
|
|
|
|
|
if name in visited:
|
|
|
|
|
print(f"⚠️ Circular dependency detected in toolset '{name}'")
|
|
|
|
|
return []
|
|
|
|
|
|
|
|
|
|
visited.add(name)
|
|
|
|
|
|
|
|
|
|
# Get toolset definition
|
|
|
|
|
toolset = TOOLSETS.get(name)
|
|
|
|
|
if not toolset:
|
|
|
|
|
return []
|
|
|
|
|
|
|
|
|
|
# Collect direct tools
|
|
|
|
|
tools = set(toolset.get("tools", []))
|
|
|
|
|
|
|
|
|
|
# Recursively resolve included toolsets
|
|
|
|
|
for included_name in toolset.get("includes", []):
|
|
|
|
|
included_tools = resolve_toolset(included_name, visited.copy())
|
|
|
|
|
tools.update(included_tools)
|
|
|
|
|
|
|
|
|
|
return list(tools)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def resolve_multiple_toolsets(toolset_names: List[str]) -> List[str]:
|
|
|
|
|
"""
|
|
|
|
|
Resolve multiple toolsets and combine their tools.
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
toolset_names (List[str]): List of toolset names to resolve
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
List[str]: Combined list of all tool names (deduplicated)
|
|
|
|
|
"""
|
|
|
|
|
all_tools = set()
|
|
|
|
|
|
|
|
|
|
for name in toolset_names:
|
|
|
|
|
tools = resolve_toolset(name)
|
|
|
|
|
all_tools.update(tools)
|
|
|
|
|
|
|
|
|
|
return list(all_tools)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_all_toolsets() -> Dict[str, Dict[str, Any]]:
|
|
|
|
|
"""
|
|
|
|
|
Get all available toolsets with their definitions.
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
Dict: All toolset definitions
|
|
|
|
|
"""
|
|
|
|
|
return TOOLSETS.copy()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_toolset_names() -> List[str]:
|
|
|
|
|
"""
|
|
|
|
|
Get names of all available toolsets (excluding aliases).
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
List[str]: List of toolset names
|
|
|
|
|
"""
|
|
|
|
|
return list(TOOLSETS.keys())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def validate_toolset(name: str) -> bool:
|
|
|
|
|
"""
|
|
|
|
|
Check if a toolset name is valid.
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
name (str): Toolset name to validate
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
bool: True if valid, False otherwise
|
|
|
|
|
"""
|
2025-10-03 13:45:29 +00:00
|
|
|
# Accept special alias names for convenience
|
|
|
|
|
if name in {"all", "*"}:
|
|
|
|
|
return True
|
2025-09-10 00:43:55 -07:00
|
|
|
return name in TOOLSETS
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def create_custom_toolset(
|
|
|
|
|
name: str,
|
|
|
|
|
description: str,
|
|
|
|
|
tools: List[str] = None,
|
|
|
|
|
includes: List[str] = None
|
|
|
|
|
) -> None:
|
|
|
|
|
"""
|
|
|
|
|
Create a custom toolset at runtime.
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
name (str): Name for the new toolset
|
|
|
|
|
description (str): Description of the toolset
|
|
|
|
|
tools (List[str]): Direct tools to include
|
|
|
|
|
includes (List[str]): Other toolsets to include
|
|
|
|
|
"""
|
|
|
|
|
TOOLSETS[name] = {
|
|
|
|
|
"description": description,
|
|
|
|
|
"tools": tools or [],
|
|
|
|
|
"includes": includes or []
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_toolset_info(name: str) -> Dict[str, Any]:
|
|
|
|
|
"""
|
|
|
|
|
Get detailed information about a toolset including resolved tools.
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
name (str): Toolset name
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
Dict: Detailed toolset information
|
|
|
|
|
"""
|
|
|
|
|
toolset = get_toolset(name)
|
|
|
|
|
if not toolset:
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
resolved_tools = resolve_toolset(name)
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
"name": name,
|
|
|
|
|
"description": toolset["description"],
|
|
|
|
|
"direct_tools": toolset["tools"],
|
|
|
|
|
"includes": toolset["includes"],
|
|
|
|
|
"resolved_tools": resolved_tools,
|
|
|
|
|
"tool_count": len(resolved_tools),
|
|
|
|
|
"is_composite": len(toolset["includes"]) > 0
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def print_toolset_tree(name: str, indent: int = 0) -> None:
|
|
|
|
|
"""
|
|
|
|
|
Print a tree view of a toolset and its composition.
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
name (str): Toolset name
|
|
|
|
|
indent (int): Current indentation level
|
|
|
|
|
"""
|
|
|
|
|
prefix = " " * indent
|
|
|
|
|
toolset = get_toolset(name)
|
|
|
|
|
|
|
|
|
|
if not toolset:
|
|
|
|
|
print(f"{prefix}❌ Unknown toolset: {name}")
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# Print toolset name and description
|
|
|
|
|
print(f"{prefix}📦 {name}: {toolset['description']}")
|
|
|
|
|
|
|
|
|
|
# Print direct tools
|
|
|
|
|
if toolset["tools"]:
|
|
|
|
|
print(f"{prefix} 🔧 Tools: {', '.join(toolset['tools'])}")
|
|
|
|
|
|
|
|
|
|
# Print included toolsets
|
|
|
|
|
if toolset["includes"]:
|
|
|
|
|
print(f"{prefix} 📂 Includes:")
|
|
|
|
|
for included in toolset["includes"]:
|
|
|
|
|
print_toolset_tree(included, indent + 2)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
2026-02-20 23:23:32 -08:00
|
|
|
print("Toolsets System Demo")
|
2025-09-10 00:43:55 -07:00
|
|
|
print("=" * 60)
|
|
|
|
|
|
2026-02-20 23:23:32 -08:00
|
|
|
print("\nAvailable Toolsets:")
|
2025-09-10 00:43:55 -07:00
|
|
|
print("-" * 40)
|
|
|
|
|
for name, toolset in get_all_toolsets().items():
|
|
|
|
|
info = get_toolset_info(name)
|
2026-02-20 23:23:32 -08:00
|
|
|
composite = "[composite]" if info["is_composite"] else "[leaf]"
|
|
|
|
|
print(f" {composite} {name:20} - {toolset['description']}")
|
|
|
|
|
print(f" Tools: {len(info['resolved_tools'])} total")
|
2025-09-10 00:43:55 -07:00
|
|
|
|
2026-02-20 23:23:32 -08:00
|
|
|
print("\nToolset Resolution Examples:")
|
2025-09-10 00:43:55 -07:00
|
|
|
print("-" * 40)
|
2026-02-20 23:23:32 -08:00
|
|
|
for name in ["web", "terminal", "safe", "debugging"]:
|
2025-09-10 00:43:55 -07:00
|
|
|
tools = resolve_toolset(name)
|
2026-02-20 23:23:32 -08:00
|
|
|
print(f"\n {name}:")
|
|
|
|
|
print(f" Resolved to {len(tools)} tools: {', '.join(sorted(tools))}")
|
2025-09-10 00:43:55 -07:00
|
|
|
|
2026-02-20 23:23:32 -08:00
|
|
|
print("\nMultiple Toolset Resolution:")
|
2025-09-10 00:43:55 -07:00
|
|
|
print("-" * 40)
|
2026-02-20 23:23:32 -08:00
|
|
|
combined = resolve_multiple_toolsets(["web", "vision", "terminal"])
|
|
|
|
|
print(f" Combining ['web', 'vision', 'terminal']:")
|
|
|
|
|
print(f" Result: {', '.join(sorted(combined))}")
|
2025-09-10 00:43:55 -07:00
|
|
|
|
2026-02-20 23:23:32 -08:00
|
|
|
print("\nCustom Toolset Creation:")
|
2025-09-10 00:43:55 -07:00
|
|
|
print("-" * 40)
|
|
|
|
|
create_custom_toolset(
|
|
|
|
|
name="my_custom",
|
|
|
|
|
description="My custom toolset for specific tasks",
|
|
|
|
|
tools=["web_search"],
|
|
|
|
|
includes=["terminal", "vision"]
|
|
|
|
|
)
|
|
|
|
|
custom_info = get_toolset_info("my_custom")
|
2026-02-20 23:23:32 -08:00
|
|
|
print(f" Created 'my_custom' toolset:")
|
|
|
|
|
print(f" Description: {custom_info['description']}")
|
|
|
|
|
print(f" Resolved tools: {', '.join(custom_info['resolved_tools'])}")
|