tools — Agent Tool Implementations

Agent Tool Implementations

Central orchestration hub for all agent capabilities — 40+ production tools with pluggable execution backends, dynamic MCP integration, multi-layer security, and a thread-safe registry powering terminal execution, browser automation, file operations, web search, code sandboxing, delegation, memory, skills, vision, TTS/STT, and more.

🎯 Why This Module Exists

Without the tools/ module, every component in Hermes Agent — the CLI, the gateway, the batch runner — would need to independently implement command execution, file I/O, web scraping, and dozens of other capabilities. Each would maintain its own subprocess management, its own security checks, its own error handling. The result would be a fragmented, inconsistent, and deeply unsafe system where adding a single new capability means touching every integration point.

The tools module solves this by providing a single, unified registry where every agent capability is declared with its schema, handler, security gates, and resource limits. A thread-safe dispatch mechanism routes LLM tool calls to the correct handler, manages async/sync bridging, enforces per-tool and per-turn output budgets to prevent context-window overflow, and applies security policies (write deny lists, dangerous command approval, skill scanning) before any side-effecting operation executes. The pluggable environment system means the same terminal() call works identically whether the agent is running locally, inside Docker, over SSH, on Modal cloud, in a Singularity container, or through a Daytona workspace — all without the LLM or higher layers knowing the difference.

💡 Real-world analogy

Think of the tools module as a hospital switchboard. When a doctor (the LLM) needs an X-ray, a blood test, or a prescription filled, they don't walk to each department — they call the switchboard. The switchboard knows which department handles each request, checks that the doctor has authorization, routes the call to the right specialist, and delivers results back in a standard format. If the radiology department moves to a new building (a Docker container instead of localhost), the switchboard updates the routing — the doctor's workflow doesn't change. And if someone tries to order a dangerous procedure, the switchboard flags it for approval before it goes through.

70
Files
40+
Tools
~37K
Lines of Code
6
Backends

🔵 40+ Tools 🟢 6 Backends 🟡 MCP Support 🔴 Security First

Key Components

FilePurposeKey Details
registry.pyThread-safe tool registry & dispatch hubRWLock-protected _tools dict; register/deregister/dispatch; snapshot isolation for readers during mutations
terminal_tool.pyTerminal command executionRoutes commands through environment backends; CWD tracking; timeout enforcement; interrupt-safe polling
file_tools.pyFile read/search/stat operationsBlocked device paths; large-file hints; offset+limit pagination; 100K char default cap
file_operations.pyFile write/create/patch/moveWrite deny list for sensitive paths (~/.ssh, /etc/sudoers); ShellFileOperations unified API
patch_parser.pyUnified diff & search-replace patch parserParses both unified diff format and search/replace blocks; applies patches atomically
browser_tool.pyBrowser automation via PlaywrightHeadless Chrome; screenshot capture; DOM interaction; configurable viewport
web_tools.pyWeb search & content extractionPluggable backends: Exa, Firecrawl, Tavily, Parallel; LLM-based intelligent extraction
code_execution_tool.pySandboxed code executionIsolated Python/script execution; configurable timeout (default 300s); max 50 tool calls within sandbox
delegate_tool.pySub-agent delegationSpawns child AIAgent instances with restricted toolsets; max 3 concurrent children
skill_manager_tool.pySkill CRUD operationsCreate, read, update, delete skills in ~/.hermes/skills/; format validation
skills_hub.pyCommunity skill registryAdapter pattern via SkillSource ABC; GitHub, bundled, and hub sources; quarantine + audit
mcp_tool.pyMCP server integrationDynamic tool discovery via list_changed notifications; stdio/HTTP transport; OAuth PKCE flow
approval.pyDangerous command approvalRegex-based pattern matching; LLM auto-approval for low-risk; per-session allowlist
checkpoint_manager.pyFilesystem snapshots via shadow gitTransparent git-based checkpoints for rollback; configurable timeout [10–60s]
tool_result_storage.py3-layer result persistencePer-tool threshold → per-result persistence → per-turn budget (200K chars); preview + disk spill

Architecture

