2025-08-09 09:52:25 -07:00
#!/usr/bin/env python3
"""
Model Tools Module
This module constructs tool schemas and handlers for AI model API calls .
It imports tools from various toolset modules and provides a unified interface
for defining tools and executing function calls .
Currently supports :
- Web tools ( search , extract , crawl ) from web_tools . py
2026-01-10 05:56:26 +00:00
- Terminal tools ( simple command execution , no session persistence ) from simple_terminal_tool . py
2025-09-06 22:07:38 -07:00
- Vision tools ( image analysis ) from vision_tools . py
- Mixture of Agents tools ( collaborative multi - model reasoning ) from mixture_of_agents_tool . py
- Image generation tools ( text - to - image with upscaling ) from image_generation_tool . py
2025-08-09 09:52:25 -07:00
Usage :
from model_tools import get_tool_definitions , handle_function_call
2025-09-06 22:07:38 -07:00
# Get all available tool definitions for model API
2025-08-09 09:52:25 -07:00
tools = get_tool_definitions ( )
2025-09-06 22:07:38 -07:00
# Get specific toolsets
web_tools = get_tool_definitions ( enabled_toolsets = [ ' web_tools ' ] )
2025-08-09 09:52:25 -07:00
# Handle function calls from model
2025-09-10 00:43:55 -07:00
result = handle_function_call ( " web_search " , { " query " : " Python " } )
2025-08-09 09:52:25 -07:00
"""
import json
import asyncio
2026-02-02 19:28:27 -08:00
import os
from typing import Dict , Any , List , Optional , Tuple
2025-08-09 09:52:25 -07:00
2026-02-20 23:23:32 -08:00
from tools . web_tools import web_search_tool , web_extract_tool , check_firecrawl_api_key
from tools . terminal_tool import terminal_tool , check_terminal_requirements , TERMINAL_TOOL_DESCRIPTION
2026-02-05 03:49:46 -08:00
# File manipulation tools (read, write, patch, search)
from tools . file_tools import read_file_tool , write_file_tool , patch_tool , search_tool
from tools import check_file_requirements
2025-10-01 09:54:17 +00:00
from tools . vision_tools import vision_analyze_tool , check_vision_requirements
from tools . mixture_of_agents_tool import mixture_of_agents_tool , check_moa_requirements
from tools . image_generation_tool import image_generate_tool , check_image_generation_requirements
2026-02-20 23:23:32 -08:00
from tools . skills_tool import skills_list , skill_view , check_skills_requirements
2026-02-19 18:25:53 -08:00
# Agent-managed skill creation/editing
2026-02-20 23:23:32 -08:00
from tools . skill_manager_tool import skill_manage , SKILL_MANAGE_SCHEMA
2026-02-03 23:41:26 -08:00
# RL Training tools (Tinker-Atropos)
from tools . rl_training_tool import (
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 ,
rl_list_runs ,
2026-02-04 10:36:01 -08:00
rl_test_inference ,
2026-02-03 23:41:26 -08:00
check_rl_api_keys ,
)
2026-02-02 08:26:42 -08:00
# Cronjob management tools (CLI-only)
from tools . cronjob_tools import (
schedule_cronjob ,
list_cronjobs ,
remove_cronjob ,
check_cronjob_requirements ,
SCHEDULE_CRONJOB_SCHEMA ,
LIST_CRONJOBS_SCHEMA ,
REMOVE_CRONJOB_SCHEMA
)
2026-01-29 06:10:24 +00:00
# Browser automation tools (agent-browser + Browserbase)
from tools . browser_tool import (
browser_navigate ,
browser_snapshot ,
browser_click ,
browser_type ,
browser_scroll ,
browser_back ,
browser_press ,
browser_close ,
browser_get_images ,
browser_vision ,
cleanup_browser ,
check_browser_requirements ,
BROWSER_TOOL_SCHEMAS
)
2026-02-12 10:05:08 -08:00
# Text-to-speech tool (Edge TTS / ElevenLabs / OpenAI)
from tools . tts_tool import text_to_speech_tool , check_tts_requirements
2026-02-17 17:02:33 -08:00
# Planning & task management tool
from tools . todo_tool import todo_tool , check_todo_requirements , TODO_SCHEMA
2026-02-19 00:57:31 -08:00
# Persistent memory tool
from tools . memory_tool import memory_tool , check_memory_requirements , MEMORY_SCHEMA
# Session search tool (past conversation recall with summarization)
from tools . session_search_tool import session_search , check_session_search_requirements , SESSION_SEARCH_SCHEMA
2026-02-19 20:06:14 -08:00
# Clarifying questions tool
from tools . clarify_tool import clarify_tool , check_clarify_requirements , CLARIFY_SCHEMA
2026-02-19 23:23:43 -08:00
# Code execution sandbox (programmatic tool calling)
from tools . code_execution_tool import execute_code , check_sandbox_requirements , EXECUTE_CODE_SCHEMA
2026-02-20 03:15:53 -08:00
# Subagent delegation
from tools . delegate_tool import delegate_task , check_delegate_requirements , DELEGATE_TASK_SCHEMA
2026-02-20 23:23:32 -08:00
from toolsets import resolve_toolset , validate_toolset
2025-08-09 09:52:25 -07:00
2026-02-02 19:28:27 -08:00
# =============================================================================
# Tool Availability Checking
# =============================================================================
# Maps toolsets to their required API keys/environment variables
TOOLSET_REQUIREMENTS = {
" web " : {
" name " : " Web Search & Extract " ,
" env_vars " : [ " FIRECRAWL_API_KEY " ] ,
" check_fn " : check_firecrawl_api_key ,
" setup_url " : " https://firecrawl.dev/ " ,
" tools " : [ " web_search " , " web_extract " ] ,
} ,
" vision " : {
" name " : " Vision (Image Analysis) " ,
" env_vars " : [ " OPENROUTER_API_KEY " ] ,
" check_fn " : check_vision_requirements ,
" setup_url " : " https://openrouter.ai/keys " ,
" tools " : [ " vision_analyze " ] ,
} ,
" moa " : {
" name " : " Mixture of Agents " ,
" env_vars " : [ " OPENROUTER_API_KEY " ] ,
" check_fn " : check_moa_requirements ,
" setup_url " : " https://openrouter.ai/keys " ,
" tools " : [ " mixture_of_agents " ] ,
} ,
" image_gen " : {
" name " : " Image Generation " ,
" env_vars " : [ " FAL_KEY " ] ,
" check_fn " : check_image_generation_requirements ,
" setup_url " : " https://fal.ai/ " ,
" tools " : [ " image_generate " ] ,
} ,
" browser " : {
" name " : " Browser Automation " ,
" env_vars " : [ " BROWSERBASE_API_KEY " , " BROWSERBASE_PROJECT_ID " ] ,
" check_fn " : check_browser_requirements ,
" setup_url " : " https://browserbase.com/ " ,
" tools " : [ " browser_navigate " , " browser_snapshot " , " browser_click " , " browser_type " ] ,
} ,
" terminal " : {
" name " : " Terminal/Command Execution " ,
" env_vars " : [ ] , # No API key required, just system dependencies
" check_fn " : check_terminal_requirements ,
" setup_url " : None ,
" tools " : [ " terminal " ] ,
} ,
" skills " : {
" name " : " Skills Knowledge Base " ,
" env_vars " : [ ] , # Just needs skills directory
" check_fn " : check_skills_requirements ,
" setup_url " : None ,
2026-02-19 18:25:53 -08:00
" tools " : [ " skills_list " , " skill_view " , " skill_manage " ] ,
2026-02-02 19:28:27 -08:00
} ,
2026-02-03 23:41:26 -08:00
" rl " : {
" name " : " RL Training (Tinker-Atropos) " ,
" env_vars " : [ " TINKER_API_KEY " , " WANDB_API_KEY " ] ,
" check_fn " : check_rl_api_keys ,
" setup_url " : " https://wandb.ai/authorize " ,
" 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
] ,
} ,
2026-02-05 03:49:46 -08:00
" file " : {
" name " : " File Operations (read, write, patch, search) " ,
" env_vars " : [ ] , # Uses terminal backend, no additional requirements
" check_fn " : check_file_requirements ,
" setup_url " : None ,
2026-02-20 02:43:57 -08:00
" tools " : [ " read_file " , " write_file " , " patch " , " search_files " ] ,
2026-02-05 03:49:46 -08:00
} ,
2026-02-12 10:05:08 -08:00
" tts " : {
" name " : " Text-to-Speech " ,
" env_vars " : [ ] , # Edge TTS needs no key; premium providers checked at runtime
" check_fn " : check_tts_requirements ,
" setup_url " : None ,
" tools " : [ " text_to_speech " ] ,
} ,
2026-02-17 17:02:33 -08:00
" todo " : {
" name " : " Planning & Task Management " ,
" env_vars " : [ ] , # Pure in-memory, no external deps
" check_fn " : check_todo_requirements ,
" setup_url " : None ,
" tools " : [ " todo " ] ,
} ,
2026-02-19 00:57:31 -08:00
" memory " : {
" name " : " Persistent Memory " ,
" env_vars " : [ ] , # File-based, no external deps
" check_fn " : check_memory_requirements ,
" setup_url " : None ,
" tools " : [ " memory " ] ,
} ,
" session_search " : {
" name " : " Session History Search " ,
" env_vars " : [ " OPENROUTER_API_KEY " ] , # Needs summarizer model
" check_fn " : check_session_search_requirements ,
" setup_url " : " https://openrouter.ai/keys " ,
" tools " : [ " session_search " ] ,
} ,
2026-02-19 20:06:14 -08:00
" clarify " : {
" name " : " Clarifying Questions " ,
" env_vars " : [ ] , # Pure UI interaction, no external deps
" check_fn " : check_clarify_requirements ,
" setup_url " : None ,
" tools " : [ " clarify " ] ,
} ,
2026-02-19 23:23:43 -08:00
" code_execution " : {
" name " : " Code Execution Sandbox " ,
" env_vars " : [ ] , # Uses stdlib only (subprocess, socket), no external deps
" check_fn " : check_sandbox_requirements ,
" setup_url " : None ,
" tools " : [ " execute_code " ] ,
} ,
2026-02-20 03:15:53 -08:00
" delegation " : {
" name " : " Subagent Delegation " ,
" env_vars " : [ ] , # Uses existing AIAgent class, no external deps
" check_fn " : check_delegate_requirements ,
" setup_url " : None ,
" tools " : [ " delegate_task " ] ,
} ,
2026-02-02 19:28:27 -08:00
}
def check_tool_availability ( quiet : bool = False ) - > Tuple [ List [ str ] , List [ Dict [ str , Any ] ] ] :
"""
Check which tool categories are available based on API keys and requirements .
Returns :
Tuple containing :
- List of available toolset names
- List of dicts with info about unavailable toolsets and what ' s missing
"""
available = [ ]
unavailable = [ ]
for toolset_id , info in TOOLSET_REQUIREMENTS . items ( ) :
if info [ " check_fn " ] ( ) :
available . append ( toolset_id )
else :
# Figure out what's missing
missing_vars = [ var for var in info [ " env_vars " ] if not os . getenv ( var ) ]
unavailable . append ( {
" id " : toolset_id ,
" name " : info [ " name " ] ,
" missing_vars " : missing_vars ,
" setup_url " : info [ " setup_url " ] ,
" tools " : info [ " tools " ] ,
} )
return available , unavailable
2025-08-09 09:52:25 -07:00
def get_web_tool_definitions ( ) - > List [ Dict [ str , Any ] ] :
"""
Get tool definitions for web tools in OpenAI ' s expected format.
Returns :
List [ Dict ] : List of web tool definitions compatible with OpenAI API
"""
return [
{
" type " : " function " ,
" function " : {
" name " : " web_search " ,
2026-02-21 02:41:30 -08:00
" description " : " Search the web for information on any topic. Returns up to 5 relevant results with titles, URLs, and descriptions. " ,
2025-08-09 09:52:25 -07:00
" parameters " : {
" type " : " object " ,
" properties " : {
" query " : {
" type " : " string " ,
" description " : " The search query to look up on the web "
}
} ,
" required " : [ " query " ]
}
}
} ,
{
" type " : " function " ,
" function " : {
" name " : " web_extract " ,
2026-02-21 02:41:30 -08:00
" description " : " Extract content from web page URLs. Pages under 5000 chars return raw content; larger pages are LLM-summarized and capped at ~5000 chars per page. Pages over 2M chars are refused. Use browser tools only when pages require interaction or dynamic content. " ,
2025-08-09 09:52:25 -07:00
" parameters " : {
" type " : " object " ,
" properties " : {
" urls " : {
" type " : " array " ,
" items " : { " type " : " string " } ,
" description " : " List of URLs to extract content from (max 5 URLs per call) " ,
" maxItems " : 5
}
} ,
" required " : [ " urls " ]
}
}
} ,
]
def get_terminal_tool_definitions ( ) - > List [ Dict [ str , Any ] ] :
"""
Get tool definitions for terminal tools in OpenAI ' s expected format.
2026-01-23 12:26:53 +00:00
Uses mini - swe - agent backend ( local / docker / modal ) by default .
2025-11-17 01:14:31 -05:00
2025-08-09 09:52:25 -07:00
Returns :
List [ Dict ] : List of terminal tool definitions compatible with OpenAI API
"""
return [
{
" type " : " function " ,
" function " : {
" name " : " terminal " ,
2026-01-23 12:26:53 +00:00
" description " : TERMINAL_TOOL_DESCRIPTION ,
2025-08-09 09:52:25 -07:00
" parameters " : {
" type " : " object " ,
" properties " : {
" command " : {
" type " : " string " ,
" description " : " The command to execute on the VM "
} ,
" background " : {
" type " : " boolean " ,
" description " : " Whether to run the command in the background (default: false) " ,
" default " : False
} ,
" timeout " : {
" type " : " integer " ,
" description " : " Command timeout in seconds (optional) " ,
" minimum " : 1
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
} ,
" workdir " : {
" type " : " string " ,
" description " : " Working directory for this command (absolute path). Defaults to the session working directory. "
} ,
" check_interval " : {
" type " : " integer " ,
" description " : " Seconds between automatic status checks for background processes (gateway/messaging only, minimum 30). When set, I ' ll proactively report progress. " ,
" minimum " : 30
} ,
" pty " : {
" type " : " boolean " ,
" description " : " Run in pseudo-terminal (PTY) mode for interactive CLI tools like Codex, Claude Code, or Python REPL. Only works with local and SSH backends. Default: false. " ,
" default " : False
2025-08-09 09:52:25 -07:00
}
} ,
2025-11-17 01:14:31 -05:00
" required " : [ " command " ]
2025-08-09 09:52:25 -07:00
}
}
}
]
def get_vision_tool_definitions ( ) - > List [ Dict [ str , Any ] ] :
"""
Get tool definitions for vision tools in OpenAI ' s expected format.
Returns :
List [ Dict ] : List of vision tool definitions compatible with OpenAI API
"""
return [
{
" type " : " function " ,
" function " : {
" name " : " vision_analyze " ,
2026-02-21 02:41:30 -08:00
" description " : " Analyze images using AI vision. Provides a comprehensive description and answers a specific question about the image content. " ,
2025-08-09 09:52:25 -07:00
" parameters " : {
" type " : " object " ,
" properties " : {
" image_url " : {
" type " : " string " ,
2026-02-21 02:41:30 -08:00
" description " : " Image URL (http/https) or local file path to analyze. "
2025-08-09 09:52:25 -07:00
} ,
" question " : {
" type " : " string " ,
2025-09-06 22:07:38 -07:00
" description " : " Your specific question or request about the image to resolve. The AI will automatically provide a complete image description AND answer your specific question. "
2025-08-09 09:52:25 -07:00
}
} ,
" required " : [ " image_url " , " question " ]
}
}
}
]
def get_moa_tool_definitions ( ) - > List [ Dict [ str , Any ] ] :
"""
Get tool definitions for Mixture - of - Agents tools in OpenAI ' s expected format.
Returns :
List [ Dict ] : List of MoA tool definitions compatible with OpenAI API
"""
return [
{
" type " : " function " ,
" function " : {
" name " : " mixture_of_agents " ,
2026-02-21 02:41:30 -08:00
" description " : " Route a hard problem through multiple frontier LLMs collaboratively. Makes 5 API calls (4 reference models + 1 aggregator) with maximum reasoning effort — use sparingly for genuinely difficult problems. Best for: complex math, advanced algorithms, multi-step analytical reasoning, problems benefiting from diverse perspectives. " ,
2025-08-09 09:52:25 -07:00
" parameters " : {
" type " : " object " ,
" properties " : {
" user_prompt " : {
" type " : " string " ,
" description " : " The complex query or problem to solve using multiple AI models. Should be a challenging problem that benefits from diverse perspectives and collaborative reasoning. "
}
} ,
" required " : [ " user_prompt " ]
}
}
}
]
def get_image_tool_definitions ( ) - > List [ Dict [ str , Any ] ] :
"""
Get tool definitions for image generation tools in OpenAI ' s expected format.
Returns :
List [ Dict ] : List of image generation tool definitions compatible with OpenAI API
"""
return [
{
" type " : " function " ,
" function " : {
" name " : " image_generate " ,
2026-02-10 21:02:40 -08:00
" description " : " Generate high-quality images from text prompts using FLUX 2 Pro model with automatic 2x upscaling. Creates detailed, artistic images that are automatically upscaled for hi-rez results. Returns a single upscaled image URL. Display it using markdown:  " ,
2025-08-09 09:52:25 -07:00
" parameters " : {
" type " : " object " ,
" properties " : {
" prompt " : {
" type " : " string " ,
2025-09-06 22:07:38 -07:00
" description " : " The text prompt describing the desired image. Be detailed and descriptive. "
2025-08-09 09:52:25 -07:00
} ,
2026-01-18 10:11:59 +00:00
" aspect_ratio " : {
2025-08-09 09:52:25 -07:00
" type " : " string " ,
2026-01-18 10:11:59 +00:00
" enum " : [ " landscape " , " square " , " portrait " ] ,
" description " : " The aspect ratio of the generated image. ' landscape ' is 16:9 wide, ' portrait ' is 16:9 tall, ' square ' is 1:1. " ,
" default " : " landscape "
2025-08-09 09:52:25 -07:00
}
} ,
" required " : [ " prompt " ]
}
}
}
]
2026-01-30 07:39:55 +00:00
def get_skills_tool_definitions ( ) - > List [ Dict [ str , Any ] ] :
"""
Get tool definitions for skills tools in OpenAI ' s expected format.
Returns :
List [ Dict ] : List of skills tool definitions compatible with OpenAI API
"""
return [
{
" type " : " function " ,
" function " : {
" name " : " skills_list " ,
" description " : " List available skills (name + description). Use skill_view(name) to load full content. " ,
" parameters " : {
" type " : " object " ,
" properties " : {
" category " : {
" type " : " string " ,
2026-02-10 19:48:38 -08:00
" description " : " Optional category filter to narrow results "
2026-02-03 15:26:59 -08:00
}
} ,
2026-01-30 07:39:55 +00:00
" required " : [ ]
}
}
} ,
{
" type " : " function " ,
" function " : {
" name " : " skill_view " ,
" description " : " Skills allow for loading information about specific tasks and workflows, as well as scripts and templates. Load a skill ' s full content or access its linked files (references, templates, scripts). First call returns SKILL.md content plus a ' linked_files ' dict showing available references/templates/scripts. To access those, call again with file_path parameter. " ,
" parameters " : {
" type " : " object " ,
" properties " : {
" name " : {
" type " : " string " ,
" description " : " The skill name (use skills_list to see available skills) "
} ,
" file_path " : {
" type " : " string " ,
" description " : " OPTIONAL: Path to a linked file within the skill (e.g., ' references/api.md ' , ' templates/config.yaml ' , ' scripts/validate.py ' ). Omit to get the main SKILL.md content. "
}
} ,
" required " : [ " name " ]
}
}
}
]
2026-02-19 18:25:53 -08:00
def get_skill_manage_tool_definitions ( ) - > List [ Dict [ str , Any ] ] :
"""
Get tool definitions for the skill management tool .
Returns :
List [ Dict ] : List containing the skill_manage tool definition compatible with OpenAI API
"""
return [ { " type " : " function " , " function " : SKILL_MANAGE_SCHEMA } ]
2026-01-29 06:10:24 +00:00
def get_browser_tool_definitions ( ) - > List [ Dict [ str , Any ] ] :
"""
Get tool definitions for browser automation tools in OpenAI ' s expected format.
Uses agent - browser CLI with Browserbase cloud execution .
Returns :
List [ Dict ] : List of browser tool definitions compatible with OpenAI API
"""
return [ { " type " : " function " , " function " : schema } for schema in BROWSER_TOOL_SCHEMAS ]
2026-02-02 08:26:42 -08:00
def get_cronjob_tool_definitions_formatted ( ) - > List [ Dict [ str , Any ] ] :
"""
Get tool definitions for cronjob management tools in OpenAI ' s expected format.
These tools are only available in the hermes - cli toolset ( interactive CLI mode ) .
Returns :
List [ Dict ] : List of cronjob tool definitions compatible with OpenAI API
"""
return [ { " type " : " function " , " function " : schema } for schema in [
SCHEDULE_CRONJOB_SCHEMA ,
LIST_CRONJOBS_SCHEMA ,
REMOVE_CRONJOB_SCHEMA
] ]
2026-02-03 23:41:26 -08:00
def get_rl_tool_definitions ( ) - > List [ Dict [ str , Any ] ] :
"""
Get tool definitions for RL training tools in OpenAI ' s expected format.
These tools enable running RL training through Tinker - Atropos .
Returns :
List [ Dict ] : List of RL tool definitions compatible with OpenAI API
"""
return [
{
" type " : " function " ,
" function " : {
" name " : " rl_list_environments " ,
" description " : " List all available RL environments. Returns environment names, paths, and descriptions. TIP: Read the file_path with file tools to understand how each environment works (verifiers, data loading, rewards). " ,
" parameters " : {
" type " : " object " ,
" properties " : { } ,
" required " : [ ]
}
}
} ,
{
" type " : " function " ,
" function " : {
" name " : " rl_select_environment " ,
" description " : " Select an RL environment for training. Loads the environment ' s default configuration. After selecting, use rl_get_current_config() to see settings and rl_edit_config() to modify them. " ,
" parameters " : {
" type " : " object " ,
" properties " : {
" name " : {
" type " : " string " ,
" description " : " Name of the environment to select (from rl_list_environments) "
}
} ,
" required " : [ " name " ]
}
}
} ,
{
" type " : " function " ,
" function " : {
" name " : " rl_get_current_config " ,
" description " : " Get the current environment configuration. Returns only fields that can be modified: group_size, max_token_length, total_steps, steps_per_eval, use_wandb, wandb_name, max_num_workers. " ,
" parameters " : {
" type " : " object " ,
" properties " : { } ,
" required " : [ ]
}
}
} ,
{
" type " : " function " ,
" function " : {
" name " : " rl_edit_config " ,
2026-02-04 09:36:51 -08:00
" description " : " Update a configuration field. Use rl_get_current_config() first to see all available fields for the selected environment. Each environment has different configurable options. Infrastructure settings (tokenizer, URLs, lora_rank, learning_rate) are locked. " ,
2026-02-03 23:41:26 -08:00
" parameters " : {
" type " : " object " ,
" properties " : {
" field " : {
" type " : " string " ,
2026-02-04 09:36:51 -08:00
" description " : " Name of the field to update (get available fields from rl_get_current_config) "
2026-02-03 23:41:26 -08:00
} ,
" value " : {
" description " : " New value for the field "
}
} ,
" required " : [ " field " , " value " ]
}
}
} ,
{
" type " : " function " ,
" function " : {
" name " : " rl_start_training " ,
2026-02-04 10:36:01 -08:00
" description " : " Start a new RL training run with the current environment and config. Most training parameters (lora_rank, learning_rate, etc.) are fixed. Use rl_edit_config() to set group_size, batch_size, wandb_project before starting. WARNING: Training takes hours. " ,
2026-02-03 23:41:26 -08:00
" parameters " : {
" type " : " object " ,
2026-02-04 09:36:51 -08:00
" properties " : { } ,
2026-02-03 23:41:26 -08:00
" required " : [ ]
}
}
} ,
{
" type " : " function " ,
" function " : {
" name " : " rl_check_status " ,
" description " : " Get status and metrics for a training run. RATE LIMITED: enforces 30-minute minimum between checks for the same run. Returns WandB metrics: step, state, reward_mean, loss, percent_correct. " ,
" parameters " : {
" type " : " object " ,
" properties " : {
" run_id " : {
" type " : " string " ,
" description " : " The run ID from rl_start_training() "
}
} ,
" required " : [ " run_id " ]
}
}
} ,
{
" type " : " function " ,
" function " : {
" name " : " rl_stop_training " ,
" description " : " Stop a running training job. Use if metrics look bad, training is stagnant, or you want to try different settings. " ,
" parameters " : {
" type " : " object " ,
" properties " : {
" run_id " : {
" type " : " string " ,
" description " : " The run ID to stop "
}
} ,
" required " : [ " run_id " ]
}
}
} ,
{
" type " : " function " ,
" function " : {
" name " : " rl_get_results " ,
" description " : " Get final results and metrics for a completed training run. Returns final metrics and path to trained weights. " ,
" parameters " : {
" type " : " object " ,
" properties " : {
" run_id " : {
" type " : " string " ,
" description " : " The run ID to get results for "
}
} ,
" required " : [ " run_id " ]
}
}
} ,
{
" type " : " function " ,
" function " : {
2026-02-04 10:36:01 -08:00
" name " : " rl_list_runs " ,
" description " : " List all training runs (active and completed) with their status. " ,
2026-02-03 23:41:26 -08:00
" parameters " : {
" type " : " object " ,
2026-02-04 10:36:01 -08:00
" properties " : { } ,
" required " : [ ]
2026-02-03 23:41:26 -08:00
}
}
} ,
{
" type " : " function " ,
" function " : {
2026-02-04 10:36:01 -08:00
" name " : " rl_test_inference " ,
" description " : " Quick inference test for any environment. Runs a few steps of inference + scoring using OpenRouter. Default: 3 steps × 16 completions = 48 rollouts per model, testing 3 models = 144 total. Tests environment loading, prompt construction, inference parsing, and verifier logic. Use BEFORE training to catch issues. " ,
2026-02-03 23:41:26 -08:00
" parameters " : {
" type " : " object " ,
2026-02-04 10:36:01 -08:00
" properties " : {
" num_steps " : {
" type " : " integer " ,
" description " : " Number of steps to run (default: 3, recommended max for testing) " ,
" default " : 3
} ,
" group_size " : {
" type " : " integer " ,
" description " : " Completions per step (default: 16, like training) " ,
" default " : 16
} ,
" models " : {
" type " : " array " ,
" items " : { " type " : " string " } ,
2026-02-04 13:57:59 -08:00
" description " : " Optional list of OpenRouter model IDs. Default: qwen/qwen3-8b, z-ai/glm-4.7-flash, minimax/minimax-m2.1 "
2026-02-04 10:36:01 -08:00
}
} ,
2026-02-03 23:41:26 -08:00
" required " : [ ]
}
}
}
]
2026-02-05 03:49:46 -08:00
def get_file_tool_definitions ( ) - > List [ Dict [ str , Any ] ] :
"""
Get tool definitions for file manipulation tools in OpenAI ' s expected format.
File tools operate via the terminal backend and support any environment
( local , docker , singularity , ssh , modal ) .
Returns :
List [ Dict ] : List of file tool definitions compatible with OpenAI API
"""
return [
{
" type " : " function " ,
" function " : {
" name " : " read_file " ,
2026-02-08 20:19:14 +00:00
" description " : (
2026-02-21 02:41:30 -08:00
" Read a file with line numbers and pagination. Output format: ' LINE_NUM|CONTENT ' . "
" Suggests similar filenames if not found. Images (png/jpg/gif/webp) returned as base64. "
" Use offset and limit for large files. "
2026-02-08 20:19:14 +00:00
) ,
2026-02-05 03:49:46 -08:00
" parameters " : {
" type " : " object " ,
" properties " : {
" path " : {
" type " : " string " ,
2026-02-08 20:19:14 +00:00
" description " : " Path to the file to read (absolute, relative, or ~/path) "
2026-02-05 03:49:46 -08:00
} ,
" offset " : {
" type " : " integer " ,
" description " : " Line number to start reading from (1-indexed, default: 1) " ,
" default " : 1 ,
" minimum " : 1
} ,
" limit " : {
" type " : " integer " ,
" description " : " Maximum number of lines to read (default: 500, max: 2000) " ,
" default " : 500 ,
" maximum " : 2000
}
} ,
" required " : [ " path " ]
}
}
} ,
{
" type " : " function " ,
" function " : {
" name " : " write_file " ,
2026-02-08 20:19:14 +00:00
" description " : (
2026-02-21 02:41:30 -08:00
" Write content to a file, completely replacing existing content. Creates parent "
" directories automatically. OVERWRITES the entire file — use ' patch ' for targeted edits. "
2026-02-08 20:19:14 +00:00
) ,
2026-02-05 03:49:46 -08:00
" parameters " : {
" type " : " object " ,
" properties " : {
" path " : {
" type " : " string " ,
2026-02-08 20:19:14 +00:00
" description " : " Path to the file to write (will be created if it doesn ' t exist, overwritten if it does) "
2026-02-05 03:49:46 -08:00
} ,
" content " : {
" type " : " string " ,
2026-02-08 20:19:14 +00:00
" description " : " Complete content to write to the file "
2026-02-05 03:49:46 -08:00
}
} ,
" required " : [ " path " , " content " ]
}
}
} ,
{
" type " : " function " ,
" function " : {
" name " : " patch " ,
2026-02-08 20:19:14 +00:00
" description " : (
2026-02-21 02:41:30 -08:00
" Targeted find-and-replace edits in files. Uses fuzzy matching (9 strategies) so "
" minor whitespace/indentation differences won ' t break it. Returns a unified diff. "
" Auto-runs syntax checks after editing. \n \n "
" Replace mode (default): find a unique string and replace it. \n "
" Patch mode: apply V4A multi-file patches for bulk changes. "
2026-02-08 20:19:14 +00:00
) ,
2026-02-05 03:49:46 -08:00
" parameters " : {
" type " : " object " ,
" properties " : {
" mode " : {
" type " : " string " ,
" enum " : [ " replace " , " patch " ] ,
2026-02-08 20:19:14 +00:00
" description " : " Edit mode: ' replace ' for targeted find-and-replace, ' patch ' for V4A multi-file patches " ,
2026-02-05 03:49:46 -08:00
" default " : " replace "
} ,
" path " : {
" type " : " string " ,
2026-02-08 20:19:14 +00:00
" description " : " File path to edit (required for ' replace ' mode) "
2026-02-05 03:49:46 -08:00
} ,
" old_string " : {
" type " : " string " ,
2026-02-08 20:19:14 +00:00
" description " : " Text to find in the file (required for ' replace ' mode). Must be unique in the file unless replace_all=true. Include enough surrounding context to ensure uniqueness. "
2026-02-05 03:49:46 -08:00
} ,
" new_string " : {
" type " : " string " ,
2026-02-08 20:19:14 +00:00
" description " : " Replacement text (required for ' replace ' mode). Can be empty string to delete the matched text. "
2026-02-05 03:49:46 -08:00
} ,
" replace_all " : {
" type " : " boolean " ,
2026-02-08 20:19:14 +00:00
" description " : " Replace all occurrences instead of requiring a unique match (default: false) " ,
2026-02-05 03:49:46 -08:00
" default " : False
} ,
" patch " : {
" type " : " string " ,
2026-02-08 20:19:14 +00:00
" description " : " V4A format patch content (required for ' patch ' mode). Format: \n *** Begin Patch \n *** Update File: path/to/file \n @@ context hint @@ \n context line \n -removed line \n +added line \n *** End Patch "
2026-02-05 03:49:46 -08:00
}
} ,
" required " : [ " mode " ]
}
}
} ,
{
" type " : " function " ,
" function " : {
2026-02-20 02:43:57 -08:00
" name " : " search_files " ,
2026-02-08 20:19:14 +00:00
" description " : (
2026-02-21 02:41:30 -08:00
" Search file contents or find files by name. Ripgrep-backed, faster than "
" grep/rg/find in the terminal. \n \n "
" Content search (target= ' content ' ): Regex search inside files. Output modes: "
" full matches with line numbers, file paths only, or match counts. \n \n "
" File search (target= ' files ' ): Find files by glob pattern (e.g., ' *.py ' , ' *config* ' ). "
" Results sorted by modification time. "
2026-02-08 20:19:14 +00:00
) ,
2026-02-05 03:49:46 -08:00
" parameters " : {
" type " : " object " ,
" properties " : {
" pattern " : {
" type " : " string " ,
2026-02-21 02:41:30 -08:00
" description " : " Regex pattern for content search, or glob pattern (e.g., ' *.py ' ) for file search "
2026-02-05 03:49:46 -08:00
} ,
" target " : {
" type " : " string " ,
2026-02-21 02:41:30 -08:00
" enum " : [ " content " , " files " ] ,
" description " : " ' content ' searches inside file contents, ' files ' searches for files by name " ,
" default " : " content "
2026-02-05 03:49:46 -08:00
} ,
" path " : {
" type " : " string " ,
2026-02-08 20:19:14 +00:00
" description " : " Directory or file to search in (default: current working directory) " ,
2026-02-05 03:49:46 -08:00
" default " : " . "
} ,
" file_glob " : {
" type " : " string " ,
2026-02-20 02:46:30 -08:00
" description " : " Filter files by pattern in grep mode (e.g., ' *.py ' to only search Python files) "
2026-02-05 03:49:46 -08:00
} ,
" limit " : {
" type " : " integer " ,
2026-02-08 20:19:14 +00:00
" description " : " Maximum number of results to return (default: 50) " ,
2026-02-05 03:49:46 -08:00
" default " : 50
} ,
" offset " : {
" type " : " integer " ,
" description " : " Skip first N results for pagination (default: 0) " ,
" default " : 0
} ,
" output_mode " : {
" type " : " string " ,
" enum " : [ " content " , " files_only " , " count " ] ,
2026-02-20 02:46:30 -08:00
" description " : " Output format for grep mode: ' content ' shows matching lines with line numbers, ' files_only ' lists file paths, ' count ' shows match counts per file " ,
2026-02-05 03:49:46 -08:00
" default " : " content "
} ,
" context " : {
" type " : " integer " ,
2026-02-20 02:46:30 -08:00
" description " : " Number of context lines before and after each match (grep mode only) " ,
2026-02-05 03:49:46 -08:00
" default " : 0
}
} ,
" required " : [ " pattern " ]
}
}
}
]
2026-02-12 10:05:08 -08:00
def get_tts_tool_definitions ( ) - > List [ Dict [ str , Any ] ] :
"""
Get tool definitions for text - to - speech tools in OpenAI ' s expected format.
Returns :
List [ Dict ] : List of TTS tool definitions compatible with OpenAI API
"""
return [
{
" type " : " function " ,
" function " : {
" name " : " text_to_speech " ,
" description " : " Convert text to speech audio. Returns a MEDIA: path that the platform delivers as a voice message. On Telegram it plays as a voice bubble, on Discord/WhatsApp as an audio attachment. In CLI mode, saves to ~/voice-memos/. Voice and provider are user-configured, not model-selected. " ,
" parameters " : {
" type " : " object " ,
" properties " : {
" text " : {
" type " : " string " ,
" description " : " The text to convert to speech. Keep under 4000 characters. "
} ,
" output_path " : {
" type " : " string " ,
" description " : " Optional custom file path to save the audio. Defaults to ~/voice-memos/<timestamp>.mp3 "
}
} ,
" required " : [ " text " ]
}
}
}
]
2026-02-17 17:02:33 -08:00
def get_todo_tool_definitions ( ) - > List [ Dict [ str , Any ] ] :
"""
Get tool definitions for the todo ( planning / task management ) tool .
Returns :
List [ Dict ] : List containing the todo tool definition compatible with OpenAI API
"""
return [ { " type " : " function " , " function " : TODO_SCHEMA } ]
2026-02-19 00:57:31 -08:00
def get_memory_tool_definitions ( ) - > List [ Dict [ str , Any ] ] :
"""
Get tool definitions for the persistent memory tool .
Returns :
List [ Dict ] : List containing the memory tool definition compatible with OpenAI API
"""
return [ { " type " : " function " , " function " : MEMORY_SCHEMA } ]
def get_session_search_tool_definitions ( ) - > List [ Dict [ str , Any ] ] :
"""
Get tool definitions for the session history search tool .
Returns :
List [ Dict ] : List containing the session_search tool definition compatible with OpenAI API
"""
return [ { " type " : " function " , " function " : SESSION_SEARCH_SCHEMA } ]
2026-02-19 20:06:14 -08:00
def get_clarify_tool_definitions ( ) - > List [ Dict [ str , Any ] ] :
"""
Get tool definitions for the clarifying questions tool .
Returns :
List [ Dict ] : List containing the clarify tool definition compatible with OpenAI API
"""
return [ { " type " : " function " , " function " : CLARIFY_SCHEMA } ]
2026-02-19 23:23:43 -08:00
def get_execute_code_tool_definitions ( ) - > List [ Dict [ str , Any ] ] :
"""
Get tool definitions for the code execution sandbox ( programmatic tool calling ) .
"""
return [ { " type " : " function " , " function " : EXECUTE_CODE_SCHEMA } ]
2026-02-20 03:15:53 -08:00
def get_delegate_tool_definitions ( ) - > List [ Dict [ str , Any ] ] :
"""
Get tool definitions for the subagent delegation tool .
"""
return [ { " type " : " function " , " function " : DELEGATE_TASK_SCHEMA } ]
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
def get_send_message_tool_definitions ( ) :
""" Tool definitions for cross-channel messaging. """
return [
{
" type " : " function " ,
" function " : {
" name " : " send_message " ,
" description " : " Send a message to a user or channel on any connected messaging platform. Use this when the user asks you to send something to a different platform, or when delivering notifications/alerts to a specific destination. " ,
" parameters " : {
" type " : " object " ,
" properties " : {
" target " : {
" type " : " string " ,
" description " : " Delivery target. Format: ' platform ' (uses home channel) or ' platform:chat_id ' (specific chat). Examples: ' telegram ' , ' discord:123456789 ' , ' slack:C01234ABCDE ' "
} ,
" message " : {
" type " : " string " ,
" description " : " The message text to send "
}
} ,
" required " : [ " target " , " message " ]
}
}
}
]
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
def get_process_tool_definitions ( ) - > List [ Dict [ str , Any ] ] :
"""
Get tool definitions for the process management tool .
The process tool manages background processes started with terminal ( background = true ) .
Actions : list , poll , log , wait , kill . Phase 2 adds : write , submit .
"""
return [
{
" type " : " function " ,
" function " : {
" name " : " process " ,
" description " : (
" Manage background processes started with terminal(background=true). "
" Actions: ' list ' (show all), ' poll ' (check status + new output), "
" ' log ' (full output with pagination), ' wait ' (block until done or timeout), "
2026-02-21 02:41:30 -08:00
" ' kill ' (terminate), ' write ' (send raw stdin data without newline), "
" ' submit ' (send data + Enter, for answering prompts). "
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
) ,
" parameters " : {
" type " : " object " ,
" properties " : {
" action " : {
" type " : " string " ,
" enum " : [ " list " , " poll " , " log " , " wait " , " kill " , " write " , " submit " ] ,
" description " : " Action to perform on background processes "
} ,
" session_id " : {
" type " : " string " ,
2026-02-21 02:41:30 -08:00
" description " : " Process session ID (from terminal background output). Required for all actions except ' list ' . "
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
} ,
" data " : {
" type " : " string " ,
" description " : " Text to send to process stdin (for ' write ' and ' submit ' actions) "
} ,
" timeout " : {
" type " : " integer " ,
" description " : " Max seconds to block for ' wait ' action. Returns partial output on timeout. " ,
" minimum " : 1
} ,
" offset " : {
" type " : " integer " ,
" description " : " Line offset for ' log ' action (default: last 200 lines) "
} ,
" limit " : {
" type " : " integer " ,
" description " : " Max lines to return for ' log ' action " ,
" minimum " : 1
}
} ,
" required " : [ " action " ]
}
}
}
]
2025-08-09 09:52:25 -07:00
def get_all_tool_names ( ) - > List [ str ] :
"""
Get the names of all available tools across all toolsets .
Returns :
List [ str ] : List of all tool names
"""
tool_names = [ ]
# Web tools
2025-08-21 08:12:24 -07:00
if check_firecrawl_api_key ( ) :
2026-01-29 06:10:24 +00:00
tool_names . extend ( [ " web_search " , " web_extract " ] )
2025-11-17 01:14:31 -05:00
2026-01-23 12:26:53 +00:00
# Terminal tools (mini-swe-agent backend)
if check_terminal_requirements ( ) :
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
tool_names . extend ( [ " terminal " , " process " ] )
2025-11-17 01:14:31 -05:00
2025-08-09 09:52:25 -07:00
# Vision tools
if check_vision_requirements ( ) :
tool_names . extend ( [ " vision_analyze " ] )
# MoA tools
if check_moa_requirements ( ) :
tool_names . extend ( [ " mixture_of_agents " ] )
# Image generation tools
if check_image_generation_requirements ( ) :
tool_names . extend ( [ " image_generate " ] )
2026-01-30 07:39:55 +00:00
# Skills tools
if check_skills_requirements ( ) :
2026-02-19 18:25:53 -08:00
tool_names . extend ( [ " skills_list " , " skill_view " , " skill_manage " ] )
2026-01-30 07:39:55 +00:00
2026-01-29 06:10:24 +00:00
# Browser automation tools
if check_browser_requirements ( ) :
tool_names . extend ( [
" browser_navigate " , " browser_snapshot " , " browser_click " ,
" browser_type " , " browser_scroll " , " browser_back " ,
" browser_press " , " browser_close " , " browser_get_images " ,
" browser_vision "
] )
2026-02-21 12:46:18 -08:00
# Cronjob management tools (CLI + gateway, checked at runtime)
2026-02-02 08:26:42 -08:00
if check_cronjob_requirements ( ) :
tool_names . extend ( [
" schedule_cronjob " , " list_cronjobs " , " remove_cronjob "
] )
2026-02-03 23:41:26 -08:00
# RL Training tools
if check_rl_api_keys ( ) :
tool_names . extend ( [
" 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 13:57:59 -08:00
" rl_list_runs " , " rl_test_inference "
2026-02-03 23:41:26 -08:00
] )
2026-02-05 03:49:46 -08:00
# File manipulation tools (use terminal backend)
if check_file_requirements ( ) :
tool_names . extend ( [
2026-02-20 02:43:57 -08:00
" read_file " , " write_file " , " patch " , " search_files "
2026-02-05 03:49:46 -08:00
] )
2026-02-12 10:05:08 -08:00
# Text-to-speech tools
if check_tts_requirements ( ) :
tool_names . extend ( [ " text_to_speech " ] )
2026-02-17 17:02:33 -08:00
# Planning & task management (always available)
if check_todo_requirements ( ) :
tool_names . extend ( [ " todo " ] )
2026-02-19 00:57:31 -08:00
# Persistent memory (always available)
if check_memory_requirements ( ) :
tool_names . extend ( [ " memory " ] )
# Session history search
if check_session_search_requirements ( ) :
tool_names . extend ( [ " session_search " ] )
2026-02-19 20:06:14 -08:00
# Clarifying questions (always available)
if check_clarify_requirements ( ) :
tool_names . extend ( [ " clarify " ] )
2026-02-19 23:23:43 -08:00
# Code execution sandbox (programmatic tool calling)
if check_sandbox_requirements ( ) :
tool_names . extend ( [ " execute_code " ] )
2026-02-20 03:15:53 -08:00
# Subagent delegation
if check_delegate_requirements ( ) :
tool_names . extend ( [ " delegate_task " ] )
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
# Cross-channel messaging (always available on messaging platforms)
tool_names . extend ( [ " send_message " ] )
2025-08-09 09:52:25 -07:00
return tool_names
2026-02-08 20:19:14 +00:00
# Master mapping of every tool name → its toolset.
# This is the single source of truth for all valid tool names in the system.
# Import TOOL_TO_TOOLSET_MAP from here whenever you need to check valid tools.
TOOL_TO_TOOLSET_MAP = {
" web_search " : " web_tools " ,
" web_extract " : " web_tools " ,
" terminal " : " terminal_tools " ,
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
" process " : " terminal_tools " ,
2026-02-08 20:19:14 +00:00
" vision_analyze " : " vision_tools " ,
" mixture_of_agents " : " moa_tools " ,
" image_generate " : " image_tools " ,
# Skills tools
" skills_list " : " skills_tools " ,
" skill_view " : " skills_tools " ,
2026-02-19 18:25:53 -08:00
" skill_manage " : " skills_tools " ,
2026-02-08 20:19:14 +00:00
# Browser automation tools
" browser_navigate " : " browser_tools " ,
" browser_snapshot " : " browser_tools " ,
" browser_click " : " browser_tools " ,
" browser_type " : " browser_tools " ,
" browser_scroll " : " browser_tools " ,
" browser_back " : " browser_tools " ,
" browser_press " : " browser_tools " ,
" browser_close " : " browser_tools " ,
" browser_get_images " : " browser_tools " ,
" browser_vision " : " browser_tools " ,
# Cronjob management tools
" schedule_cronjob " : " cronjob_tools " ,
" list_cronjobs " : " cronjob_tools " ,
" remove_cronjob " : " cronjob_tools " ,
# RL Training tools
" rl_list_environments " : " rl_tools " ,
" rl_select_environment " : " rl_tools " ,
" rl_get_current_config " : " rl_tools " ,
" rl_edit_config " : " rl_tools " ,
" rl_start_training " : " rl_tools " ,
" rl_check_status " : " rl_tools " ,
" rl_stop_training " : " rl_tools " ,
" rl_get_results " : " rl_tools " ,
" rl_list_runs " : " rl_tools " ,
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
" rl_test_inference " : " rl_tools " ,
2026-02-12 10:05:08 -08:00
# Text-to-speech tools
" text_to_speech " : " tts_tools " ,
2026-02-08 20:19:14 +00:00
# File manipulation tools
" read_file " : " file_tools " ,
" write_file " : " file_tools " ,
" patch " : " file_tools " ,
2026-02-20 02:43:57 -08:00
" search_files " : " file_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
# Cross-channel messaging
" send_message " : " messaging_tools " ,
2026-02-17 17:02:33 -08:00
# Planning & task management
" todo " : " todo_tools " ,
2026-02-19 00:57:31 -08:00
# Persistent memory
" memory " : " memory_tools " ,
# Session history search
" session_search " : " session_search_tools " ,
2026-02-19 23:23:43 -08:00
# Clarifying questions
" clarify " : " clarify_tools " ,
# Code execution sandbox
" execute_code " : " code_execution_tools " ,
2026-02-20 03:15:53 -08:00
# Subagent delegation
" delegate_task " : " delegation_tools " ,
2026-02-08 20:19:14 +00:00
}
2025-08-09 09:52:25 -07:00
def get_toolset_for_tool ( tool_name : str ) - > str :
"""
Get the toolset that a tool belongs to .
Args :
tool_name ( str ) : Name of the tool
Returns :
str : Name of the toolset , or " unknown " if not found
"""
2026-02-08 20:19:14 +00:00
return TOOL_TO_TOOLSET_MAP . get ( tool_name , " unknown " )
2025-08-09 09:52:25 -07:00
2026-02-19 23:23:43 -08:00
# Stores the resolved tool name list from the most recent get_tool_definitions()
# call, so execute_code can determine which tools are available in this session.
_last_resolved_tool_names : Optional [ List [ str ] ] = None
2025-08-09 09:52:25 -07:00
def get_tool_definitions (
enabled_toolsets : List [ str ] = None ,
2026-01-31 06:30:48 +00:00
disabled_toolsets : List [ str ] = None ,
quiet_mode : bool = False ,
2025-08-09 09:52:25 -07:00
) - > List [ Dict [ str , Any ] ] :
"""
2025-09-10 00:43:55 -07:00
Get tool definitions for model API calls with toolset - based filtering .
2025-08-09 09:52:25 -07:00
2025-09-10 00:43:55 -07:00
This function aggregates tool definitions from available toolsets .
All tools must be part of a toolset to be accessible . Individual tool
selection is not supported - use toolsets to organize and select tools .
2025-08-09 09:52:25 -07:00
Args :
2025-09-10 00:43:55 -07:00
enabled_toolsets ( List [ str ] ) : Only include tools from these toolsets .
If None , all available tools are included .
disabled_toolsets ( List [ str ] ) : Exclude tools from these toolsets .
Applied only if enabled_toolsets is None .
2025-08-09 09:52:25 -07:00
Returns :
List [ Dict ] : Filtered list of tool definitions
Examples :
2025-09-10 00:43:55 -07:00
# Use predefined toolsets
tools = get_tool_definitions ( enabled_toolsets = [ " research " ] )
tools = get_tool_definitions ( enabled_toolsets = [ " development " ] )
2025-08-09 09:52:25 -07:00
2025-09-10 00:43:55 -07:00
# Combine multiple toolsets
tools = get_tool_definitions ( enabled_toolsets = [ " web " , " vision " ] )
2025-08-09 09:52:25 -07:00
2025-09-10 00:43:55 -07:00
# All tools except those in terminal toolset
tools = get_tool_definitions ( disabled_toolsets = [ " terminal " ] )
2025-08-09 09:52:25 -07:00
2025-09-10 00:43:55 -07:00
# Default - all available tools
tools = get_tool_definitions ( )
2025-08-09 09:52:25 -07:00
"""
2025-09-10 00:43:55 -07:00
# Collect all available tool definitions
all_available_tools_map = { }
2025-08-09 09:52:25 -07:00
2025-09-10 00:43:55 -07:00
# Map tool names to their definitions
if check_firecrawl_api_key ( ) :
for tool in get_web_tool_definitions ( ) :
all_available_tools_map [ tool [ " function " ] [ " name " ] ] = tool
2025-11-17 01:14:31 -05:00
2026-01-23 12:26:53 +00:00
if check_terminal_requirements ( ) :
2025-09-10 00:43:55 -07:00
for tool in get_terminal_tool_definitions ( ) :
all_available_tools_map [ tool [ " function " ] [ " name " ] ] = tool
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
# Process management tool (paired with terminal)
for tool in get_process_tool_definitions ( ) :
all_available_tools_map [ tool [ " function " ] [ " name " ] ] = tool
2025-11-17 01:14:31 -05:00
2025-09-10 00:43:55 -07:00
if check_vision_requirements ( ) :
for tool in get_vision_tool_definitions ( ) :
all_available_tools_map [ tool [ " function " ] [ " name " ] ] = tool
if check_moa_requirements ( ) :
for tool in get_moa_tool_definitions ( ) :
all_available_tools_map [ tool [ " function " ] [ " name " ] ] = tool
if check_image_generation_requirements ( ) :
for tool in get_image_tool_definitions ( ) :
all_available_tools_map [ tool [ " function " ] [ " name " ] ] = tool
2026-01-30 07:39:55 +00:00
if check_skills_requirements ( ) :
for tool in get_skills_tool_definitions ( ) :
all_available_tools_map [ tool [ " function " ] [ " name " ] ] = tool
2026-02-19 18:25:53 -08:00
for tool in get_skill_manage_tool_definitions ( ) :
all_available_tools_map [ tool [ " function " ] [ " name " ] ] = tool
2026-01-30 07:39:55 +00:00
2026-01-29 06:10:24 +00:00
if check_browser_requirements ( ) :
for tool in get_browser_tool_definitions ( ) :
all_available_tools_map [ tool [ " function " ] [ " name " ] ] = tool
2026-02-02 08:26:42 -08:00
# Cronjob management tools (CLI-only)
if check_cronjob_requirements ( ) :
for tool in get_cronjob_tool_definitions_formatted ( ) :
all_available_tools_map [ tool [ " function " ] [ " name " ] ] = tool
2026-02-03 23:41:26 -08:00
# RL Training tools
if check_rl_api_keys ( ) :
for tool in get_rl_tool_definitions ( ) :
all_available_tools_map [ tool [ " function " ] [ " name " ] ] = tool
2026-02-05 03:49:46 -08:00
# File manipulation tools (use terminal backend)
if check_file_requirements ( ) :
for tool in get_file_tool_definitions ( ) :
all_available_tools_map [ tool [ " function " ] [ " name " ] ] = tool
2026-02-12 10:05:08 -08:00
# Text-to-speech tools
if check_tts_requirements ( ) :
for tool in get_tts_tool_definitions ( ) :
all_available_tools_map [ tool [ " function " ] [ " name " ] ] = tool
2026-02-17 17:02:33 -08:00
# Planning & task management tool
if check_todo_requirements ( ) :
for tool in get_todo_tool_definitions ( ) :
all_available_tools_map [ tool [ " function " ] [ " name " ] ] = tool
2026-02-19 00:57:31 -08:00
# Persistent memory tool
if check_memory_requirements ( ) :
for tool in get_memory_tool_definitions ( ) :
all_available_tools_map [ tool [ " function " ] [ " name " ] ] = tool
# Session history search tool
if check_session_search_requirements ( ) :
for tool in get_session_search_tool_definitions ( ) :
all_available_tools_map [ tool [ " function " ] [ " name " ] ] = tool
2026-02-19 20:06:14 -08:00
# Clarifying questions tool
if check_clarify_requirements ( ) :
for tool in get_clarify_tool_definitions ( ) :
all_available_tools_map [ tool [ " function " ] [ " name " ] ] = tool
2026-02-19 23:23:43 -08:00
# Code execution sandbox (programmatic tool calling)
if check_sandbox_requirements ( ) :
for tool in get_execute_code_tool_definitions ( ) :
all_available_tools_map [ tool [ " function " ] [ " name " ] ] = tool
2026-02-20 03:15:53 -08:00
# Subagent delegation
if check_delegate_requirements ( ) :
for tool in get_delegate_tool_definitions ( ) :
all_available_tools_map [ tool [ " function " ] [ " name " ] ] = tool
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
# Cross-channel messaging (always available on messaging platforms)
for tool in get_send_message_tool_definitions ( ) :
all_available_tools_map [ tool [ " function " ] [ " name " ] ] = tool
2025-09-10 00:43:55 -07:00
# Determine which tools to include based on toolsets
tools_to_include = set ( )
2025-08-09 09:52:25 -07:00
if enabled_toolsets :
# Only include tools from enabled toolsets
for toolset_name in enabled_toolsets :
2025-09-10 00:43:55 -07:00
if validate_toolset ( toolset_name ) :
resolved_tools = resolve_toolset ( toolset_name )
tools_to_include . update ( resolved_tools )
2026-02-02 23:46:41 -08:00
if not quiet_mode :
print ( f " ✅ Enabled toolset ' { toolset_name } ' : { ' , ' . join ( resolved_tools ) if resolved_tools else ' no tools ' } " )
2025-08-09 09:52:25 -07:00
else :
2025-09-10 00:43:55 -07:00
# Try legacy compatibility
2026-02-02 08:26:42 -08:00
if toolset_name in [ " web_tools " , " terminal_tools " , " vision_tools " , " moa_tools " , " image_tools " , " skills_tools " , " browser_tools " , " cronjob_tools " ] :
2025-09-10 00:43:55 -07:00
# Map legacy names to new system
legacy_map = {
2026-01-29 06:10:24 +00:00
" web_tools " : [ " web_search " , " web_extract " ] ,
2025-09-10 00:43:55 -07:00
" terminal_tools " : [ " terminal " ] ,
" vision_tools " : [ " vision_analyze " ] ,
" moa_tools " : [ " mixture_of_agents " ] ,
2026-01-29 06:10:24 +00:00
" image_tools " : [ " image_generate " ] ,
2026-02-19 18:25:53 -08:00
" skills_tools " : [ " skills_list " , " skill_view " , " skill_manage " ] ,
2026-01-29 06:10:24 +00:00
" browser_tools " : [
" browser_navigate " , " browser_snapshot " , " browser_click " ,
" browser_type " , " browser_scroll " , " browser_back " ,
" browser_press " , " browser_close " , " browser_get_images " ,
" browser_vision "
2026-02-02 08:26:42 -08:00
] ,
2026-02-03 23:41:26 -08:00
" cronjob_tools " : [ " schedule_cronjob " , " list_cronjobs " , " remove_cronjob " ] ,
" rl_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 13:57:59 -08:00
" rl_list_runs " , " rl_test_inference "
2026-02-05 03:49:46 -08:00
] ,
2026-02-20 02:43:57 -08:00
" file_tools " : [ " read_file " , " write_file " , " patch " , " search_files " ] ,
2026-02-12 10:05:08 -08:00
" tts_tools " : [ " text_to_speech " ]
2025-09-10 00:43:55 -07:00
}
legacy_tools = legacy_map . get ( toolset_name , [ ] )
tools_to_include . update ( legacy_tools )
2026-02-02 23:46:41 -08:00
if not quiet_mode :
print ( f " ✅ Enabled legacy toolset ' { toolset_name } ' : { ' , ' . join ( legacy_tools ) } " )
2025-09-10 00:43:55 -07:00
else :
2026-02-02 23:46:41 -08:00
if not quiet_mode :
print ( f " ⚠️ Unknown toolset: { toolset_name } " )
2025-08-09 09:52:25 -07:00
elif disabled_toolsets :
2025-09-10 00:43:55 -07:00
# Start with all tools from all toolsets, then remove disabled ones
# Note: Only tools that are part of toolsets are accessible
# We need to get all tools from all defined toolsets
from toolsets import get_all_toolsets
all_toolset_tools = set ( )
for toolset_name in get_all_toolsets ( ) :
resolved_tools = resolve_toolset ( toolset_name )
all_toolset_tools . update ( resolved_tools )
2025-08-09 09:52:25 -07:00
2025-09-10 00:43:55 -07:00
# Start with all tools from toolsets
tools_to_include = all_toolset_tools
2025-08-09 09:52:25 -07:00
2025-09-10 00:43:55 -07:00
# Remove tools from disabled toolsets
for toolset_name in disabled_toolsets :
if validate_toolset ( toolset_name ) :
resolved_tools = resolve_toolset ( toolset_name )
tools_to_include . difference_update ( resolved_tools )
2026-02-02 23:46:41 -08:00
if not quiet_mode :
print ( f " 🚫 Disabled toolset ' { toolset_name } ' : { ' , ' . join ( resolved_tools ) if resolved_tools else ' no tools ' } " )
2025-09-10 00:43:55 -07:00
else :
# Try legacy compatibility
2026-02-02 08:26:42 -08:00
if toolset_name in [ " web_tools " , " terminal_tools " , " vision_tools " , " moa_tools " , " image_tools " , " skills_tools " , " browser_tools " , " cronjob_tools " ] :
2025-09-10 00:43:55 -07:00
legacy_map = {
2026-01-29 06:10:24 +00:00
" web_tools " : [ " web_search " , " web_extract " ] ,
2025-09-10 00:43:55 -07:00
" terminal_tools " : [ " terminal " ] ,
" vision_tools " : [ " vision_analyze " ] ,
" moa_tools " : [ " mixture_of_agents " ] ,
2026-01-29 06:10:24 +00:00
" image_tools " : [ " image_generate " ] ,
2026-02-19 18:25:53 -08:00
" skills_tools " : [ " skills_list " , " skill_view " , " skill_manage " ] ,
2026-01-29 06:10:24 +00:00
" browser_tools " : [
" browser_navigate " , " browser_snapshot " , " browser_click " ,
" browser_type " , " browser_scroll " , " browser_back " ,
" browser_press " , " browser_close " , " browser_get_images " ,
" browser_vision "
2026-02-02 08:26:42 -08:00
] ,
2026-02-03 23:41:26 -08:00
" cronjob_tools " : [ " schedule_cronjob " , " list_cronjobs " , " remove_cronjob " ] ,
" rl_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 13:57:59 -08:00
" rl_list_runs " , " rl_test_inference "
2026-02-05 03:49:46 -08:00
] ,
2026-02-20 02:43:57 -08:00
" file_tools " : [ " read_file " , " write_file " , " patch " , " search_files " ] ,
2026-02-12 10:05:08 -08:00
" tts_tools " : [ " text_to_speech " ]
2025-09-10 00:43:55 -07:00
}
legacy_tools = legacy_map . get ( toolset_name , [ ] )
tools_to_include . difference_update ( legacy_tools )
2026-02-02 23:46:41 -08:00
if not quiet_mode :
print ( f " 🚫 Disabled legacy toolset ' { toolset_name } ' : { ' , ' . join ( legacy_tools ) } " )
2025-09-10 00:43:55 -07:00
else :
2026-02-02 23:46:41 -08:00
if not quiet_mode :
print ( f " ⚠️ Unknown toolset: { toolset_name } " )
2025-09-10 00:43:55 -07:00
else :
# No filtering - include all tools from all defined toolsets
from toolsets import get_all_toolsets
for toolset_name in get_all_toolsets ( ) :
resolved_tools = resolve_toolset ( toolset_name )
tools_to_include . update ( resolved_tools )
# Build final tool list (only include tools that are available)
filtered_tools = [ ]
for tool_name in tools_to_include :
if tool_name in all_available_tools_map :
filtered_tools . append ( all_available_tools_map [ tool_name ] )
# Sort tools for consistent ordering
filtered_tools . sort ( key = lambda t : t [ " function " ] [ " name " ] )
2026-01-31 06:30:48 +00:00
if not quiet_mode :
if filtered_tools :
tool_names = [ t [ " function " ] [ " name " ] for t in filtered_tools ]
print ( f " 🛠️ Final tool selection ( { len ( filtered_tools ) } tools): { ' , ' . join ( tool_names ) } " )
else :
print ( " 🛠️ No tools selected (all filtered out or unavailable) " )
2025-08-09 09:52:25 -07:00
2026-02-19 23:23:43 -08:00
# Store resolved names so execute_code knows what's available in this session
global _last_resolved_tool_names
_last_resolved_tool_names = [ t [ " function " ] [ " name " ] for t in filtered_tools ]
2025-09-10 00:43:55 -07:00
return filtered_tools
2025-08-09 09:52:25 -07:00
def handle_web_function_call ( function_name : str , function_args : Dict [ str , Any ] ) - > str :
"""
Handle function calls for web tools .
Args :
function_name ( str ) : Name of the web function to call
function_args ( Dict ) : Arguments for the function
Returns :
str : Function result as JSON string
"""
if function_name == " web_search " :
query = function_args . get ( " query " , " " )
2025-09-10 00:43:55 -07:00
# Always use fixed limit of 5
limit = 5
2025-08-09 09:52:25 -07:00
return web_search_tool ( query , limit )
elif function_name == " web_extract " :
urls = function_args . get ( " urls " , [ ] )
# Limit URLs to prevent abuse
urls = urls [ : 5 ] if isinstance ( urls , list ) else [ ]
2026-02-08 05:00:47 +00:00
# Run async function -- use existing loop if available (Atropos),
# otherwise create one (normal CLI)
try :
loop = asyncio . get_running_loop ( )
# Already in an async context (Atropos) -- run in a thread
import concurrent . futures
with concurrent . futures . ThreadPoolExecutor ( max_workers = 1 ) as pool :
return pool . submit (
lambda : asyncio . run ( web_extract_tool ( urls , " markdown " ) )
) . result ( timeout = 120 )
except RuntimeError :
# No running loop (normal CLI) -- use asyncio.run directly
return asyncio . run ( web_extract_tool ( urls , " markdown " ) )
2025-08-09 09:52:25 -07:00
else :
2025-11-05 03:47:17 +00:00
return json . dumps ( { " error " : f " Unknown web function: { function_name } " } , ensure_ascii = False )
2025-08-09 09:52:25 -07:00
2025-11-03 17:42:23 -05:00
def handle_terminal_function_call ( function_name : str , function_args : Dict [ str , Any ] , task_id : Optional [ str ] = None ) - > str :
2025-08-09 09:52:25 -07:00
"""
Handle function calls for terminal tools .
2026-01-23 12:26:53 +00:00
Uses mini - swe - agent backend ( local / docker / modal ) by default .
2025-11-03 17:42:23 -05:00
2025-08-09 09:52:25 -07:00
Args :
function_name ( str ) : Name of the terminal function to call
function_args ( Dict ) : Arguments for the function
2026-01-23 12:26:53 +00:00
task_id ( str ) : Unique identifier for this task to isolate environments between concurrent tasks ( optional )
2025-11-03 17:42:23 -05:00
2025-08-09 09:52:25 -07:00
Returns :
str : Function result as JSON string
"""
if function_name == " terminal " :
command = function_args . get ( " command " )
background = function_args . get ( " background " , False )
timeout = function_args . get ( " timeout " )
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
workdir = function_args . get ( " workdir " )
check_interval = function_args . get ( " check_interval " )
pty = function_args . get ( " pty " , False )
2025-09-06 22:07:38 -07:00
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
return terminal_tool ( command = command , background = background , timeout = timeout , task_id = task_id , workdir = workdir , check_interval = check_interval , pty = pty )
2025-11-03 17:42:23 -05:00
2025-08-09 09:52:25 -07:00
else :
2025-11-05 03:47:17 +00:00
return json . dumps ( { " error " : f " Unknown terminal function: { function_name } " } , ensure_ascii = False )
2025-08-09 09:52:25 -07:00
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
def handle_process_function_call ( function_name : str , function_args : Dict [ str , Any ] , task_id : Optional [ str ] = None ) - > str :
"""
Handle function calls for the process management tool .
Routes actions ( list , poll , log , wait , kill ) to the ProcessRegistry .
"""
from tools . process_registry import process_registry
action = function_args . get ( " action " , " " )
session_id = function_args . get ( " session_id " , " " )
if action == " list " :
sessions = process_registry . list_sessions ( task_id = task_id )
return json . dumps ( { " processes " : sessions } , ensure_ascii = False )
elif action == " poll " :
if not session_id :
return json . dumps ( { " error " : " session_id is required for poll " } , ensure_ascii = False )
return json . dumps ( process_registry . poll ( session_id ) , ensure_ascii = False )
elif action == " log " :
if not session_id :
return json . dumps ( { " error " : " session_id is required for log " } , ensure_ascii = False )
offset = function_args . get ( " offset " , 0 )
limit = function_args . get ( " limit " , 200 )
return json . dumps ( process_registry . read_log ( session_id , offset = offset , limit = limit ) , ensure_ascii = False )
elif action == " wait " :
if not session_id :
return json . dumps ( { " error " : " session_id is required for wait " } , ensure_ascii = False )
timeout = function_args . get ( " timeout " )
return json . dumps ( process_registry . wait ( session_id , timeout = timeout ) , ensure_ascii = False )
elif action == " kill " :
if not session_id :
return json . dumps ( { " error " : " session_id is required for kill " } , ensure_ascii = False )
return json . dumps ( process_registry . kill_process ( session_id ) , ensure_ascii = False )
elif action == " write " :
if not session_id :
return json . dumps ( { " error " : " session_id is required for write " } , ensure_ascii = False )
data = function_args . get ( " data " , " " )
return json . dumps ( process_registry . write_stdin ( session_id , data ) , ensure_ascii = False )
elif action == " submit " :
if not session_id :
return json . dumps ( { " error " : " session_id is required for submit " } , ensure_ascii = False )
data = function_args . get ( " data " , " " )
return json . dumps ( process_registry . submit_stdin ( session_id , data ) , ensure_ascii = False )
else :
return json . dumps ( { " error " : f " Unknown process action: { action } . Use: list, poll, log, wait, kill, write, submit " } , ensure_ascii = False )
2025-08-09 09:52:25 -07:00
def handle_vision_function_call ( function_name : str , function_args : Dict [ str , Any ] ) - > str :
"""
Handle function calls for vision tools .
Args :
function_name ( str ) : Name of the vision function to call
function_args ( Dict ) : Arguments for the function
Returns :
str : Function result as JSON string
"""
if function_name == " vision_analyze " :
image_url = function_args . get ( " image_url " , " " )
question = function_args . get ( " question " , " " )
2025-09-06 22:07:38 -07:00
full_prompt = f " Fully describe and explain everything about this image, then answer the following question: \n \n { question } "
2025-08-09 09:52:25 -07:00
# Run async function in event loop
2026-01-14 13:40:10 +00:00
return asyncio . run ( vision_analyze_tool ( image_url , full_prompt , " google/gemini-3-flash-preview " ) )
2025-08-09 09:52:25 -07:00
else :
2025-11-05 03:47:17 +00:00
return json . dumps ( { " error " : f " Unknown vision function: { function_name } " } , ensure_ascii = False )
2025-08-09 09:52:25 -07:00
def handle_moa_function_call ( function_name : str , function_args : Dict [ str , Any ] ) - > str :
"""
Handle function calls for Mixture - of - Agents tools .
Args :
function_name ( str ) : Name of the MoA function to call
function_args ( Dict ) : Arguments for the function
Returns :
str : Function result as JSON string
"""
if function_name == " mixture_of_agents " :
user_prompt = function_args . get ( " user_prompt " , " " )
if not user_prompt :
2025-11-05 03:47:17 +00:00
return json . dumps ( { " error " : " user_prompt is required for MoA processing " } , ensure_ascii = False )
2025-08-09 09:52:25 -07:00
# Run async function in event loop
return asyncio . run ( mixture_of_agents_tool ( user_prompt = user_prompt ) )
else :
2025-11-05 03:47:17 +00:00
return json . dumps ( { " error " : f " Unknown MoA function: { function_name } " } , ensure_ascii = False )
2025-08-09 09:52:25 -07:00
def handle_image_function_call ( function_name : str , function_args : Dict [ str , Any ] ) - > str :
"""
Handle function calls for image generation tools .
Args :
function_name ( str ) : Name of the image generation function to call
function_args ( Dict ) : Arguments for the function
Returns :
str : Function result as JSON string
"""
if function_name == " image_generate " :
prompt = function_args . get ( " prompt " , " " )
if not prompt :
2025-11-05 03:47:17 +00:00
return json . dumps ( { " success " : False , " image " : None } , ensure_ascii = False )
2025-08-09 09:52:25 -07:00
2026-01-18 10:11:59 +00:00
aspect_ratio = function_args . get ( " aspect_ratio " , " landscape " )
2025-08-09 09:52:25 -07:00
# Use fixed internal defaults for all other parameters (not exposed to model)
num_inference_steps = 50
guidance_scale = 4.5
num_images = 1
output_format = " png "
seed = None
2025-10-07 14:08:46 +00:00
# Run async function in event loop with proper handling for multiprocessing
try :
# Try to get existing event loop
loop = asyncio . get_event_loop ( )
if loop . is_closed ( ) :
# If closed, create a new one
loop = asyncio . new_event_loop ( )
asyncio . set_event_loop ( loop )
except RuntimeError :
# No event loop in current thread, create one
loop = asyncio . new_event_loop ( )
asyncio . set_event_loop ( loop )
# Run the coroutine in the event loop
result = loop . run_until_complete ( image_generate_tool (
2025-08-09 09:52:25 -07:00
prompt = prompt ,
2026-01-18 10:11:59 +00:00
aspect_ratio = aspect_ratio ,
2025-08-09 09:52:25 -07:00
num_inference_steps = num_inference_steps ,
guidance_scale = guidance_scale ,
num_images = num_images ,
output_format = output_format ,
seed = seed
) )
2025-10-07 14:08:46 +00:00
return result
2025-08-09 09:52:25 -07:00
else :
2025-11-05 03:47:17 +00:00
return json . dumps ( { " error " : f " Unknown image generation function: { function_name } " } , ensure_ascii = False )
2025-08-09 09:52:25 -07:00
2026-01-30 07:39:55 +00:00
def handle_skills_function_call ( function_name : str , function_args : Dict [ str , Any ] ) - > str :
"""
2026-02-19 18:25:53 -08:00
Handle function calls for skills tools ( read - only and management ) .
2026-01-30 07:39:55 +00:00
Args :
function_name ( str ) : Name of the skills function to call
function_args ( Dict ) : Arguments for the function
Returns :
str : Function result as JSON string
"""
2026-02-10 19:48:38 -08:00
if function_name == " skills_list " :
2026-01-30 07:39:55 +00:00
category = function_args . get ( " category " )
return skills_list ( category = category )
elif function_name == " skill_view " :
name = function_args . get ( " name " , " " )
if not name :
return json . dumps ( { " error " : " Skill name is required " } , ensure_ascii = False )
file_path = function_args . get ( " file_path " )
return skill_view ( name , file_path = file_path )
2026-02-19 18:25:53 -08:00
elif function_name == " skill_manage " :
action = function_args . get ( " action " , " " )
name = function_args . get ( " name " , " " )
if not action :
return json . dumps ( { " error " : " action is required " } , ensure_ascii = False )
if not name :
return json . dumps ( { " error " : " name is required " } , ensure_ascii = False )
return skill_manage (
action = action ,
name = name ,
content = function_args . get ( " content " ) ,
category = function_args . get ( " category " ) ,
file_path = function_args . get ( " file_path " ) ,
file_content = function_args . get ( " file_content " ) ,
old_string = function_args . get ( " old_string " ) ,
new_string = function_args . get ( " new_string " ) ,
replace_all = function_args . get ( " replace_all " , False ) ,
)
2026-01-30 07:39:55 +00:00
else :
return json . dumps ( { " error " : f " Unknown skills function: { function_name } " } , ensure_ascii = False )
2026-01-29 06:10:24 +00:00
# Browser tool handlers mapping
BROWSER_HANDLERS = {
" browser_navigate " : browser_navigate ,
" browser_click " : browser_click ,
" browser_type " : browser_type ,
" browser_scroll " : browser_scroll ,
" browser_back " : browser_back ,
" browser_press " : browser_press ,
" browser_close " : browser_close ,
" browser_get_images " : browser_get_images ,
" browser_vision " : browser_vision ,
}
def handle_browser_function_call (
function_name : str ,
function_args : Dict [ str , Any ] ,
task_id : Optional [ str ] = None ,
user_task : Optional [ str ] = None
) - > str :
"""
Handle function calls for browser automation tools .
Args :
function_name ( str ) : Name of the browser function to call
function_args ( Dict ) : Arguments for the function
task_id ( str ) : Task identifier for session isolation
user_task ( str ) : User ' s current task (for task-aware extraction in snapshots)
Returns :
str : Function result as JSON string
"""
# Special handling for browser_snapshot which needs user_task for extraction
if function_name == " browser_snapshot " :
full = function_args . get ( " full " , False )
return browser_snapshot ( full = full , task_id = task_id , user_task = user_task )
# Handle other browser tools
if function_name in BROWSER_HANDLERS :
handler = BROWSER_HANDLERS [ function_name ]
# Add task_id to args
return handler ( * * function_args , task_id = task_id )
return json . dumps ( { " error " : f " Unknown browser function: { function_name } " } , ensure_ascii = False )
2026-02-02 08:26:42 -08:00
def handle_cronjob_function_call (
function_name : str ,
function_args : Dict [ str , Any ] ,
task_id : Optional [ str ] = None
) - > str :
"""
Handle function calls for cronjob management tools .
These tools are only available in interactive CLI mode ( hermes - cli toolset ) .
Args :
function_name ( str ) : Name of the cronjob function to call
function_args ( Dict ) : Arguments for the function
task_id ( str ) : Task identifier ( unused , for API consistency )
Returns :
str : Function result as JSON string
"""
if function_name == " schedule_cronjob " :
return schedule_cronjob (
prompt = function_args . get ( " prompt " , " " ) ,
schedule = function_args . get ( " schedule " , " " ) ,
name = function_args . get ( " name " ) ,
repeat = function_args . get ( " repeat " ) ,
task_id = task_id
)
elif function_name == " list_cronjobs " :
return list_cronjobs (
include_disabled = function_args . get ( " include_disabled " , False ) ,
task_id = task_id
)
elif function_name == " remove_cronjob " :
return remove_cronjob (
job_id = function_args . get ( " job_id " , " " ) ,
task_id = task_id
)
return json . dumps ( { " error " : f " Unknown cronjob function: { function_name } " } , ensure_ascii = False )
2026-02-03 23:41:26 -08:00
def handle_rl_function_call (
function_name : str ,
function_args : Dict [ str , Any ]
) - > str :
"""
Handle function calls for RL training tools .
These tools communicate with the RL API server to manage training runs .
Args :
function_name ( str ) : Name of the RL function to call
function_args ( Dict ) : Arguments for the function
Returns :
str : Function result as JSON string
"""
# Run async functions in event loop
import asyncio
try :
loop = asyncio . get_event_loop ( )
except RuntimeError :
loop = asyncio . new_event_loop ( )
asyncio . set_event_loop ( loop )
if function_name == " rl_list_environments " :
return loop . run_until_complete ( rl_list_environments ( ) )
elif function_name == " rl_select_environment " :
return loop . run_until_complete (
rl_select_environment ( name = function_args . get ( " name " , " " ) )
)
elif function_name == " rl_get_current_config " :
return loop . run_until_complete ( rl_get_current_config ( ) )
elif function_name == " rl_edit_config " :
return loop . run_until_complete (
rl_edit_config (
field = function_args . get ( " field " , " " ) ,
value = function_args . get ( " value " )
)
)
elif function_name == " rl_start_training " :
2026-02-04 09:36:51 -08:00
return loop . run_until_complete ( rl_start_training ( ) )
2026-02-03 23:41:26 -08:00
elif function_name == " rl_check_status " :
return loop . run_until_complete (
rl_check_status ( run_id = function_args . get ( " run_id " , " " ) )
)
elif function_name == " rl_stop_training " :
return loop . run_until_complete (
rl_stop_training ( run_id = function_args . get ( " run_id " , " " ) )
)
elif function_name == " rl_get_results " :
return loop . run_until_complete (
rl_get_results ( run_id = function_args . get ( " run_id " , " " ) )
)
2026-02-04 10:36:01 -08:00
elif function_name == " rl_list_runs " :
return loop . run_until_complete ( rl_list_runs ( ) )
2026-02-03 23:41:26 -08:00
elif function_name == " rl_test_inference " :
return loop . run_until_complete (
rl_test_inference (
2026-02-04 10:36:01 -08:00
num_steps = function_args . get ( " num_steps " , 3 ) ,
group_size = function_args . get ( " group_size " , 16 ) ,
models = function_args . get ( " models " ) ,
2026-02-03 23:41:26 -08:00
)
)
return json . dumps ( { " error " : f " Unknown RL function: { function_name } " } , ensure_ascii = False )
2026-02-05 03:49:46 -08:00
def handle_file_function_call (
function_name : str ,
function_args : Dict [ str , Any ] ,
task_id : Optional [ str ] = None
) - > str :
"""
Handle function calls for file manipulation tools .
These tools use the terminal backend for all operations , supporting
local , docker , singularity , ssh , and modal environments .
Args :
function_name ( str ) : Name of the file function to call
function_args ( Dict ) : Arguments for the function
task_id ( str ) : Task identifier for environment isolation
Returns :
str : Function result as JSON string
"""
# Determine task_id to use
tid = task_id or " default "
if function_name == " read_file " :
return read_file_tool (
path = function_args . get ( " path " , " " ) ,
offset = function_args . get ( " offset " , 1 ) ,
limit = function_args . get ( " limit " , 500 ) ,
task_id = tid
)
elif function_name == " write_file " :
return write_file_tool (
path = function_args . get ( " path " , " " ) ,
content = function_args . get ( " content " , " " ) ,
task_id = tid
)
elif function_name == " patch " :
return patch_tool (
mode = function_args . get ( " mode " , " replace " ) ,
path = function_args . get ( " path " ) ,
old_string = function_args . get ( " old_string " ) ,
new_string = function_args . get ( " new_string " ) ,
replace_all = function_args . get ( " replace_all " , False ) ,
patch = function_args . get ( " patch " ) ,
task_id = tid
)
2026-02-20 02:43:57 -08:00
elif function_name == " search_files " :
2026-02-21 02:41:30 -08:00
# Accept both old enum values (grep/find) and new ones (content/files)
2026-02-20 02:46:30 -08:00
target_map = { " grep " : " content " , " find " : " files " }
2026-02-21 02:41:30 -08:00
raw_target = function_args . get ( " target " , " content " )
2026-02-20 02:46:30 -08:00
target = target_map . get ( raw_target , raw_target )
2026-02-05 03:49:46 -08:00
return search_tool (
pattern = function_args . get ( " pattern " , " " ) ,
2026-02-20 02:46:30 -08:00
target = target ,
2026-02-05 03:49:46 -08:00
path = function_args . get ( " path " , " . " ) ,
file_glob = function_args . get ( " file_glob " ) ,
limit = function_args . get ( " limit " , 50 ) ,
offset = function_args . get ( " offset " , 0 ) ,
output_mode = function_args . get ( " output_mode " , " content " ) ,
context = function_args . get ( " context " , 0 ) ,
task_id = tid
)
return json . dumps ( { " error " : f " Unknown file function: { function_name } " } , ensure_ascii = False )
2026-02-12 10:05:08 -08:00
def handle_tts_function_call (
function_name : str ,
function_args : Dict [ str , Any ]
) - > str :
"""
Handle function calls for text - to - speech tools .
Args :
function_name ( str ) : Name of the TTS function to call
function_args ( Dict ) : Arguments for the function
Returns :
str : Function result as JSON string
"""
if function_name == " text_to_speech " :
text = function_args . get ( " text " , " " )
output_path = function_args . get ( " output_path " )
return text_to_speech_tool ( text = text , output_path = output_path )
return json . dumps ( { " error " : f " Unknown TTS function: { function_name } " } , ensure_ascii = False )
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
def handle_send_message_function_call ( function_name , function_args ) :
2026-02-19 09:37:25 -08:00
""" Handle cross-channel send_message tool calls.
Sends a message directly to the target platform using its API .
Works in both CLI and gateway contexts - - does not require the
gateway to be running . Loads credentials from the gateway config
( env vars / ~ / . hermes / gateway . json ) .
"""
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
import json
2026-02-19 09:37:25 -08:00
import asyncio
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
target = function_args . get ( " target " , " " )
message = function_args . get ( " message " , " " )
if not target or not message :
return json . dumps ( { " error " : " Both ' target ' and ' message ' are required " } )
2026-02-19 09:37:25 -08:00
# Parse target: "platform" or "platform:chat_id"
parts = target . split ( " : " , 1 )
platform_name = parts [ 0 ] . strip ( ) . lower ( )
chat_id = parts [ 1 ] . strip ( ) if len ( parts ) > 1 else None
try :
from gateway . config import load_gateway_config , Platform
config = load_gateway_config ( )
except Exception as e :
return json . dumps ( { " error " : f " Failed to load gateway config: { e } " } )
platform_map = {
" telegram " : Platform . TELEGRAM ,
" discord " : Platform . DISCORD ,
" slack " : Platform . SLACK ,
" whatsapp " : Platform . WHATSAPP ,
}
platform = platform_map . get ( platform_name )
if not platform :
avail = " , " . join ( platform_map . keys ( ) )
return json . dumps ( { " error " : f " Unknown platform: { platform_name } . Available: { avail } " } )
pconfig = config . platforms . get ( platform )
if not pconfig or not pconfig . enabled :
return json . dumps ( { " error " : f " Platform ' { platform_name } ' is not configured. Set up credentials in ~/.hermes/gateway.json or environment variables. " } )
if not chat_id :
home = config . get_home_channel ( platform )
if home :
chat_id = home . chat_id
else :
return json . dumps ( { " error " : f " No chat_id specified and no home channel configured for { platform_name } . Use format ' platform:chat_id ' . " } )
try :
result = _run_async ( _send_to_platform ( platform , pconfig , chat_id , message ) )
return json . dumps ( result )
except Exception as e :
return json . dumps ( { " error " : f " Send failed: { e } " } )
def _run_async ( coro ) :
""" Run an async coroutine from a sync context.
If the current thread already has a running event loop ( e . g . inside
the gateway ' s async stack), we spin up a disposable thread so
asyncio . run ( ) can create its own loop without conflicting .
"""
import asyncio
try :
loop = asyncio . get_running_loop ( )
except RuntimeError :
loop = None
if loop and loop . is_running ( ) :
import concurrent . futures
with concurrent . futures . ThreadPoolExecutor ( max_workers = 1 ) as pool :
future = pool . submit ( asyncio . run , coro )
return future . result ( timeout = 30 )
return asyncio . run ( coro )
async def _send_to_platform ( platform , pconfig , chat_id , message ) :
""" Route a message to the appropriate platform sender. """
from gateway . config import Platform
if platform == Platform . TELEGRAM :
return await _send_telegram ( pconfig . token , chat_id , message )
elif platform == Platform . DISCORD :
return await _send_discord ( pconfig . token , chat_id , message )
elif platform == Platform . SLACK :
return await _send_slack ( pconfig . token , chat_id , message )
return { " error " : f " Direct sending not yet implemented for { platform . value } " }
async def _send_telegram ( token , chat_id , message ) :
""" Send via Telegram Bot API (one-shot, no polling needed). """
try :
from telegram import Bot
bot = Bot ( token = token )
msg = await bot . send_message ( chat_id = int ( chat_id ) , text = message )
return { " success " : True , " platform " : " telegram " , " chat_id " : chat_id , " message_id " : str ( msg . message_id ) }
except ImportError :
return { " error " : " python-telegram-bot not installed. Run: pip install python-telegram-bot " }
except Exception as e :
return { " error " : f " Telegram send failed: { e } " }
async def _send_discord ( token , chat_id , message ) :
""" Send via Discord REST API (no websocket client needed). """
try :
import aiohttp
except ImportError :
return { " error " : " aiohttp not installed. Run: pip install aiohttp " }
try :
url = f " https://discord.com/api/v10/channels/ { chat_id } /messages "
headers = { " Authorization " : f " Bot { token } " , " Content-Type " : " application/json " }
chunks = [ message [ i : i + 2000 ] for i in range ( 0 , len ( message ) , 2000 ) ]
message_ids = [ ]
async with aiohttp . ClientSession ( ) as session :
for chunk in chunks :
async with session . post ( url , headers = headers , json = { " content " : chunk } ) as resp :
if resp . status not in ( 200 , 201 ) :
body = await resp . text ( )
return { " error " : f " Discord API error ( { resp . status } ): { body } " }
data = await resp . json ( )
message_ids . append ( data . get ( " id " ) )
return { " success " : True , " platform " : " discord " , " chat_id " : chat_id , " message_ids " : message_ids }
except Exception as e :
return { " error " : f " Discord send failed: { e } " }
async def _send_slack ( token , chat_id , message ) :
""" Send via Slack Web API. """
try :
import aiohttp
except ImportError :
return { " error " : " aiohttp not installed. Run: pip install aiohttp " }
try :
url = " https://slack.com/api/chat.postMessage "
headers = { " Authorization " : f " Bearer { token } " , " Content-Type " : " application/json " }
async with aiohttp . ClientSession ( ) as session :
async with session . post ( url , headers = headers , json = { " channel " : chat_id , " text " : message } ) as resp :
data = await resp . json ( )
if data . get ( " ok " ) :
return { " success " : True , " platform " : " slack " , " chat_id " : chat_id , " message_id " : data . get ( " ts " ) }
return { " error " : f " Slack API error: { data . get ( ' error ' , ' unknown ' ) } " }
except Exception as e :
return { " error " : f " Slack send failed: { e } " }
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
2026-01-29 06:10:24 +00:00
def handle_function_call (
function_name : str ,
function_args : Dict [ str , Any ] ,
task_id : Optional [ str ] = None ,
user_task : Optional [ str ] = None
) - > str :
2025-08-09 09:52:25 -07:00
"""
Main function call dispatcher that routes calls to appropriate toolsets .
2025-11-03 17:42:23 -05:00
2025-08-09 09:52:25 -07:00
This function determines which toolset a function belongs to and dispatches
the call to the appropriate handler . This makes it easy to add new toolsets
without changing the main calling interface .
2025-11-03 17:42:23 -05:00
2025-08-09 09:52:25 -07:00
Args :
function_name ( str ) : Name of the function to call
function_args ( Dict ) : Arguments for the function
2026-01-29 06:10:24 +00:00
task_id ( str ) : Unique identifier for this task to isolate VMs / sessions between concurrent tasks ( optional )
user_task ( str ) : The user ' s original task/query (used for task-aware content extraction) (optional)
2025-11-03 17:42:23 -05:00
2025-08-09 09:52:25 -07:00
Returns :
str : Function result as JSON string
2025-11-03 17:42:23 -05:00
2025-08-09 09:52:25 -07:00
Raises :
None : Returns error as JSON string instead of raising exceptions
"""
try :
# Route web tools
2026-01-29 06:10:24 +00:00
if function_name in [ " web_search " , " web_extract " ] :
2025-08-09 09:52:25 -07:00
return handle_web_function_call ( function_name , function_args )
2025-11-03 17:42:23 -05:00
2025-08-09 09:52:25 -07:00
# Route terminal tools
elif function_name in [ " terminal " ] :
2025-11-03 17:42:23 -05:00
return handle_terminal_function_call ( function_name , function_args , task_id )
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
# Route process management tools
elif function_name in [ " process " ] :
return handle_process_function_call ( function_name , function_args , task_id )
2025-08-09 09:52:25 -07:00
# Route vision tools
elif function_name in [ " vision_analyze " ] :
return handle_vision_function_call ( function_name , function_args )
2025-11-03 17:42:23 -05:00
2025-08-09 09:52:25 -07:00
# Route MoA tools
elif function_name in [ " mixture_of_agents " ] :
return handle_moa_function_call ( function_name , function_args )
2025-11-03 17:42:23 -05:00
2025-08-09 09:52:25 -07:00
# Route image generation tools
elif function_name in [ " image_generate " ] :
return handle_image_function_call ( function_name , function_args )
2025-11-03 17:42:23 -05:00
2026-01-30 07:39:55 +00:00
# Route skills tools
2026-02-19 18:25:53 -08:00
elif function_name in [ " skills_list " , " skill_view " , " skill_manage " ] :
2026-01-30 07:39:55 +00:00
return handle_skills_function_call ( function_name , function_args )
2026-01-29 06:10:24 +00:00
# Route browser automation tools
elif function_name in [
" browser_navigate " , " browser_snapshot " , " browser_click " ,
" browser_type " , " browser_scroll " , " browser_back " ,
" browser_press " , " browser_close " , " browser_get_images " ,
" browser_vision "
] :
return handle_browser_function_call ( function_name , function_args , task_id , user_task )
2026-02-02 08:26:42 -08:00
# Route cronjob management tools
elif function_name in [ " schedule_cronjob " , " list_cronjobs " , " remove_cronjob " ] :
return handle_cronjob_function_call ( function_name , function_args , task_id )
2026-02-03 23:41:26 -08:00
# Route RL training tools
elif function_name in [
" 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 13:57:59 -08:00
" rl_list_runs " , " rl_test_inference "
2026-02-03 23:41:26 -08:00
] :
return handle_rl_function_call ( function_name , function_args )
2026-02-05 03:49:46 -08:00
# Route file manipulation tools
2026-02-20 02:43:57 -08:00
elif function_name in [ " read_file " , " write_file " , " patch " , " search_files " ] :
2026-02-05 03:49:46 -08:00
return handle_file_function_call ( function_name , function_args , task_id )
2026-02-19 23:23:43 -08:00
# Route code execution sandbox (programmatic tool calling)
elif function_name == " execute_code " :
code = function_args . get ( " code " , " " )
return execute_code (
code = code ,
task_id = task_id ,
enabled_tools = _last_resolved_tool_names ,
)
2026-02-12 10:05:08 -08:00
# Route text-to-speech tools
elif function_name in [ " text_to_speech " ] :
return handle_tts_function_call ( function_name , function_args )
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
# Route cross-channel messaging
elif function_name == " send_message " :
return handle_send_message_function_call ( function_name , function_args )
2026-02-17 17:02:33 -08:00
# Todo tool -- handled by the agent loop (needs TodoStore instance).
# This fallback should never execute in practice; run_agent.py intercepts first.
elif function_name == " todo " :
return json . dumps ( { " error " : " todo must be handled by the agent loop " } )
2026-02-19 00:57:31 -08:00
# Memory tool -- handled by the agent loop (needs MemoryStore instance).
elif function_name == " memory " :
return json . dumps ( { " error " : " Memory is not available. It may be disabled in config or this environment. " } )
# Session search -- handled by the agent loop (needs SessionDB instance).
elif function_name == " session_search " :
return json . dumps ( { " error " : " Session search is not available. The session database may not be initialized. " } )
2026-02-20 03:15:53 -08:00
# Delegate task -- handled by the agent loop (needs parent AIAgent instance).
elif function_name == " delegate_task " :
return json . dumps ( { " error " : " delegate_task must be handled by the agent loop " } )
2025-08-09 09:52:25 -07:00
else :
error_msg = f " Unknown function: { function_name } "
print ( f " ❌ { error_msg } " )
2025-11-04 19:54:40 -08:00
2025-11-05 03:47:17 +00:00
return json . dumps ( { " error " : error_msg } , ensure_ascii = False )
2025-08-09 09:52:25 -07:00
except Exception as e :
error_msg = f " Error executing { function_name } : { str ( e ) } "
print ( f " ❌ { error_msg } " )
2025-11-05 03:47:17 +00:00
return json . dumps ( { " error " : error_msg } , ensure_ascii = False )
2025-08-09 09:52:25 -07:00
def get_available_toolsets ( ) - > Dict [ str , Dict [ str , Any ] ] :
"""
Get information about all available toolsets and their status .
Returns :
Dict : Information about each toolset including availability and tools
"""
toolsets = {
" web_tools " : {
2025-08-21 08:12:24 -07:00
" available " : check_firecrawl_api_key ( ) ,
2026-01-29 06:10:24 +00:00
" tools " : [ " web_search_tool " , " web_extract_tool " ] ,
" description " : " Web search and content extraction tools " ,
2025-08-21 08:12:24 -07:00
" requirements " : [ " FIRECRAWL_API_KEY environment variable " ]
2025-08-09 09:52:25 -07:00
} ,
" terminal_tools " : {
2026-01-23 12:26:53 +00:00
" available " : check_terminal_requirements ( ) ,
" tools " : [ " terminal_tool " ] ,
" description " : " Execute commands using mini-swe-agent (local/docker/modal) " ,
" requirements " : [ " mini-swe-agent package, TERMINAL_ENV to select backend " ]
2025-08-09 09:52:25 -07:00
} ,
" vision_tools " : {
" available " : check_vision_requirements ( ) ,
" tools " : [ " vision_analyze_tool " ] ,
" description " : " Analyze images from URLs using AI vision for comprehensive understanding " ,
" requirements " : [ " NOUS_API_KEY environment variable " ]
} ,
" moa_tools " : {
" available " : check_moa_requirements ( ) ,
" tools " : [ " mixture_of_agents_tool " ] ,
" description " : " Process extremely difficult problems using Mixture-of-Agents methodology with multiple frontier models collaborating for enhanced reasoning. Best for complex math, coding, and analytical tasks. " ,
" requirements " : [ " NOUS_API_KEY environment variable " ]
} ,
" image_tools " : {
" available " : check_image_generation_requirements ( ) ,
" tools " : [ " image_generate_tool " ] ,
" description " : " Generate high-quality images from text prompts using FAL.ai ' s FLUX.1 Krea model with automatic 2x upscaling for enhanced quality " ,
2025-08-21 08:12:24 -07:00
" requirements " : [ " FAL_KEY environment variable " , " fal-client package " ]
2026-01-29 06:10:24 +00:00
} ,
2026-01-30 07:39:55 +00:00
" skills_tools " : {
" available " : check_skills_requirements ( ) ,
2026-02-19 18:25:53 -08:00
" tools " : [ " skills_list " , " skill_view " , " skill_manage " ] ,
" description " : " Access, create, edit, and manage skill documents that provide specialized instructions, guidelines, or knowledge the agent can load on demand " ,
" requirements " : [ " ~/.hermes/skills/ directory (seeded from bundled skills on install) " ]
2026-01-30 07:39:55 +00:00
} ,
2026-01-29 06:10:24 +00:00
" browser_tools " : {
" available " : check_browser_requirements ( ) ,
" tools " : [
" browser_navigate " , " browser_snapshot " , " browser_click " ,
" browser_type " , " browser_scroll " , " browser_back " ,
" browser_press " , " browser_close " , " browser_get_images " ,
" browser_vision "
] ,
" description " : " Browser automation for web interaction using agent-browser CLI with Browserbase cloud execution " ,
" requirements " : [ " BROWSERBASE_API_KEY " , " BROWSERBASE_PROJECT_ID " , " agent-browser npm package " ]
2026-02-02 08:26:42 -08:00
} ,
" cronjob_tools " : {
" available " : check_cronjob_requirements ( ) ,
" tools " : [ " schedule_cronjob " , " list_cronjobs " , " remove_cronjob " ] ,
2026-02-21 12:46:18 -08:00
" description " : " Schedule and manage automated tasks (cronjobs) - available in CLI and messaging platforms " ,
" requirements " : [ " Interactive or gateway session " ]
2026-02-05 03:49:46 -08:00
} ,
" file_tools " : {
" available " : check_file_requirements ( ) ,
2026-02-20 02:43:57 -08:00
" tools " : [ " read_file " , " write_file " , " patch " , " search_files " ] ,
2026-02-05 03:49:46 -08:00
" description " : " File manipulation tools: read/write files, search content/files, patch with fuzzy matching " ,
" requirements " : [ " Terminal backend available (local/docker/ssh/singularity/modal) " ]
2026-02-12 10:05:08 -08:00
} ,
" tts_tools " : {
" available " : check_tts_requirements ( ) ,
" tools " : [ " text_to_speech " ] ,
" description " : " Text-to-speech: convert text to audio (Edge TTS free, ElevenLabs, OpenAI) " ,
" requirements " : [ " edge-tts package (free) or ELEVENLABS_API_KEY or OPENAI_API_KEY " ]
2026-02-17 17:02:33 -08:00
} ,
" todo_tools " : {
" available " : check_todo_requirements ( ) ,
" tools " : [ " todo " ] ,
" description " : " Planning & task management: in-memory todo list for multi-step work " ,
" requirements " : [ ]
2026-02-19 00:57:31 -08:00
} ,
" memory_tools " : {
" available " : check_memory_requirements ( ) ,
" tools " : [ " memory " ] ,
" description " : " Persistent memory: bounded MEMORY.md + USER.md injected into system prompt " ,
" requirements " : [ ]
} ,
" session_search_tools " : {
" available " : check_session_search_requirements ( ) ,
" tools " : [ " session_search " ] ,
" description " : " Session history search: FTS5 search + Gemini Flash summarization of past conversations " ,
" requirements " : [ " OPENROUTER_API_KEY " , " ~/.hermes/state.db " ]
2026-02-19 20:06:14 -08:00
} ,
" clarify_tools " : {
" available " : check_clarify_requirements ( ) ,
" tools " : [ " clarify " ] ,
" description " : " Clarifying questions: ask the user multiple-choice or open-ended questions " ,
" requirements " : [ ]
2026-02-19 23:23:43 -08:00
} ,
" code_execution_tools " : {
" available " : check_sandbox_requirements ( ) ,
" tools " : [ " execute_code " ] ,
" description " : " Code execution sandbox: run Python scripts that call tools programmatically " ,
" requirements " : [ " Linux or macOS (Unix domain sockets) " ]
2026-02-20 03:15:53 -08:00
} ,
" delegation_tools " : {
" available " : check_delegate_requirements ( ) ,
" tools " : [ " delegate_task " ] ,
" description " : " Subagent delegation: spawn child agents with isolated context for complex subtasks " ,
" requirements " : [ ]
2025-08-09 09:52:25 -07:00
}
}
return toolsets
def check_toolset_requirements ( ) - > Dict [ str , bool ] :
"""
Check if all requirements for available toolsets are met .
2025-11-17 01:14:31 -05:00
2025-08-09 09:52:25 -07:00
Returns :
Dict : Status of each toolset ' s requirements
"""
return {
2025-08-21 08:12:24 -07:00
" web_tools " : check_firecrawl_api_key ( ) ,
2026-01-23 12:26:53 +00:00
" terminal_tools " : check_terminal_requirements ( ) ,
2025-08-09 09:52:25 -07:00
" vision_tools " : check_vision_requirements ( ) ,
" moa_tools " : check_moa_requirements ( ) ,
2026-01-29 06:10:24 +00:00
" image_tools " : check_image_generation_requirements ( ) ,
2026-01-30 07:39:55 +00:00
" skills_tools " : check_skills_requirements ( ) ,
2026-02-02 08:26:42 -08:00
" browser_tools " : check_browser_requirements ( ) ,
2026-02-05 03:49:46 -08:00
" cronjob_tools " : check_cronjob_requirements ( ) ,
2026-02-12 10:05:08 -08:00
" file_tools " : check_file_requirements ( ) ,
2026-02-19 23:23:43 -08:00
" tts_tools " : check_tts_requirements ( ) ,
" code_execution_tools " : check_sandbox_requirements ( ) ,
2026-02-20 03:15:53 -08:00
" delegation_tools " : check_delegate_requirements ( ) ,
2025-08-09 09:52:25 -07:00
}
if __name__ == " __main__ " :
"""
Simple test / demo when run directly
"""
print ( " 🛠️ Model Tools Module " )
print ( " = " * 40 )
# Check toolset requirements
requirements = check_toolset_requirements ( )
print ( " 📋 Toolset Requirements: " )
for toolset , available in requirements . items ( ) :
status = " ✅ " if available else " ❌ "
print ( f " { status } { toolset } : { ' Available ' if available else ' Missing requirements ' } " )
# Show all available tool names
all_tool_names = get_all_tool_names ( )
print ( f " \n 🔧 Available Tools ( { len ( all_tool_names ) } total): " )
for tool_name in all_tool_names :
toolset = get_toolset_for_tool ( tool_name )
print ( f " 📌 { tool_name } (from { toolset } ) " )
# Show available tools with full definitions
tools = get_tool_definitions ( )
print ( f " \n 📝 Tool Definitions ( { len ( tools ) } loaded): " )
for tool in tools :
func_name = tool [ " function " ] [ " name " ]
desc = tool [ " function " ] [ " description " ]
print ( f " 🔹 { func_name } : { desc [ : 60 ] } { ' ... ' if len ( desc ) > 60 else ' ' } " )
# Show toolset info
toolsets = get_available_toolsets ( )
print ( f " \n 📦 Toolset Information: " )
for name , info in toolsets . items ( ) :
status = " ✅ " if info [ " available " ] else " ❌ "
print ( f " { status } { name } : { info [ ' description ' ] } " )
if not info [ " available " ] :
print ( f " Requirements: { ' , ' . join ( info [ ' requirements ' ] ) } " )
print ( " \n 💡 Usage Examples: " )
print ( " from model_tools import get_tool_definitions, handle_function_call " )
print ( " # All tools " )
print ( " tools = get_tool_definitions() " )
print ( " # Only web tools " )
print ( " tools = get_tool_definitions(enabled_toolsets=[ ' web_tools ' ]) " )
print ( " # Specific tools only " )
print ( " tools = get_tool_definitions(enabled_tools=[ ' web_search ' , ' terminal ' ]) " )
print ( " # All except terminal " )
print ( " tools = get_tool_definitions(disabled_tools=[ ' terminal ' ]) " )
# Example filtering
print ( f " \n 🧪 Filtering Examples: " )
web_only = get_tool_definitions ( enabled_toolsets = [ " web_tools " ] )
print ( f " Web tools only: { len ( web_only ) } tools " )
if len ( all_tool_names ) > 1 :
specific_tools = get_tool_definitions ( enabled_tools = [ " web_search " ] )
print ( f " Only web_search: { len ( specific_tools ) } tool(s) " )
if " terminal " in all_tool_names :
no_terminal = get_tool_definitions ( disabled_tools = [ " terminal " ] )
print ( f " All except terminal: { len ( no_terminal ) } tools " )