flowchart LR
  subgraph Core["Core Infrastructure"]
    REG["registry.py\nTool Registry"]
    APPROV["approval.py\nSecurity Gate"]
    STORAGE["tool_result_storage.py\nResult Persistence"]
    BUDGET["budget_config.py\nResource Limits"]
  end

  subgraph Terminal["Terminal & Environments"]
    TERM["terminal_tool.py"]
    LOCAL["LocalEnvironment"]
    DOCKER["DockerEnvironment"]
    SSH_E["SSHEnvironment"]
    MODAL["ModalEnvironment"]
    DAYTONA["DaytonaEnvironment"]
    SING["SingularityEnvironment"]
  end

  subgraph FileOps["File Operations"]
    FTOOLS["file_tools.py\nRead / Search"]
    FOPS["file_operations.py\nWrite / Patch"]
    PATCH["patch_parser.py"]
  end

  subgraph Web["Web & Browser"]
    BROWSER["browser_tool.py"]
    WEBTOOLS["web_tools.py"]
  end

  subgraph Skills["Skills & Memory"]
    SKILLMGR["skill_manager_tool.py"]
    HUB["skills_hub.py"]
    GUARD["skills_guard.py"]
    MEMORY["memory_tool.py"]
  end

  subgraph Integration["External Integration"]
    MCP["mcp_tool.py\nMCP Protocol"]
    DELEGATE["delegate_tool.py\nSub-Agents"]
    CODE["code_execution_tool.py\nSandbox"]
  end

  REG --> TERM
  REG --> FTOOLS
  REG --> BROWSER
  REG --> SKILLMGR
  REG --> MCP
  REG --> DELEGATE
  REG --> CODE
  APPROV --> TERM
  APPROV --> FOPS
  TERM --> LOCAL
  TERM --> DOCKER
  TERM --> SSH_E
  TERM --> MODAL
  TERM --> DAYTONA
  TERM --> SING
  FTOOLS --> FOPS
  FOPS --> PATCH
  SKILLMGR --> HUB
  HUB --> GUARD
  STORAGE --> BUDGET

Data Flow

flowchart TD
  LLM["LLM Tool Call\n{name, args}"]
  DISPATCH["registry.dispatch()"]
  CHECK["check_fn\nToolset Available?"]
  APPROVE["approval.py\nDangerous?"]
  HANDLER["Tool Handler\nexecute()"]
  ASYNC{"Async\nHandler?"}
  BRIDGE["_run_async()\nCoroutine Bridge"]
  RESULT["JSON Result String"]
  PERSIST{"Size >\nThreshold?"}
  DISK["Persist to Disk\nReturn Preview"]
  INLINE["Return Inline"]
  TURNBUDGET{"Turn Budget\nExceeded?"}
  SPILL["Spill Largest\nto Disk"]
  FINAL["Final Response\nto LLM"]

  LLM --> DISPATCH
  DISPATCH --> CHECK
  CHECK -->|Pass| APPROVE
  CHECK -->|Fail| RESULT
  APPROVE -->|Approved| HANDLER
  APPROVE -->|Blocked| RESULT
  HANDLER --> ASYNC
  ASYNC -->|Yes| BRIDGE --> RESULT
  ASYNC -->|No| RESULT
  RESULT --> PERSIST
  PERSIST -->|Yes| DISK --> TURNBUDGET
  PERSIST -->|No| INLINE --> TURNBUDGET
  TURNBUDGET -->|Yes| SPILL --> FINAL
  TURNBUDGET -->|No| FINAL

🚶 Step-by-Step Narrated Walkthrough

Follow a single terminal("ls -la /workspace") command as it flows through the entire tools pipeline:

  1. LLM Generates Tool Call — The model emits a function call: {"name": "terminal", "args": {"command": "ls -la /workspace"}}. The agent runtime receives this as a structured tool-use block.
  2. Registry Dispatch — model_tools.py calls registry.dispatch("terminal", args). The registry looks up the ToolEntry for "terminal", verifying the toolset's check_fn returns true (terminal is always available).
  3. Approval Check — approval.py scans the command against DANGEROUS_PATTERNS (regex list for rm -rf /, chmod 777, sudo, etc.). ls -la matches no patterns, so execution proceeds without prompting.
  4. Environment Resolution — terminal_tool.py resolves the execution backend: check the env parameter, then TERMINAL_ENV env var, then config.yaml, then fall back to "local". For this walkthrough, we get LocalEnvironment.
  5. Session Snapshot Sourcing — BaseEnvironment._wrap_command() prepends the session snapshot (captured at init_session()) — sourcing env vars, functions, and aliases. It then cds to the tracked CWD.
  6. Subprocess Spawn — LocalEnvironment._run_bash() spawns subprocess.Popen(["bash", "-c", wrapped_cmd]) with stdout/stderr piped. This is the spawn-per-call pattern — a fresh bash for every command.
  7. Interrupt-Safe Polling — _wait_for_process() polls every 0.5 seconds, checking both process completion (proc.poll()) and user interrupt (is_interrupted()). If the 600-second timeout hits, it sends SIGTERM → 5s grace → SIGKILL.
  8. Output Parsing — The wrapper emits CWD markers (__HERMES_CWD_{session_id}__) and exit code markers. The parser extracts the actual command output, the new CWD, and the return code.
  9. CWD Persistence — The new CWD is written to /tmp/hermes-cwd-{session_id}.txt so the next command starts in the right directory.
  10. Result Sizing — tool_result_storage.py checks the output size against the per-tool threshold (terminal defaults to 100K chars). Our ls -la output is small, so it returns inline.
  11. JSON Serialization — The handler returns {"returncode": 0, "stdout": "total 42\ndrwxr-xr-x ...", "cwd": "/workspace"} as a JSON string.
  12. Response to LLM — The serialized result is sent back to the model as a tool-result content block, completing the round-trip in typically <100ms for local commands.

🏗️ Design Decisions

DecisionChoice MadeAlternatives ConsideredWhy This Approach
Command execution model Spawn-per-call: fresh bash process per command Persistent shell session (PTY), shared subprocess Eliminates shell state pollution between commands. Session snapshot captures env vars/functions cheaply. Remote backends (Docker, SSH, Modal) naturally align with stateless calls. Easier to enforce timeouts and interrupts per-call.
Result size management 3-layer defense: per-tool threshold, per-result persistence, per-turn aggregate budget Single global output cap, no persistence Per-tool allows search_files (inherently huge) to have a higher cap than read_file. Per-turn budget catches cascading medium-sized results. Preview (1.5 KB) balances readability + token efficiency. Model can always read_file() persisted content — no information loss.
Tool discovery mechanism Thread-safe registry with dynamic register/deregister Hardcoded tool list, config-file enumeration MCP servers need to add/remove tools at runtime without agent restart. Tools scattered across 50+ modules — centralized registry avoids maintaining parallel lists. RWLock + snapshots ensure readers see stable state during concurrent mutations.
Skill installation security Regex-based static analysis with trust-level-aware policy Full sandboxed execution, no scanning Externally-sourced code is inherently risky (data exfiltration, persistence, destruction). Regex patterns catch obvious red flags without requiring expensive sandboxed execution. Trust levels (builtin > trusted > community) reduce false positives.
Skill sync strategy Manifest-based: track origin hash, only update if user hasn't customized Overwrite on every update, git-merge based sync User customizations must not be lost on agent update. Manifest detects modifications via hash mismatch. Respects user deletions (won't re-add deleted skills). Supports clean v1→v2 format migration.
Environment abstraction Polymorphism via BaseEnvironment with _run_bash() override Strategy pattern, configuration-driven dispatch All 6 backends share snapshot/CWD/timeout logic. Each subclass overrides only the backend-specific _run_bash() method. Adding a new backend requires implementing a single method — no changes to the rest of the pipeline.

🔬 Algorithm Spotlight

Tool Approval Flow & Security Pipeline

The Problem: An autonomous AI agent that can execute arbitrary shell commands is inherently dangerous. It could rm -rf /, exfiltrate credentials via curl, or escalate privileges with sudo. The system needs to detect dangerous operations and gate them behind approval — without blocking legitimate work or creating excessive friction.

The Approach: A multi-stage security pipeline evaluates every side-effecting operation. First, regex-based pattern matching scans commands against a curated list of dangerous patterns (recursive deletion, world-writable permissions, credential access). If a match is found, the system attempts LLM-based auto-approval: a cheap model evaluates whether the command is actually low-risk in context. If auto-approval fails, the user is prompted interactively (CLI) or the command is logged and rejected (gateway). Approved commands are added to a per-session allowlist so repeated similar operations don't trigger re-approval. For file writes, a separate deny list blocks writes to sensitive paths (~/.ssh/, /etc/sudoers, ~/.hermes/.env) unconditionally.

Why This Algorithm: Pure regex matching would generate too many false positives (blocking harmless rm on temp files). Pure LLM-based analysis would be too slow and expensive for every command. The layered approach — fast regex filter → smart LLM analysis → user approval fallback — balances security, speed, and usability. Per-session allowlists prevent approval fatigue during iterative development.

graph TD
    CMD["Command Input"]
    REGEX{"Regex Match\nDANGEROUS_PATTERNS?"}
    SAFE["Execute Immediately"]
    WDENY{"Write Deny\nList Check?"}
    BLOCK["Block: Protected Path"]
    AUTO{"LLM Auto-Approve\nLow-risk in context?"}
    ALLOW["Add to Session Allowlist"]
    PROMPT{"User Approval\nCLI / Gateway?"}
    EXEC["Execute Command"]
    REJECT["Reject + Log"]

    CMD --> WDENY
    WDENY -->|"Denied Path"| BLOCK
    WDENY -->|"Allowed Path"| REGEX
    REGEX -->|"No Match"| SAFE
    REGEX -->|"Match Found"| AUTO
    AUTO -->|"Low Risk"| ALLOW --> EXEC
    AUTO -->|"High Risk"| PROMPT
    PROMPT -->|"Approved"| ALLOW
    PROMPT -->|"Denied"| REJECT
    SAFE --> EXEC
Trace through an example

Given: Command = rm -rf /tmp/hermes-build-cache/*

Step 1 (Write Deny): Path /tmp/hermes-build-cache/ is not in the deny list — proceed.

Step 2 (Regex): Pattern \brm\s+(-[^\s]*\s+)*\/ matches because rm -rf / prefix is detected. Flag as dangerous.

Step 3 (Auto-Approve): LLM evaluates context: "Deleting a build cache directory under /tmp, not system files." Verdict: low risk. Auto-approved.

Step 4 (Allowlist): Command pattern added to session allowlist. Future rm -rf /tmp/hermes-* commands skip approval entirely.

Result: Command executes. If it had been rm -rf /etc/*, the LLM would have flagged it as high-risk, prompting the user. A naive regex-only system would have blocked the harmless cache cleanup.

⚠️ What Could Go Wrong

Failure ModeTriggerDetectionRecovery / Mitigation
Context window overflow Tool returns massive output (e.g., find / or cat on a large file) Per-tool threshold check + per-turn aggregate budget monitor 3-layer persistence: large outputs spill to disk with a 1.5 KB preview. Model can read_file() the persisted content. Turn budget catches cascading medium results.
Command hangs indefinitely Process reads from stdin, waits on network, or enters infinite loop _wait_for_process() polls every 0.5s with hard timeout (default 600s) SIGTERM → 5-second grace period → SIGKILL. Blocked device paths (/dev/stdin, /dev/tty) are pre-emptively denied.
MCP server connection failure MCP server process crashes, network timeout, or misconfigured transport Exponential backoff with up to 5 retries; connection error exceptions Graceful degradation: MCP toolset becomes unavailable but all built-in tools continue working. Error returned to model so it can adapt.
Credential exfiltration attempt Agent tries to curl sensitive files or write to ~/.ssh/authorized_keys Write deny list checks path prefixes (~/.ssh/, ~/.aws/, ~/.docker/); dangerous command regex Operation blocked before execution with clear error message. Approval system catches curl-based exfiltration patterns.
OAuth token expiration Managed gateway or MCP OAuth token expires mid-session read_nous_access_token() checks expiry with 120s skew Automatic refresh via hermes_cli.auth; fall back to cached token; if both fail, OAuth-dependent tools degrade gracefully.
Sandbox subprocess timeout Code execution tool runs a script that exceeds the 300s timeout Timeout enforcement in code_execution_tool.py + environment-level _wait_for_process Subprocess killed; error result returned to model with timeout message. Max tool-call limit (50) prevents runaway tool loops within sandbox.

⚡ Performance Profile

Hot Paths

The most performance-sensitive code paths in this module:

Resource Usage

Sub-Modules

Terminal & Environments

terminal_tool.py

Primary command execution interface for the agent. Routes commands through the configured environment backend.

  • CWD tracking via markers
  • Timeout enforcement (default 600s)
  • Disk usage warnings
  • Interrupt-safe polling

environments/

Six pluggable execution backends behind the BaseEnvironment abstraction.

  • local.py — subprocess.Popen
  • docker.py — docker exec
  • ssh.py — remote SSH
  • modal.py — Modal cloud
  • daytona.py — HTTP API
  • singularity.py — HPC containers

File Operations

file_tools.py

Read-side file operations: read_file, search_files, list_directory, file_stat.

  • Blocked device path protection
  • Large file hints (>512 KB)
  • Offset + limit pagination

file_operations.py

Write-side operations: create, write, move, delete files via ShellFileOperations.

  • Write deny list for sensitive paths
  • Prefix-walk directory escape detection
  • Unified API across all backends

patch_parser.py

Parses unified diffs and search-replace blocks for atomic file patching.

  • Supports both diff formats
  • Atomic apply-or-fail semantics

Browser & Web

browser_tool.py

Headless browser automation via Playwright for web interaction.

  • Screenshot capture
  • DOM interaction & element queries
  • Configurable viewport

web_tools.py

Web search and content extraction with pluggable backends.

  • Exa, Firecrawl, Tavily, Parallel
  • LLM-based intelligent extraction
  • Configurable via config.yaml

Skills & Memory

skill_manager_tool.py

CRUD operations for agent skills stored in ~/.hermes/skills/.

  • Create / read / update / delete
  • Format validation

skills_hub.py & skills_tool.py

Community skill registry with adapter pattern (SkillSource ABC).

  • GitHub, bundled, and hub sources
  • Quarantine + audit logging
  • Manifest-based sync

skills_guard.py

Trust-level-aware security scanning for skill installation.

  • Regex-based static analysis
  • Trust levels: builtin > trusted > community
  • Install policy matrix

memory_tool.py

Persistent memory via Markdown files injected into system prompt.

  • Stored in HERMES_HOME/memories/
  • Auto-loaded at session start

Code Execution & Delegation

code_execution_tool.py

Sandboxed script execution with resource limits.

  • Configurable timeout (default 300s)
  • Max 50 tool calls within sandbox
  • RPC back to parent for tool dispatch

delegate_tool.py

Spawns child AIAgent instances with restricted toolsets.

  • Max 3 concurrent children
  • Isolated context per sub-agent
  • Task-specific toolset restriction

Communication & Media

send_message_tool.py

Agent-to-user messaging for status updates and notifications.

  • Structured message delivery

tts_tool.py & voice_mode.py

Text-to-speech and voice interaction with 6 provider backends.

  • Edge, ElevenLabs, OpenAI, Mistral, MiniMax, NeuTTS
  • Lazy imports for headless compatibility

vision_tools.py & image_generation_tool.py

Vision analysis and image generation capabilities.

  • Vision: auxiliary model routing
  • Image gen: FAL.ai (FLUX + Clarity Upscaler)
  • 30s download timeout

Security & Infrastructure

approval.py

Multi-stage dangerous command detection and approval pipeline.

  • Regex pattern matching
  • LLM auto-approve for low-risk
  • Per-session allowlist

path_security.py & url_safety.py

Path traversal prevention and URL safety validation.

  • Canonical path resolution
  • URL scheme validation

checkpoint_manager.py

Transparent filesystem snapshots via shadow git repositories.

  • Configurable timeout [10–60s]
  • Rollback support

registry.py & process_registry.py

Thread-safe tool registry and background process tracking.

  • RWLock for concurrent reads
  • Snapshot isolation during mutations
  • Dynamic MCP tool registration

Directory Map

Source tree for tools
tools/
├── environments/                  ← Pluggable execution backends
│   ├── base.py                    ← BaseEnvironment ABC (snapshot, CWD, timeout)
│   ├── local.py                   ← Local subprocess backend
│   ├── docker.py                  ← Docker container backend
│   ├── ssh.py                     ← Remote SSH backend
│   ├── modal.py                   ← Modal cloud backend
│   ├── daytona.py                 ← Daytona HTTP API backend
│   └── singularity.py             ← Singularity/HPC container backend
├── browser_providers/             ← Browser automation backends
├── registry.py                    ← Thread-safe tool registry & dispatch
├── process_registry.py            ← Background process tracking
├── terminal_tool.py               ← Terminal command execution
├── file_tools.py                  ← File read / search / stat
├── file_operations.py             ← File write / create / patch / move
├── patch_parser.py                ← Unified diff & search-replace parser
├── browser_tool.py                ← Headless browser automation
├── web_tools.py                   ← Web search & content extraction
├── code_execution_tool.py         ← Sandboxed script execution
├── delegate_tool.py               ← Sub-agent delegation
├── skill_manager_tool.py          ← Skill CRUD operations
├── skills_tool.py                 ← Skill discovery & execution
├── skills_hub.py                  ← Community skill registry (SkillSource)
├── skills_guard.py                ← Trust-aware security scanning
├── skills_sync.py                 ← Manifest-based skill sync
├── mcp_tool.py                    ← MCP server integration
├── mcp_oauth.py                   ← OAuth PKCE for MCP
├── memory_tool.py                 ← Persistent memory files
├── send_message_tool.py           ← Agent-to-user messaging
├── tts_tool.py                    ← Text-to-speech (6 providers)
├── voice_mode.py                  ← Voice interaction mode
├── vision_tools.py                ← Vision model routing
├── image_generation_tool.py       ← Image generation (FAL.ai)
├── transcription_tools.py         ← Audio transcription
├── approval.py                    ← Dangerous command approval
├── path_security.py               ← Path traversal prevention
├── tirith_security.py             ← Extended security policies
├── url_safety.py                  ← URL scheme validation
├── tool_result_storage.py         ← 3-layer result persistence
├── budget_config.py               ← Per-tool/turn resource limits
├── checkpoint_manager.py          ← Shadow git filesystem snapshots
├── managed_tool_gateway.py        ← Nous hosted tool gateway client
├── todo_tool.py                   ← Todo/task tracking
└── auxiliary_client.py            ← Cheap LLM calls for tool support

⚙️ Configuration Reference

ParameterSourceDefaultPurpose
TERMINAL_ENVenv varlocalBackend selection: local, docker, modal, ssh, singularity, daytona
TERMINAL_MAX_FOREGROUND_TIMEOUTenv var600Hard cap on foreground command timeout (seconds)
TERMINAL_DISK_WARNING_GBenv var500Disk usage warning threshold in GB
file_read_max_charsconfig.yaml100000Max characters per read_file call (~25-35K tokens)
web.backendconfig.yaml—Web backend: exa, firecrawl, parallel, tavily
mcp_serversconfig.yaml{}Dict of MCP server configs (stdio/HTTP, env vars, timeouts, OAuth)
code_execution.timeoutconfig.yaml300Sandbox script timeout in seconds
delegation.max_concurrent_childrenconfig.yaml3Max parallel sub-agents
tts.providerconfig.yaml—TTS backend: edge, elevenlabs, openai, mistral, minimax, neutts
HERMES_CHECKPOINT_TIMEOUTenv var30Git subprocess timeout for snapshots (clamped to [10, 60])