Spaces:
Configuration error
Configuration error
Upload 55 files
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .dockerignore +31 -0
- .env.example +446 -0
- .envrc +5 -0
- .gitattributes +2 -0
- .gitignore +72 -0
- .gitmodules +3 -0
- .mailmap +108 -0
- AGENTS.md +985 -0
- CONTRIBUTING.md +815 -0
- Dockerfile +113 -0
- LICENSE +21 -0
- MANIFEST.in +4 -0
- README.md +195 -0
- README.zh-CN.md +186 -0
- RELEASE_v0.10.0.md +27 -0
- RELEASE_v0.11.0.md +453 -0
- RELEASE_v0.12.0.md +505 -0
- RELEASE_v0.13.0.md +641 -0
- RELEASE_v0.2.0.md +383 -0
- RELEASE_v0.3.0.md +377 -0
- RELEASE_v0.4.0.md +400 -0
- RELEASE_v0.5.0.md +348 -0
- RELEASE_v0.6.0.md +249 -0
- RELEASE_v0.7.0.md +290 -0
- RELEASE_v0.8.0.md +346 -0
- RELEASE_v0.9.0.md +329 -0
- SECURITY.md +84 -0
- batch_runner.py +1302 -0
- cli-config.yaml.example +1062 -0
- cli.py +0 -0
- constraints-termux.txt +15 -0
- docker-compose.yml +71 -0
- flake.lock +202 -0
- flake.nix +45 -0
- hermes +11 -0
- hermes-already-has-routines.md +160 -0
- hermes_bootstrap.py +129 -0
- hermes_constants.py +345 -0
- hermes_logging.py +389 -0
- hermes_state.py +0 -0
- hermes_time.py +104 -0
- mcp_serve.py +897 -0
- mini_swe_runner.py +736 -0
- model_tools.py +867 -0
- package-lock.json +2660 -0
- package.json +28 -0
- pyproject.toml +232 -0
- rl_cli.py +446 -0
- run_agent.py +0 -0
- setup-hermes.sh +403 -0
.dockerignore
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Git
|
| 2 |
+
.git
|
| 3 |
+
.gitignore
|
| 4 |
+
.gitmodules
|
| 5 |
+
|
| 6 |
+
# Dependencies
|
| 7 |
+
node_modules
|
| 8 |
+
**/node_modules
|
| 9 |
+
.venv
|
| 10 |
+
**/.venv
|
| 11 |
+
|
| 12 |
+
# Built artifacts that are regenerated inside the image. Excluded so local
|
| 13 |
+
# rebuilds on the developer's machine don't invalidate the npm-install layer
|
| 14 |
+
# that now depends on the full ui-tui/packages/hermes-ink/ tree being present.
|
| 15 |
+
ui-tui/dist/
|
| 16 |
+
ui-tui/packages/hermes-ink/dist/
|
| 17 |
+
|
| 18 |
+
# CI/CD
|
| 19 |
+
.github
|
| 20 |
+
|
| 21 |
+
# Environment files
|
| 22 |
+
.env
|
| 23 |
+
|
| 24 |
+
*.md
|
| 25 |
+
|
| 26 |
+
# Runtime data (bind-mounted at /opt/data; must not leak into build context)
|
| 27 |
+
data/
|
| 28 |
+
|
| 29 |
+
# Compose/profile runtime state (bind-mounted; avoid ownership/secret issues)
|
| 30 |
+
hermes-config/
|
| 31 |
+
runtime/
|
.env.example
ADDED
|
@@ -0,0 +1,446 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Hermes Agent Environment Configuration
|
| 2 |
+
# Copy this file to .env and fill in your API keys
|
| 3 |
+
|
| 4 |
+
# =============================================================================
|
| 5 |
+
# LLM PROVIDER (OpenRouter)
|
| 6 |
+
# =============================================================================
|
| 7 |
+
# OpenRouter provides access to many models through one API
|
| 8 |
+
# All LLM calls go through OpenRouter - no direct provider keys needed
|
| 9 |
+
# Get your key at: https://openrouter.ai/keys
|
| 10 |
+
# OPENROUTER_API_KEY=
|
| 11 |
+
|
| 12 |
+
# Default model is configured in ~/.hermes/config.yaml (model.default).
|
| 13 |
+
# Use 'hermes model' or 'hermes setup' to change it.
|
| 14 |
+
# LLM_MODEL is no longer read from .env — this line is kept for reference only.
|
| 15 |
+
# LLM_MODEL=anthropic/claude-opus-4.6
|
| 16 |
+
|
| 17 |
+
# =============================================================================
|
| 18 |
+
# LLM PROVIDER (Google AI Studio / Gemini)
|
| 19 |
+
# =============================================================================
|
| 20 |
+
# Native Gemini API via Google's OpenAI-compatible endpoint.
|
| 21 |
+
# Get your key at: https://aistudio.google.com/app/apikey
|
| 22 |
+
# GOOGLE_API_KEY=your_google_ai_studio_key_here
|
| 23 |
+
# GEMINI_API_KEY=your_gemini_key_here # alias for GOOGLE_API_KEY
|
| 24 |
+
# Optional base URL override (default: Google's OpenAI-compatible endpoint)
|
| 25 |
+
# GEMINI_BASE_URL=https://generativelanguage.googleapis.com/v1beta/openai
|
| 26 |
+
|
| 27 |
+
# =============================================================================
|
| 28 |
+
# LLM PROVIDER (Ollama Cloud)
|
| 29 |
+
# =============================================================================
|
| 30 |
+
# Cloud-hosted open models via Ollama's OpenAI-compatible endpoint.
|
| 31 |
+
# Get your key at: https://ollama.com/settings
|
| 32 |
+
# OLLAMA_API_KEY=your_ollama_key_here
|
| 33 |
+
# Optional base URL override (default: https://ollama.com/v1)
|
| 34 |
+
# OLLAMA_BASE_URL=https://ollama.com/v1
|
| 35 |
+
|
| 36 |
+
# =============================================================================
|
| 37 |
+
# LLM PROVIDER (z.ai / GLM)
|
| 38 |
+
# =============================================================================
|
| 39 |
+
# z.ai provides access to ZhipuAI GLM models (GLM-4-Plus, etc.)
|
| 40 |
+
# Get your key at: https://z.ai or https://open.bigmodel.cn
|
| 41 |
+
# GLM_API_KEY=
|
| 42 |
+
# GLM_BASE_URL=https://api.z.ai/api/paas/v4 # Override default base URL
|
| 43 |
+
|
| 44 |
+
# =============================================================================
|
| 45 |
+
# LLM PROVIDER (Kimi / Moonshot)
|
| 46 |
+
# =============================================================================
|
| 47 |
+
# Kimi Code provides access to Moonshot AI coding models (kimi-k2.5, etc.)
|
| 48 |
+
# Get your key at: https://platform.kimi.ai (Kimi Code console)
|
| 49 |
+
# Keys prefixed sk-kimi- use the Kimi Code API (api.kimi.com) by default.
|
| 50 |
+
# Legacy keys from platform.moonshot.ai need KIMI_BASE_URL override below.
|
| 51 |
+
# KIMI_API_KEY=
|
| 52 |
+
# KIMI_BASE_URL=https://api.kimi.com/coding/v1 # Default for sk-kimi- keys
|
| 53 |
+
# KIMI_BASE_URL=https://api.moonshot.ai/v1 # For legacy Moonshot keys
|
| 54 |
+
# KIMI_BASE_URL=https://api.moonshot.cn/v1 # For Moonshot China keys
|
| 55 |
+
# KIMI_CN_API_KEY= # Dedicated Moonshot China key
|
| 56 |
+
|
| 57 |
+
# =============================================================================
|
| 58 |
+
# LLM PROVIDER (Arcee AI)
|
| 59 |
+
# =============================================================================
|
| 60 |
+
# Arcee AI provides access to Trinity models (trinity-mini, trinity-large-*)
|
| 61 |
+
# Get an Arcee key at: https://chat.arcee.ai/
|
| 62 |
+
# ARCEEAI_API_KEY=
|
| 63 |
+
# ARCEE_BASE_URL= # Override default base URL
|
| 64 |
+
|
| 65 |
+
# =============================================================================
|
| 66 |
+
# LLM PROVIDER (MiniMax)
|
| 67 |
+
# =============================================================================
|
| 68 |
+
# MiniMax provides access to MiniMax models (global endpoint)
|
| 69 |
+
# Get your key at: https://www.minimax.io
|
| 70 |
+
# MINIMAX_API_KEY=
|
| 71 |
+
# MINIMAX_BASE_URL=https://api.minimax.io/v1 # Override default base URL
|
| 72 |
+
|
| 73 |
+
# MiniMax China endpoint (for users in mainland China)
|
| 74 |
+
# MINIMAX_CN_API_KEY=
|
| 75 |
+
# MINIMAX_CN_BASE_URL=https://api.minimaxi.com/v1 # Override default base URL
|
| 76 |
+
|
| 77 |
+
# =============================================================================
|
| 78 |
+
# LLM PROVIDER (OpenCode Zen)
|
| 79 |
+
# =============================================================================
|
| 80 |
+
# OpenCode Zen provides curated, tested models (GPT, Claude, Gemini, MiniMax, GLM, Kimi)
|
| 81 |
+
# Pay-as-you-go pricing. Get your key at: https://opencode.ai/auth
|
| 82 |
+
# OPENCODE_ZEN_API_KEY=
|
| 83 |
+
# OPENCODE_ZEN_BASE_URL=https://opencode.ai/zen/v1 # Override default base URL
|
| 84 |
+
|
| 85 |
+
# =============================================================================
|
| 86 |
+
# LLM PROVIDER (OpenCode Go)
|
| 87 |
+
# =============================================================================
|
| 88 |
+
# OpenCode Go provides access to open models (GLM-5, Kimi K2.5, MiniMax M2.5)
|
| 89 |
+
# $10/month subscription. Get your key at: https://opencode.ai/auth
|
| 90 |
+
# OPENCODE_GO_API_KEY=
|
| 91 |
+
|
| 92 |
+
# =============================================================================
|
| 93 |
+
# LLM PROVIDER (Hugging Face Inference Providers)
|
| 94 |
+
# =============================================================================
|
| 95 |
+
# Hugging Face routes to 20+ open models via unified OpenAI-compatible endpoint.
|
| 96 |
+
# Free tier included ($0.10/month), no markup on provider rates.
|
| 97 |
+
# Get your token at: https://huggingface.co/settings/tokens
|
| 98 |
+
# Required permission: "Make calls to Inference Providers"
|
| 99 |
+
# HF_TOKEN=
|
| 100 |
+
# OPENCODE_GO_BASE_URL=https://opencode.ai/zen/go/v1 # Override default base URL
|
| 101 |
+
|
| 102 |
+
# =============================================================================
|
| 103 |
+
# LLM PROVIDER (Qwen OAuth)
|
| 104 |
+
# =============================================================================
|
| 105 |
+
# Qwen OAuth reuses your local Qwen CLI login (qwen auth qwen-oauth).
|
| 106 |
+
# No API key needed — credentials come from ~/.qwen/oauth_creds.json.
|
| 107 |
+
# Optional base URL override:
|
| 108 |
+
# HERMES_QWEN_BASE_URL=https://portal.qwen.ai/v1
|
| 109 |
+
|
| 110 |
+
# =============================================================================
|
| 111 |
+
# LLM PROVIDER (Xiaomi MiMo)
|
| 112 |
+
# =============================================================================
|
| 113 |
+
# Xiaomi MiMo models (mimo-v2-pro, mimo-v2-omni, mimo-v2-flash).
|
| 114 |
+
# Get your key at: https://platform.xiaomimimo.com
|
| 115 |
+
# XIAOMI_API_KEY=your_key_here
|
| 116 |
+
# Optional base URL override:
|
| 117 |
+
# XIAOMI_BASE_URL=https://api.xiaomimimo.com/v1
|
| 118 |
+
|
| 119 |
+
# =============================================================================
|
| 120 |
+
# TOOL API KEYS
|
| 121 |
+
# =============================================================================
|
| 122 |
+
|
| 123 |
+
# Exa API Key - AI-native web search and contents
|
| 124 |
+
# Get at: https://exa.ai
|
| 125 |
+
# EXA_API_KEY=
|
| 126 |
+
|
| 127 |
+
# Parallel API Key - AI-native web search and extract
|
| 128 |
+
# Get at: https://parallel.ai
|
| 129 |
+
# PARALLEL_API_KEY=
|
| 130 |
+
|
| 131 |
+
# Firecrawl API Key - Web search, extract, and crawl
|
| 132 |
+
# Get at: https://firecrawl.dev/
|
| 133 |
+
# FIRECRAWL_API_KEY=
|
| 134 |
+
|
| 135 |
+
|
| 136 |
+
# FAL.ai API Key - Image generation
|
| 137 |
+
# Get at: https://fal.ai/
|
| 138 |
+
# FAL_KEY=
|
| 139 |
+
|
| 140 |
+
# Honcho - Cross-session AI-native user modeling (optional)
|
| 141 |
+
# Builds a persistent understanding of the user across sessions and tools.
|
| 142 |
+
# Get at: https://app.honcho.dev
|
| 143 |
+
# Also requires ~/.honcho/config.json with enabled=true (see README).
|
| 144 |
+
# HONCHO_API_KEY=
|
| 145 |
+
|
| 146 |
+
# =============================================================================
|
| 147 |
+
# TERMINAL TOOL CONFIGURATION
|
| 148 |
+
# =============================================================================
|
| 149 |
+
# Backend type: "local", "singularity", "docker", "modal", or "ssh"
|
| 150 |
+
# Terminal backend is configured in ~/.hermes/config.yaml (terminal.backend).
|
| 151 |
+
# Use 'hermes setup' or 'hermes config set terminal.backend docker' to change.
|
| 152 |
+
# Supported: local, docker, singularity, modal, ssh
|
| 153 |
+
#
|
| 154 |
+
# Only override here if you need to force a backend without touching config.yaml:
|
| 155 |
+
# TERMINAL_ENV=local
|
| 156 |
+
|
| 157 |
+
# Override the container runtime binary (e.g. to use Podman instead of Docker).
|
| 158 |
+
# Useful on systems where Docker's storage driver is broken or unavailable.
|
| 159 |
+
# HERMES_DOCKER_BINARY=/usr/local/bin/podman
|
| 160 |
+
|
| 161 |
+
# Container images (for singularity/docker/modal backends)
|
| 162 |
+
# TERMINAL_DOCKER_IMAGE=nikolaik/python-nodejs:python3.11-nodejs20
|
| 163 |
+
# TERMINAL_SINGULARITY_IMAGE=docker://nikolaik/python-nodejs:python3.11-nodejs20
|
| 164 |
+
TERMINAL_MODAL_IMAGE=nikolaik/python-nodejs:python3.11-nodejs20
|
| 165 |
+
|
| 166 |
+
|
| 167 |
+
# Working directory for terminal commands
|
| 168 |
+
# For local backend: "." means current directory (resolved automatically)
|
| 169 |
+
# For remote backends (ssh/docker/modal/singularity): use an absolute path
|
| 170 |
+
# INSIDE the target environment, or leave unset for the backend's default
|
| 171 |
+
# (/root for modal, / for docker, ~ for ssh). Do NOT use a host-local path.
|
| 172 |
+
# Usually managed by config.yaml (terminal.cwd) — uncomment to override
|
| 173 |
+
# TERMINAL_CWD=.
|
| 174 |
+
|
| 175 |
+
# Default command timeout in seconds
|
| 176 |
+
TERMINAL_TIMEOUT=60
|
| 177 |
+
|
| 178 |
+
# Cleanup inactive environments after this many seconds
|
| 179 |
+
TERMINAL_LIFETIME_SECONDS=300
|
| 180 |
+
|
| 181 |
+
# =============================================================================
|
| 182 |
+
# SSH REMOTE EXECUTION (for TERMINAL_ENV=ssh)
|
| 183 |
+
# =============================================================================
|
| 184 |
+
# Run terminal commands on a remote server via SSH.
|
| 185 |
+
# Agent code stays on your machine, commands execute remotely.
|
| 186 |
+
#
|
| 187 |
+
# SECURITY BENEFITS:
|
| 188 |
+
# - Agent cannot read your .env file (API keys protected)
|
| 189 |
+
# - Agent cannot modify its own code
|
| 190 |
+
# - Remote server acts as isolated sandbox
|
| 191 |
+
# - Can safely configure passwordless sudo on remote
|
| 192 |
+
#
|
| 193 |
+
# TERMINAL_SSH_HOST=192.168.1.100
|
| 194 |
+
# TERMINAL_SSH_USER=agent
|
| 195 |
+
# TERMINAL_SSH_PORT=22
|
| 196 |
+
# TERMINAL_SSH_KEY=~/.ssh/id_rsa
|
| 197 |
+
|
| 198 |
+
# =============================================================================
|
| 199 |
+
# SUDO SUPPORT (works with ALL terminal backends)
|
| 200 |
+
# =============================================================================
|
| 201 |
+
# If set, enables sudo commands by piping password via `sudo -S`.
|
| 202 |
+
# Works with: local, docker, singularity, modal, and ssh backends.
|
| 203 |
+
#
|
| 204 |
+
# SECURITY WARNING: Password stored in plaintext. Only use on trusted machines.
|
| 205 |
+
#
|
| 206 |
+
# ALTERNATIVES:
|
| 207 |
+
# - For SSH backend: Configure passwordless sudo on the remote server
|
| 208 |
+
# - For containers: Run as root inside the container (no sudo needed)
|
| 209 |
+
# - For local: Configure /etc/sudoers for specific commands
|
| 210 |
+
# - For CLI: Leave unset - you'll be prompted interactively with 45s timeout
|
| 211 |
+
#
|
| 212 |
+
# SUDO_PASSWORD=your_password_here
|
| 213 |
+
|
| 214 |
+
# =============================================================================
|
| 215 |
+
# MODAL CLOUD BACKEND (Optional - for TERMINAL_ENV=modal)
|
| 216 |
+
# =============================================================================
|
| 217 |
+
# Modal uses CLI authentication, not environment variables.
|
| 218 |
+
# Run: pip install modal && modal setup
|
| 219 |
+
# This will authenticate via browser and store credentials locally.
|
| 220 |
+
# No API key needed in .env - Modal handles auth automatically.
|
| 221 |
+
|
| 222 |
+
# =============================================================================
|
| 223 |
+
# BROWSER TOOL CONFIGURATION (agent-browser + Browserbase)
|
| 224 |
+
# =============================================================================
|
| 225 |
+
# Browser automation requires Browserbase cloud service for remote browser execution.
|
| 226 |
+
# This allows the agent to navigate websites, fill forms, and extract information.
|
| 227 |
+
#
|
| 228 |
+
# STEALTH MODES:
|
| 229 |
+
# - Basic Stealth: ALWAYS active (random fingerprints, auto CAPTCHA solving)
|
| 230 |
+
# - Advanced Stealth: Requires BROWSERBASE_ADVANCED_STEALTH=true (Scale Plan only)
|
| 231 |
+
|
| 232 |
+
# Browserbase API Key - Cloud browser execution
|
| 233 |
+
# Get at: https://browserbase.com/
|
| 234 |
+
# BROWSERBASE_API_KEY=
|
| 235 |
+
|
| 236 |
+
# Browserbase Project ID - From your Browserbase dashboard
|
| 237 |
+
# BROWSERBASE_PROJECT_ID=
|
| 238 |
+
|
| 239 |
+
# Enable residential proxies for better CAPTCHA solving (default: true)
|
| 240 |
+
# Routes traffic through residential IPs, significantly improves success rate
|
| 241 |
+
BROWSERBASE_PROXIES=true
|
| 242 |
+
|
| 243 |
+
# Enable advanced stealth mode (default: false, requires Scale Plan)
|
| 244 |
+
# Uses custom Chromium build to avoid bot detection altogether
|
| 245 |
+
BROWSERBASE_ADVANCED_STEALTH=false
|
| 246 |
+
|
| 247 |
+
# Browser engine for local mode (default: auto = Chrome)
|
| 248 |
+
# "auto" — use Chrome (don't pass --engine flag)
|
| 249 |
+
# "lightpanda" — use Lightpanda (1.3-5.8x faster navigation, no screenshots)
|
| 250 |
+
# "chrome" — explicitly request Chrome
|
| 251 |
+
# Requires agent-browser v0.25.3+. Lightpanda commands that fail or return
|
| 252 |
+
# empty results are automatically retried with Chrome.
|
| 253 |
+
# Also configurable via browser.engine in config.yaml.
|
| 254 |
+
# AGENT_BROWSER_ENGINE=auto
|
| 255 |
+
|
| 256 |
+
# Browser session timeout in seconds (default: 300)
|
| 257 |
+
# Sessions are cleaned up after this duration of inactivity
|
| 258 |
+
BROWSER_SESSION_TIMEOUT=300
|
| 259 |
+
|
| 260 |
+
# Browser inactivity timeout - auto-cleanup inactive sessions (default: 120 = 2 min)
|
| 261 |
+
# Browser sessions are automatically closed after this period of no activity
|
| 262 |
+
BROWSER_INACTIVITY_TIMEOUT=120
|
| 263 |
+
|
| 264 |
+
# =============================================================================
|
| 265 |
+
# SESSION LOGGING
|
| 266 |
+
# =============================================================================
|
| 267 |
+
# Session trajectories are automatically saved to logs/ directory
|
| 268 |
+
# Format: logs/session_YYYYMMDD_HHMMSS_UUID.json
|
| 269 |
+
# Contains full conversation history in trajectory format for debugging/replay
|
| 270 |
+
|
| 271 |
+
# =============================================================================
|
| 272 |
+
# VOICE TRANSCRIPTION & OPENAI TTS
|
| 273 |
+
# =============================================================================
|
| 274 |
+
# Required for voice message transcription (Whisper) and OpenAI TTS voices.
|
| 275 |
+
# Uses OpenAI's API directly (not via OpenRouter).
|
| 276 |
+
# Named VOICE_TOOLS_OPENAI_KEY to avoid interference with OpenRouter.
|
| 277 |
+
# Get at: https://platform.openai.com/api-keys
|
| 278 |
+
# VOICE_TOOLS_OPENAI_KEY=
|
| 279 |
+
|
| 280 |
+
# =============================================================================
|
| 281 |
+
# SLACK INTEGRATION
|
| 282 |
+
# =============================================================================
|
| 283 |
+
# Slack Bot Token - From Slack App settings (OAuth & Permissions)
|
| 284 |
+
# Get at: https://api.slack.com/apps
|
| 285 |
+
# SLACK_BOT_TOKEN=xoxb-...
|
| 286 |
+
|
| 287 |
+
# Slack App Token - For Socket Mode (App-Level Tokens in Slack App settings)
|
| 288 |
+
# SLACK_APP_TOKEN=xapp-...
|
| 289 |
+
|
| 290 |
+
# Slack allowed users (comma-separated Slack user IDs)
|
| 291 |
+
# SLACK_ALLOWED_USERS=
|
| 292 |
+
|
| 293 |
+
# =============================================================================
|
| 294 |
+
# TELEGRAM INTEGRATION
|
| 295 |
+
# =============================================================================
|
| 296 |
+
# Telegram Bot Token - From @BotFather (https://t.me/BotFather)
|
| 297 |
+
# TELEGRAM_BOT_TOKEN=
|
| 298 |
+
# TELEGRAM_ALLOWED_USERS= # Comma-separated user IDs
|
| 299 |
+
# TELEGRAM_HOME_CHANNEL= # Default chat for cron delivery
|
| 300 |
+
# TELEGRAM_HOME_CHANNEL_NAME= # Display name for home channel
|
| 301 |
+
|
| 302 |
+
# Webhook mode (optional — for cloud deployments like Fly.io/Railway)
|
| 303 |
+
# Default is long polling. Setting TELEGRAM_WEBHOOK_URL switches to webhook mode.
|
| 304 |
+
# TELEGRAM_WEBHOOK_URL=https://my-app.fly.dev/telegram
|
| 305 |
+
# TELEGRAM_WEBHOOK_PORT=8443
|
| 306 |
+
# TELEGRAM_WEBHOOK_SECRET= # Recommended for production
|
| 307 |
+
|
| 308 |
+
# WhatsApp (built-in Baileys bridge — run `hermes whatsapp` to pair)
|
| 309 |
+
# WHATSAPP_ENABLED=false
|
| 310 |
+
# WHATSAPP_ALLOWED_USERS=15551234567
|
| 311 |
+
|
| 312 |
+
# Email (IMAP/SMTP — send and receive emails as Hermes)
|
| 313 |
+
# For Gmail: enable 2FA → create App Password at https://myaccount.google.com/apppasswords
|
| 314 |
+
# EMAIL_ADDRESS=hermes@gmail.com
|
| 315 |
+
# EMAIL_PASSWORD=xxxx xxxx xxxx xxxx
|
| 316 |
+
# EMAIL_IMAP_HOST=imap.gmail.com
|
| 317 |
+
# EMAIL_IMAP_PORT=993
|
| 318 |
+
# EMAIL_SMTP_HOST=smtp.gmail.com
|
| 319 |
+
# EMAIL_SMTP_PORT=587
|
| 320 |
+
# EMAIL_POLL_INTERVAL=15
|
| 321 |
+
# EMAIL_ALLOWED_USERS=your@email.com
|
| 322 |
+
# EMAIL_HOME_ADDRESS=your@email.com
|
| 323 |
+
|
| 324 |
+
# Gateway-wide: allow ALL users without an allowlist (default: false = deny)
|
| 325 |
+
# Only set to true if you intentionally want open access.
|
| 326 |
+
# GATEWAY_ALLOW_ALL_USERS=false
|
| 327 |
+
|
| 328 |
+
# =============================================================================
|
| 329 |
+
# RESPONSE PACING
|
| 330 |
+
# =============================================================================
|
| 331 |
+
# Human-like delays between message chunks on messaging platforms.
|
| 332 |
+
# Makes the bot feel less robotic.
|
| 333 |
+
# HERMES_HUMAN_DELAY_MODE=off # off | natural | custom
|
| 334 |
+
# HERMES_HUMAN_DELAY_MIN_MS=800 # Min delay in ms (custom mode)
|
| 335 |
+
# HERMES_HUMAN_DELAY_MAX_MS=2500 # Max delay in ms (custom mode)
|
| 336 |
+
|
| 337 |
+
# =============================================================================
|
| 338 |
+
# DEBUG OPTIONS
|
| 339 |
+
# =============================================================================
|
| 340 |
+
WEB_TOOLS_DEBUG=false
|
| 341 |
+
VISION_TOOLS_DEBUG=false
|
| 342 |
+
MOA_TOOLS_DEBUG=false
|
| 343 |
+
IMAGE_TOOLS_DEBUG=false
|
| 344 |
+
|
| 345 |
+
# =============================================================================
|
| 346 |
+
# CONTEXT COMPRESSION (Auto-shrinks long conversations)
|
| 347 |
+
# =============================================================================
|
| 348 |
+
# When conversation approaches model's context limit, middle turns are
|
| 349 |
+
# automatically summarized to free up space.
|
| 350 |
+
#
|
| 351 |
+
# Context compression is configured in ~/.hermes/config.yaml under compression:
|
| 352 |
+
# CONTEXT_COMPRESSION_ENABLED=true # Enable auto-compression (default: true)
|
| 353 |
+
# CONTEXT_COMPRESSION_THRESHOLD=0.85 # Compress at 85% of context limit
|
| 354 |
+
# Model is set via compression.summary_model in config.yaml (default: google/gemini-3-flash-preview)
|
| 355 |
+
|
| 356 |
+
# =============================================================================
|
| 357 |
+
# RL TRAINING (Tinker + Atropos)
|
| 358 |
+
# =============================================================================
|
| 359 |
+
# Run reinforcement learning training on language models using the Tinker API.
|
| 360 |
+
# Requires the rl-server to be running (from tinker-atropos package).
|
| 361 |
+
|
| 362 |
+
# Tinker API Key - RL training service
|
| 363 |
+
# Get at: https://tinker-console.thinkingmachines.ai/keys
|
| 364 |
+
# TINKER_API_KEY=
|
| 365 |
+
|
| 366 |
+
# Weights & Biases API Key - Experiment tracking and metrics
|
| 367 |
+
# Get at: https://wandb.ai/authorize
|
| 368 |
+
# WANDB_API_KEY=
|
| 369 |
+
|
| 370 |
+
# RL API Server URL (default: http://localhost:8080)
|
| 371 |
+
# Change if running the rl-server on a different host/port
|
| 372 |
+
# RL_API_URL=http://localhost:8080
|
| 373 |
+
|
| 374 |
+
# =============================================================================
|
| 375 |
+
# SKILLS HUB (GitHub integration for skill search/install/publish)
|
| 376 |
+
# =============================================================================
|
| 377 |
+
|
| 378 |
+
# GitHub Personal Access Token — for higher API rate limits on skill search/install
|
| 379 |
+
# Get at: https://github.com/settings/tokens (Fine-grained recommended)
|
| 380 |
+
# GITHUB_TOKEN=ghp_xxxxxxxxxxxxxxxxxxxx
|
| 381 |
+
|
| 382 |
+
# GitHub App credentials (optional — for bot identity on PRs)
|
| 383 |
+
# GITHUB_APP_ID=
|
| 384 |
+
# GITHUB_APP_PRIVATE_KEY_PATH=
|
| 385 |
+
# GITHUB_APP_INSTALLATION_ID=
|
| 386 |
+
|
| 387 |
+
# Groq API key (free tier — used for Whisper STT in voice mode)
|
| 388 |
+
# GROQ_API_KEY=
|
| 389 |
+
|
| 390 |
+
# =============================================================================
|
| 391 |
+
# STT PROVIDER SELECTION
|
| 392 |
+
# =============================================================================
|
| 393 |
+
# Default STT provider is "local" (faster-whisper) — runs on your machine, no API key needed.
|
| 394 |
+
# Install with: pip install faster-whisper
|
| 395 |
+
# Model downloads automatically on first use (~150 MB for "base").
|
| 396 |
+
# To use cloud providers instead, set GROQ_API_KEY or VOICE_TOOLS_OPENAI_KEY above.
|
| 397 |
+
# Provider priority: local > groq > openai
|
| 398 |
+
# Configure in config.yaml: stt.provider: local | groq | openai
|
| 399 |
+
|
| 400 |
+
# =============================================================================
|
| 401 |
+
# STT ADVANCED OVERRIDES (optional)
|
| 402 |
+
# =============================================================================
|
| 403 |
+
# Override default STT models per provider (normally set via stt.model in config.yaml)
|
| 404 |
+
# STT_GROQ_MODEL=whisper-large-v3-turbo
|
| 405 |
+
# STT_OPENAI_MODEL=whisper-1
|
| 406 |
+
|
| 407 |
+
# Override STT provider endpoints (for proxies or self-hosted instances)
|
| 408 |
+
# GROQ_BASE_URL=https://api.groq.com/openai/v1
|
| 409 |
+
# STT_OPENAI_BASE_URL=https://api.openai.com/v1
|
| 410 |
+
|
| 411 |
+
# =============================================================================
|
| 412 |
+
# MICROSOFT TEAMS INTEGRATION
|
| 413 |
+
# =============================================================================
|
| 414 |
+
# Register a Bot in Azure: https://dev.botframework.com/ → "Register a bot"
|
| 415 |
+
# Or use Azure Portal: Azure Active Directory → App registrations → New registration
|
| 416 |
+
# Then add the bot to Teams via the Bot Framework or App Studio.
|
| 417 |
+
#
|
| 418 |
+
# TEAMS_CLIENT_ID= # Azure AD App (client) ID
|
| 419 |
+
# TEAMS_CLIENT_SECRET= # Azure AD client secret value
|
| 420 |
+
# TEAMS_TENANT_ID= # Azure AD tenant ID (or "common" for multi-tenant)
|
| 421 |
+
# TEAMS_ALLOWED_USERS= # Comma-separated AAD object IDs or UPNs
|
| 422 |
+
# TEAMS_ALLOW_ALL_USERS=false # Set true to skip the allowlist
|
| 423 |
+
# TEAMS_HOME_CHANNEL= # Default channel/chat ID for cron delivery
|
| 424 |
+
# TEAMS_HOME_CHANNEL_NAME= # Display name for the home channel
|
| 425 |
+
# TEAMS_PORT=3978 # Webhook listen port (Bot Framework default)
|
| 426 |
+
|
| 427 |
+
# =============================================================================
|
| 428 |
+
# GOOGLE CHAT INTEGRATION
|
| 429 |
+
# =============================================================================
|
| 430 |
+
# Connects via Cloud Pub/Sub pull subscription (no public URL required).
|
| 431 |
+
# Setup walkthrough: website/docs/user-guide/messaging/google_chat.md.
|
| 432 |
+
# 1. Create a GCP project, enable the Google Chat API and Cloud Pub/Sub.
|
| 433 |
+
# 2. Create a Service Account with roles/pubsub.subscriber on the
|
| 434 |
+
# subscription (NOT project-wide); download the JSON key.
|
| 435 |
+
# 3. Configure your Chat app at console.cloud.google.com/apis/credentials
|
| 436 |
+
# → Google Chat API → Configuration → Cloud Pub/Sub topic.
|
| 437 |
+
# 4. (Optional, for native attachment delivery) Each user runs
|
| 438 |
+
# `/setup-files` once in their own DM after Pub/Sub is wired up.
|
| 439 |
+
#
|
| 440 |
+
# GOOGLE_CHAT_PROJECT_ID= # GCP project hosting the topic (or set GOOGLE_CLOUD_PROJECT)
|
| 441 |
+
# GOOGLE_CHAT_SUBSCRIPTION_NAME= # Full path: projects/<id>/subscriptions/<name>
|
| 442 |
+
# GOOGLE_CHAT_SERVICE_ACCOUNT_JSON= # Path to SA JSON (or set GOOGLE_APPLICATION_CREDENTIALS)
|
| 443 |
+
# GOOGLE_CHAT_ALLOWED_USERS= # Comma-separated emails allowed to talk to the bot
|
| 444 |
+
# GOOGLE_CHAT_ALLOW_ALL_USERS=false # Set true to skip the allowlist
|
| 445 |
+
# GOOGLE_CHAT_HOME_CHANNEL= # Default space (spaces/XXXX) for cron delivery
|
| 446 |
+
# GOOGLE_CHAT_HOME_CHANNEL_NAME= # Display name for the home channel
|
.envrc
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
watch_file pyproject.toml uv.lock
|
| 2 |
+
watch_file ui-tui/package-lock.json ui-tui/package.json
|
| 3 |
+
watch_file flake.nix flake.lock nix/devShell.nix nix/tui.nix nix/package.nix nix/python.nix
|
| 4 |
+
|
| 5 |
+
use flake
|
.gitattributes
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Auto-generated files — collapse diffs and exclude from language stats
|
| 2 |
+
web/package-lock.json linguist-generated=true
|
.gitignore
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.DS_Store
|
| 2 |
+
/venv/
|
| 3 |
+
/_pycache/
|
| 4 |
+
*.pyc*
|
| 5 |
+
__pycache__/
|
| 6 |
+
.venv/
|
| 7 |
+
.vscode/
|
| 8 |
+
.env
|
| 9 |
+
.env.local
|
| 10 |
+
.env.development.local
|
| 11 |
+
.env.test.local
|
| 12 |
+
.env.production.local
|
| 13 |
+
.env.development
|
| 14 |
+
.env.test
|
| 15 |
+
export*
|
| 16 |
+
__pycache__/model_tools.cpython-310.pyc
|
| 17 |
+
__pycache__/web_tools.cpython-310.pyc
|
| 18 |
+
logs/
|
| 19 |
+
data/
|
| 20 |
+
.pytest_cache/
|
| 21 |
+
tmp/
|
| 22 |
+
temp_vision_images/
|
| 23 |
+
hermes-*/*
|
| 24 |
+
examples/
|
| 25 |
+
tests/quick_test_dataset.jsonl
|
| 26 |
+
tests/sample_dataset.jsonl
|
| 27 |
+
run_datagen_kimik2-thinking.sh
|
| 28 |
+
run_datagen_megascience_glm4-6.sh
|
| 29 |
+
run_datagen_sonnet.sh
|
| 30 |
+
source-data/*
|
| 31 |
+
run_datagen_megascience_glm4-6.sh
|
| 32 |
+
data/*
|
| 33 |
+
node_modules/
|
| 34 |
+
browser-use/
|
| 35 |
+
agent-browser/
|
| 36 |
+
# Private keys
|
| 37 |
+
*.ppk
|
| 38 |
+
*.pem
|
| 39 |
+
privvy*
|
| 40 |
+
images/
|
| 41 |
+
__pycache__/
|
| 42 |
+
hermes_agent.egg-info/
|
| 43 |
+
wandb/
|
| 44 |
+
testlogs
|
| 45 |
+
|
| 46 |
+
# CLI config (may contain sensitive SSH paths)
|
| 47 |
+
cli-config.yaml
|
| 48 |
+
|
| 49 |
+
# Skills Hub state (lives in ~/.hermes/skills/.hub/ at runtime, but just in case)
|
| 50 |
+
skills/.hub/
|
| 51 |
+
ignored/
|
| 52 |
+
.worktrees/
|
| 53 |
+
environments/benchmarks/evals/
|
| 54 |
+
|
| 55 |
+
# Web UI build output
|
| 56 |
+
hermes_cli/web_dist/
|
| 57 |
+
|
| 58 |
+
# Web UI assets — synced from @nous-research/ui at build time via
|
| 59 |
+
# `npm run sync-assets` (see web/package.json).
|
| 60 |
+
web/public/fonts/
|
| 61 |
+
web/public/ds-assets/
|
| 62 |
+
|
| 63 |
+
# Release script temp files
|
| 64 |
+
.release_notes.md
|
| 65 |
+
mini-swe-agent/
|
| 66 |
+
|
| 67 |
+
# Nix
|
| 68 |
+
.direnv/
|
| 69 |
+
.nix-stamps/
|
| 70 |
+
result
|
| 71 |
+
website/static/api/skills-index.json
|
| 72 |
+
models-dev-upstream/
|
.gitmodules
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[submodule "tinker-atropos"]
|
| 2 |
+
path = tinker-atropos
|
| 3 |
+
url = https://github.com/nousresearch/tinker-atropos
|
.mailmap
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# .mailmap — canonical author mapping for git shortlog / git log / GitHub
|
| 2 |
+
# Format: Canonical Name <canonical@email> <commit@email>
|
| 3 |
+
# See: https://git-scm.com/docs/gitmailmap
|
| 4 |
+
#
|
| 5 |
+
# This maps commit emails to GitHub noreply addresses so that:
|
| 6 |
+
# 1. `git shortlog -sn` shows deduplicated contributor counts
|
| 7 |
+
# 2. GitHub's contributor graph can attribute commits correctly
|
| 8 |
+
# 3. Contributors with personal/work emails get proper credit
|
| 9 |
+
#
|
| 10 |
+
# When adding entries: use the contributor's GitHub noreply email as canonical
|
| 11 |
+
# so GitHub can link commits to their profile.
|
| 12 |
+
|
| 13 |
+
# === Teknium (multiple emails) ===
|
| 14 |
+
Teknium <127238744+teknium1@users.noreply.github.com> <teknium1@gmail.com>
|
| 15 |
+
Teknium <127238744+teknium1@users.noreply.github.com> <teknium@nousresearch.com>
|
| 16 |
+
|
| 17 |
+
# === Contributors — personal/work emails mapped to GitHub noreply ===
|
| 18 |
+
# Format: Canonical Name <GH-noreply> <commit-email>
|
| 19 |
+
|
| 20 |
+
# Verified via GH API email search
|
| 21 |
+
luyao618 <364939526@qq.com> <364939526@qq.com>
|
| 22 |
+
ethernet8023 <arilotter@gmail.com> <arilotter@gmail.com>
|
| 23 |
+
nicoloboschi <boschi1997@gmail.com> <boschi1997@gmail.com>
|
| 24 |
+
cherifya <chef.ya@gmail.com> <chef.ya@gmail.com>
|
| 25 |
+
BongSuCHOI <chlqhdtn98@gmail.com> <chlqhdtn98@gmail.com>
|
| 26 |
+
dsocolobsky <dsocolobsky@gmail.com> <dsocolobsky@gmail.com>
|
| 27 |
+
pefontana <fontana.pedro93@gmail.com> <fontana.pedro93@gmail.com>
|
| 28 |
+
Helmi <frank@helmschrott.de> <frank@helmschrott.de>
|
| 29 |
+
hata1234 <hata1234@gmail.com> <hata1234@gmail.com>
|
| 30 |
+
|
| 31 |
+
# Verified via PR investigation / salvage PR bodies
|
| 32 |
+
DeployFaith <agents@kylefrench.dev> <agents@kylefrench.dev>
|
| 33 |
+
flobo3 <floptopbot33@gmail.com> <floptopbot33@gmail.com>
|
| 34 |
+
gaixianggeng <gaixg94@gmail.com> <gaixg94@gmail.com>
|
| 35 |
+
KUSH42 <xush@xush.org> <xush@xush.org>
|
| 36 |
+
konsisumer <der@konsi.org> <der@konsi.org>
|
| 37 |
+
WorldInnovationsDepartment <vorvul.danylo@gmail.com> <vorvul.danylo@gmail.com>
|
| 38 |
+
m0n5t3r <iacobs@m0n5t3r.info> <iacobs@m0n5t3r.info>
|
| 39 |
+
sprmn24 <oncuevtv@gmail.com> <oncuevtv@gmail.com>
|
| 40 |
+
fancydirty <fancydirty@gmail.com> <fancydirty@gmail.com>
|
| 41 |
+
fxfitz <francis.x.fitzpatrick@gmail.com> <francis.x.fitzpatrick@gmail.com>
|
| 42 |
+
limars874 <limars874@gmail.com> <limars874@gmail.com>
|
| 43 |
+
AaronWong1999 <aaronwong1999@icloud.com> <aaronwong1999@icloud.com>
|
| 44 |
+
dippwho <dipp.who@gmail.com> <dipp.who@gmail.com>
|
| 45 |
+
duerzy <duerzy@gmail.com> <duerzy@gmail.com>
|
| 46 |
+
geoffwellman <geoff.wellman@gmail.com> <geoff.wellman@gmail.com>
|
| 47 |
+
hcshen0111 <shenhaocheng19990111@gmail.com> <shenhaocheng19990111@gmail.com>
|
| 48 |
+
jamesarch <han.shan@live.cn> <han.shan@live.cn>
|
| 49 |
+
stephenschoettler <stephenschoettler@gmail.com> <stephenschoettler@gmail.com>
|
| 50 |
+
Tranquil-Flow <tranquil_flow@protonmail.com> <tranquil_flow@protonmail.com>
|
| 51 |
+
Dusk1e <yusufalweshdemir@gmail.com> <yusufalweshdemir@gmail.com>
|
| 52 |
+
Awsh1 <ysfalweshcan@gmail.com> <ysfalweshcan@gmail.com>
|
| 53 |
+
WAXLYY <ysfwaxlycan@gmail.com> <ysfwaxlycan@gmail.com>
|
| 54 |
+
donrhmexe <don.rhm@gmail.com> <don.rhm@gmail.com>
|
| 55 |
+
hqhq1025 <1506751656@qq.com> <1506751656@qq.com>
|
| 56 |
+
BlackishGreen33 <s5460703@gmail.com> <s5460703@gmail.com>
|
| 57 |
+
tomqiaozc <zqiao@microsoft.com> <zqiao@microsoft.com>
|
| 58 |
+
MagicRay1217 <mingjwan@microsoft.com> <mingjwan@microsoft.com>
|
| 59 |
+
aaronagent <1115117931@qq.com> <1115117931@qq.com>
|
| 60 |
+
YoungYang963 <young@YoungdeMacBook-Pro.local> <young@YoungdeMacBook-Pro.local>
|
| 61 |
+
LongOddCode <haolong@microsoft.com> <haolong@microsoft.com>
|
| 62 |
+
Cafexss <coffeemjj@gmail.com> <coffeemjj@gmail.com>
|
| 63 |
+
Cygra <sjtuwbh@gmail.com> <sjtuwbh@gmail.com>
|
| 64 |
+
DomGrieco <dgrieco@redhat.com> <dgrieco@redhat.com>
|
| 65 |
+
|
| 66 |
+
# Duplicate email mapping (same person, multiple emails)
|
| 67 |
+
Sertug17 <104278804+Sertug17@users.noreply.github.com> <srhtsrht17@gmail.com>
|
| 68 |
+
yyovil <birdiegyal@gmail.com> <tanishq231003@gmail.com>
|
| 69 |
+
DomGrieco <dgrieco@redhat.com> <dgrieco@redhat.com>
|
| 70 |
+
dsocolobsky <dsocolobsky@gmail.com> <dylan.socolobsky@lambdaclass.com>
|
| 71 |
+
olafthiele <programming@olafthiele.com> <olafthiele@gmail.com>
|
| 72 |
+
|
| 73 |
+
# Verified via git display name matching GH contributor username
|
| 74 |
+
cokemine <aptx4561@gmail.com> <aptx4561@gmail.com>
|
| 75 |
+
dalianmao000 <dalianmao0107@gmail.com> <dalianmao0107@gmail.com>
|
| 76 |
+
emozilla <emozilla@nousresearch.com> <emozilla@nousresearch.com>
|
| 77 |
+
jjovalle99 <juan.ovalle@mistral.ai> <juan.ovalle@mistral.ai>
|
| 78 |
+
kagura-agent <kagura.chen28@gmail.com> <kagura.chen28@gmail.com>
|
| 79 |
+
spniyant <niyant@spicefi.xyz> <niyant@spicefi.xyz>
|
| 80 |
+
olafthiele <programming@olafthiele.com> <programming@olafthiele.com>
|
| 81 |
+
r266-tech <r2668940489@gmail.com> <r2668940489@gmail.com>
|
| 82 |
+
xingkongliang <tianliangjay@gmail.com> <tianliangjay@gmail.com>
|
| 83 |
+
win4r <win4r@outlook.com> <win4r@outlook.com>
|
| 84 |
+
zhouboli <zhouboli@gmail.com> <zhouboli@gmail.com>
|
| 85 |
+
yongtenglei <yongtenglei@gmail.com> <yongtenglei@gmail.com>
|
| 86 |
+
|
| 87 |
+
# Nous Research team
|
| 88 |
+
benbarclay <ben@nousresearch.com> <ben@nousresearch.com>
|
| 89 |
+
jquesnelle <jonny@nousresearch.com> <jonny@nousresearch.com>
|
| 90 |
+
|
| 91 |
+
# GH contributor list verified
|
| 92 |
+
spideystreet <dhicham.pro@gmail.com> <dhicham.pro@gmail.com>
|
| 93 |
+
dorukardahan <dorukardahan@hotmail.com> <dorukardahan@hotmail.com>
|
| 94 |
+
MustafaKara7 <karamusti912@gmail.com> <karamusti912@gmail.com>
|
| 95 |
+
Hmbown <hmbown@gmail.com> <hmbown@gmail.com>
|
| 96 |
+
kamil-gwozdz <kamil@gwozdz.me> <kamil@gwozdz.me>
|
| 97 |
+
kira-ariaki <kira@ariaki.me> <kira@ariaki.me>
|
| 98 |
+
knopki <knopki@duck.com> <knopki@duck.com>
|
| 99 |
+
Unayung <unayung@gmail.com> <unayung@gmail.com>
|
| 100 |
+
SeeYangZhi <yangzhi.see@gmail.com> <yangzhi.see@gmail.com>
|
| 101 |
+
Julientalbot <julien.talbot@ergonomia.re> <julien.talbot@ergonomia.re>
|
| 102 |
+
lesterli <lisicheng168@gmail.com> <lisicheng168@gmail.com>
|
| 103 |
+
JiayuuWang <jiayuw794@gmail.com> <jiayuw794@gmail.com>
|
| 104 |
+
tesseracttars-creator <tesseracttars@gmail.com> <tesseracttars@gmail.com>
|
| 105 |
+
xinbenlv <zzn+pa@zzn.im> <zzn+pa@zzn.im>
|
| 106 |
+
SaulJWu <saul.jj.wu@gmail.com> <saul.jj.wu@gmail.com>
|
| 107 |
+
angelos <angelos@oikos.lan.home.malaiwah.com> <angelos@oikos.lan.home.malaiwah.com>
|
| 108 |
+
MestreY0d4-Uninter <241404605+MestreY0d4-Uninter@users.noreply.github.com> <MestreY0d4-Uninter@users.noreply.github.com>
|
AGENTS.md
ADDED
|
@@ -0,0 +1,985 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Hermes Agent - Development Guide
|
| 2 |
+
|
| 3 |
+
Instructions for AI coding assistants and developers working on the hermes-agent codebase.
|
| 4 |
+
|
| 5 |
+
## Development Environment
|
| 6 |
+
|
| 7 |
+
```bash
|
| 8 |
+
# Prefer .venv; fall back to venv if that's what your checkout has.
|
| 9 |
+
source .venv/bin/activate # or: source venv/bin/activate
|
| 10 |
+
```
|
| 11 |
+
|
| 12 |
+
`scripts/run_tests.sh` probes `.venv` first, then `venv`, then
|
| 13 |
+
`$HOME/.hermes/hermes-agent/venv` (for worktrees that share a venv with the
|
| 14 |
+
main checkout).
|
| 15 |
+
|
| 16 |
+
## Project Structure
|
| 17 |
+
|
| 18 |
+
File counts shift constantly — don't treat the tree below as exhaustive.
|
| 19 |
+
The canonical source is the filesystem. The notes call out the load-bearing
|
| 20 |
+
entry points you'll actually edit.
|
| 21 |
+
|
| 22 |
+
```
|
| 23 |
+
hermes-agent/
|
| 24 |
+
├── run_agent.py # AIAgent class — core conversation loop (~12k LOC)
|
| 25 |
+
├── model_tools.py # Tool orchestration, discover_builtin_tools(), handle_function_call()
|
| 26 |
+
├── toolsets.py # Toolset definitions, _HERMES_CORE_TOOLS list
|
| 27 |
+
├── cli.py # HermesCLI class — interactive CLI orchestrator (~11k LOC)
|
| 28 |
+
├── hermes_state.py # SessionDB — SQLite session store (FTS5 search)
|
| 29 |
+
├── hermes_constants.py # get_hermes_home(), display_hermes_home() — profile-aware paths
|
| 30 |
+
├── hermes_logging.py # setup_logging() — agent.log / errors.log / gateway.log (profile-aware)
|
| 31 |
+
├── batch_runner.py # Parallel batch processing
|
| 32 |
+
├── agent/ # Agent internals (provider adapters, memory, caching, compression, etc.)
|
| 33 |
+
├── hermes_cli/ # CLI subcommands, setup wizard, plugins loader, skin engine
|
| 34 |
+
├── tools/ # Tool implementations — auto-discovered via tools/registry.py
|
| 35 |
+
│ └── environments/ # Terminal backends (local, docker, ssh, modal, daytona, singularity)
|
| 36 |
+
├── gateway/ # Messaging gateway — run.py + session.py + platforms/
|
| 37 |
+
│ ├── platforms/ # Adapter per platform (telegram, discord, slack, whatsapp,
|
| 38 |
+
│ │ # homeassistant, signal, matrix, mattermost, email, sms,
|
| 39 |
+
│ │ # dingtalk, wecom, weixin, feishu, qqbot, bluebubbles,
|
| 40 |
+
│ │ # yuanbao, webhook, api_server, ...). See ADDING_A_PLATFORM.md.
|
| 41 |
+
│ └── builtin_hooks/ # Extension point for always-registered gateway hooks (none shipped)
|
| 42 |
+
├── plugins/ # Plugin system (see "Plugins" section below)
|
| 43 |
+
│ ├── memory/ # Memory-provider plugins (honcho, mem0, supermemory, ...)
|
| 44 |
+
│ ├── context_engine/ # Context-engine plugins
|
| 45 |
+
│ ├── model-providers/ # Inference backend plugins (openrouter, anthropic, gmi, ...)
|
| 46 |
+
│ ├── kanban/ # Multi-agent board dispatcher + worker plugin
|
| 47 |
+
│ ├── hermes-achievements/ # Gamified achievement tracking
|
| 48 |
+
│ ├── observability/ # Metrics / traces / logs plugin
|
| 49 |
+
│ ├── image_gen/ # Image-generation providers
|
| 50 |
+
│ └── <others>/ # disk-cleanup, example-dashboard, google_meet, platforms,
|
| 51 |
+
│ # spotify, strike-freedom-cockpit, ...
|
| 52 |
+
├── optional-skills/ # Heavier/niche skills shipped but NOT active by default
|
| 53 |
+
├── skills/ # Built-in skills bundled with the repo
|
| 54 |
+
├── ui-tui/ # Ink (React) terminal UI — `hermes --tui`
|
| 55 |
+
│ └── src/ # entry.tsx, app.tsx, gatewayClient.ts + app/components/hooks/lib
|
| 56 |
+
├── tui_gateway/ # Python JSON-RPC backend for the TUI
|
| 57 |
+
├── acp_adapter/ # ACP server (VS Code / Zed / JetBrains integration)
|
| 58 |
+
├── cron/ # Scheduler — jobs.py, scheduler.py
|
| 59 |
+
├── environments/ # RL training environments (Atropos)
|
| 60 |
+
├── scripts/ # run_tests.sh, release.py, auxiliary scripts
|
| 61 |
+
├── website/ # Docusaurus docs site
|
| 62 |
+
└── tests/ # Pytest suite (~17k tests across ~900 files as of May 2026)
|
| 63 |
+
```
|
| 64 |
+
|
| 65 |
+
**User config:** `~/.hermes/config.yaml` (settings), `~/.hermes/.env` (API keys only).
|
| 66 |
+
**Logs:** `~/.hermes/logs/` — `agent.log` (INFO+), `errors.log` (WARNING+),
|
| 67 |
+
`gateway.log` when running the gateway. Profile-aware via `get_hermes_home()`.
|
| 68 |
+
Browse with `hermes logs [--follow] [--level ...] [--session ...]`.
|
| 69 |
+
|
| 70 |
+
## File Dependency Chain
|
| 71 |
+
|
| 72 |
+
```
|
| 73 |
+
tools/registry.py (no deps — imported by all tool files)
|
| 74 |
+
↑
|
| 75 |
+
tools/*.py (each calls registry.register() at import time)
|
| 76 |
+
↑
|
| 77 |
+
model_tools.py (imports tools/registry + triggers tool discovery)
|
| 78 |
+
↑
|
| 79 |
+
run_agent.py, cli.py, batch_runner.py, environments/
|
| 80 |
+
```
|
| 81 |
+
|
| 82 |
+
---
|
| 83 |
+
|
| 84 |
+
## AIAgent Class (run_agent.py)
|
| 85 |
+
|
| 86 |
+
The real `AIAgent.__init__` takes ~60 parameters (credentials, routing, callbacks,
|
| 87 |
+
session context, budget, credential pool, etc.). The signature below is the
|
| 88 |
+
minimum subset you'll usually touch — read `run_agent.py` for the full list.
|
| 89 |
+
|
| 90 |
+
```python
|
| 91 |
+
class AIAgent:
|
| 92 |
+
def __init__(self,
|
| 93 |
+
base_url: str = None,
|
| 94 |
+
api_key: str = None,
|
| 95 |
+
provider: str = None,
|
| 96 |
+
api_mode: str = None, # "chat_completions" | "codex_responses" | ...
|
| 97 |
+
model: str = "", # empty → resolved from config/provider later
|
| 98 |
+
max_iterations: int = 90, # tool-calling iterations (shared with subagents)
|
| 99 |
+
enabled_toolsets: list = None,
|
| 100 |
+
disabled_toolsets: list = None,
|
| 101 |
+
quiet_mode: bool = False,
|
| 102 |
+
save_trajectories: bool = False,
|
| 103 |
+
platform: str = None, # "cli", "telegram", etc.
|
| 104 |
+
session_id: str = None,
|
| 105 |
+
skip_context_files: bool = False,
|
| 106 |
+
skip_memory: bool = False,
|
| 107 |
+
credential_pool=None,
|
| 108 |
+
# ... plus callbacks, thread/user/chat IDs, iteration_budget, fallback_model,
|
| 109 |
+
# checkpoints config, prefill_messages, service_tier, reasoning_config, etc.
|
| 110 |
+
): ...
|
| 111 |
+
|
| 112 |
+
def chat(self, message: str) -> str:
|
| 113 |
+
"""Simple interface — returns final response string."""
|
| 114 |
+
|
| 115 |
+
def run_conversation(self, user_message: str, system_message: str = None,
|
| 116 |
+
conversation_history: list = None, task_id: str = None) -> dict:
|
| 117 |
+
"""Full interface — returns dict with final_response + messages."""
|
| 118 |
+
```
|
| 119 |
+
|
| 120 |
+
### Agent Loop
|
| 121 |
+
|
| 122 |
+
The core loop is inside `run_conversation()` — entirely synchronous, with
|
| 123 |
+
interrupt checks, budget tracking, and a one-turn grace call:
|
| 124 |
+
|
| 125 |
+
```python
|
| 126 |
+
while (api_call_count < self.max_iterations and self.iteration_budget.remaining > 0) \
|
| 127 |
+
or self._budget_grace_call:
|
| 128 |
+
if self._interrupt_requested: break
|
| 129 |
+
response = client.chat.completions.create(model=model, messages=messages, tools=tool_schemas)
|
| 130 |
+
if response.tool_calls:
|
| 131 |
+
for tool_call in response.tool_calls:
|
| 132 |
+
result = handle_function_call(tool_call.name, tool_call.args, task_id)
|
| 133 |
+
messages.append(tool_result_message(result))
|
| 134 |
+
api_call_count += 1
|
| 135 |
+
else:
|
| 136 |
+
return response.content
|
| 137 |
+
```
|
| 138 |
+
|
| 139 |
+
Messages follow OpenAI format: `{"role": "system/user/assistant/tool", ...}`.
|
| 140 |
+
Reasoning content is stored in `assistant_msg["reasoning"]`.
|
| 141 |
+
|
| 142 |
+
---
|
| 143 |
+
|
| 144 |
+
## CLI Architecture (cli.py)
|
| 145 |
+
|
| 146 |
+
- **Rich** for banner/panels, **prompt_toolkit** for input with autocomplete
|
| 147 |
+
- **KawaiiSpinner** (`agent/display.py`) — animated faces during API calls, `┊` activity feed for tool results
|
| 148 |
+
- `load_cli_config()` in cli.py merges hardcoded defaults + user config YAML
|
| 149 |
+
- **Skin engine** (`hermes_cli/skin_engine.py`) — data-driven CLI theming; initialized from `display.skin` config key at startup; skins customize banner colors, spinner faces/verbs/wings, tool prefix, response box, branding text
|
| 150 |
+
- `process_command()` is a method on `HermesCLI` — dispatches on canonical command name resolved via `resolve_command()` from the central registry
|
| 151 |
+
- Skill slash commands: `agent/skill_commands.py` scans `~/.hermes/skills/`, injects as **user message** (not system prompt) to preserve prompt caching
|
| 152 |
+
|
| 153 |
+
### Slash Command Registry (`hermes_cli/commands.py`)
|
| 154 |
+
|
| 155 |
+
All slash commands are defined in a central `COMMAND_REGISTRY` list of `CommandDef` objects. Every downstream consumer derives from this registry automatically:
|
| 156 |
+
|
| 157 |
+
- **CLI** — `process_command()` resolves aliases via `resolve_command()`, dispatches on canonical name
|
| 158 |
+
- **Gateway** — `GATEWAY_KNOWN_COMMANDS` frozenset for hook emission, `resolve_command()` for dispatch
|
| 159 |
+
- **Gateway help** — `gateway_help_lines()` generates `/help` output
|
| 160 |
+
- **Telegram** — `telegram_bot_commands()` generates the BotCommand menu
|
| 161 |
+
- **Slack** — `slack_subcommand_map()` generates `/hermes` subcommand routing
|
| 162 |
+
- **Autocomplete** — `COMMANDS` flat dict feeds `SlashCommandCompleter`
|
| 163 |
+
- **CLI help** — `COMMANDS_BY_CATEGORY` dict feeds `show_help()`
|
| 164 |
+
|
| 165 |
+
### Adding a Slash Command
|
| 166 |
+
|
| 167 |
+
1. Add a `CommandDef` entry to `COMMAND_REGISTRY` in `hermes_cli/commands.py`:
|
| 168 |
+
```python
|
| 169 |
+
CommandDef("mycommand", "Description of what it does", "Session",
|
| 170 |
+
aliases=("mc",), args_hint="[arg]"),
|
| 171 |
+
```
|
| 172 |
+
2. Add handler in `HermesCLI.process_command()` in `cli.py`:
|
| 173 |
+
```python
|
| 174 |
+
elif canonical == "mycommand":
|
| 175 |
+
self._handle_mycommand(cmd_original)
|
| 176 |
+
```
|
| 177 |
+
3. If the command is available in the gateway, add a handler in `gateway/run.py`:
|
| 178 |
+
```python
|
| 179 |
+
if canonical == "mycommand":
|
| 180 |
+
return await self._handle_mycommand(event)
|
| 181 |
+
```
|
| 182 |
+
4. For persistent settings, use `save_config_value()` in `cli.py`
|
| 183 |
+
|
| 184 |
+
**CommandDef fields:**
|
| 185 |
+
- `name` — canonical name without slash (e.g. `"background"`)
|
| 186 |
+
- `description` — human-readable description
|
| 187 |
+
- `category` — one of `"Session"`, `"Configuration"`, `"Tools & Skills"`, `"Info"`, `"Exit"`
|
| 188 |
+
- `aliases` — tuple of alternative names (e.g. `("bg",)`)
|
| 189 |
+
- `args_hint` — argument placeholder shown in help (e.g. `"<prompt>"`, `"[name]"`)
|
| 190 |
+
- `cli_only` — only available in the interactive CLI
|
| 191 |
+
- `gateway_only` — only available in messaging platforms
|
| 192 |
+
- `gateway_config_gate` — config dotpath (e.g. `"display.tool_progress_command"`); when set on a `cli_only` command, the command becomes available in the gateway if the config value is truthy. `GATEWAY_KNOWN_COMMANDS` always includes config-gated commands so the gateway can dispatch them; help/menus only show them when the gate is open.
|
| 193 |
+
|
| 194 |
+
**Adding an alias** requires only adding it to the `aliases` tuple on the existing `CommandDef`. No other file changes needed — dispatch, help text, Telegram menu, Slack mapping, and autocomplete all update automatically.
|
| 195 |
+
|
| 196 |
+
---
|
| 197 |
+
|
| 198 |
+
## TUI Architecture (ui-tui + tui_gateway)
|
| 199 |
+
|
| 200 |
+
The TUI is a full replacement for the classic (prompt_toolkit) CLI, activated via `hermes --tui` or `HERMES_TUI=1`.
|
| 201 |
+
|
| 202 |
+
### Process Model
|
| 203 |
+
|
| 204 |
+
```
|
| 205 |
+
hermes --tui
|
| 206 |
+
└─ Node (Ink) ──stdio JSON-RPC── Python (tui_gateway)
|
| 207 |
+
│ └─ AIAgent + tools + sessions
|
| 208 |
+
└─ renders transcript, composer, prompts, activity
|
| 209 |
+
```
|
| 210 |
+
|
| 211 |
+
TypeScript owns the screen. Python owns sessions, tools, model calls, and slash command logic.
|
| 212 |
+
|
| 213 |
+
### Transport
|
| 214 |
+
|
| 215 |
+
Newline-delimited JSON-RPC over stdio. Requests from Ink, events from Python. See `tui_gateway/server.py` for the full method/event catalog.
|
| 216 |
+
|
| 217 |
+
### Key Surfaces
|
| 218 |
+
|
| 219 |
+
| Surface | Ink component | Gateway method |
|
| 220 |
+
|---------|---------------|----------------|
|
| 221 |
+
| Chat streaming | `app.tsx` + `messageLine.tsx` | `prompt.submit` → `message.delta/complete` |
|
| 222 |
+
| Tool activity | `thinking.tsx` | `tool.start/progress/complete` |
|
| 223 |
+
| Approvals | `prompts.tsx` | `approval.respond` ← `approval.request` |
|
| 224 |
+
| Clarify/sudo/secret | `prompts.tsx`, `maskedPrompt.tsx` | `clarify/sudo/secret.respond` |
|
| 225 |
+
| Session picker | `sessionPicker.tsx` | `session.list/resume` |
|
| 226 |
+
| Slash commands | Local handler + fallthrough | `slash.exec` → `_SlashWorker`, `command.dispatch` |
|
| 227 |
+
| Completions | `useCompletion` hook | `complete.slash`, `complete.path` |
|
| 228 |
+
| Theming | `theme.ts` + `branding.tsx` | `gateway.ready` with skin data |
|
| 229 |
+
|
| 230 |
+
### Slash Command Flow
|
| 231 |
+
|
| 232 |
+
1. Built-in client commands (`/help`, `/quit`, `/clear`, `/resume`, `/copy`, `/paste`, etc.) handled locally in `app.tsx`
|
| 233 |
+
2. Everything else → `slash.exec` (runs in persistent `_SlashWorker` subprocess) → `command.dispatch` fallback
|
| 234 |
+
|
| 235 |
+
### Dev Commands
|
| 236 |
+
|
| 237 |
+
```bash
|
| 238 |
+
cd ui-tui
|
| 239 |
+
npm install # first time
|
| 240 |
+
npm run dev # watch mode (rebuilds hermes-ink + tsx --watch)
|
| 241 |
+
npm start # production
|
| 242 |
+
npm run build # full build (hermes-ink + tsc)
|
| 243 |
+
npm run type-check # typecheck only (tsc --noEmit)
|
| 244 |
+
npm run lint # eslint
|
| 245 |
+
npm run fmt # prettier
|
| 246 |
+
npm test # vitest
|
| 247 |
+
```
|
| 248 |
+
|
| 249 |
+
### TUI in the Dashboard (`hermes dashboard` → `/chat`)
|
| 250 |
+
|
| 251 |
+
The dashboard embeds the real `hermes --tui` — **not** a rewrite. See `hermes_cli/pty_bridge.py` + the `@app.websocket("/api/pty")` endpoint in `hermes_cli/web_server.py`.
|
| 252 |
+
|
| 253 |
+
- Browser loads `web/src/pages/ChatPage.tsx`, which mounts xterm.js's `Terminal` with the WebGL renderer, `@xterm/addon-fit` for container-driven resize, and `@xterm/addon-unicode11` for modern wide-character widths.
|
| 254 |
+
- `/api/pty?token=…` upgrades to a WebSocket; auth uses the same ephemeral `_SESSION_TOKEN` as REST, via query param (browsers can't set `Authorization` on WS upgrade).
|
| 255 |
+
- The server spawns whatever `hermes --tui` would spawn, through `ptyprocess` (POSIX PTY — WSL works, native Windows does not).
|
| 256 |
+
- Frames: raw PTY bytes each direction; resize via `\x1b[RESIZE:<cols>;<rows>]` intercepted on the server and applied with `TIOCSWINSZ`.
|
| 257 |
+
|
| 258 |
+
**Do not re-implement the primary chat experience in React.** The main transcript, composer/input flow (including slash-command behavior), and PTY-backed terminal belong to the embedded `hermes --tui` — anything new you add to Ink shows up in the dashboard automatically. If you find yourself rebuilding the transcript or composer for the dashboard, stop and extend Ink instead.
|
| 259 |
+
|
| 260 |
+
**Structured React UI around the TUI is allowed when it is not a second chat surface.** Sidebar widgets, inspectors, summaries, status panels, and similar supporting views (e.g. `ChatSidebar`, `ModelPickerDialog`, `ToolCall`) are fine when they complement the embedded TUI rather than replacing the transcript / composer / terminal. Keep their state independent of the PTY child's session and surface their failures non-destructively so the terminal pane keeps working unimpaired.
|
| 261 |
+
|
| 262 |
+
---
|
| 263 |
+
|
| 264 |
+
## Adding New Tools
|
| 265 |
+
|
| 266 |
+
For most custom or local-only tools, do **not** edit Hermes core. Use the plugin
|
| 267 |
+
route instead: create `~/.hermes/plugins/<name>/plugin.yaml` and
|
| 268 |
+
`~/.hermes/plugins/<name>/__init__.py`, then register tools with
|
| 269 |
+
`ctx.register_tool(...)`. Plugin toolsets are discovered automatically and can be
|
| 270 |
+
enabled or disabled without touching `tools/` or `toolsets.py`.
|
| 271 |
+
|
| 272 |
+
Use the built-in route below only when the user is explicitly contributing a new
|
| 273 |
+
core Hermes tool that should ship in the base system.
|
| 274 |
+
|
| 275 |
+
Built-in/core tools require changes in **2 files**:
|
| 276 |
+
|
| 277 |
+
**1. Create `tools/your_tool.py`:**
|
| 278 |
+
```python
|
| 279 |
+
import json, os
|
| 280 |
+
from tools.registry import registry
|
| 281 |
+
|
| 282 |
+
def check_requirements() -> bool:
|
| 283 |
+
return bool(os.getenv("EXAMPLE_API_KEY"))
|
| 284 |
+
|
| 285 |
+
def example_tool(param: str, task_id: str = None) -> str:
|
| 286 |
+
return json.dumps({"success": True, "data": "..."})
|
| 287 |
+
|
| 288 |
+
registry.register(
|
| 289 |
+
name="example_tool",
|
| 290 |
+
toolset="example",
|
| 291 |
+
schema={"name": "example_tool", "description": "...", "parameters": {...}},
|
| 292 |
+
handler=lambda args, **kw: example_tool(param=args.get("param", ""), task_id=kw.get("task_id")),
|
| 293 |
+
check_fn=check_requirements,
|
| 294 |
+
requires_env=["EXAMPLE_API_KEY"],
|
| 295 |
+
)
|
| 296 |
+
```
|
| 297 |
+
|
| 298 |
+
**2. Add to `toolsets.py`** — either `_HERMES_CORE_TOOLS` (all platforms) or a new toolset. **This step is required:** auto-discovery imports the tool and registers its schema, but the tool is only *exposed to an agent* if its name appears in a toolset. `_HERMES_CORE_TOOLS` is not dead code — it's the default bundle every platform's base toolset inherits from.
|
| 299 |
+
|
| 300 |
+
Auto-discovery: any `tools/*.py` file with a top-level `registry.register()` call is imported automatically — no manual import list to maintain. Wiring into a toolset is still a deliberate, manual step.
|
| 301 |
+
|
| 302 |
+
The registry handles schema collection, dispatch, availability checking, and error wrapping. All handlers MUST return a JSON string.
|
| 303 |
+
|
| 304 |
+
**Path references in tool schemas**: If the schema description mentions file paths (e.g. default output directories), use `display_hermes_home()` to make them profile-aware. The schema is generated at import time, which is after `_apply_profile_override()` sets `HERMES_HOME`.
|
| 305 |
+
|
| 306 |
+
**State files**: If a tool stores persistent state (caches, logs, checkpoints), use `get_hermes_home()` for the base directory — never `Path.home() / ".hermes"`. This ensures each profile gets its own state.
|
| 307 |
+
|
| 308 |
+
**Agent-level tools** (todo, memory): intercepted by `run_agent.py` before `handle_function_call()`. See `tools/todo_tool.py` for the pattern.
|
| 309 |
+
|
| 310 |
+
---
|
| 311 |
+
|
| 312 |
+
## Adding Configuration
|
| 313 |
+
|
| 314 |
+
### config.yaml options:
|
| 315 |
+
1. Add to `DEFAULT_CONFIG` in `hermes_cli/config.py`
|
| 316 |
+
2. Bump `_config_version` (check the current value at the top of `DEFAULT_CONFIG`)
|
| 317 |
+
ONLY if you need to actively migrate/transform existing user config
|
| 318 |
+
(renaming keys, changing structure). Adding a new key to an existing
|
| 319 |
+
section is handled automatically by the deep-merge and does NOT require
|
| 320 |
+
a version bump.
|
| 321 |
+
|
| 322 |
+
### Top-level `config.yaml` sections (non-exhaustive):
|
| 323 |
+
|
| 324 |
+
`model`, `agent`, `terminal`, `compression`, `display`, `stt`, `tts`,
|
| 325 |
+
`memory`, `security`, `delegation`, `smart_model_routing`, `checkpoints`,
|
| 326 |
+
`auxiliary`, `curator`, `skills`, `gateway`, `logging`, `cron`, `profiles`,
|
| 327 |
+
`plugins`, `honcho`.
|
| 328 |
+
|
| 329 |
+
`auxiliary` holds per-task overrides for side-LLM work (curator, vision,
|
| 330 |
+
embedding, title generation, session_search, etc.) — each task can pin
|
| 331 |
+
its own provider/model/base_url/max_tokens/reasoning_effort. See
|
| 332 |
+
`agent/auxiliary_client.py::_resolve_auto` for resolution order.
|
| 333 |
+
|
| 334 |
+
`curator` holds the background skill-maintenance config —
|
| 335 |
+
`enabled`, `interval_hours`, `min_idle_hours`, `stale_after_days`,
|
| 336 |
+
`archive_after_days`, `backup` (nested).
|
| 337 |
+
|
| 338 |
+
### .env variables (SECRETS ONLY — API keys, tokens, passwords):
|
| 339 |
+
1. Add to `OPTIONAL_ENV_VARS` in `hermes_cli/config.py` with metadata:
|
| 340 |
+
```python
|
| 341 |
+
"NEW_API_KEY": {
|
| 342 |
+
"description": "What it's for",
|
| 343 |
+
"prompt": "Display name",
|
| 344 |
+
"url": "https://...",
|
| 345 |
+
"password": True,
|
| 346 |
+
"category": "tool", # provider, tool, messaging, setting
|
| 347 |
+
},
|
| 348 |
+
```
|
| 349 |
+
|
| 350 |
+
Non-secret settings (timeouts, thresholds, feature flags, paths, display
|
| 351 |
+
preferences) belong in `config.yaml`, not `.env`. If internal code needs an
|
| 352 |
+
env var mirror for backward compatibility, bridge it from `config.yaml` to
|
| 353 |
+
the env var in code (see `gateway_timeout`, `terminal.cwd` → `TERMINAL_CWD`).
|
| 354 |
+
|
| 355 |
+
### Config loaders (three paths — know which one you're in):
|
| 356 |
+
|
| 357 |
+
| Loader | Used by | Location |
|
| 358 |
+
|--------|---------|----------|
|
| 359 |
+
| `load_cli_config()` | CLI mode | `cli.py` — merges CLI-specific defaults + user YAML |
|
| 360 |
+
| `load_config()` | `hermes tools`, `hermes setup`, most CLI subcommands | `hermes_cli/config.py` — merges `DEFAULT_CONFIG` + user YAML |
|
| 361 |
+
| Direct YAML load | Gateway runtime | `gateway/run.py` + `gateway/config.py` — reads user YAML raw |
|
| 362 |
+
|
| 363 |
+
If you add a new key and the CLI sees it but the gateway doesn't (or vice
|
| 364 |
+
versa), you're on the wrong loader. Check `DEFAULT_CONFIG` coverage.
|
| 365 |
+
|
| 366 |
+
### Working directory:
|
| 367 |
+
- **CLI** — uses the process's current directory (`os.getcwd()`).
|
| 368 |
+
- **Messaging** — uses `terminal.cwd` from `config.yaml`. The gateway bridges this
|
| 369 |
+
to the `TERMINAL_CWD` env var for child tools. **`MESSAGING_CWD` has been
|
| 370 |
+
removed** — the config loader prints a deprecation warning if it's set in
|
| 371 |
+
`.env`. Same for `TERMINAL_CWD` in `.env`; the canonical setting is
|
| 372 |
+
`terminal.cwd` in `config.yaml`.
|
| 373 |
+
|
| 374 |
+
---
|
| 375 |
+
|
| 376 |
+
## Skin/Theme System
|
| 377 |
+
|
| 378 |
+
The skin engine (`hermes_cli/skin_engine.py`) provides data-driven CLI visual customization. Skins are **pure data** — no code changes needed to add a new skin.
|
| 379 |
+
|
| 380 |
+
### Architecture
|
| 381 |
+
|
| 382 |
+
```
|
| 383 |
+
hermes_cli/skin_engine.py # SkinConfig dataclass, built-in skins, YAML loader
|
| 384 |
+
~/.hermes/skins/*.yaml # User-installed custom skins (drop-in)
|
| 385 |
+
```
|
| 386 |
+
|
| 387 |
+
- `init_skin_from_config()` — called at CLI startup, reads `display.skin` from config
|
| 388 |
+
- `get_active_skin()` — returns cached `SkinConfig` for the current skin
|
| 389 |
+
- `set_active_skin(name)` — switches skin at runtime (used by `/skin` command)
|
| 390 |
+
- `load_skin(name)` — loads from user skins first, then built-ins, then falls back to default
|
| 391 |
+
- Missing skin values inherit from the `default` skin automatically
|
| 392 |
+
|
| 393 |
+
### What skins customize
|
| 394 |
+
|
| 395 |
+
| Element | Skin Key | Used By |
|
| 396 |
+
|---------|----------|---------|
|
| 397 |
+
| Banner panel border | `colors.banner_border` | `banner.py` |
|
| 398 |
+
| Banner panel title | `colors.banner_title` | `banner.py` |
|
| 399 |
+
| Banner section headers | `colors.banner_accent` | `banner.py` |
|
| 400 |
+
| Banner dim text | `colors.banner_dim` | `banner.py` |
|
| 401 |
+
| Banner body text | `colors.banner_text` | `banner.py` |
|
| 402 |
+
| Response box border | `colors.response_border` | `cli.py` |
|
| 403 |
+
| Spinner faces (waiting) | `spinner.waiting_faces` | `display.py` |
|
| 404 |
+
| Spinner faces (thinking) | `spinner.thinking_faces` | `display.py` |
|
| 405 |
+
| Spinner verbs | `spinner.thinking_verbs` | `display.py` |
|
| 406 |
+
| Spinner wings (optional) | `spinner.wings` | `display.py` |
|
| 407 |
+
| Tool output prefix | `tool_prefix` | `display.py` |
|
| 408 |
+
| Per-tool emojis | `tool_emojis` | `display.py` → `get_tool_emoji()` |
|
| 409 |
+
| Agent name | `branding.agent_name` | `banner.py`, `cli.py` |
|
| 410 |
+
| Welcome message | `branding.welcome` | `cli.py` |
|
| 411 |
+
| Response box label | `branding.response_label` | `cli.py` |
|
| 412 |
+
| Prompt symbol | `branding.prompt_symbol` | `cli.py` |
|
| 413 |
+
|
| 414 |
+
### Built-in skins
|
| 415 |
+
|
| 416 |
+
- `default` — Classic Hermes gold/kawaii (the current look)
|
| 417 |
+
- `ares` — Crimson/bronze war-god theme with custom spinner wings
|
| 418 |
+
- `mono` — Clean grayscale monochrome
|
| 419 |
+
- `slate` — Cool blue developer-focused theme
|
| 420 |
+
|
| 421 |
+
### Adding a built-in skin
|
| 422 |
+
|
| 423 |
+
Add to `_BUILTIN_SKINS` dict in `hermes_cli/skin_engine.py`:
|
| 424 |
+
|
| 425 |
+
```python
|
| 426 |
+
"mytheme": {
|
| 427 |
+
"name": "mytheme",
|
| 428 |
+
"description": "Short description",
|
| 429 |
+
"colors": { ... },
|
| 430 |
+
"spinner": { ... },
|
| 431 |
+
"branding": { ... },
|
| 432 |
+
"tool_prefix": "┊",
|
| 433 |
+
},
|
| 434 |
+
```
|
| 435 |
+
|
| 436 |
+
### User skins (YAML)
|
| 437 |
+
|
| 438 |
+
Users create `~/.hermes/skins/<name>.yaml`:
|
| 439 |
+
|
| 440 |
+
```yaml
|
| 441 |
+
name: cyberpunk
|
| 442 |
+
description: Neon-soaked terminal theme
|
| 443 |
+
|
| 444 |
+
colors:
|
| 445 |
+
banner_border: "#FF00FF"
|
| 446 |
+
banner_title: "#00FFFF"
|
| 447 |
+
banner_accent: "#FF1493"
|
| 448 |
+
|
| 449 |
+
spinner:
|
| 450 |
+
thinking_verbs: ["jacking in", "decrypting", "uploading"]
|
| 451 |
+
wings:
|
| 452 |
+
- ["⟨⚡", "⚡⟩"]
|
| 453 |
+
|
| 454 |
+
branding:
|
| 455 |
+
agent_name: "Cyber Agent"
|
| 456 |
+
response_label: " ⚡ Cyber "
|
| 457 |
+
|
| 458 |
+
tool_prefix: "▏"
|
| 459 |
+
```
|
| 460 |
+
|
| 461 |
+
Activate with `/skin cyberpunk` or `display.skin: cyberpunk` in config.yaml.
|
| 462 |
+
|
| 463 |
+
---
|
| 464 |
+
|
| 465 |
+
## Plugins
|
| 466 |
+
|
| 467 |
+
Hermes has two plugin surfaces. Both live under `plugins/` in the repo so
|
| 468 |
+
repo-shipped plugins can be discovered alongside user-installed ones in
|
| 469 |
+
`~/.hermes/plugins/` and pip-installed entry points.
|
| 470 |
+
|
| 471 |
+
### General plugins (`hermes_cli/plugins.py` + `plugins/<name>/`)
|
| 472 |
+
|
| 473 |
+
`PluginManager` discovers plugins from `~/.hermes/plugins/`, `./.hermes/plugins/`,
|
| 474 |
+
and pip entry points. Each plugin exposes a `register(ctx)` function that
|
| 475 |
+
can:
|
| 476 |
+
|
| 477 |
+
- Register Python-callback lifecycle hooks:
|
| 478 |
+
`pre_tool_call`, `post_tool_call`, `pre_llm_call`, `post_llm_call`,
|
| 479 |
+
`on_session_start`, `on_session_end`
|
| 480 |
+
- Register new tools via `ctx.register_tool(...)`
|
| 481 |
+
- Register CLI subcommands via `ctx.register_cli_command(...)` — the
|
| 482 |
+
plugin's argparse tree is wired into `hermes` at startup so
|
| 483 |
+
`hermes <pluginname> <subcmd>` works with no change to `main.py`
|
| 484 |
+
|
| 485 |
+
Hooks are invoked from `model_tools.py` (pre/post tool) and `run_agent.py`
|
| 486 |
+
(lifecycle). **Discovery timing pitfall:** `discover_plugins()` only runs
|
| 487 |
+
as a side effect of importing `model_tools.py`. Code paths that read plugin
|
| 488 |
+
state without importing `model_tools.py` first must call `discover_plugins()`
|
| 489 |
+
explicitly (it's idempotent).
|
| 490 |
+
|
| 491 |
+
### Memory-provider plugins (`plugins/memory/<name>/`)
|
| 492 |
+
|
| 493 |
+
Separate discovery system for pluggable memory backends. Current built-in
|
| 494 |
+
providers include **honcho, mem0, supermemory, byterover, hindsight,
|
| 495 |
+
holographic, openviking, retaindb**.
|
| 496 |
+
|
| 497 |
+
Each provider implements the `MemoryProvider` ABC (see `agent/memory_provider.py`)
|
| 498 |
+
and is orchestrated by `agent/memory_manager.py`. Lifecycle hooks include
|
| 499 |
+
`sync_turn(turn_messages)`, `prefetch(query)`, `shutdown()`, and optional
|
| 500 |
+
`post_setup(hermes_home, config)` for setup-wizard integration.
|
| 501 |
+
|
| 502 |
+
**CLI commands via `plugins/memory/<name>/cli.py`:** if a memory plugin
|
| 503 |
+
defines `register_cli(subparser)`, `discover_plugin_cli_commands()` finds
|
| 504 |
+
it at argparse setup time and wires it into `hermes <plugin>`. The
|
| 505 |
+
framework only exposes CLI commands for the **currently active** memory
|
| 506 |
+
provider (read from `memory.provider` in config.yaml), so disabled
|
| 507 |
+
providers don't clutter `hermes --help`.
|
| 508 |
+
|
| 509 |
+
**Rule (Teknium, May 2026):** plugins MUST NOT modify core files
|
| 510 |
+
(`run_agent.py`, `cli.py`, `gateway/run.py`, `hermes_cli/main.py`, etc.).
|
| 511 |
+
If a plugin needs a capability the framework doesn't expose, expand the
|
| 512 |
+
generic plugin surface (new hook, new ctx method) — never hardcode
|
| 513 |
+
plugin-specific logic into core. PR #5295 removed 95 lines of hardcoded
|
| 514 |
+
honcho argparse from `main.py` for exactly this reason.
|
| 515 |
+
|
| 516 |
+
### Model-provider plugins (`plugins/model-providers/<name>/`)
|
| 517 |
+
|
| 518 |
+
Every inference backend (openrouter, anthropic, gmi, deepseek, nvidia, …)
|
| 519 |
+
ships as a plugin here. Each plugin's `__init__.py` calls
|
| 520 |
+
`providers.register_provider(ProviderProfile(...))` at module load.
|
| 521 |
+
`providers/__init__.py._discover_providers()` is a **lazy, separate
|
| 522 |
+
discovery system** — scanned on first `get_provider_profile()` or
|
| 523 |
+
`list_providers()` call, NOT by the general PluginManager.
|
| 524 |
+
|
| 525 |
+
Scan order:
|
| 526 |
+
1. Bundled: `<repo>/plugins/model-providers/<name>/`
|
| 527 |
+
2. User: `$HERMES_HOME/plugins/model-providers/<name>/`
|
| 528 |
+
3. Legacy: `<repo>/providers/<name>.py` (back-compat)
|
| 529 |
+
|
| 530 |
+
User plugins of the same name override bundled ones — `register_provider()`
|
| 531 |
+
is last-writer-wins. This lets third parties swap out any built-in
|
| 532 |
+
profile without a repo patch.
|
| 533 |
+
|
| 534 |
+
The general PluginManager records `kind: model-provider` manifests but does
|
| 535 |
+
NOT import them (would double-instantiate `ProviderProfile`). Plugins
|
| 536 |
+
without an explicit `kind:` get auto-coerced via a source-text heuristic
|
| 537 |
+
(`register_provider` + `ProviderProfile` in `__init__.py`).
|
| 538 |
+
|
| 539 |
+
Full authoring guide: `website/docs/developer-guide/model-provider-plugin.md`.
|
| 540 |
+
|
| 541 |
+
### Dashboard / context-engine / image-gen plugin directories
|
| 542 |
+
|
| 543 |
+
`plugins/context_engine/`, `plugins/image_gen/`, `plugins/example-dashboard/`,
|
| 544 |
+
etc. follow the same pattern (ABC + orchestrator + per-plugin directory).
|
| 545 |
+
Context engines plug into `agent/context_engine.py`; image-gen providers
|
| 546 |
+
into `agent/image_gen_provider.py`.
|
| 547 |
+
|
| 548 |
+
---
|
| 549 |
+
|
| 550 |
+
## Skills
|
| 551 |
+
|
| 552 |
+
Two parallel surfaces:
|
| 553 |
+
|
| 554 |
+
- **`skills/`** — built-in skills shipped and loadable by default.
|
| 555 |
+
Organized by category directories (e.g. `skills/github/`, `skills/mlops/`).
|
| 556 |
+
- **`optional-skills/`** — heavier or niche skills shipped with the repo but
|
| 557 |
+
NOT active by default. Installed explicitly via
|
| 558 |
+
`hermes skills install official/<category>/<skill>`. Adapter lives in
|
| 559 |
+
`tools/skills_hub.py` (`OptionalSkillSource`). Categories include
|
| 560 |
+
`autonomous-ai-agents`, `blockchain`, `communication`, `creative`,
|
| 561 |
+
`devops`, `email`, `health`, `mcp`, `migration`, `mlops`, `productivity`,
|
| 562 |
+
`research`, `security`, `web-development`.
|
| 563 |
+
|
| 564 |
+
When reviewing skill PRs, check which directory they target — heavy-dep or
|
| 565 |
+
niche skills belong in `optional-skills/`.
|
| 566 |
+
|
| 567 |
+
### SKILL.md frontmatter
|
| 568 |
+
|
| 569 |
+
Standard fields: `name`, `description`, `version`, `author`, `license`,
|
| 570 |
+
`platforms` (OS-gating list: `[macos]`, `[linux, macos]`, ...),
|
| 571 |
+
`metadata.hermes.tags`, `metadata.hermes.category`,
|
| 572 |
+
`metadata.hermes.related_skills`, `metadata.hermes.config` (config.yaml
|
| 573 |
+
settings the skill needs — stored under `skills.config.<key>`, prompted
|
| 574 |
+
during setup, injected at load time).
|
| 575 |
+
|
| 576 |
+
Top-level `tags:` and `category:` are also accepted and mirrored from
|
| 577 |
+
`metadata.hermes.*` by the loader.
|
| 578 |
+
|
| 579 |
+
---
|
| 580 |
+
|
| 581 |
+
## Toolsets
|
| 582 |
+
|
| 583 |
+
All toolsets are defined in `toolsets.py` as a single `TOOLSETS` dict.
|
| 584 |
+
Each platform's adapter picks a base toolset (e.g. Telegram uses
|
| 585 |
+
`"messaging"`); `_HERMES_CORE_TOOLS` is the default bundle most
|
| 586 |
+
platforms inherit from.
|
| 587 |
+
|
| 588 |
+
Current toolset keys: `browser`, `clarify`, `code_execution`, `cronjob`,
|
| 589 |
+
`debugging`, `delegation`, `discord`, `discord_admin`, `feishu_doc`,
|
| 590 |
+
`feishu_drive`, `file`, `homeassistant`, `image_gen`, `kanban`, `memory`,
|
| 591 |
+
`messaging`, `moa`, `rl`, `safe`, `search`, `session_search`, `skills`,
|
| 592 |
+
`spotify`, `terminal`, `todo`, `tts`, `video`, `vision`, `web`, `yuanbao`.
|
| 593 |
+
|
| 594 |
+
Enable/disable per platform via `hermes tools` (the curses UI) or the
|
| 595 |
+
`tools.<platform>.enabled` / `tools.<platform>.disabled` lists in
|
| 596 |
+
`config.yaml`.
|
| 597 |
+
|
| 598 |
+
---
|
| 599 |
+
|
| 600 |
+
## Delegation (`delegate_task`)
|
| 601 |
+
|
| 602 |
+
`tools/delegate_tool.py` spawns a subagent with an isolated
|
| 603 |
+
context + terminal session. Synchronous: the parent waits for the
|
| 604 |
+
child's summary before continuing its own loop — if the parent is
|
| 605 |
+
interrupted, the child is cancelled.
|
| 606 |
+
|
| 607 |
+
Two shapes:
|
| 608 |
+
|
| 609 |
+
- **Single:** pass `goal` (+ optional `context`, `toolsets`).
|
| 610 |
+
- **Batch (parallel):** pass `tasks: [...]` — each gets its own subagent
|
| 611 |
+
running concurrently. Concurrency is capped by
|
| 612 |
+
`delegation.max_concurrent_children` (default 3).
|
| 613 |
+
|
| 614 |
+
Roles:
|
| 615 |
+
|
| 616 |
+
- `role="leaf"` (default) — focused worker. Cannot call `delegate_task`,
|
| 617 |
+
`clarify`, `memory`, `send_message`, `execute_code`.
|
| 618 |
+
- `role="orchestrator"` — retains `delegate_task` so it can spawn its
|
| 619 |
+
own workers. Gated by `delegation.orchestrator_enabled` (default true)
|
| 620 |
+
and bounded by `delegation.max_spawn_depth` (default 2).
|
| 621 |
+
|
| 622 |
+
Key config knobs (under `delegation:` in `config.yaml`):
|
| 623 |
+
`max_concurrent_children`, `max_spawn_depth`, `child_timeout_seconds`,
|
| 624 |
+
`orchestrator_enabled`, `subagent_auto_approve`, `inherit_mcp_toolsets`,
|
| 625 |
+
`max_iterations`.
|
| 626 |
+
|
| 627 |
+
Synchronicity rule: delegate_task is **not** durable. For long-running
|
| 628 |
+
work that must outlive the current turn, use `cronjob` or
|
| 629 |
+
`terminal(background=True, notify_on_complete=True)` instead.
|
| 630 |
+
|
| 631 |
+
---
|
| 632 |
+
|
| 633 |
+
## Curator (skill lifecycle)
|
| 634 |
+
|
| 635 |
+
Background skill-maintenance system that tracks usage on agent-created
|
| 636 |
+
skills and auto-archives stale ones. Users never lose skills; archives
|
| 637 |
+
go to `~/.hermes/skills/.archive/` and are restorable.
|
| 638 |
+
|
| 639 |
+
- **Core:** `agent/curator.py` (review loop, auto-transitions, LLM review
|
| 640 |
+
prompt) + `agent/curator_backup.py` (pre-run tar.gz snapshots).
|
| 641 |
+
- **CLI:** `hermes_cli/curator.py` wires `hermes curator <verb>` where
|
| 642 |
+
verbs are: `status`, `run`, `pause`, `resume`, `pin`, `unpin`,
|
| 643 |
+
`archive`, `restore`, `prune`, `backup`, `rollback`.
|
| 644 |
+
- **Telemetry:** `tools/skill_usage.py` owns the sidecar
|
| 645 |
+
`~/.hermes/skills/.usage.json` — per-skill `use_count`, `view_count`,
|
| 646 |
+
`patch_count`, `last_activity_at`, `state` (active / stale /
|
| 647 |
+
archived), `pinned`.
|
| 648 |
+
|
| 649 |
+
Invariants:
|
| 650 |
+
- Curator only touches skills with `created_by: "agent"` provenance —
|
| 651 |
+
bundled + hub-installed skills are off-limits.
|
| 652 |
+
- Never deletes; max destructive action is archive.
|
| 653 |
+
- Pinned skills are exempt from every auto-transition and from the
|
| 654 |
+
LLM review pass.
|
| 655 |
+
- `skill_manage(action="delete")` refuses pinned skills; patch/edit/
|
| 656 |
+
write_file/remove_file go through so the agent can keep improving
|
| 657 |
+
pinned skills.
|
| 658 |
+
|
| 659 |
+
Config section (`curator:` in `config.yaml`):
|
| 660 |
+
`enabled`, `interval_hours`, `min_idle_hours`, `stale_after_days`,
|
| 661 |
+
`archive_after_days`, `backup.*`.
|
| 662 |
+
|
| 663 |
+
Full user-facing docs: `website/docs/user-guide/features/curator.md`.
|
| 664 |
+
|
| 665 |
+
---
|
| 666 |
+
|
| 667 |
+
## Cron (scheduled jobs)
|
| 668 |
+
|
| 669 |
+
`cron/jobs.py` (job store) + `cron/scheduler.py` (tick loop). Agents
|
| 670 |
+
schedule jobs via the `cronjob` tool; users via `hermes cron <verb>`
|
| 671 |
+
(`list`, `add`, `edit`, `pause`, `resume`, `run`, `remove`) or the
|
| 672 |
+
`/cron` slash command.
|
| 673 |
+
|
| 674 |
+
Supported schedule formats:
|
| 675 |
+
- Duration: `"30m"`, `"2h"`, `"1d"`
|
| 676 |
+
- "every" phrase: `"every 2h"`, `"every monday 9am"`
|
| 677 |
+
- 5-field cron expression: `"0 9 * * *"`
|
| 678 |
+
- ISO timestamp (one-shot): `"2026-06-01T09:00:00Z"`
|
| 679 |
+
|
| 680 |
+
Per-job fields include `skills` (load specific skills), `model` /
|
| 681 |
+
`provider` overrides, `script` (pre-run data-collection script whose
|
| 682 |
+
stdout is injected into the prompt; `no_agent=True` turns the script
|
| 683 |
+
into the entire job), `context_from` (chain job A's last output into
|
| 684 |
+
job B's prompt), `workdir` (run in a specific directory with its
|
| 685 |
+
`AGENTS.md`/`CLAUDE.md` loaded), and multi-platform delivery.
|
| 686 |
+
|
| 687 |
+
Hardening invariants:
|
| 688 |
+
- **3-minute hard interrupt** on cron sessions — runaway agent loops
|
| 689 |
+
cannot monopolize the scheduler.
|
| 690 |
+
- Catchup window: half the job's period, clamped to 120s–2h.
|
| 691 |
+
- Grace window: 120s for one-shot jobs whose fire time was missed.
|
| 692 |
+
- File lock at `~/.hermes/cron/.tick.lock` prevents duplicate ticks
|
| 693 |
+
across processes.
|
| 694 |
+
- Cron sessions pass `skip_memory=True` by default; memory providers
|
| 695 |
+
intentionally do not run during cron.
|
| 696 |
+
|
| 697 |
+
Cron deliveries are **not** mirrored into the target gateway session —
|
| 698 |
+
they land in their own cron session with a header/footer frame so the
|
| 699 |
+
main conversation's message-role alternation stays intact.
|
| 700 |
+
|
| 701 |
+
---
|
| 702 |
+
|
| 703 |
+
## Kanban (multi-agent work queue)
|
| 704 |
+
|
| 705 |
+
Durable SQLite-backed board that lets multiple profiles / workers
|
| 706 |
+
collaborate on shared tasks. Users drive it via `hermes kanban <verb>`;
|
| 707 |
+
workers spawned by the dispatcher drive it via a dedicated `kanban_*`
|
| 708 |
+
toolset so their schema footprint is zero when they're not inside a
|
| 709 |
+
kanban task.
|
| 710 |
+
|
| 711 |
+
- **CLI:** `hermes_cli/kanban.py` wires `hermes kanban` with verbs
|
| 712 |
+
`init`, `create`, `list` (alias `ls`), `show`, `assign`, `link`,
|
| 713 |
+
`unlink`, `comment`, `complete`, `block`, `unblock`, `archive`,
|
| 714 |
+
`tail`, plus less-commonly-used `watch`, `stats`, `runs`, `log`,
|
| 715 |
+
`assignees`, `heartbeat`, `notify-*`, `dispatch`, `daemon`, `gc`.
|
| 716 |
+
- **Worker toolset:** `tools/kanban_tools.py` exposes `kanban_show`,
|
| 717 |
+
`kanban_complete`, `kanban_block`, `kanban_heartbeat`, `kanban_comment`,
|
| 718 |
+
`kanban_create`, `kanban_link` — gated by `HERMES_KANBAN_TASK` so
|
| 719 |
+
the schema only appears for processes actually running as a worker.
|
| 720 |
+
- **Dispatcher:** long-lived loop that (default every 60s) reclaims
|
| 721 |
+
stale claims, promotes ready tasks, atomically claims, and spawns
|
| 722 |
+
assigned profiles. Runs **inside the gateway** by default via
|
| 723 |
+
`kanban.dispatch_in_gateway: true`.
|
| 724 |
+
- **Plugin assets:** `plugins/kanban/dashboard/` (web UI) +
|
| 725 |
+
`plugins/kanban/systemd/` (`hermes-kanban-dispatcher.service` for
|
| 726 |
+
standalone dispatcher deployment).
|
| 727 |
+
|
| 728 |
+
Isolation model:
|
| 729 |
+
- **Board** is the hard boundary — workers are spawned with
|
| 730 |
+
`HERMES_KANBAN_BOARD` pinned in their env so they can't see other
|
| 731 |
+
boards.
|
| 732 |
+
- **Tenant** is a soft namespace *within* a board — one specialist
|
| 733 |
+
fleet can serve multiple businesses with workspace-path + memory-key
|
| 734 |
+
isolation.
|
| 735 |
+
- After ~5 consecutive spawn failures on the same task the dispatcher
|
| 736 |
+
auto-blocks it to prevent spin loops.
|
| 737 |
+
|
| 738 |
+
Full user-facing docs: `website/docs/user-guide/features/kanban.md`.
|
| 739 |
+
|
| 740 |
+
---
|
| 741 |
+
|
| 742 |
+
## Important Policies
|
| 743 |
+
|
| 744 |
+
### Prompt Caching Must Not Break
|
| 745 |
+
|
| 746 |
+
Hermes-Agent ensures caching remains valid throughout a conversation. **Do NOT implement changes that would:**
|
| 747 |
+
- Alter past context mid-conversation
|
| 748 |
+
- Change toolsets mid-conversation
|
| 749 |
+
- Reload memories or rebuild system prompts mid-conversation
|
| 750 |
+
|
| 751 |
+
Cache-breaking forces dramatically higher costs. The ONLY time we alter context is during context compression.
|
| 752 |
+
|
| 753 |
+
Slash commands that mutate system-prompt state (skills, tools, memory, etc.)
|
| 754 |
+
must be **cache-aware**: default to deferred invalidation (change takes
|
| 755 |
+
effect next session), with an opt-in `--now` flag for immediate
|
| 756 |
+
invalidation. See `/skills install --now` for the canonical pattern.
|
| 757 |
+
|
| 758 |
+
### Background Process Notifications (Gateway)
|
| 759 |
+
|
| 760 |
+
When `terminal(background=true, notify_on_complete=true)` is used, the gateway runs a watcher that
|
| 761 |
+
detects process completion and triggers a new agent turn. Control verbosity of background process
|
| 762 |
+
messages with `display.background_process_notifications`
|
| 763 |
+
in config.yaml (or `HERMES_BACKGROUND_NOTIFICATIONS` env var):
|
| 764 |
+
|
| 765 |
+
- `all` — running-output updates + final message (default)
|
| 766 |
+
- `result` — only the final completion message
|
| 767 |
+
- `error` — only the final message when exit code != 0
|
| 768 |
+
- `off` — no watcher messages at all
|
| 769 |
+
|
| 770 |
+
---
|
| 771 |
+
|
| 772 |
+
## Profiles: Multi-Instance Support
|
| 773 |
+
|
| 774 |
+
Hermes supports **profiles** — multiple fully isolated instances, each with its own
|
| 775 |
+
`HERMES_HOME` directory (config, API keys, memory, sessions, skills, gateway, etc.).
|
| 776 |
+
|
| 777 |
+
The core mechanism: `_apply_profile_override()` in `hermes_cli/main.py` sets
|
| 778 |
+
`HERMES_HOME` before any module imports. All `get_hermes_home()` references
|
| 779 |
+
automatically scope to the active profile.
|
| 780 |
+
|
| 781 |
+
### Rules for profile-safe code
|
| 782 |
+
|
| 783 |
+
1. **Use `get_hermes_home()` for all HERMES_HOME paths.** Import from `hermes_constants`.
|
| 784 |
+
NEVER hardcode `~/.hermes` or `Path.home() / ".hermes"` in code that reads/writes state.
|
| 785 |
+
```python
|
| 786 |
+
# GOOD
|
| 787 |
+
from hermes_constants import get_hermes_home
|
| 788 |
+
config_path = get_hermes_home() / "config.yaml"
|
| 789 |
+
|
| 790 |
+
# BAD — breaks profiles
|
| 791 |
+
config_path = Path.home() / ".hermes" / "config.yaml"
|
| 792 |
+
```
|
| 793 |
+
|
| 794 |
+
2. **Use `display_hermes_home()` for user-facing messages.** Import from `hermes_constants`.
|
| 795 |
+
This returns `~/.hermes` for default or `~/.hermes/profiles/<name>` for profiles.
|
| 796 |
+
```python
|
| 797 |
+
# GOOD
|
| 798 |
+
from hermes_constants import display_hermes_home
|
| 799 |
+
print(f"Config saved to {display_hermes_home()}/config.yaml")
|
| 800 |
+
|
| 801 |
+
# BAD — shows wrong path for profiles
|
| 802 |
+
print("Config saved to ~/.hermes/config.yaml")
|
| 803 |
+
```
|
| 804 |
+
|
| 805 |
+
3. **Module-level constants are fine** — they cache `get_hermes_home()` at import time,
|
| 806 |
+
which is AFTER `_apply_profile_override()` sets the env var. Just use `get_hermes_home()`,
|
| 807 |
+
not `Path.home() / ".hermes"`.
|
| 808 |
+
|
| 809 |
+
4. **Tests that mock `Path.home()` must also set `HERMES_HOME`** — since code now uses
|
| 810 |
+
`get_hermes_home()` (reads env var), not `Path.home() / ".hermes"`:
|
| 811 |
+
```python
|
| 812 |
+
with patch.object(Path, "home", return_value=tmp_path), \
|
| 813 |
+
patch.dict(os.environ, {"HERMES_HOME": str(tmp_path / ".hermes")}):
|
| 814 |
+
...
|
| 815 |
+
```
|
| 816 |
+
|
| 817 |
+
5. **Gateway platform adapters should use token locks** — if the adapter connects with
|
| 818 |
+
a unique credential (bot token, API key), call `acquire_scoped_lock()` from
|
| 819 |
+
`gateway.status` in the `connect()`/`start()` method and `release_scoped_lock()` in
|
| 820 |
+
`disconnect()`/`stop()`. This prevents two profiles from using the same credential.
|
| 821 |
+
See `gateway/platforms/telegram.py` for the canonical pattern.
|
| 822 |
+
|
| 823 |
+
6. **Profile operations are HOME-anchored, not HERMES_HOME-anchored** — `_get_profiles_root()`
|
| 824 |
+
returns `Path.home() / ".hermes" / "profiles"`, NOT `get_hermes_home() / "profiles"`.
|
| 825 |
+
This is intentional — it lets `hermes -p coder profile list` see all profiles regardless
|
| 826 |
+
of which one is active.
|
| 827 |
+
|
| 828 |
+
## Known Pitfalls
|
| 829 |
+
|
| 830 |
+
### DO NOT hardcode `~/.hermes` paths
|
| 831 |
+
Use `get_hermes_home()` from `hermes_constants` for code paths. Use `display_hermes_home()`
|
| 832 |
+
for user-facing print/log messages. Hardcoding `~/.hermes` breaks profiles — each profile
|
| 833 |
+
has its own `HERMES_HOME` directory. This was the source of 5 bugs fixed in PR #3575.
|
| 834 |
+
|
| 835 |
+
### DO NOT introduce new `simple_term_menu` usage
|
| 836 |
+
Existing call sites in `hermes_cli/main.py` remain for legacy fallback only;
|
| 837 |
+
the preferred UI is curses (stdlib) because `simple_term_menu` has
|
| 838 |
+
ghost-duplication rendering bugs in tmux/iTerm2 with arrow keys. New
|
| 839 |
+
interactive menus must use `hermes_cli/curses_ui.py` — see
|
| 840 |
+
`hermes_cli/tools_config.py` for the canonical pattern.
|
| 841 |
+
|
| 842 |
+
### DO NOT use `\033[K` (ANSI erase-to-EOL) in spinner/display code
|
| 843 |
+
Leaks as literal `?[K` text under `prompt_toolkit`'s `patch_stdout`. Use space-padding: `f"\r{line}{' ' * pad}"`.
|
| 844 |
+
|
| 845 |
+
### `_last_resolved_tool_names` is a process-global in `model_tools.py`
|
| 846 |
+
`_run_single_child()` in `delegate_tool.py` saves and restores this global around subagent execution. If you add new code that reads this global, be aware it may be temporarily stale during child agent runs.
|
| 847 |
+
|
| 848 |
+
### DO NOT hardcode cross-tool references in schema descriptions
|
| 849 |
+
Tool schema descriptions must not mention tools from other toolsets by name (e.g., `browser_navigate` saying "prefer web_search"). Those tools may be unavailable (missing API keys, disabled toolset), causing the model to hallucinate calls to non-existent tools. If a cross-reference is needed, add it dynamically in `get_tool_definitions()` in `model_tools.py` — see the `browser_navigate` / `execute_code` post-processing blocks for the pattern.
|
| 850 |
+
|
| 851 |
+
### The gateway has TWO message guards — both must bypass approval/control commands
|
| 852 |
+
When an agent is running, messages pass through two sequential guards:
|
| 853 |
+
(1) **base adapter** (`gateway/platforms/base.py`) queues messages in
|
| 854 |
+
`_pending_messages` when `session_key in self._active_sessions`, and
|
| 855 |
+
(2) **gateway runner** (`gateway/run.py`) intercepts `/stop`, `/new`,
|
| 856 |
+
`/queue`, `/status`, `/approve`, `/deny` before they reach
|
| 857 |
+
`running_agent.interrupt()`. Any new command that must reach the runner
|
| 858 |
+
while the agent is blocked (e.g. approval prompts) MUST bypass BOTH
|
| 859 |
+
guards and be dispatched inline, not via `_process_message_background()`
|
| 860 |
+
(which races session lifecycle).
|
| 861 |
+
|
| 862 |
+
### Squash merges from stale branches silently revert recent fixes
|
| 863 |
+
Before squash-merging a PR, ensure the branch is up to date with `main`
|
| 864 |
+
(`git fetch origin main && git reset --hard origin/main` in the worktree,
|
| 865 |
+
then re-apply the PR's commits). A stale branch's version of an unrelated
|
| 866 |
+
file will silently overwrite recent fixes on main when squashed. Verify
|
| 867 |
+
with `git diff HEAD~1..HEAD` after merging — unexpected deletions are a
|
| 868 |
+
red flag.
|
| 869 |
+
|
| 870 |
+
### Don't wire in dead code without E2E validation
|
| 871 |
+
Unused code that was never shipped was dead for a reason. Before wiring an
|
| 872 |
+
unused module into a live code path, E2E test the real resolution chain
|
| 873 |
+
with actual imports (not mocks) against a temp `HERMES_HOME`.
|
| 874 |
+
|
| 875 |
+
### Tests must not write to `~/.hermes/`
|
| 876 |
+
The `_isolate_hermes_home` autouse fixture in `tests/conftest.py` redirects `HERMES_HOME` to a temp dir. Never hardcode `~/.hermes/` paths in tests.
|
| 877 |
+
|
| 878 |
+
**Profile tests**: When testing profile features, also mock `Path.home()` so that
|
| 879 |
+
`_get_profiles_root()` and `_get_default_hermes_home()` resolve within the temp dir.
|
| 880 |
+
Use the pattern from `tests/hermes_cli/test_profiles.py`:
|
| 881 |
+
```python
|
| 882 |
+
@pytest.fixture
|
| 883 |
+
def profile_env(tmp_path, monkeypatch):
|
| 884 |
+
home = tmp_path / ".hermes"
|
| 885 |
+
home.mkdir()
|
| 886 |
+
monkeypatch.setattr(Path, "home", lambda: tmp_path)
|
| 887 |
+
monkeypatch.setenv("HERMES_HOME", str(home))
|
| 888 |
+
return home
|
| 889 |
+
```
|
| 890 |
+
|
| 891 |
+
---
|
| 892 |
+
|
| 893 |
+
## Testing
|
| 894 |
+
|
| 895 |
+
**ALWAYS use `scripts/run_tests.sh`** — do not call `pytest` directly. The script enforces
|
| 896 |
+
hermetic environment parity with CI (unset credential vars, TZ=UTC, LANG=C.UTF-8,
|
| 897 |
+
4 xdist workers matching GHA ubuntu-latest). Direct `pytest` on a 16+ core
|
| 898 |
+
developer machine with API keys set diverges from CI in ways that have caused
|
| 899 |
+
multiple "works locally, fails in CI" incidents (and the reverse).
|
| 900 |
+
|
| 901 |
+
```bash
|
| 902 |
+
scripts/run_tests.sh # full suite, CI-parity
|
| 903 |
+
scripts/run_tests.sh tests/gateway/ # one directory
|
| 904 |
+
scripts/run_tests.sh tests/agent/test_foo.py::test_x # one test
|
| 905 |
+
scripts/run_tests.sh -v --tb=long # pass-through pytest flags
|
| 906 |
+
```
|
| 907 |
+
|
| 908 |
+
### Why the wrapper (and why the old "just call pytest" doesn't work)
|
| 909 |
+
|
| 910 |
+
Five real sources of local-vs-CI drift the script closes:
|
| 911 |
+
|
| 912 |
+
| | Without wrapper | With wrapper |
|
| 913 |
+
|---|---|---|
|
| 914 |
+
| Provider API keys | Whatever is in your env (auto-detects pool) | All `*_API_KEY`/`*_TOKEN`/etc. unset |
|
| 915 |
+
| HOME / `~/.hermes/` | Your real config+auth.json | Temp dir per test |
|
| 916 |
+
| Timezone | Local TZ (PDT etc.) | UTC |
|
| 917 |
+
| Locale | Whatever is set | C.UTF-8 |
|
| 918 |
+
| xdist workers | `-n auto` = all cores (20+ on a workstation) | `-n 4` matching CI |
|
| 919 |
+
|
| 920 |
+
`tests/conftest.py` also enforces points 1-4 as an autouse fixture so ANY pytest
|
| 921 |
+
invocation (including IDE integrations) gets hermetic behavior — but the wrapper
|
| 922 |
+
is belt-and-suspenders.
|
| 923 |
+
|
| 924 |
+
### Running without the wrapper (only if you must)
|
| 925 |
+
|
| 926 |
+
If you can't use the wrapper (e.g. on Windows or inside an IDE that shells
|
| 927 |
+
pytest directly), at minimum activate the venv and pass `-n 4`:
|
| 928 |
+
|
| 929 |
+
```bash
|
| 930 |
+
source .venv/bin/activate # or: source venv/bin/activate
|
| 931 |
+
python -m pytest tests/ -q -n 4
|
| 932 |
+
```
|
| 933 |
+
|
| 934 |
+
Worker count above 4 will surface test-ordering flakes that CI never sees.
|
| 935 |
+
|
| 936 |
+
Always run the full suite before pushing changes.
|
| 937 |
+
|
| 938 |
+
### Don't write change-detector tests
|
| 939 |
+
|
| 940 |
+
A test is a **change-detector** if it fails whenever data that is **expected
|
| 941 |
+
to change** gets updated — model catalogs, config version numbers,
|
| 942 |
+
enumeration counts, hardcoded lists of provider models. These tests add no
|
| 943 |
+
behavioral coverage; they just guarantee that routine source updates break
|
| 944 |
+
CI and cost engineering time to "fix."
|
| 945 |
+
|
| 946 |
+
**Do not write:**
|
| 947 |
+
|
| 948 |
+
```python
|
| 949 |
+
# catalog snapshot — breaks every model release
|
| 950 |
+
assert "gemini-2.5-pro" in _PROVIDER_MODELS["gemini"]
|
| 951 |
+
assert "MiniMax-M2.7" in models
|
| 952 |
+
|
| 953 |
+
# config version literal — breaks every schema bump
|
| 954 |
+
assert DEFAULT_CONFIG["_config_version"] == 21
|
| 955 |
+
|
| 956 |
+
# enumeration count — breaks every time a skill/provider is added
|
| 957 |
+
assert len(_PROVIDER_MODELS["huggingface"]) == 8
|
| 958 |
+
```
|
| 959 |
+
|
| 960 |
+
**Do write:**
|
| 961 |
+
|
| 962 |
+
```python
|
| 963 |
+
# behavior: does the catalog plumbing work at all?
|
| 964 |
+
assert "gemini" in _PROVIDER_MODELS
|
| 965 |
+
assert len(_PROVIDER_MODELS["gemini"]) >= 1
|
| 966 |
+
|
| 967 |
+
# behavior: does migration bump the user's version to current latest?
|
| 968 |
+
assert raw["_config_version"] == DEFAULT_CONFIG["_config_version"]
|
| 969 |
+
|
| 970 |
+
# invariant: no plan-only model leaks into the legacy list
|
| 971 |
+
assert not (set(moonshot_models) & coding_plan_only_models)
|
| 972 |
+
|
| 973 |
+
# invariant: every model in the catalog has a context-length entry
|
| 974 |
+
for m in _PROVIDER_MODELS["huggingface"]:
|
| 975 |
+
assert m.lower() in DEFAULT_CONTEXT_LENGTHS_LOWER
|
| 976 |
+
```
|
| 977 |
+
|
| 978 |
+
The rule: if the test reads like a snapshot of current data, delete it. If
|
| 979 |
+
it reads like a contract about how two pieces of data must relate, keep it.
|
| 980 |
+
When a PR adds a new provider/model and you want a test, make the test
|
| 981 |
+
assert the relationship (e.g. "catalog entries all have context lengths"),
|
| 982 |
+
not the specific names.
|
| 983 |
+
|
| 984 |
+
Reviewers should reject new change-detector tests; authors should convert
|
| 985 |
+
them into invariants before re-requesting review.
|
CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,815 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Contributing to Hermes Agent
|
| 2 |
+
|
| 3 |
+
Thank you for contributing to Hermes Agent! This guide covers everything you need: setting up your dev environment, understanding the architecture, deciding what to build, and getting your PR merged.
|
| 4 |
+
|
| 5 |
+
---
|
| 6 |
+
|
| 7 |
+
## Contribution Priorities
|
| 8 |
+
|
| 9 |
+
We value contributions in this order:
|
| 10 |
+
|
| 11 |
+
1. **Bug fixes** — crashes, incorrect behavior, data loss. Always top priority.
|
| 12 |
+
2. **Cross-platform compatibility** — macOS, different Linux distros, and WSL2 on Windows. We want Hermes to work everywhere.
|
| 13 |
+
3. **Security hardening** — shell injection, prompt injection, path traversal, privilege escalation. See [Security](#security-considerations).
|
| 14 |
+
4. **Performance and robustness** — retry logic, error handling, graceful degradation.
|
| 15 |
+
5. **New skills** — but only broadly useful ones. See [Should it be a Skill or a Tool?](#should-it-be-a-skill-or-a-tool)
|
| 16 |
+
6. **New tools** — rarely needed. Most capabilities should be skills. See below.
|
| 17 |
+
7. **Documentation** — fixes, clarifications, new examples.
|
| 18 |
+
|
| 19 |
+
---
|
| 20 |
+
|
| 21 |
+
## Should it be a Skill or a Tool?
|
| 22 |
+
|
| 23 |
+
This is the most common question for new contributors. The answer is almost always **skill**.
|
| 24 |
+
|
| 25 |
+
### Make it a Skill when:
|
| 26 |
+
|
| 27 |
+
- The capability can be expressed as instructions + shell commands + existing tools
|
| 28 |
+
- It wraps an external CLI or API that the agent can call via `terminal` or `web_extract`
|
| 29 |
+
- It doesn't need custom Python integration or API key management baked into the agent
|
| 30 |
+
- Examples: arXiv search, git workflows, Docker management, PDF processing, email via CLI tools
|
| 31 |
+
|
| 32 |
+
### Make it a Tool when:
|
| 33 |
+
|
| 34 |
+
- It requires end-to-end integration with API keys, auth flows, or multi-component configuration managed by the agent harness
|
| 35 |
+
- It needs custom processing logic that must execute precisely every time (not "best effort" from LLM interpretation)
|
| 36 |
+
- It handles binary data, streaming, or real-time events that can't go through the terminal
|
| 37 |
+
- Examples: browser automation (Browserbase session management), TTS (audio encoding + platform delivery), vision analysis (base64 image handling)
|
| 38 |
+
|
| 39 |
+
### Should the Skill be bundled?
|
| 40 |
+
|
| 41 |
+
Bundled skills (in `skills/`) ship with every Hermes install. They should be **broadly useful to most users**:
|
| 42 |
+
|
| 43 |
+
- Document handling, web research, common dev workflows, system administration
|
| 44 |
+
- Used regularly by a wide range of people
|
| 45 |
+
|
| 46 |
+
If your skill is official and useful but not universally needed (e.g., a paid service integration, a heavyweight dependency), put it in **`optional-skills/`** — it ships with the repo but isn't activated by default. Users can discover it via `hermes skills browse` (labeled "official") and install it with `hermes skills install` (no third-party warning, builtin trust).
|
| 47 |
+
|
| 48 |
+
If your skill is specialized, community-contributed, or niche, it's better suited for a **Skills Hub** — upload it to a skills registry and share it in the [Nous Research Discord](https://discord.gg/NousResearch). Users can install it with `hermes skills install`.
|
| 49 |
+
|
| 50 |
+
---
|
| 51 |
+
|
| 52 |
+
## Development Setup
|
| 53 |
+
|
| 54 |
+
### Prerequisites
|
| 55 |
+
|
| 56 |
+
| Requirement | Notes |
|
| 57 |
+
|-------------|-------|
|
| 58 |
+
| **Git** | With `--recurse-submodules` support, and the `git-lfs` extension installed |
|
| 59 |
+
| **Python 3.11+** | uv will install it if missing |
|
| 60 |
+
| **uv** | Fast Python package manager ([install](https://docs.astral.sh/uv/)) |
|
| 61 |
+
| **Node.js 20+** | Optional — needed for browser tools and WhatsApp bridge (matches root `package.json` engines) |
|
| 62 |
+
|
| 63 |
+
### Clone and install
|
| 64 |
+
|
| 65 |
+
```bash
|
| 66 |
+
git clone --recurse-submodules https://github.com/NousResearch/hermes-agent.git
|
| 67 |
+
cd hermes-agent
|
| 68 |
+
|
| 69 |
+
# Create venv with Python 3.11
|
| 70 |
+
uv venv venv --python 3.11
|
| 71 |
+
export VIRTUAL_ENV="$(pwd)/venv"
|
| 72 |
+
|
| 73 |
+
# Install with all extras (messaging, cron, CLI menus, dev tools)
|
| 74 |
+
uv pip install -e ".[all,dev]"
|
| 75 |
+
|
| 76 |
+
# Optional: RL training submodule
|
| 77 |
+
# git submodule update --init tinker-atropos && uv pip install -e "./tinker-atropos"
|
| 78 |
+
|
| 79 |
+
# Optional: browser tools
|
| 80 |
+
npm install
|
| 81 |
+
```
|
| 82 |
+
|
| 83 |
+
### Configure for development
|
| 84 |
+
|
| 85 |
+
```bash
|
| 86 |
+
mkdir -p ~/.hermes/{cron,sessions,logs,memories,skills}
|
| 87 |
+
cp cli-config.yaml.example ~/.hermes/config.yaml
|
| 88 |
+
touch ~/.hermes/.env
|
| 89 |
+
|
| 90 |
+
# Add at minimum an LLM provider key:
|
| 91 |
+
echo "OPENROUTER_API_KEY=***" >> ~/.hermes/.env
|
| 92 |
+
```
|
| 93 |
+
|
| 94 |
+
### Run
|
| 95 |
+
|
| 96 |
+
```bash
|
| 97 |
+
# Symlink for global access
|
| 98 |
+
mkdir -p ~/.local/bin
|
| 99 |
+
ln -sf "$(pwd)/venv/bin/hermes" ~/.local/bin/hermes
|
| 100 |
+
|
| 101 |
+
# Verify
|
| 102 |
+
hermes doctor
|
| 103 |
+
hermes chat -q "Hello"
|
| 104 |
+
```
|
| 105 |
+
|
| 106 |
+
### Run tests
|
| 107 |
+
|
| 108 |
+
```bash
|
| 109 |
+
# Preferred — matches CI (hermetic env, 4 xdist workers); see AGENTS.md
|
| 110 |
+
scripts/run_tests.sh
|
| 111 |
+
|
| 112 |
+
# Alternative (activate the venv first). The wrapper is still recommended
|
| 113 |
+
# for parity with GitHub Actions before you open a PR:
|
| 114 |
+
pytest tests/ -v
|
| 115 |
+
```
|
| 116 |
+
|
| 117 |
+
---
|
| 118 |
+
|
| 119 |
+
## Project Structure
|
| 120 |
+
|
| 121 |
+
```
|
| 122 |
+
hermes-agent/
|
| 123 |
+
├── run_agent.py # AIAgent class — core conversation loop, tool dispatch, session persistence
|
| 124 |
+
├── cli.py # HermesCLI class — interactive TUI, prompt_toolkit integration
|
| 125 |
+
├── model_tools.py # Tool orchestration (thin layer over tools/registry.py)
|
| 126 |
+
├── toolsets.py # Tool groupings and presets (hermes-cli, hermes-telegram, etc.)
|
| 127 |
+
├── hermes_state.py # SQLite session database with FTS5 full-text search, session titles
|
| 128 |
+
├── batch_runner.py # Parallel batch processing for trajectory generation
|
| 129 |
+
│
|
| 130 |
+
├── agent/ # Agent internals (extracted modules)
|
| 131 |
+
│ ├── prompt_builder.py # System prompt assembly (identity, skills, context files, memory)
|
| 132 |
+
│ ├── context_compressor.py # Auto-summarization when approaching context limits
|
| 133 |
+
│ ├── auxiliary_client.py # Resolves auxiliary OpenAI clients (summarization, vision)
|
| 134 |
+
│ ├── display.py # KawaiiSpinner, tool progress formatting
|
| 135 |
+
│ ├── model_metadata.py # Model context lengths, token estimation
|
| 136 |
+
│ └── trajectory.py # Trajectory saving helpers
|
| 137 |
+
│
|
| 138 |
+
├── hermes_cli/ # CLI command implementations
|
| 139 |
+
│ ├── main.py # Entry point, argument parsing, command dispatch
|
| 140 |
+
│ ├── config.py # Config management, migration, env var definitions
|
| 141 |
+
│ ├── setup.py # Interactive setup wizard
|
| 142 |
+
│ ├── auth.py # Provider resolution, OAuth, Nous Portal
|
| 143 |
+
│ ├── models.py # OpenRouter model selection lists
|
| 144 |
+
│ ├── banner.py # Welcome banner, ASCII art
|
| 145 |
+
│ ├── commands.py # Central slash command registry (CommandDef), autocomplete, gateway helpers
|
| 146 |
+
│ ├── callbacks.py # Interactive callbacks (clarify, sudo, approval)
|
| 147 |
+
│ ├── doctor.py # Diagnostics
|
| 148 |
+
│ ├── skills_hub.py # Skills Hub CLI + /skills slash command
|
| 149 |
+
│ └── skin_engine.py # Skin/theme engine — data-driven CLI visual customization
|
| 150 |
+
│
|
| 151 |
+
├── tools/ # Tool implementations (self-registering)
|
| 152 |
+
│ ├── registry.py # Central tool registry (schemas, handlers, dispatch)
|
| 153 |
+
│ ├── approval.py # Dangerous command detection + per-session approval
|
| 154 |
+
│ ├── terminal_tool.py # Terminal orchestration (sudo, env lifecycle, backends)
|
| 155 |
+
│ ├── file_operations.py # read_file, write_file, search, patch, etc.
|
| 156 |
+
│ ├── web_tools.py # web_search, web_extract (Parallel/Firecrawl + Gemini summarization)
|
| 157 |
+
│ ├── vision_tools.py # Image analysis via multimodal models
|
| 158 |
+
│ ├── delegate_tool.py # Subagent spawning and parallel task execution
|
| 159 |
+
│ ├── code_execution_tool.py # Sandboxed Python with RPC tool access
|
| 160 |
+
│ ├── session_search_tool.py # Search past conversations with FTS5 + summarization
|
| 161 |
+
│ ├── cronjob_tools.py # Scheduled task management
|
| 162 |
+
│ ├── skill_tools.py # Skill search, load, manage
|
| 163 |
+
│ └── environments/ # Terminal execution backends
|
| 164 |
+
│ ├── base.py # BaseEnvironment ABC
|
| 165 |
+
│ ├── local.py, docker.py, ssh.py, singularity.py, modal.py, daytona.py
|
| 166 |
+
│
|
| 167 |
+
├── gateway/ # Messaging gateway
|
| 168 |
+
│ ├── run.py # GatewayRunner — platform lifecycle, message routing, cron
|
| 169 |
+
│ ├── config.py # Platform configuration resolution
|
| 170 |
+
│ ├── session.py # Session store, context prompts, reset policies
|
| 171 |
+
│ └── platforms/ # Platform adapters
|
| 172 |
+
│ ├── telegram.py, discord_adapter.py, slack.py, whatsapp.py
|
| 173 |
+
│
|
| 174 |
+
├── scripts/ # Installer and bridge scripts
|
| 175 |
+
│ ├── install.sh # Linux/macOS installer
|
| 176 |
+
│ ├── install.ps1 # Windows PowerShell installer
|
| 177 |
+
│ └── whatsapp-bridge/ # Node.js WhatsApp bridge (Baileys)
|
| 178 |
+
│
|
| 179 |
+
├── skills/ # Bundled skills (copied to ~/.hermes/skills/ on install)
|
| 180 |
+
├── optional-skills/ # Official optional skills (discoverable via hub, not activated by default)
|
| 181 |
+
├── environments/ # RL training environments (Atropos integration)
|
| 182 |
+
├── tests/ # Test suite
|
| 183 |
+
├── website/ # Documentation site (hermes-agent.nousresearch.com)
|
| 184 |
+
│
|
| 185 |
+
├── cli-config.yaml.example # Example configuration (copied to ~/.hermes/config.yaml)
|
| 186 |
+
└── AGENTS.md # Development guide for AI coding assistants
|
| 187 |
+
```
|
| 188 |
+
|
| 189 |
+
### User configuration (stored in `~/.hermes/`)
|
| 190 |
+
|
| 191 |
+
| Path | Purpose |
|
| 192 |
+
|------|---------|
|
| 193 |
+
| `~/.hermes/config.yaml` | Settings (model, terminal, toolsets, compression, etc.) |
|
| 194 |
+
| `~/.hermes/.env` | API keys and secrets |
|
| 195 |
+
| `~/.hermes/auth.json` | OAuth credentials (Nous Portal) |
|
| 196 |
+
| `~/.hermes/skills/` | All active skills (bundled + hub-installed + agent-created) |
|
| 197 |
+
| `~/.hermes/memories/` | Persistent memory (MEMORY.md, USER.md) |
|
| 198 |
+
| `~/.hermes/state.db` | SQLite session database |
|
| 199 |
+
| `~/.hermes/sessions/` | JSON session logs |
|
| 200 |
+
| `~/.hermes/cron/` | Scheduled job data |
|
| 201 |
+
| `~/.hermes/whatsapp/session/` | WhatsApp bridge credentials |
|
| 202 |
+
|
| 203 |
+
---
|
| 204 |
+
|
| 205 |
+
## Architecture Overview
|
| 206 |
+
|
| 207 |
+
### Core Loop
|
| 208 |
+
|
| 209 |
+
```
|
| 210 |
+
User message → AIAgent._run_agent_loop()
|
| 211 |
+
├── Build system prompt (prompt_builder.py)
|
| 212 |
+
├── Build API kwargs (model, messages, tools, reasoning config)
|
| 213 |
+
├── Call LLM (OpenAI-compatible API)
|
| 214 |
+
├── If tool_calls in response:
|
| 215 |
+
│ ├── Execute each tool via registry dispatch
|
| 216 |
+
│ ├── Add tool results to conversation
|
| 217 |
+
│ └── Loop back to LLM call
|
| 218 |
+
├── If text response:
|
| 219 |
+
│ ├── Persist session to DB
|
| 220 |
+
│ └── Return final_response
|
| 221 |
+
└── Context compression if approaching token limit
|
| 222 |
+
```
|
| 223 |
+
|
| 224 |
+
### Key Design Patterns
|
| 225 |
+
|
| 226 |
+
- **Self-registering tools**: Each tool file calls `registry.register()` at import time. `model_tools.py` triggers discovery by importing all tool modules.
|
| 227 |
+
- **Toolset grouping**: Tools are grouped into toolsets (`web`, `terminal`, `file`, `browser`, etc.) that can be enabled/disabled per platform.
|
| 228 |
+
- **Session persistence**: All conversations are stored in SQLite (`hermes_state.py`) with full-text search and unique session titles. JSON logs go to `~/.hermes/sessions/`.
|
| 229 |
+
- **Ephemeral injection**: System prompts and prefill messages are injected at API call time, never persisted to the database or logs.
|
| 230 |
+
- **Provider abstraction**: The agent works with any OpenAI-compatible API. Provider resolution happens at init time (Nous Portal OAuth, OpenRouter API key, or custom endpoint).
|
| 231 |
+
- **Provider routing**: When using OpenRouter, `provider_routing` in config.yaml controls provider selection (sort by throughput/latency/price, allow/ignore specific providers, data retention policies). These are injected as `extra_body.provider` in API requests.
|
| 232 |
+
|
| 233 |
+
---
|
| 234 |
+
|
| 235 |
+
## Code Style
|
| 236 |
+
|
| 237 |
+
- **PEP 8** with practical exceptions (we don't enforce strict line length)
|
| 238 |
+
- **Comments**: Only when explaining non-obvious intent, trade-offs, or API quirks. Don't narrate what the code does — `# increment counter` adds nothing
|
| 239 |
+
- **Error handling**: Catch specific exceptions. Log with `logger.warning()`/`logger.error()` — use `exc_info=True` for unexpected errors so stack traces appear in logs
|
| 240 |
+
- **Cross-platform**: Never assume Unix. See [Cross-Platform Compatibility](#cross-platform-compatibility)
|
| 241 |
+
|
| 242 |
+
---
|
| 243 |
+
|
| 244 |
+
## Adding a New Tool
|
| 245 |
+
|
| 246 |
+
Before writing a tool, ask: [should this be a skill instead?](#should-it-be-a-skill-or-a-tool)
|
| 247 |
+
|
| 248 |
+
Tools self-register with the central registry. Each tool file co-locates its schema, handler, and registration:
|
| 249 |
+
|
| 250 |
+
```python
|
| 251 |
+
"""my_tool — Brief description of what this tool does."""
|
| 252 |
+
|
| 253 |
+
import json
|
| 254 |
+
from tools.registry import registry
|
| 255 |
+
|
| 256 |
+
|
| 257 |
+
def my_tool(param1: str, param2: int = 10, **kwargs) -> str:
|
| 258 |
+
"""Handler. Returns a string result (often JSON)."""
|
| 259 |
+
result = do_work(param1, param2)
|
| 260 |
+
return json.dumps(result)
|
| 261 |
+
|
| 262 |
+
|
| 263 |
+
MY_TOOL_SCHEMA = {
|
| 264 |
+
"type": "function",
|
| 265 |
+
"function": {
|
| 266 |
+
"name": "my_tool",
|
| 267 |
+
"description": "What this tool does and when the agent should use it.",
|
| 268 |
+
"parameters": {
|
| 269 |
+
"type": "object",
|
| 270 |
+
"properties": {
|
| 271 |
+
"param1": {"type": "string", "description": "What param1 is"},
|
| 272 |
+
"param2": {"type": "integer", "description": "What param2 is", "default": 10},
|
| 273 |
+
},
|
| 274 |
+
"required": ["param1"],
|
| 275 |
+
},
|
| 276 |
+
},
|
| 277 |
+
}
|
| 278 |
+
|
| 279 |
+
|
| 280 |
+
def _check_requirements() -> bool:
|
| 281 |
+
"""Return True if this tool's dependencies are available."""
|
| 282 |
+
return True
|
| 283 |
+
|
| 284 |
+
|
| 285 |
+
registry.register(
|
| 286 |
+
name="my_tool",
|
| 287 |
+
toolset="my_toolset",
|
| 288 |
+
schema=MY_TOOL_SCHEMA,
|
| 289 |
+
handler=lambda args, **kw: my_tool(**args, **kw),
|
| 290 |
+
check_fn=_check_requirements,
|
| 291 |
+
)
|
| 292 |
+
```
|
| 293 |
+
|
| 294 |
+
**Wire into a toolset (required):** Built-in tools are auto-discovered: any
|
| 295 |
+
`tools/*.py` file that contains a top-level `registry.register(...)` call is
|
| 296 |
+
imported by `discover_builtin_tools()` in `tools/registry.py` when `model_tools`
|
| 297 |
+
loads. There is **no** manual import list in `model_tools.py` to maintain.
|
| 298 |
+
|
| 299 |
+
You must still add the tool name to the appropriate list in `toolsets.py`
|
| 300 |
+
(for example `_HERMES_CORE_TOOLS` or a dedicated toolset); otherwise the tool
|
| 301 |
+
registers but is never exposed to the agent. If you introduce a new toolset,
|
| 302 |
+
add it in `toolsets.py` and wire it into the relevant platform presets.
|
| 303 |
+
|
| 304 |
+
See `AGENTS.md` (section **Adding New Tools**) for profile-aware paths and
|
| 305 |
+
plugin vs core guidance.
|
| 306 |
+
|
| 307 |
+
---
|
| 308 |
+
|
| 309 |
+
## Adding a Skill
|
| 310 |
+
|
| 311 |
+
Bundled skills live in `skills/` organized by category. Official optional skills use the same structure in `optional-skills/`:
|
| 312 |
+
|
| 313 |
+
```
|
| 314 |
+
skills/
|
| 315 |
+
├── research/
|
| 316 |
+
│ └── arxiv/
|
| 317 |
+
│ ├── SKILL.md # Required: main instructions
|
| 318 |
+
│ └── scripts/ # Optional: helper scripts
|
| 319 |
+
│ └── search_arxiv.py
|
| 320 |
+
├── productivity/
|
| 321 |
+
│ └── ocr-and-documents/
|
| 322 |
+
│ ├── SKILL.md
|
| 323 |
+
│ ├── scripts/
|
| 324 |
+
│ └── references/
|
| 325 |
+
└── ...
|
| 326 |
+
```
|
| 327 |
+
|
| 328 |
+
### SKILL.md format
|
| 329 |
+
|
| 330 |
+
```markdown
|
| 331 |
+
---
|
| 332 |
+
name: my-skill
|
| 333 |
+
description: Brief description (shown in skill search results)
|
| 334 |
+
version: 1.0.0
|
| 335 |
+
author: Your Name
|
| 336 |
+
license: MIT
|
| 337 |
+
platforms: [macos, linux] # Optional — restrict to specific OS platforms
|
| 338 |
+
# Valid: macos, linux, windows
|
| 339 |
+
# Omit to load on all platforms (default)
|
| 340 |
+
required_environment_variables: # Optional — secure setup-on-load metadata
|
| 341 |
+
- name: MY_API_KEY
|
| 342 |
+
prompt: API key
|
| 343 |
+
help: Where to get it
|
| 344 |
+
required_for: full functionality
|
| 345 |
+
prerequisites: # Optional legacy runtime requirements
|
| 346 |
+
env_vars: [MY_API_KEY] # Backward-compatible alias for required env vars
|
| 347 |
+
commands: [curl, jq] # Advisory only; does not hide the skill
|
| 348 |
+
metadata:
|
| 349 |
+
hermes:
|
| 350 |
+
tags: [Category, Subcategory, Keywords]
|
| 351 |
+
related_skills: [other-skill-name]
|
| 352 |
+
fallback_for_toolsets: [web] # Optional — show only when toolset is unavailable
|
| 353 |
+
requires_toolsets: [terminal] # Optional — show only when toolset is available
|
| 354 |
+
---
|
| 355 |
+
|
| 356 |
+
# Skill Title
|
| 357 |
+
|
| 358 |
+
Brief intro.
|
| 359 |
+
|
| 360 |
+
## When to Use
|
| 361 |
+
Trigger conditions — when should the agent load this skill?
|
| 362 |
+
|
| 363 |
+
## Quick Reference
|
| 364 |
+
Table of common commands or API calls.
|
| 365 |
+
|
| 366 |
+
## Procedure
|
| 367 |
+
Step-by-step instructions the agent follows.
|
| 368 |
+
|
| 369 |
+
## Pitfalls
|
| 370 |
+
Known failure modes and how to handle them.
|
| 371 |
+
|
| 372 |
+
## Verification
|
| 373 |
+
How the agent confirms it worked.
|
| 374 |
+
```
|
| 375 |
+
|
| 376 |
+
### Platform-specific skills
|
| 377 |
+
|
| 378 |
+
Skills can declare which OS platforms they support via the `platforms` frontmatter field. Skills with this field are automatically hidden from the system prompt, `skills_list()`, and slash commands on incompatible platforms.
|
| 379 |
+
|
| 380 |
+
```yaml
|
| 381 |
+
platforms: [macos] # macOS only (e.g., iMessage, Apple Reminders)
|
| 382 |
+
platforms: [macos, linux] # macOS and Linux
|
| 383 |
+
platforms: [windows] # Windows only
|
| 384 |
+
```
|
| 385 |
+
|
| 386 |
+
If the field is omitted or empty, the skill loads on all platforms (backward compatible). See `skills/apple/` for examples of macOS-only skills.
|
| 387 |
+
|
| 388 |
+
### Conditional skill activation
|
| 389 |
+
|
| 390 |
+
Skills can declare conditions that control when they appear in the system prompt, based on which tools and toolsets are available in the current session. This is primarily used for **fallback skills** — alternatives that should only be shown when a primary tool is unavailable.
|
| 391 |
+
|
| 392 |
+
Four fields are supported under `metadata.hermes`:
|
| 393 |
+
|
| 394 |
+
```yaml
|
| 395 |
+
metadata:
|
| 396 |
+
hermes:
|
| 397 |
+
fallback_for_toolsets: [web] # Show ONLY when these toolsets are unavailable
|
| 398 |
+
requires_toolsets: [terminal] # Show ONLY when these toolsets are available
|
| 399 |
+
fallback_for_tools: [web_search] # Show ONLY when these specific tools are unavailable
|
| 400 |
+
requires_tools: [terminal] # Show ONLY when these specific tools are available
|
| 401 |
+
```
|
| 402 |
+
|
| 403 |
+
**Semantics:**
|
| 404 |
+
- `fallback_for_*`: The skill is a backup. It is **hidden** when the listed tools/toolsets are available, and **shown** when they are unavailable. Use this for free alternatives to premium tools.
|
| 405 |
+
- `requires_*`: The skill needs certain tools to function. It is **hidden** when the listed tools/toolsets are unavailable. Use this for skills that depend on specific capabilities (e.g., a skill that only makes sense with terminal access).
|
| 406 |
+
- If both are specified, both conditions must be satisfied for the skill to appear.
|
| 407 |
+
- If neither is specified, the skill is always shown (backward compatible).
|
| 408 |
+
|
| 409 |
+
**Examples:**
|
| 410 |
+
|
| 411 |
+
```yaml
|
| 412 |
+
# DuckDuckGo search — shown when Firecrawl (web toolset) is unavailable
|
| 413 |
+
metadata:
|
| 414 |
+
hermes:
|
| 415 |
+
fallback_for_toolsets: [web]
|
| 416 |
+
|
| 417 |
+
# Smart home skill — only useful when terminal is available
|
| 418 |
+
metadata:
|
| 419 |
+
hermes:
|
| 420 |
+
requires_toolsets: [terminal]
|
| 421 |
+
|
| 422 |
+
# Local browser fallback — shown when Browserbase is unavailable
|
| 423 |
+
metadata:
|
| 424 |
+
hermes:
|
| 425 |
+
fallback_for_toolsets: [browser]
|
| 426 |
+
```
|
| 427 |
+
|
| 428 |
+
The filtering happens at prompt build time in `agent/prompt_builder.py`. The `build_skills_system_prompt()` function receives the set of available tools and toolsets from the agent and uses `_skill_should_show()` to evaluate each skill's conditions.
|
| 429 |
+
|
| 430 |
+
### Skill setup metadata
|
| 431 |
+
|
| 432 |
+
Skills can declare secure setup-on-load metadata via the `required_environment_variables` frontmatter field. Missing values do not hide the skill from discovery; they trigger a CLI-only secure prompt when the skill is actually loaded.
|
| 433 |
+
|
| 434 |
+
```yaml
|
| 435 |
+
required_environment_variables:
|
| 436 |
+
- name: TENOR_API_KEY
|
| 437 |
+
prompt: Tenor API key
|
| 438 |
+
help: Get a key from https://developers.google.com/tenor
|
| 439 |
+
required_for: full functionality
|
| 440 |
+
```
|
| 441 |
+
|
| 442 |
+
The user may skip setup and keep loading the skill. Hermes only exposes metadata (`stored_as`, `skipped`, `validated`) to the model — never the secret value.
|
| 443 |
+
|
| 444 |
+
Legacy `prerequisites.env_vars` remains supported and is normalized into the new representation.
|
| 445 |
+
|
| 446 |
+
```yaml
|
| 447 |
+
prerequisites:
|
| 448 |
+
env_vars: [TENOR_API_KEY] # Legacy alias for required_environment_variables
|
| 449 |
+
commands: [curl, jq] # Advisory CLI checks
|
| 450 |
+
```
|
| 451 |
+
|
| 452 |
+
Gateway and messaging sessions never collect secrets in-band; they instruct the user to run `hermes setup` or update `~/.hermes/.env` locally.
|
| 453 |
+
|
| 454 |
+
**When to declare required environment variables:**
|
| 455 |
+
- The skill uses an API key or token that should be collected securely at load time
|
| 456 |
+
- The skill can still be useful if the user skips setup, but may degrade gracefully
|
| 457 |
+
|
| 458 |
+
**When to declare command prerequisites:**
|
| 459 |
+
- The skill relies on a CLI tool that may not be installed (e.g., `himalaya`, `openhue`, `ddgs`)
|
| 460 |
+
- Treat command checks as guidance, not discovery-time hiding
|
| 461 |
+
|
| 462 |
+
See `skills/gifs/gif-search/` and `skills/email/himalaya/` for examples.
|
| 463 |
+
|
| 464 |
+
### Skill guidelines
|
| 465 |
+
|
| 466 |
+
- **No external dependencies unless absolutely necessary.** Prefer stdlib Python, curl, and existing Hermes tools (`web_extract`, `terminal`, `read_file`).
|
| 467 |
+
- **Progressive disclosure.** Put the most common workflow first. Edge cases and advanced usage go at the bottom.
|
| 468 |
+
- **Include helper scripts** for XML/JSON parsing or complex logic — don't expect the LLM to write parsers inline every time.
|
| 469 |
+
- **Test it.** Run `hermes --toolsets skills -q "Use the X skill to do Y"` and verify the agent follows the instructions correctly.
|
| 470 |
+
|
| 471 |
+
---
|
| 472 |
+
|
| 473 |
+
## Adding a Skin / Theme
|
| 474 |
+
|
| 475 |
+
Hermes uses a data-driven skin system — no code changes needed to add a new skin.
|
| 476 |
+
|
| 477 |
+
**Option A: User skin (YAML file)**
|
| 478 |
+
|
| 479 |
+
Create `~/.hermes/skins/<name>.yaml`:
|
| 480 |
+
|
| 481 |
+
```yaml
|
| 482 |
+
name: mytheme
|
| 483 |
+
description: Short description of the theme
|
| 484 |
+
|
| 485 |
+
colors:
|
| 486 |
+
banner_border: "#HEX" # Panel border color
|
| 487 |
+
banner_title: "#HEX" # Panel title color
|
| 488 |
+
banner_accent: "#HEX" # Section header color
|
| 489 |
+
banner_dim: "#HEX" # Muted/dim text color
|
| 490 |
+
banner_text: "#HEX" # Body text color
|
| 491 |
+
response_border: "#HEX" # Response box border
|
| 492 |
+
|
| 493 |
+
spinner:
|
| 494 |
+
waiting_faces: ["(⚔)", "(⛨)"]
|
| 495 |
+
thinking_faces: ["(⚔)", "(⌁)"]
|
| 496 |
+
thinking_verbs: ["forging", "plotting"]
|
| 497 |
+
wings: # Optional left/right decorations
|
| 498 |
+
- ["⟪⚔", "⚔⟫"]
|
| 499 |
+
|
| 500 |
+
branding:
|
| 501 |
+
agent_name: "My Agent"
|
| 502 |
+
welcome: "Welcome message"
|
| 503 |
+
response_label: " ⚔ Agent "
|
| 504 |
+
prompt_symbol: "⚔"
|
| 505 |
+
|
| 506 |
+
tool_prefix: "╎" # Tool output line prefix
|
| 507 |
+
```
|
| 508 |
+
|
| 509 |
+
All fields are optional — missing values inherit from the default skin.
|
| 510 |
+
|
| 511 |
+
**Option B: Built-in skin**
|
| 512 |
+
|
| 513 |
+
Add to `_BUILTIN_SKINS` dict in `hermes_cli/skin_engine.py`. Use the same schema as above but as a Python dict. Built-in skins ship with the package and are always available.
|
| 514 |
+
|
| 515 |
+
**Activating:**
|
| 516 |
+
- CLI: `/skin mytheme` or set `display.skin: mytheme` in config.yaml
|
| 517 |
+
- Config: `display: { skin: mytheme }`
|
| 518 |
+
|
| 519 |
+
See `hermes_cli/skin_engine.py` for the full schema and existing skins as examples.
|
| 520 |
+
|
| 521 |
+
---
|
| 522 |
+
|
| 523 |
+
## Cross-Platform Compatibility
|
| 524 |
+
|
| 525 |
+
Hermes runs on Linux, macOS, and native Windows (plus WSL2). When writing code
|
| 526 |
+
that touches the OS, assume *any* platform can hit your code path.
|
| 527 |
+
|
| 528 |
+
> **Before you PR:** run `scripts/check-windows-footguns.py` to catch the
|
| 529 |
+
> common Windows-unsafe patterns in your diff. It's grep-based and cheap;
|
| 530 |
+
> CI runs it on every PR too.
|
| 531 |
+
|
| 532 |
+
### Critical rules
|
| 533 |
+
|
| 534 |
+
1. **Never call `os.kill(pid, 0)` for liveness checks.** `os.kill(pid, 0)`
|
| 535 |
+
is a standard POSIX idiom to check "is this PID alive" — the signal 0
|
| 536 |
+
is a no-op permission check. **On Windows it is NOT a no-op.** Python's
|
| 537 |
+
Windows `os.kill` maps `sig=0` to `CTRL_C_EVENT` (they collide at the
|
| 538 |
+
integer value 0) and routes it through `GenerateConsoleCtrlEvent(0, pid)`,
|
| 539 |
+
which broadcasts Ctrl+C to the **entire console process group** containing
|
| 540 |
+
the target PID. "Probe if alive" silently becomes "kill the target and
|
| 541 |
+
often unrelated processes sharing its console." See [bpo-14484](https://bugs.python.org/issue14484)
|
| 542 |
+
(open since 2012 — will never be fixed for compat reasons).
|
| 543 |
+
|
| 544 |
+
**Preferred:** use `psutil` (a core dependency — always available):
|
| 545 |
+
|
| 546 |
+
```python
|
| 547 |
+
import psutil
|
| 548 |
+
if psutil.pid_exists(pid):
|
| 549 |
+
# process is alive — safe on every platform
|
| 550 |
+
...
|
| 551 |
+
```
|
| 552 |
+
|
| 553 |
+
If you specifically need the hermes wrapper (it has a stdlib fallback
|
| 554 |
+
for scaffold-phase imports before pip install finishes), use
|
| 555 |
+
`gateway.status._pid_exists(pid)`. It calls `psutil.pid_exists` first
|
| 556 |
+
and falls back to a hand-rolled `OpenProcess + WaitForSingleObject`
|
| 557 |
+
dance on Windows only when psutil is somehow missing.
|
| 558 |
+
|
| 559 |
+
Audit grep for new callsites: `rg "os\.kill\([^,]+,\s*0\s*\)"`. Any hit
|
| 560 |
+
in non-test code is presumptively a Windows silent-kill bug.
|
| 561 |
+
|
| 562 |
+
2. **Use `shutil.which()` before shelling out — don't assume Windows has
|
| 563 |
+
tools Linux has.** `wmic` was removed in Windows 10 21H1 and later. `ps`,
|
| 564 |
+
`kill`, `grep`, `awk`, `fuser`, `lsof`, `pgrep`, and most POSIX CLI tools
|
| 565 |
+
simply don't exist on Windows. Test availability with
|
| 566 |
+
`shutil.which("tool")` and fall back to a Windows-native equivalent —
|
| 567 |
+
usually PowerShell via `subprocess.run(["powershell", "-NoProfile",
|
| 568 |
+
"-Command", ...])`.
|
| 569 |
+
|
| 570 |
+
For process enumeration: PowerShell's `Get-CimInstance Win32_Process` is
|
| 571 |
+
the modern replacement for `wmic process`. See
|
| 572 |
+
`hermes_cli/gateway.py::_scan_gateway_pids` for the pattern.
|
| 573 |
+
|
| 574 |
+
3. **`termios` and `fcntl` are Unix-only.** Always catch both `ImportError`
|
| 575 |
+
and `NotImplementedError`:
|
| 576 |
+
```python
|
| 577 |
+
try:
|
| 578 |
+
from simple_term_menu import TerminalMenu
|
| 579 |
+
menu = TerminalMenu(options)
|
| 580 |
+
idx = menu.show()
|
| 581 |
+
except (ImportError, NotImplementedError):
|
| 582 |
+
# Fallback: numbered menu for Windows
|
| 583 |
+
for i, opt in enumerate(options):
|
| 584 |
+
print(f" {i+1}. {opt}")
|
| 585 |
+
idx = int(input("Choice: ")) - 1
|
| 586 |
+
```
|
| 587 |
+
|
| 588 |
+
4. **File encoding.** Windows may save `.env` files in `cp1252`. Always
|
| 589 |
+
handle encoding errors:
|
| 590 |
+
```python
|
| 591 |
+
try:
|
| 592 |
+
load_dotenv(env_path)
|
| 593 |
+
except UnicodeDecodeError:
|
| 594 |
+
load_dotenv(env_path, encoding="latin-1")
|
| 595 |
+
```
|
| 596 |
+
Config files (`config.yaml`) may be saved with a UTF-8 BOM by Notepad and
|
| 597 |
+
similar editors — use `encoding="utf-8-sig"` when reading files that
|
| 598 |
+
could have been touched by a Windows GUI editor.
|
| 599 |
+
|
| 600 |
+
5. **Process management.** `os.setsid()`, `os.killpg()`, `os.fork()`,
|
| 601 |
+
`os.getuid()`, and POSIX signal handling differ on Windows. Guard with
|
| 602 |
+
`platform.system()`, `sys.platform`, or `hasattr(os, "setsid")`:
|
| 603 |
+
```python
|
| 604 |
+
if platform.system() != "Windows":
|
| 605 |
+
kwargs["preexec_fn"] = os.setsid
|
| 606 |
+
else:
|
| 607 |
+
kwargs["creationflags"] = subprocess.CREATE_NEW_PROCESS_GROUP
|
| 608 |
+
```
|
| 609 |
+
|
| 610 |
+
**Preferred:** for killing a process AND its children (what `os.killpg`
|
| 611 |
+
does on POSIX), use `psutil` — it works on every platform:
|
| 612 |
+
```python
|
| 613 |
+
import psutil
|
| 614 |
+
try:
|
| 615 |
+
parent = psutil.Process(pid)
|
| 616 |
+
# Kill children first (leaf-up), then the parent.
|
| 617 |
+
for child in parent.children(recursive=True):
|
| 618 |
+
child.kill()
|
| 619 |
+
parent.kill()
|
| 620 |
+
except psutil.NoSuchProcess:
|
| 621 |
+
pass
|
| 622 |
+
```
|
| 623 |
+
|
| 624 |
+
6. **Signals that don't exist on Windows: `SIGALRM`, `SIGCHLD`, `SIGHUP`,
|
| 625 |
+
`SIGUSR1`, `SIGUSR2`, `SIGPIPE`, `SIGQUIT`, `SIGKILL`.** Python's
|
| 626 |
+
`signal` module raises `AttributeError` at import time if you reference
|
| 627 |
+
them on Windows. Use `getattr(signal, "SIGKILL", signal.SIGTERM)` or
|
| 628 |
+
gate the whole block behind a platform check. `loop.add_signal_handler`
|
| 629 |
+
raises `NotImplementedError` on Windows — always catch it.
|
| 630 |
+
|
| 631 |
+
7. **Path separators.** Use `pathlib.Path` instead of string concatenation
|
| 632 |
+
with `/`. Forward slashes work almost everywhere on Windows, but
|
| 633 |
+
`subprocess.run(["cmd.exe", "/c", ...])` and other shell contexts can
|
| 634 |
+
require backslashes — convert with `str(path)` at the subprocess boundary,
|
| 635 |
+
not inside Python logic.
|
| 636 |
+
|
| 637 |
+
8. **Symlinks need elevated privileges on Windows** (unless Developer Mode is
|
| 638 |
+
on). Tests that create symlinks need `@pytest.mark.skipif(sys.platform ==
|
| 639 |
+
"win32", reason="Symlinks require elevated privileges on Windows")`.
|
| 640 |
+
|
| 641 |
+
9. **POSIX file modes (0o600, 0o644, etc.) are NOT enforced on NTFS** by
|
| 642 |
+
default. Tests that assert on `stat().st_mode & 0o777` must skip on
|
| 643 |
+
Windows — the concept doesn't translate. Use ACLs (`icacls`, `pywin32`)
|
| 644 |
+
for Windows secret-file protection if needed.
|
| 645 |
+
|
| 646 |
+
10. **Detached background daemons on Windows need `pythonw.exe`, NOT
|
| 647 |
+
`python.exe`.** `python.exe` always allocates or attaches to a console,
|
| 648 |
+
which makes it vulnerable to `CTRL_C_EVENT` broadcasts from any sibling
|
| 649 |
+
process. `pythonw.exe` is the no-console variant. Combine with
|
| 650 |
+
`CREATE_NO_WINDOW | DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP |
|
| 651 |
+
CREATE_BREAKAWAY_FROM_JOB` in `subprocess.Popen(creationflags=...)`.
|
| 652 |
+
See `hermes_cli/gateway_windows.py::_spawn_detached` for the reference
|
| 653 |
+
implementation.
|
| 654 |
+
|
| 655 |
+
11. **`subprocess.Popen` with `.cmd` or `.bat` shims needs `shutil.which`
|
| 656 |
+
to resolve.** Passing `"agent-browser"` to `Popen` on Windows finds
|
| 657 |
+
the extensionless POSIX shebang shim in `node_modules/.bin/`, which
|
| 658 |
+
`CreateProcessW` can't execute — you'll get `WinError 193 "not a valid
|
| 659 |
+
Win32 application"`. Use `shutil.which("agent-browser", path=local_bin)`
|
| 660 |
+
which honors PATHEXT and picks the `.CMD` variant on Windows.
|
| 661 |
+
|
| 662 |
+
12. **Don't use shell shebangs as a way to run Python.** `#!/usr/bin/env
|
| 663 |
+
python` only works when the file is executed through a Unix shell.
|
| 664 |
+
`subprocess.run(["./myscript.py"])` on Windows fails even if the file
|
| 665 |
+
has a shebang line. Always invoke Python explicitly:
|
| 666 |
+
`[sys.executable, "myscript.py"]`.
|
| 667 |
+
|
| 668 |
+
13. **Shell commands in installers.** If you change `scripts/install.sh`,
|
| 669 |
+
make the equivalent change in `scripts/install.ps1`. The two scripts
|
| 670 |
+
are the canonical example of "works on Linux does not mean works on
|
| 671 |
+
Windows" and have drifted multiple times — keep them in lockstep.
|
| 672 |
+
|
| 673 |
+
14. **Known paths that are OneDrive-redirected on Windows:** Desktop,
|
| 674 |
+
Documents, Pictures, Videos. The "real" path when OneDrive Backup is
|
| 675 |
+
enabled is `%USERPROFILE%\OneDrive\Desktop` (etc.), NOT
|
| 676 |
+
`%USERPROFILE%\Desktop` (which exists as an empty husk). Resolve the
|
| 677 |
+
real location via `ctypes` + `SHGetKnownFolderPath` or by reading the
|
| 678 |
+
`Shell Folders` registry key — never assume `~/Desktop`.
|
| 679 |
+
|
| 680 |
+
15. **CRLF vs LF in generated scripts.** Windows `cmd.exe` and `schtasks`
|
| 681 |
+
parse line-by-line; mixed or LF-only line endings can break multi-line
|
| 682 |
+
`.cmd` / `.bat` files. Use `open(path, "w", encoding="utf-8",
|
| 683 |
+
newline="\r\n")` — or `open(path, "wb")` + explicit bytes — when
|
| 684 |
+
generating scripts Windows will execute.
|
| 685 |
+
|
| 686 |
+
16. **Two different quoting schemes in one command line.** `subprocess.run
|
| 687 |
+
(["schtasks", "/TR", some_cmd])` → schtasks itself parses `/TR`, AND
|
| 688 |
+
the `some_cmd` string is re-parsed by `cmd.exe` when the task fires.
|
| 689 |
+
Different parsers, different escape rules. Use two separate quoting
|
| 690 |
+
helpers and never cross them. See `hermes_cli/gateway_windows.py::
|
| 691 |
+
_quote_cmd_script_arg` and `_quote_schtasks_arg` for the reference
|
| 692 |
+
pair.
|
| 693 |
+
|
| 694 |
+
### Testing cross-platform
|
| 695 |
+
|
| 696 |
+
Tests that use POSIX-only syscalls need a skip marker. Common ones:
|
| 697 |
+
- Symlinks → `@pytest.mark.skipif(sys.platform == "win32", ...)`
|
| 698 |
+
- `0o600` file modes → `@pytest.mark.skipif(sys.platform.startswith("win"), ...)`
|
| 699 |
+
- `signal.SIGALRM` → Unix-only (see `tests/conftest.py::_enforce_test_timeout`)
|
| 700 |
+
- `os.setsid` / `os.fork` → Unix-only
|
| 701 |
+
- Live Winsock / Windows-specific regression tests →
|
| 702 |
+
`@pytest.mark.skipif(sys.platform != "win32", reason="Windows-specific regression")`
|
| 703 |
+
|
| 704 |
+
If you monkeypatch `sys.platform` for cross-platform tests, also patch
|
| 705 |
+
`platform.system()` / `platform.release()` / `platform.mac_ver()` — each
|
| 706 |
+
re-reads the real OS independently, so half-patched tests still route
|
| 707 |
+
through the wrong branch on a Windows runner.
|
| 708 |
+
|
| 709 |
+
---
|
| 710 |
+
|
| 711 |
+
## Security Considerations
|
| 712 |
+
|
| 713 |
+
Hermes has terminal access. Security matters.
|
| 714 |
+
|
| 715 |
+
### Existing protections
|
| 716 |
+
|
| 717 |
+
| Layer | Implementation |
|
| 718 |
+
|-------|---------------|
|
| 719 |
+
| **Sudo password piping** | Uses `shlex.quote()` to prevent shell injection |
|
| 720 |
+
| **Dangerous command detection** | Regex patterns in `tools/approval.py` with user approval flow |
|
| 721 |
+
| **Cron prompt injection** | Scanner in `tools/cronjob_tools.py` blocks instruction-override patterns |
|
| 722 |
+
| **Write deny list** | Protected paths (`~/.ssh/authorized_keys`, `/etc/shadow`) resolved via `os.path.realpath()` to prevent symlink bypass |
|
| 723 |
+
| **Skills guard** | Security scanner for hub-installed skills (`tools/skills_guard.py`) |
|
| 724 |
+
| **Code execution sandbox** | `execute_code` child process runs with API keys stripped from environment |
|
| 725 |
+
| **Container hardening** | Docker: all capabilities dropped, no privilege escalation, PID limits, size-limited tmpfs |
|
| 726 |
+
|
| 727 |
+
### When contributing security-sensitive code
|
| 728 |
+
|
| 729 |
+
- **Always use `shlex.quote()`** when interpolating user input into shell commands
|
| 730 |
+
- **Resolve symlinks** with `os.path.realpath()` before path-based access control checks
|
| 731 |
+
- **Don't log secrets.** API keys, tokens, and passwords should never appear in log output
|
| 732 |
+
- **Catch broad exceptions** around tool execution so a single failure doesn't crash the agent loop
|
| 733 |
+
- **Test on all platforms** if your change touches file paths, process management, or shell commands
|
| 734 |
+
|
| 735 |
+
If your PR affects security, note it explicitly in the description.
|
| 736 |
+
|
| 737 |
+
---
|
| 738 |
+
|
| 739 |
+
## Pull Request Process
|
| 740 |
+
|
| 741 |
+
### Branch naming
|
| 742 |
+
|
| 743 |
+
```
|
| 744 |
+
fix/description # Bug fixes
|
| 745 |
+
feat/description # New features
|
| 746 |
+
docs/description # Documentation
|
| 747 |
+
test/description # Tests
|
| 748 |
+
refactor/description # Code restructuring
|
| 749 |
+
```
|
| 750 |
+
|
| 751 |
+
### Before submitting
|
| 752 |
+
|
| 753 |
+
1. **Run tests**: `scripts/run_tests.sh` (recommended; same as CI) or `pytest tests/ -v` with the project venv activated
|
| 754 |
+
2. **Test manually**: Run `hermes` and exercise the code path you changed
|
| 755 |
+
3. **Check cross-platform impact**: If you touch file I/O, process management, or terminal handling, consider macOS, Linux, and WSL2
|
| 756 |
+
4. **Keep PRs focused**: One logical change per PR. Don't mix a bug fix with a refactor with a new feature.
|
| 757 |
+
|
| 758 |
+
### PR description
|
| 759 |
+
|
| 760 |
+
Include:
|
| 761 |
+
- **What** changed and **why**
|
| 762 |
+
- **How to test** it (reproduction steps for bugs, usage examples for features)
|
| 763 |
+
- **What platforms** you tested on
|
| 764 |
+
- Reference any related issues
|
| 765 |
+
|
| 766 |
+
### Commit messages
|
| 767 |
+
|
| 768 |
+
We use [Conventional Commits](https://www.conventionalcommits.org/):
|
| 769 |
+
|
| 770 |
+
```
|
| 771 |
+
<type>(<scope>): <description>
|
| 772 |
+
```
|
| 773 |
+
|
| 774 |
+
| Type | Use for |
|
| 775 |
+
|------|---------|
|
| 776 |
+
| `fix` | Bug fixes |
|
| 777 |
+
| `feat` | New features |
|
| 778 |
+
| `docs` | Documentation |
|
| 779 |
+
| `test` | Tests |
|
| 780 |
+
| `refactor` | Code restructuring (no behavior change) |
|
| 781 |
+
| `chore` | Build, CI, dependency updates |
|
| 782 |
+
|
| 783 |
+
Scopes: `cli`, `gateway`, `tools`, `skills`, `agent`, `install`, `whatsapp`, `security`, etc.
|
| 784 |
+
|
| 785 |
+
Examples:
|
| 786 |
+
```
|
| 787 |
+
fix(cli): prevent crash in save_config_value when model is a string
|
| 788 |
+
feat(gateway): add WhatsApp multi-user session isolation
|
| 789 |
+
fix(security): prevent shell injection in sudo password piping
|
| 790 |
+
test(tools): add unit tests for file_operations
|
| 791 |
+
```
|
| 792 |
+
|
| 793 |
+
---
|
| 794 |
+
|
| 795 |
+
## Reporting Issues
|
| 796 |
+
|
| 797 |
+
- Use [GitHub Issues](https://github.com/NousResearch/hermes-agent/issues)
|
| 798 |
+
- Include: OS, Python version, Hermes version (`hermes version`), full error traceback
|
| 799 |
+
- Include steps to reproduce
|
| 800 |
+
- Check existing issues before creating duplicates
|
| 801 |
+
- For security vulnerabilities, please report privately
|
| 802 |
+
|
| 803 |
+
---
|
| 804 |
+
|
| 805 |
+
## Community
|
| 806 |
+
|
| 807 |
+
- **Discord**: [discord.gg/NousResearch](https://discord.gg/NousResearch) — for questions, showcasing projects, and sharing skills
|
| 808 |
+
- **GitHub Discussions**: For design proposals and architecture discussions
|
| 809 |
+
- **Skills Hub**: Upload specialized skills to a registry and share them with the community
|
| 810 |
+
|
| 811 |
+
---
|
| 812 |
+
|
| 813 |
+
## License
|
| 814 |
+
|
| 815 |
+
By contributing, you agree that your contributions will be licensed under the [MIT License](LICENSE).
|
Dockerfile
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM ghcr.io/astral-sh/uv:0.11.6-python3.13-trixie@sha256:b3c543b6c4f23a5f2df22866bd7857e5d304b67a564f4feab6ac22044dde719b AS uv_source
|
| 2 |
+
FROM tianon/gosu:1.19-trixie@sha256:3b176695959c71e123eb390d427efc665eeb561b1540e82679c15e992006b8b9 AS gosu_source
|
| 3 |
+
FROM debian:13.4
|
| 4 |
+
|
| 5 |
+
# Disable Python stdout buffering to ensure logs are printed immediately
|
| 6 |
+
ENV PYTHONUNBUFFERED=1
|
| 7 |
+
|
| 8 |
+
# Store Playwright browsers outside the volume mount so the build-time
|
| 9 |
+
# install survives the /opt/data volume overlay at runtime.
|
| 10 |
+
ENV PLAYWRIGHT_BROWSERS_PATH=/opt/hermes/.playwright
|
| 11 |
+
|
| 12 |
+
# Install system dependencies in one layer, clear APT cache
|
| 13 |
+
# tini reaps orphaned zombie processes (MCP stdio subprocesses, git, bun, etc.)
|
| 14 |
+
# that would otherwise accumulate when hermes runs as PID 1. See #15012.
|
| 15 |
+
RUN apt-get update && \
|
| 16 |
+
apt-get install -y --no-install-recommends \
|
| 17 |
+
build-essential curl nodejs npm python3 ripgrep ffmpeg gcc python3-dev libffi-dev procps git openssh-client docker-cli tini && \
|
| 18 |
+
rm -rf /var/lib/apt/lists/*
|
| 19 |
+
|
| 20 |
+
# Non-root user for runtime; UID can be overridden via HERMES_UID at runtime
|
| 21 |
+
RUN useradd -u 10000 -m -d /opt/data hermes
|
| 22 |
+
|
| 23 |
+
COPY --chmod=0755 --from=gosu_source /gosu /usr/local/bin/
|
| 24 |
+
COPY --chmod=0755 --from=uv_source /usr/local/bin/uv /usr/local/bin/uvx /usr/local/bin/
|
| 25 |
+
|
| 26 |
+
WORKDIR /opt/hermes
|
| 27 |
+
|
| 28 |
+
# ---------- Layer-cached dependency install ----------
|
| 29 |
+
# Copy only package manifests first so npm install + Playwright are cached
|
| 30 |
+
# unless the lockfiles themselves change.
|
| 31 |
+
#
|
| 32 |
+
# ui-tui/packages/hermes-ink/ is copied IN FULL (not just its manifests)
|
| 33 |
+
# because it is referenced as a `file:` workspace dependency from
|
| 34 |
+
# ui-tui/package.json. Copying the tree up front lets npm resolve the
|
| 35 |
+
# workspace to real content instead of stopping at a bare package.json.
|
| 36 |
+
COPY package.json package-lock.json ./
|
| 37 |
+
COPY web/package.json web/package-lock.json web/
|
| 38 |
+
COPY ui-tui/package.json ui-tui/package-lock.json ui-tui/
|
| 39 |
+
COPY ui-tui/packages/hermes-ink/ ui-tui/packages/hermes-ink/
|
| 40 |
+
|
| 41 |
+
# `npm_config_install_links=false` forces npm to install `file:` deps as
|
| 42 |
+
# symlinks (the npm 10+ default) even on Debian's older bundled npm 9.x,
|
| 43 |
+
# which defaults to `install-links=true` and installs file deps as *copies*.
|
| 44 |
+
# The host-side package-lock.json is generated with a newer npm that uses
|
| 45 |
+
# symlinks, so an install-as-copy produces a hidden node_modules/.package-lock.json
|
| 46 |
+
# that permanently disagrees with the root lock on the @hermes/ink entry.
|
| 47 |
+
# That disagreement trips the TUI launcher's `_tui_need_npm_install()`
|
| 48 |
+
# check on every startup and triggers a runtime `npm install` that then
|
| 49 |
+
# fails with EACCES (node_modules/ is root-owned from build time).
|
| 50 |
+
ENV npm_config_install_links=false
|
| 51 |
+
|
| 52 |
+
RUN npm install --prefer-offline --no-audit && \
|
| 53 |
+
npx playwright install --with-deps chromium --only-shell && \
|
| 54 |
+
(cd web && npm install --prefer-offline --no-audit) && \
|
| 55 |
+
(cd ui-tui && npm install --prefer-offline --no-audit) && \
|
| 56 |
+
npm cache clean --force
|
| 57 |
+
|
| 58 |
+
# ---------- Layer-cached Python dependency install ----------
|
| 59 |
+
# Copy only pyproject.toml + uv.lock so the Python dep resolve + wheel
|
| 60 |
+
# download + native-extension compile layer is cached unless those inputs
|
| 61 |
+
# change. Before this split the Python install sat after `COPY . .`, so
|
| 62 |
+
# every source-only commit re-did ~4-5 min of dep work on cold builds.
|
| 63 |
+
#
|
| 64 |
+
# README.md is referenced by pyproject.toml's `readme =` field, but it's
|
| 65 |
+
# excluded from the build context by .dockerignore's `*.md`. uv's build
|
| 66 |
+
# frontend stats the readme path during dep resolution, so we `touch` an
|
| 67 |
+
# empty placeholder — the real README is restored by `COPY . .` below.
|
| 68 |
+
#
|
| 69 |
+
# `uv sync --frozen --no-install-project --extra all` installs only the
|
| 70 |
+
# deps reachable through the composite `[all]` extra (handpicked set
|
| 71 |
+
# intended for the production image). We do NOT use `--all-extras`:
|
| 72 |
+
# that would pull in `[rl]` (atroposlib + tinker + torch + wandb from
|
| 73 |
+
# git), `[yc-bench]` (another git dep), and `[termux-all]` (Android
|
| 74 |
+
# redundancy), none of which belong in the published container.
|
| 75 |
+
#
|
| 76 |
+
# The editable link is created after the source copy below.
|
| 77 |
+
COPY pyproject.toml uv.lock ./
|
| 78 |
+
RUN touch ./README.md
|
| 79 |
+
RUN uv sync --frozen --no-install-project --extra all
|
| 80 |
+
|
| 81 |
+
# ---------- Source code ----------
|
| 82 |
+
# .dockerignore excludes node_modules, so the installs above survive.
|
| 83 |
+
COPY --chown=hermes:hermes . .
|
| 84 |
+
|
| 85 |
+
# Build browser dashboard and terminal UI assets.
|
| 86 |
+
RUN cd web && npm run build && \
|
| 87 |
+
cd ../ui-tui && npm run build
|
| 88 |
+
|
| 89 |
+
# ---------- Permissions ----------
|
| 90 |
+
# Make install dir world-readable so any HERMES_UID can read it at runtime.
|
| 91 |
+
# The venv needs to be traversable too.
|
| 92 |
+
# node_modules trees additionally need to be writable by the hermes user
|
| 93 |
+
# so the runtime `npm install` triggered by _tui_need_npm_install() in
|
| 94 |
+
# hermes_cli/main.py succeeds (see #18800). /opt/hermes/web is build-time
|
| 95 |
+
# only (HERMES_WEB_DIST points at hermes_cli/web_dist) and is intentionally
|
| 96 |
+
# not chowned here.
|
| 97 |
+
USER root
|
| 98 |
+
RUN chmod -R a+rX /opt/hermes && \
|
| 99 |
+
chown -R hermes:hermes /opt/hermes/ui-tui /opt/hermes/node_modules
|
| 100 |
+
# Start as root so the entrypoint can usermod/groupmod + gosu.
|
| 101 |
+
# If HERMES_UID is unset, the entrypoint drops to the default hermes user (10000).
|
| 102 |
+
|
| 103 |
+
# ---------- Link hermes-agent itself (editable) ----------
|
| 104 |
+
# Deps are already installed in the cached layer above; `--no-deps` makes
|
| 105 |
+
# this a fast (~1s) egg-link creation with no resolution or downloads.
|
| 106 |
+
RUN uv pip install --no-cache-dir --no-deps -e "."
|
| 107 |
+
|
| 108 |
+
# ---------- Runtime ----------
|
| 109 |
+
ENV HERMES_WEB_DIST=/opt/hermes/hermes_cli/web_dist
|
| 110 |
+
ENV HERMES_HOME=/opt/data
|
| 111 |
+
ENV PATH="/opt/data/.local/bin:${PATH}"
|
| 112 |
+
VOLUME [ "/opt/data" ]
|
| 113 |
+
ENTRYPOINT [ "/usr/bin/tini", "-g", "--", "/opt/hermes/docker/entrypoint.sh" ]
|
LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MIT License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2025 Nous Research
|
| 4 |
+
|
| 5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 6 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 7 |
+
in the Software without restriction, including without limitation the rights
|
| 8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 9 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 10 |
+
furnished to do so, subject to the following conditions:
|
| 11 |
+
|
| 12 |
+
The above copyright notice and this permission notice shall be included in all
|
| 13 |
+
copies or substantial portions of the Software.
|
| 14 |
+
|
| 15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 21 |
+
SOFTWARE.
|
MANIFEST.in
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
graft skills
|
| 2 |
+
graft optional-skills
|
| 3 |
+
global-exclude __pycache__
|
| 4 |
+
global-exclude *.py[cod]
|
README.md
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<p align="center">
|
| 2 |
+
<img src="assets/banner.png" alt="Hermes Agent" width="100%">
|
| 3 |
+
</p>
|
| 4 |
+
|
| 5 |
+
# Hermes Agent ☤
|
| 6 |
+
|
| 7 |
+
<p align="center">
|
| 8 |
+
<a href="https://hermes-agent.nousresearch.com/docs/"><img src="https://img.shields.io/badge/Docs-hermes--agent.nousresearch.com-FFD700?style=for-the-badge" alt="Documentation"></a>
|
| 9 |
+
<a href="https://discord.gg/NousResearch"><img src="https://img.shields.io/badge/Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white" alt="Discord"></a>
|
| 10 |
+
<a href="https://github.com/NousResearch/hermes-agent/blob/main/LICENSE"><img src="https://img.shields.io/badge/License-MIT-green?style=for-the-badge" alt="License: MIT"></a>
|
| 11 |
+
<a href="https://nousresearch.com"><img src="https://img.shields.io/badge/Built%20by-Nous%20Research-blueviolet?style=for-the-badge" alt="Built by Nous Research"></a>
|
| 12 |
+
<a href="README.zh-CN.md"><img src="https://img.shields.io/badge/Lang-中文-red?style=for-the-badge" alt="中文"></a>
|
| 13 |
+
</p>
|
| 14 |
+
|
| 15 |
+
**The self-improving AI agent built by [Nous Research](https://nousresearch.com).** It's the only agent with a built-in learning loop — it creates skills from experience, improves them during use, nudges itself to persist knowledge, searches its own past conversations, and builds a deepening model of who you are across sessions. Run it on a $5 VPS, a GPU cluster, or serverless infrastructure that costs nearly nothing when idle. It's not tied to your laptop — talk to it from Telegram while it works on a cloud VM.
|
| 16 |
+
|
| 17 |
+
Use any model you want — [Nous Portal](https://portal.nousresearch.com), [OpenRouter](https://openrouter.ai) (200+ models), [NVIDIA NIM](https://build.nvidia.com) (Nemotron), [Xiaomi MiMo](https://platform.xiaomimimo.com), [z.ai/GLM](https://z.ai), [Kimi/Moonshot](https://platform.moonshot.ai), [MiniMax](https://www.minimax.io), [Hugging Face](https://huggingface.co), OpenAI, or your own endpoint. Switch with `hermes model` — no code changes, no lock-in.
|
| 18 |
+
|
| 19 |
+
<table>
|
| 20 |
+
<tr><td><b>A real terminal interface</b></td><td>Full TUI with multiline editing, slash-command autocomplete, conversation history, interrupt-and-redirect, and streaming tool output.</td></tr>
|
| 21 |
+
<tr><td><b>Lives where you do</b></td><td>Telegram, Discord, Slack, WhatsApp, Signal, and CLI — all from a single gateway process. Voice memo transcription, cross-platform conversation continuity.</td></tr>
|
| 22 |
+
<tr><td><b>A closed learning loop</b></td><td>Agent-curated memory with periodic nudges. Autonomous skill creation after complex tasks. Skills self-improve during use. FTS5 session search with LLM summarization for cross-session recall. <a href="https://github.com/plastic-labs/honcho">Honcho</a> dialectic user modeling. Compatible with the <a href="https://agentskills.io">agentskills.io</a> open standard.</td></tr>
|
| 23 |
+
<tr><td><b>Scheduled automations</b></td><td>Built-in cron scheduler with delivery to any platform. Daily reports, nightly backups, weekly audits — all in natural language, running unattended.</td></tr>
|
| 24 |
+
<tr><td><b>Delegates and parallelizes</b></td><td>Spawn isolated subagents for parallel workstreams. Write Python scripts that call tools via RPC, collapsing multi-step pipelines into zero-context-cost turns.</td></tr>
|
| 25 |
+
<tr><td><b>Runs anywhere, not just your laptop</b></td><td>Seven terminal backends — local, Docker, SSH, Singularity, Modal, Daytona, and Vercel Sandbox. Daytona and Modal offer serverless persistence — your agent's environment hibernates when idle and wakes on demand, costing nearly nothing between sessions. Run it on a $5 VPS or a GPU cluster.</td></tr>
|
| 26 |
+
<tr><td><b>Research-ready</b></td><td>Batch trajectory generation, Atropos RL environments, trajectory compression for training the next generation of tool-calling models.</td></tr>
|
| 27 |
+
</table>
|
| 28 |
+
|
| 29 |
+
---
|
| 30 |
+
|
| 31 |
+
## Quick Install
|
| 32 |
+
|
| 33 |
+
### Linux, macOS, WSL2, Termux
|
| 34 |
+
|
| 35 |
+
```bash
|
| 36 |
+
curl -fsSL https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.sh | bash
|
| 37 |
+
```
|
| 38 |
+
|
| 39 |
+
### Windows (native, PowerShell) — Early Beta
|
| 40 |
+
|
| 41 |
+
> **Heads up:** Native Windows support is **early beta**. It installs and runs, but hasn't been road-tested as broadly as our Linux/macOS/WSL2 paths. Please [file issues](https://github.com/NousResearch/hermes-agent/issues) when you hit rough edges. For the most battle-tested Windows setup today, run the Linux/macOS one-liner above inside **WSL2**.
|
| 42 |
+
|
| 43 |
+
Run this in PowerShell:
|
| 44 |
+
|
| 45 |
+
```powershell
|
| 46 |
+
irm https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.ps1 | iex
|
| 47 |
+
```
|
| 48 |
+
|
| 49 |
+
The installer handles everything: uv, Python 3.11, Node.js, ripgrep, ffmpeg, **and a portable Git Bash** (MinGit, unpacked to `%LOCALAPPDATA%\hermes\git` — no admin required, completely isolated from any system Git install). Hermes uses this bundled Git Bash to run shell commands.
|
| 50 |
+
|
| 51 |
+
If you already have Git installed, the installer detects it and uses that instead. Otherwise a ~45MB MinGit download is all you need — it won't touch or interfere with any system Git.
|
| 52 |
+
|
| 53 |
+
> **Android / Termux:** The tested manual path is documented in the [Termux guide](https://hermes-agent.nousresearch.com/docs/getting-started/termux). On Termux, Hermes installs a curated `.[termux]` extra because the full `.[all]` extra currently pulls Android-incompatible voice dependencies.
|
| 54 |
+
>
|
| 55 |
+
> **Windows:** Native Windows is supported as an **early beta** — the PowerShell one-liner above installs everything, but expect rough edges and please file issues when you hit them. If you'd rather use WSL2 (our most battle-tested Windows path), the Linux command works there too. Native Windows install lives under `%LOCALAPPDATA%\hermes`; WSL2 installs under `~/.hermes` as on Linux. The only Hermes feature that currently needs WSL2 specifically is the browser-based dashboard chat pane (it uses a POSIX PTY — classic CLI and gateway both run natively).
|
| 56 |
+
|
| 57 |
+
After installation:
|
| 58 |
+
|
| 59 |
+
```bash
|
| 60 |
+
source ~/.bashrc # reload shell (or: source ~/.zshrc)
|
| 61 |
+
hermes # start chatting!
|
| 62 |
+
```
|
| 63 |
+
|
| 64 |
+
---
|
| 65 |
+
|
| 66 |
+
## Getting Started
|
| 67 |
+
|
| 68 |
+
```bash
|
| 69 |
+
hermes # Interactive CLI — start a conversation
|
| 70 |
+
hermes model # Choose your LLM provider and model
|
| 71 |
+
hermes tools # Configure which tools are enabled
|
| 72 |
+
hermes config set # Set individual config values
|
| 73 |
+
hermes gateway # Start the messaging gateway (Telegram, Discord, etc.)
|
| 74 |
+
hermes setup # Run the full setup wizard (configures everything at once)
|
| 75 |
+
hermes claw migrate # Migrate from OpenClaw (if coming from OpenClaw)
|
| 76 |
+
hermes update # Update to the latest version
|
| 77 |
+
hermes doctor # Diagnose any issues
|
| 78 |
+
```
|
| 79 |
+
|
| 80 |
+
📖 **[Full documentation →](https://hermes-agent.nousresearch.com/docs/)**
|
| 81 |
+
|
| 82 |
+
## CLI vs Messaging Quick Reference
|
| 83 |
+
|
| 84 |
+
Hermes has two entry points: start the terminal UI with `hermes`, or run the gateway and talk to it from Telegram, Discord, Slack, WhatsApp, Signal, or Email. Once you're in a conversation, many slash commands are shared across both interfaces.
|
| 85 |
+
|
| 86 |
+
| Action | CLI | Messaging platforms |
|
| 87 |
+
|---------|-----|---------------------|
|
| 88 |
+
| Start chatting | `hermes` | Run `hermes gateway setup` + `hermes gateway start`, then send the bot a message |
|
| 89 |
+
| Start fresh conversation | `/new` or `/reset` | `/new` or `/reset` |
|
| 90 |
+
| Change model | `/model [provider:model]` | `/model [provider:model]` |
|
| 91 |
+
| Set a personality | `/personality [name]` | `/personality [name]` |
|
| 92 |
+
| Retry or undo the last turn | `/retry`, `/undo` | `/retry`, `/undo` |
|
| 93 |
+
| Compress context / check usage | `/compress`, `/usage`, `/insights [--days N]` | `/compress`, `/usage`, `/insights [days]` |
|
| 94 |
+
| Browse skills | `/skills` or `/<skill-name>` | `/<skill-name>` |
|
| 95 |
+
| Interrupt current work | `Ctrl+C` or send a new message | `/stop` or send a new message |
|
| 96 |
+
| Platform-specific status | `/platforms` | `/status`, `/sethome` |
|
| 97 |
+
|
| 98 |
+
For the full command lists, see the [CLI guide](https://hermes-agent.nousresearch.com/docs/user-guide/cli) and the [Messaging Gateway guide](https://hermes-agent.nousresearch.com/docs/user-guide/messaging).
|
| 99 |
+
|
| 100 |
+
---
|
| 101 |
+
|
| 102 |
+
## Documentation
|
| 103 |
+
|
| 104 |
+
All documentation lives at **[hermes-agent.nousresearch.com/docs](https://hermes-agent.nousresearch.com/docs/)**:
|
| 105 |
+
|
| 106 |
+
| Section | What's Covered |
|
| 107 |
+
|---------|---------------|
|
| 108 |
+
| [Quickstart](https://hermes-agent.nousresearch.com/docs/getting-started/quickstart) | Install → setup → first conversation in 2 minutes |
|
| 109 |
+
| [CLI Usage](https://hermes-agent.nousresearch.com/docs/user-guide/cli) | Commands, keybindings, personalities, sessions |
|
| 110 |
+
| [Configuration](https://hermes-agent.nousresearch.com/docs/user-guide/configuration) | Config file, providers, models, all options |
|
| 111 |
+
| [Messaging Gateway](https://hermes-agent.nousresearch.com/docs/user-guide/messaging) | Telegram, Discord, Slack, WhatsApp, Signal, Home Assistant |
|
| 112 |
+
| [Security](https://hermes-agent.nousresearch.com/docs/user-guide/security) | Command approval, DM pairing, container isolation |
|
| 113 |
+
| [Tools & Toolsets](https://hermes-agent.nousresearch.com/docs/user-guide/features/tools) | 40+ tools, toolset system, terminal backends |
|
| 114 |
+
| [Skills System](https://hermes-agent.nousresearch.com/docs/user-guide/features/skills) | Procedural memory, Skills Hub, creating skills |
|
| 115 |
+
| [Memory](https://hermes-agent.nousresearch.com/docs/user-guide/features/memory) | Persistent memory, user profiles, best practices |
|
| 116 |
+
| [MCP Integration](https://hermes-agent.nousresearch.com/docs/user-guide/features/mcp) | Connect any MCP server for extended capabilities |
|
| 117 |
+
| [Cron Scheduling](https://hermes-agent.nousresearch.com/docs/user-guide/features/cron) | Scheduled tasks with platform delivery |
|
| 118 |
+
| [Context Files](https://hermes-agent.nousresearch.com/docs/user-guide/features/context-files) | Project context that shapes every conversation |
|
| 119 |
+
| [Architecture](https://hermes-agent.nousresearch.com/docs/developer-guide/architecture) | Project structure, agent loop, key classes |
|
| 120 |
+
| [Contributing](https://hermes-agent.nousresearch.com/docs/developer-guide/contributing) | Development setup, PR process, code style |
|
| 121 |
+
| [CLI Reference](https://hermes-agent.nousresearch.com/docs/reference/cli-commands) | All commands and flags |
|
| 122 |
+
| [Environment Variables](https://hermes-agent.nousresearch.com/docs/reference/environment-variables) | Complete env var reference |
|
| 123 |
+
|
| 124 |
+
---
|
| 125 |
+
|
| 126 |
+
## Migrating from OpenClaw
|
| 127 |
+
|
| 128 |
+
If you're coming from OpenClaw, Hermes can automatically import your settings, memories, skills, and API keys.
|
| 129 |
+
|
| 130 |
+
**During first-time setup:** The setup wizard (`hermes setup`) automatically detects `~/.openclaw` and offers to migrate before configuration begins.
|
| 131 |
+
|
| 132 |
+
**Anytime after install:**
|
| 133 |
+
|
| 134 |
+
```bash
|
| 135 |
+
hermes claw migrate # Interactive migration (full preset)
|
| 136 |
+
hermes claw migrate --dry-run # Preview what would be migrated
|
| 137 |
+
hermes claw migrate --preset user-data # Migrate without secrets
|
| 138 |
+
hermes claw migrate --overwrite # Overwrite existing conflicts
|
| 139 |
+
```
|
| 140 |
+
|
| 141 |
+
What gets imported:
|
| 142 |
+
- **SOUL.md** — persona file
|
| 143 |
+
- **Memories** — MEMORY.md and USER.md entries
|
| 144 |
+
- **Skills** — user-created skills → `~/.hermes/skills/openclaw-imports/`
|
| 145 |
+
- **Command allowlist** — approval patterns
|
| 146 |
+
- **Messaging settings** — platform configs, allowed users, working directory
|
| 147 |
+
- **API keys** — allowlisted secrets (Telegram, OpenRouter, OpenAI, Anthropic, ElevenLabs)
|
| 148 |
+
- **TTS assets** — workspace audio files
|
| 149 |
+
- **Workspace instructions** — AGENTS.md (with `--workspace-target`)
|
| 150 |
+
|
| 151 |
+
See `hermes claw migrate --help` for all options, or use the `openclaw-migration` skill for an interactive agent-guided migration with dry-run previews.
|
| 152 |
+
|
| 153 |
+
---
|
| 154 |
+
|
| 155 |
+
## Contributing
|
| 156 |
+
|
| 157 |
+
We welcome contributions! See the [Contributing Guide](https://hermes-agent.nousresearch.com/docs/developer-guide/contributing) for development setup, code style, and PR process.
|
| 158 |
+
|
| 159 |
+
Quick start for contributors — clone and go with `setup-hermes.sh`:
|
| 160 |
+
|
| 161 |
+
```bash
|
| 162 |
+
git clone https://github.com/NousResearch/hermes-agent.git
|
| 163 |
+
cd hermes-agent
|
| 164 |
+
./setup-hermes.sh # installs uv, creates venv, installs .[all], symlinks ~/.local/bin/hermes
|
| 165 |
+
./hermes # auto-detects the venv, no need to `source` first
|
| 166 |
+
```
|
| 167 |
+
|
| 168 |
+
Manual path (equivalent to the above):
|
| 169 |
+
|
| 170 |
+
```bash
|
| 171 |
+
curl -LsSf https://astral.sh/uv/install.sh | sh
|
| 172 |
+
uv venv .venv --python 3.11
|
| 173 |
+
source .venv/bin/activate
|
| 174 |
+
uv pip install -e ".[all,dev]"
|
| 175 |
+
scripts/run_tests.sh
|
| 176 |
+
```
|
| 177 |
+
|
| 178 |
+
> **RL Training (optional):** The RL/Atropos integration (`environments/`) — see [`CONTRIBUTING.md`](https://github.com/NousResearch/hermes-agent/blob/main/CONTRIBUTING.md#development-setup) for the full setup.
|
| 179 |
+
|
| 180 |
+
---
|
| 181 |
+
|
| 182 |
+
## Community
|
| 183 |
+
|
| 184 |
+
- 💬 [Discord](https://discord.gg/NousResearch)
|
| 185 |
+
- 📚 [Skills Hub](https://agentskills.io)
|
| 186 |
+
- 🐛 [Issues](https://github.com/NousResearch/hermes-agent/issues)
|
| 187 |
+
- 🔌 [HermesClaw](https://github.com/AaronWong1999/hermesclaw) — Community WeChat bridge: Run Hermes Agent and OpenClaw on the same WeChat account.
|
| 188 |
+
|
| 189 |
+
---
|
| 190 |
+
|
| 191 |
+
## License
|
| 192 |
+
|
| 193 |
+
MIT — see [LICENSE](LICENSE).
|
| 194 |
+
|
| 195 |
+
Built by [Nous Research](https://nousresearch.com).
|
README.zh-CN.md
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<p align="center">
|
| 2 |
+
<img src="assets/banner.png" alt="Hermes Agent" width="100%">
|
| 3 |
+
</p>
|
| 4 |
+
|
| 5 |
+
# Hermes Agent ☤
|
| 6 |
+
|
| 7 |
+
<p align="center">
|
| 8 |
+
<a href="https://hermes-agent.nousresearch.com/docs/"><img src="https://img.shields.io/badge/Docs-hermes--agent.nousresearch.com-FFD700?style=for-the-badge" alt="Documentation"></a>
|
| 9 |
+
<a href="https://discord.gg/NousResearch"><img src="https://img.shields.io/badge/Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white" alt="Discord"></a>
|
| 10 |
+
<a href="https://github.com/NousResearch/hermes-agent/blob/main/LICENSE"><img src="https://img.shields.io/badge/License-MIT-green?style=for-the-badge" alt="License: MIT"></a>
|
| 11 |
+
<a href="https://nousresearch.com"><img src="https://img.shields.io/badge/Built%20by-Nous%20Research-blueviolet?style=for-the-badge" alt="Built by Nous Research"></a>
|
| 12 |
+
<a href="README.md"><img src="https://img.shields.io/badge/Lang-English-lightgrey?style=for-the-badge" alt="English"></a>
|
| 13 |
+
</p>
|
| 14 |
+
|
| 15 |
+
**由 [Nous Research](https://nousresearch.com) 构建的自进化 AI 代理。** 它是唯一内置学习闭环的智能代理——从经验中创建技能,在使用中改进技能,主动持久化知识,搜索过往对话,并在跨会话中逐步构建对你的深度理解。可以在 $5 的 VPS 上运行,也可以在 GPU 集群上运行,或者使用几乎零成本的 Serverless 基础设施。它不绑定你的笔记本——你可以在 Telegram 上与它对话,而它在云端 VM 上工作。
|
| 16 |
+
|
| 17 |
+
支持任意模型——[Nous Portal](https://portal.nousresearch.com)、[OpenRouter](https://openrouter.ai)(200+ 模型)、[NVIDIA NIM](https://build.nvidia.com)(Nemotron)、[小米 MiMo](https://platform.xiaomimimo.com)、[z.ai/GLM](https://z.ai)、[Kimi/Moonshot](https://platform.moonshot.ai)、[MiniMax](https://www.minimax.io)、[Hugging Face](https://huggingface.co)、OpenAI,或自定义端点。使用 `hermes model` 即可切换——无需改代码,无锁定。
|
| 18 |
+
|
| 19 |
+
<table>
|
| 20 |
+
<tr><td><b>真正的终端界面</b></td><td>完整的 TUI,支持多行编辑、斜杠命令自动补全、对话历史、中断重定向和流式工具输出。</td></tr>
|
| 21 |
+
<tr><td><b>随你所在</b></td><td>Telegram、Discord、Slack、WhatsApp、Signal 和 CLI——全部从单个网关进程运行。语音备忘录转写、跨平台对话连续性。</td></tr>
|
| 22 |
+
<tr><td><b>闭环学习</b></td><td>代理管理记忆并定期自我提醒。复杂任务后自动创建技能。技能在使用中自我改进。FTS5 会话搜索配合 LLM 摘要实现跨会话回溯。<a href="https://github.com/plastic-labs/honcho">Honcho</a> 辩证式用户建模。兼容 <a href="https://agentskills.io">agentskills.io</a> 开放标准。</td></tr>
|
| 23 |
+
<tr><td><b>定时自动化</b></td><td>内置 cron 调度器,支持向任何平台投递。日报、夜间备份、周审计——全部用自然语言描述,无人值守运行。</td></tr>
|
| 24 |
+
<tr><td><b>委派与并行</b></td><td>生成隔离子代理处理并行工作流。编写 Python 脚本通过 RPC 调用工具,将多步管道压缩为零上下文开销的轮次。</td></tr>
|
| 25 |
+
<tr><td><b>随处运行</b></td><td>六种终端后端——本地、Docker、SSH、Daytona、Singularity 和 Modal。Daytona 和 Modal 提供 Serverless 持久化——代理环境空闲时休眠、按需唤醒,空闲期间几乎零成本。$5 VPS 或 GPU 集群都能跑。</td></tr>
|
| 26 |
+
<tr><td><b>研究就绪</b></td><td>批量轨迹生成、Atropos RL 环境、轨迹压缩——用于训练下一代工具调用模型。</td></tr>
|
| 27 |
+
</table>
|
| 28 |
+
|
| 29 |
+
---
|
| 30 |
+
|
| 31 |
+
## 快速安装
|
| 32 |
+
|
| 33 |
+
```bash
|
| 34 |
+
curl -fsSL https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.sh | bash
|
| 35 |
+
```
|
| 36 |
+
|
| 37 |
+
支持 Linux、macOS、WSL2 和 Android (Termux)。安装程序会自动处理平台特定的配置。
|
| 38 |
+
|
| 39 |
+
> **Android / Termux:** 已测试的手动安装路径请参考 [Termux 指南](https://hermes-agent.nousresearch.com/docs/getting-started/termux)。在 Termux 上,Hermes 会安装精选的 `.[termux]` 扩展,因为完整的 `.[all]` 扩展会拉取 Android 不兼容的语音依赖。
|
| 40 |
+
>
|
| 41 |
+
> **Windows:** 原生 Windows 不受支持。请安装 [WSL2](https://learn.microsoft.com/zh-cn/windows/wsl/install) 并运行上述命令。
|
| 42 |
+
|
| 43 |
+
安装后:
|
| 44 |
+
|
| 45 |
+
```bash
|
| 46 |
+
source ~/.bashrc # 重新加载 shell(或: source ~/.zshrc)
|
| 47 |
+
hermes # 开始对话!
|
| 48 |
+
```
|
| 49 |
+
|
| 50 |
+
---
|
| 51 |
+
|
| 52 |
+
## 快速入门
|
| 53 |
+
|
| 54 |
+
```bash
|
| 55 |
+
hermes # 交互式 CLI — 开始对话
|
| 56 |
+
hermes model # 选择 LLM 提供商和模型
|
| 57 |
+
hermes tools # 配置启用的工具
|
| 58 |
+
hermes config set # 设置单个配置项
|
| 59 |
+
hermes gateway # 启动消息网关(Telegram、Discord 等)
|
| 60 |
+
hermes setup # 运行完整设置向导(一次性配置所有内容)
|
| 61 |
+
hermes claw migrate # 从 OpenClaw 迁移(如果来自 OpenClaw)
|
| 62 |
+
hermes update # 更新到最新版本
|
| 63 |
+
hermes doctor # 诊断问题
|
| 64 |
+
```
|
| 65 |
+
|
| 66 |
+
📖 **[完整文档 →](https://hermes-agent.nousresearch.com/docs/)**
|
| 67 |
+
|
| 68 |
+
## CLI 与消息平台 快速对照
|
| 69 |
+
|
| 70 |
+
Hermes 有两种入口:用 `hermes` 启动终端 UI,或运行网关从 Telegram、Discord、Slack、WhatsApp、Signal 或 Email 与之对话。进入对话后,许多斜杠命令在两种界面中通用。
|
| 71 |
+
|
| 72 |
+
| 操作 | CLI | 消息平台 |
|
| 73 |
+
|------|-----|----------|
|
| 74 |
+
| 开始对话 | `hermes` | 运行 `hermes gateway setup` + `hermes gateway start`,然后给机器人发消息 |
|
| 75 |
+
| 开始新对话 | `/new` 或 `/reset` | `/new` 或 `/reset` |
|
| 76 |
+
| 更换模型 | `/model [provider:model]` | `/model [provider:model]` |
|
| 77 |
+
| 设置人格 | `/personality [name]` | `/personality [name]` |
|
| 78 |
+
| 重试或撤销上一轮 | `/retry`、`/undo` | `/retry`、`/undo` |
|
| 79 |
+
| 压缩上下文 / 查看用量 | `/compress`、`/usage`、`/insights [--days N]` | `/compress`、`/usage`、`/insights [days]` |
|
| 80 |
+
| 浏览技能 | `/skills` 或 `/<skill-name>` | `/skills` 或 `/<skill-name>` |
|
| 81 |
+
| 中断当前工作 | `Ctrl+C` 或发送新消息 | `/stop` 或发送新消息 |
|
| 82 |
+
| 平台特定状态 | `/platforms` | `/status`、`/sethome` |
|
| 83 |
+
|
| 84 |
+
完整命令列表请参阅 [CLI 指南](https://hermes-agent.nousresearch.com/docs/user-guide/cli) 和 [消息网关指南](https://hermes-agent.nousresearch.com/docs/user-guide/messaging)。
|
| 85 |
+
|
| 86 |
+
---
|
| 87 |
+
|
| 88 |
+
## 文档
|
| 89 |
+
|
| 90 |
+
所有文档位于 **[hermes-agent.nousresearch.com/docs](https://hermes-agent.nousresearch.com/docs/)**:
|
| 91 |
+
|
| 92 |
+
| 章节 | 内容 |
|
| 93 |
+
|------|------|
|
| 94 |
+
| [快速开始](https://hermes-agent.nousresearch.com/docs/getting-started/quickstart) | 安装 → 设置 → 2 分钟内开始首次对话 |
|
| 95 |
+
| [CLI 使用](https://hermes-agent.nousresearch.com/docs/user-guide/cli) | 命令、快捷键、人格、会话 |
|
| 96 |
+
| [配置](https://hermes-agent.nousresearch.com/docs/user-guide/configuration) | 配置文件、提供商、模型、所有选项 |
|
| 97 |
+
| [消息网关](https://hermes-agent.nousresearch.com/docs/user-guide/messaging) | Telegram、Discord、Slack、WhatsApp、Signal、Home Assistant |
|
| 98 |
+
| [安全](https://hermes-agent.nousresearch.com/docs/user-guide/security) | 命令审批、DM 配对、容器隔离 |
|
| 99 |
+
| [工具与工具集](https://hermes-agent.nousresearch.com/docs/user-guide/features/tools) | 40+ 工具、工具集系统、终端后端 |
|
| 100 |
+
| [技能系统](https://hermes-agent.nousresearch.com/docs/user-guide/features/skills) | 过程记忆、技能中心、创建技能 |
|
| 101 |
+
| [记忆](https://hermes-agent.nousresearch.com/docs/user-guide/features/memory) | 持久记忆、用户画像、最佳实践 |
|
| 102 |
+
| [MCP 集成](https://hermes-agent.nousresearch.com/docs/user-guide/features/mcp) | 连接任意 MCP 服务器扩展能力 |
|
| 103 |
+
| [定时调度](https://hermes-agent.nousresearch.com/docs/user-guide/features/cron) | 定时任务与平台投递 |
|
| 104 |
+
| [上下文文件](https://hermes-agent.nousresearch.com/docs/user-guide/features/context-files) | 影响每次对话的项目上下文 |
|
| 105 |
+
| [架构](https://hermes-agent.nousresearch.com/docs/developer-guide/architecture) | 项目结构、代理循环、关键类 |
|
| 106 |
+
| [贡献](https://hermes-agent.nousresearch.com/docs/developer-guide/contributing) | 开发设置、PR 流程、代码风格 |
|
| 107 |
+
| [CLI 参考](https://hermes-agent.nousresearch.com/docs/reference/cli-commands) | 所有命令和标志 |
|
| 108 |
+
| [环境变量](https://hermes-agent.nousresearch.com/docs/reference/environment-variables) | 完整环境变量参考 |
|
| 109 |
+
|
| 110 |
+
---
|
| 111 |
+
|
| 112 |
+
## 从 OpenClaw 迁移
|
| 113 |
+
|
| 114 |
+
如果你来自 OpenClaw,Hermes 可以自动导入你的设置、记忆、技能和 API 密钥。
|
| 115 |
+
|
| 116 |
+
**首次安装时:** 安装向导(`hermes setup`)会自动检测 `~/.openclaw` 并在配置开始前提供迁移选项。
|
| 117 |
+
|
| 118 |
+
**安装后任意时间:**
|
| 119 |
+
|
| 120 |
+
```bash
|
| 121 |
+
hermes claw migrate # 交互式迁移(完整预设)
|
| 122 |
+
hermes claw migrate --dry-run # 预览将要迁移的内容
|
| 123 |
+
hermes claw migrate --preset user-data # 仅迁移用户数据,不含密钥
|
| 124 |
+
hermes claw migrate --overwrite # 覆盖已有冲突
|
| 125 |
+
```
|
| 126 |
+
|
| 127 |
+
导入内容:
|
| 128 |
+
- **SOUL.md** — 人格文件
|
| 129 |
+
- **记忆** — MEMORY.md 和 USER.md 条目
|
| 130 |
+
- **技能** — 用户创建的技能 → `~/.hermes/skills/openclaw-imports/`
|
| 131 |
+
- **命令白名单** — 审批模式
|
| 132 |
+
- **消息设置** — 平台配置、允许用户、工作目录
|
| 133 |
+
- **API 密钥** — 白名单中的密钥(Telegram、OpenRouter、OpenAI、Anthropic、ElevenLabs)
|
| 134 |
+
- **TTS 资产** — 工作区音频文件
|
| 135 |
+
- **工作区指令** — AGENTS.md(使用 `--workspace-target`)
|
| 136 |
+
|
| 137 |
+
使用 `hermes claw migrate --help` 查看所有选项,或使用 `openclaw-migration` 技能进行交互式代理引导迁移(含干运行预览)。
|
| 138 |
+
|
| 139 |
+
---
|
| 140 |
+
|
| 141 |
+
## 贡献
|
| 142 |
+
|
| 143 |
+
欢迎贡献!请参阅 [贡献指南](https://hermes-agent.nousresearch.com/docs/developer-guide/contributing) 了解开发设置、代码风格和 PR 流程。
|
| 144 |
+
|
| 145 |
+
贡献者快速开始——克隆并使用 `setup-hermes.sh`:
|
| 146 |
+
|
| 147 |
+
```bash
|
| 148 |
+
git clone https://github.com/NousResearch/hermes-agent.git
|
| 149 |
+
cd hermes-agent
|
| 150 |
+
./setup-hermes.sh # 安装 uv、创建 venv、安装 .[all]、创建符号链接 ~/.local/bin/hermes
|
| 151 |
+
./hermes # 自动检测 venv,无需先 source
|
| 152 |
+
```
|
| 153 |
+
|
| 154 |
+
手动安装(等效于上述命令):
|
| 155 |
+
|
| 156 |
+
```bash
|
| 157 |
+
curl -LsSf https://astral.sh/uv/install.sh | sh
|
| 158 |
+
uv venv venv --python 3.11
|
| 159 |
+
source venv/bin/activate
|
| 160 |
+
uv pip install -e ".[all,dev]"
|
| 161 |
+
python -m pytest tests/ -q
|
| 162 |
+
```
|
| 163 |
+
|
| 164 |
+
> **RL 训练(可选):** 如需参与 RL/Tinker-Atropos 集成开发:
|
| 165 |
+
> ```bash
|
| 166 |
+
> git submodule update --init tinker-atropos
|
| 167 |
+
> uv pip install -e "./tinker-atropos"
|
| 168 |
+
> ```
|
| 169 |
+
|
| 170 |
+
---
|
| 171 |
+
|
| 172 |
+
## 社区
|
| 173 |
+
|
| 174 |
+
- 💬 [Discord](https://discord.gg/NousResearch)
|
| 175 |
+
- 📚 [技能中心](https://agentskills.io)
|
| 176 |
+
- 🐛 [问题反馈](https://github.com/NousResearch/hermes-agent/issues)
|
| 177 |
+
- 💡 [讨论区](https://github.com/NousResearch/hermes-agent/discussions)
|
| 178 |
+
- 🔌 [HermesClaw](https://github.com/AaronWong1999/hermesclaw) — 社区微信桥接:在同一微信账号上运行 Hermes Agent 和 OpenClaw。
|
| 179 |
+
|
| 180 |
+
---
|
| 181 |
+
|
| 182 |
+
## 许可证
|
| 183 |
+
|
| 184 |
+
MIT — 详见 [LICENSE](LICENSE)。
|
| 185 |
+
|
| 186 |
+
由 [Nous Research](https://nousresearch.com) 构建。
|
RELEASE_v0.10.0.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Hermes Agent v0.10.0 (v2026.4.16)
|
| 2 |
+
|
| 3 |
+
**Release Date:** April 16, 2026
|
| 4 |
+
|
| 5 |
+
> The Tool Gateway release — paid Nous Portal subscribers can now use web search, image generation, text-to-speech, and browser automation through their existing subscription with zero additional API keys.
|
| 6 |
+
|
| 7 |
+
---
|
| 8 |
+
|
| 9 |
+
## ✨ Highlights
|
| 10 |
+
|
| 11 |
+
- **Nous Tool Gateway** — Paid [Nous Portal](https://portal.nousresearch.com) subscribers now get automatic access to **web search** (Firecrawl), **image generation** (FAL / FLUX 2 Pro), **text-to-speech** (OpenAI TTS), and **browser automation** (Browser Use) through their existing subscription. No separate API keys needed — just run `hermes model`, select Nous Portal, and pick which tools to enable. Per-tool opt-in via `use_gateway` config, full integration with `hermes tools` and `hermes status`, and the runtime correctly prefers the gateway even when direct API keys exist. Replaces the old hidden `HERMES_ENABLE_NOUS_MANAGED_TOOLS` env var with clean subscription-based detection. ([#11206](https://github.com/NousResearch/hermes-agent/pull/11206), based on work by @jquesnelle; docs: [#11208](https://github.com/NousResearch/hermes-agent/pull/11208))
|
| 12 |
+
|
| 13 |
+
---
|
| 14 |
+
|
| 15 |
+
## 🐛 Bug Fixes & Improvements
|
| 16 |
+
|
| 17 |
+
This release includes 180+ commits with numerous bug fixes, platform improvements, and reliability enhancements across the agent core, gateway, CLI, and tool system. Full details will be published in the v0.11.0 changelog.
|
| 18 |
+
|
| 19 |
+
---
|
| 20 |
+
|
| 21 |
+
## 👥 Contributors
|
| 22 |
+
|
| 23 |
+
- **@jquesnelle** (emozilla) — Original Tool Gateway implementation ([#10799](https://github.com/NousResearch/hermes-agent/pull/10799)), salvaged and shipped in this release
|
| 24 |
+
|
| 25 |
+
---
|
| 26 |
+
|
| 27 |
+
**Full Changelog**: [v2026.4.13...v2026.4.16](https://github.com/NousResearch/hermes-agent/compare/v2026.4.13...v2026.4.16)
|
RELEASE_v0.11.0.md
ADDED
|
@@ -0,0 +1,453 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Hermes Agent v0.11.0 (v2026.4.23)
|
| 2 |
+
|
| 3 |
+
**Release Date:** April 23, 2026
|
| 4 |
+
**Since v0.9.0:** 1,556 commits · 761 merged PRs · 1,314 files changed · 224,174 insertions · 29 community contributors (290 including co-authors)
|
| 5 |
+
|
| 6 |
+
> The Interface release — a full React/Ink rewrite of the interactive CLI, a pluggable transport architecture underneath every provider, native AWS Bedrock support, five new inference paths, a 17th messaging platform (QQBot), a dramatically expanded plugin surface, and GPT-5.5 via Codex OAuth.
|
| 7 |
+
|
| 8 |
+
This release also folds in all the highlights deferred from v0.10.0 (which shipped only the Nous Tool Gateway) — so it covers roughly two weeks of work across the whole stack.
|
| 9 |
+
|
| 10 |
+
---
|
| 11 |
+
|
| 12 |
+
## ✨ Highlights
|
| 13 |
+
|
| 14 |
+
- **New Ink-based TUI** — `hermes --tui` is now a full React/Ink rewrite of the interactive CLI, with a Python JSON-RPC backend (`tui_gateway`). Sticky composer, live streaming with OSC-52 clipboard support, stable picker keys, status bar with per-turn stopwatch and git branch, `/clear` confirm, light-theme preset, and a subagent spawn observability overlay. ~310 commits to `ui-tui/` + `tui_gateway/`. (@OutThisLife + Teknium)
|
| 15 |
+
|
| 16 |
+
- **Transport ABC + Native AWS Bedrock** — Format conversion and HTTP transport were extracted from `run_agent.py` into a pluggable `agent/transports/` layer. `AnthropicTransport`, `ChatCompletionsTransport`, `ResponsesApiTransport`, and `BedrockTransport` each own their own format conversion and API shape. Native AWS Bedrock support via the Converse API ships on top of the new abstraction. ([#10549](https://github.com/NousResearch/hermes-agent/pull/10549), [#13347](https://github.com/NousResearch/hermes-agent/pull/13347), [#13366](https://github.com/NousResearch/hermes-agent/pull/13366), [#13430](https://github.com/NousResearch/hermes-agent/pull/13430), [#13805](https://github.com/NousResearch/hermes-agent/pull/13805), [#13814](https://github.com/NousResearch/hermes-agent/pull/13814) — @kshitijk4poor + Teknium)
|
| 17 |
+
|
| 18 |
+
- **Five new inference paths** — Native NVIDIA NIM ([#11774](https://github.com/NousResearch/hermes-agent/pull/11774)), Arcee AI ([#9276](https://github.com/NousResearch/hermes-agent/pull/9276)), Step Plan ([#13893](https://github.com/NousResearch/hermes-agent/pull/13893)), Google Gemini CLI OAuth ([#11270](https://github.com/NousResearch/hermes-agent/pull/11270)), and Vercel ai-gateway with pricing + dynamic discovery ([#13223](https://github.com/NousResearch/hermes-agent/pull/13223) — @jerilynzheng). Plus Gemini routed through the native AI Studio API for better performance ([#12674](https://github.com/NousResearch/hermes-agent/pull/12674)).
|
| 19 |
+
|
| 20 |
+
- **GPT-5.5 over Codex OAuth** — OpenAI's new GPT-5.5 reasoning model is now available through your ChatGPT Codex OAuth, with live model discovery wired into the model picker so new OpenAI releases show up without catalog updates. ([#14720](https://github.com/NousResearch/hermes-agent/pull/14720))
|
| 21 |
+
|
| 22 |
+
- **QQBot — 17th supported platform** — Native QQBot adapter via QQ Official API v2, with QR scan-to-configure setup wizard, streaming cursor, emoji reactions, and DM/group policy gating that matches WeCom/Weixin parity. ([#9364](https://github.com/NousResearch/hermes-agent/pull/9364), [#11831](https://github.com/NousResearch/hermes-agent/pull/11831))
|
| 23 |
+
|
| 24 |
+
- **Plugin surface expanded** — Plugins can now register slash commands (`register_command`), dispatch tools directly (`dispatch_tool`), block tool execution from hooks (`pre_tool_call` can veto), rewrite tool results (`transform_tool_result`), transform terminal output (`transform_terminal_output`), ship image_gen backends, and add custom dashboard tabs. The bundled disk-cleanup plugin is opt-in by default as a reference implementation. ([#9377](https://github.com/NousResearch/hermes-agent/pull/9377), [#10626](https://github.com/NousResearch/hermes-agent/pull/10626), [#10763](https://github.com/NousResearch/hermes-agent/pull/10763), [#10951](https://github.com/NousResearch/hermes-agent/pull/10951), [#12929](https://github.com/NousResearch/hermes-agent/pull/12929), [#12944](https://github.com/NousResearch/hermes-agent/pull/12944), [#12972](https://github.com/NousResearch/hermes-agent/pull/12972), [#13799](https://github.com/NousResearch/hermes-agent/pull/13799), [#14175](https://github.com/NousResearch/hermes-agent/pull/14175))
|
| 25 |
+
|
| 26 |
+
- **`/steer` — mid-run agent nudges** — `/steer <prompt>` injects a note that the running agent sees after its next tool call, without interrupting the turn or breaking prompt cache. For when you want to course-correct an agent in-flight. ([#12116](https://github.com/NousResearch/hermes-agent/pull/12116))
|
| 27 |
+
|
| 28 |
+
- **Shell hooks** — Wire any shell script as a Hermes lifecycle hook (pre_tool_call, post_tool_call, on_session_start, etc.) without writing a Python plugin. ([#13296](https://github.com/NousResearch/hermes-agent/pull/13296))
|
| 29 |
+
|
| 30 |
+
- **Webhook direct-delivery mode** — Webhook subscriptions can now forward payloads straight to a platform chat without going through the agent — zero-LLM push notifications for alerting, uptime checks, and event streams. ([#12473](https://github.com/NousResearch/hermes-agent/pull/12473))
|
| 31 |
+
|
| 32 |
+
- **Smarter delegation** — Subagents now have an explicit `orchestrator` role that can spawn their own workers, with configurable `max_spawn_depth` (default flat). Concurrent sibling subagents share filesystem state through a file-coordination layer so they don't clobber each other's edits. ([#13691](https://github.com/NousResearch/hermes-agent/pull/13691), [#13718](https://github.com/NousResearch/hermes-agent/pull/13718))
|
| 33 |
+
|
| 34 |
+
- **Auxiliary models — configurable UI + main-model-first** — `hermes model` has a dedicated "Configure auxiliary models" screen for per-task overrides (compression, vision, session_search, title_generation). `auto` routing now defaults to the main model for side tasks across all users (previously aggregator users were silently routed to a cheap provider-side default). ([#11891](https://github.com/NousResearch/hermes-agent/pull/11891), [#11900](https://github.com/NousResearch/hermes-agent/pull/11900))
|
| 35 |
+
|
| 36 |
+
- **Dashboard plugin system + live theme switching** — The web dashboard is now extensible. Third-party plugins can add custom tabs, widgets, and views without forking. Paired with a live-switching theme system — themes now control colors, fonts, layout, and density — so users can hot-swap the dashboard look without a reload. Same theming discipline the CLI has, now on the web. ([#10951](https://github.com/NousResearch/hermes-agent/pull/10951), [#10687](https://github.com/NousResearch/hermes-agent/pull/10687), [#14725](https://github.com/NousResearch/hermes-agent/pull/14725))
|
| 37 |
+
|
| 38 |
+
- **Dashboard polish** — i18n (English + Chinese), react-router sidebar layout, mobile-responsive, Vercel deployment, real per-session API call tracking, and one-click update + gateway restart buttons. ([#9228](https://github.com/NousResearch/hermes-agent/pull/9228), [#9370](https://github.com/NousResearch/hermes-agent/pull/9370), [#9453](https://github.com/NousResearch/hermes-agent/pull/9453), [#10686](https://github.com/NousResearch/hermes-agent/pull/10686), [#13526](https://github.com/NousResearch/hermes-agent/pull/13526), [#14004](https://github.com/NousResearch/hermes-agent/pull/14004) — @austinpickett + @DeployFaith + Teknium)
|
| 39 |
+
|
| 40 |
+
---
|
| 41 |
+
|
| 42 |
+
## 🏗️ Core Agent & Architecture
|
| 43 |
+
|
| 44 |
+
### Transport Layer (NEW)
|
| 45 |
+
- **Transport ABC** abstracts format conversion and HTTP transport from `run_agent.py` into `agent/transports/` ([#13347](https://github.com/NousResearch/hermes-agent/pull/13347))
|
| 46 |
+
- **AnthropicTransport** — Anthropic Messages API path ([#13366](https://github.com/NousResearch/hermes-agent/pull/13366), @kshitijk4poor)
|
| 47 |
+
- **ChatCompletionsTransport** — default path for OpenAI-compatible providers ([#13805](https://github.com/NousResearch/hermes-agent/pull/13805))
|
| 48 |
+
- **ResponsesApiTransport** — OpenAI Responses API + Codex build_kwargs wiring ([#13430](https://github.com/NousResearch/hermes-agent/pull/13430), @kshitijk4poor)
|
| 49 |
+
- **BedrockTransport** — AWS Bedrock Converse API transport ([#13814](https://github.com/NousResearch/hermes-agent/pull/13814))
|
| 50 |
+
|
| 51 |
+
### Provider & Model Support
|
| 52 |
+
- **Native AWS Bedrock provider** via Converse API ([#10549](https://github.com/NousResearch/hermes-agent/pull/10549))
|
| 53 |
+
- **NVIDIA NIM native provider** (salvage of #11703) ([#11774](https://github.com/NousResearch/hermes-agent/pull/11774))
|
| 54 |
+
- **Arcee AI direct provider** ([#9276](https://github.com/NousResearch/hermes-agent/pull/9276))
|
| 55 |
+
- **Step Plan provider** (salvage #6005) ([#13893](https://github.com/NousResearch/hermes-agent/pull/13893), @kshitijk4poor)
|
| 56 |
+
- **Google Gemini CLI OAuth** inference provider ([#11270](https://github.com/NousResearch/hermes-agent/pull/11270))
|
| 57 |
+
- **Vercel ai-gateway** with pricing, attribution, and dynamic discovery ([#13223](https://github.com/NousResearch/hermes-agent/pull/13223), @jerilynzheng)
|
| 58 |
+
- **GPT-5.5 over Codex OAuth** with live model discovery in the picker ([#14720](https://github.com/NousResearch/hermes-agent/pull/14720))
|
| 59 |
+
- **Gemini routed through native AI Studio API** ([#12674](https://github.com/NousResearch/hermes-agent/pull/12674))
|
| 60 |
+
- **xAI Grok upgraded to Responses API** ([#10783](https://github.com/NousResearch/hermes-agent/pull/10783))
|
| 61 |
+
- **Ollama improvements** — Cloud provider support, GLM continuation, `think=false` control, surrogate sanitization, `/v1` hint ([#10782](https://github.com/NousResearch/hermes-agent/pull/10782))
|
| 62 |
+
- **Kimi K2.6** across OpenRouter, Nous Portal, native Kimi, and HuggingFace ([#13148](https://github.com/NousResearch/hermes-agent/pull/13148), [#13152](https://github.com/NousResearch/hermes-agent/pull/13152), [#13169](https://github.com/NousResearch/hermes-agent/pull/13169))
|
| 63 |
+
- **Kimi K2.5** promoted to first position in all model suggestion lists ([#11745](https://github.com/NousResearch/hermes-agent/pull/11745), @kshitijk4poor)
|
| 64 |
+
- **Xiaomi MiMo v2.5-pro + v2.5** on OpenRouter, Nous Portal, and native ([#14184](https://github.com/NousResearch/hermes-agent/pull/14184), [#14635](https://github.com/NousResearch/hermes-agent/pull/14635), @kshitijk4poor)
|
| 65 |
+
- **GLM-5V-Turbo** for coding plan ([#9907](https://github.com/NousResearch/hermes-agent/pull/9907))
|
| 66 |
+
- **Claude Opus 4.7** in Nous Portal catalog ([#11398](https://github.com/NousResearch/hermes-agent/pull/11398))
|
| 67 |
+
- **OpenRouter elephant-alpha** in curated lists ([#9378](https://github.com/NousResearch/hermes-agent/pull/9378))
|
| 68 |
+
- **OpenCode-Go** — Kimi K2.6 and Qwen3.5/3.6 Plus in curated catalog ([#13429](https://github.com/NousResearch/hermes-agent/pull/13429))
|
| 69 |
+
- **minimax/minimax-m2.5:free** in OpenRouter catalog ([#13836](https://github.com/NousResearch/hermes-agent/pull/13836))
|
| 70 |
+
- **`/model` merges models.dev entries** for lesser-loved providers ([#14221](https://github.com/NousResearch/hermes-agent/pull/14221))
|
| 71 |
+
- **Per-provider + per-model `request_timeout_seconds`** config ([#12652](https://github.com/NousResearch/hermes-agent/pull/12652))
|
| 72 |
+
- **Configurable API retry count** via `agent.api_max_retries` ([#14730](https://github.com/NousResearch/hermes-agent/pull/14730))
|
| 73 |
+
- **ctx_size context length key** for Lemonade server (salvage #8536) ([#14215](https://github.com/NousResearch/hermes-agent/pull/14215))
|
| 74 |
+
- **Custom provider display name prompt** ([#9420](https://github.com/NousResearch/hermes-agent/pull/9420))
|
| 75 |
+
- **Recommendation badges** on tool provider selection ([#9929](https://github.com/NousResearch/hermes-agent/pull/9929))
|
| 76 |
+
- Fix: correct GPT-5 family context lengths in fallback defaults ([#9309](https://github.com/NousResearch/hermes-agent/pull/9309))
|
| 77 |
+
- Fix: clamp `minimal` reasoning effort to `low` on Responses API ([#9429](https://github.com/NousResearch/hermes-agent/pull/9429))
|
| 78 |
+
- Fix: strip reasoning item IDs from Responses API input when `store=False` ([#10217](https://github.com/NousResearch/hermes-agent/pull/10217))
|
| 79 |
+
- Fix: OpenViking correct account default + commit session on `/new` and compress ([#10463](https://github.com/NousResearch/hermes-agent/pull/10463))
|
| 80 |
+
- Fix: Kimi `/coding` thinking block survival + empty reasoning_content + block ordering (multiple PRs)
|
| 81 |
+
- Fix: don't send Anthropic thinking to api.kimi.com/coding ([#13826](https://github.com/NousResearch/hermes-agent/pull/13826))
|
| 82 |
+
- Fix: send `max_tokens`, `reasoning_effort`, and `thinking` for Kimi/Moonshot
|
| 83 |
+
- Fix: stream reasoning content through OpenAI-compatible providers that emit it
|
| 84 |
+
|
| 85 |
+
### Agent Loop & Conversation
|
| 86 |
+
- **`/steer <prompt>`** — mid-run agent nudges after next tool call ([#12116](https://github.com/NousResearch/hermes-agent/pull/12116))
|
| 87 |
+
- **Orchestrator role + configurable spawn depth** for `delegate_task` (default flat) ([#13691](https://github.com/NousResearch/hermes-agent/pull/13691))
|
| 88 |
+
- **Cross-agent file state coordination** for concurrent subagents ([#13718](https://github.com/NousResearch/hermes-agent/pull/13718))
|
| 89 |
+
- **Compressor smart collapse, dedup, anti-thrashing**, template upgrade, hardening ([#10088](https://github.com/NousResearch/hermes-agent/pull/10088))
|
| 90 |
+
- **Compression summaries respect the conversation's language** ([#12556](https://github.com/NousResearch/hermes-agent/pull/12556))
|
| 91 |
+
- **Compression model falls back to main model** on permanent 503/404 ([#10093](https://github.com/NousResearch/hermes-agent/pull/10093))
|
| 92 |
+
- **Auto-continue interrupted agent work** after gateway restart ([#9934](https://github.com/NousResearch/hermes-agent/pull/9934))
|
| 93 |
+
- **Activity heartbeats** prevent false gateway inactivity timeouts ([#10501](https://github.com/NousResearch/hermes-agent/pull/10501))
|
| 94 |
+
- **Auxiliary models UI** — dedicated screen for per-task overrides ([#11891](https://github.com/NousResearch/hermes-agent/pull/11891))
|
| 95 |
+
- **Auxiliary auto routing defaults to main model** for all users ([#11900](https://github.com/NousResearch/hermes-agent/pull/11900))
|
| 96 |
+
- **PLATFORM_HINTS for Matrix, Mattermost, Feishu** ([#14428](https://github.com/NousResearch/hermes-agent/pull/14428), @alt-glitch)
|
| 97 |
+
- Fix: reset retry counters after compression; stop poisoning conversation history ([#10055](https://github.com/NousResearch/hermes-agent/pull/10055))
|
| 98 |
+
- Fix: break compression-exhaustion infinite loop and auto-reset session ([#10063](https://github.com/NousResearch/hermes-agent/pull/10063))
|
| 99 |
+
- Fix: stale agent timeout, uv venv detection, empty response after tools ([#10065](https://github.com/NousResearch/hermes-agent/pull/10065))
|
| 100 |
+
- Fix: prevent premature loop exit when weak models return empty after substantive tool calls ([#10472](https://github.com/NousResearch/hermes-agent/pull/10472))
|
| 101 |
+
- Fix: preserve pre-start terminal interrupts ([#10504](https://github.com/NousResearch/hermes-agent/pull/10504))
|
| 102 |
+
- Fix: improve interrupt responsiveness during concurrent tool execution ([#10935](https://github.com/NousResearch/hermes-agent/pull/10935))
|
| 103 |
+
- Fix: word-wrap spinner, interruptable agent join, and delegate_task interrupt ([#10940](https://github.com/NousResearch/hermes-agent/pull/10940))
|
| 104 |
+
- Fix: `/stop` no longer resets the session ([#9224](https://github.com/NousResearch/hermes-agent/pull/9224))
|
| 105 |
+
- Fix: honor interrupts during MCP tool waits ([#9382](https://github.com/NousResearch/hermes-agent/pull/9382), @helix4u)
|
| 106 |
+
- Fix: break stuck session resume loops after repeated restarts ([#9941](https://github.com/NousResearch/hermes-agent/pull/9941))
|
| 107 |
+
- Fix: empty response nudge crash + placeholder leak to cron targets ([#11021](https://github.com/NousResearch/hermes-agent/pull/11021))
|
| 108 |
+
- Fix: streaming cursor sanitization to prevent message truncation (multiple PRs)
|
| 109 |
+
- Fix: resolve `context_length` for plugin context engines ([#9238](https://github.com/NousResearch/hermes-agent/pull/9238))
|
| 110 |
+
|
| 111 |
+
### Session & Memory
|
| 112 |
+
- **Auto-prune old sessions + VACUUM state.db** at startup ([#13861](https://github.com/NousResearch/hermes-agent/pull/13861))
|
| 113 |
+
- **Honcho overhaul** — context injection, 5-tool surface, cost safety, session isolation ([#10619](https://github.com/NousResearch/hermes-agent/pull/10619))
|
| 114 |
+
- **Hindsight richer session-scoped retain metadata** (salvage of #6290) ([#13987](https://github.com/NousResearch/hermes-agent/pull/13987))
|
| 115 |
+
- Fix: deduplicate memory provider tools to prevent 400 on strict providers ([#10511](https://github.com/NousResearch/hermes-agent/pull/10511))
|
| 116 |
+
- Fix: discover user-installed memory providers from `$HERMES_HOME/plugins/` ([#10529](https://github.com/NousResearch/hermes-agent/pull/10529))
|
| 117 |
+
- Fix: add `on_memory_write` bridge to sequential tool execution path ([#10507](https://github.com/NousResearch/hermes-agent/pull/10507))
|
| 118 |
+
- Fix: preserve `session_id` across `previous_response_id` chains in `/v1/responses` ([#10059](https://github.com/NousResearch/hermes-agent/pull/10059))
|
| 119 |
+
|
| 120 |
+
---
|
| 121 |
+
|
| 122 |
+
## 🖥️ New Ink-based TUI
|
| 123 |
+
|
| 124 |
+
A full React/Ink rewrite of the interactive CLI — invoked via `hermes --tui` or `HERMES_TUI=1`. Shipped across ~310 commits to `ui-tui/` and `tui_gateway/`.
|
| 125 |
+
|
| 126 |
+
### TUI Foundations
|
| 127 |
+
- New TUI based on Ink + Python JSON-RPC backend
|
| 128 |
+
- Prettier + ESLint + vitest tooling for `ui-tui/`
|
| 129 |
+
- Entry split between `src/entry.tsx` (TTY gate) and `src/app.tsx` (state machine)
|
| 130 |
+
- Persistent `_SlashWorker` subprocess for slash command dispatch
|
| 131 |
+
|
| 132 |
+
### UX & Features
|
| 133 |
+
- **Stable picker keys, /clear confirm, light-theme preset** ([#12312](https://github.com/NousResearch/hermes-agent/pull/12312), @OutThisLife)
|
| 134 |
+
- **Git branch in status bar** cwd label ([#12305](https://github.com/NousResearch/hermes-agent/pull/12305), @OutThisLife)
|
| 135 |
+
- **Per-turn elapsed stopwatch in FaceTicker + done-in sys line** ([#13105](https://github.com/NousResearch/hermes-agent/pull/13105), @OutThisLife)
|
| 136 |
+
- **Subagent spawn observability overlay** ([#14045](https://github.com/NousResearch/hermes-agent/pull/14045), @OutThisLife)
|
| 137 |
+
- **Per-prompt elapsed stopwatch in status bar** ([#12948](https://github.com/NousResearch/hermes-agent/pull/12948))
|
| 138 |
+
- Sticky composer that freezes during scroll
|
| 139 |
+
- OSC-52 clipboard support for copy across SSH sessions
|
| 140 |
+
- Virtualized history rendering for performance
|
| 141 |
+
- Slash command autocomplete via `complete.slash` RPC
|
| 142 |
+
- Path autocomplete via `complete.path` RPC
|
| 143 |
+
- Dozens of resize/ghosting/sticky-prompt fixes landed through the week
|
| 144 |
+
|
| 145 |
+
### Structural Refactors
|
| 146 |
+
- Decomposed `app.tsx` into `app/event-handler`, `app/slash-handler`, `app/stores`, `app/hooks` ([#14640](https://github.com/NousResearch/hermes-agent/pull/14640) and surrounding)
|
| 147 |
+
- Component split: `branding.tsx`, `markdown.tsx`, `prompts.tsx`, `sessionPicker.tsx`, `messageLine.tsx`, `thinking.tsx`, `maskedPrompt.tsx`
|
| 148 |
+
- Hook split: `useCompletion`, `useInputHistory`, `useQueue`, `useVirtualHistory`
|
| 149 |
+
|
| 150 |
+
---
|
| 151 |
+
|
| 152 |
+
## 📱 Messaging Platforms (Gateway)
|
| 153 |
+
|
| 154 |
+
### New Platforms
|
| 155 |
+
- **QQBot (17th platform)** — QQ Official API v2 adapter with QR setup, streaming, package split ([#9364](https://github.com/NousResearch/hermes-agent/pull/9364), [#11831](https://github.com/NousResearch/hermes-agent/pull/11831))
|
| 156 |
+
|
| 157 |
+
### Telegram
|
| 158 |
+
- **Dedicated `TELEGRAM_PROXY` env var + config.yaml proxy support** (closes #9414, #6530, #9074, #7786) ([#10681](https://github.com/NousResearch/hermes-agent/pull/10681))
|
| 159 |
+
- **`ignored_threads` config** for Telegram groups ([#9530](https://github.com/NousResearch/hermes-agent/pull/9530))
|
| 160 |
+
- **Config option to disable link previews** (closes #8728) ([#10610](https://github.com/NousResearch/hermes-agent/pull/10610))
|
| 161 |
+
- **Auto-wrap markdown tables** in code blocks ([#11794](https://github.com/NousResearch/hermes-agent/pull/11794))
|
| 162 |
+
- Fix: prevent duplicate replies when stream task is cancelled ([#9319](https://github.com/NousResearch/hermes-agent/pull/9319))
|
| 163 |
+
- Fix: prevent streaming cursor (▉) from appearing as standalone messages ([#9538](https://github.com/NousResearch/hermes-agent/pull/9538))
|
| 164 |
+
- Fix: retry transient tool sends + cold-boot budget ([#10947](https://github.com/NousResearch/hermes-agent/pull/10947))
|
| 165 |
+
- Fix: Markdown special char escaping in `send_exec_approval`
|
| 166 |
+
- Fix: parentheses in URLs during MarkdownV2 link conversion
|
| 167 |
+
- Fix: Unicode dash normalization in model switch (closes iOS smart-punctuation issue)
|
| 168 |
+
- Many platform hint / streaming / session-key fixes
|
| 169 |
+
|
| 170 |
+
### Discord
|
| 171 |
+
- **Forum channel support** (salvage of #10145 + media + polish) ([#11920](https://github.com/NousResearch/hermes-agent/pull/11920))
|
| 172 |
+
- **`DISCORD_ALLOWED_ROLES`** for role-based access control ([#11608](https://github.com/NousResearch/hermes-agent/pull/11608))
|
| 173 |
+
- **Config option to disable slash commands** (salvage #13130) ([#14315](https://github.com/NousResearch/hermes-agent/pull/14315))
|
| 174 |
+
- **Native `send_animation`** for inline GIF playback ([#10283](https://github.com/NousResearch/hermes-agent/pull/10283))
|
| 175 |
+
- **`send_message` Discord media attachments** ([#10246](https://github.com/NousResearch/hermes-agent/pull/10246))
|
| 176 |
+
- **`/skill` command group** with category subcommands ([#9909](https://github.com/NousResearch/hermes-agent/pull/9909))
|
| 177 |
+
- **Extract reply text from message references** ([#9781](https://github.com/NousResearch/hermes-agent/pull/9781))
|
| 178 |
+
|
| 179 |
+
### Feishu
|
| 180 |
+
- **Intelligent reply on document comments** with 3-tier access control ([#11898](https://github.com/NousResearch/hermes-agent/pull/11898))
|
| 181 |
+
- **Show processing state via reactions** on user messages ([#12927](https://github.com/NousResearch/hermes-agent/pull/12927))
|
| 182 |
+
- **Preserve @mention context for agent consumption** (salvage #13874) ([#14167](https://github.com/NousResearch/hermes-agent/pull/14167))
|
| 183 |
+
|
| 184 |
+
### DingTalk
|
| 185 |
+
- **`require_mention` + `allowed_users` gating** (parity with Slack/Telegram/Discord) ([#11564](https://github.com/NousResearch/hermes-agent/pull/11564))
|
| 186 |
+
- **QR-code device-flow authorization** for setup wizard ([#11574](https://github.com/NousResearch/hermes-agent/pull/11574))
|
| 187 |
+
- **AI Cards streaming, emoji reactions, and media handling** (salvage of #10985) ([#11910](https://github.com/NousResearch/hermes-agent/pull/11910))
|
| 188 |
+
|
| 189 |
+
### WhatsApp
|
| 190 |
+
- **`send_voice`** — native audio message delivery ([#13002](https://github.com/NousResearch/hermes-agent/pull/13002))
|
| 191 |
+
- **`dm_policy` and `group_policy`** parity with WeCom/Weixin/QQ adapters ([#13151](https://github.com/NousResearch/hermes-agent/pull/13151))
|
| 192 |
+
|
| 193 |
+
### WeCom / Weixin
|
| 194 |
+
- **WeCom QR-scan bot creation + interactive setup wizard** (salvage #13923) ([#13961](https://github.com/NousResearch/hermes-agent/pull/13961))
|
| 195 |
+
|
| 196 |
+
### Signal
|
| 197 |
+
- **Media delivery support** via `send_message` ([#13178](https://github.com/NousResearch/hermes-agent/pull/13178))
|
| 198 |
+
|
| 199 |
+
### Slack
|
| 200 |
+
- **Per-thread sessions for DMs by default** ([#10987](https://github.com/NousResearch/hermes-agent/pull/10987))
|
| 201 |
+
|
| 202 |
+
### BlueBubbles (iMessage)
|
| 203 |
+
- Group chat session separation, webhook registration & auth fixes ([#9806](https://github.com/NousResearch/hermes-agent/pull/9806))
|
| 204 |
+
|
| 205 |
+
### Gateway Core
|
| 206 |
+
- **Gateway proxy mode** — forward messages to a remote API server ([#9787](https://github.com/NousResearch/hermes-agent/pull/9787))
|
| 207 |
+
- **Per-channel ephemeral prompts** (Discord, Telegram, Slack, Mattermost) ([#10564](https://github.com/NousResearch/hermes-agent/pull/10564))
|
| 208 |
+
- **Surface plugin slash commands** natively on all platforms + decision-capable command hook ([#14175](https://github.com/NousResearch/hermes-agent/pull/14175))
|
| 209 |
+
- **Support document/archive extensions in MEDIA: tag extraction** (salvage #8255) ([#14307](https://github.com/NousResearch/hermes-agent/pull/14307))
|
| 210 |
+
- **Recognize `.pdf` in MEDIA: tag extraction** ([#13683](https://github.com/NousResearch/hermes-agent/pull/13683))
|
| 211 |
+
- **`--all` flag for `gateway start` and `restart`** ([#10043](https://github.com/NousResearch/hermes-agent/pull/10043))
|
| 212 |
+
- **Notify active sessions on gateway shutdown** + update health check ([#9850](https://github.com/NousResearch/hermes-agent/pull/9850))
|
| 213 |
+
- **Block agent from self-destructing the gateway** via terminal (closes #6666) ([#9895](https://github.com/NousResearch/hermes-agent/pull/9895))
|
| 214 |
+
- Fix: suppress duplicate replies on interrupt and streaming flood control ([#10235](https://github.com/NousResearch/hermes-agent/pull/10235))
|
| 215 |
+
- Fix: close temporary agents after one-off tasks ([#11028](https://github.com/NousResearch/hermes-agent/pull/11028), @kshitijk4poor)
|
| 216 |
+
- Fix: busy-session ack when user messages during active agent run ([#10068](https://github.com/NousResearch/hermes-agent/pull/10068))
|
| 217 |
+
- Fix: route watch-pattern notifications to the originating session ([#10460](https://github.com/NousResearch/hermes-agent/pull/10460))
|
| 218 |
+
- Fix: preserve notify context in executor threads ([#10921](https://github.com/NousResearch/hermes-agent/pull/10921), @kshitijk4poor)
|
| 219 |
+
- Fix: avoid duplicate replies after interrupted long tasks ([#11018](https://github.com/NousResearch/hermes-agent/pull/11018))
|
| 220 |
+
- Fix: unlink stale PID + lock files on cleanup
|
| 221 |
+
- Fix: force-unlink stale PID file after `--replace` takeover
|
| 222 |
+
|
| 223 |
+
---
|
| 224 |
+
|
| 225 |
+
## 🔧 Tool System
|
| 226 |
+
|
| 227 |
+
### Plugin Surface (major expansion)
|
| 228 |
+
- **`register_command()`** — plugins can now add slash commands ([#10626](https://github.com/NousResearch/hermes-agent/pull/10626))
|
| 229 |
+
- **`dispatch_tool()`** — plugins can invoke tools from their code ([#10763](https://github.com/NousResearch/hermes-agent/pull/10763))
|
| 230 |
+
- **`pre_tool_call` blocking** — plugins can veto tool execution ([#9377](https://github.com/NousResearch/hermes-agent/pull/9377))
|
| 231 |
+
- **`transform_tool_result`** — plugins rewrite tool results generically ([#12972](https://github.com/NousResearch/hermes-agent/pull/12972))
|
| 232 |
+
- **`transform_terminal_output`** — plugins rewrite terminal tool output ([#12929](https://github.com/NousResearch/hermes-agent/pull/12929))
|
| 233 |
+
- **Namespaced skill registration** for plugin skill bundles ([#9786](https://github.com/NousResearch/hermes-agent/pull/9786))
|
| 234 |
+
- **Opt-in-by-default + bundled disk-cleanup plugin** (salvage #12212) ([#12944](https://github.com/NousResearch/hermes-agent/pull/12944))
|
| 235 |
+
- **Pluggable `image_gen` backends + OpenAI provider** ([#13799](https://github.com/NousResearch/hermes-agent/pull/13799))
|
| 236 |
+
- **`openai-codex` image_gen plugin** (gpt-image-2 via Codex OAuth) ([#14317](https://github.com/NousResearch/hermes-agent/pull/14317))
|
| 237 |
+
- **Shell hooks** — wire shell scripts as hook callbacks ([#13296](https://github.com/NousResearch/hermes-agent/pull/13296))
|
| 238 |
+
|
| 239 |
+
### Browser
|
| 240 |
+
- **`browser_cdp` raw DevTools Protocol passthrough** ([#12369](https://github.com/NousResearch/hermes-agent/pull/12369))
|
| 241 |
+
- Camofox hardening + connection stability across the window
|
| 242 |
+
|
| 243 |
+
### Execute Code
|
| 244 |
+
- **Project/strict execution modes** (default: project) ([#11971](https://github.com/NousResearch/hermes-agent/pull/11971))
|
| 245 |
+
|
| 246 |
+
### Image Generation
|
| 247 |
+
- **Multi-model FAL support** with picker in `hermes tools` ([#11265](https://github.com/NousResearch/hermes-agent/pull/11265))
|
| 248 |
+
- **Recraft V3 → V4 Pro, Nano Banana → Pro upgrades** ([#11406](https://github.com/NousResearch/hermes-agent/pull/11406))
|
| 249 |
+
- **GPT Image 2** in FAL catalog ([#13677](https://github.com/NousResearch/hermes-agent/pull/13677))
|
| 250 |
+
- **xAI image generation provider** (grok-imagine-image) ([#14765](https://github.com/NousResearch/hermes-agent/pull/14765))
|
| 251 |
+
|
| 252 |
+
### TTS / STT / Voice
|
| 253 |
+
- **Google Gemini TTS provider** ([#11229](https://github.com/NousResearch/hermes-agent/pull/11229))
|
| 254 |
+
- **xAI Grok STT provider** ([#14473](https://github.com/NousResearch/hermes-agent/pull/14473))
|
| 255 |
+
- **xAI TTS** (shipped with Responses API upgrade) ([#10783](https://github.com/NousResearch/hermes-agent/pull/10783))
|
| 256 |
+
- **KittenTTS local provider** (salvage of #2109) ([#13395](https://github.com/NousResearch/hermes-agent/pull/13395))
|
| 257 |
+
- **CLI record beep toggle** ([#13247](https://github.com/NousResearch/hermes-agent/pull/13247), @helix4u)
|
| 258 |
+
|
| 259 |
+
### Webhook / Cron
|
| 260 |
+
- **Webhook direct-delivery mode** — zero-LLM push notifications ([#12473](https://github.com/NousResearch/hermes-agent/pull/12473))
|
| 261 |
+
- **Cron `wakeAgent` gate** — scripts can skip the agent entirely ([#12373](https://github.com/NousResearch/hermes-agent/pull/12373))
|
| 262 |
+
- **Cron per-job `enabled_toolsets`** — cap token overhead + cost per job ([#14767](https://github.com/NousResearch/hermes-agent/pull/14767))
|
| 263 |
+
|
| 264 |
+
### Delegate
|
| 265 |
+
- **Orchestrator role** + configurable spawn depth (default flat) ([#13691](https://github.com/NousResearch/hermes-agent/pull/13691))
|
| 266 |
+
- **Cross-agent file state coordination** ([#13718](https://github.com/NousResearch/hermes-agent/pull/13718))
|
| 267 |
+
|
| 268 |
+
### File / Patch
|
| 269 |
+
- **`patch` — "did you mean?" feedback** when patch fails to match ([#13435](https://github.com/NousResearch/hermes-agent/pull/13435))
|
| 270 |
+
|
| 271 |
+
### API Server
|
| 272 |
+
- **Stream `/v1/responses` SSE tool events** (salvage #9779) ([#10049](https://github.com/NousResearch/hermes-agent/pull/10049))
|
| 273 |
+
- **Inline image inputs** on `/v1/chat/completions` and `/v1/responses` ([#12969](https://github.com/NousResearch/hermes-agent/pull/12969))
|
| 274 |
+
|
| 275 |
+
### Docker / Podman
|
| 276 |
+
- **Entry-level Podman support** — `find_docker()` + rootless entrypoint ([#10066](https://github.com/NousResearch/hermes-agent/pull/10066))
|
| 277 |
+
- **Add docker-cli to Docker image** (salvage #10096) ([#14232](https://github.com/NousResearch/hermes-agent/pull/14232))
|
| 278 |
+
- **File-sync back to host on teardown** (salvage of #8189 + hardening) ([#11291](https://github.com/NousResearch/hermes-agent/pull/11291))
|
| 279 |
+
|
| 280 |
+
### MCP
|
| 281 |
+
- 12 MCP improvements across the window (status, timeout handling, tool-call forwarding, etc.)
|
| 282 |
+
|
| 283 |
+
---
|
| 284 |
+
|
| 285 |
+
## 🧩 Skills Ecosystem
|
| 286 |
+
|
| 287 |
+
### Skill System
|
| 288 |
+
- **Namespaced skill registration** for plugin bundles ([#9786](https://github.com/NousResearch/hermes-agent/pull/9786))
|
| 289 |
+
- **`hermes skills reset`** to un-stick bundled skills ([#11468](https://github.com/NousResearch/hermes-agent/pull/11468))
|
| 290 |
+
- **Skills guard opt-in** — `config.skills.guard_agent_created` (default off) ([#14557](https://github.com/NousResearch/hermes-agent/pull/14557))
|
| 291 |
+
- **Bundled skill scripts runnable out of the box** ([#13384](https://github.com/NousResearch/hermes-agent/pull/13384))
|
| 292 |
+
- **`xitter` replaced with `xurl`** — the official X API CLI ([#12303](https://github.com/NousResearch/hermes-agent/pull/12303))
|
| 293 |
+
- **MiniMax-AI/cli as default skill tap** (salvage #7501) ([#14493](https://github.com/NousResearch/hermes-agent/pull/14493))
|
| 294 |
+
- **Fuzzy `@` file completions + mtime sorting** ([#9467](https://github.com/NousResearch/hermes-agent/pull/9467))
|
| 295 |
+
|
| 296 |
+
### New Skills
|
| 297 |
+
- **concept-diagrams** (salvage of #11045, @v1k22) ([#11363](https://github.com/NousResearch/hermes-agent/pull/11363))
|
| 298 |
+
- **architecture-diagram** (Cocoon AI port) ([#9906](https://github.com/NousResearch/hermes-agent/pull/9906))
|
| 299 |
+
- **pixel-art** with hardware palettes and video animation ([#12663](https://github.com/NousResearch/hermes-agent/pull/12663), [#12725](https://github.com/NousResearch/hermes-agent/pull/12725))
|
| 300 |
+
- **baoyu-comic** ([#13257](https://github.com/NousResearch/hermes-agent/pull/13257), @JimLiu)
|
| 301 |
+
- **baoyu-infographic** — 21 layouts × 21 styles (salvage #9901) ([#12254](https://github.com/NousResearch/hermes-agent/pull/12254))
|
| 302 |
+
- **page-agent** — embed Alibaba's in-page GUI agent in your webapp ([#13976](https://github.com/NousResearch/hermes-agent/pull/13976))
|
| 303 |
+
- **fitness-nutrition** optional skill + optional env var support ([#9355](https://github.com/NousResearch/hermes-agent/pull/9355))
|
| 304 |
+
- **drug-discovery** — ChEMBL, PubChem, OpenFDA, ADMET ([#9443](https://github.com/NousResearch/hermes-agent/pull/9443))
|
| 305 |
+
- **touchdesigner-mcp** (salvage of #10081) ([#12298](https://github.com/NousResearch/hermes-agent/pull/12298))
|
| 306 |
+
- **adversarial-ux-test** optional skill (salvage of #2494, @omnissiah-comelse) ([#13425](https://github.com/NousResearch/hermes-agent/pull/13425))
|
| 307 |
+
- **maps** — added `guest_house`, `camp_site`, and dual-key bakery lookup ([#13398](https://github.com/NousResearch/hermes-agent/pull/13398))
|
| 308 |
+
- **llm-wiki** — port provenance markers, source hashing, and quality signals ([#13700](https://github.com/NousResearch/hermes-agent/pull/13700))
|
| 309 |
+
|
| 310 |
+
---
|
| 311 |
+
|
| 312 |
+
## 📊 Web Dashboard
|
| 313 |
+
|
| 314 |
+
- **i18n (English + Chinese) language switcher** ([#9453](https://github.com/NousResearch/hermes-agent/pull/9453))
|
| 315 |
+
- **Live-switching theme system** ([#10687](https://github.com/NousResearch/hermes-agent/pull/10687))
|
| 316 |
+
- **Dashboard plugin system** — extend the web UI with custom tabs ([#10951](https://github.com/NousResearch/hermes-agent/pull/10951))
|
| 317 |
+
- **react-router, sidebar layout, sticky header, dropdown component** ([#9370](https://github.com/NousResearch/hermes-agent/pull/9370), @austinpickett)
|
| 318 |
+
- **Responsive for mobile** ([#9228](https://github.com/NousResearch/hermes-agent/pull/9228), @DeployFaith)
|
| 319 |
+
- **Vercel deployment** ([#10686](https://github.com/NousResearch/hermes-agent/pull/10686), [#11061](https://github.com/NousResearch/hermes-agent/pull/11061), @austinpickett)
|
| 320 |
+
- **Context window config support** ([#9357](https://github.com/NousResearch/hermes-agent/pull/9357))
|
| 321 |
+
- **HTTP health probe for cross-container gateway detection** ([#9894](https://github.com/NousResearch/hermes-agent/pull/9894))
|
| 322 |
+
- **Update + restart gateway buttons** ([#13526](https://github.com/NousResearch/hermes-agent/pull/13526), @austinpickett)
|
| 323 |
+
- **Real API call count per session** (salvages #10140) ([#14004](https://github.com/NousResearch/hermes-agent/pull/14004))
|
| 324 |
+
|
| 325 |
+
---
|
| 326 |
+
|
| 327 |
+
## 🖱️ CLI & User Experience
|
| 328 |
+
|
| 329 |
+
- **Dynamic shell completion for bash, zsh, and fish** ([#9785](https://github.com/NousResearch/hermes-agent/pull/9785))
|
| 330 |
+
- **Light-mode skins + skin-aware completion menus** ([#9461](https://github.com/NousResearch/hermes-agent/pull/9461))
|
| 331 |
+
- **Numbered keyboard shortcuts** on approval and clarify prompts ([#13416](https://github.com/NousResearch/hermes-agent/pull/13416))
|
| 332 |
+
- **Markdown stripping, compact multiline previews, external editor** ([#12934](https://github.com/NousResearch/hermes-agent/pull/12934))
|
| 333 |
+
- **`--ignore-user-config` and `--ignore-rules` flags** (port codex#18646) ([#14277](https://github.com/NousResearch/hermes-agent/pull/14277))
|
| 334 |
+
- **Account limits section in `/usage`** ([#13428](https://github.com/NousResearch/hermes-agent/pull/13428))
|
| 335 |
+
- **Doctor: Command Installation check** for `hermes` bin symlink ([#10112](https://github.com/NousResearch/hermes-agent/pull/10112))
|
| 336 |
+
- **ESC cancels secret/sudo prompts**, clearer skip messaging ([#9902](https://github.com/NousResearch/hermes-agent/pull/9902))
|
| 337 |
+
- Fix: agent-facing text uses `display_hermes_home()` instead of hardcoded `~/.hermes` ([#10285](https://github.com/NousResearch/hermes-agent/pull/10285))
|
| 338 |
+
- Fix: enforce `config.yaml` as sole CWD source + deprecate `.env` CWD vars + add `hermes memory reset` ([#11029](https://github.com/NousResearch/hermes-agent/pull/11029))
|
| 339 |
+
|
| 340 |
+
---
|
| 341 |
+
|
| 342 |
+
## 🔒 Security & Reliability
|
| 343 |
+
|
| 344 |
+
- **Global toggle to allow private/internal URL resolution** ([#14166](https://github.com/NousResearch/hermes-agent/pull/14166))
|
| 345 |
+
- **Block agent from self-destructing the gateway** via terminal (closes #6666) ([#9895](https://github.com/NousResearch/hermes-agent/pull/9895))
|
| 346 |
+
- **Telegram callback authorization** on update prompts ([#10536](https://github.com/NousResearch/hermes-agent/pull/10536))
|
| 347 |
+
- **SECURITY.md** added ([#10532](https://github.com/NousResearch/hermes-agent/pull/10532), @I3eg1nner)
|
| 348 |
+
- **Warn about legacy hermes.service units** during `hermes update` ([#11918](https://github.com/NousResearch/hermes-agent/pull/11918))
|
| 349 |
+
- **Complete ASCII-locale UnicodeEncodeError recovery** for `api_messages`/`reasoning_content` (closes #6843) ([#10537](https://github.com/NousResearch/hermes-agent/pull/10537))
|
| 350 |
+
- **Prevent stale `os.environ` leak** after `clear_session_vars` ([#10527](https://github.com/NousResearch/hermes-agent/pull/10527))
|
| 351 |
+
- **Prevent agent hang when backgrounding processes** via terminal tool ([#10584](https://github.com/NousResearch/hermes-agent/pull/10584))
|
| 352 |
+
- Many smaller session-resume, interrupt, streaming, and memory-race fixes throughout the window
|
| 353 |
+
|
| 354 |
+
---
|
| 355 |
+
|
| 356 |
+
## 🐛 Notable Bug Fixes
|
| 357 |
+
|
| 358 |
+
The `fix:` category in this window covers 482 PRs. Highlights:
|
| 359 |
+
|
| 360 |
+
- Streaming cursor artifacts filtered from Matrix, Telegram, WhatsApp, Discord (multiple PRs)
|
| 361 |
+
- `<think>` and `<thought>` blocks filtered from gateway stream consumers ([#9408](https://github.com/NousResearch/hermes-agent/pull/9408))
|
| 362 |
+
- Gateway display.streaming root-config override regression ([#9799](https://github.com/NousResearch/hermes-agent/pull/9799))
|
| 363 |
+
- Context `session_search` coerces limit to int (prevents TypeError) ([#10522](https://github.com/NousResearch/hermes-agent/pull/10522))
|
| 364 |
+
- Memory tool stays available when `fcntl` is unavailable (Windows) ([#9783](https://github.com/NousResearch/hermes-agent/pull/9783))
|
| 365 |
+
- Trajectory compressor credentials load from `HERMES_HOME/.env` ([#9632](https://github.com/NousResearch/hermes-agent/pull/9632), @Dusk1e)
|
| 366 |
+
- `@_context_completions` no longer crashes on `@` mention ([#9683](https://github.com/NousResearch/hermes-agent/pull/9683), @kshitijk4poor)
|
| 367 |
+
- Group session `user_id` no longer treated as `thread_id` in shutdown notifications ([#10546](https://github.com/NousResearch/hermes-agent/pull/10546))
|
| 368 |
+
- Telegram `platform_hint` — markdown is supported (closes #8261) ([#10612](https://github.com/NousResearch/hermes-agent/pull/10612))
|
| 369 |
+
- Doctor checks for Kimi China credentials fixed
|
| 370 |
+
- Streaming: don't suppress final response when commentary message is sent ([#10540](https://github.com/NousResearch/hermes-agent/pull/10540))
|
| 371 |
+
- Rapid Telegram follow-ups no longer get cut off
|
| 372 |
+
|
| 373 |
+
---
|
| 374 |
+
|
| 375 |
+
## 🧪 Testing & CI
|
| 376 |
+
|
| 377 |
+
- **Contributor attribution CI check** on PRs ([#9376](https://github.com/NousResearch/hermes-agent/pull/9376))
|
| 378 |
+
- Hermetic test parity (`scripts/run_tests.sh`) held across this window
|
| 379 |
+
- Test count stabilized post-Transport refactor; CI matrix held green through the transport rollout
|
| 380 |
+
|
| 381 |
+
---
|
| 382 |
+
|
| 383 |
+
## 📚 Documentation
|
| 384 |
+
|
| 385 |
+
- Atropos + wandb links in user guide
|
| 386 |
+
- ACP / VS Code / Zed / JetBrains integration docs refresh
|
| 387 |
+
- Webhook subscription docs updated for direct-delivery mode
|
| 388 |
+
- Plugin author guide expanded for new hooks (`register_command`, `dispatch_tool`, `transform_tool_result`)
|
| 389 |
+
- Transport layer developer guide added
|
| 390 |
+
- Website removed Discussions link from README
|
| 391 |
+
|
| 392 |
+
---
|
| 393 |
+
|
| 394 |
+
## 👥 Contributors
|
| 395 |
+
|
| 396 |
+
### Core
|
| 397 |
+
- **@teknium1** (Teknium)
|
| 398 |
+
|
| 399 |
+
### Top Community Contributors (by merged PR count)
|
| 400 |
+
- **@kshitijk4poor** — 49 PRs · Transport refactor (AnthropicTransport, ResponsesApiTransport), Step Plan provider, Xiaomi MiMo v2.5 support, numerous gateway fixes, promoted Kimi K2.5, @ mention crash fix
|
| 401 |
+
- **@OutThisLife** (Brooklyn) — 31 PRs · TUI polish, git branch in status bar, per-turn stopwatch, stable picker keys, `/clear` confirm, light-theme preset, subagent spawn observability overlay
|
| 402 |
+
- **@helix4u** — 11 PRs · Voice CLI record beep, MCP tool interrupt handling, assorted stability fixes
|
| 403 |
+
- **@austinpickett** — 8 PRs · Dashboard react-router + sidebar + sticky header + dropdown, Vercel deployment, update + restart buttons
|
| 404 |
+
- **@alt-glitch** — 8 PRs · PLATFORM_HINTS for Matrix/Mattermost/Feishu, Matrix fixes
|
| 405 |
+
- **@ethernet8023** — 3 PRs
|
| 406 |
+
- **@benbarclay** — 3 PRs
|
| 407 |
+
- **@Aslaaen** — 2 PRs
|
| 408 |
+
|
| 409 |
+
### Also contributing
|
| 410 |
+
@jerilynzheng (ai-gateway pricing), @JimLiu (baoyu-comic skill), @Dusk1e (trajectory compressor credentials), @DeployFaith (mobile-responsive dashboard), @LeonSGP43, @v1k22 (concept-diagrams), @omnissiah-comelse (adversarial-ux-test), @coekfung (Telegram MarkdownV2 expandable blockquotes), @liftaris (TUI provider resolution), @arihantsethia (skill analytics dashboard), @topcheer + @xing8star (QQBot foundation), @kovyrin, @I3eg1nner (SECURITY.md), @PeterBerthelsen, @lengxii, @priveperfumes, @sjz-ks, @cuyua9, @Disaster-Terminator, @leozeli, @LehaoLin, @trevthefoolish, @loongfay, @MrNiceRicee, @WideLee, @bluefishs, @malaiwah, @bobashopcashier, @dsocolobsky, @iamagenius00, @IAvecilla, @aniruddhaadak80, @Es1la, @asheriif, @walli, @jquesnelle (original Tool Gateway work).
|
| 411 |
+
|
| 412 |
+
### All Contributors (alphabetical)
|
| 413 |
+
|
| 414 |
+
@0xyg3n, @10ishq, @A-afflatus, @Abnertheforeman, @admin28980, @adybag14-cyber, @akhater, @alexzhu0,
|
| 415 |
+
@AllardQuek, @alt-glitch, @aniruddhaadak80, @anna-oake, @anniesurla, @anthhub, @areu01or00, @arihantsethia,
|
| 416 |
+
@arthurbr11, @asheriif, @Aslaaen, @Asunfly, @austinpickett, @AviArora02-commits, @AxDSan, @azhengbot, @Bartok9,
|
| 417 |
+
@benbarclay, @bennytimz, @bernylinville, @bingo906, @binhnt92, @bkadish, @bluefishs, @bobashopcashier,
|
| 418 |
+
@brantzh6, @BrennerSpear, @brianclemens, @briandevans, @brooklynnicholson, @bugkill3r, @buray, @burtenshaw,
|
| 419 |
+
@cdanis, @cgarwood82, @ChimingLiu, @chongweiliu, @christopherwoodall, @coekfung, @cola-runner, @corazzione,
|
| 420 |
+
@counterposition, @cresslank, @cuyua9, @cypres0099, @danieldoderlein, @davetist, @davidvv, @DeployFaith,
|
| 421 |
+
@Dev-Mriganka, @devorun, @dieutx, @Disaster-Terminator, @dodo-reach, @draix, @DrStrangerUJN, @dsocolobsky,
|
| 422 |
+
@Dusk1e, @dyxushuai, @elkimek, @elmatadorgh, @emozilla, @entropidelic, @Erosika, @erosika, @Es1la, @etcircle,
|
| 423 |
+
@etherman-os, @ethernet8023, @fancydirty, @farion1231, @fatinghenji, @Fatty911, @fengtianyu88, @Feranmi10,
|
| 424 |
+
@flobo3, @francip, @fuleinist, @g-guthrie, @GenKoKo, @gianfrancopiana, @gnanam1990, @GuyCui, @haileymarshall,
|
| 425 |
+
@haimu0x, @handsdiff, @hansnow, @hedgeho9X, @helix4u, @hengm3467, @HenkDz, @heykb, @hharry11, @HiddenPuppy,
|
| 426 |
+
@honghua, @houko, @houziershi, @hsy5571616, @huangke19, @hxp-plus, @Hypn0sis, @I3eg1nner, @iacker,
|
| 427 |
+
@iamagenius00, @IAvecilla, @iborazzi, @Ifkellx, @ifrederico, @imink, @isaachuangGMICLOUD, @ismell0992-afk,
|
| 428 |
+
@j0sephz, @Jaaneek, @jackjin1997, @JackTheGit, @jaffarkeikei, @jerilynzheng, @JiaDe-Wu, @Jiawen-lee, @JimLiu,
|
| 429 |
+
@jinzheng8115, @jneeee, @jplew, @jquesnelle, @Julientalbot, @Junass1, @jvcl, @kagura-agent, @keifergu,
|
| 430 |
+
@kevinskysunny, @keyuyuan, @konsisumer, @kovyrin, @kshitijk4poor, @leeyang1990, @LehaoLin, @lengxii,
|
| 431 |
+
@LeonSGP43, @leozeli, @li0near, @liftaris, @Lind3ey, @Linux2010, @liujinkun2025, @LLQWQ, @Llugaes, @lmoncany,
|
| 432 |
+
@longsizhuo, @lrawnsley, @Lubrsy706, @lumenradley, @luyao618, @lvnilesh, @LVT382009, @m0n5t3r, @Magaav,
|
| 433 |
+
@MagicRay1217, @malaiwah, @manuelschipper, @Marvae, @MassiveMassimo, @mavrickdeveloper, @maxchernin, @memosr,
|
| 434 |
+
@meng93, @mengjian-github, @MestreY0d4-Uninter, @Mibayy, @MikeFac, @mikewaters, @milkoor, @minorgod,
|
| 435 |
+
@MrNiceRicee, @ms-alan, @mvanhorn, @n-WN, @N0nb0at, @Nan93, @NIDNASSER-Abdelmajid, @nish3451, @niyoh120,
|
| 436 |
+
@nocoo, @nosleepcassette, @NousResearch, @ogzerber, @omnissiah-comelse, @Only-Code-A, @opriz, @OwenYWT, @pedh,
|
| 437 |
+
@pefontana, @PeterBerthelsen, @phpoh, @pinion05, @plgonzalezrx8, @pradeep7127, @priveperfumes,
|
| 438 |
+
@projectadmin-dev, @PStarH, @rnijhara, @Roy-oss1, @roytian1217, @RucchiZ, @Ruzzgar, @RyanLee-Dev, @Salt-555,
|
| 439 |
+
@Sanjays2402, @sgaofen, @sharziki, @shenuu, @shin4, @SHL0MS, @shushuzn, @sicnuyudidi, @simon-gtcl,
|
| 440 |
+
@simon-marcus, @sirEven, @Sisyphus, @sjz-ks, @snreynolds, @Societus, @Somme4096, @sontianye, @sprmn24,
|
| 441 |
+
@StefanIsMe, @stephenschoettler, @Swift42, @taeng0204, @taeuk178, @tannerfokkens-maker, @TaroballzChen,
|
| 442 |
+
@ten-ltw, @teyrebaz33, @Tianworld, @topcheer, @Tranquil-Flow, @trevthefoolish, @TroyMitchell911, @UNLINEARITY,
|
| 443 |
+
@v1k22, @vivganes, @vominh1919, @vrinek, @VTRiot, @WadydX, @walli, @wenhao7, @WhiteWorld, @WideLee, @wujhsu,
|
| 444 |
+
@WuTianyi123, @Wysie, @xandersbell, @xiaoqiang243, @xiayh0107, @xinpengdr, @Xowiek, @ycbai, @yeyitech, @ygd58,
|
| 445 |
+
@youngDoo, @yudaiyan, @Yukipukii1, @yule975, @yyq4193, @yzx9, @ZaynJarvis, @zhang9w0v5, @zhanggttry,
|
| 446 |
+
@zhangxicen, @zhongyueming1121, @zhouxiaoya12, @zons-zhaozhy
|
| 447 |
+
|
| 448 |
+
Also: @maelrx, @Marco Rutsch, @MaxsolcuCrypto, @Mind-Dragon, @Paul Bergeron, @say8hi, @whitehatjr1001.
|
| 449 |
+
|
| 450 |
+
|
| 451 |
+
---
|
| 452 |
+
|
| 453 |
+
**Full Changelog**: [v2026.4.13...v2026.4.23](https://github.com/NousResearch/hermes-agent/compare/v2026.4.13...v2026.4.23)
|
RELEASE_v0.12.0.md
ADDED
|
@@ -0,0 +1,505 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Hermes Agent v0.12.0 (v2026.4.30)
|
| 2 |
+
|
| 3 |
+
**Release Date:** April 30, 2026
|
| 4 |
+
**Since v0.11.0:** 1,096 commits · 550 merged PRs · 1,270 files changed · 217,776 insertions · 213 community contributors (including co-authors)
|
| 5 |
+
|
| 6 |
+
> The Curator release — Hermes Agent now maintains itself. An autonomous background Curator grades, prunes, and consolidates your skill library on its own schedule. The self-improvement loop that reviews what to save got a substantial upgrade. Four new inference providers, a 18th messaging platform, a 19th via Teams plugin, native Spotify + Google Meet integrations, ComfyUI and TouchDesigner-MCP moved from optional to bundled-by-default, and a ~57% cut to visible TUI cold start.
|
| 7 |
+
|
| 8 |
+
---
|
| 9 |
+
|
| 10 |
+
## ✨ Highlights
|
| 11 |
+
|
| 12 |
+
- **Autonomous Curator** — `hermes curator` runs as a background agent on the gateway's cron ticker (7-day cycle default). It grades your skill library, consolidates related skills, prunes dead ones, and writes per-run reports to `logs/curator/run.json` + `REPORT.md`. Archived skills are classified consolidated-vs-pruned via model + heuristic. Defense-in-depth gates protect bundled/hub skills from mutation. Unified under `auxiliary.curator` — pick the curator's model in `hermes model`, manage it from the dashboard. `hermes curator status` ranks skills by usage (most-used / least-used). ([#17277](https://github.com/NousResearch/hermes-agent/pull/17277), [#17307](https://github.com/NousResearch/hermes-agent/pull/17307), [#17941](https://github.com/NousResearch/hermes-agent/pull/17941), [#17868](https://github.com/NousResearch/hermes-agent/pull/17868), [#18033](https://github.com/NousResearch/hermes-agent/pull/18033))
|
| 13 |
+
|
| 14 |
+
- **Self-improvement loop — substantially upgraded** — The background review fork (the core of Hermes' self-improvement: after each turn it decides what memories/skills to save or update) is now class-first (rubric-based rather than free-form), active-update biased (prefers the skill the agent just loaded), handles `references/`/`templates/` sub-files, and properly inherits the parent's live runtime (provider, model, credentials actually propagate). Restricted to memory + skills toolsets so it can't sprawl. Memory providers shut down cleanly. Prior-turn tool messages excluded from the summary so the fork sees a clean context. ([#16026](https://github.com/NousResearch/hermes-agent/pull/16026), [#17213](https://github.com/NousResearch/hermes-agent/pull/17213), [#16099](https://github.com/NousResearch/hermes-agent/pull/16099), [#16569](https://github.com/NousResearch/hermes-agent/pull/16569), [#16204](https://github.com/NousResearch/hermes-agent/pull/16204), [#15057](https://github.com/NousResearch/hermes-agent/pull/15057))
|
| 15 |
+
|
| 16 |
+
- **Skill integrations — major expansion** — **ComfyUI v5** with official CLI + REST + hardware-gated local install, moved from optional to **built-in by default** ([#17610](https://github.com/NousResearch/hermes-agent/pull/17610), [#17631](https://github.com/NousResearch/hermes-agent/pull/17631), [#17734](https://github.com/NousResearch/hermes-agent/pull/17734)). **TouchDesigner-MCP** bundled by default, expanded with GLSL, post-FX, audio, geometry, and 9 new reference docs ([#16753](https://github.com/NousResearch/hermes-agent/pull/16753), [#16624](https://github.com/NousResearch/hermes-agent/pull/16624), [#16768](https://github.com/NousResearch/hermes-agent/pull/16768) — @kshitijk4poor + @SHL0MS). **Humanizer** skill ports a text-cleaner that strips AI-isms ([#16787](https://github.com/NousResearch/hermes-agent/pull/16787)). **claude-design** HTML artifact skill + design-md (Google DESIGN.md spec) + airtable salvage + `skill_manage` edits in `external_dirs` + direct-URL skill install + `/reload-skills` slash command. ([#16358](https://github.com/NousResearch/hermes-agent/pull/16358), [#14876](https://github.com/NousResearch/hermes-agent/pull/14876), [#16291](https://github.com/NousResearch/hermes-agent/pull/16291), [#17512](https://github.com/NousResearch/hermes-agent/pull/17512), [#16323](https://github.com/NousResearch/hermes-agent/pull/16323), [#17744](https://github.com/NousResearch/hermes-agent/pull/17744))
|
| 17 |
+
|
| 18 |
+
- **LM Studio — first-class provider** — upgraded from a custom-endpoint alias to a full-blown native provider: dedicated auth, `hermes doctor` checks, reasoning transport, live `/models` listing. (Salvage of @kshitijk4poor's #17061.) ([#17102](https://github.com/NousResearch/hermes-agent/pull/17102))
|
| 19 |
+
|
| 20 |
+
- **Four more new inference providers** — **GMI Cloud** (first-class, salvage of #11955 — @isaachuangGMICLOUD), **Azure AI Foundry** with auto-detection, **MiniMax OAuth** with PKCE browser flow (salvage #15203), **Tencent Tokenhub** (salvage of #16860). ([#16663](https://github.com/NousResearch/hermes-agent/pull/16663), [#15845](https://github.com/NousResearch/hermes-agent/pull/15845), [#17524](https://github.com/NousResearch/hermes-agent/pull/17524), [#16960](https://github.com/NousResearch/hermes-agent/pull/16960))
|
| 21 |
+
|
| 22 |
+
- **Pluggable gateway platforms + Microsoft Teams** — the gateway is now a plugin host. Drop-in messaging adapters live outside the core, and Microsoft Teams is the first plugin-shipped platform. (Salvage of #17664.) ([#17751](https://github.com/NousResearch/hermes-agent/pull/17751), [#17828](https://github.com/NousResearch/hermes-agent/pull/17828))
|
| 23 |
+
|
| 24 |
+
- **Tencent 元宝 (Yuanbao) — 18th messaging platform** — native gateway adapter with text + media delivery. ([#16298](https://github.com/NousResearch/hermes-agent/pull/16298), [#17424](https://github.com/NousResearch/hermes-agent/pull/17424))
|
| 25 |
+
|
| 26 |
+
- **Spotify — native tools + bundled skill + wizard** — 7 tools (play, search, queue, playlists, devices) behind PKCE OAuth, interactive setup wizard, bundled skill, surfacing in `hermes tools`, cron usage documented. ([#15121](https://github.com/NousResearch/hermes-agent/pull/15121), [#15130](https://github.com/NousResearch/hermes-agent/pull/15130), [#15154](https://github.com/NousResearch/hermes-agent/pull/15154), [#15180](https://github.com/NousResearch/hermes-agent/pull/15180))
|
| 27 |
+
|
| 28 |
+
- **Google Meet plugin** — join calls, transcribe, speak, follow up. Realtime OpenAI transport + Node bot server, full pipeline bundled as a plugin. ([#16364](https://github.com/NousResearch/hermes-agent/pull/16364))
|
| 29 |
+
|
| 30 |
+
- **`hermes -z` one-shot mode + `hermes update --check`** — non-interactive `hermes -z <prompt>` with `--model`/`--provider`/`HERMES_INFERENCE_MODEL`. `hermes update --check` preflight. Opt-in pre-update HERMES_HOME backup. ([#15702](https://github.com/NousResearch/hermes-agent/pull/15702), [#15704](https://github.com/NousResearch/hermes-agent/pull/15704), [#15841](https://github.com/NousResearch/hermes-agent/pull/15841), [#16539](https://github.com/NousResearch/hermes-agent/pull/16539), [#16566](https://github.com/NousResearch/hermes-agent/pull/16566))
|
| 31 |
+
|
| 32 |
+
- **Models dashboard tab + in-browser model config** — rich per-model analytics, switch main + auxiliary models from the dashboard. ([#17745](https://github.com/NousResearch/hermes-agent/pull/17745), [#17802](https://github.com/NousResearch/hermes-agent/pull/17802))
|
| 33 |
+
|
| 34 |
+
- **Remote model catalog manifest** — OpenRouter + Nous Portal model catalogs are now pulled from a remote manifest so new models show up without a release. ([#16033](https://github.com/NousResearch/hermes-agent/pull/16033))
|
| 35 |
+
|
| 36 |
+
- **Native multimodal image routing** — images now route based on the model's actual vision capability rather than provider defaults. ([#16506](https://github.com/NousResearch/hermes-agent/pull/16506))
|
| 37 |
+
|
| 38 |
+
- **Gateway media parity** — native multi-image sending across Telegram, Discord, Slack, Mattermost, Email, and Signal; centralized audio routing with FLAC support + Telegram document fallback. ([#17909](https://github.com/NousResearch/hermes-agent/pull/17909), [#17833](https://github.com/NousResearch/hermes-agent/pull/17833))
|
| 39 |
+
|
| 40 |
+
- **TUI catches up to (and past) the classic CLI** — LaTeX rendering (@austinpickett), `/reload` .env hot-reload, pluggable busy-indicator styles (@OutThisLife, #13610), opt-in auto-resume of last session, expanded light-terminal auto-detection, session delete from `/resume` picker with `d`, modified mouse-wheel line scroll, and a `/mouse` toggle that kills ConPTY's phantom mouse injection (@kevin-ho). ([#17175](https://github.com/NousResearch/hermes-agent/pull/17175), [#17286](https://github.com/NousResearch/hermes-agent/pull/17286), [#17150](https://github.com/NousResearch/hermes-agent/pull/17150), [#17130](https://github.com/NousResearch/hermes-agent/pull/17130), [#17113](https://github.com/NousResearch/hermes-agent/pull/17113), [#17668](https://github.com/NousResearch/hermes-agent/pull/17668), [#17669](https://github.com/NousResearch/hermes-agent/pull/17669), [#15488](https://github.com/NousResearch/hermes-agent/pull/15488))
|
| 41 |
+
|
| 42 |
+
- **Observability + achievements plugins** — bundled Langfuse observability plugin (salvage #16845) + bundled hermes-achievements plugin that scans full session history. ([#16917](https://github.com/NousResearch/hermes-agent/pull/16917), [#17754](https://github.com/NousResearch/hermes-agent/pull/17754))
|
| 43 |
+
|
| 44 |
+
- **TTS provider registry + Piper local TTS** — pluggable `tts.providers.<name>` registry; Piper ships as a native local TTS provider. (Closes #8508.) ([#17843](https://github.com/NousResearch/hermes-agent/pull/17843), [#17885](https://github.com/NousResearch/hermes-agent/pull/17885))
|
| 45 |
+
|
| 46 |
+
- **Vercel Sandbox backend** — Vercel sandboxes as an execute_code/terminal backend (@kshitijk4poor). ([#17445](https://github.com/NousResearch/hermes-agent/pull/17445))
|
| 47 |
+
|
| 48 |
+
- **Secret redaction off by default** — default flipped to off. Prevents the long-standing patch-corruption incidents where fake secret-shaped substrings mangled tool outputs. Opt in via `redaction.enabled: true` when you need it. ([#16794](https://github.com/NousResearch/hermes-agent/pull/16794))
|
| 49 |
+
|
| 50 |
+
- **Cold-start performance** — visible TUI cold start cut **~57%** via lazy agent init (@OutThisLife), lazy imports of OpenAI / Anthropic / Firecrawl / account_usage, mtime-cached `load_config()`, memoized `get_tool_definitions()` with TTL-cached `check_fn` results, precompiled dangerous-command patterns. ([#17190](https://github.com/NousResearch/hermes-agent/pull/17190), [#17046](https://github.com/NousResearch/hermes-agent/pull/17046), [#17041](https://github.com/NousResearch/hermes-agent/pull/17041), [#17098](https://github.com/NousResearch/hermes-agent/pull/17098), [#17206](https://github.com/NousResearch/hermes-agent/pull/17206))
|
| 51 |
+
|
| 52 |
+
- **Configurable prompt cache TTL** — `prompt_caching.cache_ttl` (5m default, 1h opt-in — cost savings for bursty sessions that keep cache warm). Salvage of #12659. ([#15065](https://github.com/NousResearch/hermes-agent/pull/15065))
|
| 53 |
+
|
| 54 |
+
---
|
| 55 |
+
|
| 56 |
+
## 🧠 Autonomous Curator & Self-Improvement Loop
|
| 57 |
+
|
| 58 |
+
### Curator — autonomous skill maintenance
|
| 59 |
+
- **`hermes curator` as a background agent** — runs on the gateway's cron ticker, 7-day cycle by default, umbrella-first prompt, inherits parent config, unbounded iterations ([#17277](https://github.com/NousResearch/hermes-agent/pull/17277) — issue #7816)
|
| 60 |
+
- **Per-run reports** — `logs/curator/run.json` + `REPORT.md` per cycle ([#17307](https://github.com/NousResearch/hermes-agent/pull/17307))
|
| 61 |
+
- **Consolidated vs pruned classification** — archived skills split with model + heuristic ([#17941](https://github.com/NousResearch/hermes-agent/pull/17941))
|
| 62 |
+
- **`hermes curator status`** — ranks skills by usage, shows most-used and least-used ([#18033](https://github.com/NousResearch/hermes-agent/pull/18033))
|
| 63 |
+
- **Unified under `auxiliary.curator`** — pick the model in `hermes model`, configure from the dashboard ([#17868](https://github.com/NousResearch/hermes-agent/pull/17868))
|
| 64 |
+
- **Documentation** — dedicated curator feature page on the docs site ([#17563](https://github.com/NousResearch/hermes-agent/pull/17563))
|
| 65 |
+
- Fix: seed defaults on update, create `logs/curator/` directory, defer fire import ([#17927](https://github.com/NousResearch/hermes-agent/pull/17927))
|
| 66 |
+
- Fix: scan nested archive subdirs in `restore_skill` (@0xDevNinja) ([#17951](https://github.com/NousResearch/hermes-agent/pull/17951))
|
| 67 |
+
- Fix: use actual skill activity in curator status (@y0shua1ee) ([#17953](https://github.com/NousResearch/hermes-agent/pull/17953))
|
| 68 |
+
- Fix: `skill_manage` refuses writes on pinned skills; pinning now blocks curator writes ([#17562](https://github.com/NousResearch/hermes-agent/pull/17562), [#17578](https://github.com/NousResearch/hermes-agent/pull/17578))
|
| 69 |
+
- Fix: `bump_use()` wired into skill invocation + preload + skill_view (salvage #17782) ([#17932](https://github.com/NousResearch/hermes-agent/pull/17932))
|
| 70 |
+
|
| 71 |
+
### Self-improvement loop (background review fork)
|
| 72 |
+
- **Class-first skill-review prompt** — rubric-based grading rather than free-form "should this update" ([#16026](https://github.com/NousResearch/hermes-agent/pull/16026))
|
| 73 |
+
- **Active-update bias** — prefers updating skills the agent just loaded, handles `references/` + `templates/` sub-files ([#17213](https://github.com/NousResearch/hermes-agent/pull/17213))
|
| 74 |
+
- **Fork inherits parent's live runtime** — provider, model, credentials actually propagate now ([#16099](https://github.com/NousResearch/hermes-agent/pull/16099))
|
| 75 |
+
- **Scoped toolsets** — review fork restricted to memory + skills (no shell, no web) ([#16569](https://github.com/NousResearch/hermes-agent/pull/16569))
|
| 76 |
+
- **Clean shutdown** — background review memory providers exit properly (salvage #15289) ([#16204](https://github.com/NousResearch/hermes-agent/pull/16204))
|
| 77 |
+
- **Clean context** — prior-history tool messages excluded from review summary (salvage #14967) ([#15057](https://github.com/NousResearch/hermes-agent/pull/15057))
|
| 78 |
+
|
| 79 |
+
---
|
| 80 |
+
|
| 81 |
+
## 🧩 Skills Ecosystem
|
| 82 |
+
|
| 83 |
+
### Skill integrations — newly bundled or promoted
|
| 84 |
+
- **ComfyUI v5** — official CLI + REST + hardware-gated local install; **moved from optional to built-in** ([#17610](https://github.com/NousResearch/hermes-agent/pull/17610), [#17631](https://github.com/NousResearch/hermes-agent/pull/17631), [#17734](https://github.com/NousResearch/hermes-agent/pull/17734), [#17612](https://github.com/NousResearch/hermes-agent/pull/17612))
|
| 85 |
+
- **TouchDesigner-MCP** — **bundled by default** ([#16753](https://github.com/NousResearch/hermes-agent/pull/16753) — @kshitijk4poor), expanded with GLSL, post-FX, audio, geometry references ([#16624](https://github.com/NousResearch/hermes-agent/pull/16624)), 9 new reference docs ([#16768](https://github.com/NousResearch/hermes-agent/pull/16768) — @SHL0MS)
|
| 86 |
+
- **Humanizer** — strips AI-isms from text ([#16787](https://github.com/NousResearch/hermes-agent/pull/16787))
|
| 87 |
+
- **claude-design** — HTML artifact skill with disambiguation from other design skills ([#16358](https://github.com/NousResearch/hermes-agent/pull/16358))
|
| 88 |
+
- **design-md** — Google's DESIGN.md spec skill ([#14876](https://github.com/NousResearch/hermes-agent/pull/14876))
|
| 89 |
+
- **airtable** — salvaged skill + skill API keys wired into `.env` (#15838) ([#16291](https://github.com/NousResearch/hermes-agent/pull/16291))
|
| 90 |
+
- **pretext** — creative browser demos with @chenglou/pretext ([#17259](https://github.com/NousResearch/hermes-agent/pull/17259))
|
| 91 |
+
- **spike** + **sketch** — throwaway experiments + HTML mockups, adapted from gsd-build ([#17421](https://github.com/NousResearch/hermes-agent/pull/17421))
|
| 92 |
+
|
| 93 |
+
### Skills UX
|
| 94 |
+
- **Install skills from a direct HTTP(S) URL** — `hermes skills install <url>` ([#16323](https://github.com/NousResearch/hermes-agent/pull/16323))
|
| 95 |
+
- **`/reload-skills`** slash command (salvage #17670) ([#17744](https://github.com/NousResearch/hermes-agent/pull/17744))
|
| 96 |
+
- **`hermes skills list`** shows enabled/disabled status ([#16129](https://github.com/NousResearch/hermes-agent/pull/16129))
|
| 97 |
+
- **`skill_manage` refuses writes on pinned skills** ([#17562](https://github.com/NousResearch/hermes-agent/pull/17562))
|
| 98 |
+
- **`skill_manage` edits external_dirs skills in place** (salvage #9966) ([#17512](https://github.com/NousResearch/hermes-agent/pull/17512), [#17289](https://github.com/NousResearch/hermes-agent/pull/17289))
|
| 99 |
+
- Fix: inline-shell rendering in `skill_view` ([#15376](https://github.com/NousResearch/hermes-agent/pull/15376))
|
| 100 |
+
- Fix: exclude `.archive/` from skill index walk (salvage #17639) ([#17931](https://github.com/NousResearch/hermes-agent/pull/17931))
|
| 101 |
+
- Fix: dedicated docs page per bundled + optional skill ([#14929](https://github.com/NousResearch/hermes-agent/pull/14929))
|
| 102 |
+
- Fix: `google-workspace` shared HERMES_HOME helper + ship deps as optional extra ([#15405](https://github.com/NousResearch/hermes-agent/pull/15405))
|
| 103 |
+
- Fix: auto-wrap ASCII-art code blocks in generated skill pages ([#16497](https://github.com/NousResearch/hermes-agent/pull/16497))
|
| 104 |
+
- Point agent at `hermes-agent` skill + docs site for Hermes questions ([#16535](https://github.com/NousResearch/hermes-agent/pull/16535))
|
| 105 |
+
|
| 106 |
+
---
|
| 107 |
+
|
| 108 |
+
## 🏗️ Core Agent & Architecture
|
| 109 |
+
|
| 110 |
+
### Provider & Model Support
|
| 111 |
+
|
| 112 |
+
#### New providers
|
| 113 |
+
- **GMI Cloud** — first-class API-key provider on par with Arcee/Kilocode/Xiaomi (salvage of #11955 — @isaachuangGMICLOUD) ([#16663](https://github.com/NousResearch/hermes-agent/pull/16663))
|
| 114 |
+
- **Azure AI Foundry** — auto-detection, full wiring ([#15845](https://github.com/NousResearch/hermes-agent/pull/15845))
|
| 115 |
+
- **LM Studio** — upgraded from custom-endpoint alias to first-class provider: dedicated auth, doctor checks, reasoning transport, live `/models` (salvage of #17061 — @kshitijk4poor) ([#17102](https://github.com/NousResearch/hermes-agent/pull/17102))
|
| 116 |
+
- **MiniMax OAuth** — PKCE browser flow with full OAuth integration (salvage #15203) ([#17524](https://github.com/NousResearch/hermes-agent/pull/17524))
|
| 117 |
+
- **Tencent Tokenhub** — new provider (salvage of #16860) ([#16960](https://github.com/NousResearch/hermes-agent/pull/16960))
|
| 118 |
+
|
| 119 |
+
#### Model catalog
|
| 120 |
+
- **Remote model catalog manifest** — OpenRouter + Nous Portal catalogs pulled from remote manifest so new models show up without a release ([#16033](https://github.com/NousResearch/hermes-agent/pull/16033))
|
| 121 |
+
- `openai/gpt-5.5` and `gpt-5.5-pro` added to OpenRouter + Nous Portal ([#15343](https://github.com/NousResearch/hermes-agent/pull/15343))
|
| 122 |
+
- `deepseek-v4-pro` and `deepseek-v4-flash` added ([#14934](https://github.com/NousResearch/hermes-agent/pull/14934))
|
| 123 |
+
- `qwen3.6-plus` added to Alibaba-supported models ([#16896](https://github.com/NousResearch/hermes-agent/pull/16896))
|
| 124 |
+
- Gemini free-tier keys blocked at setup with 429 guidance surfacing ([#15100](https://github.com/NousResearch/hermes-agent/pull/15100))
|
| 125 |
+
|
| 126 |
+
#### Model configuration
|
| 127 |
+
- **Configurable `prompt_caching.cache_ttl`** — 5m default, 1h opt-in (salvage #12659) ([#15065](https://github.com/NousResearch/hermes-agent/pull/15065))
|
| 128 |
+
- `/fast` whitelist broadened to all OpenAI + Anthropic models ([#16883](https://github.com/NousResearch/hermes-agent/pull/16883))
|
| 129 |
+
- `auxiliary.extra_body.reasoning` translates into Codex Responses API ([#17004](https://github.com/NousResearch/hermes-agent/pull/17004))
|
| 130 |
+
- `hermes fallback` command for managing fallback providers ([#16052](https://github.com/NousResearch/hermes-agent/pull/16052))
|
| 131 |
+
|
| 132 |
+
### Agent Loop & Conversation
|
| 133 |
+
- **Native multimodal image routing** — based on model vision capability, not provider defaults ([#16506](https://github.com/NousResearch/hermes-agent/pull/16506))
|
| 134 |
+
- **Delegate `child_timeout_seconds` default bumped to 600s** ([#14809](https://github.com/NousResearch/hermes-agent/pull/14809))
|
| 135 |
+
- **Diagnostic dump when subagent times out with 0 API calls** ([#15105](https://github.com/NousResearch/hermes-agent/pull/15105))
|
| 136 |
+
- **Gateway busts cached agent on compression/context_length config edits** ([#17008](https://github.com/NousResearch/hermes-agent/pull/17008))
|
| 137 |
+
- **Opt-in runtime-metadata footer on final replies** ([#17026](https://github.com/NousResearch/hermes-agent/pull/17026))
|
| 138 |
+
- `/reload-mcp` awareness — rebuild cached agents + prompt-cache cost confirmation ([#17729](https://github.com/NousResearch/hermes-agent/pull/17729))
|
| 139 |
+
- Fix: repair CamelCase + `_tool` suffix tool-call emissions ([#15124](https://github.com/NousResearch/hermes-agent/pull/15124))
|
| 140 |
+
- Fix: retry on `json.JSONDecodeError` instead of treating as local validation error ([#15107](https://github.com/NousResearch/hermes-agent/pull/15107))
|
| 141 |
+
- Fix: handle unescaped control chars in `tool_call.arguments` ([#15356](https://github.com/NousResearch/hermes-agent/pull/15356))
|
| 142 |
+
- Fix: ordering fix in `_copy_reasoning_content_for_api` — cross-provider reasoning isolation (@Zjianru) ([#15749](https://github.com/NousResearch/hermes-agent/pull/15749))
|
| 143 |
+
- Fix: inject empty `reasoning_content` for DeepSeek/Kimi `tool_calls` unconditionally (@Zjianru) ([#15762](https://github.com/NousResearch/hermes-agent/pull/15762))
|
| 144 |
+
- Fix: persist streamed `reasoning_content` on assistant turns (#16844) ([#16892](https://github.com/NousResearch/hermes-agent/pull/16892))
|
| 145 |
+
- Fix: cancel coroutine on timeout so worker thread exits; full traceback on tool failure ([#17428](https://github.com/NousResearch/hermes-agent/pull/17428))
|
| 146 |
+
- Fix: isolate `get_tool_definitions` quiet_mode cache + dedup LCM injection (#17335) ([#17889](https://github.com/NousResearch/hermes-agent/pull/17889))
|
| 147 |
+
- Fix: serialize concurrent `hermes_tools` RPC calls from `execute_code` (#17770) ([#17894](https://github.com/NousResearch/hermes-agent/pull/17894), [#17902](https://github.com/NousResearch/hermes-agent/pull/17902))
|
| 148 |
+
- Fix: rename `[SYSTEM:` → `[IMPORTANT:` in all user-injected markers (dodges Azure content filter) ([#16114](https://github.com/NousResearch/hermes-agent/pull/16114))
|
| 149 |
+
|
| 150 |
+
### Compression
|
| 151 |
+
- **Retry summary on main model for unknown errors before giving up** ([#16774](https://github.com/NousResearch/hermes-agent/pull/16774))
|
| 152 |
+
- **Notify users when configured aux model fails even if main-model fallback recovers** ([#16775](https://github.com/NousResearch/hermes-agent/pull/16775))
|
| 153 |
+
- `/compress` wrapped in `_busy_command` to block input during compression ([#15388](https://github.com/NousResearch/hermes-agent/pull/15388))
|
| 154 |
+
- Fix: reserve system + tools headroom when aux binds threshold ([#15631](https://github.com/NousResearch/hermes-agent/pull/15631))
|
| 155 |
+
- Fix: use text-char sum for multimodal token estimation in `_find_tail_cut_by_tokens` ([#16369](https://github.com/NousResearch/hermes-agent/pull/16369))
|
| 156 |
+
|
| 157 |
+
### Session, Memory & State
|
| 158 |
+
- **Trigram FTS5 index for CJK search, replace LIKE fallback** (@alt-glitch) ([#16651](https://github.com/NousResearch/hermes-agent/pull/16651))
|
| 159 |
+
- **Index `tool_name` + `tool_calls` in FTS5, with repair + migration** (salvages #16866) ([#16914](https://github.com/NousResearch/hermes-agent/pull/16914))
|
| 160 |
+
- **Checkpoints: auto-prune orphan and stale shadow repos at startup** ([#16303](https://github.com/NousResearch/hermes-agent/pull/16303))
|
| 161 |
+
- **Memory providers notified on mid-process session_id rotation** (#6672) ([#17409](https://github.com/NousResearch/hermes-agent/pull/17409))
|
| 162 |
+
- Fix: quote underscored terms in FTS5 query sanitization ([#16915](https://github.com/NousResearch/hermes-agent/pull/16915))
|
| 163 |
+
- Fix: resolve viking_read 500/412 on file URIs + pseudo-summary URIs (salvage #5886) ([#17869](https://github.com/NousResearch/hermes-agent/pull/17869))
|
| 164 |
+
- Fix: skip external-provider sync on interrupted turns ([#15395](https://github.com/NousResearch/hermes-agent/pull/15395))
|
| 165 |
+
- Fix: close embedded Hindsight async client cleanly (salvage #14605) ([#16209](https://github.com/NousResearch/hermes-agent/pull/16209))
|
| 166 |
+
- Fix: pass session transcript to `shutdown_memory_provider` on gateway + CLI (#15165) ([#16571](https://github.com/NousResearch/hermes-agent/pull/16571))
|
| 167 |
+
- Fix: write-origin metadata seam ([#15346](https://github.com/NousResearch/hermes-agent/pull/15346))
|
| 168 |
+
- Fix: preserve symlinks during atomic file writes ([#16980](https://github.com/NousResearch/hermes-agent/pull/16980))
|
| 169 |
+
- Refactor: remove `flush_memories` entirely ([#15696](https://github.com/NousResearch/hermes-agent/pull/15696))
|
| 170 |
+
|
| 171 |
+
### Auxiliary models
|
| 172 |
+
- Fix: surface auxiliary failures in UI (previously silent) ([#15324](https://github.com/NousResearch/hermes-agent/pull/15324))
|
| 173 |
+
- Fix: surface title-gen auxiliary failures instead of silently dropping ([#16371](https://github.com/NousResearch/hermes-agent/pull/16371))
|
| 174 |
+
- Fix: generalize unsupported-parameter detector and harden `max_tokens` retry ([#15633](https://github.com/NousResearch/hermes-agent/pull/15633))
|
| 175 |
+
|
| 176 |
+
---
|
| 177 |
+
|
| 178 |
+
## 📱 Messaging Platforms (Gateway)
|
| 179 |
+
|
| 180 |
+
### New Platforms
|
| 181 |
+
- **Microsoft Teams (19th platform)** — as a plugin, + xdist collision guard ([#17828](https://github.com/NousResearch/hermes-agent/pull/17828))
|
| 182 |
+
- **Yuanbao (Tencent 元宝, 18th platform)** — native adapter with text + media delivery ([#16298](https://github.com/NousResearch/hermes-agent/pull/16298), [#17424](https://github.com/NousResearch/hermes-agent/pull/17424), [#16880](https://github.com/NousResearch/hermes-agent/pull/16880))
|
| 183 |
+
|
| 184 |
+
### Pluggable Gateway Platforms
|
| 185 |
+
- **Drop-in messaging adapters** — the gateway is now a plugin host for platforms (salvage of #17664) ([#17751](https://github.com/NousResearch/hermes-agent/pull/17751))
|
| 186 |
+
|
| 187 |
+
### Telegram
|
| 188 |
+
- **Chat allowlists for groups and forums** (@web3blind) ([#15027](https://github.com/NousResearch/hermes-agent/pull/15027))
|
| 189 |
+
- **Send fresh finals for stale preview streams** (port openclaw#72038) ([#16261](https://github.com/NousResearch/hermes-agent/pull/16261))
|
| 190 |
+
- **Render markdown tables as row-group bullets + prompt hint** ([#16997](https://github.com/NousResearch/hermes-agent/pull/16997))
|
| 191 |
+
- Document fallback in centralized audio routing ([#17833](https://github.com/NousResearch/hermes-agent/pull/17833))
|
| 192 |
+
- Native multi-image sending ([#17909](https://github.com/NousResearch/hermes-agent/pull/17909))
|
| 193 |
+
|
| 194 |
+
### Discord
|
| 195 |
+
- **Opt-in toolsets + ID injection + tool split + Feishu wiring** (salvage #15457, #15458) ([#15610](https://github.com/NousResearch/hermes-agent/pull/15610), [#15613](https://github.com/NousResearch/hermes-agent/pull/15613))
|
| 196 |
+
- Fix: coerce `limit` parameter to int before `min()` call ([#16319](https://github.com/NousResearch/hermes-agent/pull/16319))
|
| 197 |
+
|
| 198 |
+
### Slack
|
| 199 |
+
- **Register every gateway command as a native slash (Discord/Telegram parity)** ([#16164](https://github.com/NousResearch/hermes-agent/pull/16164))
|
| 200 |
+
- **`strict_mention` config** — prevents thread auto-engagement ([#16193](https://github.com/NousResearch/hermes-agent/pull/16193))
|
| 201 |
+
- **`channel_skill_bindings`** — bind specific skills to specific Slack channels ([#16283](https://github.com/NousResearch/hermes-agent/pull/16283))
|
| 202 |
+
|
| 203 |
+
### Signal
|
| 204 |
+
- **Native formatting** — markdown → bodyRanges, reply quotes, reactions ([#17417](https://github.com/NousResearch/hermes-agent/pull/17417))
|
| 205 |
+
- Native multi-image sending ([#17909](https://github.com/NousResearch/hermes-agent/pull/17909))
|
| 206 |
+
|
| 207 |
+
### Feishu / Mattermost / Email / Signal
|
| 208 |
+
- All participate in **native multi-image sending** ([#17909](https://github.com/NousResearch/hermes-agent/pull/17909))
|
| 209 |
+
|
| 210 |
+
### Gateway Core
|
| 211 |
+
- **Centralized audio routing + FLAC support + Telegram doc fallback** ([#17833](https://github.com/NousResearch/hermes-agent/pull/17833))
|
| 212 |
+
- **Native multi-image sending** across Telegram, Discord, Slack, Mattermost, Email, Signal ([#17909](https://github.com/NousResearch/hermes-agent/pull/17909))
|
| 213 |
+
- **Make hygiene hard message limit configurable** ([#17000](https://github.com/NousResearch/hermes-agent/pull/17000))
|
| 214 |
+
- **Opt-in runtime-metadata footer on final replies** ([#17026](https://github.com/NousResearch/hermes-agent/pull/17026))
|
| 215 |
+
- **`pre_gateway_dispatch` hook** — plugins can intercept before dispatch ([#15050](https://github.com/NousResearch/hermes-agent/pull/15050))
|
| 216 |
+
- **`pre_approval_request` / `post_approval_response` hooks** ([#16776](https://github.com/NousResearch/hermes-agent/pull/16776))
|
| 217 |
+
- Fix: timeouts — guard `load_config()` call against runtime exceptions ([#16318](https://github.com/NousResearch/hermes-agent/pull/16318))
|
| 218 |
+
- Fix: support passing handler tools via registry ([#15613](https://github.com/NousResearch/hermes-agent/pull/15613))
|
| 219 |
+
|
| 220 |
+
---
|
| 221 |
+
|
| 222 |
+
## 🔧 Tool System
|
| 223 |
+
|
| 224 |
+
### Plugin-first architecture
|
| 225 |
+
- **Pluggable gateway platforms** — platforms can ship as plugins ([#17751](https://github.com/NousResearch/hermes-agent/pull/17751))
|
| 226 |
+
- **Microsoft Teams as first plugin-shipped platform** ([#17828](https://github.com/NousResearch/hermes-agent/pull/17828))
|
| 227 |
+
- **`pre_gateway_dispatch` hook** ([#15050](https://github.com/NousResearch/hermes-agent/pull/15050))
|
| 228 |
+
- **`pre_approval_request` + `post_approval_response` hooks** ([#16776](https://github.com/NousResearch/hermes-agent/pull/16776))
|
| 229 |
+
- **`duration_ms` on `post_tool_call`** (inspired by Claude Code 2.1.119) ([#15429](https://github.com/NousResearch/hermes-agent/pull/15429))
|
| 230 |
+
- **Bundled plugins**: Spotify ([#15174](https://github.com/NousResearch/hermes-agent/pull/15174)), Google Meet ([#16364](https://github.com/NousResearch/hermes-agent/pull/16364)), Langfuse observability ([#16917](https://github.com/NousResearch/hermes-agent/pull/16917)), hermes-achievements ([#17754](https://github.com/NousResearch/hermes-agent/pull/17754))
|
| 231 |
+
- **Page-scoped plugin slots for built-in dashboard pages** ([#15658](https://github.com/NousResearch/hermes-agent/pull/15658))
|
| 232 |
+
- **Declarative plugin installation for NixOS module** (@alt-glitch) ([#15953](https://github.com/NousResearch/hermes-agent/pull/15953))
|
| 233 |
+
|
| 234 |
+
### Browser
|
| 235 |
+
- **CDP supervisor** — dialog detection + response + cross-origin iframe eval ([#14540](https://github.com/NousResearch/hermes-agent/pull/14540))
|
| 236 |
+
- **Auto-spawn local Chromium for LAN/localhost URLs** when cloud provider is configured ([#16136](https://github.com/NousResearch/hermes-agent/pull/16136))
|
| 237 |
+
|
| 238 |
+
### Execute code / Terminal
|
| 239 |
+
- **Vercel Sandbox backend** for `execute_code` / terminal (@kshitijk4poor) ([#17445](https://github.com/NousResearch/hermes-agent/pull/17445))
|
| 240 |
+
- **Collapse subagent `task_id`s to shared container** ([#16177](https://github.com/NousResearch/hermes-agent/pull/16177))
|
| 241 |
+
- **Docker: run container as host user** to avoid root-owned bind mounts (@benbarclay) ([#17305](https://github.com/NousResearch/hermes-agent/pull/17305))
|
| 242 |
+
- Fix: safely quote `~/` subpaths in wrapped `cd` commands ([#15394](https://github.com/NousResearch/hermes-agent/pull/15394))
|
| 243 |
+
- Fix: close file descriptor in `LocalEnvironment._update_cwd` ([#17300](https://github.com/NousResearch/hermes-agent/pull/17300))
|
| 244 |
+
- Fix: SSH — prevent tar from overwriting remote home dir permissions ([#17898](https://github.com/NousResearch/hermes-agent/pull/17898), [#17867](https://github.com/NousResearch/hermes-agent/pull/17867))
|
| 245 |
+
|
| 246 |
+
### Image generation
|
| 247 |
+
- See Provider section for updates; no new image providers this window.
|
| 248 |
+
|
| 249 |
+
### TTS / Voice
|
| 250 |
+
- **Pluggable TTS provider registry** under `tts.providers.<name>` ([#17843](https://github.com/NousResearch/hermes-agent/pull/17843))
|
| 251 |
+
- **Piper** as native local TTS provider (closes #8508) ([#17885](https://github.com/NousResearch/hermes-agent/pull/17885))
|
| 252 |
+
- **Voice mode CLI parity in the TUI** — VAD loop + TTS + crash forensics ([#14810](https://github.com/NousResearch/hermes-agent/pull/14810))
|
| 253 |
+
- Fix: vision — use HERMES_HOME-based cache dir instead of cwd ([#17719](https://github.com/NousResearch/hermes-agent/pull/17719))
|
| 254 |
+
|
| 255 |
+
### Cron
|
| 256 |
+
- **Honor `hermes tools` config for the cron platform** ([#14798](https://github.com/NousResearch/hermes-agent/pull/14798))
|
| 257 |
+
- **Per-job `workdir`** — project-aware cron runs ([#15110](https://github.com/NousResearch/hermes-agent/pull/15110))
|
| 258 |
+
- **`context_from` field** — chain cron job outputs ([#15606](https://github.com/NousResearch/hermes-agent/pull/15606))
|
| 259 |
+
- Fix: promote `croniter` to a core dependency ([#17577](https://github.com/NousResearch/hermes-agent/pull/17577))
|
| 260 |
+
|
| 261 |
+
### Web search
|
| 262 |
+
- **Expose `limit` for `web_search`** ([#16934](https://github.com/NousResearch/hermes-agent/pull/16934))
|
| 263 |
+
|
| 264 |
+
### Maps
|
| 265 |
+
- Fix: include seconds in timezone UTC offset output ([#16300](https://github.com/NousResearch/hermes-agent/pull/16300))
|
| 266 |
+
|
| 267 |
+
### Approvals
|
| 268 |
+
- **Hardline blocklist for unrecoverable commands** ([#15878](https://github.com/NousResearch/hermes-agent/pull/15878))
|
| 269 |
+
- Perf: precompile DANGEROUS_PATTERNS and HARDLINE_PATTERNS ([#17206](https://github.com/NousResearch/hermes-agent/pull/17206))
|
| 270 |
+
|
| 271 |
+
### ACP
|
| 272 |
+
- **Advertise and forward image prompts** ([#18030](https://github.com/NousResearch/hermes-agent/pull/18030))
|
| 273 |
+
|
| 274 |
+
### API Server
|
| 275 |
+
- **POST `/v1/runs/{run_id}/stop`** (salvage of #15656) ([#15842](https://github.com/NousResearch/hermes-agent/pull/15842))
|
| 276 |
+
- **Expose run status for external UIs** (#17085) ([#17458](https://github.com/NousResearch/hermes-agent/pull/17458))
|
| 277 |
+
|
| 278 |
+
### Nix
|
| 279 |
+
- **Declarative plugin installation for NixOS module** (@alt-glitch) ([#15953](https://github.com/NousResearch/hermes-agent/pull/15953))
|
| 280 |
+
- Fix: use `--rebuild` in fix-lockfiles to bypass cached FOD store paths ([#15444](https://github.com/NousResearch/hermes-agent/pull/15444))
|
| 281 |
+
- Fix: `extraPackages` now actually works via per-user profile ([#17047](https://github.com/NousResearch/hermes-agent/pull/17047))
|
| 282 |
+
- Fix: refresh web/ npm-deps hash to unblock main builds ([#17174](https://github.com/NousResearch/hermes-agent/pull/17174))
|
| 283 |
+
- Fix: replace magic-nix-cache with Cachix ([#17928](https://github.com/NousResearch/hermes-agent/pull/17928))
|
| 284 |
+
|
| 285 |
+
---
|
| 286 |
+
|
| 287 |
+
## 🖥️ TUI
|
| 288 |
+
|
| 289 |
+
### New features
|
| 290 |
+
- **LaTeX rendering** (@austinpickett) ([#17175](https://github.com/NousResearch/hermes-agent/pull/17175))
|
| 291 |
+
- **`/reload` .env hot-reload** — ported from the classic CLI ([#17286](https://github.com/NousResearch/hermes-agent/pull/17286))
|
| 292 |
+
- **Pluggable busy-indicator styles** (@OutThisLife, #13610) ([#17150](https://github.com/NousResearch/hermes-agent/pull/17150))
|
| 293 |
+
- **Opt-in auto-resume of the most recent session** (@OutThisLife) ([#17130](https://github.com/NousResearch/hermes-agent/pull/17130))
|
| 294 |
+
- **Expanded light-terminal auto-detection** — `HERMES_TUI_THEME` + background hex (@OutThisLife) ([#17113](https://github.com/NousResearch/hermes-agent/pull/17113))
|
| 295 |
+
- **Delete sessions from `/resume` picker with `d`** (@OutThisLife) ([#17668](https://github.com/NousResearch/hermes-agent/pull/17668))
|
| 296 |
+
- **Line-by-line scroll on modified mouse wheel** (@OutThisLife) ([#17669](https://github.com/NousResearch/hermes-agent/pull/17669))
|
| 297 |
+
- **Delete queued message while editing with ctrl-x / cancel with esc** (@OutThisLife) ([#16707](https://github.com/NousResearch/hermes-agent/pull/16707))
|
| 298 |
+
- **Per-section visibility for the details accordion** (@OutThisLife) ([#14968](https://github.com/NousResearch/hermes-agent/pull/14968))
|
| 299 |
+
- **Voice mode CLI parity** — VAD loop + TTS + crash forensics ([#14810](https://github.com/NousResearch/hermes-agent/pull/14810))
|
| 300 |
+
- **Contextual first-touch hints ported to TUI** — `/busy`, `/verbose` ([#16054](https://github.com/NousResearch/hermes-agent/pull/16054))
|
| 301 |
+
- **Mini help menu on `?` in the input field** (@ethernet8023) ([#18043](https://github.com/NousResearch/hermes-agent/pull/18043))
|
| 302 |
+
|
| 303 |
+
### Fixes
|
| 304 |
+
- Fix: proactive mouse disable on ConPTY + `/mouse` toggle command (@kevin-ho, WSL2 ghost-mouse fix) ([#15488](https://github.com/NousResearch/hermes-agent/pull/15488))
|
| 305 |
+
- Fix: restore skills search RPC ([#15870](https://github.com/NousResearch/hermes-agent/pull/15870))
|
| 306 |
+
- Perf: cache text measurements across yoga flex re-passes ([#14818](https://github.com/NousResearch/hermes-agent/pull/14818))
|
| 307 |
+
- Perf: stabilize long-session scrolling ([#15926](https://github.com/NousResearch/hermes-agent/pull/15926))
|
| 308 |
+
- Perf: lazily seed virtual history heights ([#16523](https://github.com/NousResearch/hermes-agent/pull/16523))
|
| 309 |
+
- Perf: cut visible cold start ~57% with lazy agent init ([#17190](https://github.com/NousResearch/hermes-agent/pull/17190))
|
| 310 |
+
|
| 311 |
+
---
|
| 312 |
+
|
| 313 |
+
## 🖱️ CLI & User Experience
|
| 314 |
+
|
| 315 |
+
### New commands
|
| 316 |
+
- **`hermes -z <prompt>`** — non-interactive one-shot mode ([#15702](https://github.com/NousResearch/hermes-agent/pull/15702))
|
| 317 |
+
- **`hermes -z` with `--model` / `--provider` / `HERMES_INFERENCE_MODEL`** ([#15704](https://github.com/NousResearch/hermes-agent/pull/15704))
|
| 318 |
+
- **`hermes update --check`** preflight flag ([#15841](https://github.com/NousResearch/hermes-agent/pull/15841))
|
| 319 |
+
- **`hermes fallback`** command for managing fallback providers ([#16052](https://github.com/NousResearch/hermes-agent/pull/16052))
|
| 320 |
+
- **`/busy`** slash command for busy input mode ([#15382](https://github.com/NousResearch/hermes-agent/pull/15382))
|
| 321 |
+
- **`/busy` input mode 'steer'** as a third option ([#16279](https://github.com/NousResearch/hermes-agent/pull/16279))
|
| 322 |
+
- **`/btw` as alias for `/background`** ([#16053](https://github.com/NousResearch/hermes-agent/pull/16053))
|
| 323 |
+
- **`/reload-skills`** slash command (salvage #17670) ([#17744](https://github.com/NousResearch/hermes-agent/pull/17744))
|
| 324 |
+
- **Surface `/queue`, `/bg`, `/steer` in agent-running placeholder** ([#16118](https://github.com/NousResearch/hermes-agent/pull/16118))
|
| 325 |
+
|
| 326 |
+
### Setup / onboarding
|
| 327 |
+
- **Auto-reconfigure on existing installs** ([#15879](https://github.com/NousResearch/hermes-agent/pull/15879))
|
| 328 |
+
- **Contextual first-touch hints for `/busy` and `/verbose`** ([#16046](https://github.com/NousResearch/hermes-agent/pull/16046))
|
| 329 |
+
- **Cost-saving tips from the April 30 tip-of-the-day** ([#17841](https://github.com/NousResearch/hermes-agent/pull/17841))
|
| 330 |
+
- **Hyperlink startup banner title to the latest GitHub Release** ([#14945](https://github.com/NousResearch/hermes-agent/pull/14945))
|
| 331 |
+
|
| 332 |
+
### Update / backup
|
| 333 |
+
- **Snapshot pairing data before `git pull`** ([#16383](https://github.com/NousResearch/hermes-agent/pull/16383))
|
| 334 |
+
- **Auto-backup HERMES_HOME before `hermes update`** (opt-in, off by default) ([#16539](https://github.com/NousResearch/hermes-agent/pull/16539), [#16566](https://github.com/NousResearch/hermes-agent/pull/16566))
|
| 335 |
+
- **Exclude `checkpoints/` from backups** ([#16572](https://github.com/NousResearch/hermes-agent/pull/16572))
|
| 336 |
+
- **Exclude SQLite WAL/SHM/journal sidecars from backups** ([#16576](https://github.com/NousResearch/hermes-agent/pull/16576))
|
| 337 |
+
- **Installer FHS layout for root installs on Linux** ([#15608](https://github.com/NousResearch/hermes-agent/pull/15608))
|
| 338 |
+
- Fix: kill stale dashboards instead of warning ([#17832](https://github.com/NousResearch/hermes-agent/pull/17832))
|
| 339 |
+
- Fix: show correct update status on nix-built hermes ([#17550](https://github.com/NousResearch/hermes-agent/pull/17550))
|
| 340 |
+
|
| 341 |
+
### Slash-command housekeeping
|
| 342 |
+
- Refactor: drop `/provider`, `/plan` handler, and clean up slash registry ([#15047](https://github.com/NousResearch/hermes-agent/pull/15047))
|
| 343 |
+
- Refactor: drop `persist_session` plumbing + fix broken `/btw` mid-turn bypass ([#16075](https://github.com/NousResearch/hermes-agent/pull/16075))
|
| 344 |
+
|
| 345 |
+
### OpenClaw migration (for folks coming from OpenClaw)
|
| 346 |
+
- **Hardened OpenClaw import** — plan-first apply, redaction, pre-migration backup ([#16911](https://github.com/NousResearch/hermes-agent/pull/16911))
|
| 347 |
+
- Fix: case-preserving brand rewrite + one-time `~/.openclaw` residue banner ([#16327](https://github.com/NousResearch/hermes-agent/pull/16327))
|
| 348 |
+
- Fix: resolve `openclaw` workspace files from `agents.defaults.workspace` ([#16879](https://github.com/NousResearch/hermes-agent/pull/16879))
|
| 349 |
+
- Fix: resolve model aliases against real OpenClaw catalog schema (salvage #16778) ([#16977](https://github.com/NousResearch/hermes-agent/pull/16977))
|
| 350 |
+
|
| 351 |
+
---
|
| 352 |
+
|
| 353 |
+
## 📊 Web Dashboard
|
| 354 |
+
|
| 355 |
+
- **Models tab** — rich per-model analytics ([#17745](https://github.com/NousResearch/hermes-agent/pull/17745))
|
| 356 |
+
- **Configure main + auxiliary models from the Models page** ([#17802](https://github.com/NousResearch/hermes-agent/pull/17802))
|
| 357 |
+
- **Dashboard Chat tab — xterm.js + JSON-RPC sidecar** (supersedes #12710 + #13379, @OutThisLife) ([#14890](https://github.com/NousResearch/hermes-agent/pull/14890))
|
| 358 |
+
- **Dashboard layout refresh** (@austinpickett) ([#14899](https://github.com/NousResearch/hermes-agent/pull/14899))
|
| 359 |
+
- **`--stop` and `--status` flags** on the dashboard CLI ([#17840](https://github.com/NousResearch/hermes-agent/pull/17840))
|
| 360 |
+
- **Page-scoped plugin slots for built-in pages** ([#15658](https://github.com/NousResearch/hermes-agent/pull/15658))
|
| 361 |
+
- Fix: replace all buttons for design system buttons ([#17007](https://github.com/NousResearch/hermes-agent/pull/17007))
|
| 362 |
+
|
| 363 |
+
---
|
| 364 |
+
|
| 365 |
+
## ⚡ Performance
|
| 366 |
+
|
| 367 |
+
- **TUI visible cold start cut ~57%** via lazy agent init ([#17190](https://github.com/NousResearch/hermes-agent/pull/17190))
|
| 368 |
+
- **Lazy-import OpenAI, Anthropic, Firecrawl, account_usage** ([#17046](https://github.com/NousResearch/hermes-agent/pull/17046))
|
| 369 |
+
- **mtime-cache `load_config()` and `read_raw_config()`** ([#17041](https://github.com/NousResearch/hermes-agent/pull/17041))
|
| 370 |
+
- **Memoize `get_tool_definitions()` + TTL-cache `check_fn` results** ([#17098](https://github.com/NousResearch/hermes-agent/pull/17098))
|
| 371 |
+
- **Precompile DANGEROUS_PATTERNS and HARDLINE_PATTERNS** ([#17206](https://github.com/NousResearch/hermes-agent/pull/17206))
|
| 372 |
+
- **Cache Ink text measurements across yoga flex re-passes** ([#14818](https://github.com/NousResearch/hermes-agent/pull/14818))
|
| 373 |
+
- **Stabilize long-session scrolling** ([#15926](https://github.com/NousResearch/hermes-agent/pull/15926))
|
| 374 |
+
- **Lazily seed virtual history heights** ([#16523](https://github.com/NousResearch/hermes-agent/pull/16523))
|
| 375 |
+
|
| 376 |
+
---
|
| 377 |
+
|
| 378 |
+
## 🔒 Security & Reliability
|
| 379 |
+
|
| 380 |
+
- **Secret redaction off by default** — stops corrupting patches / API payloads with fake-key substitutions. Opt in via `redaction.enabled: true` ([#16794](https://github.com/NousResearch/hermes-agent/pull/16794))
|
| 381 |
+
- **`[SYSTEM:` → `[IMPORTANT:`** in all user-injected markers (Azure content filter dodge) ([#16114](https://github.com/NousResearch/hermes-agent/pull/16114))
|
| 382 |
+
- **Hardline blocklist for unrecoverable commands** ([#15878](https://github.com/NousResearch/hermes-agent/pull/15878))
|
| 383 |
+
- **Canonical `mask_secret` helper; fix status.py DIM drift** ([#17207](https://github.com/NousResearch/hermes-agent/pull/17207))
|
| 384 |
+
- **Sweep expired paste.rs uploads on a real timer** ([#16431](https://github.com/NousResearch/hermes-agent/pull/16431))
|
| 385 |
+
- **Preserve symlinks during atomic file writes** ([#16980](https://github.com/NousResearch/hermes-agent/pull/16980))
|
| 386 |
+
- **Probe `/dev/tty` by opening it, not bare existence** ([#17024](https://github.com/NousResearch/hermes-agent/pull/17024))
|
| 387 |
+
|
| 388 |
+
---
|
| 389 |
+
|
| 390 |
+
## 🐛 Notable Bug Fixes
|
| 391 |
+
|
| 392 |
+
This window includes 360 `fix:` PRs. Selected highlights from across the stack:
|
| 393 |
+
|
| 394 |
+
- **Background review fork inherits parent's live runtime** — provider/model/creds now propagate correctly ([#16099](https://github.com/NousResearch/hermes-agent/pull/16099))
|
| 395 |
+
- **Hindsight configurable `HINDSIGHT_TIMEOUT` env var** ([#15077](https://github.com/NousResearch/hermes-agent/pull/15077))
|
| 396 |
+
- **Tools: normalize numeric entries + clear stale `no_mcp` in `_save_platform_tools`** ([#15607](https://github.com/NousResearch/hermes-agent/pull/15607))
|
| 397 |
+
- **MCP: rewrite `definitions` refs to `$defs` in input schemas** — closes provider-side 400s
|
| 398 |
+
- **Azure content filter compatibility** — renamed `[SYSTEM:` markers so Azure's content filter stops flagging them ([#16114](https://github.com/NousResearch/hermes-agent/pull/16114))
|
| 399 |
+
- **Vision cache uses HERMES_HOME instead of cwd** ([#17719](https://github.com/NousResearch/hermes-agent/pull/17719))
|
| 400 |
+
- **FTS5 search** — tool_name + tool_calls indexing with repair + migration ([#16914](https://github.com/NousResearch/hermes-agent/pull/16914))
|
| 401 |
+
- **Streaming reasoning persists on assistant turns** ([#16892](https://github.com/NousResearch/hermes-agent/pull/16892))
|
| 402 |
+
- **execute_code concurrent RPC serialization** (#17770) ([#17894](https://github.com/NousResearch/hermes-agent/pull/17894), [#17902](https://github.com/NousResearch/hermes-agent/pull/17902))
|
| 403 |
+
- **Background reviewer scoped to memory + skills toolsets** — no more accidental web/shell escapes ([#16569](https://github.com/NousResearch/hermes-agent/pull/16569))
|
| 404 |
+
- **Compression recovery** — retry on main before giving up; notify user when aux fails ([#16774](https://github.com/NousResearch/hermes-agent/pull/16774), [#16775](https://github.com/NousResearch/hermes-agent/pull/16775))
|
| 405 |
+
- **`croniter` promoted to a core dependency** ([#17577](https://github.com/NousResearch/hermes-agent/pull/17577))
|
| 406 |
+
- **Discord tool `limit` parameter coerced to int** before `min()` call ([#16319](https://github.com/NousResearch/hermes-agent/pull/16319))
|
| 407 |
+
- **Yuanbao messaging platform entrance fix** ([#16880](https://github.com/NousResearch/hermes-agent/pull/16880))
|
| 408 |
+
- **ACP advertise and forward image prompts** ([#18030](https://github.com/NousResearch/hermes-agent/pull/18030))
|
| 409 |
+
- **DeepSeek / Kimi reasoning content isolation** across cross-provider histories (@Zjianru) ([#15749](https://github.com/NousResearch/hermes-agent/pull/15749), [#15762](https://github.com/NousResearch/hermes-agent/pull/15762))
|
| 410 |
+
- **Preserve reasoning_content replay on DeepSeek v4 + Kimi/Moonshot thinking** ([#18045](https://github.com/NousResearch/hermes-agent/pull/18045))
|
| 411 |
+
|
| 412 |
+
The vast majority of the 360 fixes landed in the streaming/compression/tool-calling paths across all providers — DeepSeek, Kimi, Moonshot, GLM, Qwen, MiniMax, Gemini, Anthropic, OpenAI — alongside TUI polish (resize, scroll, sticky-prompt) and gateway platform-specific edge cases.
|
| 413 |
+
|
| 414 |
+
---
|
| 415 |
+
|
| 416 |
+
## 🧪 Testing & CI
|
| 417 |
+
|
| 418 |
+
- Hermetic test parity (`scripts/run_tests.sh`) held across this window
|
| 419 |
+
- **Microsoft Teams xdist collision guard** — prevents worker collisions when Teams platform tests run in parallel ([#17828](https://github.com/NousResearch/hermes-agent/pull/17828))
|
| 420 |
+
- Chore: remove unused imports and dead locals (ruff F401, F841) ([#17010](https://github.com/NousResearch/hermes-agent/pull/17010))
|
| 421 |
+
|
| 422 |
+
---
|
| 423 |
+
|
| 424 |
+
## 📚 Documentation
|
| 425 |
+
|
| 426 |
+
- **Curator feature page** added to docs site ([#17563](https://github.com/NousResearch/hermes-agent/pull/17563))
|
| 427 |
+
- **Document pin also blocking `skill_manage` writes** ([#17578](https://github.com/NousResearch/hermes-agent/pull/17578))
|
| 428 |
+
- **Direct-URL skill install documented** across features, reference, guide, and `hermes-agent` skill ([#16355](https://github.com/NousResearch/hermes-agent/pull/16355))
|
| 429 |
+
- **Hooks tutorial — build a BOOT.md startup checklist** (replaces the removed built-in hook) ([#17202](https://github.com/NousResearch/hermes-agent/pull/17202))
|
| 430 |
+
- **ComfyUI docs: ask local vs cloud FIRST before hardware check** ([#17612](https://github.com/NousResearch/hermes-agent/pull/17612))
|
| 431 |
+
- **Obliteratus skill: link YouTube video guide in SKILL.md** ([#15808](https://github.com/NousResearch/hermes-agent/pull/15808))
|
| 432 |
+
- Per-skill docs pages generated for bundled + optional skills; ASCII art code blocks auto-wrapped ([#14929](https://github.com/NousResearch/hermes-agent/pull/14929), [#16497](https://github.com/NousResearch/hermes-agent/pull/16497))
|
| 433 |
+
|
| 434 |
+
---
|
| 435 |
+
|
| 436 |
+
## ⚖️ Removed / Reverted
|
| 437 |
+
|
| 438 |
+
- **Kanban multi-profile collaboration board** — landed in #16081, reverted in ([#16098](https://github.com/NousResearch/hermes-agent/pull/16098)) while the design is reworked
|
| 439 |
+
- **computer-use cua-driver** — 3 preparatory PRs landed then were reverted in ([#16927](https://github.com/NousResearch/hermes-agent/pull/16927))
|
| 440 |
+
- **BOOT.md built-in hook** removed ([#17093](https://github.com/NousResearch/hermes-agent/pull/17093)); the hooks tutorial ([#17202](https://github.com/NousResearch/hermes-agent/pull/17202)) shows how to build the same workflow yourself with a shell hook
|
| 441 |
+
- **`/provider` + `/plan` slash commands dropped** ([#15047](https://github.com/NousResearch/hermes-agent/pull/15047))
|
| 442 |
+
- **`flush_memories` removed entirely** ([#15696](https://github.com/NousResearch/hermes-agent/pull/15696))
|
| 443 |
+
|
| 444 |
+
---
|
| 445 |
+
|
| 446 |
+
## 👥 Contributors
|
| 447 |
+
|
| 448 |
+
### Core
|
| 449 |
+
- **@teknium1** (Teknium)
|
| 450 |
+
|
| 451 |
+
### Top Community Contributors (by merged PR count since v0.11.0)
|
| 452 |
+
|
| 453 |
+
- **@OutThisLife** (Brooklyn) — 52 PRs · TUI — light-terminal detection + pluggable busy styles + auto-resume + session-delete from /resume + mouse-wheel scrolling + xterm.js dashboard Chat tab + cold-start cut + accordion polish
|
| 454 |
+
- **@kshitijk4poor** — 12 PRs · LM Studio first-class provider (salvage), Vercel Sandbox backend, GMI Cloud salvage, bundled-by-default touchdesigner-mcp, many tool-call / reasoning fixes
|
| 455 |
+
- **@helix4u** — 10 PRs · MCP schema robustness, assorted stability fixes
|
| 456 |
+
- **@alt-glitch** — 8 PRs · trigram FTS5 CJK search, declarative Nix plugin install, matrix/feishu hints and fixes
|
| 457 |
+
- **@ethernet8023** — 4 PRs
|
| 458 |
+
- **@austinpickett** — 4 PRs · LaTeX rendering in TUI, dashboard layout refresh
|
| 459 |
+
- **@benbarclay** — 3 PRs · Docker run-as-host-user so bind mounts don't get root-owned
|
| 460 |
+
- **@vominh1919** — 2 PRs
|
| 461 |
+
- **@stephenschoettler** — 2 PRs
|
| 462 |
+
- **@kevin-ho** — ConPTY mouse-injection fix (#15488)
|
| 463 |
+
- **@Zjianru** — cross-provider reasoning_content isolation + DeepSeek/Kimi empty-reasoning injection (#15749, #15762)
|
| 464 |
+
- **@web3blind** — Telegram chat allowlists for groups and forums (#15027)
|
| 465 |
+
- **@SHL0MS** — 9 new TouchDesigner-MCP reference docs (#16768)
|
| 466 |
+
- **@0xDevNinja** — curator `restore_skill` nested-archive fix (#17951)
|
| 467 |
+
- **@y0shua1ee** — curator `use` activity fix (#17953)
|
| 468 |
+
|
| 469 |
+
### Also contributing
|
| 470 |
+
Salvaged or co-authored work from **@isaachuangGMICLOUD** (GMI Cloud), earlier upstream PRs from the original author of each salvage chain, and a long tail of one-shot fixes, documentation nudges, and skill contributions from the community.
|
| 471 |
+
|
| 472 |
+
### All Contributors (alphabetical, excluding @teknium1)
|
| 473 |
+
|
| 474 |
+
@0xbyt4, @0xharryriddle, @0xDevNinja, @0z1-ghb, @5park1e, @A-FdL-Prog, @aj-nt, @akhater, @alblez, @alexg0bot,
|
| 475 |
+
@alexzhu0, @AllardQuek, @alt-glitch, @amanning3390, @amanuel2, @AndreKurait, @andrewhosf, @Andy283, @andyylin,
|
| 476 |
+
@angel12, @AntAISecurityLab, @ash, @austinpickett, @badgerbees, @BadTechBandit, @Bartok9, @beenherebefore,
|
| 477 |
+
@beesrsj2500, @BeliefanX, @benbarclay, @benjaminsehl, @BlackishGreen33, @bloodcarter, @BlueBirdBack,
|
| 478 |
+
@briandevans, @brooklynnicholson, @bsgdigital, @buray, @bwjoke, @camaragon, @cdanis, @cgarwood82,
|
| 479 |
+
@charles-brooks, @chen1749144759, @chengoak, @ching-kaching, @Contentment003111, @crayfish-ai, @CruxExperts,
|
| 480 |
+
@cyclingwithelephants, @dandaka, @danklynn, @ddupont808, @dhabibi, @difujia, @dimitrovi, @dlkakbs,
|
| 481 |
+
@dontcallmejames, @EKKOLearnAI, @emozilla, @ericnicolaides, @Erosika, @ethernet8023, @exiao, @Feranmi10,
|
| 482 |
+
@flobo3, @foxion37, @georgeglessner, @georgex8001, @ghostmfr, @H-Ali13381, @HangGlidersRule, @harryplusplus,
|
| 483 |
+
@haru398801, @heathley, @hejuntt1014, @hekaru-agent, @helix4u, @Heltman, @HenkDz, @heyitsaamir, @hharry11,
|
| 484 |
+
@hhhonzik, @hhuang91, @HiddenPuppy, @htsh, @iamagenius00, @in-liberty420, @innocarpe, @irispillars, @iRonin,
|
| 485 |
+
@isaachuangGMICLOUD, @Ito-69, @j3ffffff, @jackjin1997, @jakubkrcmar, @Jason2031, @JayGwod, @jerome-benoit,
|
| 486 |
+
@johnncenae, @Kailigithub, @keiravoss94, @kevin-ho, @knockyai, @konsisumer, @kshitijk4poor, @kunlabs, @l0hde,
|
| 487 |
+
@Leihb, @leoneparise, @LeonSGP43, @liizfq, @liuhao1024, @loongzhao, @lsdsjy, @luyao618, @ma-pony, @Magaav,
|
| 488 |
+
@MagicRay1217, @math0r-be, @MattMaximo, @maxims-oss, @MaxyMoos, @maymuneth, @mcndjxlefnd, @memosr,
|
| 489 |
+
@MestreY0d4-Uninter, @mewwts, @Mirac1eSky, @MorAlekss, @mrhwick, @mrunmayee17, @mssteuer, @Nanako0129,
|
| 490 |
+
@nazirulhafiy, @Nerijusas, @Nicecsh, @nicoloboschi, @nightq, @ningfangbin, @octo-patch, @Octopus,
|
| 491 |
+
@OutThisLife, @Paperclip, @pein892, @perlowja, @prasadus92, @qike-ms, @qiyin-code, @Readon, @ReginaldasR,
|
| 492 |
+
@revaraver, @rfilgueiras, @rmoen, @romanornr, @rugvedS07, @rylena, @samrusani, @Sanjays2402, @sasha-id,
|
| 493 |
+
@Satoshi-agi, @scheidti, @scotttrinh, @season179, @SeeYangZhi, @sgaofen, @shamork, @shannonsands, @SHL0MS,
|
| 494 |
+
@simbam99, @Societus, @socrates1024, @Sonoyunchu, @sprmn24, @stephenschoettler, @tangyuanjc, @TechPrototyper,
|
| 495 |
+
@tekgnosis-net, @ThomassJonax, @tmimmanuel, @tochukwuada, @Tosko4, @Tranquil-Flow, @twozle, @txbxxx,
|
| 496 |
+
@UgwujaGeorge, @Versun, @vlwkaos, @voidborne-d, @vominh1919, @Wang-tianhao, @Wangshengyang2004, @web3blind,
|
| 497 |
+
@westers, @Wysie, @xandersbell, @xiahu88988, @XieNBi, @xinbenlv, @xnbi, @y0shua1ee, @yatesjalex, @yes999zc,
|
| 498 |
+
@yeyitech, @Yoimex, @YueLich, @Yukipukii1, @zhiyanliu, @zicochaos, @Zjianru, @zkl2333, @zons-zhaozhy,
|
| 499 |
+
@ztexydt-cqh.
|
| 500 |
+
|
| 501 |
+
Also: @Siddharth Balyan, @YuShu.
|
| 502 |
+
|
| 503 |
+
---
|
| 504 |
+
|
| 505 |
+
**Full Changelog**: [v2026.4.23...v2026.4.30](https://github.com/NousResearch/hermes-agent/compare/v2026.4.23...v2026.4.30)
|
RELEASE_v0.13.0.md
ADDED
|
@@ -0,0 +1,641 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Hermes Agent v0.13.0 (v2026.5.7)
|
| 2 |
+
|
| 3 |
+
**Release Date:** May 7, 2026
|
| 4 |
+
**Since v0.12.0:** 864 commits · 588 merged PRs · 829 files changed · 128,366 insertions · 282 issues closed (13 P0, 36 P1) · 295 community contributors (including co-authors)
|
| 5 |
+
|
| 6 |
+
> The Tenacity Release — Hermes Agent now finishes what it starts. Kanban ships as a durable multi-agent board (heartbeat, reclaim, zombie detection, auto-block on incomplete exit, per-task retries, hallucination recovery). `/goal` keeps the agent locked on a target across turns (Ralph loop). Checkpoints v2 rewrites state persistence with real pruning. Gateway auto-resumes interrupted sessions after restart. Cron grows a `no_agent` watchdog mode. A security wave closes 8 P0s — redaction is now ON by default, Discord role-allowlists are guild-scoped, WhatsApp rejects strangers by default, and TOCTOU windows close across auth.json and MCP OAuth. Google Chat becomes the 20th platform. Providers become a pluggable surface. Seven i18n locales ship.
|
| 7 |
+
|
| 8 |
+
---
|
| 9 |
+
|
| 10 |
+
## ✨ Highlights
|
| 11 |
+
|
| 12 |
+
- **Multi-agent Kanban — delegate to an AI team that actually finishes** — Spin up a durable board, drop tasks on it, and let multiple Hermes workers pick them up, hand off, and close them out. Heartbeats, reclaim, zombie detection, retry budgets, and a hallucination gate keep the team honest. One install, many kanbans. ([#17805](https://github.com/NousResearch/hermes-agent/pull/17805), [#19653](https://github.com/NousResearch/hermes-agent/pull/19653), [#20232](https://github.com/NousResearch/hermes-agent/pull/20232), [#20332](https://github.com/NousResearch/hermes-agent/pull/20332), [#21330](https://github.com/NousResearch/hermes-agent/pull/21330), [#21183](https://github.com/NousResearch/hermes-agent/pull/21183), [#21214](https://github.com/NousResearch/hermes-agent/pull/21214))
|
| 13 |
+
|
| 14 |
+
- **`/goal` — the agent doesn't forget what you asked it to do** — Lock the agent onto a target and it stays on task across turns. The Ralph loop as a first-class primitive. ([#18262](https://github.com/NousResearch/hermes-agent/pull/18262), [#18275](https://github.com/NousResearch/hermes-agent/pull/18275), [#21287](https://github.com/NousResearch/hermes-agent/pull/21287))
|
| 15 |
+
|
| 16 |
+
- **Show it a video** — new `video_analyze` tool for native video understanding on Gemini and compatible multimodal models. (@alt-glitch) ([#19301](https://github.com/NousResearch/hermes-agent/pull/19301))
|
| 17 |
+
|
| 18 |
+
- **Clone a voice** — xAI Custom Voices lands as a TTS provider with voice cloning support. (@alt-glitch) ([#18776](https://github.com/NousResearch/hermes-agent/pull/18776))
|
| 19 |
+
|
| 20 |
+
- **Hermes speaks your language** — static gateway + CLI messages translate to 7 locales: Chinese, Japanese, German, Spanish, French, Ukrainian, and Turkish. Docs site gains a Chinese (zh-Hans) locale. ([#20231](https://github.com/NousResearch/hermes-agent/pull/20231), [#20329](https://github.com/NousResearch/hermes-agent/pull/20329), [#20467](https://github.com/NousResearch/hermes-agent/pull/20467), [#20474](https://github.com/NousResearch/hermes-agent/pull/20474), [#20430](https://github.com/NousResearch/hermes-agent/pull/20430), [#20431](https://github.com/NousResearch/hermes-agent/pull/20431))
|
| 21 |
+
|
| 22 |
+
- **Google Chat — the 20th messaging platform** — plus a generic platform-plugin hooks surface so third-party adapters drop in without touching core (IRC and Teams migrated). ([#21306](https://github.com/NousResearch/hermes-agent/pull/21306), [#21331](https://github.com/NousResearch/hermes-agent/pull/21331))
|
| 23 |
+
|
| 24 |
+
- **Sessions survive restarts** — gateway bounces mid-agent, `/update` restarts, source-file reloads — conversations auto-resume when the gateway comes back. ([#21192](https://github.com/NousResearch/hermes-agent/pull/21192))
|
| 25 |
+
|
| 26 |
+
- **Security wave — 8 P0 closures** — redaction ON by default, Discord role-allowlists guild-scoped (CVSS 8.1 cross-guild DM bypass closed), WhatsApp rejects strangers by default, TOCTOU windows closed across `auth.json` and MCP OAuth, browser enforces cloud-metadata SSRF floor, cron prompt-injection scans assembled skill content, `hermes debug share` redacts at upload. ([#21193](https://github.com/NousResearch/hermes-agent/pull/21193), [#21241](https://github.com/NousResearch/hermes-agent/pull/21241), [#21291](https://github.com/NousResearch/hermes-agent/pull/21291), [#21176](https://github.com/NousResearch/hermes-agent/pull/21176), [#21194](https://github.com/NousResearch/hermes-agent/pull/21194), [#21228](https://github.com/NousResearch/hermes-agent/pull/21228), [#21350](https://github.com/NousResearch/hermes-agent/pull/21350), [#19318](https://github.com/NousResearch/hermes-agent/pull/19318))
|
| 27 |
+
|
| 28 |
+
- **Checkpoints v2** — state persistence rewritten. Real pruning, disk guardrails, no more orphan shadow repos. ([#20709](https://github.com/NousResearch/hermes-agent/pull/20709))
|
| 29 |
+
|
| 30 |
+
- **The agent lints its own writes** — post-write delta lint on `write_file` + `patch`. Python, JSON, YAML, TOML. Syntax errors surface immediately instead of shipping downstream. ([#20191](https://github.com/NousResearch/hermes-agent/pull/20191))
|
| 31 |
+
|
| 32 |
+
- **`no_agent` cron mode — script-only watchdog** — cron jobs can now skip the agent entirely and just run a script. Empty stdout is silent, non-empty gets delivered verbatim. ([#19709](https://github.com/NousResearch/hermes-agent/pull/19709))
|
| 33 |
+
|
| 34 |
+
- **Platform allowlists everywhere** — `allowed_channels` / `allowed_chats` / `allowed_rooms` config across Slack, Telegram, Mattermost, Matrix, and DingTalk. ([#21251](https://github.com/NousResearch/hermes-agent/pull/21251))
|
| 35 |
+
|
| 36 |
+
- **Providers are now plugins** — `ProviderProfile` ABC + `plugins/model-providers/`. Drop in third-party providers without touching core. ([#20324](https://github.com/NousResearch/hermes-agent/pull/20324))
|
| 37 |
+
|
| 38 |
+
- **API server — long-term memory per session** — `X-Hermes-Session-Key` header gives memory providers a stable session identifier. ([#20199](https://github.com/NousResearch/hermes-agent/pull/20199))
|
| 39 |
+
|
| 40 |
+
- **MCP levels up** — SSE transport with OAuth forwarding, stale-pipe retries, image results surface as MEDIA tags instead of getting dropped, keepalive on long-lived lifecycle waits. ([#21227](https://github.com/NousResearch/hermes-agent/pull/21227), [#21323](https://github.com/NousResearch/hermes-agent/pull/21323), [#21289](https://github.com/NousResearch/hermes-agent/pull/21289), [#21328](https://github.com/NousResearch/hermes-agent/pull/21328), [#20209](https://github.com/NousResearch/hermes-agent/pull/20209))
|
| 41 |
+
|
| 42 |
+
- **Curator grows subcommands** — `hermes curator archive`, `prune`, `list-archived`. Manual `hermes curator run` is synchronous now — you see results without polling. ([#20200](https://github.com/NousResearch/hermes-agent/pull/20200), [#21236](https://github.com/NousResearch/hermes-agent/pull/21236), [#21216](https://github.com/NousResearch/hermes-agent/pull/21216))
|
| 43 |
+
|
| 44 |
+
- **ACP — `/steer` and `/queue`** — direct the in-flight agent or queue follow-ups from Zed, VS Code, or JetBrains. Plus atomic session persistence and reasoning-metadata preservation across restarts. (@HenkDz) ([#18114](https://github.com/NousResearch/hermes-agent/pull/18114), [#20279](https://github.com/NousResearch/hermes-agent/pull/20279), [#20296](https://github.com/NousResearch/hermes-agent/pull/20296), [#20433](https://github.com/NousResearch/hermes-agent/pull/20433))
|
| 45 |
+
|
| 46 |
+
- **TUI glow-up** — `/model` picker matches `hermes model` with inline auth (@austinpickett), collapsible startup banner sections (@kshitijk4poor), context-compression counter in the status bar. ([#18117](https://github.com/NousResearch/hermes-agent/pull/18117), [#20625](https://github.com/NousResearch/hermes-agent/pull/20625), [#21218](https://github.com/NousResearch/hermes-agent/pull/21218))
|
| 47 |
+
|
| 48 |
+
- **Dashboard grows up** — Plugins page (manage, enable/disable, auth status) (@austinpickett), Profiles management page (@vincez-hms-coder), sortable analytics tables, reverse-proxy support via `X-Forwarded-Prefix`, new `default-large` 18px theme. ([#18095](https://github.com/NousResearch/hermes-agent/pull/18095), [#16419](https://github.com/NousResearch/hermes-agent/pull/16419), [#18192](https://github.com/NousResearch/hermes-agent/pull/18192), [#21296](https://github.com/NousResearch/hermes-agent/pull/21296), [#20820](https://github.com/NousResearch/hermes-agent/pull/20820))
|
| 49 |
+
|
| 50 |
+
- **SearXNG + split web tools** — SearXNG ships as a native search-only backend; web tools now let you pick different backends per capability (search vs extract vs browse). (@kshitijk4poor) ([#20823](https://github.com/NousResearch/hermes-agent/pull/20823), [#20061](https://github.com/NousResearch/hermes-agent/pull/20061), [#20841](https://github.com/NousResearch/hermes-agent/pull/20841))
|
| 51 |
+
|
| 52 |
+
- **OpenRouter response caching** — explicit cache control for models that expose it. (@kshitijk4poor) ([#19132](https://github.com/NousResearch/hermes-agent/pull/19132))
|
| 53 |
+
|
| 54 |
+
- **`[[as_document]]` — skill media-routing directive** — skills can force the gateway to deliver output as a document on platforms that support it. ([#21210](https://github.com/NousResearch/hermes-agent/pull/21210))
|
| 55 |
+
|
| 56 |
+
- **`transform_llm_output` plugin hook** — new lifecycle hook that lets plugins reshape or filter LLM output before it hits the conversation. Useful for context-window reducers and content filters. ([#21235](https://github.com/NousResearch/hermes-agent/pull/21235))
|
| 57 |
+
|
| 58 |
+
- **Nous OAuth persists across profiles** — shared token store: sign in once, every profile inherits the session. ([#19712](https://github.com/NousResearch/hermes-agent/pull/19712))
|
| 59 |
+
|
| 60 |
+
- **QQBot — native approval keyboards** — feature parity with Telegram / Discord approval UX. Chunked upload, quoted attachments. ([#21342](https://github.com/NousResearch/hermes-agent/pull/21342), [#21353](https://github.com/NousResearch/hermes-agent/pull/21353))
|
| 61 |
+
|
| 62 |
+
- **6 new optional skills** — Shopify (Admin + Storefront GraphQL), here.now, shop-app personal shopping assistant, Anthropic financial-services bundle, kanban-video-orchestrator (@SHL0MS), searxng-search (@kshitijk4poor). ([#18116](https://github.com/NousResearch/hermes-agent/pull/18116), [#18170](https://github.com/NousResearch/hermes-agent/pull/18170), [#20702](https://github.com/NousResearch/hermes-agent/pull/20702), [#21180](https://github.com/NousResearch/hermes-agent/pull/21180), [#19281](https://github.com/NousResearch/hermes-agent/pull/19281), [#20841](https://github.com/NousResearch/hermes-agent/pull/20841))
|
| 63 |
+
|
| 64 |
+
- **New models** — `deepseek/deepseek-v4-pro`, `x-ai/grok-4.3`, `openrouter/owl-alpha` (free), `tencent/hy3-preview` (@Contentment003111), Arcee Trinity Large Thinking temperature + compression overrides. ([#20495](https://github.com/NousResearch/hermes-agent/pull/20495), [#20497](https://github.com/NousResearch/hermes-agent/pull/20497), [#18071](https://github.com/NousResearch/hermes-agent/pull/18071), [#21077](https://github.com/NousResearch/hermes-agent/pull/21077), [#20473](https://github.com/NousResearch/hermes-agent/pull/20473))
|
| 65 |
+
|
| 66 |
+
- **100 fresh CLI startup tips** — the random tip banner gets 100 new entries covering cron, kanban, curator, plugins, and lesser-known flags. ([#20168](https://github.com/NousResearch/hermes-agent/pull/20168))
|
| 67 |
+
|
| 68 |
+
---
|
| 69 |
+
|
| 70 |
+
## 🧩 Multi-Agent Kanban (Durable)
|
| 71 |
+
|
| 72 |
+
### New — durable multi-profile collaboration board
|
| 73 |
+
- **`feat(kanban): durable multi-profile collaboration board`** — post-revert reimplementation, multi-profile by design ([#17805](https://github.com/NousResearch/hermes-agent/pull/17805))
|
| 74 |
+
- **Multi-project boards** — one install, many kanbans ([#19653](https://github.com/NousResearch/hermes-agent/pull/19653), [#19679](https://github.com/NousResearch/hermes-agent/pull/19679))
|
| 75 |
+
- **Share board, workspaces, and worker logs across profiles** ([#19378](https://github.com/NousResearch/hermes-agent/pull/19378))
|
| 76 |
+
- **Hallucination gate + recovery UX for worker-created-card claims** (closes #20017) ([#20232](https://github.com/NousResearch/hermes-agent/pull/20232))
|
| 77 |
+
- **Generic diagnostics engine for task distress signals** ([#20332](https://github.com/NousResearch/hermes-agent/pull/20332))
|
| 78 |
+
- **Per-task `max_retries` override** (supersedes #20972) ([#21330](https://github.com/NousResearch/hermes-agent/pull/21330))
|
| 79 |
+
- **Multiline textarea for inline-create title** (salvage of #20970) ([#21243](https://github.com/NousResearch/hermes-agent/pull/21243))
|
| 80 |
+
|
| 81 |
+
### Kanban Dashboard
|
| 82 |
+
- **Workspace kind + path inputs in inline create form** ([#19679](https://github.com/NousResearch/hermes-agent/pull/19679))
|
| 83 |
+
- **Per-platform home-channel notification toggles** ([#19864](https://github.com/NousResearch/hermes-agent/pull/19864))
|
| 84 |
+
- **Sharper home-channel toggle contrast + drop → running action** ([#19916](https://github.com/NousResearch/hermes-agent/pull/19916))
|
| 85 |
+
- Fix: reject direct status transition to 'running' via dashboard API (salvage of #19554) ([#19705](https://github.com/NousResearch/hermes-agent/pull/19705))
|
| 86 |
+
- Fix: dashboard board pin authoritative over server current file (#20879) ([#21230](https://github.com/NousResearch/hermes-agent/pull/21230))
|
| 87 |
+
- Fix: treat dashboard event-stream cancellation as normal shutdown (#20790) ([#21222](https://github.com/NousResearch/hermes-agent/pull/21222))
|
| 88 |
+
- Fix: filter dashboard board by selected tenant (#19817) ([#21349](https://github.com/NousResearch/hermes-agent/pull/21349))
|
| 89 |
+
- Fix: code/pre styling theme-immune across all themes (#21086) ([#21247](https://github.com/NousResearch/hermes-agent/pull/21247))
|
| 90 |
+
- Fix: reset `<code>` background inside dashboard board ([#20687](https://github.com/NousResearch/hermes-agent/pull/20687))
|
| 91 |
+
- Fix: preserve dashboard completion summaries + add kanban edit (salvages #20016) ([#20195](https://github.com/NousResearch/hermes-agent/pull/20195))
|
| 92 |
+
- Fix: avoid fragile failure-column renames (salvage #20848) (@kshitijk4poor) ([#20855](https://github.com/NousResearch/hermes-agent/pull/20855))
|
| 93 |
+
|
| 94 |
+
### Worker lifecycle + reliability
|
| 95 |
+
- **Heartbeat + reclaim + zombie + retry-cap fixes** (#21147, #21141, #21169, #20881) ([#21183](https://github.com/NousResearch/hermes-agent/pull/21183))
|
| 96 |
+
- **Auto-block workers that exit without completing + shutdown race** (#20894) ([#21214](https://github.com/NousResearch/hermes-agent/pull/21214))
|
| 97 |
+
- **Detect darwin zombie workers** (salvages #20023) ([#20188](https://github.com/NousResearch/hermes-agent/pull/20188))
|
| 98 |
+
- **Unify failure counter across spawn/timeout/crash outcomes** ([#20410](https://github.com/NousResearch/hermes-agent/pull/20410))
|
| 99 |
+
- **Enforce worker task-ownership on destructive tool calls** ([#19713](https://github.com/NousResearch/hermes-agent/pull/19713))
|
| 100 |
+
- **Drop worker identity claim from KANBAN_GUIDANCE** ([#19427](https://github.com/NousResearch/hermes-agent/pull/19427))
|
| 101 |
+
- Fix: skip dispatch for tasks assigned to non-profile lanes (salvages #20105, #20134) ([#20165](https://github.com/NousResearch/hermes-agent/pull/20165))
|
| 102 |
+
- Fix: include default profile in on-disk assignee enumeration (salvages #20123) ([#20170](https://github.com/NousResearch/hermes-agent/pull/20170))
|
| 103 |
+
- Fix: ignore stale current board pointers (salvages #20063) ([#20183](https://github.com/NousResearch/hermes-agent/pull/20183))
|
| 104 |
+
- Fix: profile discovery ignores HERMES_HOME in custom-root deployments (@jackey8616) ([#19020](https://github.com/NousResearch/hermes-agent/pull/19020))
|
| 105 |
+
- Fix: allow orchestrator profiles to see kanban tools via toolsets config ([#19606](https://github.com/NousResearch/hermes-agent/pull/19606))
|
| 106 |
+
|
| 107 |
+
### Batch salvages
|
| 108 |
+
- Tier-1 batch — metadata test, max_spawn config, run-id lifecycle guard (salvages #19522 #19556 #19829) ([#20440](https://github.com/NousResearch/hermes-agent/pull/20440))
|
| 109 |
+
- Tier-2 batch — doctor, started_at, parent-guard, latest_summary, selects, linked-children ([#20448](https://github.com/NousResearch/hermes-agent/pull/20448))
|
| 110 |
+
|
| 111 |
+
### Documentation
|
| 112 |
+
- Backfill multi-board refs in reference docs ([#19704](https://github.com/NousResearch/hermes-agent/pull/19704))
|
| 113 |
+
- Document `/kanban` slash command ([#19584](https://github.com/NousResearch/hermes-agent/pull/19584))
|
| 114 |
+
- Document recommended handoff evidence metadata (salvage #19512) ([#20415](https://github.com/NousResearch/hermes-agent/pull/20415))
|
| 115 |
+
- Fix orchestrator + worker skill setup instructions (@helix4u) ([#20958](https://github.com/NousResearch/hermes-agent/pull/20958), [#20960](https://github.com/NousResearch/hermes-agent/pull/20960))
|
| 116 |
+
|
| 117 |
+
---
|
| 118 |
+
|
| 119 |
+
## 🎯 Persistent Goals, Checkpoints & Session Durability
|
| 120 |
+
|
| 121 |
+
### `/goal` — persistent cross-turn goals (Ralph loop)
|
| 122 |
+
- **`feat: /goal — persistent cross-turn goals`** ([#18262](https://github.com/NousResearch/hermes-agent/pull/18262))
|
| 123 |
+
- **Docs page — Persistent Goals (/goal)** ([#18275](https://github.com/NousResearch/hermes-agent/pull/18275))
|
| 124 |
+
- Fix: honor configured goal turn budget (salvage #19423) ([#21287](https://github.com/NousResearch/hermes-agent/pull/21287))
|
| 125 |
+
|
| 126 |
+
### Checkpoints v2
|
| 127 |
+
- **Single-store rewrite with real pruning + disk guardrails** ([#20709](https://github.com/NousResearch/hermes-agent/pull/20709))
|
| 128 |
+
|
| 129 |
+
### Session durability
|
| 130 |
+
- **Auto-resume interrupted sessions after gateway restart** (salvage #20888) ([#21192](https://github.com/NousResearch/hermes-agent/pull/21192))
|
| 131 |
+
- **Preserve pending update prompts across restarts** ([#20160](https://github.com/NousResearch/hermes-agent/pull/20160))
|
| 132 |
+
- **Preserve home-channel thread targets across restart notifications** (salvage #18440) ([#19271](https://github.com/NousResearch/hermes-agent/pull/19271))
|
| 133 |
+
- **Preserve thread routing from cached live session sources** ([#21206](https://github.com/NousResearch/hermes-agent/pull/21206))
|
| 134 |
+
- **Preserve assistant metadata when branching sessions** ([#18222](https://github.com/NousResearch/hermes-agent/pull/18222))
|
| 135 |
+
- **Preserve thread routing for /update progress and prompts** ([#18193](https://github.com/NousResearch/hermes-agent/pull/18193))
|
| 136 |
+
- **Preserve document type when merging queued events** ([#18215](https://github.com/NousResearch/hermes-agent/pull/18215))
|
| 137 |
+
|
| 138 |
+
---
|
| 139 |
+
|
| 140 |
+
## 🛡️ Security & Reliability
|
| 141 |
+
|
| 142 |
+
### Security hardening (8 P0 closures)
|
| 143 |
+
- **Enable secret redaction by default** (#17691, #20785) ([#21193](https://github.com/NousResearch/hermes-agent/pull/21193))
|
| 144 |
+
- **Discord — scope `DISCORD_ALLOWED_ROLES` to originating guild** (#12136, CVSS 8.1) ([#21241](https://github.com/NousResearch/hermes-agent/pull/21241))
|
| 145 |
+
- **WhatsApp — reject strangers by default, never respond in self-chat** (#8389) ([#21291](https://github.com/NousResearch/hermes-agent/pull/21291))
|
| 146 |
+
- **MCP OAuth — close TOCTOU window when saving credentials** ([#21176](https://github.com/NousResearch/hermes-agent/pull/21176))
|
| 147 |
+
- **`hermes_cli/auth.py` — close TOCTOU window in credential writers** ([#21194](https://github.com/NousResearch/hermes-agent/pull/21194))
|
| 148 |
+
- **Browser — enforce cloud-metadata SSRF floor in hybrid routing** (#16234) ([#21228](https://github.com/NousResearch/hermes-agent/pull/21228))
|
| 149 |
+
- **`hermes debug share` — redact log content at upload time** (@GodsBoy) ([#19318](https://github.com/NousResearch/hermes-agent/pull/19318))
|
| 150 |
+
- **Cron — scan assembled prompt including skill content for prompt injection** (#3968) ([#21350](https://github.com/NousResearch/hermes-agent/pull/21350))
|
| 151 |
+
- **Restore .env/auth.json/state.db with 0600 perms** ([#19699](https://github.com/NousResearch/hermes-agent/pull/19699))
|
| 152 |
+
- **SRI integrity for dashboard plugin scripts** (salvage #19389) ([#21277](https://github.com/NousResearch/hermes-agent/pull/21277))
|
| 153 |
+
- **Bind Meet node server to localhost, restrict token file to owner read** ([#19597](https://github.com/NousResearch/hermes-agent/pull/19597))
|
| 154 |
+
- **Extend sensitive-write target to cover shell RC and credential files** ([#19282](https://github.com/NousResearch/hermes-agent/pull/19282))
|
| 155 |
+
- **Harden YOLO mode env parsing against quoted-bool strings** ([#18214](https://github.com/NousResearch/hermes-agent/pull/18214))
|
| 156 |
+
- **OSV-Scanner CI + Dependabot for github-actions only** ([#20037](https://github.com/NousResearch/hermes-agent/pull/20037))
|
| 157 |
+
|
| 158 |
+
### Reliability — critical bug closures
|
| 159 |
+
- **CLI crash on startup — `Invalid key 'c-S-c'`** (P0, prompt_toolkit doesn't support Shift modifier) ([#19895](https://github.com/NousResearch/hermes-agent/pull/19895), [#19919](https://github.com/NousResearch/hermes-agent/pull/19919))
|
| 160 |
+
- **CLOSE_WAIT fd leak audit** — httpx keepalive + WhatsApp aiohttp leak + Feishu hygiene (#18451) ([#18766](https://github.com/NousResearch/hermes-agent/pull/18766))
|
| 161 |
+
- **Gateway creates AIAgent with empty OpenRouter API key when OPENROUTER_API_KEY is missing** (#20982) — fallback providers correctly honored
|
| 162 |
+
- **Background review + curator protected from overwriting bundled/hub skills** (#20273) ([#20194](https://github.com/NousResearch/hermes-agent/pull/20194))
|
| 163 |
+
- **TUI compression continuation — ghost sessions with incomplete metadata** (#20001)
|
| 164 |
+
- **`hermes mcp add` silently launches chat instead of registering MCP server** (#19785) ([#21204](https://github.com/NousResearch/hermes-agent/pull/21204))
|
| 165 |
+
- **Background review agent runtime propagation** — provider/model/credentials now actually inherit from parent
|
| 166 |
+
- **Inbound document host paths translated to container paths for Docker backend** (salvage #19048) ([#21184](https://github.com/NousResearch/hermes-agent/pull/21184))
|
| 167 |
+
- **Matrix gateway race between auto-redaction and message delivery with high-speed models** (#19075)
|
| 168 |
+
- **`/new` during active agent session never sends response on Telegram** (#18912)
|
| 169 |
+
|
| 170 |
+
---
|
| 171 |
+
|
| 172 |
+
## 📱 Messaging Platforms (Gateway)
|
| 173 |
+
|
| 174 |
+
### New platform
|
| 175 |
+
- **Google Chat — 20th platform** + generic `env_enablement_fn` / `cron_deliver_env_var` platform-plugin hooks (IRC + Teams migrated) ([#21306](https://github.com/NousResearch/hermes-agent/pull/21306), [#21331](https://github.com/NousResearch/hermes-agent/pull/21331))
|
| 176 |
+
|
| 177 |
+
### Cross-platform
|
| 178 |
+
- **`allowed_{channels,chats,rooms}` whitelist** — Slack (salvage #7401), Telegram, Mattermost, Matrix, DingTalk ([#21251](https://github.com/NousResearch/hermes-agent/pull/21251))
|
| 179 |
+
- **Per-platform `gateway_restart_notification` flag** ([#20892](https://github.com/NousResearch/hermes-agent/pull/20892))
|
| 180 |
+
- **`busy_ack_enabled` config — suppress ack messages** ([#18194](https://github.com/NousResearch/hermes-agent/pull/18194))
|
| 181 |
+
- **Auto-delete slash-command system notices after TTL** ([#18266](https://github.com/NousResearch/hermes-agent/pull/18266))
|
| 182 |
+
- **Opt-in cleanup of temporary progress bubbles** ([#21186](https://github.com/NousResearch/hermes-agent/pull/21186))
|
| 183 |
+
- **`[[as_document]]` directive — skill media routing** (salvage #19069) ([#21210](https://github.com/NousResearch/hermes-agent/pull/21210))
|
| 184 |
+
- **`hermes gateway list` — cross-profile status** (salvage #19129) ([#21225](https://github.com/NousResearch/hermes-agent/pull/21225))
|
| 185 |
+
- **Auto-resume interrupted sessions after restart** (salvage #20888) ([#21192](https://github.com/NousResearch/hermes-agent/pull/21192))
|
| 186 |
+
- **Atomic restart markers + Windows runtime-lock offset** (#17842) ([#18179](https://github.com/NousResearch/hermes-agent/pull/18179))
|
| 187 |
+
- Fix: `config.yaml` wins over `.env` for agent/display/timezone settings ([#18764](https://github.com/NousResearch/hermes-agent/pull/18764))
|
| 188 |
+
- Fix: auto-restart when source files change out from under us (#17648) ([#18409](https://github.com/NousResearch/hermes-agent/pull/18409))
|
| 189 |
+
- Fix: use git HEAD SHA for stale-code check, not file mtimes ([#19740](https://github.com/NousResearch/hermes-agent/pull/19740))
|
| 190 |
+
- Fix: shutdown + restart hygiene — drain timeout, false-fatal, success log ([#18761](https://github.com/NousResearch/hermes-agent/pull/18761))
|
| 191 |
+
- Fix: preserve max_turns after env reload (salvage #19183) ([#21240](https://github.com/NousResearch/hermes-agent/pull/21240))
|
| 192 |
+
- Fix: exclude ancestor PIDs from gateway process scan ([#19586](https://github.com/NousResearch/hermes-agent/pull/19586))
|
| 193 |
+
- Fix: move quick-command alias dispatch before built-ins ([#19588](https://github.com/NousResearch/hermes-agent/pull/19588))
|
| 194 |
+
- Fix: show other profiles in 'gateway status' to prevent confusion ([#19582](https://github.com/NousResearch/hermes-agent/pull/19582))
|
| 195 |
+
- Fix: include external_dirs skills in Telegram/Discord slash commands (salvage #8790) ([#18741](https://github.com/NousResearch/hermes-agent/pull/18741))
|
| 196 |
+
- Fix: match disabled/optional skills by frontmatter slug, not dir name ([#18753](https://github.com/NousResearch/hermes-agent/pull/18753))
|
| 197 |
+
- Fix: read /status token totals from SessionDB (#17158) ([#18206](https://github.com/NousResearch/hermes-agent/pull/18206))
|
| 198 |
+
- Fix: snapshot callback generation after agent binds it, not before ([#18219](https://github.com/NousResearch/hermes-agent/pull/18219))
|
| 199 |
+
- Fix: re-inject topic-bound skill after /new or /reset ([#18205](https://github.com/NousResearch/hermes-agent/pull/18205))
|
| 200 |
+
- Fix: isolate pending native image paths by session ([#18202](https://github.com/NousResearch/hermes-agent/pull/18202))
|
| 201 |
+
- Fix: clear queued reload skills notes on new/resume/branch ([#19431](https://github.com/NousResearch/hermes-agent/pull/19431))
|
| 202 |
+
- Fix: hide required-arg commands from Telegram menu ([#19400](https://github.com/NousResearch/hermes-agent/pull/19400))
|
| 203 |
+
- Fix: bridge top-level `require_mention` to Telegram config ([#19429](https://github.com/NousResearch/hermes-agent/pull/19429))
|
| 204 |
+
- Fix: suppress duplicate voice transcripts ([#19428](https://github.com/NousResearch/hermes-agent/pull/19428))
|
| 205 |
+
- Fix: show friendly error when service is not installed ([#19707](https://github.com/NousResearch/hermes-agent/pull/19707))
|
| 206 |
+
- Fix: read context_length from custom_providers in session info header ([#19708](https://github.com/NousResearch/hermes-agent/pull/19708))
|
| 207 |
+
- Fix: preserve WSL interop PATH in systemd units ([#19867](https://github.com/NousResearch/hermes-agent/pull/19867))
|
| 208 |
+
- Fix: handle planned service stops (salvage #19876) ([#19936](https://github.com/NousResearch/hermes-agent/pull/19936))
|
| 209 |
+
- Fix: keep DoH-confirmed Telegram IPs that match system DNS (salvage #17043) ([#20175](https://github.com/NousResearch/hermes-agent/pull/20175))
|
| 210 |
+
- Fix: load `reply_to_mode` from config.yaml for Discord + Telegram (salvage #17117) ([#20171](https://github.com/NousResearch/hermes-agent/pull/20171))
|
| 211 |
+
- Fix: tolerate malformed HERMES_HUMAN_DELAY_* env vars (salvage #16933) ([#20217](https://github.com/NousResearch/hermes-agent/pull/20217))
|
| 212 |
+
- Fix: deterministic thread eviction preserves newest entries (salvage #13639) ([#20285](https://github.com/NousResearch/hermes-agent/pull/20285))
|
| 213 |
+
- Fix: don't dead-end setup wizard when only system-scope unit is installed ([#20905](https://github.com/NousResearch/hermes-agent/pull/20905))
|
| 214 |
+
- Fix: wait for systemd restart readiness + harden Discord slash-command sync ([#20949](https://github.com/NousResearch/hermes-agent/pull/20949))
|
| 215 |
+
- Fix: avoid duplicated Responses history (salvage #18995) ([#21185](https://github.com/NousResearch/hermes-agent/pull/21185))
|
| 216 |
+
- Fix: surface bootstrap failures to stderr (salvage #21157) ([#21278](https://github.com/NousResearch/hermes-agent/pull/21278))
|
| 217 |
+
- Fix: log agent task failures instead of silently losing usage data (salvage #21159) ([#21274](https://github.com/NousResearch/hermes-agent/pull/21274))
|
| 218 |
+
- Fix: log runtime-status write failures with rate-limiting (salvage #21158) ([#21285](https://github.com/NousResearch/hermes-agent/pull/21285))
|
| 219 |
+
- Fix: reset-failed before every fallback restart so the gateway can't get stranded ([#21371](https://github.com/NousResearch/hermes-agent/pull/21371))
|
| 220 |
+
- Fix: Telegram — preserve `thread_id=1` for forum General typing indicator ([#21390](https://github.com/NousResearch/hermes-agent/pull/21390))
|
| 221 |
+
- Fix: batch critical fixes — session resume, /new race, HA WebSocket scheme (@kshitijk4poor) ([#19182](https://github.com/NousResearch/hermes-agent/pull/19182))
|
| 222 |
+
|
| 223 |
+
### Telegram
|
| 224 |
+
- **DM user-managed multi-session topics** (salvage of #19185) ([#19206](https://github.com/NousResearch/hermes-agent/pull/19206))
|
| 225 |
+
|
| 226 |
+
### Discord
|
| 227 |
+
- **Message deletion action** (salvage #19052) ([#21197](https://github.com/NousResearch/hermes-agent/pull/21197))
|
| 228 |
+
- Fix: allow `free_response_channels` to override `DISCORD_IGNORE_NO_MENTION` ([#19629](https://github.com/NousResearch/hermes-agent/pull/19629))
|
| 229 |
+
|
| 230 |
+
### Slack
|
| 231 |
+
- Fix: ephemeral slash-command ack, private notice delivery, format_message fixes (@kshitijk4poor) ([#18198](https://github.com/NousResearch/hermes-agent/pull/18198))
|
| 232 |
+
|
| 233 |
+
### WhatsApp
|
| 234 |
+
- Fix: load WhatsApp home channel from env overrides ([#18190](https://github.com/NousResearch/hermes-agent/pull/18190))
|
| 235 |
+
|
| 236 |
+
### Feishu
|
| 237 |
+
- **Operator-configurable bot admission and mention policy** ([#18208](https://github.com/NousResearch/hermes-agent/pull/18208))
|
| 238 |
+
- Fix: force text mode for markdown tables (salvage of #13723 by @WuTianyi123) ([#20275](https://github.com/NousResearch/hermes-agent/pull/20275))
|
| 239 |
+
|
| 240 |
+
### Matrix + Email
|
| 241 |
+
- Fix: `/sethome` on Matrix and Email now persists across restarts ([#18272](https://github.com/NousResearch/hermes-agent/pull/18272))
|
| 242 |
+
|
| 243 |
+
### Teams
|
| 244 |
+
- **Docs + feat: sidebar + threading with group-chat fallback** ([#20042](https://github.com/NousResearch/hermes-agent/pull/20042))
|
| 245 |
+
|
| 246 |
+
### Weixin
|
| 247 |
+
- Fix: deduplicate Weixin messages by content fingerprint ([#19742](https://github.com/NousResearch/hermes-agent/pull/19742))
|
| 248 |
+
|
| 249 |
+
### QQBot
|
| 250 |
+
- **Port SDK improvements in-tree — chunked upload, approval keyboards, quoted attachments** ([#21342](https://github.com/NousResearch/hermes-agent/pull/21342))
|
| 251 |
+
- **Wire native tool-approval UX via inline keyboards** ([#21353](https://github.com/NousResearch/hermes-agent/pull/21353))
|
| 252 |
+
|
| 253 |
+
---
|
| 254 |
+
|
| 255 |
+
## 🏗️ Core Agent & Architecture
|
| 256 |
+
|
| 257 |
+
### Provider & Model Support
|
| 258 |
+
|
| 259 |
+
#### Pluggable providers
|
| 260 |
+
- **ProviderProfile ABC + `plugins/model-providers/`** — inference providers are now a pluggable surface (salvage of #14424) ([#20324](https://github.com/NousResearch/hermes-agent/pull/20324))
|
| 261 |
+
- **`list_picker_providers`** — credential-filtered picker (salvage #13561) ([#20298](https://github.com/NousResearch/hermes-agent/pull/20298))
|
| 262 |
+
- **Remove `/provider` alias for `/model`** ([#20358](https://github.com/NousResearch/hermes-agent/pull/20358))
|
| 263 |
+
- **Shared Hermes dotenv loader across CLI + plugins** (salvage #13660) ([#20281](https://github.com/NousResearch/hermes-agent/pull/20281))
|
| 264 |
+
- **Nous OAuth persisted across profiles via shared token store** ([#19712](https://github.com/NousResearch/hermes-agent/pull/19712))
|
| 265 |
+
|
| 266 |
+
#### New models
|
| 267 |
+
- `deepseek/deepseek-v4-pro` added to OpenRouter + Nous Portal ([#20495](https://github.com/NousResearch/hermes-agent/pull/20495))
|
| 268 |
+
- `x-ai/grok-4.3` added to OpenRouter + Nous Portal ([#20497](https://github.com/NousResearch/hermes-agent/pull/20497))
|
| 269 |
+
- `openrouter/owl-alpha` (free tier) added to curated OpenRouter list ([#18071](https://github.com/NousResearch/hermes-agent/pull/18071))
|
| 270 |
+
- `tencent/hy3-preview` paid route on OpenRouter (@Contentment003111) ([#21077](https://github.com/NousResearch/hermes-agent/pull/21077))
|
| 271 |
+
- Arcee Trinity Large Thinking — temperature + compression overrides ([#20473](https://github.com/NousResearch/hermes-agent/pull/20473))
|
| 272 |
+
- Rename `x-ai/grok-4.20-beta` to `x-ai/grok-4.20` ([#19640](https://github.com/NousResearch/hermes-agent/pull/19640))
|
| 273 |
+
- Demote Vercel AI Gateway to bottom of provider picker ([#18112](https://github.com/NousResearch/hermes-agent/pull/18112))
|
| 274 |
+
|
| 275 |
+
#### Provider configuration
|
| 276 |
+
- **OpenRouter — response caching support** (@kshitijk4poor) ([#19132](https://github.com/NousResearch/hermes-agent/pull/19132))
|
| 277 |
+
- **`image_gen.model` from config.yaml honored** (salvage #19376) ([#21273](https://github.com/NousResearch/hermes-agent/pull/21273))
|
| 278 |
+
- Fix: honor runtime default model during delegate provider resolution (@johnncenae) ([#17587](https://github.com/NousResearch/hermes-agent/pull/17587))
|
| 279 |
+
- Fix: avoid Bedrock credential probe in provider picker (@helix4u) ([#18998](https://github.com/NousResearch/hermes-agent/pull/18998))
|
| 280 |
+
- Fix: drop stale env-var override of persisted provider for cron ([#19627](https://github.com/NousResearch/hermes-agent/pull/19627))
|
| 281 |
+
- Fix: auxiliary curator api_key/base_url into runtime resolution ([#19421](https://github.com/NousResearch/hermes-agent/pull/19421))
|
| 282 |
+
|
| 283 |
+
### Agent Loop & Conversation
|
| 284 |
+
- **`video_analyze` — native video understanding tool** (@alt-glitch) ([#19301](https://github.com/NousResearch/hermes-agent/pull/19301))
|
| 285 |
+
- **Show context compression count in status bar** (CLI + TUI) ([#21218](https://github.com/NousResearch/hermes-agent/pull/21218))
|
| 286 |
+
- **Isolate `get_tool_definitions` quiet_mode cache + dedup LCM injection** (#17335) ([#17889](https://github.com/NousResearch/hermes-agent/pull/17889))
|
| 287 |
+
- Fix: warning-first tool-call loop guardrails ([#18227](https://github.com/NousResearch/hermes-agent/pull/18227))
|
| 288 |
+
- Fix: break permanent empty-response loop from orphan tool-tail ([#21385](https://github.com/NousResearch/hermes-agent/pull/21385))
|
| 289 |
+
- Fix: propagate ContextVars to concurrent tool worker threads (salvage #16660) ([#18123](https://github.com/NousResearch/hermes-agent/pull/18123))
|
| 290 |
+
- Fix: surface self-improvement review summaries across CLI, TUI, and gateway ([#18073](https://github.com/NousResearch/hermes-agent/pull/18073))
|
| 291 |
+
- Fix: serialize concurrent `hermes_tools` RPC calls from `execute_code` ([#17894](https://github.com/NousResearch/hermes-agent/pull/17894), [#17902](https://github.com/NousResearch/hermes-agent/pull/17902))
|
| 292 |
+
- Fix: include system prompt + tool schemas in token estimates for compression ([#18265](https://github.com/NousResearch/hermes-agent/pull/18265))
|
| 293 |
+
|
| 294 |
+
### Compression
|
| 295 |
+
- Fix: skip non-string tool content in dedup pass to prevent AttributeError ([#19398](https://github.com/NousResearch/hermes-agent/pull/19398))
|
| 296 |
+
- Fix: reset `_summary_failure_cooldown_until` on session reset ([#19622](https://github.com/NousResearch/hermes-agent/pull/19622))
|
| 297 |
+
- Fix: trigger fallback on timeout errors alongside model-unavailable errors ([#19665](https://github.com/NousResearch/hermes-agent/pull/19665))
|
| 298 |
+
- Fix: `_prune_old_tool_results` boundary direction ([#19725](https://github.com/NousResearch/hermes-agent/pull/19725))
|
| 299 |
+
- Fix: soften summary prompt for content filters (salvage #19456) ([#21302](https://github.com/NousResearch/hermes-agent/pull/21302))
|
| 300 |
+
|
| 301 |
+
### Delegate
|
| 302 |
+
- Fix: inherit parent fallback_chain in `_build_child_agent` ([#19601](https://github.com/NousResearch/hermes-agent/pull/19601))
|
| 303 |
+
- Fix: guard `_load_config()` against `delegation: null` in config.yaml ([#19662](https://github.com/NousResearch/hermes-agent/pull/19662))
|
| 304 |
+
- Fix: inherit parent api_key when `delegation.base_url` set without `delegation.api_key` ([#19741](https://github.com/NousResearch/hermes-agent/pull/19741))
|
| 305 |
+
- Fix: expand composite toolsets before intersection (salvage #19455) ([#21300](https://github.com/NousResearch/hermes-agent/pull/21300))
|
| 306 |
+
- Fix: correct ACP docs — Claude Code CLI has no --acp flag (salvage #19058) ([#21201](https://github.com/NousResearch/hermes-agent/pull/21201))
|
| 307 |
+
|
| 308 |
+
### Session & Memory
|
| 309 |
+
- **Hindsight — probe API for `update_mode='append'` to dedupe across processes** (@nicoloboschi) ([#20222](https://github.com/NousResearch/hermes-agent/pull/20222))
|
| 310 |
+
|
| 311 |
+
### Curator
|
| 312 |
+
- **`hermes curator archive` and `prune` subcommands** ([#20200](https://github.com/NousResearch/hermes-agent/pull/20200))
|
| 313 |
+
- **`hermes curator list-archived`** (#20651) ([#21236](https://github.com/NousResearch/hermes-agent/pull/21236))
|
| 314 |
+
- **Synchronous manual `hermes curator run`** (#20555) ([#21216](https://github.com/NousResearch/hermes-agent/pull/21216))
|
| 315 |
+
- Fix: preserve `last_report_path` in state ([#18169](https://github.com/NousResearch/hermes-agent/pull/18169))
|
| 316 |
+
- Fix: rewrite cron job skill refs after consolidation ([#18253](https://github.com/NousResearch/hermes-agent/pull/18253))
|
| 317 |
+
- Fix: defer first run + `--dry-run` preview (#18373) ([#18389](https://github.com/NousResearch/hermes-agent/pull/18389))
|
| 318 |
+
- Fix: authoritative `absorbed_into` on delete + restore cron skill links on rollback (#18671) ([#18731](https://github.com/NousResearch/hermes-agent/pull/18731))
|
| 319 |
+
- Fix: prevent false-positive consolidation from substring matching ([#19573](https://github.com/NousResearch/hermes-agent/pull/19573))
|
| 320 |
+
- Fix: only mark agent-created for background-review sediment ([#19621](https://github.com/NousResearch/hermes-agent/pull/19621))
|
| 321 |
+
- Fix: protect hub skills by frontmatter name ([#20194](https://github.com/NousResearch/hermes-agent/pull/20194))
|
| 322 |
+
|
| 323 |
+
---
|
| 324 |
+
|
| 325 |
+
## 🔧 Tool System
|
| 326 |
+
|
| 327 |
+
### File tools
|
| 328 |
+
- **Post-write delta lint on `write_file` + `patch`** — in-proc linters for Python, JSON, YAML, TOML ([#20191](https://github.com/NousResearch/hermes-agent/pull/20191))
|
| 329 |
+
|
| 330 |
+
### Cron
|
| 331 |
+
- **`no_agent` mode — script-only cron jobs (watchdog pattern)** ([#19709](https://github.com/NousResearch/hermes-agent/pull/19709))
|
| 332 |
+
- **`context_from` chaining docs** (salvage #15724) ([#20394](https://github.com/NousResearch/hermes-agent/pull/20394))
|
| 333 |
+
- Fix: treat non-dict origin as missing instead of crashing tick ([#19283](https://github.com/NousResearch/hermes-agent/pull/19283))
|
| 334 |
+
- Fix: bump skill usage when cron jobs load skills ([#19433](https://github.com/NousResearch/hermes-agent/pull/19433))
|
| 335 |
+
- Fix: recover null `next_run_at` jobs ([#19576](https://github.com/NousResearch/hermes-agent/pull/19576))
|
| 336 |
+
- Fix: skip AI call when prerun script produces no output ([#19628](https://github.com/NousResearch/hermes-agent/pull/19628))
|
| 337 |
+
- Fix: expand config.yaml refs during job execution ([#19872](https://github.com/NousResearch/hermes-agent/pull/19872))
|
| 338 |
+
- Fix: serialize `get_due_jobs` writes to prevent parallel state corruption ([#19874](https://github.com/NousResearch/hermes-agent/pull/19874))
|
| 339 |
+
- Fix: initialize MCP servers before constructing the cron AIAgent ([#21354](https://github.com/NousResearch/hermes-agent/pull/21354))
|
| 340 |
+
|
| 341 |
+
### MCP
|
| 342 |
+
- **SSE transport support** (salvage #19135) ([#21227](https://github.com/NousResearch/hermes-agent/pull/21227))
|
| 343 |
+
- **Forward OAuth auth + bump `sse_read_timeout` on SSE transport** ([#21323](https://github.com/NousResearch/hermes-agent/pull/21323))
|
| 344 |
+
- **Retry stale pipe transport failures as session-expired** ([#21289](https://github.com/NousResearch/hermes-agent/pull/21289))
|
| 345 |
+
- **Surface image tool results as MEDIA tags instead of dropping them** ([#21328](https://github.com/NousResearch/hermes-agent/pull/21328))
|
| 346 |
+
- **Periodic keepalive to `_wait_for_lifecycle_event`** (salvage #17016) ([#20209](https://github.com/NousResearch/hermes-agent/pull/20209))
|
| 347 |
+
- Fix: reconnect on terminated sessions ([#19380](https://github.com/NousResearch/hermes-agent/pull/19380))
|
| 348 |
+
- Fix: decouple AnyUrl import from mcp dependency ([#19695](https://github.com/NousResearch/hermes-agent/pull/19695))
|
| 349 |
+
- Fix: `mcp add --command` gets distinct argparse dest ([#21204](https://github.com/NousResearch/hermes-agent/pull/21204))
|
| 350 |
+
- Fix: clear stale thread interrupt before MCP discovery ([#21276](https://github.com/NousResearch/hermes-agent/pull/21276))
|
| 351 |
+
- Fix: report configured timeout in MCP call errors ([#21281](https://github.com/NousResearch/hermes-agent/pull/21281))
|
| 352 |
+
- Fix: include exception type in error messages when str(exc) is empty (salvage #19425) ([#21292](https://github.com/NousResearch/hermes-agent/pull/21292))
|
| 353 |
+
- Fix: re-raise CancelledError explicitly in `MCPServerTask.run` ([#21318](https://github.com/NousResearch/hermes-agent/pull/21318))
|
| 354 |
+
- Fix: coerce numeric tool args defensively in `mcp_serve` ([#21329](https://github.com/NousResearch/hermes-agent/pull/21329))
|
| 355 |
+
- Fix: gate utility stubs on server-advertised capabilities ([#21347](https://github.com/NousResearch/hermes-agent/pull/21347))
|
| 356 |
+
|
| 357 |
+
### Browser
|
| 358 |
+
- Fix: allow explicit CDP override without local agent-browser ([#19670](https://github.com/NousResearch/hermes-agent/pull/19670))
|
| 359 |
+
- Fix: inject `--no-sandbox` for root + AppArmor userns restrictions ([#19747](https://github.com/NousResearch/hermes-agent/pull/19747))
|
| 360 |
+
- Fix: tighten Lightpanda fallback edge cases (@kshitijk4poor) ([#20672](https://github.com/NousResearch/hermes-agent/pull/20672))
|
| 361 |
+
|
| 362 |
+
### Web tools
|
| 363 |
+
- **Per-capability backend selection — search/extract split** (@kshitijk4poor) ([#20061](https://github.com/NousResearch/hermes-agent/pull/20061))
|
| 364 |
+
- **SearXNG native search-only backend** (@kshitijk4poor) ([#20823](https://github.com/NousResearch/hermes-agent/pull/20823))
|
| 365 |
+
|
| 366 |
+
### Approval / Tool gating
|
| 367 |
+
- Fix: wake blocked gateway approvals on session cleanup ([#18171](https://github.com/NousResearch/hermes-agent/pull/18171))
|
| 368 |
+
- Fix: harden YOLO mode env parsing against quoted-bool strings ([#18214](https://github.com/NousResearch/hermes-agent/pull/18214))
|
| 369 |
+
- Fix: extend sensitive write target to cover shell RC and credential files ([#19282](https://github.com/NousResearch/hermes-agent/pull/19282))
|
| 370 |
+
|
| 371 |
+
---
|
| 372 |
+
|
| 373 |
+
## 🔌 Plugin System
|
| 374 |
+
|
| 375 |
+
- **`transform_llm_output` plugin hook** (salvage of #20813) ([#21235](https://github.com/NousResearch/hermes-agent/pull/21235))
|
| 376 |
+
- **Document `env_enablement_fn` + `cron_deliver_env_var` platform-plugin hooks** ([#21331](https://github.com/NousResearch/hermes-agent/pull/21331))
|
| 377 |
+
- **Pluggable surfaces coverage — model-provider guide, full plugin map, opt-in fix** ([#20749](https://github.com/NousResearch/hermes-agent/pull/20749))
|
| 378 |
+
- **Plugin-authoring gaps — image-gen provider guide + publishing a skill tap** ([#20800](https://github.com/NousResearch/hermes-agent/pull/20800))
|
| 379 |
+
|
| 380 |
+
---
|
| 381 |
+
|
| 382 |
+
## 🧩 Skills Ecosystem
|
| 383 |
+
|
| 384 |
+
### New optional skills
|
| 385 |
+
- **Shopify** — Admin + Storefront GraphQL optional skill ([#18116](https://github.com/NousResearch/hermes-agent/pull/18116))
|
| 386 |
+
- **here.now** — optional skill ([#18170](https://github.com/NousResearch/hermes-agent/pull/18170))
|
| 387 |
+
- **shop-app** — personal shopping assistant (optional) ([#20702](https://github.com/NousResearch/hermes-agent/pull/20702))
|
| 388 |
+
- **Anthropic financial-services bundle** — ported as optional finance skills ([#21180](https://github.com/NousResearch/hermes-agent/pull/21180))
|
| 389 |
+
- **kanban-video-orchestrator** — creative optional skill (@SHL0MS) ([#19281](https://github.com/NousResearch/hermes-agent/pull/19281))
|
| 390 |
+
- **searxng-search** — optional skill + Web Search + Extract docs page (@kshitijk4poor) ([#20841](https://github.com/NousResearch/hermes-agent/pull/20841), [#20844](https://github.com/NousResearch/hermes-agent/pull/20844))
|
| 391 |
+
|
| 392 |
+
### Skill UX
|
| 393 |
+
- **Linear skill — add Documents support + Python helper script** ([#20752](https://github.com/NousResearch/hermes-agent/pull/20752))
|
| 394 |
+
- **Modernize Obsidian skill to use file tools** (salvage #19332) ([#20413](https://github.com/NousResearch/hermes-agent/pull/20413))
|
| 395 |
+
- **Default custom tool creation to plugins** (@kshitijk4poor) ([#19755](https://github.com/NousResearch/hermes-agent/pull/19755))
|
| 396 |
+
- **skill_commands cache — rescan on platform scope changes** (salvage #14570 by @LeonSGP43) ([#18739](https://github.com/NousResearch/hermes-agent/pull/18739))
|
| 397 |
+
- **Skills — additional rescan paths in skill_commands cache** (salvage #19042) ([#21181](https://github.com/NousResearch/hermes-agent/pull/21181))
|
| 398 |
+
- Fix: regression tests for non-dict metadata in `extract_skill_conditions` ([#18213](https://github.com/NousResearch/hermes-agent/pull/18213))
|
| 399 |
+
- Docs: explain restoring bundled skills (salvage #19254) ([#20404](https://github.com/NousResearch/hermes-agent/pull/20404))
|
| 400 |
+
- Docs: document `hermes skills reset` subcommand (salvage #11544) ([#20395](https://github.com/NousResearch/hermes-agent/pull/20395))
|
| 401 |
+
- Docs: himalaya v1.2.0 `folder.aliases` syntax ([#19882](https://github.com/NousResearch/hermes-agent/pull/19882))
|
| 402 |
+
- Point agent at `hermes-agent` skill + docs site sync ([#20390](https://github.com/NousResearch/hermes-agent/pull/20390))
|
| 403 |
+
|
| 404 |
+
---
|
| 405 |
+
|
| 406 |
+
## 🖥️ CLI & User Experience
|
| 407 |
+
|
| 408 |
+
### CLI
|
| 409 |
+
- **`/new` accepts optional session name argument** (salvage of #19555) ([#19637](https://github.com/NousResearch/hermes-agent/pull/19637))
|
| 410 |
+
- **100 new CLI startup tips** ([#20168](https://github.com/NousResearch/hermes-agent/pull/20168))
|
| 411 |
+
- **`display.language` — static message translation** (zh/ja/de/es) ([#20231](https://github.com/NousResearch/hermes-agent/pull/20231))
|
| 412 |
+
- **French (fr) locale** (@Foolafroos) ([#20329](https://github.com/NousResearch/hermes-agent/pull/20329))
|
| 413 |
+
- **Ukrainian (uk) locale** ([#20467](https://github.com/NousResearch/hermes-agent/pull/20467))
|
| 414 |
+
- **Turkish (tr) locale** ([#20474](https://github.com/NousResearch/hermes-agent/pull/20474))
|
| 415 |
+
- Fix: recover classic CLI output after resize (@helix4u) ([#20444](https://github.com/NousResearch/hermes-agent/pull/20444))
|
| 416 |
+
- Fix: complete absolute paths as paths (@helix4u) ([#19930](https://github.com/NousResearch/hermes-agent/pull/19930))
|
| 417 |
+
- Fix: resolve lazy session creation regressions (#18370 fallout) (@alt-glitch) ([#20363](https://github.com/NousResearch/hermes-agent/pull/20363))
|
| 418 |
+
- Fix: local backend CLI always uses launch directory (@alt-glitch) ([#19334](https://github.com/NousResearch/hermes-agent/pull/19334))
|
| 419 |
+
- Refactor: drop dead c-S-c key binding (follow-up to #19895) ([#19919](https://github.com/NousResearch/hermes-agent/pull/19919))
|
| 420 |
+
|
| 421 |
+
### TUI (Ink)
|
| 422 |
+
- **`/model` picker overhaul to match `hermes model` with inline auth** (@austinpickett) ([#18117](https://github.com/NousResearch/hermes-agent/pull/18117))
|
| 423 |
+
- **Collapsible sections in startup banner** — skills, system prompt, MCP (@kshitijk4poor) ([#20625](https://github.com/NousResearch/hermes-agent/pull/20625))
|
| 424 |
+
- **Show context compression count in status bar** ([#21218](https://github.com/NousResearch/hermes-agent/pull/21218))
|
| 425 |
+
- Perf: reduce overlay render churn with focused selectors (@OutThisLife) ([#20393](https://github.com/NousResearch/hermes-agent/pull/20393))
|
| 426 |
+
- Fix: restore voice push-to-talk parity (salvage of #16189 by @Montbra) (@OutThisLife) ([#20897](https://github.com/NousResearch/hermes-agent/pull/20897))
|
| 427 |
+
- Fix: kanban button (@austinpickett) ([#18358](https://github.com/NousResearch/hermes-agent/pull/18358))
|
| 428 |
+
|
| 429 |
+
### Dashboard
|
| 430 |
+
- **Plugins page — manage, enable/disable, auth status** (@austinpickett) ([#18095](https://github.com/NousResearch/hermes-agent/pull/18095))
|
| 431 |
+
- **Profiles management page** (@vincez-hms-coder) ([#16419](https://github.com/NousResearch/hermes-agent/pull/16419))
|
| 432 |
+
- **Interactive column sorting in analytics tables** ([#18192](https://github.com/NousResearch/hermes-agent/pull/18192))
|
| 433 |
+
- **`default-large` built-in theme with 18px base size** ([#20820](https://github.com/NousResearch/hermes-agent/pull/20820))
|
| 434 |
+
- **Support serving under URL prefix via `X-Forwarded-Prefix`** (salvage #19450) ([#21296](https://github.com/NousResearch/hermes-agent/pull/21296))
|
| 435 |
+
- **Launch dashboard as side-process via `HERMES_DASHBOARD=1` in Docker** (@benbarclay) ([#19540](https://github.com/NousResearch/hermes-agent/pull/19540))
|
| 436 |
+
- Fix: dashboard theme layout shift (@AllardQuek) ([#17232](https://github.com/NousResearch/hermes-agent/pull/17232))
|
| 437 |
+
- Fix: gateway model picker current context (@helix4u) ([#20513](https://github.com/NousResearch/hermes-agent/pull/20513))
|
| 438 |
+
|
| 439 |
+
### Update + setup
|
| 440 |
+
- **`hermes update --yes/-y` to skip interactive prompts** ([#18261](https://github.com/NousResearch/hermes-agent/pull/18261))
|
| 441 |
+
- **Restart manual profile gateways after update** ([#18178](https://github.com/NousResearch/hermes-agent/pull/18178))
|
| 442 |
+
|
| 443 |
+
### Profiles
|
| 444 |
+
- **`--no-skills` flag for empty profile creation** ([#20986](https://github.com/NousResearch/hermes-agent/pull/20986))
|
| 445 |
+
|
| 446 |
+
---
|
| 447 |
+
|
| 448 |
+
## 🎵 Voice, Image & Media
|
| 449 |
+
|
| 450 |
+
- **xAI Custom Voices — voice cloning** (@alt-glitch) ([#18776](https://github.com/NousResearch/hermes-agent/pull/18776))
|
| 451 |
+
- **Achievements — share card render on unlocked badges** ([#19657](https://github.com/NousResearch/hermes-agent/pull/19657))
|
| 452 |
+
- **Refresh systemd unit on gateway boot (not just start/restart)** (@alt-glitch) ([#19684](https://github.com/NousResearch/hermes-agent/pull/19684))
|
| 453 |
+
|
| 454 |
+
---
|
| 455 |
+
|
| 456 |
+
## 🔗 API Server & Remote Access
|
| 457 |
+
|
| 458 |
+
- **`X-Hermes-Session-Key` header for long-term memory scoping** (closes #20060) ([#20199](https://github.com/NousResearch/hermes-agent/pull/20199))
|
| 459 |
+
|
| 460 |
+
---
|
| 461 |
+
|
| 462 |
+
## 🧰 ACP Adapter (VS Code / Zed / JetBrains)
|
| 463 |
+
|
| 464 |
+
- **`/steer` and `/queue` slash commands** (@HenkDz) ([#18114](https://github.com/NousResearch/hermes-agent/pull/18114))
|
| 465 |
+
- Fix: translate Windows cwd for WSL sessions (salvage #18128) ([#18233](https://github.com/NousResearch/hermes-agent/pull/18233))
|
| 466 |
+
- Fix: run `/steer` as a regular prompt on idle sessions ([#18258](https://github.com/NousResearch/hermes-agent/pull/18258))
|
| 467 |
+
- Fix: route Zed thoughts to reasoning + polish tool/context rendering ([#19139](https://github.com/NousResearch/hermes-agent/pull/19139))
|
| 468 |
+
- Fix: atomic session persistence via `replace_messages` (salvage #13675) ([#20279](https://github.com/NousResearch/hermes-agent/pull/20279))
|
| 469 |
+
- Fix: preserve assistant reasoning metadata in session persistence (salvage #13575) ([#20296](https://github.com/NousResearch/hermes-agent/pull/20296))
|
| 470 |
+
- Docs: update VS Code setup for ACP Client extension (salvage #12495) ([#20433](https://github.com/NousResearch/hermes-agent/pull/20433))
|
| 471 |
+
|
| 472 |
+
---
|
| 473 |
+
|
| 474 |
+
## 🐳 Docker
|
| 475 |
+
|
| 476 |
+
- **Launch dashboard as side-process via `HERMES_DASHBOARD=1`** (@benbarclay) ([#19540](https://github.com/NousResearch/hermes-agent/pull/19540))
|
| 477 |
+
- **Refuse root gateway runs in official image** (salvage #19215) ([#21250](https://github.com/NousResearch/hermes-agent/pull/21250))
|
| 478 |
+
- **Chown runtime `node_modules` trees to hermes user** (salvage #19303) ([#21267](https://github.com/NousResearch/hermes-agent/pull/21267))
|
| 479 |
+
- Fix: exclude compose/profile runtime state from build context ([#19626](https://github.com/NousResearch/hermes-agent/pull/19626))
|
| 480 |
+
- CI: don't cancel overlapping builds, guard `:latest` (@ethernet8023) ([#20890](https://github.com/NousResearch/hermes-agent/pull/20890))
|
| 481 |
+
- Test: align Dockerfile contract tests with simplified TUI flow (salvage #19024) ([#21174](https://github.com/NousResearch/hermes-agent/pull/21174))
|
| 482 |
+
- Docs: connect to local inference servers (vLLM, Ollama) (salvage #12335) ([#20407](https://github.com/NousResearch/hermes-agent/pull/20407))
|
| 483 |
+
- Docs: document `API_SERVER_*` env vars (salvage #11758) ([#20409](https://github.com/NousResearch/hermes-agent/pull/20409))
|
| 484 |
+
- Docs: clarify Docker terminal backend is a single persistent container ([#20003](https://github.com/NousResearch/hermes-agent/pull/20003))
|
| 485 |
+
|
| 486 |
+
---
|
| 487 |
+
|
| 488 |
+
## 🐛 Notable Bug Fixes
|
| 489 |
+
|
| 490 |
+
### Agent
|
| 491 |
+
- Fix: recover lazy session creation regressions (#18370 fallout) (@alt-glitch) ([#20363](https://github.com/NousResearch/hermes-agent/pull/20363))
|
| 492 |
+
- Fix: propagate ContextVars to concurrent tool worker threads (salvage #16660) ([#18123](https://github.com/NousResearch/hermes-agent/pull/18123))
|
| 493 |
+
- Fix: warning-first tool-call loop guardrails ([#18227](https://github.com/NousResearch/hermes-agent/pull/18227))
|
| 494 |
+
- Fix: surface self-improvement review summaries across CLI, TUI, and gateway ([#18073](https://github.com/NousResearch/hermes-agent/pull/18073))
|
| 495 |
+
|
| 496 |
+
### Gateway streaming
|
| 497 |
+
- Fix: harden StreamingConfig bool and numeric coercion (@simbam99) ([#16463](https://github.com/NousResearch/hermes-agent/pull/16463))
|
| 498 |
+
|
| 499 |
+
### Model
|
| 500 |
+
- Fix: avoid Bedrock credential probe in provider picker (@helix4u) ([#18998](https://github.com/NousResearch/hermes-agent/pull/18998))
|
| 501 |
+
|
| 502 |
+
### Doctor
|
| 503 |
+
- Fix: check global agent-browser when local install not found ([#19671](https://github.com/NousResearch/hermes-agent/pull/19671))
|
| 504 |
+
- Test: kimi-coding-cn provider validation regression ([#19734](https://github.com/NousResearch/hermes-agent/pull/19734))
|
| 505 |
+
|
| 506 |
+
### Update
|
| 507 |
+
- Fix: patch `isatty` on real streams to fix xdist-flaky `--yes` tests (salvage #19026) ([#21175](https://github.com/NousResearch/hermes-agent/pull/21175))
|
| 508 |
+
- Fix: teach restart-mocks about the post-update survivor sweep (salvage #19031) ([#21177](https://github.com/NousResearch/hermes-agent/pull/21177))
|
| 509 |
+
|
| 510 |
+
### Auth
|
| 511 |
+
- Fix: acp preserve assistant reasoning metadata ([#20296](https://github.com/NousResearch/hermes-agent/pull/20296))
|
| 512 |
+
|
| 513 |
+
### Redact
|
| 514 |
+
- Fix: add `code_file` param to skip false-positive ENV/JSON patterns ([#19715](https://github.com/NousResearch/hermes-agent/pull/19715))
|
| 515 |
+
|
| 516 |
+
### Email
|
| 517 |
+
- Fix: quoted-relative file-drop paths + Date header on tool email path ([#19646](https://github.com/NousResearch/hermes-agent/pull/19646))
|
| 518 |
+
|
| 519 |
+
---
|
| 520 |
+
|
| 521 |
+
## 🧪 Testing
|
| 522 |
+
|
| 523 |
+
- **ACP — accept prompt persistence kwargs in MCP E2E mocks** (@stephenschoettler) ([#18047](https://github.com/NousResearch/hermes-agent/pull/18047))
|
| 524 |
+
- **Toolsets — include kanban in expected post-#17805 toolset assertions** (@briandevans) ([#18122](https://github.com/NousResearch/hermes-agent/pull/18122))
|
| 525 |
+
- **Agent — cover max-iterations summary message sanitization** ([#19580](https://github.com/NousResearch/hermes-agent/pull/19580))
|
| 526 |
+
- **run_agent — `-inf` and `nan` regression coverage for `_coerce_number`** ([#19703](https://github.com/NousResearch/hermes-agent/pull/19703))
|
| 527 |
+
|
| 528 |
+
---
|
| 529 |
+
|
| 530 |
+
## 📚 Documentation
|
| 531 |
+
|
| 532 |
+
### Major docs additions
|
| 533 |
+
- **`llms.txt` + `llms-full.txt` — agent-friendly ingestion** ([#18276](https://github.com/NousResearch/hermes-agent/pull/18276))
|
| 534 |
+
- **User Stories and Use Cases collage page** ([#18282](https://github.com/NousResearch/hermes-agent/pull/18282))
|
| 535 |
+
- **Persistent Goals (/goal) feature page** ([#18275](https://github.com/NousResearch/hermes-agent/pull/18275))
|
| 536 |
+
- **Windows (WSL2) guide expansion** — filesystem, networking, services, pitfalls ([#20748](https://github.com/NousResearch/hermes-agent/pull/20748))
|
| 537 |
+
- **Chinese (zh-CN) README translation** (salvage #13508) ([#20431](https://github.com/NousResearch/hermes-agent/pull/20431))
|
| 538 |
+
- **zh-Hans Docusaurus locale** + Tool Gateway / image-gen / WSL quickstart translations (salvage #11728) ([#20430](https://github.com/NousResearch/hermes-agent/pull/20430))
|
| 539 |
+
- **Tool Gateway docs restructure** — lead with what it does, config moved to bottom ([#20827](https://github.com/NousResearch/hermes-agent/pull/20827))
|
| 540 |
+
- **Quickstart — Onchain AI Garage Hermes tutorials playlist** ([#20192](https://github.com/NousResearch/hermes-agent/pull/20192))
|
| 541 |
+
- **Open WebUI bootstrap script** (salvage #9566) ([#20427](https://github.com/NousResearch/hermes-agent/pull/20427))
|
| 542 |
+
- **Local Ollama setup guide** (salvage #5842) ([#20426](https://github.com/NousResearch/hermes-agent/pull/20426))
|
| 543 |
+
- **Google Gemini guide** (salvage #17450) ([#20401](https://github.com/NousResearch/hermes-agent/pull/20401))
|
| 544 |
+
- **Custom model aliases for /model command** ([#20475](https://github.com/NousResearch/hermes-agent/pull/20475))
|
| 545 |
+
- **Together/Groq/Perplexity cookbook via `custom_providers`** (salvage #15214) ([#20400](https://github.com/NousResearch/hermes-agent/pull/20400))
|
| 546 |
+
- **Doubao speech integration examples** (TTS + STT) (salvage #18065) ([#20418](https://github.com/NousResearch/hermes-agent/pull/20418))
|
| 547 |
+
- **WSL-to-Windows Chrome MCP bridge** (salvage #8313) ([#20428](https://github.com/NousResearch/hermes-agent/pull/20428))
|
| 548 |
+
- **Hermes skills docs sync** — slash commands + durable-systems section ([#20390](https://github.com/NousResearch/hermes-agent/pull/20390))
|
| 549 |
+
- **AGENTS.md — curator/cron/delegation/toolsets + fix plugin tree** ([#20226](https://github.com/NousResearch/hermes-agent/pull/20226))
|
| 550 |
+
- **Bedrock quickstart entry + fallback comment + deployment link** (salvage #11093) ([#20397](https://github.com/NousResearch/hermes-agent/pull/20397))
|
| 551 |
+
|
| 552 |
+
### Docs polish
|
| 553 |
+
- Collapse exploding skills tree to a single Skills node ([#18259](https://github.com/NousResearch/hermes-agent/pull/18259))
|
| 554 |
+
- Clarify `session_search` auxiliary model docs ([#19593](https://github.com/NousResearch/hermes-agent/pull/19593))
|
| 555 |
+
- Open WebUI Quick Setup gap fill ([#19654](https://github.com/NousResearch/hermes-agent/pull/19654))
|
| 556 |
+
- Default custom tool creation to plugins (@kshitijk4poor) ([#19755](https://github.com/NousResearch/hermes-agent/pull/19755))
|
| 557 |
+
- Clarify Telegram group chat troubleshooting (salvage #18672) ([#20416](https://github.com/NousResearch/hermes-agent/pull/20416))
|
| 558 |
+
- Codex OAuth auth prerequisite clarification (salvage #18688) ([#20417](https://github.com/NousResearch/hermes-agent/pull/20417))
|
| 559 |
+
- Discord Server Members Intent + SSRC-mapping drift + /voice join slash Choice (salvage #11350) ([#20411](https://github.com/NousResearch/hermes-agent/pull/20411))
|
| 560 |
+
- Document `ctx.dispatch_tool()` (salvage #10955) ([#20391](https://github.com/NousResearch/hermes-agent/pull/20391))
|
| 561 |
+
- Document `hermes webhook subscribe --deliver-only` (salvage #12612) ([#20392](https://github.com/NousResearch/hermes-agent/pull/20392))
|
| 562 |
+
- Document `hermes import` reference (salvage #14711) ([#20396](https://github.com/NousResearch/hermes-agent/pull/20396))
|
| 563 |
+
- Document per-provider TTS `max_text_length` caps (salvage #13825) ([#20389](https://github.com/NousResearch/hermes-agent/pull/20389))
|
| 564 |
+
- Clarify supported prompt customization surfaces (salvage #19987) ([#20383](https://github.com/NousResearch/hermes-agent/pull/20383))
|
| 565 |
+
- Correct `web_extract` summarizer timeout comment (salvage #20051) ([#20381](https://github.com/NousResearch/hermes-agent/pull/20381))
|
| 566 |
+
- Fix fallback provider config paths (salvage #20033) ([#20382](https://github.com/NousResearch/hermes-agent/pull/20382))
|
| 567 |
+
- Fix misleading RL install-extras claim (salvage #19080) ([#21213](https://github.com/NousResearch/hermes-agent/pull/21213))
|
| 568 |
+
- Clarify API server tool execution locality (salvage #19117) ([#21223](https://github.com/NousResearch/hermes-agent/pull/21223))
|
| 569 |
+
- Prefer `.venv` to match AGENTS.md and scripts/run_tests.sh (@xxxigm) ([#21334](https://github.com/NousResearch/hermes-agent/pull/21334))
|
| 570 |
+
- Align tool discovery + test runner with AGENTS.md (@xxxigm) ([#20791](https://github.com/NousResearch/hermes-agent/pull/20791))
|
| 571 |
+
- Align terminal-backend count and naming across docs and code (salvage #19044) ([#20402](https://github.com/NousResearch/hermes-agent/pull/20402))
|
| 572 |
+
- Refresh stale platform counts (salvage #19053) ([#20403](https://github.com/NousResearch/hermes-agent/pull/20403))
|
| 573 |
+
|
| 574 |
+
---
|
| 575 |
+
|
| 576 |
+
## 👥 Contributors
|
| 577 |
+
|
| 578 |
+
### Core
|
| 579 |
+
- **@teknium1** — salvage, triage, review, feature work, and release management
|
| 580 |
+
|
| 581 |
+
### Top Community Contributors
|
| 582 |
+
|
| 583 |
+
- **@kshitijk4poor** (21 PRs) — SearXNG native search backend, per-capability backend selection, collapsible TUI startup banner, Slack ephemeral ack + format fixes, Lightpanda fallback hardening, searxng-search optional skill + Web Search + Extract docs, default custom tool creation to plugins, kanban failure-column fix
|
| 584 |
+
- **@alt-glitch** (13 PRs) — video_analyze tool, xAI Custom Voices (voice cloning), local-backend CLI launch-directory fix, lazy-session creation regression recovery, systemd unit refresh on gateway boot
|
| 585 |
+
- **@OutThisLife** (9 PRs) — TUI perf — overlay render churn reduction, voice push-to-talk parity restoration (salvaging @Montbra)
|
| 586 |
+
- **@helix4u** (6 PRs) — Classic CLI output recovery after resize, absolute-path TUI completion, gateway model picker current-context fix, Bedrock credential probe avoidance, kanban docs fixes
|
| 587 |
+
- **@ethernet8023** (3 PRs) — Docker CI — don't cancel overlapping builds, :latest guard
|
| 588 |
+
- **@benbarclay** (3 PRs) — Docker — launch dashboard as side-process via HERMES_DASHBOARD=1
|
| 589 |
+
- **@austinpickett** (3 PRs) — Dashboard Plugins page, TUI /model picker overhaul with inline auth, kanban button fix
|
| 590 |
+
- **@sprmn24** (2 PRs) — Contributor (2 PRs)
|
| 591 |
+
- **@asheriif** (2 PRs) — Contributor (2 PRs)
|
| 592 |
+
- **@xxxigm** (2 PRs) — Contributing docs — .venv preference and test runner alignment with AGENTS.md
|
| 593 |
+
- **@stephenschoettler** (1 PR) — ACP — MCP E2E mock kwargs
|
| 594 |
+
- **@vincez-hms-coder** (1 PR) — Dashboard — Profiles management page
|
| 595 |
+
- **@cdanis** (1 PR) — Contributor
|
| 596 |
+
- **@briandevans** (1 PR) — Toolsets test — kanban assertions post-#17805
|
| 597 |
+
- **@heyitsaamir** (1 PR) — Contributor
|
| 598 |
+
|
| 599 |
+
### All Contributors
|
| 600 |
+
|
| 601 |
+
Thanks to everyone who contributed to v0.13.0 — commits, co-authored work, and salvaged PRs. 295 contributors in one week.
|
| 602 |
+
|
| 603 |
+
@0oAstro, @0xDevNinja, @0xharryriddle, @0xKingBack, @0xsir0000, @0xyg3n, @0z1-ghb, @abhinav11082001-stack,
|
| 604 |
+
@acc001k, @acesjohnny, @adamludwin, @adybag14-cyber, @agentlinker, @agilejava, @ai-ag2026, @AJV20,
|
| 605 |
+
@alanxchen85, @albert748, @AllardQuek, @alt-glitch, @altmazza0-star, @ambition0802, @amitgaur, @amroessam,
|
| 606 |
+
@andrewhosf, @Asce66, @asheriif, @ashermorse, @asimons81, @Aslaaen, @Asunfly, @atongrun, @austinpickett,
|
| 607 |
+
@banditburai, @barteqpl, @Bartok9, @Beandon13, @beardthelion, @beibi9966, @benbarclay, @binhnt92, @bjianhang,
|
| 608 |
+
@BlackJulySnow, @bobashopcashier, @bogerman1, @Bongulielmi, @Brecht-H, @briandevans, @brooklynnicholson,
|
| 609 |
+
@c3115644151, @camaragon, @CashWilliams, @CCClelo, @cdanis, @CES4751, @cg2aigc, @changchun989, @ChanlerDev,
|
| 610 |
+
@CharlieKerfoot, @chengoak, @chenyunbo411, @chinadbo, @CIRWEL, @cixuuz, @cmcgrabby-hue, @colorcross,
|
| 611 |
+
@Contentment003111, @CoreyNoDream, @counterposition, @curiouscleo, @DaniuXie, @deep-name, @dengtaoyuan450-a11y,
|
| 612 |
+
@discodirector, @donramon77, @dpaluy, @ee-blog, @ehz0ah, @el-analista, @elmatadorgh, @EmelyanenkoK,
|
| 613 |
+
@Emidomenge, @emozilla, @Es1la, @EthanGuo-coder, @etherman-os, @ethernet8023, @EvilDrag0n, @exxmen, @Fearvox,
|
| 614 |
+
@Feranmi10, @firefly, @flobo3, @fmercurio, @Foolafroos, @formulahendry, @franksong2702, @ggnnggez, @GinWU05,
|
| 615 |
+
@giwaov, @glesperance, @gnanirahulnutakki, @GodsBoy, @Gosuj, @Grey0202, @guillaumemeyer, @Gutslabs, @h0tp-ftw,
|
| 616 |
+
@haidao1919, @halmisen, @happy5318, @hedirman, @helix4u, @hendrixfreire, @HenkDz, @hex-clawd, @heyitsaamir,
|
| 617 |
+
@hharry11, @Hinotoi-agent, @holynn-q, @hrkzogw, @Hypn0sis, @Hypnus-Yuan, @ideathinklab01-source, @IMHaoyan,
|
| 618 |
+
@Interstellar-code, @ishardo, @jacdevos, @jackey8616, @JanCong, @jasonoutland, @jatingodnani, @JayGwod,
|
| 619 |
+
@jethac, @JezzaHehn, @JiaDe-Wu, @jjjojoj, @jkausel-ai, @John-tip, @johnncenae, @jrusso1020, @jslizar,
|
| 620 |
+
@JTroyerOvermatch, @julysir, @Junass1, @JustinUssuri, @Kailigithub, @keepcalmqqf, @kiala9, @konsisumer,
|
| 621 |
+
@kowenhaoai, @Krionex, @kshitijk4poor, @kyan12, @leavrcn, @leon7609, @LeonSGP43, @leprincep35700, @lhysdl,
|
| 622 |
+
@likejudy, @lisanhu, @liu-collab, @liuguangyong93, @liuhao1024, @LucianoSP, @luoyuctl, @luyao618, @M3RCUR2Y,
|
| 623 |
+
@maciekczech, @Magicray1217, @magicray1217, @MaHaoHao-ch, @malaiwah, @manateelazycat, @masonjames, @megastary,
|
| 624 |
+
@memosr, @MichaelWDanko, @mikeyobrien, @millerc79, @Mind-Dragon, @mioimotoai-lgtm, @misery-hl, @molvikar,
|
| 625 |
+
@momowind, @Montbra, @MottledShadow, @mrbob-git, @mrcharlesiv, @mrcoferland, @ms-alan, @mwnickerson,
|
| 626 |
+
@nazirulhafiy, @nftpoetrist, @nicoloboschi, @nightq, @nikolay-bratanov, @NikolayGusev-astra, @nocturnum91,
|
| 627 |
+
@noOne-list, @nouseman666, @novax635, @npmisantosh, @nudiltoys-cmyk, @olisikh, @oluwadareab12, @Oxidane-bot,
|
| 628 |
+
@pama0227, @pander, @pasevin, @paul-tian, @pdonizete, @perlowja, @pingchesu, @PratikRai0101, @priveperfumes,
|
| 629 |
+
@probepark, @QifengKuang, @quocanh261997, @qWaitCrypto, @qxxaa, @r266-tech, @rames-jusso, @revaraver,
|
| 630 |
+
@Ricardo-M-L, @rob-maron, @Roy-oss1, @rxdxxxx, @SandroHub013, @Sanjays2402, @Sertug17, @shashwatgokhe,
|
| 631 |
+
@shellybotmoyer, @SHL0MS, @SimbaKingjoe, @simbam99, @simplenamebox-ops, @socrates1024, @sonic-netizen,
|
| 632 |
+
@sprmn24, @steezkelly, @stephen0110, @stephenschoettler, @stevenchanin, @stevenchouai, @stormhierta,
|
| 633 |
+
@subtract0, @suncokret12, @swithek, @taeng0204, @TakeshiSawaguchi, @tangyuanjc, @TheEpTic, @thelumiereguy,
|
| 634 |
+
@Tkander1715, @tmdgusya, @Tranquil-Flow, @TruaShamu, @UgwujaGeorge, @valda, @vincez-hms-coder, @VinVC,
|
| 635 |
+
@vominh1919, @wabrent, @WadydX, @wanazhar, @WanderWang, @warabe1122, @web-dev0521, @WideLee, @willy-scr,
|
| 636 |
+
@wmagev, @WuTianyi123, @wxst, @wysie, @Wysie, @xsfX20, @xxxigm, @xyiy001, @YanzhongSu, @ygd58, @Yoimex,
|
| 637 |
+
@yuehei, @Yukipukii1, @yuqianma, @YX234, @zeejaytan, @zhanggttry, @zhao0112, @zng8418, @zons-zhaozhy, @Zyproth
|
| 638 |
+
|
| 639 |
+
---
|
| 640 |
+
|
| 641 |
+
**Full Changelog**: [v2026.4.30...v2026.5.7](https://github.com/NousResearch/hermes-agent/compare/v2026.4.30...v2026.5.7)
|
RELEASE_v0.2.0.md
ADDED
|
@@ -0,0 +1,383 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Hermes Agent v0.2.0 (v2026.3.12)
|
| 2 |
+
|
| 3 |
+
**Release Date:** March 12, 2026
|
| 4 |
+
|
| 5 |
+
> First tagged release since v0.1.0 (the initial pre-public foundation). In just over two weeks, Hermes Agent went from a small internal project to a full-featured AI agent platform — thanks to an explosion of community contributions. This release covers **216 merged pull requests** from **63 contributors**, resolving **119 issues**.
|
| 6 |
+
|
| 7 |
+
---
|
| 8 |
+
|
| 9 |
+
## ✨ Highlights
|
| 10 |
+
|
| 11 |
+
- **Multi-Platform Messaging Gateway** — Telegram, Discord, Slack, WhatsApp, Signal, Email (IMAP/SMTP), and Home Assistant platforms with unified session management, media attachments, and per-platform tool configuration.
|
| 12 |
+
|
| 13 |
+
- **MCP (Model Context Protocol) Client** — Native MCP support with stdio and HTTP transports, reconnection, resource/prompt discovery, and sampling (server-initiated LLM requests). ([#291](https://github.com/NousResearch/hermes-agent/pull/291) — @0xbyt4, [#301](https://github.com/NousResearch/hermes-agent/pull/301), [#753](https://github.com/NousResearch/hermes-agent/pull/753))
|
| 14 |
+
|
| 15 |
+
- **Skills Ecosystem** — 70+ bundled and optional skills across 15+ categories with a Skills Hub for community discovery, per-platform enable/disable, conditional activation based on tool availability, and prerequisite validation. ([#743](https://github.com/NousResearch/hermes-agent/pull/743) — @teyrebaz33, [#785](https://github.com/NousResearch/hermes-agent/pull/785) — @teyrebaz33)
|
| 16 |
+
|
| 17 |
+
- **Centralized Provider Router** — Unified `call_llm()`/`async_call_llm()` API replaces scattered provider logic across vision, summarization, compression, and trajectory saving. All auxiliary consumers route through a single code path with automatic credential resolution. ([#1003](https://github.com/NousResearch/hermes-agent/pull/1003))
|
| 18 |
+
|
| 19 |
+
- **ACP Server** — VS Code, Zed, and JetBrains editor integration via the Agent Communication Protocol standard. ([#949](https://github.com/NousResearch/hermes-agent/pull/949))
|
| 20 |
+
|
| 21 |
+
- **CLI Skin/Theme Engine** — Data-driven visual customization: banners, spinners, colors, branding. 7 built-in skins + custom YAML skins.
|
| 22 |
+
|
| 23 |
+
- **Git Worktree Isolation** — `hermes -w` launches isolated agent sessions in git worktrees for safe parallel work on the same repo. ([#654](https://github.com/NousResearch/hermes-agent/pull/654))
|
| 24 |
+
|
| 25 |
+
- **Filesystem Checkpoints & Rollback** — Automatic snapshots before destructive operations with `/rollback` to restore. ([#824](https://github.com/NousResearch/hermes-agent/pull/824))
|
| 26 |
+
|
| 27 |
+
- **3,289 Tests** — From near-zero test coverage to a comprehensive test suite covering agent, gateway, tools, cron, and CLI.
|
| 28 |
+
|
| 29 |
+
---
|
| 30 |
+
|
| 31 |
+
## 🏗️ Core Agent & Architecture
|
| 32 |
+
|
| 33 |
+
### Provider & Model Support
|
| 34 |
+
- Centralized provider router with `resolve_provider_client()` + `call_llm()` API ([#1003](https://github.com/NousResearch/hermes-agent/pull/1003))
|
| 35 |
+
- Nous Portal as first-class provider in setup ([#644](https://github.com/NousResearch/hermes-agent/issues/644))
|
| 36 |
+
- OpenAI Codex (Responses API) with ChatGPT subscription support ([#43](https://github.com/NousResearch/hermes-agent/pull/43)) — @grp06
|
| 37 |
+
- Codex OAuth vision support + multimodal content adapter
|
| 38 |
+
- Validate `/model` against live API instead of hardcoded lists
|
| 39 |
+
- Self-hosted Firecrawl support ([#460](https://github.com/NousResearch/hermes-agent/pull/460)) — @caentzminger
|
| 40 |
+
- Kimi Code API support ([#635](https://github.com/NousResearch/hermes-agent/pull/635)) — @christomitov
|
| 41 |
+
- MiniMax model ID update ([#473](https://github.com/NousResearch/hermes-agent/pull/473)) — @tars90percent
|
| 42 |
+
- OpenRouter provider routing configuration (provider_preferences)
|
| 43 |
+
- Nous credential refresh on 401 errors ([#571](https://github.com/NousResearch/hermes-agent/pull/571), [#269](https://github.com/NousResearch/hermes-agent/pull/269)) — @rewbs
|
| 44 |
+
- z.ai/GLM, Kimi/Moonshot, MiniMax, Azure OpenAI as first-class providers
|
| 45 |
+
- Unified `/model` and `/provider` into single view
|
| 46 |
+
|
| 47 |
+
### Agent Loop & Conversation
|
| 48 |
+
- Simple fallback model for provider resilience ([#740](https://github.com/NousResearch/hermes-agent/pull/740))
|
| 49 |
+
- Shared iteration budget across parent + subagent delegation
|
| 50 |
+
- Iteration budget pressure via tool result injection
|
| 51 |
+
- Configurable subagent provider/model with full credential resolution
|
| 52 |
+
- Handle 413 payload-too-large via compression instead of aborting ([#153](https://github.com/NousResearch/hermes-agent/pull/153)) — @tekelala
|
| 53 |
+
- Retry with rebuilt payload after compression ([#616](https://github.com/NousResearch/hermes-agent/pull/616)) — @tripledoublev
|
| 54 |
+
- Auto-compress pathologically large gateway sessions ([#628](https://github.com/NousResearch/hermes-agent/issues/628))
|
| 55 |
+
- Tool call repair middleware — auto-lowercase and invalid tool handler
|
| 56 |
+
- Reasoning effort configuration and `/reasoning` command ([#921](https://github.com/NousResearch/hermes-agent/pull/921))
|
| 57 |
+
- Detect and block file re-read/search loops after context compression ([#705](https://github.com/NousResearch/hermes-agent/pull/705)) — @0xbyt4
|
| 58 |
+
|
| 59 |
+
### Session & Memory
|
| 60 |
+
- Session naming with unique titles, auto-lineage, rich listing, and resume by name ([#720](https://github.com/NousResearch/hermes-agent/pull/720))
|
| 61 |
+
- Interactive session browser with search filtering ([#733](https://github.com/NousResearch/hermes-agent/pull/733))
|
| 62 |
+
- Display previous messages when resuming a session ([#734](https://github.com/NousResearch/hermes-agent/pull/734))
|
| 63 |
+
- Honcho AI-native cross-session user modeling ([#38](https://github.com/NousResearch/hermes-agent/pull/38)) — @erosika
|
| 64 |
+
- Proactive async memory flush on session expiry
|
| 65 |
+
- Smart context length probing with persistent caching + banner display
|
| 66 |
+
- `/resume` command for switching to named sessions in gateway
|
| 67 |
+
- Session reset policy for messaging platforms
|
| 68 |
+
|
| 69 |
+
---
|
| 70 |
+
|
| 71 |
+
## 📱 Messaging Platforms (Gateway)
|
| 72 |
+
|
| 73 |
+
### Telegram
|
| 74 |
+
- Native file attachments: send_document + send_video
|
| 75 |
+
- Document file processing for PDF, text, and Office files — @tekelala
|
| 76 |
+
- Forum topic session isolation ([#766](https://github.com/NousResearch/hermes-agent/pull/766)) — @spanishflu-est1918
|
| 77 |
+
- Browser screenshot sharing via MEDIA: protocol ([#657](https://github.com/NousResearch/hermes-agent/pull/657))
|
| 78 |
+
- Location support for find-nearby skill
|
| 79 |
+
- TTS voice message accumulation fix ([#176](https://github.com/NousResearch/hermes-agent/pull/176)) — @Bartok9
|
| 80 |
+
- Improved error handling and logging ([#763](https://github.com/NousResearch/hermes-agent/pull/763)) — @aydnOktay
|
| 81 |
+
- Italic regex newline fix + 43 format tests ([#204](https://github.com/NousResearch/hermes-agent/pull/204)) — @0xbyt4
|
| 82 |
+
|
| 83 |
+
### Discord
|
| 84 |
+
- Channel topic included in session context ([#248](https://github.com/NousResearch/hermes-agent/pull/248)) — @Bartok9
|
| 85 |
+
- DISCORD_ALLOW_BOTS config for bot message filtering ([#758](https://github.com/NousResearch/hermes-agent/pull/758))
|
| 86 |
+
- Document and video support ([#784](https://github.com/NousResearch/hermes-agent/pull/784))
|
| 87 |
+
- Improved error handling and logging ([#761](https://github.com/NousResearch/hermes-agent/pull/761)) — @aydnOktay
|
| 88 |
+
|
| 89 |
+
### Slack
|
| 90 |
+
- App_mention 404 fix + document/video support ([#784](https://github.com/NousResearch/hermes-agent/pull/784))
|
| 91 |
+
- Structured logging replacing print statements — @aydnOktay
|
| 92 |
+
|
| 93 |
+
### WhatsApp
|
| 94 |
+
- Native media sending — images, videos, documents ([#292](https://github.com/NousResearch/hermes-agent/pull/292)) — @satelerd
|
| 95 |
+
- Multi-user session isolation ([#75](https://github.com/NousResearch/hermes-agent/pull/75)) — @satelerd
|
| 96 |
+
- Cross-platform port cleanup replacing Linux-only fuser ([#433](https://github.com/NousResearch/hermes-agent/pull/433)) — @Farukest
|
| 97 |
+
- DM interrupt key mismatch fix ([#350](https://github.com/NousResearch/hermes-agent/pull/350)) — @Farukest
|
| 98 |
+
|
| 99 |
+
### Signal
|
| 100 |
+
- Full Signal messenger gateway via signal-cli-rest-api ([#405](https://github.com/NousResearch/hermes-agent/issues/405))
|
| 101 |
+
- Media URL support in message events ([#871](https://github.com/NousResearch/hermes-agent/pull/871))
|
| 102 |
+
|
| 103 |
+
### Email (IMAP/SMTP)
|
| 104 |
+
- New email gateway platform — @0xbyt4
|
| 105 |
+
|
| 106 |
+
### Home Assistant
|
| 107 |
+
- REST tools + WebSocket gateway integration ([#184](https://github.com/NousResearch/hermes-agent/pull/184)) — @0xbyt4
|
| 108 |
+
- Service discovery and enhanced setup
|
| 109 |
+
- Toolset mapping fix ([#538](https://github.com/NousResearch/hermes-agent/pull/538)) — @Himess
|
| 110 |
+
|
| 111 |
+
### Gateway Core
|
| 112 |
+
- Expose subagent tool calls and thinking to users ([#186](https://github.com/NousResearch/hermes-agent/pull/186)) — @cutepawss
|
| 113 |
+
- Configurable background process watcher notifications ([#840](https://github.com/NousResearch/hermes-agent/pull/840))
|
| 114 |
+
- `edit_message()` for Telegram/Discord/Slack with fallback
|
| 115 |
+
- `/compress`, `/usage`, `/update` slash commands
|
| 116 |
+
- Eliminated 3x SQLite message duplication in gateway sessions ([#873](https://github.com/NousResearch/hermes-agent/pull/873))
|
| 117 |
+
- Stabilize system prompt across gateway turns for cache hits ([#754](https://github.com/NousResearch/hermes-agent/pull/754))
|
| 118 |
+
- MCP server shutdown on gateway exit ([#796](https://github.com/NousResearch/hermes-agent/pull/796)) — @0xbyt4
|
| 119 |
+
- Pass session_db to AIAgent, fixing session_search error ([#108](https://github.com/NousResearch/hermes-agent/pull/108)) — @Bartok9
|
| 120 |
+
- Persist transcript changes in /retry, /undo; fix /reset attribute ([#217](https://github.com/NousResearch/hermes-agent/pull/217)) — @Farukest
|
| 121 |
+
- UTF-8 encoding fix preventing Windows crashes ([#369](https://github.com/NousResearch/hermes-agent/pull/369)) — @ch3ronsa
|
| 122 |
+
|
| 123 |
+
---
|
| 124 |
+
|
| 125 |
+
## 🖥️ CLI & User Experience
|
| 126 |
+
|
| 127 |
+
### Interactive CLI
|
| 128 |
+
- Data-driven skin/theme engine — 7 built-in skins (default, ares, mono, slate, poseidon, sisyphus, charizard) + custom YAML skins
|
| 129 |
+
- `/personality` command with custom personality + disable support ([#773](https://github.com/NousResearch/hermes-agent/pull/773)) — @teyrebaz33
|
| 130 |
+
- User-defined quick commands that bypass the agent loop ([#746](https://github.com/NousResearch/hermes-agent/pull/746)) — @teyrebaz33
|
| 131 |
+
- `/reasoning` command for effort level and display toggle ([#921](https://github.com/NousResearch/hermes-agent/pull/921))
|
| 132 |
+
- `/verbose` slash command to toggle debug at runtime ([#94](https://github.com/NousResearch/hermes-agent/pull/94)) — @cesareth
|
| 133 |
+
- `/insights` command — usage analytics, cost estimation & activity patterns ([#552](https://github.com/NousResearch/hermes-agent/pull/552))
|
| 134 |
+
- `/background` command for managing background processes
|
| 135 |
+
- `/help` formatting with command categories
|
| 136 |
+
- Bell-on-complete — terminal bell when agent finishes ([#738](https://github.com/NousResearch/hermes-agent/pull/738))
|
| 137 |
+
- Up/down arrow history navigation
|
| 138 |
+
- Clipboard image paste (Alt+V / Ctrl+V)
|
| 139 |
+
- Loading indicators for slow slash commands ([#882](https://github.com/NousResearch/hermes-agent/pull/882))
|
| 140 |
+
- Spinner flickering fix under patch_stdout ([#91](https://github.com/NousResearch/hermes-agent/pull/91)) — @0xbyt4
|
| 141 |
+
- `--quiet/-Q` flag for programmatic single-query mode
|
| 142 |
+
- `--fuck-it-ship-it` flag to bypass all approval prompts ([#724](https://github.com/NousResearch/hermes-agent/pull/724)) — @dmahan93
|
| 143 |
+
- Tools summary flag ([#767](https://github.com/NousResearch/hermes-agent/pull/767)) — @luisv-1
|
| 144 |
+
- Terminal blinking fix on SSH ([#284](https://github.com/NousResearch/hermes-agent/pull/284)) — @ygd58
|
| 145 |
+
- Multi-line paste detection fix ([#84](https://github.com/NousResearch/hermes-agent/pull/84)) — @0xbyt4
|
| 146 |
+
|
| 147 |
+
### Setup & Configuration
|
| 148 |
+
- Modular setup wizard with section subcommands and tool-first UX
|
| 149 |
+
- Container resource configuration prompts
|
| 150 |
+
- Backend validation for required binaries
|
| 151 |
+
- Config migration system (currently v7)
|
| 152 |
+
- API keys properly routed to .env instead of config.yaml ([#469](https://github.com/NousResearch/hermes-agent/pull/469)) — @ygd58
|
| 153 |
+
- Atomic write for .env to prevent API key loss on crash ([#954](https://github.com/NousResearch/hermes-agent/pull/954))
|
| 154 |
+
- `hermes tools` — per-platform tool enable/disable with curses UI
|
| 155 |
+
- `hermes doctor` for health checks across all configured providers
|
| 156 |
+
- `hermes update` with auto-restart for gateway service
|
| 157 |
+
- Show update-available notice in CLI banner
|
| 158 |
+
- Multiple named custom providers
|
| 159 |
+
- Shell config detection improvement for PATH setup ([#317](https://github.com/NousResearch/hermes-agent/pull/317)) — @mehmetkr-31
|
| 160 |
+
- Consistent HERMES_HOME and .env path resolution ([#51](https://github.com/NousResearch/hermes-agent/pull/51), [#48](https://github.com/NousResearch/hermes-agent/pull/48)) — @deankerr
|
| 161 |
+
- Docker backend fix on macOS + subagent auth for Nous Portal ([#46](https://github.com/NousResearch/hermes-agent/pull/46)) — @rsavitt
|
| 162 |
+
|
| 163 |
+
---
|
| 164 |
+
|
| 165 |
+
## 🔧 Tool System
|
| 166 |
+
|
| 167 |
+
### MCP (Model Context Protocol)
|
| 168 |
+
- Native MCP client with stdio + HTTP transports ([#291](https://github.com/NousResearch/hermes-agent/pull/291) — @0xbyt4, [#301](https://github.com/NousResearch/hermes-agent/pull/301))
|
| 169 |
+
- Sampling support — server-initiated LLM requests ([#753](https://github.com/NousResearch/hermes-agent/pull/753))
|
| 170 |
+
- Resource and prompt discovery
|
| 171 |
+
- Automatic reconnection and security hardening
|
| 172 |
+
- Banner integration, `/reload-mcp` command
|
| 173 |
+
- `hermes tools` UI integration
|
| 174 |
+
|
| 175 |
+
### Browser
|
| 176 |
+
- Local browser backend — zero-cost headless Chromium (no Browserbase needed)
|
| 177 |
+
- Console/errors tool, annotated screenshots, auto-recording, dogfood QA skill ([#745](https://github.com/NousResearch/hermes-agent/pull/745))
|
| 178 |
+
- Screenshot sharing via MEDIA: on all messaging platforms ([#657](https://github.com/NousResearch/hermes-agent/pull/657))
|
| 179 |
+
|
| 180 |
+
### Terminal & Execution
|
| 181 |
+
- `execute_code` sandbox with json_parse, shell_quote, retry helpers
|
| 182 |
+
- Docker: custom volume mounts ([#158](https://github.com/NousResearch/hermes-agent/pull/158)) — @Indelwin
|
| 183 |
+
- Daytona cloud sandbox backend ([#451](https://github.com/NousResearch/hermes-agent/pull/451)) — @rovle
|
| 184 |
+
- SSH backend fix ([#59](https://github.com/NousResearch/hermes-agent/pull/59)) — @deankerr
|
| 185 |
+
- Shell noise filtering and login shell execution for environment consistency
|
| 186 |
+
- Head+tail truncation for execute_code stdout overflow
|
| 187 |
+
- Configurable background process notification modes
|
| 188 |
+
|
| 189 |
+
### File Operations
|
| 190 |
+
- Filesystem checkpoints and `/rollback` command ([#824](https://github.com/NousResearch/hermes-agent/pull/824))
|
| 191 |
+
- Structured tool result hints (next-action guidance) for patch and search_files ([#722](https://github.com/NousResearch/hermes-agent/issues/722))
|
| 192 |
+
- Docker volumes passed to sandbox container config ([#687](https://github.com/NousResearch/hermes-agent/pull/687)) — @manuelschipper
|
| 193 |
+
|
| 194 |
+
---
|
| 195 |
+
|
| 196 |
+
## 🧩 Skills Ecosystem
|
| 197 |
+
|
| 198 |
+
### Skills System
|
| 199 |
+
- Per-platform skill enable/disable ([#743](https://github.com/NousResearch/hermes-agent/pull/743)) — @teyrebaz33
|
| 200 |
+
- Conditional skill activation based on tool availability ([#785](https://github.com/NousResearch/hermes-agent/pull/785)) — @teyrebaz33
|
| 201 |
+
- Skill prerequisites — hide skills with unmet dependencies ([#659](https://github.com/NousResearch/hermes-agent/pull/659)) — @kshitijk4poor
|
| 202 |
+
- Optional skills — shipped but not activated by default
|
| 203 |
+
- `hermes skills browse` — paginated hub browsing
|
| 204 |
+
- Skills sub-category organization
|
| 205 |
+
- Platform-conditional skill loading
|
| 206 |
+
- Atomic skill file writes ([#551](https://github.com/NousResearch/hermes-agent/pull/551)) — @aydnOktay
|
| 207 |
+
- Skills sync data loss prevention ([#563](https://github.com/NousResearch/hermes-agent/pull/563)) — @0xbyt4
|
| 208 |
+
- Dynamic skill slash commands for CLI and gateway
|
| 209 |
+
|
| 210 |
+
### New Skills (selected)
|
| 211 |
+
- **ASCII Art** — pyfiglet (571 fonts), cowsay, image-to-ascii ([#209](https://github.com/NousResearch/hermes-agent/pull/209)) — @0xbyt4
|
| 212 |
+
- **ASCII Video** — Full production pipeline ([#854](https://github.com/NousResearch/hermes-agent/pull/854)) — @SHL0MS
|
| 213 |
+
- **DuckDuckGo Search** — Firecrawl fallback ([#267](https://github.com/NousResearch/hermes-agent/pull/267)) — @gamedevCloudy; DDGS API expansion ([#598](https://github.com/NousResearch/hermes-agent/pull/598)) — @areu01or00
|
| 214 |
+
- **Solana Blockchain** — Wallet balances, USD pricing, token names ([#212](https://github.com/NousResearch/hermes-agent/pull/212)) — @gizdusum
|
| 215 |
+
- **AgentMail** — Agent-owned email inboxes ([#330](https://github.com/NousResearch/hermes-agent/pull/330)) — @teyrebaz33
|
| 216 |
+
- **Polymarket** — Prediction market data (read-only) ([#629](https://github.com/NousResearch/hermes-agent/pull/629))
|
| 217 |
+
- **OpenClaw Migration** — Official migration tool ([#570](https://github.com/NousResearch/hermes-agent/pull/570)) — @unmodeled-tyler
|
| 218 |
+
- **Domain Intelligence** — Passive recon: subdomains, SSL, WHOIS, DNS ([#136](https://github.com/NousResearch/hermes-agent/pull/136)) — @FurkanL0
|
| 219 |
+
- **Superpowers** — Software development skills ([#137](https://github.com/NousResearch/hermes-agent/pull/137)) — @kaos35
|
| 220 |
+
- **Hermes-Atropos** — RL environment development skill ([#815](https://github.com/NousResearch/hermes-agent/pull/815))
|
| 221 |
+
- Plus: arXiv search, OCR/documents, Excalidraw diagrams, YouTube transcripts, GIF search, Pokémon player, Minecraft modpack server, OpenHue (Philips Hue), Google Workspace, Notion, PowerPoint, Obsidian, find-nearby, and 40+ MLOps skills
|
| 222 |
+
|
| 223 |
+
---
|
| 224 |
+
|
| 225 |
+
## 🔒 Security & Reliability
|
| 226 |
+
|
| 227 |
+
### Security Hardening
|
| 228 |
+
- Path traversal fix in skill_view — prevented reading arbitrary files ([#220](https://github.com/NousResearch/hermes-agent/issues/220)) — @Farukest
|
| 229 |
+
- Shell injection prevention in sudo password piping ([#65](https://github.com/NousResearch/hermes-agent/pull/65)) — @leonsgithub
|
| 230 |
+
- Dangerous command detection: multiline bypass fix ([#233](https://github.com/NousResearch/hermes-agent/pull/233)) — @Farukest; tee/process substitution patterns ([#280](https://github.com/NousResearch/hermes-agent/pull/280)) — @dogiladeveloper
|
| 231 |
+
- Symlink boundary check fix in skills_guard ([#386](https://github.com/NousResearch/hermes-agent/pull/386)) — @Farukest
|
| 232 |
+
- Symlink bypass fix in write deny list on macOS ([#61](https://github.com/NousResearch/hermes-agent/pull/61)) — @0xbyt4
|
| 233 |
+
- Multi-word prompt injection bypass prevention ([#192](https://github.com/NousResearch/hermes-agent/pull/192)) — @0xbyt4
|
| 234 |
+
- Cron prompt injection scanner bypass fix ([#63](https://github.com/NousResearch/hermes-agent/pull/63)) — @0xbyt4
|
| 235 |
+
- Enforce 0600/0700 file permissions on sensitive files ([#757](https://github.com/NousResearch/hermes-agent/pull/757))
|
| 236 |
+
- .env file permissions restricted to owner-only ([#529](https://github.com/NousResearch/hermes-agent/pull/529)) — @Himess
|
| 237 |
+
- `--force` flag properly blocked from overriding dangerous verdicts ([#388](https://github.com/NousResearch/hermes-agent/pull/388)) — @Farukest
|
| 238 |
+
- FTS5 query sanitization + DB connection leak fix ([#565](https://github.com/NousResearch/hermes-agent/pull/565)) — @0xbyt4
|
| 239 |
+
- Expand secret redaction patterns + config toggle to disable
|
| 240 |
+
- In-memory permanent allowlist to prevent data leak ([#600](https://github.com/NousResearch/hermes-agent/pull/600)) — @alireza78a
|
| 241 |
+
|
| 242 |
+
### Atomic Writes (data loss prevention)
|
| 243 |
+
- sessions.json ([#611](https://github.com/NousResearch/hermes-agent/pull/611)) — @alireza78a
|
| 244 |
+
- Cron jobs ([#146](https://github.com/NousResearch/hermes-agent/pull/146)) — @alireza78a
|
| 245 |
+
- .env config ([#954](https://github.com/NousResearch/hermes-agent/pull/954))
|
| 246 |
+
- Process checkpoints ([#298](https://github.com/NousResearch/hermes-agent/pull/298)) — @aydnOktay
|
| 247 |
+
- Batch runner ([#297](https://github.com/NousResearch/hermes-agent/pull/297)) — @aydnOktay
|
| 248 |
+
- Skill files ([#551](https://github.com/NousResearch/hermes-agent/pull/551)) — @aydnOktay
|
| 249 |
+
|
| 250 |
+
### Reliability
|
| 251 |
+
- Guard all print() against OSError for systemd/headless environments ([#963](https://github.com/NousResearch/hermes-agent/pull/963))
|
| 252 |
+
- Reset all retry counters at start of run_conversation ([#607](https://github.com/NousResearch/hermes-agent/pull/607)) — @0xbyt4
|
| 253 |
+
- Return deny on approval callback timeout instead of None ([#603](https://github.com/NousResearch/hermes-agent/pull/603)) — @0xbyt4
|
| 254 |
+
- Fix None message content crashes across codebase ([#277](https://github.com/NousResearch/hermes-agent/pull/277))
|
| 255 |
+
- Fix context overrun crash with local LLM backends ([#403](https://github.com/NousResearch/hermes-agent/pull/403)) — @ch3ronsa
|
| 256 |
+
- Prevent `_flush_sentinel` from leaking to external APIs ([#227](https://github.com/NousResearch/hermes-agent/pull/227)) — @Farukest
|
| 257 |
+
- Prevent conversation_history mutation in callers ([#229](https://github.com/NousResearch/hermes-agent/pull/229)) — @Farukest
|
| 258 |
+
- Fix systemd restart loop ([#614](https://github.com/NousResearch/hermes-agent/pull/614)) — @voidborne-d
|
| 259 |
+
- Close file handles and sockets to prevent fd leaks ([#568](https://github.com/NousResearch/hermes-agent/pull/568) — @alireza78a, [#296](https://github.com/NousResearch/hermes-agent/pull/296) — @alireza78a, [#709](https://github.com/NousResearch/hermes-agent/pull/709) — @memosr)
|
| 260 |
+
- Prevent data loss in clipboard PNG conversion ([#602](https://github.com/NousResearch/hermes-agent/pull/602)) — @0xbyt4
|
| 261 |
+
- Eliminate shell noise from terminal output ([#293](https://github.com/NousResearch/hermes-agent/pull/293)) — @0xbyt4
|
| 262 |
+
- Timezone-aware now() for prompt, cron, and execute_code ([#309](https://github.com/NousResearch/hermes-agent/pull/309)) — @areu01or00
|
| 263 |
+
|
| 264 |
+
### Windows Compatibility
|
| 265 |
+
- Guard POSIX-only process functions ([#219](https://github.com/NousResearch/hermes-agent/pull/219)) — @Farukest
|
| 266 |
+
- Windows native support via Git Bash + ZIP-based update fallback
|
| 267 |
+
- pywinpty for PTY support ([#457](https://github.com/NousResearch/hermes-agent/pull/457)) — @shitcoinsherpa
|
| 268 |
+
- Explicit UTF-8 encoding on all config/data file I/O ([#458](https://github.com/NousResearch/hermes-agent/pull/458)) — @shitcoinsherpa
|
| 269 |
+
- Windows-compatible path handling ([#354](https://github.com/NousResearch/hermes-agent/pull/354), [#390](https://github.com/NousResearch/hermes-agent/pull/390)) — @Farukest
|
| 270 |
+
- Regex-based search output parsing for drive-letter paths ([#533](https://github.com/NousResearch/hermes-agent/pull/533)) — @Himess
|
| 271 |
+
- Auth store file lock for Windows ([#455](https://github.com/NousResearch/hermes-agent/pull/455)) — @shitcoinsherpa
|
| 272 |
+
|
| 273 |
+
---
|
| 274 |
+
|
| 275 |
+
## 🐛 Notable Bug Fixes
|
| 276 |
+
|
| 277 |
+
- Fix DeepSeek V3 tool call parser silently dropping multi-line JSON arguments ([#444](https://github.com/NousResearch/hermes-agent/pull/444)) — @PercyDikec
|
| 278 |
+
- Fix gateway transcript losing 1 message per turn due to offset mismatch ([#395](https://github.com/NousResearch/hermes-agent/pull/395)) — @PercyDikec
|
| 279 |
+
- Fix /retry command silently discarding the agent's final response ([#441](https://github.com/NousResearch/hermes-agent/pull/441)) — @PercyDikec
|
| 280 |
+
- Fix max-iterations retry returning empty string after think-block stripping ([#438](https://github.com/NousResearch/hermes-agent/pull/438)) — @PercyDikec
|
| 281 |
+
- Fix max-iterations retry using hardcoded max_tokens ([#436](https://github.com/NousResearch/hermes-agent/pull/436)) — @Farukest
|
| 282 |
+
- Fix Codex status dict key mismatch ([#448](https://github.com/NousResearch/hermes-agent/pull/448)) and visibility filter ([#446](https://github.com/NousResearch/hermes-agent/pull/446)) — @PercyDikec
|
| 283 |
+
- Strip \<think\> blocks from final user-facing responses ([#174](https://github.com/NousResearch/hermes-agent/pull/174)) — @Bartok9
|
| 284 |
+
- Fix \<think\> block regex stripping visible content when model discusses tags literally ([#786](https://github.com/NousResearch/hermes-agent/issues/786))
|
| 285 |
+
- Fix Mistral 422 errors from leftover finish_reason in assistant messages ([#253](https://github.com/NousResearch/hermes-agent/pull/253)) — @Sertug17
|
| 286 |
+
- Fix OPENROUTER_API_KEY resolution order across all code paths ([#295](https://github.com/NousResearch/hermes-agent/pull/295)) — @0xbyt4
|
| 287 |
+
- Fix OPENAI_BASE_URL API key priority ([#420](https://github.com/NousResearch/hermes-agent/pull/420)) — @manuelschipper
|
| 288 |
+
- Fix Anthropic "prompt is too long" 400 error not detected as context length error ([#813](https://github.com/NousResearch/hermes-agent/issues/813))
|
| 289 |
+
- Fix SQLite session transcript accumulating duplicate messages — 3-4x token inflation ([#860](https://github.com/NousResearch/hermes-agent/issues/860))
|
| 290 |
+
- Fix setup wizard skipping API key prompts on first install ([#748](https://github.com/NousResearch/hermes-agent/pull/748))
|
| 291 |
+
- Fix setup wizard showing OpenRouter model list for Nous Portal ([#575](https://github.com/NousResearch/hermes-agent/pull/575)) — @PercyDikec
|
| 292 |
+
- Fix provider selection not persisting when switching via hermes model ([#881](https://github.com/NousResearch/hermes-agent/pull/881))
|
| 293 |
+
- Fix Docker backend failing when docker not in PATH on macOS ([#889](https://github.com/NousResearch/hermes-agent/pull/889))
|
| 294 |
+
- Fix ClawHub Skills Hub adapter for API endpoint changes ([#286](https://github.com/NousResearch/hermes-agent/pull/286)) — @BP602
|
| 295 |
+
- Fix Honcho auto-enable when API key is present ([#243](https://github.com/NousResearch/hermes-agent/pull/243)) — @Bartok9
|
| 296 |
+
- Fix duplicate 'skills' subparser crash on Python 3.11+ ([#898](https://github.com/NousResearch/hermes-agent/issues/898))
|
| 297 |
+
- Fix memory tool entry parsing when content contains section sign ([#162](https://github.com/NousResearch/hermes-agent/pull/162)) — @aydnOktay
|
| 298 |
+
- Fix piped install silently aborting when interactive prompts fail ([#72](https://github.com/NousResearch/hermes-agent/pull/72)) — @cutepawss
|
| 299 |
+
- Fix false positives in recursive delete detection ([#68](https://github.com/NousResearch/hermes-agent/pull/68)) — @cutepawss
|
| 300 |
+
- Fix Ruff lint warnings across codebase ([#608](https://github.com/NousResearch/hermes-agent/pull/608)) — @JackTheGit
|
| 301 |
+
- Fix Anthropic native base URL fail-fast ([#173](https://github.com/NousResearch/hermes-agent/pull/173)) — @adavyas
|
| 302 |
+
- Fix install.sh creating ~/.hermes before moving Node.js directory ([#53](https://github.com/NousResearch/hermes-agent/pull/53)) — @JoshuaMart
|
| 303 |
+
- Fix SystemExit traceback during atexit cleanup on Ctrl+C ([#55](https://github.com/NousResearch/hermes-agent/pull/55)) — @bierlingm
|
| 304 |
+
- Restore missing MIT license file ([#620](https://github.com/NousResearch/hermes-agent/pull/620)) — @stablegenius49
|
| 305 |
+
|
| 306 |
+
---
|
| 307 |
+
|
| 308 |
+
## 🧪 Testing
|
| 309 |
+
|
| 310 |
+
- **3,289 tests** across agent, gateway, tools, cron, and CLI
|
| 311 |
+
- Parallelized test suite with pytest-xdist ([#802](https://github.com/NousResearch/hermes-agent/pull/802)) — @OutThisLife
|
| 312 |
+
- Unit tests batch 1: 8 core modules ([#60](https://github.com/NousResearch/hermes-agent/pull/60)) — @0xbyt4
|
| 313 |
+
- Unit tests batch 2: 8 more modules ([#62](https://github.com/NousResearch/hermes-agent/pull/62)) — @0xbyt4
|
| 314 |
+
- Unit tests batch 3: 8 untested modules ([#191](https://github.com/NousResearch/hermes-agent/pull/191)) — @0xbyt4
|
| 315 |
+
- Unit tests batch 4: 5 security/logic-critical modules ([#193](https://github.com/NousResearch/hermes-agent/pull/193)) — @0xbyt4
|
| 316 |
+
- AIAgent (run_agent.py) unit tests ([#67](https://github.com/NousResearch/hermes-agent/pull/67)) — @0xbyt4
|
| 317 |
+
- Trajectory compressor tests ([#203](https://github.com/NousResearch/hermes-agent/pull/203)) — @0xbyt4
|
| 318 |
+
- Clarify tool tests ([#121](https://github.com/NousResearch/hermes-agent/pull/121)) — @Bartok9
|
| 319 |
+
- Telegram format tests — 43 tests for italic/bold/code rendering ([#204](https://github.com/NousResearch/hermes-agent/pull/204)) — @0xbyt4
|
| 320 |
+
- Vision tools type hints + 42 tests ([#792](https://github.com/NousResearch/hermes-agent/pull/792))
|
| 321 |
+
- Compressor tool-call boundary regression tests ([#648](https://github.com/NousResearch/hermes-agent/pull/648)) — @intertwine
|
| 322 |
+
- Test structure reorganization ([#34](https://github.com/NousResearch/hermes-agent/pull/34)) — @0xbyt4
|
| 323 |
+
- Shell noise elimination + fix 36 test failures ([#293](https://github.com/NousResearch/hermes-agent/pull/293)) — @0xbyt4
|
| 324 |
+
|
| 325 |
+
---
|
| 326 |
+
|
| 327 |
+
## 🔬 RL & Evaluation Environments
|
| 328 |
+
|
| 329 |
+
- WebResearchEnv — Multi-step web research RL environment ([#434](https://github.com/NousResearch/hermes-agent/pull/434)) — @jackx707
|
| 330 |
+
- Modal sandbox concurrency limits to avoid deadlocks ([#621](https://github.com/NousResearch/hermes-agent/pull/621)) — @voteblake
|
| 331 |
+
- Hermes-atropos-environments bundled skill ([#815](https://github.com/NousResearch/hermes-agent/pull/815))
|
| 332 |
+
- Local vLLM instance support for evaluation — @dmahan93
|
| 333 |
+
- YC-Bench long-horizon agent benchmark environment
|
| 334 |
+
- OpenThoughts-TBLite evaluation environment and scripts
|
| 335 |
+
|
| 336 |
+
---
|
| 337 |
+
|
| 338 |
+
## 📚 Documentation
|
| 339 |
+
|
| 340 |
+
- Full documentation website (Docusaurus) with 37+ pages
|
| 341 |
+
- Comprehensive platform setup guides for Telegram, Discord, Slack, WhatsApp, Signal, Email
|
| 342 |
+
- AGENTS.md — development guide for AI coding assistants
|
| 343 |
+
- CONTRIBUTING.md ([#117](https://github.com/NousResearch/hermes-agent/pull/117)) — @Bartok9
|
| 344 |
+
- Slash commands reference ([#142](https://github.com/NousResearch/hermes-agent/pull/142)) — @Bartok9
|
| 345 |
+
- Comprehensive AGENTS.md accuracy audit ([#732](https://github.com/NousResearch/hermes-agent/pull/732))
|
| 346 |
+
- Skin/theme system documentation
|
| 347 |
+
- MCP documentation and examples
|
| 348 |
+
- Docs accuracy audit — 35+ corrections
|
| 349 |
+
- Documentation typo fixes ([#825](https://github.com/NousResearch/hermes-agent/pull/825), [#439](https://github.com/NousResearch/hermes-agent/pull/439)) — @JackTheGit
|
| 350 |
+
- CLI config precedence and terminology standardization ([#166](https://github.com/NousResearch/hermes-agent/pull/166), [#167](https://github.com/NousResearch/hermes-agent/pull/167), [#168](https://github.com/NousResearch/hermes-agent/pull/168)) — @Jr-kenny
|
| 351 |
+
- Telegram token regex documentation ([#713](https://github.com/NousResearch/hermes-agent/pull/713)) — @VolodymyrBg
|
| 352 |
+
|
| 353 |
+
---
|
| 354 |
+
|
| 355 |
+
## 👥 Contributors
|
| 356 |
+
|
| 357 |
+
Thank you to the 63 contributors who made this release possible! In just over two weeks, the Hermes Agent community came together to ship an extraordinary amount of work.
|
| 358 |
+
|
| 359 |
+
### Core
|
| 360 |
+
- **@teknium1** — 43 PRs: Project lead, core architecture, provider router, sessions, skills, CLI, documentation
|
| 361 |
+
|
| 362 |
+
### Top Community Contributors
|
| 363 |
+
- **@0xbyt4** — 40 PRs: MCP client, Home Assistant, security fixes (symlink, prompt injection, cron), extensive test coverage (6 batches), ascii-art skill, shell noise elimination, skills sync, Telegram formatting, and dozens more
|
| 364 |
+
- **@Farukest** — 16 PRs: Security hardening (path traversal, dangerous command detection, symlink boundary), Windows compatibility (POSIX guards, path handling), WhatsApp fixes, max-iterations retry, gateway fixes
|
| 365 |
+
- **@aydnOktay** — 11 PRs: Atomic writes (process checkpoints, batch runner, skill files), error handling improvements across Telegram, Discord, code execution, transcription, TTS, and skills
|
| 366 |
+
- **@Bartok9** — 9 PRs: CONTRIBUTING.md, slash commands reference, Discord channel topics, think-block stripping, TTS fix, Honcho fix, session count fix, clarify tests
|
| 367 |
+
- **@PercyDikec** — 7 PRs: DeepSeek V3 parser fix, /retry response discard, gateway transcript offset, Codex status/visibility, max-iterations retry, setup wizard fix
|
| 368 |
+
- **@teyrebaz33** — 5 PRs: Skills enable/disable system, quick commands, personality customization, conditional skill activation
|
| 369 |
+
- **@alireza78a** — 5 PRs: Atomic writes (cron, sessions), fd leak prevention, security allowlist, code execution socket cleanup
|
| 370 |
+
- **@shitcoinsherpa** — 3 PRs: Windows support (pywinpty, UTF-8 encoding, auth store lock)
|
| 371 |
+
- **@Himess** — 3 PRs: Cron/HomeAssistant/Daytona fix, Windows drive-letter parsing, .env permissions
|
| 372 |
+
- **@satelerd** — 2 PRs: WhatsApp native media, multi-user session isolation
|
| 373 |
+
- **@rovle** — 1 PR: Daytona cloud sandbox backend (4 commits)
|
| 374 |
+
- **@erosika** — 1 PR: Honcho AI-native memory integration
|
| 375 |
+
- **@dmahan93** — 1 PR: --fuck-it-ship-it flag + RL environment work
|
| 376 |
+
- **@SHL0MS** — 1 PR: ASCII video skill
|
| 377 |
+
|
| 378 |
+
### All Contributors
|
| 379 |
+
@0xbyt4, @BP602, @Bartok9, @Farukest, @FurkanL0, @Himess, @Indelwin, @JackTheGit, @JoshuaMart, @Jr-kenny, @OutThisLife, @PercyDikec, @SHL0MS, @Sertug17, @VencentSoliman, @VolodymyrBg, @adavyas, @alireza78a, @areu01or00, @aydnOktay, @batuhankocyigit, @bierlingm, @caentzminger, @cesareth, @ch3ronsa, @christomitov, @cutepawss, @deankerr, @dmahan93, @dogiladeveloper, @dragonkhoi, @erosika, @gamedevCloudy, @gizdusum, @grp06, @intertwine, @jackx707, @jdblackstar, @johnh4098, @kaos35, @kshitijk4poor, @leonsgithub, @luisv-1, @manuelschipper, @mehmetkr-31, @memosr, @PeterFile, @rewbs, @rovle, @rsavitt, @satelerd, @spanishflu-est1918, @stablegenius49, @tars90percent, @tekelala, @teknium1, @teyrebaz33, @tripledoublev, @unmodeled-tyler, @voidborne-d, @voteblake, @ygd58
|
| 380 |
+
|
| 381 |
+
---
|
| 382 |
+
|
| 383 |
+
**Full Changelog**: [v0.1.0...v2026.3.12](https://github.com/NousResearch/hermes-agent/compare/v0.1.0...v2026.3.12)
|
RELEASE_v0.3.0.md
ADDED
|
@@ -0,0 +1,377 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Hermes Agent v0.3.0 (v2026.3.17)
|
| 2 |
+
|
| 3 |
+
**Release Date:** March 17, 2026
|
| 4 |
+
|
| 5 |
+
> The streaming, plugins, and provider release — unified real-time token delivery, first-class plugin architecture, rebuilt provider system with Vercel AI Gateway, native Anthropic provider, smart approvals, live Chrome CDP browser connect, ACP IDE integration, Honcho memory, voice mode, persistent shell, and 50+ bug fixes across every platform.
|
| 6 |
+
|
| 7 |
+
---
|
| 8 |
+
|
| 9 |
+
## ✨ Highlights
|
| 10 |
+
|
| 11 |
+
- **Unified Streaming Infrastructure** — Real-time token-by-token delivery in CLI and all gateway platforms. Responses stream as they're generated instead of arriving as a block. ([#1538](https://github.com/NousResearch/hermes-agent/pull/1538))
|
| 12 |
+
|
| 13 |
+
- **First-Class Plugin Architecture** — Drop Python files into `~/.hermes/plugins/` to extend Hermes with custom tools, commands, and hooks. No forking required. ([#1544](https://github.com/NousResearch/hermes-agent/pull/1544), [#1555](https://github.com/NousResearch/hermes-agent/pull/1555))
|
| 14 |
+
|
| 15 |
+
- **Native Anthropic Provider** — Direct Anthropic API calls with Claude Code credential auto-discovery, OAuth PKCE flows, and native prompt caching. No OpenRouter middleman needed. ([#1097](https://github.com/NousResearch/hermes-agent/pull/1097))
|
| 16 |
+
|
| 17 |
+
- **Smart Approvals + /stop Command** — Codex-inspired approval system that learns which commands are safe and remembers your preferences. `/stop` kills the current agent run immediately. ([#1543](https://github.com/NousResearch/hermes-agent/pull/1543))
|
| 18 |
+
|
| 19 |
+
- **Honcho Memory Integration** — Async memory writes, configurable recall modes, session title integration, and multi-user isolation in gateway mode. By @erosika. ([#736](https://github.com/NousResearch/hermes-agent/pull/736))
|
| 20 |
+
|
| 21 |
+
- **Voice Mode** — Push-to-talk in CLI, voice notes in Telegram/Discord, Discord voice channel support, and local Whisper transcription via faster-whisper. ([#1299](https://github.com/NousResearch/hermes-agent/pull/1299), [#1185](https://github.com/NousResearch/hermes-agent/pull/1185), [#1429](https://github.com/NousResearch/hermes-agent/pull/1429))
|
| 22 |
+
|
| 23 |
+
- **Concurrent Tool Execution** — Multiple independent tool calls now run in parallel via ThreadPoolExecutor, significantly reducing latency for multi-tool turns. ([#1152](https://github.com/NousResearch/hermes-agent/pull/1152))
|
| 24 |
+
|
| 25 |
+
- **PII Redaction** — When `privacy.redact_pii` is enabled, personally identifiable information is automatically scrubbed before sending context to LLM providers. ([#1542](https://github.com/NousResearch/hermes-agent/pull/1542))
|
| 26 |
+
|
| 27 |
+
- **`/browser connect` via CDP** — Attach browser tools to a live Chrome instance through Chrome DevTools Protocol. Debug, inspect, and interact with pages you already have open. ([#1549](https://github.com/NousResearch/hermes-agent/pull/1549))
|
| 28 |
+
|
| 29 |
+
- **Vercel AI Gateway Provider** — Route Hermes through Vercel's AI Gateway for access to their model catalog and infrastructure. ([#1628](https://github.com/NousResearch/hermes-agent/pull/1628))
|
| 30 |
+
|
| 31 |
+
- **Centralized Provider Router** — Rebuilt provider system with `call_llm` API, unified `/model` command, auto-detect provider on model switch, and direct endpoint overrides for auxiliary/delegation clients. ([#1003](https://github.com/NousResearch/hermes-agent/pull/1003), [#1506](https://github.com/NousResearch/hermes-agent/pull/1506), [#1375](https://github.com/NousResearch/hermes-agent/pull/1375))
|
| 32 |
+
|
| 33 |
+
- **ACP Server (IDE Integration)** — VS Code, Zed, and JetBrains can now connect to Hermes as an agent backend, with full slash command support. ([#1254](https://github.com/NousResearch/hermes-agent/pull/1254), [#1532](https://github.com/NousResearch/hermes-agent/pull/1532))
|
| 34 |
+
|
| 35 |
+
- **Persistent Shell Mode** — Local and SSH terminal backends can maintain shell state across tool calls — cd, env vars, and aliases persist. By @alt-glitch. ([#1067](https://github.com/NousResearch/hermes-agent/pull/1067), [#1483](https://github.com/NousResearch/hermes-agent/pull/1483))
|
| 36 |
+
|
| 37 |
+
- **Agentic On-Policy Distillation (OPD)** — New RL training environment for distilling agent policies, expanding the Atropos training ecosystem. ([#1149](https://github.com/NousResearch/hermes-agent/pull/1149))
|
| 38 |
+
|
| 39 |
+
---
|
| 40 |
+
|
| 41 |
+
## 🏗️ Core Agent & Architecture
|
| 42 |
+
|
| 43 |
+
### Provider & Model Support
|
| 44 |
+
- **Centralized provider router** with `call_llm` API and unified `/model` command — switch models and providers seamlessly ([#1003](https://github.com/NousResearch/hermes-agent/pull/1003))
|
| 45 |
+
- **Vercel AI Gateway** provider support ([#1628](https://github.com/NousResearch/hermes-agent/pull/1628))
|
| 46 |
+
- **Auto-detect provider** when switching models via `/model` ([#1506](https://github.com/NousResearch/hermes-agent/pull/1506))
|
| 47 |
+
- **Direct endpoint overrides** for auxiliary and delegation clients — point vision/subagent calls at specific endpoints ([#1375](https://github.com/NousResearch/hermes-agent/pull/1375))
|
| 48 |
+
- **Native Anthropic auxiliary vision** — use Claude's native vision API instead of routing through OpenAI-compatible endpoints ([#1377](https://github.com/NousResearch/hermes-agent/pull/1377))
|
| 49 |
+
- Anthropic OAuth flow improvements — auto-run `claude setup-token`, reauthentication, PKCE state persistence, identity fingerprinting ([#1132](https://github.com/NousResearch/hermes-agent/pull/1132), [#1360](https://github.com/NousResearch/hermes-agent/pull/1360), [#1396](https://github.com/NousResearch/hermes-agent/pull/1396), [#1597](https://github.com/NousResearch/hermes-agent/pull/1597))
|
| 50 |
+
- Fix adaptive thinking without `budget_tokens` for Claude 4.6 models — by @ASRagab ([#1128](https://github.com/NousResearch/hermes-agent/pull/1128))
|
| 51 |
+
- Fix Anthropic cache markers through adapter — by @brandtcormorant ([#1216](https://github.com/NousResearch/hermes-agent/pull/1216))
|
| 52 |
+
- Retry Anthropic 429/529 errors and surface details to users — by @0xbyt4 ([#1585](https://github.com/NousResearch/hermes-agent/pull/1585))
|
| 53 |
+
- Fix Anthropic adapter max_tokens, fallback crash, proxy base_url — by @0xbyt4 ([#1121](https://github.com/NousResearch/hermes-agent/pull/1121))
|
| 54 |
+
- Fix DeepSeek V3 parser dropping multiple parallel tool calls — by @mr-emmett-one ([#1365](https://github.com/NousResearch/hermes-agent/pull/1365), [#1300](https://github.com/NousResearch/hermes-agent/pull/1300))
|
| 55 |
+
- Accept unlisted models with warning instead of rejecting ([#1047](https://github.com/NousResearch/hermes-agent/pull/1047), [#1102](https://github.com/NousResearch/hermes-agent/pull/1102))
|
| 56 |
+
- Skip reasoning params for unsupported OpenRouter models ([#1485](https://github.com/NousResearch/hermes-agent/pull/1485))
|
| 57 |
+
- MiniMax Anthropic API compatibility fix ([#1623](https://github.com/NousResearch/hermes-agent/pull/1623))
|
| 58 |
+
- Custom endpoint `/models` verification and `/v1` base URL suggestion ([#1480](https://github.com/NousResearch/hermes-agent/pull/1480))
|
| 59 |
+
- Resolve delegation providers from `custom_providers` config ([#1328](https://github.com/NousResearch/hermes-agent/pull/1328))
|
| 60 |
+
- Kimi model additions and User-Agent fix ([#1039](https://github.com/NousResearch/hermes-agent/pull/1039))
|
| 61 |
+
- Strip `call_id`/`response_item_id` for Mistral compatibility ([#1058](https://github.com/NousResearch/hermes-agent/pull/1058))
|
| 62 |
+
|
| 63 |
+
### Agent Loop & Conversation
|
| 64 |
+
- **Anthropic Context Editing API** support ([#1147](https://github.com/NousResearch/hermes-agent/pull/1147))
|
| 65 |
+
- Improved context compaction handoff summaries — compressor now preserves more actionable state ([#1273](https://github.com/NousResearch/hermes-agent/pull/1273))
|
| 66 |
+
- Sync session_id after mid-run context compression ([#1160](https://github.com/NousResearch/hermes-agent/pull/1160))
|
| 67 |
+
- Session hygiene threshold tuned to 50% for more proactive compression ([#1096](https://github.com/NousResearch/hermes-agent/pull/1096), [#1161](https://github.com/NousResearch/hermes-agent/pull/1161))
|
| 68 |
+
- Include session ID in system prompt via `--pass-session-id` flag ([#1040](https://github.com/NousResearch/hermes-agent/pull/1040))
|
| 69 |
+
- Prevent closed OpenAI client reuse across retries ([#1391](https://github.com/NousResearch/hermes-agent/pull/1391))
|
| 70 |
+
- Sanitize chat payloads and provider precedence ([#1253](https://github.com/NousResearch/hermes-agent/pull/1253))
|
| 71 |
+
- Handle dict tool call arguments from Codex and local backends ([#1393](https://github.com/NousResearch/hermes-agent/pull/1393), [#1440](https://github.com/NousResearch/hermes-agent/pull/1440))
|
| 72 |
+
|
| 73 |
+
### Memory & Sessions
|
| 74 |
+
- **Improve memory prioritization** — user preferences and corrections weighted above procedural knowledge ([#1548](https://github.com/NousResearch/hermes-agent/pull/1548))
|
| 75 |
+
- Tighter memory and session recall guidance in system prompts ([#1329](https://github.com/NousResearch/hermes-agent/pull/1329))
|
| 76 |
+
- Persist CLI token counts to session DB for `/insights` ([#1498](https://github.com/NousResearch/hermes-agent/pull/1498))
|
| 77 |
+
- Keep Honcho recall out of the cached system prefix ([#1201](https://github.com/NousResearch/hermes-agent/pull/1201))
|
| 78 |
+
- Correct `seed_ai_identity` to use `session.add_messages()` ([#1475](https://github.com/NousResearch/hermes-agent/pull/1475))
|
| 79 |
+
- Isolate Honcho session routing for multi-user gateway ([#1500](https://github.com/NousResearch/hermes-agent/pull/1500))
|
| 80 |
+
|
| 81 |
+
---
|
| 82 |
+
|
| 83 |
+
## 📱 Messaging Platforms (Gateway)
|
| 84 |
+
|
| 85 |
+
### Gateway Core
|
| 86 |
+
- **System gateway service mode** — run as a system-level systemd service, not just user-level ([#1371](https://github.com/NousResearch/hermes-agent/pull/1371))
|
| 87 |
+
- **Gateway install scope prompts** — choose user vs system scope during setup ([#1374](https://github.com/NousResearch/hermes-agent/pull/1374))
|
| 88 |
+
- **Reasoning hot reload** — change reasoning settings without restarting the gateway ([#1275](https://github.com/NousResearch/hermes-agent/pull/1275))
|
| 89 |
+
- Default group sessions to per-user isolation — no more shared state across users in group chats ([#1495](https://github.com/NousResearch/hermes-agent/pull/1495), [#1417](https://github.com/NousResearch/hermes-agent/pull/1417))
|
| 90 |
+
- Harden gateway restart recovery ([#1310](https://github.com/NousResearch/hermes-agent/pull/1310))
|
| 91 |
+
- Cancel active runs during shutdown ([#1427](https://github.com/NousResearch/hermes-agent/pull/1427))
|
| 92 |
+
- SSL certificate auto-detection for NixOS and non-standard systems ([#1494](https://github.com/NousResearch/hermes-agent/pull/1494))
|
| 93 |
+
- Auto-detect D-Bus session bus for `systemctl --user` on headless servers ([#1601](https://github.com/NousResearch/hermes-agent/pull/1601))
|
| 94 |
+
- Auto-enable systemd linger during gateway install on headless servers ([#1334](https://github.com/NousResearch/hermes-agent/pull/1334))
|
| 95 |
+
- Fall back to module entrypoint when `hermes` is not on PATH ([#1355](https://github.com/NousResearch/hermes-agent/pull/1355))
|
| 96 |
+
- Fix dual gateways on macOS launchd after `hermes update` ([#1567](https://github.com/NousResearch/hermes-agent/pull/1567))
|
| 97 |
+
- Remove recursive ExecStop from systemd units ([#1530](https://github.com/NousResearch/hermes-agent/pull/1530))
|
| 98 |
+
- Prevent logging handler accumulation in gateway mode ([#1251](https://github.com/NousResearch/hermes-agent/pull/1251))
|
| 99 |
+
- Restart on retryable startup failures — by @jplew ([#1517](https://github.com/NousResearch/hermes-agent/pull/1517))
|
| 100 |
+
- Backfill model on gateway sessions after agent runs ([#1306](https://github.com/NousResearch/hermes-agent/pull/1306))
|
| 101 |
+
- PID-based gateway kill and deferred config write ([#1499](https://github.com/NousResearch/hermes-agent/pull/1499))
|
| 102 |
+
|
| 103 |
+
### Telegram
|
| 104 |
+
- Buffer media groups to prevent self-interruption from photo bursts ([#1341](https://github.com/NousResearch/hermes-agent/pull/1341), [#1422](https://github.com/NousResearch/hermes-agent/pull/1422))
|
| 105 |
+
- Retry on transient TLS failures during connect and send ([#1535](https://github.com/NousResearch/hermes-agent/pull/1535))
|
| 106 |
+
- Harden polling conflict handling ([#1339](https://github.com/NousResearch/hermes-agent/pull/1339))
|
| 107 |
+
- Escape chunk indicators and inline code in MarkdownV2 ([#1478](https://github.com/NousResearch/hermes-agent/pull/1478), [#1626](https://github.com/NousResearch/hermes-agent/pull/1626))
|
| 108 |
+
- Check updater/app state before disconnect ([#1389](https://github.com/NousResearch/hermes-agent/pull/1389))
|
| 109 |
+
|
| 110 |
+
### Discord
|
| 111 |
+
- `/thread` command with `auto_thread` config and media metadata fixes ([#1178](https://github.com/NousResearch/hermes-agent/pull/1178))
|
| 112 |
+
- Auto-thread on @mention, skip mention text in bot threads ([#1438](https://github.com/NousResearch/hermes-agent/pull/1438))
|
| 113 |
+
- Retry without reply reference for system messages ([#1385](https://github.com/NousResearch/hermes-agent/pull/1385))
|
| 114 |
+
- Preserve native document and video attachment support ([#1392](https://github.com/NousResearch/hermes-agent/pull/1392))
|
| 115 |
+
- Defer discord adapter annotations to avoid optional import crashes ([#1314](https://github.com/NousResearch/hermes-agent/pull/1314))
|
| 116 |
+
|
| 117 |
+
### Slack
|
| 118 |
+
- Thread handling overhaul — progress messages, responses, and session isolation all respect threads ([#1103](https://github.com/NousResearch/hermes-agent/pull/1103))
|
| 119 |
+
- Formatting, reactions, user resolution, and command improvements ([#1106](https://github.com/NousResearch/hermes-agent/pull/1106))
|
| 120 |
+
- Fix MAX_MESSAGE_LENGTH 3900 → 39000 ([#1117](https://github.com/NousResearch/hermes-agent/pull/1117))
|
| 121 |
+
- File upload fallback preserves thread context — by @0xbyt4 ([#1122](https://github.com/NousResearch/hermes-agent/pull/1122))
|
| 122 |
+
- Improve setup guidance ([#1387](https://github.com/NousResearch/hermes-agent/pull/1387))
|
| 123 |
+
|
| 124 |
+
### Email
|
| 125 |
+
- Fix IMAP UID tracking and SMTP TLS verification ([#1305](https://github.com/NousResearch/hermes-agent/pull/1305))
|
| 126 |
+
- Add `skip_attachments` option via config.yaml ([#1536](https://github.com/NousResearch/hermes-agent/pull/1536))
|
| 127 |
+
|
| 128 |
+
### Home Assistant
|
| 129 |
+
- Event filtering closed by default ([#1169](https://github.com/NousResearch/hermes-agent/pull/1169))
|
| 130 |
+
|
| 131 |
+
---
|
| 132 |
+
|
| 133 |
+
## 🖥️ CLI & User Experience
|
| 134 |
+
|
| 135 |
+
### Interactive CLI
|
| 136 |
+
- **Persistent CLI status bar** — always-visible model, provider, and token counts ([#1522](https://github.com/NousResearch/hermes-agent/pull/1522))
|
| 137 |
+
- **File path autocomplete** in the input prompt ([#1545](https://github.com/NousResearch/hermes-agent/pull/1545))
|
| 138 |
+
- **`/plan` command** — generate implementation plans from specs ([#1372](https://github.com/NousResearch/hermes-agent/pull/1372), [#1381](https://github.com/NousResearch/hermes-agent/pull/1381))
|
| 139 |
+
- **Major `/rollback` improvements** — richer checkpoint history, clearer UX ([#1505](https://github.com/NousResearch/hermes-agent/pull/1505))
|
| 140 |
+
- **Preload CLI skills on launch** — skills are ready before the first prompt ([#1359](https://github.com/NousResearch/hermes-agent/pull/1359))
|
| 141 |
+
- **Centralized slash command registry** — all commands defined once, consumed everywhere ([#1603](https://github.com/NousResearch/hermes-agent/pull/1603))
|
| 142 |
+
- `/bg` alias for `/background` ([#1590](https://github.com/NousResearch/hermes-agent/pull/1590))
|
| 143 |
+
- Prefix matching for slash commands — `/mod` resolves to `/model` ([#1320](https://github.com/NousResearch/hermes-agent/pull/1320))
|
| 144 |
+
- `/new`, `/reset`, `/clear` now start genuinely fresh sessions ([#1237](https://github.com/NousResearch/hermes-agent/pull/1237))
|
| 145 |
+
- Accept session ID prefixes for session actions ([#1425](https://github.com/NousResearch/hermes-agent/pull/1425))
|
| 146 |
+
- TUI prompt and accent output now respect active skin ([#1282](https://github.com/NousResearch/hermes-agent/pull/1282))
|
| 147 |
+
- Centralize tool emoji metadata in registry + skin integration ([#1484](https://github.com/NousResearch/hermes-agent/pull/1484))
|
| 148 |
+
- "View full command" option added to dangerous command approval — by @teknium1 based on design by community ([#887](https://github.com/NousResearch/hermes-agent/pull/887))
|
| 149 |
+
- Non-blocking startup update check and banner deduplication ([#1386](https://github.com/NousResearch/hermes-agent/pull/1386))
|
| 150 |
+
- `/reasoning` command output ordering and inline think extraction fixes ([#1031](https://github.com/NousResearch/hermes-agent/pull/1031))
|
| 151 |
+
- Verbose mode shows full untruncated output ([#1472](https://github.com/NousResearch/hermes-agent/pull/1472))
|
| 152 |
+
- Fix `/status` to report live state and tokens ([#1476](https://github.com/NousResearch/hermes-agent/pull/1476))
|
| 153 |
+
- Seed a default global SOUL.md ([#1311](https://github.com/NousResearch/hermes-agent/pull/1311))
|
| 154 |
+
|
| 155 |
+
### Setup & Configuration
|
| 156 |
+
- **OpenClaw migration** during first-time setup — by @kshitijk4poor ([#981](https://github.com/NousResearch/hermes-agent/pull/981))
|
| 157 |
+
- `hermes claw migrate` command + migration docs ([#1059](https://github.com/NousResearch/hermes-agent/pull/1059))
|
| 158 |
+
- Smart vision setup that respects the user's chosen provider ([#1323](https://github.com/NousResearch/hermes-agent/pull/1323))
|
| 159 |
+
- Handle headless setup flows end-to-end ([#1274](https://github.com/NousResearch/hermes-agent/pull/1274))
|
| 160 |
+
- Prefer curses over `simple_term_menu` in setup.py ([#1487](https://github.com/NousResearch/hermes-agent/pull/1487))
|
| 161 |
+
- Show effective model and provider in `/status` ([#1284](https://github.com/NousResearch/hermes-agent/pull/1284))
|
| 162 |
+
- Config set examples use placeholder syntax ([#1322](https://github.com/NousResearch/hermes-agent/pull/1322))
|
| 163 |
+
- Reload .env over stale shell overrides ([#1434](https://github.com/NousResearch/hermes-agent/pull/1434))
|
| 164 |
+
- Fix is_coding_plan NameError crash — by @0xbyt4 ([#1123](https://github.com/NousResearch/hermes-agent/pull/1123))
|
| 165 |
+
- Add missing packages to setuptools config — by @alt-glitch ([#912](https://github.com/NousResearch/hermes-agent/pull/912))
|
| 166 |
+
- Installer: clarify why sudo is needed at every prompt ([#1602](https://github.com/NousResearch/hermes-agent/pull/1602))
|
| 167 |
+
|
| 168 |
+
---
|
| 169 |
+
|
| 170 |
+
## 🔧 Tool System
|
| 171 |
+
|
| 172 |
+
### Terminal & Execution
|
| 173 |
+
- **Persistent shell mode** for local and SSH backends — maintain shell state across tool calls — by @alt-glitch ([#1067](https://github.com/NousResearch/hermes-agent/pull/1067), [#1483](https://github.com/NousResearch/hermes-agent/pull/1483))
|
| 174 |
+
- **Tirith pre-exec command scanning** — security layer that analyzes commands before execution ([#1256](https://github.com/NousResearch/hermes-agent/pull/1256))
|
| 175 |
+
- Strip Hermes provider env vars from all subprocess environments ([#1157](https://github.com/NousResearch/hermes-agent/pull/1157), [#1172](https://github.com/NousResearch/hermes-agent/pull/1172), [#1399](https://github.com/NousResearch/hermes-agent/pull/1399), [#1419](https://github.com/NousResearch/hermes-agent/pull/1419)) — initial fix by @eren-karakus0
|
| 176 |
+
- SSH preflight check ([#1486](https://github.com/NousResearch/hermes-agent/pull/1486))
|
| 177 |
+
- Docker backend: make cwd workspace mount explicit opt-in ([#1534](https://github.com/NousResearch/hermes-agent/pull/1534))
|
| 178 |
+
- Add project root to PYTHONPATH in execute_code sandbox ([#1383](https://github.com/NousResearch/hermes-agent/pull/1383))
|
| 179 |
+
- Eliminate execute_code progress spam on gateway platforms ([#1098](https://github.com/NousResearch/hermes-agent/pull/1098))
|
| 180 |
+
- Clearer docker backend preflight errors ([#1276](https://github.com/NousResearch/hermes-agent/pull/1276))
|
| 181 |
+
|
| 182 |
+
### Browser
|
| 183 |
+
- **`/browser connect`** — attach browser tools to a live Chrome instance via CDP ([#1549](https://github.com/NousResearch/hermes-agent/pull/1549))
|
| 184 |
+
- Improve browser cleanup, local browser PATH setup, and screenshot recovery ([#1333](https://github.com/NousResearch/hermes-agent/pull/1333))
|
| 185 |
+
|
| 186 |
+
### MCP
|
| 187 |
+
- **Selective tool loading** with utility policies — filter which MCP tools are available ([#1302](https://github.com/NousResearch/hermes-agent/pull/1302))
|
| 188 |
+
- Auto-reload MCP tools when `mcp_servers` config changes without restart ([#1474](https://github.com/NousResearch/hermes-agent/pull/1474))
|
| 189 |
+
- Resolve npx stdio connection failures ([#1291](https://github.com/NousResearch/hermes-agent/pull/1291))
|
| 190 |
+
- Preserve MCP toolsets when saving platform tool config ([#1421](https://github.com/NousResearch/hermes-agent/pull/1421))
|
| 191 |
+
|
| 192 |
+
### Vision
|
| 193 |
+
- Unify vision backend gating ([#1367](https://github.com/NousResearch/hermes-agent/pull/1367))
|
| 194 |
+
- Surface actual error reason instead of generic message ([#1338](https://github.com/NousResearch/hermes-agent/pull/1338))
|
| 195 |
+
- Make Claude image handling work end-to-end ([#1408](https://github.com/NousResearch/hermes-agent/pull/1408))
|
| 196 |
+
|
| 197 |
+
### Cron
|
| 198 |
+
- **Compress cron management into one tool** — single `cronjob` tool replaces multiple commands ([#1343](https://github.com/NousResearch/hermes-agent/pull/1343))
|
| 199 |
+
- Suppress duplicate cron sends to auto-delivery targets ([#1357](https://github.com/NousResearch/hermes-agent/pull/1357))
|
| 200 |
+
- Persist cron sessions to SQLite ([#1255](https://github.com/NousResearch/hermes-agent/pull/1255))
|
| 201 |
+
- Per-job runtime overrides (provider, model, base_url) ([#1398](https://github.com/NousResearch/hermes-agent/pull/1398))
|
| 202 |
+
- Atomic write in `save_job_output` to prevent data loss on crash ([#1173](https://github.com/NousResearch/hermes-agent/pull/1173))
|
| 203 |
+
- Preserve thread context for `deliver=origin` ([#1437](https://github.com/NousResearch/hermes-agent/pull/1437))
|
| 204 |
+
|
| 205 |
+
### Patch Tool
|
| 206 |
+
- Avoid corrupting pipe chars in V4A patch apply ([#1286](https://github.com/NousResearch/hermes-agent/pull/1286))
|
| 207 |
+
- Permissive `block_anchor` thresholds and unicode normalization ([#1539](https://github.com/NousResearch/hermes-agent/pull/1539))
|
| 208 |
+
|
| 209 |
+
### Delegation
|
| 210 |
+
- Add observability metadata to subagent results (model, tokens, duration, tool trace) ([#1175](https://github.com/NousResearch/hermes-agent/pull/1175))
|
| 211 |
+
|
| 212 |
+
---
|
| 213 |
+
|
| 214 |
+
## 🧩 Skills Ecosystem
|
| 215 |
+
|
| 216 |
+
### Skills System
|
| 217 |
+
- **Integrate skills.sh** as a hub source alongside ClawHub ([#1303](https://github.com/NousResearch/hermes-agent/pull/1303))
|
| 218 |
+
- Secure skill env setup on load ([#1153](https://github.com/NousResearch/hermes-agent/pull/1153))
|
| 219 |
+
- Honor policy table for dangerous verdicts ([#1330](https://github.com/NousResearch/hermes-agent/pull/1330))
|
| 220 |
+
- Harden ClawHub skill search exact matches ([#1400](https://github.com/NousResearch/hermes-agent/pull/1400))
|
| 221 |
+
- Fix ClawHub skill install — use `/download` ZIP endpoint ([#1060](https://github.com/NousResearch/hermes-agent/pull/1060))
|
| 222 |
+
- Avoid mislabeling local skills as builtin — by @arceus77-7 ([#862](https://github.com/NousResearch/hermes-agent/pull/862))
|
| 223 |
+
|
| 224 |
+
### New Skills
|
| 225 |
+
- **Linear** project management ([#1230](https://github.com/NousResearch/hermes-agent/pull/1230))
|
| 226 |
+
- **X/Twitter** via x-cli ([#1285](https://github.com/NousResearch/hermes-agent/pull/1285))
|
| 227 |
+
- **Telephony** — Twilio, SMS, and AI calls ([#1289](https://github.com/NousResearch/hermes-agent/pull/1289))
|
| 228 |
+
- **1Password** — by @arceus77-7 ([#883](https://github.com/NousResearch/hermes-agent/pull/883), [#1179](https://github.com/NousResearch/hermes-agent/pull/1179))
|
| 229 |
+
- **NeuroSkill BCI** integration ([#1135](https://github.com/NousResearch/hermes-agent/pull/1135))
|
| 230 |
+
- **Blender MCP** for 3D modeling ([#1531](https://github.com/NousResearch/hermes-agent/pull/1531))
|
| 231 |
+
- **OSS Security Forensics** ([#1482](https://github.com/NousResearch/hermes-agent/pull/1482))
|
| 232 |
+
- **Parallel CLI** research skill ([#1301](https://github.com/NousResearch/hermes-agent/pull/1301))
|
| 233 |
+
- **OpenCode** CLI skill ([#1174](https://github.com/NousResearch/hermes-agent/pull/1174))
|
| 234 |
+
- **ASCII Video** skill refactored — by @SHL0MS ([#1213](https://github.com/NousResearch/hermes-agent/pull/1213), [#1598](https://github.com/NousResearch/hermes-agent/pull/1598))
|
| 235 |
+
|
| 236 |
+
---
|
| 237 |
+
|
| 238 |
+
## 🎙️ Voice Mode
|
| 239 |
+
|
| 240 |
+
- Voice mode foundation — push-to-talk CLI, Telegram/Discord voice notes ([#1299](https://github.com/NousResearch/hermes-agent/pull/1299))
|
| 241 |
+
- Free local Whisper transcription via faster-whisper ([#1185](https://github.com/NousResearch/hermes-agent/pull/1185))
|
| 242 |
+
- Discord voice channel reliability fixes ([#1429](https://github.com/NousResearch/hermes-agent/pull/1429))
|
| 243 |
+
- Restore local STT fallback for gateway voice notes ([#1490](https://github.com/NousResearch/hermes-agent/pull/1490))
|
| 244 |
+
- Honor `stt.enabled: false` across gateway transcription ([#1394](https://github.com/NousResearch/hermes-agent/pull/1394))
|
| 245 |
+
- Fix bogus incapability message on Telegram voice notes (Issue [#1033](https://github.com/NousResearch/hermes-agent/issues/1033))
|
| 246 |
+
|
| 247 |
+
---
|
| 248 |
+
|
| 249 |
+
## 🔌 ACP (IDE Integration)
|
| 250 |
+
|
| 251 |
+
- Restore ACP server implementation ([#1254](https://github.com/NousResearch/hermes-agent/pull/1254))
|
| 252 |
+
- Support slash commands in ACP adapter ([#1532](https://github.com/NousResearch/hermes-agent/pull/1532))
|
| 253 |
+
|
| 254 |
+
---
|
| 255 |
+
|
| 256 |
+
## 🧪 RL Training
|
| 257 |
+
|
| 258 |
+
- **Agentic On-Policy Distillation (OPD)** environment — new RL training environment for agent policy distillation ([#1149](https://github.com/NousResearch/hermes-agent/pull/1149))
|
| 259 |
+
- Make tinker-atropos RL training fully optional ([#1062](https://github.com/NousResearch/hermes-agent/pull/1062))
|
| 260 |
+
|
| 261 |
+
---
|
| 262 |
+
|
| 263 |
+
## 🔒 Security & Reliability
|
| 264 |
+
|
| 265 |
+
### Security Hardening
|
| 266 |
+
- **Tirith pre-exec command scanning** — static analysis of terminal commands before execution ([#1256](https://github.com/NousResearch/hermes-agent/pull/1256))
|
| 267 |
+
- **PII redaction** when `privacy.redact_pii` is enabled ([#1542](https://github.com/NousResearch/hermes-agent/pull/1542))
|
| 268 |
+
- Strip Hermes provider/gateway/tool env vars from all subprocess environments ([#1157](https://github.com/NousResearch/hermes-agent/pull/1157), [#1172](https://github.com/NousResearch/hermes-agent/pull/1172), [#1399](https://github.com/NousResearch/hermes-agent/pull/1399), [#1419](https://github.com/NousResearch/hermes-agent/pull/1419))
|
| 269 |
+
- Docker cwd workspace mount now explicit opt-in — never auto-mount host directories ([#1534](https://github.com/NousResearch/hermes-agent/pull/1534))
|
| 270 |
+
- Escape parens and braces in fork bomb regex pattern ([#1397](https://github.com/NousResearch/hermes-agent/pull/1397))
|
| 271 |
+
- Harden `.worktreeinclude` path containment ([#1388](https://github.com/NousResearch/hermes-agent/pull/1388))
|
| 272 |
+
- Use description as `pattern_key` to prevent approval collisions ([#1395](https://github.com/NousResearch/hermes-agent/pull/1395))
|
| 273 |
+
|
| 274 |
+
### Reliability
|
| 275 |
+
- Guard init-time stdio writes ([#1271](https://github.com/NousResearch/hermes-agent/pull/1271))
|
| 276 |
+
- Session log writes reuse shared atomic JSON helper ([#1280](https://github.com/NousResearch/hermes-agent/pull/1280))
|
| 277 |
+
- Atomic temp cleanup protected on interrupts ([#1401](https://github.com/NousResearch/hermes-agent/pull/1401))
|
| 278 |
+
|
| 279 |
+
---
|
| 280 |
+
|
| 281 |
+
## 🐛 Notable Bug Fixes
|
| 282 |
+
|
| 283 |
+
- **`/status` always showing 0 tokens** — now reports live state (Issue [#1465](https://github.com/NousResearch/hermes-agent/issues/1465), [#1476](https://github.com/NousResearch/hermes-agent/pull/1476))
|
| 284 |
+
- **Custom model endpoints not working** — restored config-saved endpoint resolution (Issue [#1460](https://github.com/NousResearch/hermes-agent/issues/1460), [#1373](https://github.com/NousResearch/hermes-agent/pull/1373))
|
| 285 |
+
- **MCP tools not visible until restart** — auto-reload on config change (Issue [#1036](https://github.com/NousResearch/hermes-agent/issues/1036), [#1474](https://github.com/NousResearch/hermes-agent/pull/1474))
|
| 286 |
+
- **`hermes tools` removing MCP tools** — preserve MCP toolsets when saving (Issue [#1247](https://github.com/NousResearch/hermes-agent/issues/1247), [#1421](https://github.com/NousResearch/hermes-agent/pull/1421))
|
| 287 |
+
- **Terminal subprocesses inheriting `OPENAI_BASE_URL`** breaking external tools (Issue [#1002](https://github.com/NousResearch/hermes-agent/issues/1002), [#1399](https://github.com/NousResearch/hermes-agent/pull/1399))
|
| 288 |
+
- **Background process lost on gateway restart** — improved recovery (Issue [#1144](https://github.com/NousResearch/hermes-agent/issues/1144))
|
| 289 |
+
- **Cron jobs not persisting state** — now stored in SQLite (Issue [#1416](https://github.com/NousResearch/hermes-agent/issues/1416), [#1255](https://github.com/NousResearch/hermes-agent/pull/1255))
|
| 290 |
+
- **Cronjob `deliver: origin` not preserving thread context** (Issue [#1219](https://github.com/NousResearch/hermes-agent/issues/1219), [#1437](https://github.com/NousResearch/hermes-agent/pull/1437))
|
| 291 |
+
- **Gateway systemd service failing to auto-restart** when browser processes orphaned (Issue [#1617](https://github.com/NousResearch/hermes-agent/issues/1617))
|
| 292 |
+
- **`/background` completion report cut off in Telegram** (Issue [#1443](https://github.com/NousResearch/hermes-agent/issues/1443))
|
| 293 |
+
- **Model switching not taking effect** (Issue [#1244](https://github.com/NousResearch/hermes-agent/issues/1244), [#1183](https://github.com/NousResearch/hermes-agent/pull/1183))
|
| 294 |
+
- **`hermes doctor` reporting cronjob as unavailable** (Issue [#878](https://github.com/NousResearch/hermes-agent/issues/878), [#1180](https://github.com/NousResearch/hermes-agent/pull/1180))
|
| 295 |
+
- **WhatsApp bridge messages not received** from mobile (Issue [#1142](https://github.com/NousResearch/hermes-agent/issues/1142))
|
| 296 |
+
- **Setup wizard hanging on headless SSH** (Issue [#905](https://github.com/NousResearch/hermes-agent/issues/905), [#1274](https://github.com/NousResearch/hermes-agent/pull/1274))
|
| 297 |
+
- **Log handler accumulation** degrading gateway performance (Issue [#990](https://github.com/NousResearch/hermes-agent/issues/990), [#1251](https://github.com/NousResearch/hermes-agent/pull/1251))
|
| 298 |
+
- **Gateway NULL model in DB** (Issue [#987](https://github.com/NousResearch/hermes-agent/issues/987), [#1306](https://github.com/NousResearch/hermes-agent/pull/1306))
|
| 299 |
+
- **Strict endpoints rejecting replayed tool_calls** (Issue [#893](https://github.com/NousResearch/hermes-agent/issues/893))
|
| 300 |
+
- **Remaining hardcoded `~/.hermes` paths** — all now respect `HERMES_HOME` (Issue [#892](https://github.com/NousResearch/hermes-agent/issues/892), [#1233](https://github.com/NousResearch/hermes-agent/pull/1233))
|
| 301 |
+
- **Delegate tool not working with custom inference providers** (Issue [#1011](https://github.com/NousResearch/hermes-agent/issues/1011), [#1328](https://github.com/NousResearch/hermes-agent/pull/1328))
|
| 302 |
+
- **Skills Guard blocking official skills** (Issue [#1006](https://github.com/NousResearch/hermes-agent/issues/1006), [#1330](https://github.com/NousResearch/hermes-agent/pull/1330))
|
| 303 |
+
- **Setup writing provider before model selection** (Issue [#1182](https://github.com/NousResearch/hermes-agent/issues/1182))
|
| 304 |
+
- **`GatewayConfig.get()` AttributeError** crashing all message handling (Issue [#1158](https://github.com/NousResearch/hermes-agent/issues/1158), [#1287](https://github.com/NousResearch/hermes-agent/pull/1287))
|
| 305 |
+
- **`/update` hard-failing with "command not found"** (Issue [#1049](https://github.com/NousResearch/hermes-agent/issues/1049))
|
| 306 |
+
- **Image analysis failing silently** (Issue [#1034](https://github.com/NousResearch/hermes-agent/issues/1034), [#1338](https://github.com/NousResearch/hermes-agent/pull/1338))
|
| 307 |
+
- **API `BadRequestError` from `'dict'` object has no attribute `'strip'`** (Issue [#1071](https://github.com/NousResearch/hermes-agent/issues/1071))
|
| 308 |
+
- **Slash commands requiring exact full name** — now uses prefix matching (Issue [#928](https://github.com/NousResearch/hermes-agent/issues/928), [#1320](https://github.com/NousResearch/hermes-agent/pull/1320))
|
| 309 |
+
- **Gateway stops responding when terminal is closed on headless** (Issue [#1005](https://github.com/NousResearch/hermes-agent/issues/1005))
|
| 310 |
+
|
| 311 |
+
---
|
| 312 |
+
|
| 313 |
+
## 🧪 Testing
|
| 314 |
+
|
| 315 |
+
- Cover empty cached Anthropic tool-call turns ([#1222](https://github.com/NousResearch/hermes-agent/pull/1222))
|
| 316 |
+
- Fix stale CI assumptions in parser and quick-command coverage ([#1236](https://github.com/NousResearch/hermes-agent/pull/1236))
|
| 317 |
+
- Fix gateway async tests without implicit event loop ([#1278](https://github.com/NousResearch/hermes-agent/pull/1278))
|
| 318 |
+
- Make gateway async tests xdist-safe ([#1281](https://github.com/NousResearch/hermes-agent/pull/1281))
|
| 319 |
+
- Cross-timezone naive timestamp regression for cron ([#1319](https://github.com/NousResearch/hermes-agent/pull/1319))
|
| 320 |
+
- Isolate codex provider tests from local env ([#1335](https://github.com/NousResearch/hermes-agent/pull/1335))
|
| 321 |
+
- Lock retry replacement semantics ([#1379](https://github.com/NousResearch/hermes-agent/pull/1379))
|
| 322 |
+
- Improve error logging in session search tool — by @aydnOktay ([#1533](https://github.com/NousResearch/hermes-agent/pull/1533))
|
| 323 |
+
|
| 324 |
+
---
|
| 325 |
+
|
| 326 |
+
## 📚 Documentation
|
| 327 |
+
|
| 328 |
+
- Comprehensive SOUL.md guide ([#1315](https://github.com/NousResearch/hermes-agent/pull/1315))
|
| 329 |
+
- Voice mode documentation ([#1316](https://github.com/NousResearch/hermes-agent/pull/1316), [#1362](https://github.com/NousResearch/hermes-agent/pull/1362))
|
| 330 |
+
- Provider contribution guide ([#1361](https://github.com/NousResearch/hermes-agent/pull/1361))
|
| 331 |
+
- ACP and internal systems implementation guides ([#1259](https://github.com/NousResearch/hermes-agent/pull/1259))
|
| 332 |
+
- Expand Docusaurus coverage across CLI, tools, skills, and skins ([#1232](https://github.com/NousResearch/hermes-agent/pull/1232))
|
| 333 |
+
- Terminal backend and Windows troubleshooting ([#1297](https://github.com/NousResearch/hermes-agent/pull/1297))
|
| 334 |
+
- Skills hub reference section ([#1317](https://github.com/NousResearch/hermes-agent/pull/1317))
|
| 335 |
+
- Checkpoint, /rollback, and git worktrees guide ([#1493](https://github.com/NousResearch/hermes-agent/pull/1493), [#1524](https://github.com/NousResearch/hermes-agent/pull/1524))
|
| 336 |
+
- CLI status bar and /usage reference ([#1523](https://github.com/NousResearch/hermes-agent/pull/1523))
|
| 337 |
+
- Fallback providers + /background command docs ([#1430](https://github.com/NousResearch/hermes-agent/pull/1430))
|
| 338 |
+
- Gateway service scopes docs ([#1378](https://github.com/NousResearch/hermes-agent/pull/1378))
|
| 339 |
+
- Slack thread reply behavior docs ([#1407](https://github.com/NousResearch/hermes-agent/pull/1407))
|
| 340 |
+
- Redesigned landing page with Nous blue palette — by @austinpickett ([#974](https://github.com/NousResearch/hermes-agent/pull/974))
|
| 341 |
+
- Fix several documentation typos — by @JackTheGit ([#953](https://github.com/NousResearch/hermes-agent/pull/953))
|
| 342 |
+
- Stabilize website diagrams ([#1405](https://github.com/NousResearch/hermes-agent/pull/1405))
|
| 343 |
+
- CLI vs messaging quick reference in README ([#1491](https://github.com/NousResearch/hermes-agent/pull/1491))
|
| 344 |
+
- Add search to Docusaurus ([#1053](https://github.com/NousResearch/hermes-agent/pull/1053))
|
| 345 |
+
- Home Assistant integration docs ([#1170](https://github.com/NousResearch/hermes-agent/pull/1170))
|
| 346 |
+
|
| 347 |
+
---
|
| 348 |
+
|
| 349 |
+
## 👥 Contributors
|
| 350 |
+
|
| 351 |
+
### Core
|
| 352 |
+
- **@teknium1** — 220+ PRs spanning every area of the codebase
|
| 353 |
+
|
| 354 |
+
### Top Community Contributors
|
| 355 |
+
|
| 356 |
+
- **@0xbyt4** (4 PRs) — Anthropic adapter fixes (max_tokens, fallback crash, 429/529 retry), Slack file upload thread context, setup NameError fix
|
| 357 |
+
- **@erosika** (1 PR) — Honcho memory integration: async writes, memory modes, session title integration
|
| 358 |
+
- **@SHL0MS** (2 PRs) — ASCII video skill design patterns and refactoring
|
| 359 |
+
- **@alt-glitch** (2 PRs) — Persistent shell mode for local/SSH backends, setuptools packaging fix
|
| 360 |
+
- **@arceus77-7** (2 PRs) — 1Password skill, fix skills list mislabeling
|
| 361 |
+
- **@kshitijk4poor** (1 PR) — OpenClaw migration during setup wizard
|
| 362 |
+
- **@ASRagab** (1 PR) — Fix adaptive thinking for Claude 4.6 models
|
| 363 |
+
- **@eren-karakus0** (1 PR) — Strip Hermes provider env vars from subprocess environment
|
| 364 |
+
- **@mr-emmett-one** (1 PR) — Fix DeepSeek V3 parser multi-tool call support
|
| 365 |
+
- **@jplew** (1 PR) — Gateway restart on retryable startup failures
|
| 366 |
+
- **@brandtcormorant** (1 PR) — Fix Anthropic cache control for empty text blocks
|
| 367 |
+
- **@aydnOktay** (1 PR) — Improve error logging in session search tool
|
| 368 |
+
- **@austinpickett** (1 PR) — Landing page redesign with Nous blue palette
|
| 369 |
+
- **@JackTheGit** (1 PR) — Documentation typo fixes
|
| 370 |
+
|
| 371 |
+
### All Contributors
|
| 372 |
+
|
| 373 |
+
@0xbyt4, @alt-glitch, @arceus77-7, @ASRagab, @austinpickett, @aydnOktay, @brandtcormorant, @eren-karakus0, @erosika, @JackTheGit, @jplew, @kshitijk4poor, @mr-emmett-one, @SHL0MS, @teknium1
|
| 374 |
+
|
| 375 |
+
---
|
| 376 |
+
|
| 377 |
+
**Full Changelog**: [v2026.3.12...v2026.3.17](https://github.com/NousResearch/hermes-agent/compare/v2026.3.12...v2026.3.17)
|
RELEASE_v0.4.0.md
ADDED
|
@@ -0,0 +1,400 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Hermes Agent v0.4.0 (v2026.3.23)
|
| 2 |
+
|
| 3 |
+
**Release Date:** March 23, 2026
|
| 4 |
+
|
| 5 |
+
> The platform expansion release — OpenAI-compatible API server, 6 new messaging adapters, 4 new inference providers, MCP server management with OAuth 2.1, @ context references, gateway prompt caching, streaming enabled by default, and a sweeping reliability pass with 200+ bug fixes.
|
| 6 |
+
|
| 7 |
+
---
|
| 8 |
+
|
| 9 |
+
## ✨ Highlights
|
| 10 |
+
|
| 11 |
+
- **OpenAI-compatible API server** — Expose Hermes as an `/v1/chat/completions` endpoint with a new `/api/jobs` REST API for cron job management, hardened with input limits, field whitelists, SQLite-backed response persistence, and CORS origin protection ([#1756](https://github.com/NousResearch/hermes-agent/pull/1756), [#2450](https://github.com/NousResearch/hermes-agent/pull/2450), [#2456](https://github.com/NousResearch/hermes-agent/pull/2456), [#2451](https://github.com/NousResearch/hermes-agent/pull/2451), [#2472](https://github.com/NousResearch/hermes-agent/pull/2472))
|
| 12 |
+
|
| 13 |
+
- **6 new messaging platform adapters** — Signal, DingTalk, SMS (Twilio), Mattermost, Matrix, and Webhook adapters join Telegram, Discord, and WhatsApp. Gateway auto-reconnects failed platforms with exponential backoff ([#2206](https://github.com/NousResearch/hermes-agent/pull/2206), [#1685](https://github.com/NousResearch/hermes-agent/pull/1685), [#1688](https://github.com/NousResearch/hermes-agent/pull/1688), [#1683](https://github.com/NousResearch/hermes-agent/pull/1683), [#2166](https://github.com/NousResearch/hermes-agent/pull/2166), [#2584](https://github.com/NousResearch/hermes-agent/pull/2584))
|
| 14 |
+
|
| 15 |
+
- **@ context references** — Claude Code-style `@file` and `@url` context injection with tab completions in the CLI ([#2343](https://github.com/NousResearch/hermes-agent/pull/2343), [#2482](https://github.com/NousResearch/hermes-agent/pull/2482))
|
| 16 |
+
|
| 17 |
+
- **4 new inference providers** — GitHub Copilot (OAuth + token validation), Alibaba Cloud / DashScope, Kilo Code, and OpenCode Zen/Go ([#1924](https://github.com/NousResearch/hermes-agent/pull/1924), [#1879](https://github.com/NousResearch/hermes-agent/pull/1879) by @mchzimm, [#1673](https://github.com/NousResearch/hermes-agent/pull/1673), [#1666](https://github.com/NousResearch/hermes-agent/pull/1666), [#1650](https://github.com/NousResearch/hermes-agent/pull/1650))
|
| 18 |
+
|
| 19 |
+
- **MCP server management CLI** — `hermes mcp` commands for installing, configuring, and authenticating MCP servers with full OAuth 2.1 PKCE flow ([#2465](https://github.com/NousResearch/hermes-agent/pull/2465))
|
| 20 |
+
|
| 21 |
+
- **Gateway prompt caching** — Cache AIAgent instances per session, preserving Anthropic prompt cache across turns for dramatic cost reduction on long conversations ([#2282](https://github.com/NousResearch/hermes-agent/pull/2282), [#2284](https://github.com/NousResearch/hermes-agent/pull/2284), [#2361](https://github.com/NousResearch/hermes-agent/pull/2361))
|
| 22 |
+
|
| 23 |
+
- **Context compression overhaul** — Structured summaries with iterative updates, token-budget tail protection, configurable summary endpoint, and fallback model support ([#2323](https://github.com/NousResearch/hermes-agent/pull/2323), [#1727](https://github.com/NousResearch/hermes-agent/pull/1727), [#2224](https://github.com/NousResearch/hermes-agent/pull/2224))
|
| 24 |
+
|
| 25 |
+
- **Streaming enabled by default** — CLI streaming on by default with proper spinner/tool progress display during streaming mode, plus extensive linebreak and concatenation fixes ([#2340](https://github.com/NousResearch/hermes-agent/pull/2340), [#2161](https://github.com/NousResearch/hermes-agent/pull/2161), [#2258](https://github.com/NousResearch/hermes-agent/pull/2258))
|
| 26 |
+
|
| 27 |
+
---
|
| 28 |
+
|
| 29 |
+
## 🖥️ CLI & User Experience
|
| 30 |
+
|
| 31 |
+
### New Commands & Interactions
|
| 32 |
+
- **@ context completions** — Tab-completable `@file`/`@url` references that inject file content or web pages into the conversation ([#2482](https://github.com/NousResearch/hermes-agent/pull/2482), [#2343](https://github.com/NousResearch/hermes-agent/pull/2343))
|
| 33 |
+
- **`/statusbar`** — Toggle a persistent config bar showing model + provider info in the prompt ([#2240](https://github.com/NousResearch/hermes-agent/pull/2240), [#1917](https://github.com/NousResearch/hermes-agent/pull/1917))
|
| 34 |
+
- **`/queue`** — Queue prompts for the agent without interrupting the current run ([#2191](https://github.com/NousResearch/hermes-agent/pull/2191), [#2469](https://github.com/NousResearch/hermes-agent/pull/2469))
|
| 35 |
+
- **`/permission`** — Switch approval mode dynamically during a session ([#2207](https://github.com/NousResearch/hermes-agent/pull/2207))
|
| 36 |
+
- **`/browser`** — Interactive browser sessions from the CLI ([#2273](https://github.com/NousResearch/hermes-agent/pull/2273), [#1814](https://github.com/NousResearch/hermes-agent/pull/1814))
|
| 37 |
+
- **`/cost`** — Live pricing and usage tracking in gateway mode ([#2180](https://github.com/NousResearch/hermes-agent/pull/2180))
|
| 38 |
+
- **`/approve` and `/deny`** — Replaced bare text approval in gateway with explicit commands ([#2002](https://github.com/NousResearch/hermes-agent/pull/2002))
|
| 39 |
+
|
| 40 |
+
### Streaming & Display
|
| 41 |
+
- Streaming enabled by default in CLI ([#2340](https://github.com/NousResearch/hermes-agent/pull/2340))
|
| 42 |
+
- Show spinners and tool progress during streaming mode ([#2161](https://github.com/NousResearch/hermes-agent/pull/2161))
|
| 43 |
+
- Show reasoning/thinking blocks when `show_reasoning` enabled ([#2118](https://github.com/NousResearch/hermes-agent/pull/2118))
|
| 44 |
+
- Context pressure warnings for CLI and gateway ([#2159](https://github.com/NousResearch/hermes-agent/pull/2159))
|
| 45 |
+
- Fix: streaming chunks concatenated without whitespace ([#2258](https://github.com/NousResearch/hermes-agent/pull/2258))
|
| 46 |
+
- Fix: iteration boundary linebreak prevents stream concatenation ([#2413](https://github.com/NousResearch/hermes-agent/pull/2413))
|
| 47 |
+
- Fix: defer streaming linebreak to prevent blank line stacking ([#2473](https://github.com/NousResearch/hermes-agent/pull/2473))
|
| 48 |
+
- Fix: suppress spinner animation in non-TTY environments ([#2216](https://github.com/NousResearch/hermes-agent/pull/2216))
|
| 49 |
+
- Fix: display provider and endpoint in API error messages ([#2266](https://github.com/NousResearch/hermes-agent/pull/2266))
|
| 50 |
+
- Fix: resolve garbled ANSI escape codes in status printouts ([#2448](https://github.com/NousResearch/hermes-agent/pull/2448))
|
| 51 |
+
- Fix: update gold ANSI color to true-color format ([#2246](https://github.com/NousResearch/hermes-agent/pull/2246))
|
| 52 |
+
- Fix: normalize toolset labels and use skin colors in banner ([#1912](https://github.com/NousResearch/hermes-agent/pull/1912))
|
| 53 |
+
|
| 54 |
+
### CLI Polish
|
| 55 |
+
- Fix: prevent 'Press ENTER to continue...' on exit ([#2555](https://github.com/NousResearch/hermes-agent/pull/2555))
|
| 56 |
+
- Fix: flush stdout during agent loop to prevent macOS display freeze ([#1654](https://github.com/NousResearch/hermes-agent/pull/1654))
|
| 57 |
+
- Fix: show human-readable error when `hermes setup` hits permissions error ([#2196](https://github.com/NousResearch/hermes-agent/pull/2196))
|
| 58 |
+
- Fix: `/stop` command crash + UnboundLocalError in streaming media delivery ([#2463](https://github.com/NousResearch/hermes-agent/pull/2463))
|
| 59 |
+
- Fix: allow custom/local endpoints without API key ([#2556](https://github.com/NousResearch/hermes-agent/pull/2556))
|
| 60 |
+
- Fix: Kitty keyboard protocol Shift+Enter for Ghostty/WezTerm (attempted + reverted due to prompt_toolkit crash) ([#2345](https://github.com/NousResearch/hermes-agent/pull/2345), [#2349](https://github.com/NousResearch/hermes-agent/pull/2349))
|
| 61 |
+
|
| 62 |
+
### Configuration
|
| 63 |
+
- **`${ENV_VAR}` substitution** in config.yaml ([#2684](https://github.com/NousResearch/hermes-agent/pull/2684))
|
| 64 |
+
- **Real-time config reload** — config.yaml changes apply without restart ([#2210](https://github.com/NousResearch/hermes-agent/pull/2210))
|
| 65 |
+
- **`custom_models.yaml`** for user-managed model additions ([#2214](https://github.com/NousResearch/hermes-agent/pull/2214))
|
| 66 |
+
- **Priority-based context file selection** + CLAUDE.md support ([#2301](https://github.com/NousResearch/hermes-agent/pull/2301))
|
| 67 |
+
- **Merge nested YAML sections** instead of replacing on config update ([#2213](https://github.com/NousResearch/hermes-agent/pull/2213))
|
| 68 |
+
- Fix: config.yaml provider key overrides env var silently ([#2272](https://github.com/NousResearch/hermes-agent/pull/2272))
|
| 69 |
+
- Fix: log warning instead of silently swallowing config.yaml errors ([#2683](https://github.com/NousResearch/hermes-agent/pull/2683))
|
| 70 |
+
- Fix: disabled toolsets re-enable themselves after `hermes tools` ([#2268](https://github.com/NousResearch/hermes-agent/pull/2268))
|
| 71 |
+
- Fix: platform default toolsets silently override tool deselection ([#2624](https://github.com/NousResearch/hermes-agent/pull/2624))
|
| 72 |
+
- Fix: honor bare YAML `approvals.mode: off` ([#2620](https://github.com/NousResearch/hermes-agent/pull/2620))
|
| 73 |
+
- Fix: `hermes update` use `.[all]` extras with fallback ([#1728](https://github.com/NousResearch/hermes-agent/pull/1728))
|
| 74 |
+
- Fix: `hermes update` prompt before resetting working tree on stash conflicts ([#2390](https://github.com/NousResearch/hermes-agent/pull/2390))
|
| 75 |
+
- Fix: use git pull --rebase in update/install to avoid divergent branch error ([#2274](https://github.com/NousResearch/hermes-agent/pull/2274))
|
| 76 |
+
- Fix: add zprofile fallback and create zshrc on fresh macOS installs ([#2320](https://github.com/NousResearch/hermes-agent/pull/2320))
|
| 77 |
+
- Fix: remove `ANTHROPIC_BASE_URL` env var to avoid collisions ([#1675](https://github.com/NousResearch/hermes-agent/pull/1675))
|
| 78 |
+
- Fix: don't ask IMAP password if already in keyring or env ([#2212](https://github.com/NousResearch/hermes-agent/pull/2212))
|
| 79 |
+
- Fix: OpenCode Zen/Go show OpenRouter models instead of their own ([#2277](https://github.com/NousResearch/hermes-agent/pull/2277))
|
| 80 |
+
|
| 81 |
+
---
|
| 82 |
+
|
| 83 |
+
## 🏗️ Core Agent & Architecture
|
| 84 |
+
|
| 85 |
+
### New Providers
|
| 86 |
+
- **GitHub Copilot** — Full OAuth auth, API routing, token validation, and 400k context. ([#1924](https://github.com/NousResearch/hermes-agent/pull/1924), [#1896](https://github.com/NousResearch/hermes-agent/pull/1896), [#1879](https://github.com/NousResearch/hermes-agent/pull/1879) by @mchzimm, [#2507](https://github.com/NousResearch/hermes-agent/pull/2507))
|
| 87 |
+
- **Alibaba Cloud / DashScope** — Full integration with DashScope v1 runtime, model dot preservation, and 401 auth fixes ([#1673](https://github.com/NousResearch/hermes-agent/pull/1673), [#2332](https://github.com/NousResearch/hermes-agent/pull/2332), [#2459](https://github.com/NousResearch/hermes-agent/pull/2459))
|
| 88 |
+
- **Kilo Code** — First-class inference provider ([#1666](https://github.com/NousResearch/hermes-agent/pull/1666))
|
| 89 |
+
- **OpenCode Zen and OpenCode Go** — New provider backends ([#1650](https://github.com/NousResearch/hermes-agent/pull/1650), [#2393](https://github.com/NousResearch/hermes-agent/pull/2393) by @0xbyt4)
|
| 90 |
+
- **NeuTTS** — Local TTS provider backend with built-in setup flow, replacing the old optional skill ([#1657](https://github.com/NousResearch/hermes-agent/pull/1657), [#1664](https://github.com/NousResearch/hermes-agent/pull/1664))
|
| 91 |
+
|
| 92 |
+
### Provider Improvements
|
| 93 |
+
- **Eager fallback** to backup model on rate-limit errors ([#1730](https://github.com/NousResearch/hermes-agent/pull/1730))
|
| 94 |
+
- **Endpoint metadata** for custom model context and pricing; query local servers for actual context window size ([#1906](https://github.com/NousResearch/hermes-agent/pull/1906), [#2091](https://github.com/NousResearch/hermes-agent/pull/2091) by @dusterbloom)
|
| 95 |
+
- **Context length detection overhaul** — models.dev integration, provider-aware resolution, fuzzy matching for custom endpoints, `/v1/props` for llama.cpp ([#2158](https://github.com/NousResearch/hermes-agent/pull/2158), [#2051](https://github.com/NousResearch/hermes-agent/pull/2051), [#2403](https://github.com/NousResearch/hermes-agent/pull/2403))
|
| 96 |
+
- **Model catalog updates** — gpt-5.4-mini, gpt-5.4-nano, healer-alpha, haiku-4.5, minimax-m2.7, claude 4.6 at 1M context ([#1913](https://github.com/NousResearch/hermes-agent/pull/1913), [#1915](https://github.com/NousResearch/hermes-agent/pull/1915), [#1900](https://github.com/NousResearch/hermes-agent/pull/1900), [#2155](https://github.com/NousResearch/hermes-agent/pull/2155), [#2474](https://github.com/NousResearch/hermes-agent/pull/2474))
|
| 97 |
+
- **Custom endpoint improvements** — `model.base_url` in config.yaml, `api_mode` override for responses API, allow endpoints without API key, fail fast on missing keys ([#2330](https://github.com/NousResearch/hermes-agent/pull/2330), [#1651](https://github.com/NousResearch/hermes-agent/pull/1651), [#2556](https://github.com/NousResearch/hermes-agent/pull/2556), [#2445](https://github.com/NousResearch/hermes-agent/pull/2445), [#1994](https://github.com/NousResearch/hermes-agent/pull/1994), [#1998](https://github.com/NousResearch/hermes-agent/pull/1998))
|
| 98 |
+
- Inject model and provider into system prompt ([#1929](https://github.com/NousResearch/hermes-agent/pull/1929))
|
| 99 |
+
- Tie `api_mode` to provider config instead of env var ([#1656](https://github.com/NousResearch/hermes-agent/pull/1656))
|
| 100 |
+
- Fix: prevent Anthropic token leaking to third-party `anthropic_messages` providers ([#2389](https://github.com/NousResearch/hermes-agent/pull/2389))
|
| 101 |
+
- Fix: prevent Anthropic fallback from inheriting non-Anthropic `base_url` ([#2388](https://github.com/NousResearch/hermes-agent/pull/2388))
|
| 102 |
+
- Fix: `auxiliary_is_nous` flag never resets — leaked Nous tags to other providers ([#1713](https://github.com/NousResearch/hermes-agent/pull/1713))
|
| 103 |
+
- Fix: Anthropic `tool_choice 'none'` still allowed tool calls ([#1714](https://github.com/NousResearch/hermes-agent/pull/1714))
|
| 104 |
+
- Fix: Mistral parser nested JSON fallback extraction ([#2335](https://github.com/NousResearch/hermes-agent/pull/2335))
|
| 105 |
+
- Fix: MiniMax 401 auth resolved by defaulting to `anthropic_messages` ([#2103](https://github.com/NousResearch/hermes-agent/pull/2103))
|
| 106 |
+
- Fix: case-insensitive model family matching ([#2350](https://github.com/NousResearch/hermes-agent/pull/2350))
|
| 107 |
+
- Fix: ignore placeholder provider keys in activation checks ([#2358](https://github.com/NousResearch/hermes-agent/pull/2358))
|
| 108 |
+
- Fix: Preserve Ollama model:tag colons in context length detection ([#2149](https://github.com/NousResearch/hermes-agent/pull/2149))
|
| 109 |
+
- Fix: recognize Claude Code OAuth credentials in startup gate ([#1663](https://github.com/NousResearch/hermes-agent/pull/1663))
|
| 110 |
+
- Fix: detect Claude Code version dynamically for OAuth user-agent ([#1670](https://github.com/NousResearch/hermes-agent/pull/1670))
|
| 111 |
+
- Fix: OAuth flag stale after refresh/fallback ([#1890](https://github.com/NousResearch/hermes-agent/pull/1890))
|
| 112 |
+
- Fix: auxiliary client skips expired Codex JWT ([#2397](https://github.com/NousResearch/hermes-agent/pull/2397))
|
| 113 |
+
|
| 114 |
+
### Agent Loop
|
| 115 |
+
- **Gateway prompt caching** — Cache AIAgent per session, keep assistant turns, fix session restore ([#2282](https://github.com/NousResearch/hermes-agent/pull/2282), [#2284](https://github.com/NousResearch/hermes-agent/pull/2284), [#2361](https://github.com/NousResearch/hermes-agent/pull/2361))
|
| 116 |
+
- **Context compression overhaul** — Structured summaries, iterative updates, token-budget tail protection, configurable `summary_base_url` ([#2323](https://github.com/NousResearch/hermes-agent/pull/2323), [#1727](https://github.com/NousResearch/hermes-agent/pull/1727), [#2224](https://github.com/NousResearch/hermes-agent/pull/2224))
|
| 117 |
+
- **Pre-call sanitization and post-call tool guardrails** ([#1732](https://github.com/NousResearch/hermes-agent/pull/1732))
|
| 118 |
+
- **Auto-recover** from provider-rejected `tool_choice` by retrying without ([#2174](https://github.com/NousResearch/hermes-agent/pull/2174))
|
| 119 |
+
- **Background memory/skill review** replaces inline nudges ([#2235](https://github.com/NousResearch/hermes-agent/pull/2235))
|
| 120 |
+
- **SOUL.md as primary agent identity** instead of hardcoded default ([#1922](https://github.com/NousResearch/hermes-agent/pull/1922))
|
| 121 |
+
- Fix: prevent silent tool result loss during context compression ([#1993](https://github.com/NousResearch/hermes-agent/pull/1993))
|
| 122 |
+
- Fix: handle empty/null function arguments in tool call recovery ([#2163](https://github.com/NousResearch/hermes-agent/pull/2163))
|
| 123 |
+
- Fix: handle API refusal responses gracefully instead of crashing ([#2156](https://github.com/NousResearch/hermes-agent/pull/2156))
|
| 124 |
+
- Fix: prevent stuck agent loop on malformed tool calls ([#2114](https://github.com/NousResearch/hermes-agent/pull/2114))
|
| 125 |
+
- Fix: return JSON parse error to model instead of dispatching with empty args ([#2342](https://github.com/NousResearch/hermes-agent/pull/2342))
|
| 126 |
+
- Fix: consecutive assistant message merge drops content on mixed types ([#1703](https://github.com/NousResearch/hermes-agent/pull/1703))
|
| 127 |
+
- Fix: message role alternation violations in JSON recovery and error handler ([#1722](https://github.com/NousResearch/hermes-agent/pull/1722))
|
| 128 |
+
- Fix: `compression_attempts` resets each iteration — allowed unlimited compressions ([#1723](https://github.com/NousResearch/hermes-agent/pull/1723))
|
| 129 |
+
- Fix: `length_continue_retries` never resets — later truncations got fewer retries ([#1717](https://github.com/NousResearch/hermes-agent/pull/1717))
|
| 130 |
+
- Fix: compressor summary role violated consecutive-role constraint ([#1720](https://github.com/NousResearch/hermes-agent/pull/1720), [#1743](https://github.com/NousResearch/hermes-agent/pull/1743))
|
| 131 |
+
- Fix: remove hardcoded `gemini-3-flash-preview` as default summary model ([#2464](https://github.com/NousResearch/hermes-agent/pull/2464))
|
| 132 |
+
- Fix: correctly handle empty tool results ([#2201](https://github.com/NousResearch/hermes-agent/pull/2201))
|
| 133 |
+
- Fix: crash on None entry in `tool_calls` list ([#2209](https://github.com/NousResearch/hermes-agent/pull/2209) by @0xbyt4, [#2316](https://github.com/NousResearch/hermes-agent/pull/2316))
|
| 134 |
+
- Fix: per-thread persistent event loops in worker threads ([#2214](https://github.com/NousResearch/hermes-agent/pull/2214) by @jquesnelle)
|
| 135 |
+
- Fix: prevent 'event loop already running' when async tools run in parallel ([#2207](https://github.com/NousResearch/hermes-agent/pull/2207))
|
| 136 |
+
- Fix: strip ANSI at the source — clean terminal output before it reaches the model ([#2115](https://github.com/NousResearch/hermes-agent/pull/2115))
|
| 137 |
+
- Fix: skip top-level `cache_control` on role:tool for OpenRouter ([#2391](https://github.com/NousResearch/hermes-agent/pull/2391))
|
| 138 |
+
- Fix: delegate tool — save parent tool names before child construction mutates global ([#2083](https://github.com/NousResearch/hermes-agent/pull/2083) by @ygd58, [#1894](https://github.com/NousResearch/hermes-agent/pull/1894))
|
| 139 |
+
- Fix: only strip last assistant message if empty string ([#2326](https://github.com/NousResearch/hermes-agent/pull/2326))
|
| 140 |
+
|
| 141 |
+
### Session & Memory
|
| 142 |
+
- **Session search** and management slash commands ([#2198](https://github.com/NousResearch/hermes-agent/pull/2198))
|
| 143 |
+
- **Auto session titles** and `.hermes.md` project config ([#1712](https://github.com/NousResearch/hermes-agent/pull/1712))
|
| 144 |
+
- Fix: concurrent memory writes silently drop entries — added file locking ([#1726](https://github.com/NousResearch/hermes-agent/pull/1726))
|
| 145 |
+
- Fix: search all sources by default in `session_search` ([#1892](https://github.com/NousResearch/hermes-agent/pull/1892))
|
| 146 |
+
- Fix: handle hyphenated FTS5 queries and preserve quoted literals ([#1776](https://github.com/NousResearch/hermes-agent/pull/1776))
|
| 147 |
+
- Fix: skip corrupt lines in `load_transcript` instead of crashing ([#1744](https://github.com/NousResearch/hermes-agent/pull/1744))
|
| 148 |
+
- Fix: normalize session keys to prevent case-sensitive duplicates ([#2157](https://github.com/NousResearch/hermes-agent/pull/2157))
|
| 149 |
+
- Fix: prevent `session_search` crash when no sessions exist ([#2194](https://github.com/NousResearch/hermes-agent/pull/2194))
|
| 150 |
+
- Fix: reset token counters on new session for accurate usage display ([#2101](https://github.com/NousResearch/hermes-agent/pull/2101) by @InB4DevOps)
|
| 151 |
+
- Fix: prevent stale memory overwrites by flush agent ([#2687](https://github.com/NousResearch/hermes-agent/pull/2687))
|
| 152 |
+
- Fix: remove synthetic error message injection, fix session resume after repeated failures ([#2303](https://github.com/NousResearch/hermes-agent/pull/2303))
|
| 153 |
+
- Fix: quiet mode with `--resume` now passes conversation_history ([#2357](https://github.com/NousResearch/hermes-agent/pull/2357))
|
| 154 |
+
- Fix: unify resume logic in batch mode ([#2331](https://github.com/NousResearch/hermes-agent/pull/2331))
|
| 155 |
+
|
| 156 |
+
### Honcho Memory
|
| 157 |
+
- Honcho config fixes and @ context reference integration ([#2343](https://github.com/NousResearch/hermes-agent/pull/2343))
|
| 158 |
+
- Self-hosted / Docker configuration documentation ([#2475](https://github.com/NousResearch/hermes-agent/pull/2475))
|
| 159 |
+
|
| 160 |
+
---
|
| 161 |
+
|
| 162 |
+
## 📱 Messaging Platforms (Gateway)
|
| 163 |
+
|
| 164 |
+
### New Platform Adapters
|
| 165 |
+
- **Signal Messenger** — Full adapter with attachment handling, group message filtering, and Note to Self echo-back protection ([#2206](https://github.com/NousResearch/hermes-agent/pull/2206), [#2400](https://github.com/NousResearch/hermes-agent/pull/2400), [#2297](https://github.com/NousResearch/hermes-agent/pull/2297), [#2156](https://github.com/NousResearch/hermes-agent/pull/2156))
|
| 166 |
+
- **DingTalk** — Adapter with gateway wiring and setup docs ([#1685](https://github.com/NousResearch/hermes-agent/pull/1685), [#1690](https://github.com/NousResearch/hermes-agent/pull/1690), [#1692](https://github.com/NousResearch/hermes-agent/pull/1692))
|
| 167 |
+
- **SMS (Twilio)** ([#1688](https://github.com/NousResearch/hermes-agent/pull/1688))
|
| 168 |
+
- **Mattermost** — With @-mention-only channel filter ([#1683](https://github.com/NousResearch/hermes-agent/pull/1683), [#2443](https://github.com/NousResearch/hermes-agent/pull/2443))
|
| 169 |
+
- **Matrix** — With vision support and image caching ([#1683](https://github.com/NousResearch/hermes-agent/pull/1683), [#2520](https://github.com/NousResearch/hermes-agent/pull/2520))
|
| 170 |
+
- **Webhook** — Platform adapter for external event triggers ([#2166](https://github.com/NousResearch/hermes-agent/pull/2166))
|
| 171 |
+
- **OpenAI-compatible API server** — `/v1/chat/completions` endpoint with `/api/jobs` cron management ([#1756](https://github.com/NousResearch/hermes-agent/pull/1756), [#2450](https://github.com/NousResearch/hermes-agent/pull/2450), [#2456](https://github.com/NousResearch/hermes-agent/pull/2456))
|
| 172 |
+
|
| 173 |
+
### Telegram Improvements
|
| 174 |
+
- MarkdownV2 support — strikethrough, spoiler, blockquotes, escape parentheses/braces/backslashes/backticks ([#2199](https://github.com/NousResearch/hermes-agent/pull/2199), [#2200](https://github.com/NousResearch/hermes-agent/pull/2200) by @llbn, [#2386](https://github.com/NousResearch/hermes-agent/pull/2386))
|
| 175 |
+
- Auto-detect HTML tags and use `parse_mode=HTML` ([#1709](https://github.com/NousResearch/hermes-agent/pull/1709))
|
| 176 |
+
- Telegram group vision support + thread-based sessions ([#2153](https://github.com/NousResearch/hermes-agent/pull/2153))
|
| 177 |
+
- Auto-reconnect polling after network interruption ([#2517](https://github.com/NousResearch/hermes-agent/pull/2517))
|
| 178 |
+
- Aggregate split text messages before dispatching ([#1674](https://github.com/NousResearch/hermes-agent/pull/1674))
|
| 179 |
+
- Fix: streaming config bridge, not-modified, flood control ([#1782](https://github.com/NousResearch/hermes-agent/pull/1782), [#1783](https://github.com/NousResearch/hermes-agent/pull/1783))
|
| 180 |
+
- Fix: edited_message event crashes ([#2074](https://github.com/NousResearch/hermes-agent/pull/2074))
|
| 181 |
+
- Fix: retry 409 polling conflicts before giving up ([#2312](https://github.com/NousResearch/hermes-agent/pull/2312))
|
| 182 |
+
- Fix: topic delivery via `platform:chat_id:thread_id` format ([#2455](https://github.com/NousResearch/hermes-agent/pull/2455))
|
| 183 |
+
|
| 184 |
+
### Discord Improvements
|
| 185 |
+
- Document caching and text-file injection ([#2503](https://github.com/NousResearch/hermes-agent/pull/2503))
|
| 186 |
+
- Persistent typing indicator for DMs ([#2468](https://github.com/NousResearch/hermes-agent/pull/2468))
|
| 187 |
+
- Discord DM vision — inline images + attachment analysis ([#2186](https://github.com/NousResearch/hermes-agent/pull/2186))
|
| 188 |
+
- Persist thread participation across gateway restarts ([#1661](https://github.com/NousResearch/hermes-agent/pull/1661))
|
| 189 |
+
- Fix: gateway crash on non-ASCII guild names ([#2302](https://github.com/NousResearch/hermes-agent/pull/2302))
|
| 190 |
+
- Fix: thread permission errors ([#2073](https://github.com/NousResearch/hermes-agent/pull/2073))
|
| 191 |
+
- Fix: slash event routing in threads ([#2460](https://github.com/NousResearch/hermes-agent/pull/2460))
|
| 192 |
+
- Fix: remove bugged followup messages + `/ask` command ([#1836](https://github.com/NousResearch/hermes-agent/pull/1836))
|
| 193 |
+
- Fix: graceful WebSocket reconnection ([#2127](https://github.com/NousResearch/hermes-agent/pull/2127))
|
| 194 |
+
- Fix: voice channel TTS when streaming enabled ([#2322](https://github.com/NousResearch/hermes-agent/pull/2322))
|
| 195 |
+
|
| 196 |
+
### WhatsApp & Other Adapters
|
| 197 |
+
- WhatsApp: outbound `send_message` routing ([#1769](https://github.com/NousResearch/hermes-agent/pull/1769) by @sai-samarth), LID format self-chat ([#1667](https://github.com/NousResearch/hermes-agent/pull/1667)), `reply_prefix` config fix ([#1923](https://github.com/NousResearch/hermes-agent/pull/1923)), restart on bridge child exit ([#2334](https://github.com/NousResearch/hermes-agent/pull/2334)), image/bridge improvements ([#2181](https://github.com/NousResearch/hermes-agent/pull/2181))
|
| 198 |
+
- Matrix: correct `reply_to_message_id` parameter ([#1895](https://github.com/NousResearch/hermes-agent/pull/1895)), bare media types fix ([#1736](https://github.com/NousResearch/hermes-agent/pull/1736))
|
| 199 |
+
- Mattermost: MIME types for media attachments ([#2329](https://github.com/NousResearch/hermes-agent/pull/2329))
|
| 200 |
+
|
| 201 |
+
### Gateway Core
|
| 202 |
+
- **Auto-reconnect** failed platforms with exponential backoff ([#2584](https://github.com/NousResearch/hermes-agent/pull/2584))
|
| 203 |
+
- **Notify users when session auto-resets** ([#2519](https://github.com/NousResearch/hermes-agent/pull/2519))
|
| 204 |
+
- **Reply-to message context** for out-of-session replies ([#1662](https://github.com/NousResearch/hermes-agent/pull/1662))
|
| 205 |
+
- **Ignore unauthorized DMs** config option ([#1919](https://github.com/NousResearch/hermes-agent/pull/1919))
|
| 206 |
+
- Fix: `/reset` in thread-mode resets global session instead of thread ([#2254](https://github.com/NousResearch/hermes-agent/pull/2254))
|
| 207 |
+
- Fix: deliver MEDIA: files after streaming responses ([#2382](https://github.com/NousResearch/hermes-agent/pull/2382))
|
| 208 |
+
- Fix: cap interrupt recursion depth to prevent resource exhaustion ([#1659](https://github.com/NousResearch/hermes-agent/pull/1659))
|
| 209 |
+
- Fix: detect stopped processes and release stale locks on `--replace` ([#2406](https://github.com/NousResearch/hermes-agent/pull/2406), [#1908](https://github.com/NousResearch/hermes-agent/pull/1908))
|
| 210 |
+
- Fix: PID-based wait with force-kill for gateway restart ([#1902](https://github.com/NousResearch/hermes-agent/pull/1902))
|
| 211 |
+
- Fix: prevent `--replace` mode from killing the caller process ([#2185](https://github.com/NousResearch/hermes-agent/pull/2185))
|
| 212 |
+
- Fix: `/model` shows active fallback model instead of config default ([#1660](https://github.com/NousResearch/hermes-agent/pull/1660))
|
| 213 |
+
- Fix: `/title` command fails when session doesn't exist in SQLite yet ([#2379](https://github.com/NousResearch/hermes-agent/pull/2379) by @ten-jampa)
|
| 214 |
+
- Fix: process `/queue`'d messages after agent completion ([#2469](https://github.com/NousResearch/hermes-agent/pull/2469))
|
| 215 |
+
- Fix: strip orphaned `tool_results` + let `/reset` bypass running agent ([#2180](https://github.com/NousResearch/hermes-agent/pull/2180))
|
| 216 |
+
- Fix: prevent agents from starting gateway outside systemd management ([#2617](https://github.com/NousResearch/hermes-agent/pull/2617))
|
| 217 |
+
- Fix: prevent systemd restart storm on gateway connection failure ([#2327](https://github.com/NousResearch/hermes-agent/pull/2327))
|
| 218 |
+
- Fix: include resolved node path in systemd unit ([#1767](https://github.com/NousResearch/hermes-agent/pull/1767) by @sai-samarth)
|
| 219 |
+
- Fix: send error details to user in gateway outer exception handler ([#1966](https://github.com/NousResearch/hermes-agent/pull/1966))
|
| 220 |
+
- Fix: improve error handling for 429 usage limits and 500 context overflow ([#1839](https://github.com/NousResearch/hermes-agent/pull/1839))
|
| 221 |
+
- Fix: add all missing platform allowlist env vars to startup warning check ([#2628](https://github.com/NousResearch/hermes-agent/pull/2628))
|
| 222 |
+
- Fix: media delivery fails for file paths containing spaces ([#2621](https://github.com/NousResearch/hermes-agent/pull/2621))
|
| 223 |
+
- Fix: duplicate session-key collision in multi-platform gateway ([#2171](https://github.com/NousResearch/hermes-agent/pull/2171))
|
| 224 |
+
- Fix: Matrix and Mattermost never report as connected ([#1711](https://github.com/NousResearch/hermes-agent/pull/1711))
|
| 225 |
+
- Fix: PII redaction config never read — missing yaml import ([#1701](https://github.com/NousResearch/hermes-agent/pull/1701))
|
| 226 |
+
- Fix: NameError on skill slash commands ([#1697](https://github.com/NousResearch/hermes-agent/pull/1697))
|
| 227 |
+
- Fix: persist watcher metadata in checkpoint for crash recovery ([#1706](https://github.com/NousResearch/hermes-agent/pull/1706))
|
| 228 |
+
- Fix: pass `message_thread_id` in send_image_file, send_document, send_video ([#2339](https://github.com/NousResearch/hermes-agent/pull/2339))
|
| 229 |
+
- Fix: media-group aggregation on rapid successive photo messages ([#2160](https://github.com/NousResearch/hermes-agent/pull/2160))
|
| 230 |
+
|
| 231 |
+
---
|
| 232 |
+
|
| 233 |
+
## 🔧 Tool System
|
| 234 |
+
|
| 235 |
+
### MCP Enhancements
|
| 236 |
+
- **MCP server management CLI** + OAuth 2.1 PKCE auth ([#2465](https://github.com/NousResearch/hermes-agent/pull/2465))
|
| 237 |
+
- **Expose MCP servers as standalone toolsets** ([#1907](https://github.com/NousResearch/hermes-agent/pull/1907))
|
| 238 |
+
- **Interactive MCP tool configuration** in `hermes tools` ([#1694](https://github.com/NousResearch/hermes-agent/pull/1694))
|
| 239 |
+
- Fix: MCP-OAuth port mismatch, path traversal, and shared handler state ([#2552](https://github.com/NousResearch/hermes-agent/pull/2552))
|
| 240 |
+
- Fix: preserve MCP tool registrations across session resets ([#2124](https://github.com/NousResearch/hermes-agent/pull/2124))
|
| 241 |
+
- Fix: concurrent file access crash + duplicate MCP registration ([#2154](https://github.com/NousResearch/hermes-agent/pull/2154))
|
| 242 |
+
- Fix: normalise MCP schemas + expand session list columns ([#2102](https://github.com/NousResearch/hermes-agent/pull/2102))
|
| 243 |
+
- Fix: `tool_choice` `mcp_` prefix handling ([#1775](https://github.com/NousResearch/hermes-agent/pull/1775))
|
| 244 |
+
|
| 245 |
+
### Web Tool Backends
|
| 246 |
+
- **Tavily** as web search/extract/crawl backend ([#1731](https://github.com/NousResearch/hermes-agent/pull/1731))
|
| 247 |
+
- **Parallel** as alternative web search/extract backend ([#1696](https://github.com/NousResearch/hermes-agent/pull/1696))
|
| 248 |
+
- **Configurable web backend** — Firecrawl/BeautifulSoup/Playwright selection ([#2256](https://github.com/NousResearch/hermes-agent/pull/2256))
|
| 249 |
+
- Fix: whitespace-only env vars bypass web backend detection ([#2341](https://github.com/NousResearch/hermes-agent/pull/2341))
|
| 250 |
+
|
| 251 |
+
### New Tools
|
| 252 |
+
- **IMAP email** reading and sending ([#2173](https://github.com/NousResearch/hermes-agent/pull/2173))
|
| 253 |
+
- **STT (speech-to-text)** tool using Whisper API ([#2072](https://github.com/NousResearch/hermes-agent/pull/2072))
|
| 254 |
+
- **Route-aware pricing estimates** ([#1695](https://github.com/NousResearch/hermes-agent/pull/1695))
|
| 255 |
+
|
| 256 |
+
### Tool Improvements
|
| 257 |
+
- TTS: `base_url` support for OpenAI TTS provider ([#2064](https://github.com/NousResearch/hermes-agent/pull/2064) by @hanai)
|
| 258 |
+
- Vision: configurable timeout, tilde expansion in file paths, DM vision with multi-image and base64 fallback ([#2480](https://github.com/NousResearch/hermes-agent/pull/2480), [#2585](https://github.com/NousResearch/hermes-agent/pull/2585), [#2211](https://github.com/NousResearch/hermes-agent/pull/2211))
|
| 259 |
+
- Browser: race condition fix in session creation ([#1721](https://github.com/NousResearch/hermes-agent/pull/1721)), TypeError on unexpected LLM params ([#1735](https://github.com/NousResearch/hermes-agent/pull/1735))
|
| 260 |
+
- File tools: strip ANSI escape codes from write_file and patch content ([#2532](https://github.com/NousResearch/hermes-agent/pull/2532)), include pagination args in repeated search key ([#1824](https://github.com/NousResearch/hermes-agent/pull/1824) by @cutepawss), improve fuzzy matching accuracy + position calculation refactor ([#2096](https://github.com/NousResearch/hermes-agent/pull/2096), [#1681](https://github.com/NousResearch/hermes-agent/pull/1681))
|
| 261 |
+
- Code execution: resource leak and double socket close fix ([#2381](https://github.com/NousResearch/hermes-agent/pull/2381))
|
| 262 |
+
- Delegate: thread safety for concurrent subagent delegation ([#1672](https://github.com/NousResearch/hermes-agent/pull/1672)), preserve parent agent's tool list after delegation ([#1778](https://github.com/NousResearch/hermes-agent/pull/1778))
|
| 263 |
+
- Fix: make concurrent tool batching path-aware for file mutations ([#1914](https://github.com/NousResearch/hermes-agent/pull/1914))
|
| 264 |
+
- Fix: chunk long messages in `send_message_tool` before platform dispatch ([#1646](https://github.com/NousResearch/hermes-agent/pull/1646))
|
| 265 |
+
- Fix: add missing 'messaging' toolset ([#1718](https://github.com/NousResearch/hermes-agent/pull/1718))
|
| 266 |
+
- Fix: prevent unavailable tool names from leaking into model schemas ([#2072](https://github.com/NousResearch/hermes-agent/pull/2072))
|
| 267 |
+
- Fix: pass visited set by reference to prevent diamond dependency duplication ([#2311](https://github.com/NousResearch/hermes-agent/pull/2311))
|
| 268 |
+
- Fix: Daytona sandbox lookup migrated from `find_one` to `get/list` ([#2063](https://github.com/NousResearch/hermes-agent/pull/2063) by @rovle)
|
| 269 |
+
|
| 270 |
+
---
|
| 271 |
+
|
| 272 |
+
## 🧩 Skills Ecosystem
|
| 273 |
+
|
| 274 |
+
### Skills System Improvements
|
| 275 |
+
- **Agent-created skills** — Caution-level findings allowed, dangerous skills ask instead of block ([#1840](https://github.com/NousResearch/hermes-agent/pull/1840), [#2446](https://github.com/NousResearch/hermes-agent/pull/2446))
|
| 276 |
+
- **`--yes` flag** to bypass confirmation in `/skills install` and uninstall ([#1647](https://github.com/NousResearch/hermes-agent/pull/1647))
|
| 277 |
+
- **Disabled skills respected** across banner, system prompt, and slash commands ([#1897](https://github.com/NousResearch/hermes-agent/pull/1897))
|
| 278 |
+
- Fix: skills custom_tools import crash + sandbox file_tools integration ([#2239](https://github.com/NousResearch/hermes-agent/pull/2239))
|
| 279 |
+
- Fix: agent-created skills with pip requirements crash on install ([#2145](https://github.com/NousResearch/hermes-agent/pull/2145))
|
| 280 |
+
- Fix: race condition in `Skills.__init__` when `hub.yaml` missing ([#2242](https://github.com/NousResearch/hermes-agent/pull/2242))
|
| 281 |
+
- Fix: validate skill metadata before install and block duplicates ([#2241](https://github.com/NousResearch/hermes-agent/pull/2241))
|
| 282 |
+
- Fix: skills hub inspect/resolve — 4 bugs in inspect, redirects, discovery, tap list ([#2447](https://github.com/NousResearch/hermes-agent/pull/2447))
|
| 283 |
+
- Fix: agent-created skills keep working after session reset ([#2121](https://github.com/NousResearch/hermes-agent/pull/2121))
|
| 284 |
+
|
| 285 |
+
### New Skills
|
| 286 |
+
- **OCR-and-documents** — PDF/DOCX/XLS/PPTX/image OCR with optional GPU ([#2236](https://github.com/NousResearch/hermes-agent/pull/2236), [#2461](https://github.com/NousResearch/hermes-agent/pull/2461))
|
| 287 |
+
- **Huggingface-hub** bundled skill ([#1921](https://github.com/NousResearch/hermes-agent/pull/1921))
|
| 288 |
+
- **Sherlock OSINT** username search ([#1671](https://github.com/NousResearch/hermes-agent/pull/1671))
|
| 289 |
+
- **Meme-generation** — Image generator with Pillow ([#2344](https://github.com/NousResearch/hermes-agent/pull/2344))
|
| 290 |
+
- **Bioinformatics** gateway skill — index to 400+ bio skills ([#2387](https://github.com/NousResearch/hermes-agent/pull/2387))
|
| 291 |
+
- **Inference.sh** skill (terminal-based) ([#1686](https://github.com/NousResearch/hermes-agent/pull/1686))
|
| 292 |
+
- **Base blockchain** optional skill ([#1643](https://github.com/NousResearch/hermes-agent/pull/1643))
|
| 293 |
+
- **3D-model-viewer** optional skill ([#2226](https://github.com/NousResearch/hermes-agent/pull/2226))
|
| 294 |
+
- **FastMCP** optional skill ([#2113](https://github.com/NousResearch/hermes-agent/pull/2113))
|
| 295 |
+
- **Hermes-agent-setup** skill ([#1905](https://github.com/NousResearch/hermes-agent/pull/1905))
|
| 296 |
+
|
| 297 |
+
---
|
| 298 |
+
|
| 299 |
+
## 🔌 Plugin System Enhancements
|
| 300 |
+
|
| 301 |
+
- **TUI extension hooks** — Build custom CLIs on top of Hermes ([#2333](https://github.com/NousResearch/hermes-agent/pull/2333))
|
| 302 |
+
- **`hermes plugins install/remove/list`** commands ([#2337](https://github.com/NousResearch/hermes-agent/pull/2337))
|
| 303 |
+
- **Slash command registration** for plugins ([#2359](https://github.com/NousResearch/hermes-agent/pull/2359))
|
| 304 |
+
- **`session:end` lifecycle event** hook ([#1725](https://github.com/NousResearch/hermes-agent/pull/1725))
|
| 305 |
+
- Fix: require opt-in for project plugin discovery ([#2215](https://github.com/NousResearch/hermes-agent/pull/2215))
|
| 306 |
+
|
| 307 |
+
---
|
| 308 |
+
|
| 309 |
+
## 🔒 Security & Reliability
|
| 310 |
+
|
| 311 |
+
### Security
|
| 312 |
+
- **SSRF protection** for vision_tools and web_tools ([#2679](https://github.com/NousResearch/hermes-agent/pull/2679))
|
| 313 |
+
- **Shell injection prevention** in `_expand_path` via `~user` path suffix ([#2685](https://github.com/NousResearch/hermes-agent/pull/2685))
|
| 314 |
+
- **Block untrusted browser-origin** API server access ([#2451](https://github.com/NousResearch/hermes-agent/pull/2451))
|
| 315 |
+
- **Block sandbox backend creds** from subprocess env ([#1658](https://github.com/NousResearch/hermes-agent/pull/1658))
|
| 316 |
+
- **Block @ references** from reading secrets outside workspace ([#2601](https://github.com/NousResearch/hermes-agent/pull/2601) by @Gutslabs)
|
| 317 |
+
- **Malicious code pattern pre-exec scanner** for terminal_tool ([#2245](https://github.com/NousResearch/hermes-agent/pull/2245))
|
| 318 |
+
- **Harden terminal safety** and sandbox file writes ([#1653](https://github.com/NousResearch/hermes-agent/pull/1653))
|
| 319 |
+
- **PKCE verifier leak** fix + OAuth refresh Content-Type ([#1775](https://github.com/NousResearch/hermes-agent/pull/1775))
|
| 320 |
+
- **Eliminate SQL string formatting** in `execute()` calls ([#2061](https://github.com/NousResearch/hermes-agent/pull/2061) by @dusterbloom)
|
| 321 |
+
- **Harden jobs API** — input limits, field whitelist, startup check ([#2456](https://github.com/NousResearch/hermes-agent/pull/2456))
|
| 322 |
+
|
| 323 |
+
### Reliability
|
| 324 |
+
- Thread locks on 4 SessionDB methods ([#1704](https://github.com/NousResearch/hermes-agent/pull/1704))
|
| 325 |
+
- File locking for concurrent memory writes ([#1726](https://github.com/NousResearch/hermes-agent/pull/1726))
|
| 326 |
+
- Handle OpenRouter errors gracefully ([#2112](https://github.com/NousResearch/hermes-agent/pull/2112))
|
| 327 |
+
- Guard print() calls against OSError ([#1668](https://github.com/NousResearch/hermes-agent/pull/1668))
|
| 328 |
+
- Safely handle non-string inputs in redacting formatter ([#2392](https://github.com/NousResearch/hermes-agent/pull/2392), [#1700](https://github.com/NousResearch/hermes-agent/pull/1700))
|
| 329 |
+
- ACP: preserve session provider on model switch, persist sessions to disk ([#2380](https://github.com/NousResearch/hermes-agent/pull/2380), [#2071](https://github.com/NousResearch/hermes-agent/pull/2071))
|
| 330 |
+
- API server: persist ResponseStore to SQLite across restarts ([#2472](https://github.com/NousResearch/hermes-agent/pull/2472))
|
| 331 |
+
- Fix: `fetch_nous_models` always TypeError from positional args ([#1699](https://github.com/NousResearch/hermes-agent/pull/1699))
|
| 332 |
+
- Fix: resolve merge conflict markers in cli.py breaking startup ([#2347](https://github.com/NousResearch/hermes-agent/pull/2347))
|
| 333 |
+
- Fix: `minisweagent_path.py` missing from wheel ([#2098](https://github.com/NousResearch/hermes-agent/pull/2098) by @JiwaniZakir)
|
| 334 |
+
|
| 335 |
+
### Cron System
|
| 336 |
+
- **`[SILENT]` response** — cron agents can suppress delivery ([#1833](https://github.com/NousResearch/hermes-agent/pull/1833))
|
| 337 |
+
- **Scale missed-job grace window** with schedule frequency ([#2449](https://github.com/NousResearch/hermes-agent/pull/2449))
|
| 338 |
+
- **Recover recent one-shot jobs** ([#1918](https://github.com/NousResearch/hermes-agent/pull/1918))
|
| 339 |
+
- Fix: normalize `repeat<=0` to None — jobs deleted after first run when LLM passes -1 ([#2612](https://github.com/NousResearch/hermes-agent/pull/2612) by @Mibayy)
|
| 340 |
+
- Fix: Matrix added to scheduler delivery platform_map ([#2167](https://github.com/NousResearch/hermes-agent/pull/2167) by @buntingszn)
|
| 341 |
+
- Fix: naive ISO timestamps without timezone — jobs fire at wrong time ([#1729](https://github.com/NousResearch/hermes-agent/pull/1729))
|
| 342 |
+
- Fix: `get_due_jobs` reads `jobs.json` twice — race condition ([#1716](https://github.com/NousResearch/hermes-agent/pull/1716))
|
| 343 |
+
- Fix: silent jobs return empty response for delivery skip ([#2442](https://github.com/NousResearch/hermes-agent/pull/2442))
|
| 344 |
+
- Fix: stop injecting cron outputs into gateway session history ([#2313](https://github.com/NousResearch/hermes-agent/pull/2313))
|
| 345 |
+
- Fix: close abandoned coroutine when `asyncio.run()` raises RuntimeError ([#2317](https://github.com/NousResearch/hermes-agent/pull/2317))
|
| 346 |
+
|
| 347 |
+
---
|
| 348 |
+
|
| 349 |
+
## 🧪 Testing
|
| 350 |
+
|
| 351 |
+
- Resolve all consistently failing tests ([#2488](https://github.com/NousResearch/hermes-agent/pull/2488))
|
| 352 |
+
- Replace `FakePath` with `monkeypatch` for Python 3.12 compat ([#2444](https://github.com/NousResearch/hermes-agent/pull/2444))
|
| 353 |
+
- Align Hermes setup and full-suite expectations ([#1710](https://github.com/NousResearch/hermes-agent/pull/1710))
|
| 354 |
+
|
| 355 |
+
---
|
| 356 |
+
|
| 357 |
+
## 📚 Documentation
|
| 358 |
+
|
| 359 |
+
- Comprehensive docs update for recent features ([#1693](https://github.com/NousResearch/hermes-agent/pull/1693), [#2183](https://github.com/NousResearch/hermes-agent/pull/2183))
|
| 360 |
+
- Alibaba Cloud and DingTalk setup guides ([#1687](https://github.com/NousResearch/hermes-agent/pull/1687), [#1692](https://github.com/NousResearch/hermes-agent/pull/1692))
|
| 361 |
+
- Detailed skills documentation ([#2244](https://github.com/NousResearch/hermes-agent/pull/2244))
|
| 362 |
+
- Honcho self-hosted / Docker configuration ([#2475](https://github.com/NousResearch/hermes-agent/pull/2475))
|
| 363 |
+
- Context length detection FAQ and quickstart references ([#2179](https://github.com/NousResearch/hermes-agent/pull/2179))
|
| 364 |
+
- Fix docs inconsistencies across reference and user guides ([#1995](https://github.com/NousResearch/hermes-agent/pull/1995))
|
| 365 |
+
- Fix MCP install commands — use uv, not bare pip ([#1909](https://github.com/NousResearch/hermes-agent/pull/1909))
|
| 366 |
+
- Replace ASCII diagrams with Mermaid/lists ([#2402](https://github.com/NousResearch/hermes-agent/pull/2402))
|
| 367 |
+
- Gemini OAuth provider implementation plan ([#2467](https://github.com/NousResearch/hermes-agent/pull/2467))
|
| 368 |
+
- Discord Server Members Intent marked as required ([#2330](https://github.com/NousResearch/hermes-agent/pull/2330))
|
| 369 |
+
- Fix MDX build error in api-server.md ([#1787](https://github.com/NousResearch/hermes-agent/pull/1787))
|
| 370 |
+
- Align venv path to match installer ([#2114](https://github.com/NousResearch/hermes-agent/pull/2114))
|
| 371 |
+
- New skills added to hub index ([#2281](https://github.com/NousResearch/hermes-agent/pull/2281))
|
| 372 |
+
|
| 373 |
+
---
|
| 374 |
+
|
| 375 |
+
## 👥 Contributors
|
| 376 |
+
|
| 377 |
+
### Core
|
| 378 |
+
- **@teknium1** (Teknium) — 280 PRs
|
| 379 |
+
|
| 380 |
+
### Community Contributors
|
| 381 |
+
- **@mchzimm** (to_the_max) — GitHub Copilot provider integration ([#1879](https://github.com/NousResearch/hermes-agent/pull/1879))
|
| 382 |
+
- **@jquesnelle** (Jeffrey Quesnelle) — Per-thread persistent event loops fix ([#2214](https://github.com/NousResearch/hermes-agent/pull/2214))
|
| 383 |
+
- **@llbn** (lbn) — Telegram MarkdownV2 strikethrough, spoiler, blockquotes, and escape fixes ([#2199](https://github.com/NousResearch/hermes-agent/pull/2199), [#2200](https://github.com/NousResearch/hermes-agent/pull/2200))
|
| 384 |
+
- **@dusterbloom** — SQL injection prevention + local server context window querying ([#2061](https://github.com/NousResearch/hermes-agent/pull/2061), [#2091](https://github.com/NousResearch/hermes-agent/pull/2091))
|
| 385 |
+
- **@0xbyt4** — Anthropic tool_calls None guard + OpenCode-Go provider config fix ([#2209](https://github.com/NousResearch/hermes-agent/pull/2209), [#2393](https://github.com/NousResearch/hermes-agent/pull/2393))
|
| 386 |
+
- **@sai-samarth** (Saisamarth) — WhatsApp send_message routing + systemd node path ([#1769](https://github.com/NousResearch/hermes-agent/pull/1769), [#1767](https://github.com/NousResearch/hermes-agent/pull/1767))
|
| 387 |
+
- **@Gutslabs** (Guts) — Block @ references from reading secrets ([#2601](https://github.com/NousResearch/hermes-agent/pull/2601))
|
| 388 |
+
- **@Mibayy** (Mibay) — Cron job repeat normalization ([#2612](https://github.com/NousResearch/hermes-agent/pull/2612))
|
| 389 |
+
- **@ten-jampa** (Tenzin Jampa) — Gateway /title command fix ([#2379](https://github.com/NousResearch/hermes-agent/pull/2379))
|
| 390 |
+
- **@cutepawss** (lila) — File tools search pagination fix ([#1824](https://github.com/NousResearch/hermes-agent/pull/1824))
|
| 391 |
+
- **@hanai** (Hanai) — OpenAI TTS base_url support ([#2064](https://github.com/NousResearch/hermes-agent/pull/2064))
|
| 392 |
+
- **@rovle** (Lovre Pešut) — Daytona sandbox API migration ([#2063](https://github.com/NousResearch/hermes-agent/pull/2063))
|
| 393 |
+
- **@buntingszn** (bunting szn) — Matrix cron delivery support ([#2167](https://github.com/NousResearch/hermes-agent/pull/2167))
|
| 394 |
+
- **@InB4DevOps** — Token counter reset on new session ([#2101](https://github.com/NousResearch/hermes-agent/pull/2101))
|
| 395 |
+
- **@JiwaniZakir** (Zakir Jiwani) — Missing file in wheel fix ([#2098](https://github.com/NousResearch/hermes-agent/pull/2098))
|
| 396 |
+
- **@ygd58** (buray) — Delegate tool parent tool names fix ([#2083](https://github.com/NousResearch/hermes-agent/pull/2083))
|
| 397 |
+
|
| 398 |
+
---
|
| 399 |
+
|
| 400 |
+
**Full Changelog**: [v2026.3.17...v2026.3.23](https://github.com/NousResearch/hermes-agent/compare/v2026.3.17...v2026.3.23)
|
RELEASE_v0.5.0.md
ADDED
|
@@ -0,0 +1,348 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Hermes Agent v0.5.0 (v2026.3.28)
|
| 2 |
+
|
| 3 |
+
**Release Date:** March 28, 2026
|
| 4 |
+
|
| 5 |
+
> The hardening release — Hugging Face provider, /model command overhaul, Telegram Private Chat Topics, native Modal SDK, plugin lifecycle hooks, tool-use enforcement for GPT models, Nix flake, 50+ security and reliability fixes, and a comprehensive supply chain audit.
|
| 6 |
+
|
| 7 |
+
---
|
| 8 |
+
|
| 9 |
+
## ✨ Highlights
|
| 10 |
+
|
| 11 |
+
- **Nous Portal now supports 400+ models** — The Nous Research inference portal has expanded dramatically, giving Hermes Agent users access to over 400 models through a single provider endpoint
|
| 12 |
+
|
| 13 |
+
- **Hugging Face as a first-class inference provider** — Full integration with HF Inference API including curated agentic model picker that maps to OpenRouter analogues, live `/models` endpoint probe, and setup wizard flow ([#3419](https://github.com/NousResearch/hermes-agent/pull/3419), [#3440](https://github.com/NousResearch/hermes-agent/pull/3440))
|
| 14 |
+
|
| 15 |
+
- **Telegram Private Chat Topics** — Project-based conversations with functional skill binding per topic, enabling isolated workflows within a single Telegram chat ([#3163](https://github.com/NousResearch/hermes-agent/pull/3163))
|
| 16 |
+
|
| 17 |
+
- **Native Modal SDK backend** — Replaced swe-rex dependency with native Modal SDK (`Sandbox.create.aio` + `exec.aio`), eliminating tunnels and simplifying the Modal terminal backend ([#3538](https://github.com/NousResearch/hermes-agent/pull/3538))
|
| 18 |
+
|
| 19 |
+
- **Plugin lifecycle hooks activated** — `pre_llm_call`, `post_llm_call`, `on_session_start`, and `on_session_end` hooks now fire in the agent loop and CLI/gateway, completing the plugin hook system ([#3542](https://github.com/NousResearch/hermes-agent/pull/3542))
|
| 20 |
+
|
| 21 |
+
- **Improved OpenAI Model Reliability** — Added `GPT_TOOL_USE_GUIDANCE` to prevent GPT models from describing intended actions instead of making tool calls, plus automatic stripping of stale budget warnings from conversation history that caused models to avoid tools across turns ([#3528](https://github.com/NousResearch/hermes-agent/pull/3528))
|
| 22 |
+
|
| 23 |
+
- **Nix flake** — Full uv2nix build, NixOS module with persistent container mode, auto-generated config keys from Python source, and suffix PATHs for agent-friendliness ([#20](https://github.com/NousResearch/hermes-agent/pull/20), [#3274](https://github.com/NousResearch/hermes-agent/pull/3274), [#3061](https://github.com/NousResearch/hermes-agent/pull/3061)) by @alt-glitch
|
| 24 |
+
|
| 25 |
+
- **Supply chain hardening** — Removed compromised `litellm` dependency, pinned all dependency version ranges, regenerated `uv.lock` with hashes, added CI workflow scanning PRs for supply chain attack patterns, and bumped deps to fix CVEs ([#2796](https://github.com/NousResearch/hermes-agent/pull/2796), [#2810](https://github.com/NousResearch/hermes-agent/pull/2810), [#2812](https://github.com/NousResearch/hermes-agent/pull/2812), [#2816](https://github.com/NousResearch/hermes-agent/pull/2816), [#3073](https://github.com/NousResearch/hermes-agent/pull/3073))
|
| 26 |
+
|
| 27 |
+
- **Anthropic output limits fix** — Replaced hardcoded 16K `max_tokens` with per-model native output limits (128K for Opus 4.6, 64K for Sonnet 4.6), fixing "Response truncated" and thinking-budget exhaustion on direct Anthropic API ([#3426](https://github.com/NousResearch/hermes-agent/pull/3426), [#3444](https://github.com/NousResearch/hermes-agent/pull/3444))
|
| 28 |
+
|
| 29 |
+
---
|
| 30 |
+
|
| 31 |
+
## 🏗️ Core Agent & Architecture
|
| 32 |
+
|
| 33 |
+
### New Provider: Hugging Face
|
| 34 |
+
- First-class Hugging Face Inference API integration with auth, setup wizard, and model picker ([#3419](https://github.com/NousResearch/hermes-agent/pull/3419))
|
| 35 |
+
- Curated model list mapping OpenRouter agentic defaults to HF equivalents — providers with 8+ curated models skip live `/models` probe for speed ([#3440](https://github.com/NousResearch/hermes-agent/pull/3440))
|
| 36 |
+
- Added glm-5-turbo to Z.AI provider model list ([#3095](https://github.com/NousResearch/hermes-agent/pull/3095))
|
| 37 |
+
|
| 38 |
+
### Provider & Model Improvements
|
| 39 |
+
- `/model` command overhaul — extracted shared `switch_model()` pipeline for CLI and gateway, custom endpoint support, provider-aware routing ([#2795](https://github.com/NousResearch/hermes-agent/pull/2795), [#2799](https://github.com/NousResearch/hermes-agent/pull/2799))
|
| 40 |
+
- Removed `/model` slash command from CLI and gateway in favor of `hermes model` subcommand ([#3080](https://github.com/NousResearch/hermes-agent/pull/3080))
|
| 41 |
+
- Preserve `custom` provider instead of silently remapping to `openrouter` ([#2792](https://github.com/NousResearch/hermes-agent/pull/2792))
|
| 42 |
+
- Read root-level `provider` and `base_url` from config.yaml into model config ([#3112](https://github.com/NousResearch/hermes-agent/pull/3112))
|
| 43 |
+
- Align Nous Portal model slugs with OpenRouter naming ([#3253](https://github.com/NousResearch/hermes-agent/pull/3253))
|
| 44 |
+
- Fix Alibaba provider default endpoint and model list ([#3484](https://github.com/NousResearch/hermes-agent/pull/3484))
|
| 45 |
+
- Allow MiniMax users to override `/v1` → `/anthropic` auto-correction ([#3553](https://github.com/NousResearch/hermes-agent/pull/3553))
|
| 46 |
+
- Migrate OAuth token refresh to `platform.claude.com` with fallback ([#3246](https://github.com/NousResearch/hermes-agent/pull/3246))
|
| 47 |
+
|
| 48 |
+
### Agent Loop & Conversation
|
| 49 |
+
- **Improved OpenAI model reliability** — `GPT_TOOL_USE_GUIDANCE` prevents GPT models from describing actions instead of calling tools + automatic budget warning stripping from history ([#3528](https://github.com/NousResearch/hermes-agent/pull/3528))
|
| 50 |
+
- **Surface lifecycle events** — All retry, fallback, and compression events now surface to the user as formatted messages ([#3153](https://github.com/NousResearch/hermes-agent/pull/3153))
|
| 51 |
+
- **Anthropic output limits** — Per-model native output limits instead of hardcoded 16K `max_tokens` ([#3426](https://github.com/NousResearch/hermes-agent/pull/3426))
|
| 52 |
+
- **Thinking-budget exhaustion detection** — Skip useless continuation retries when model uses all output tokens on reasoning ([#3444](https://github.com/NousResearch/hermes-agent/pull/3444))
|
| 53 |
+
- Always prefer streaming for API calls to prevent hung subagents ([#3120](https://github.com/NousResearch/hermes-agent/pull/3120))
|
| 54 |
+
- Restore safe non-streaming fallback after stream failures ([#3020](https://github.com/NousResearch/hermes-agent/pull/3020))
|
| 55 |
+
- Give subagents independent iteration budgets ([#3004](https://github.com/NousResearch/hermes-agent/pull/3004))
|
| 56 |
+
- Update `api_key` in `_try_activate_fallback` for subagent auth ([#3103](https://github.com/NousResearch/hermes-agent/pull/3103))
|
| 57 |
+
- Graceful return on max retries instead of crashing thread ([untagged commit](https://github.com/NousResearch/hermes-agent))
|
| 58 |
+
- Count compression restarts toward retry limit ([#3070](https://github.com/NousResearch/hermes-agent/pull/3070))
|
| 59 |
+
- Include tool tokens in preflight estimate, guard context probe persistence ([#3164](https://github.com/NousResearch/hermes-agent/pull/3164))
|
| 60 |
+
- Update context compressor limits after fallback activation ([#3305](https://github.com/NousResearch/hermes-agent/pull/3305))
|
| 61 |
+
- Validate empty user messages to prevent Anthropic API 400 errors ([#3322](https://github.com/NousResearch/hermes-agent/pull/3322))
|
| 62 |
+
- GLM reasoning-only and max-length handling ([#3010](https://github.com/NousResearch/hermes-agent/pull/3010))
|
| 63 |
+
- Increase API timeout default from 900s to 1800s for slow-thinking models ([#3431](https://github.com/NousResearch/hermes-agent/pull/3431))
|
| 64 |
+
- Send `max_tokens` for Claude/OpenRouter + retry SSE connection errors ([#3497](https://github.com/NousResearch/hermes-agent/pull/3497))
|
| 65 |
+
- Prevent AsyncOpenAI/httpx cross-loop deadlock in gateway mode ([#2701](https://github.com/NousResearch/hermes-agent/pull/2701)) by @ctlst
|
| 66 |
+
|
| 67 |
+
### Streaming & Reasoning
|
| 68 |
+
- **Persist reasoning across gateway session turns** with new schema v6 columns (`reasoning`, `reasoning_details`, `codex_reasoning_items`) ([#2974](https://github.com/NousResearch/hermes-agent/pull/2974))
|
| 69 |
+
- Detect and kill stale SSE connections ([untagged commit](https://github.com/NousResearch/hermes-agent))
|
| 70 |
+
- Fix stale stream detector race causing spurious `RemoteProtocolError` ([untagged commit](https://github.com/NousResearch/hermes-agent))
|
| 71 |
+
- Skip duplicate callback for `<think>`-extracted reasoning during streaming ([#3116](https://github.com/NousResearch/hermes-agent/pull/3116))
|
| 72 |
+
- Preserve reasoning fields in `rewrite_transcript` ([#3311](https://github.com/NousResearch/hermes-agent/pull/3311))
|
| 73 |
+
- Preserve Gemini thought signatures in streamed tool calls ([#2997](https://github.com/NousResearch/hermes-agent/pull/2997))
|
| 74 |
+
- Ensure first delta is fired during reasoning updates ([untagged commit](https://github.com/NousResearch/hermes-agent))
|
| 75 |
+
|
| 76 |
+
### Session & Memory
|
| 77 |
+
- **Session search recent sessions mode** — Omit query to browse recent sessions with titles, previews, and timestamps ([#2533](https://github.com/NousResearch/hermes-agent/pull/2533))
|
| 78 |
+
- **Session config surfacing** on `/new`, `/reset`, and auto-reset ([#3321](https://github.com/NousResearch/hermes-agent/pull/3321))
|
| 79 |
+
- **Third-party session isolation** — `--source` flag for isolating sessions by origin ([#3255](https://github.com/NousResearch/hermes-agent/pull/3255))
|
| 80 |
+
- Add `/resume` CLI handler, session log truncation guard, `reopen_session` API ([#3315](https://github.com/NousResearch/hermes-agent/pull/3315))
|
| 81 |
+
- Clear compressor summary and turn counter on `/clear` and `/new` ([#3102](https://github.com/NousResearch/hermes-agent/pull/3102))
|
| 82 |
+
- Surface silent SessionDB failures that cause session data loss ([#2999](https://github.com/NousResearch/hermes-agent/pull/2999))
|
| 83 |
+
- Session search fallback preview on summarization failure ([#3478](https://github.com/NousResearch/hermes-agent/pull/3478))
|
| 84 |
+
- Prevent stale memory overwrites by flush agent ([#2687](https://github.com/NousResearch/hermes-agent/pull/2687))
|
| 85 |
+
|
| 86 |
+
### Context Compression
|
| 87 |
+
- Replace dead `summary_target_tokens` with ratio-based scaling ([#2554](https://github.com/NousResearch/hermes-agent/pull/2554))
|
| 88 |
+
- Expose `compression.target_ratio`, `protect_last_n`, and `threshold` in `DEFAULT_CONFIG` ([untagged commit](https://github.com/NousResearch/hermes-agent))
|
| 89 |
+
- Restore sane defaults and cap summary at 12K tokens ([untagged commit](https://github.com/NousResearch/hermes-agent))
|
| 90 |
+
- Preserve transcript on `/compress` and hygiene compression ([#3556](https://github.com/NousResearch/hermes-agent/pull/3556))
|
| 91 |
+
- Update context pressure warnings and token estimates after compaction ([untagged commit](https://github.com/NousResearch/hermes-agent))
|
| 92 |
+
|
| 93 |
+
### Architecture & Dependencies
|
| 94 |
+
- **Remove mini-swe-agent dependency** — Inline Docker and Modal backends directly ([#2804](https://github.com/NousResearch/hermes-agent/pull/2804))
|
| 95 |
+
- **Replace swe-rex with native Modal SDK** for Modal backend ([#3538](https://github.com/NousResearch/hermes-agent/pull/3538))
|
| 96 |
+
- **Plugin lifecycle hooks** — `pre_llm_call`, `post_llm_call`, `on_session_start`, `on_session_end` now fire in the agent loop ([#3542](https://github.com/NousResearch/hermes-agent/pull/3542))
|
| 97 |
+
- Fix plugin toolsets invisible in `hermes tools` and standalone processes ([#3457](https://github.com/NousResearch/hermes-agent/pull/3457))
|
| 98 |
+
- Consolidate `get_hermes_home()` and `parse_reasoning_effort()` ([#3062](https://github.com/NousResearch/hermes-agent/pull/3062))
|
| 99 |
+
- Remove unused Hermes-native PKCE OAuth flow ([#3107](https://github.com/NousResearch/hermes-agent/pull/3107))
|
| 100 |
+
- Remove ~100 unused imports across 55 files ([#3016](https://github.com/NousResearch/hermes-agent/pull/3016))
|
| 101 |
+
- Fix 154 f-strings, simplify getattr/URL patterns, remove dead code ([#3119](https://github.com/NousResearch/hermes-agent/pull/3119))
|
| 102 |
+
|
| 103 |
+
---
|
| 104 |
+
|
| 105 |
+
## 📱 Messaging Platforms (Gateway)
|
| 106 |
+
|
| 107 |
+
### Telegram
|
| 108 |
+
- **Private Chat Topics** — Project-based conversations with functional skill binding per topic, enabling isolated workflows within a single Telegram chat ([#3163](https://github.com/NousResearch/hermes-agent/pull/3163))
|
| 109 |
+
- **Auto-discover fallback IPs via DNS-over-HTTPS** when `api.telegram.org` is unreachable ([#3376](https://github.com/NousResearch/hermes-agent/pull/3376))
|
| 110 |
+
- **Configurable reply threading mode** ([#2907](https://github.com/NousResearch/hermes-agent/pull/2907))
|
| 111 |
+
- Fall back to no `thread_id` on "Message thread not found" BadRequest ([#3390](https://github.com/NousResearch/hermes-agent/pull/3390))
|
| 112 |
+
- Self-reschedule reconnect when `start_polling` fails after 502 ([#3268](https://github.com/NousResearch/hermes-agent/pull/3268))
|
| 113 |
+
|
| 114 |
+
### Discord
|
| 115 |
+
- Stop phantom typing indicator after agent turn completes ([#3003](https://github.com/NousResearch/hermes-agent/pull/3003))
|
| 116 |
+
|
| 117 |
+
### Slack
|
| 118 |
+
- Send tool call progress messages to correct Slack thread ([#3063](https://github.com/NousResearch/hermes-agent/pull/3063))
|
| 119 |
+
- Scope progress thread fallback to Slack only ([#3488](https://github.com/NousResearch/hermes-agent/pull/3488))
|
| 120 |
+
|
| 121 |
+
### WhatsApp
|
| 122 |
+
- Download documents, audio, and video media from messages ([#2978](https://github.com/NousResearch/hermes-agent/pull/2978))
|
| 123 |
+
|
| 124 |
+
### Matrix
|
| 125 |
+
- Add missing Matrix entry in `PLATFORMS` dict ([#3473](https://github.com/NousResearch/hermes-agent/pull/3473))
|
| 126 |
+
- Harden e2ee access-token handling ([#3562](https://github.com/NousResearch/hermes-agent/pull/3562))
|
| 127 |
+
- Add backoff for `SyncError` in sync loop ([#3280](https://github.com/NousResearch/hermes-agent/pull/3280))
|
| 128 |
+
|
| 129 |
+
### Signal
|
| 130 |
+
- Track SSE keepalive comments as connection activity ([#3316](https://github.com/NousResearch/hermes-agent/pull/3316))
|
| 131 |
+
|
| 132 |
+
### Email
|
| 133 |
+
- Prevent unbounded growth of `_seen_uids` in EmailAdapter ([#3490](https://github.com/NousResearch/hermes-agent/pull/3490))
|
| 134 |
+
|
| 135 |
+
### Gateway Core
|
| 136 |
+
- **Config-gated `/verbose` command** for messaging platforms — toggle tool output verbosity from chat ([#3262](https://github.com/NousResearch/hermes-agent/pull/3262))
|
| 137 |
+
- **Background review notifications** delivered to user chat ([#3293](https://github.com/NousResearch/hermes-agent/pull/3293))
|
| 138 |
+
- **Retry transient send failures** and notify user on exhaustion ([#3288](https://github.com/NousResearch/hermes-agent/pull/3288))
|
| 139 |
+
- Recover from hung agents — `/stop` hard-kills session lock ([#3104](https://github.com/NousResearch/hermes-agent/pull/3104))
|
| 140 |
+
- Thread-safe `SessionStore` — protect `_entries` with `threading.Lock` ([#3052](https://github.com/NousResearch/hermes-agent/pull/3052))
|
| 141 |
+
- Fix gateway token double-counting with cached agents — use absolute set instead of increment ([#3306](https://github.com/NousResearch/hermes-agent/pull/3306), [#3317](https://github.com/NousResearch/hermes-agent/pull/3317))
|
| 142 |
+
- Fingerprint full auth token in agent cache signature ([#3247](https://github.com/NousResearch/hermes-agent/pull/3247))
|
| 143 |
+
- Silence background agent terminal output ([#3297](https://github.com/NousResearch/hermes-agent/pull/3297))
|
| 144 |
+
- Include per-platform `ALLOW_ALL` and `SIGNAL_GROUP` in startup allowlist check ([#3313](https://github.com/NousResearch/hermes-agent/pull/3313))
|
| 145 |
+
- Include user-local bin paths in systemd unit PATH ([#3527](https://github.com/NousResearch/hermes-agent/pull/3527))
|
| 146 |
+
- Track background task references in `GatewayRunner` ([#3254](https://github.com/NousResearch/hermes-agent/pull/3254))
|
| 147 |
+
- Add request timeouts to HA, Email, Mattermost, SMS adapters ([#3258](https://github.com/NousResearch/hermes-agent/pull/3258))
|
| 148 |
+
- Add media download retry to Mattermost, Slack, and base cache ([#3323](https://github.com/NousResearch/hermes-agent/pull/3323))
|
| 149 |
+
- Detect virtualenv path instead of hardcoding `venv/` ([#2797](https://github.com/NousResearch/hermes-agent/pull/2797))
|
| 150 |
+
- Use `TERMINAL_CWD` for context file discovery, not process cwd ([untagged commit](https://github.com/NousResearch/hermes-agent))
|
| 151 |
+
- Stop loading hermes repo AGENTS.md into gateway sessions (~10k wasted tokens) ([#2891](https://github.com/NousResearch/hermes-agent/pull/2891))
|
| 152 |
+
|
| 153 |
+
---
|
| 154 |
+
|
| 155 |
+
## 🖥️ CLI & User Experience
|
| 156 |
+
|
| 157 |
+
### Interactive CLI
|
| 158 |
+
- **Configurable busy input mode** + fix `/queue` always working ([#3298](https://github.com/NousResearch/hermes-agent/pull/3298))
|
| 159 |
+
- **Preserve user input on multiline paste** ([#3065](https://github.com/NousResearch/hermes-agent/pull/3065))
|
| 160 |
+
- **Tool generation callback** — streaming "preparing terminal…" updates during tool argument generation ([untagged commit](https://github.com/NousResearch/hermes-agent))
|
| 161 |
+
- Show tool progress for substantive tools, not just "preparing" ([untagged commit](https://github.com/NousResearch/hermes-agent))
|
| 162 |
+
- Buffer reasoning preview chunks and fix duplicate display ([#3013](https://github.com/NousResearch/hermes-agent/pull/3013))
|
| 163 |
+
- Prevent reasoning box from rendering 3x during tool-calling loops ([#3405](https://github.com/NousResearch/hermes-agent/pull/3405))
|
| 164 |
+
- Eliminate "Event loop is closed" / "Press ENTER to continue" during idle — three-layer fix with `neuter_async_httpx_del()`, custom exception handler, and stale client cleanup ([#3398](https://github.com/NousResearch/hermes-agent/pull/3398))
|
| 165 |
+
- Fix status bar shows 26K instead of 260K for token counts with trailing zeros ([#3024](https://github.com/NousResearch/hermes-agent/pull/3024))
|
| 166 |
+
- Fix status bar duplicates and degrades during long sessions ([#3291](https://github.com/NousResearch/hermes-agent/pull/3291))
|
| 167 |
+
- Refresh TUI before background task output to prevent status bar overlap ([#3048](https://github.com/NousResearch/hermes-agent/pull/3048))
|
| 168 |
+
- Suppress KawaiiSpinner animation under `patch_stdout` ([#2994](https://github.com/NousResearch/hermes-agent/pull/2994))
|
| 169 |
+
- Skip KawaiiSpinner when TUI handles tool progress ([#2973](https://github.com/NousResearch/hermes-agent/pull/2973))
|
| 170 |
+
- Guard `isatty()` against closed streams via `_is_tty` property ([#3056](https://github.com/NousResearch/hermes-agent/pull/3056))
|
| 171 |
+
- Ensure single closure of streaming boxes during tool generation ([untagged commit](https://github.com/NousResearch/hermes-agent))
|
| 172 |
+
- Cap context pressure percentage at 100% in display ([#3480](https://github.com/NousResearch/hermes-agent/pull/3480))
|
| 173 |
+
- Clean up HTML error messages in CLI display ([#3069](https://github.com/NousResearch/hermes-agent/pull/3069))
|
| 174 |
+
- Show HTTP status code and 400 body in API error output ([#3096](https://github.com/NousResearch/hermes-agent/pull/3096))
|
| 175 |
+
- Extract useful info from HTML error pages, dump debug on max retries ([untagged commit](https://github.com/NousResearch/hermes-agent))
|
| 176 |
+
- Prevent TypeError on startup when `base_url` is None ([#3068](https://github.com/NousResearch/hermes-agent/pull/3068))
|
| 177 |
+
- Prevent update crash in non-TTY environments ([#3094](https://github.com/NousResearch/hermes-agent/pull/3094))
|
| 178 |
+
- Handle EOFError in sessions delete/prune confirmation prompts ([#3101](https://github.com/NousResearch/hermes-agent/pull/3101))
|
| 179 |
+
- Catch KeyboardInterrupt during `flush_memories` on exit and in exit cleanup handlers ([#3025](https://github.com/NousResearch/hermes-agent/pull/3025), [#3257](https://github.com/NousResearch/hermes-agent/pull/3257))
|
| 180 |
+
- Guard `.strip()` against None values from YAML config ([#3552](https://github.com/NousResearch/hermes-agent/pull/3552))
|
| 181 |
+
- Guard `config.get()` against YAML null values to prevent AttributeError ([#3377](https://github.com/NousResearch/hermes-agent/pull/3377))
|
| 182 |
+
- Store asyncio task references to prevent GC mid-execution ([#3267](https://github.com/NousResearch/hermes-agent/pull/3267))
|
| 183 |
+
|
| 184 |
+
### Setup & Configuration
|
| 185 |
+
- Use explicit key mapping for returning-user menu dispatch instead of positional index ([#3083](https://github.com/NousResearch/hermes-agent/pull/3083))
|
| 186 |
+
- Use `sys.executable` for pip in update commands to fix PEP 668 ([#3099](https://github.com/NousResearch/hermes-agent/pull/3099))
|
| 187 |
+
- Harden `hermes update` against diverged history, non-main branches, and gateway edge cases ([#3492](https://github.com/NousResearch/hermes-agent/pull/3492))
|
| 188 |
+
- OpenClaw migration overwrites defaults and setup wizard skips imported sections — fixed ([#3282](https://github.com/NousResearch/hermes-agent/pull/3282))
|
| 189 |
+
- Stop recursive AGENTS.md walk, load top-level only ([#3110](https://github.com/NousResearch/hermes-agent/pull/3110))
|
| 190 |
+
- Add macOS Homebrew paths to browser and terminal PATH resolution ([#2713](https://github.com/NousResearch/hermes-agent/pull/2713))
|
| 191 |
+
- YAML boolean handling for `tool_progress` config ([#3300](https://github.com/NousResearch/hermes-agent/pull/3300))
|
| 192 |
+
- Reset default SOUL.md to baseline identity text ([#3159](https://github.com/NousResearch/hermes-agent/pull/3159))
|
| 193 |
+
- Reject relative cwd paths for container terminal backends ([untagged commit](https://github.com/NousResearch/hermes-agent))
|
| 194 |
+
- Add explicit `hermes-api-server` toolset for API server platform ([#3304](https://github.com/NousResearch/hermes-agent/pull/3304))
|
| 195 |
+
- Reorder setup wizard providers — OpenRouter first ([untagged commit](https://github.com/NousResearch/hermes-agent))
|
| 196 |
+
|
| 197 |
+
---
|
| 198 |
+
|
| 199 |
+
## 🔧 Tool System
|
| 200 |
+
|
| 201 |
+
### API Server
|
| 202 |
+
- **Idempotency-Key support**, body size limit, and OpenAI error envelope ([#2903](https://github.com/NousResearch/hermes-agent/pull/2903))
|
| 203 |
+
- Allow Idempotency-Key in CORS headers ([#3530](https://github.com/NousResearch/hermes-agent/pull/3530))
|
| 204 |
+
- Cancel orphaned agent + true interrupt on SSE disconnect ([#3427](https://github.com/NousResearch/hermes-agent/pull/3427))
|
| 205 |
+
- Fix streaming breaks when agent makes tool calls ([#2985](https://github.com/NousResearch/hermes-agent/pull/2985))
|
| 206 |
+
|
| 207 |
+
### Terminal & File Operations
|
| 208 |
+
- Handle addition-only hunks in V4A patch parser ([#3325](https://github.com/NousResearch/hermes-agent/pull/3325))
|
| 209 |
+
- Exponential backoff for persistent shell polling ([#2996](https://github.com/NousResearch/hermes-agent/pull/2996))
|
| 210 |
+
- Add timeout to subprocess calls in `context_references` ([#3469](https://github.com/NousResearch/hermes-agent/pull/3469))
|
| 211 |
+
|
| 212 |
+
### Browser & Vision
|
| 213 |
+
- Handle 402 insufficient credits error in vision tool ([#2802](https://github.com/NousResearch/hermes-agent/pull/2802))
|
| 214 |
+
- Fix `browser_vision` ignores `auxiliary.vision.timeout` config ([#2901](https://github.com/NousResearch/hermes-agent/pull/2901))
|
| 215 |
+
- Make browser command timeout configurable via config.yaml ([#2801](https://github.com/NousResearch/hermes-agent/pull/2801))
|
| 216 |
+
|
| 217 |
+
### MCP
|
| 218 |
+
- MCP toolset resolution for runtime and config ([#3252](https://github.com/NousResearch/hermes-agent/pull/3252))
|
| 219 |
+
- Add MCP tool name collision protection ([#3077](https://github.com/NousResearch/hermes-agent/pull/3077))
|
| 220 |
+
|
| 221 |
+
### Auxiliary LLM
|
| 222 |
+
- Guard aux LLM calls against None content + reasoning fallback + retry ([#3449](https://github.com/NousResearch/hermes-agent/pull/3449))
|
| 223 |
+
- Catch ImportError from `build_anthropic_client` in vision auto-detection ([#3312](https://github.com/NousResearch/hermes-agent/pull/3312))
|
| 224 |
+
|
| 225 |
+
### Other Tools
|
| 226 |
+
- Add request timeouts to `send_message_tool` HTTP calls ([#3162](https://github.com/NousResearch/hermes-agent/pull/3162)) by @memosr
|
| 227 |
+
- Auto-repair `jobs.json` with invalid control characters ([#3537](https://github.com/NousResearch/hermes-agent/pull/3537))
|
| 228 |
+
- Enable fine-grained tool streaming for Claude/OpenRouter ([#3497](https://github.com/NousResearch/hermes-agent/pull/3497))
|
| 229 |
+
|
| 230 |
+
---
|
| 231 |
+
|
| 232 |
+
## 🧩 Skills Ecosystem
|
| 233 |
+
|
| 234 |
+
### Skills System
|
| 235 |
+
- **Env var passthrough** for skills and user config — skills can declare environment variables to pass through ([#2807](https://github.com/NousResearch/hermes-agent/pull/2807))
|
| 236 |
+
- Cache skills prompt with shared `skill_utils` module for faster TTFT ([#3421](https://github.com/NousResearch/hermes-agent/pull/3421))
|
| 237 |
+
- Avoid redundant file re-read for skill conditions ([#2992](https://github.com/NousResearch/hermes-agent/pull/2992))
|
| 238 |
+
- Use Git Trees API to prevent silent subdirectory loss during install ([#2995](https://github.com/NousResearch/hermes-agent/pull/2995))
|
| 239 |
+
- Fix skills-sh install for deeply nested repo structures ([#2980](https://github.com/NousResearch/hermes-agent/pull/2980))
|
| 240 |
+
- Handle null metadata in skill frontmatter ([untagged commit](https://github.com/NousResearch/hermes-agent))
|
| 241 |
+
- Preserve trust for skills-sh identifiers + reduce resolution churn ([#3251](https://github.com/NousResearch/hermes-agent/pull/3251))
|
| 242 |
+
- Agent-created skills were incorrectly treated as untrusted community content — fixed ([untagged commit](https://github.com/NousResearch/hermes-agent))
|
| 243 |
+
|
| 244 |
+
### New Skills
|
| 245 |
+
- **G0DM0D3 godmode jailbreaking skill** + docs ([#3157](https://github.com/NousResearch/hermes-agent/pull/3157))
|
| 246 |
+
- **Docker management skill** added to optional-skills ([#3060](https://github.com/NousResearch/hermes-agent/pull/3060))
|
| 247 |
+
- **OpenClaw migration v2** — 17 new modules, terminal recap for migrating from OpenClaw to Hermes ([#2906](https://github.com/NousResearch/hermes-agent/pull/2906))
|
| 248 |
+
|
| 249 |
+
---
|
| 250 |
+
|
| 251 |
+
## 🔒 Security & Reliability
|
| 252 |
+
|
| 253 |
+
### Security Hardening
|
| 254 |
+
- **SSRF protection** added to `browser_navigate` ([#3058](https://github.com/NousResearch/hermes-agent/pull/3058))
|
| 255 |
+
- **SSRF protection** added to `vision_tools` and `web_tools` (hardened) ([#2679](https://github.com/NousResearch/hermes-agent/pull/2679))
|
| 256 |
+
- **Restrict subagent toolsets** to parent's enabled set ([#3269](https://github.com/NousResearch/hermes-agent/pull/3269))
|
| 257 |
+
- **Prevent zip-slip path traversal** in self-update ([#3250](https://github.com/NousResearch/hermes-agent/pull/3250))
|
| 258 |
+
- **Prevent shell injection** in `_expand_path` via `~user` path suffix ([#2685](https://github.com/NousResearch/hermes-agent/pull/2685))
|
| 259 |
+
- **Normalize input** before dangerous command detection ([#3260](https://github.com/NousResearch/hermes-agent/pull/3260))
|
| 260 |
+
- Make tirith block verdicts approvable instead of hard-blocking ([#3428](https://github.com/NousResearch/hermes-agent/pull/3428))
|
| 261 |
+
- Remove compromised `litellm`/`typer`/`platformdirs` from deps ([#2796](https://github.com/NousResearch/hermes-agent/pull/2796))
|
| 262 |
+
- Pin all dependency version ranges ([#2810](https://github.com/NousResearch/hermes-agent/pull/2810))
|
| 263 |
+
- Regenerate `uv.lock` with hashes, use lockfile in setup ([#2812](https://github.com/NousResearch/hermes-agent/pull/2812))
|
| 264 |
+
- Bump dependencies to fix CVEs + regenerate `uv.lock` ([#3073](https://github.com/NousResearch/hermes-agent/pull/3073))
|
| 265 |
+
- Supply chain audit CI workflow for PR scanning ([#2816](https://github.com/NousResearch/hermes-agent/pull/2816))
|
| 266 |
+
|
| 267 |
+
### Reliability
|
| 268 |
+
- **SQLite WAL write-lock contention** causing 15-20s TUI freeze — fixed ([#3385](https://github.com/NousResearch/hermes-agent/pull/3385))
|
| 269 |
+
- **SQLite concurrency hardening** + session transcript integrity ([#3249](https://github.com/NousResearch/hermes-agent/pull/3249))
|
| 270 |
+
- Prevent recurring cron job re-fire on gateway crash/restart loop ([#3396](https://github.com/NousResearch/hermes-agent/pull/3396))
|
| 271 |
+
- Mark cron session as ended after job completes ([#2998](https://github.com/NousResearch/hermes-agent/pull/2998))
|
| 272 |
+
|
| 273 |
+
---
|
| 274 |
+
|
| 275 |
+
## ⚡ Performance
|
| 276 |
+
|
| 277 |
+
- **TTFT startup optimizations** — salvaged easy-win startup improvements ([#3395](https://github.com/NousResearch/hermes-agent/pull/3395))
|
| 278 |
+
- Cache skills prompt with shared `skill_utils` module ([#3421](https://github.com/NousResearch/hermes-agent/pull/3421))
|
| 279 |
+
- Avoid redundant file re-read for skill conditions in prompt builder ([#2992](https://github.com/NousResearch/hermes-agent/pull/2992))
|
| 280 |
+
|
| 281 |
+
---
|
| 282 |
+
|
| 283 |
+
## 🐛 Notable Bug Fixes
|
| 284 |
+
|
| 285 |
+
- Fix gateway token double-counting with cached agents ([#3306](https://github.com/NousResearch/hermes-agent/pull/3306), [#3317](https://github.com/NousResearch/hermes-agent/pull/3317))
|
| 286 |
+
- Fix "Event loop is closed" / "Press ENTER to continue" during idle sessions ([#3398](https://github.com/NousResearch/hermes-agent/pull/3398))
|
| 287 |
+
- Fix reasoning box rendering 3x during tool-calling loops ([#3405](https://github.com/NousResearch/hermes-agent/pull/3405))
|
| 288 |
+
- Fix status bar shows 26K instead of 260K for token counts ([#3024](https://github.com/NousResearch/hermes-agent/pull/3024))
|
| 289 |
+
- Fix `/queue` always working regardless of config ([#3298](https://github.com/NousResearch/hermes-agent/pull/3298))
|
| 290 |
+
- Fix phantom Discord typing indicator after agent turn ([#3003](https://github.com/NousResearch/hermes-agent/pull/3003))
|
| 291 |
+
- Fix Slack progress messages appearing in wrong thread ([#3063](https://github.com/NousResearch/hermes-agent/pull/3063))
|
| 292 |
+
- Fix WhatsApp media downloads (documents, audio, video) ([#2978](https://github.com/NousResearch/hermes-agent/pull/2978))
|
| 293 |
+
- Fix Telegram "Message thread not found" killing progress messages ([#3390](https://github.com/NousResearch/hermes-agent/pull/3390))
|
| 294 |
+
- Fix OpenClaw migration overwriting defaults ([#3282](https://github.com/NousResearch/hermes-agent/pull/3282))
|
| 295 |
+
- Fix returning-user setup menu dispatching wrong section ([#3083](https://github.com/NousResearch/hermes-agent/pull/3083))
|
| 296 |
+
- Fix `hermes update` PEP 668 "externally-managed-environment" error ([#3099](https://github.com/NousResearch/hermes-agent/pull/3099))
|
| 297 |
+
- Fix subagents hitting `max_iterations` prematurely via shared budget ([#3004](https://github.com/NousResearch/hermes-agent/pull/3004))
|
| 298 |
+
- Fix YAML boolean handling for `tool_progress` config ([#3300](https://github.com/NousResearch/hermes-agent/pull/3300))
|
| 299 |
+
- Fix `config.get()` crashes on YAML null values ([#3377](https://github.com/NousResearch/hermes-agent/pull/3377))
|
| 300 |
+
- Fix `.strip()` crash on None values from YAML config ([#3552](https://github.com/NousResearch/hermes-agent/pull/3552))
|
| 301 |
+
- Fix hung agents on gateway — `/stop` now hard-kills session lock ([#3104](https://github.com/NousResearch/hermes-agent/pull/3104))
|
| 302 |
+
- Fix `_custom` provider silently remapped to `openrouter` ([#2792](https://github.com/NousResearch/hermes-agent/pull/2792))
|
| 303 |
+
- Fix Matrix missing from `PLATFORMS` dict ([#3473](https://github.com/NousResearch/hermes-agent/pull/3473))
|
| 304 |
+
- Fix Email adapter unbounded `_seen_uids` growth ([#3490](https://github.com/NousResearch/hermes-agent/pull/3490))
|
| 305 |
+
|
| 306 |
+
---
|
| 307 |
+
|
| 308 |
+
## 🧪 Testing
|
| 309 |
+
|
| 310 |
+
- Pin `agent-client-protocol` < 0.9 to handle breaking upstream release ([#3320](https://github.com/NousResearch/hermes-agent/pull/3320))
|
| 311 |
+
- Catch anthropic ImportError in vision auto-detection tests ([#3312](https://github.com/NousResearch/hermes-agent/pull/3312))
|
| 312 |
+
- Update retry-exhaust test for new graceful return behavior ([#3320](https://github.com/NousResearch/hermes-agent/pull/3320))
|
| 313 |
+
- Add regression tests for null metadata frontmatter ([untagged commit](https://github.com/NousResearch/hermes-agent))
|
| 314 |
+
|
| 315 |
+
---
|
| 316 |
+
|
| 317 |
+
## 📚 Documentation
|
| 318 |
+
|
| 319 |
+
- Update all docs for `/model` command overhaul and custom provider support ([#2800](https://github.com/NousResearch/hermes-agent/pull/2800))
|
| 320 |
+
- Fix stale and incorrect documentation across 18 files ([#2805](https://github.com/NousResearch/hermes-agent/pull/2805))
|
| 321 |
+
- Document 9 previously undocumented features ([#2814](https://github.com/NousResearch/hermes-agent/pull/2814))
|
| 322 |
+
- Add missing skills, CLI commands, and messaging env vars to docs ([#2809](https://github.com/NousResearch/hermes-agent/pull/2809))
|
| 323 |
+
- Fix api-server response storage documentation — SQLite, not in-memory ([#2819](https://github.com/NousResearch/hermes-agent/pull/2819))
|
| 324 |
+
- Quote pip install extras to fix zsh glob errors ([#2815](https://github.com/NousResearch/hermes-agent/pull/2815))
|
| 325 |
+
- Unify hooks documentation — add plugin hooks to hooks page, add `session:end` event ([untagged commit](https://github.com/NousResearch/hermes-agent))
|
| 326 |
+
- Clarify two-mode behavior in `session_search` schema description ([untagged commit](https://github.com/NousResearch/hermes-agent))
|
| 327 |
+
- Fix Discord Public Bot setting for Discord-provided invite link ([#3519](https://github.com/NousResearch/hermes-agent/pull/3519)) by @mehmoodosman
|
| 328 |
+
- Revise v0.4.0 changelog — fix feature attribution, reorder sections ([untagged commit](https://github.com/NousResearch/hermes-agent))
|
| 329 |
+
|
| 330 |
+
---
|
| 331 |
+
|
| 332 |
+
## 👥 Contributors
|
| 333 |
+
|
| 334 |
+
### Core
|
| 335 |
+
- **@teknium1** — 157 PRs covering the full scope of this release
|
| 336 |
+
|
| 337 |
+
### Community Contributors
|
| 338 |
+
- **@alt-glitch** (Siddharth Balyan) — 2 PRs: Nix flake with uv2nix build, NixOS module, and persistent container mode ([#20](https://github.com/NousResearch/hermes-agent/pull/20)); auto-generated config keys and suffix PATHs for Nix builds ([#3061](https://github.com/NousResearch/hermes-agent/pull/3061), [#3274](https://github.com/NousResearch/hermes-agent/pull/3274))
|
| 339 |
+
- **@ctlst** — 1 PR: Prevent AsyncOpenAI/httpx cross-loop deadlock in gateway mode ([#2701](https://github.com/NousResearch/hermes-agent/pull/2701))
|
| 340 |
+
- **@memosr** (memosr.eth) — 1 PR: Add request timeouts to `send_message_tool` HTTP calls ([#3162](https://github.com/NousResearch/hermes-agent/pull/3162))
|
| 341 |
+
- **@mehmoodosman** (Osman Mehmood) — 1 PR: Fix Discord docs for Public Bot setting ([#3519](https://github.com/NousResearch/hermes-agent/pull/3519))
|
| 342 |
+
|
| 343 |
+
### All Contributors
|
| 344 |
+
@alt-glitch, @ctlst, @mehmoodosman, @memosr, @teknium1
|
| 345 |
+
|
| 346 |
+
---
|
| 347 |
+
|
| 348 |
+
**Full Changelog**: [v2026.3.23...v2026.3.28](https://github.com/NousResearch/hermes-agent/compare/v2026.3.23...v2026.3.28)
|
RELEASE_v0.6.0.md
ADDED
|
@@ -0,0 +1,249 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Hermes Agent v0.6.0 (v2026.3.30)
|
| 2 |
+
|
| 3 |
+
**Release Date:** March 30, 2026
|
| 4 |
+
|
| 5 |
+
> The multi-instance release — Profiles for running isolated agent instances, MCP server mode, Docker container, fallback provider chains, two new messaging platforms (Feishu/Lark and WeCom), Telegram webhook mode, Slack multi-workspace OAuth, 95 PRs and 16 resolved issues in 2 days.
|
| 6 |
+
|
| 7 |
+
---
|
| 8 |
+
|
| 9 |
+
## ✨ Highlights
|
| 10 |
+
|
| 11 |
+
- **Profiles — Multi-Instance Hermes** — Run multiple isolated Hermes instances from the same installation. Each profile gets its own config, memory, sessions, skills, and gateway service. Create with `hermes profile create`, switch with `hermes -p <name>`, export/import for sharing. Full token-lock isolation prevents two profiles from using the same bot credential. ([#3681](https://github.com/NousResearch/hermes-agent/pull/3681))
|
| 12 |
+
|
| 13 |
+
- **MCP Server Mode** — Expose Hermes conversations and sessions to any MCP-compatible client (Claude Desktop, Cursor, VS Code, etc.) via `hermes mcp serve`. Browse conversations, read messages, search across sessions, and manage attachments — all through the Model Context Protocol. Supports both stdio and Streamable HTTP transports. ([#3795](https://github.com/NousResearch/hermes-agent/pull/3795))
|
| 14 |
+
|
| 15 |
+
- **Docker Container** — Official Dockerfile for running Hermes Agent in a container. Supports both CLI and gateway modes with volume-mounted config. ([#3668](https://github.com/NousResearch/hermes-agent/pull/3668), closes [#850](https://github.com/NousResearch/hermes-agent/issues/850))
|
| 16 |
+
|
| 17 |
+
- **Ordered Fallback Provider Chain** — Configure multiple inference providers with automatic failover. When your primary provider returns errors or is unreachable, Hermes automatically tries the next provider in the chain. Configure via `fallback_providers` in config.yaml. ([#3813](https://github.com/NousResearch/hermes-agent/pull/3813), closes [#1734](https://github.com/NousResearch/hermes-agent/issues/1734))
|
| 18 |
+
|
| 19 |
+
- **Feishu/Lark Platform Support** — Full gateway adapter for Feishu (飞书) and Lark with event subscriptions, message cards, group chat, image/file attachments, and interactive card callbacks. ([#3799](https://github.com/NousResearch/hermes-agent/pull/3799), [#3817](https://github.com/NousResearch/hermes-agent/pull/3817), closes [#1788](https://github.com/NousResearch/hermes-agent/issues/1788))
|
| 20 |
+
|
| 21 |
+
- **WeCom (Enterprise WeChat) Platform Support** — New gateway adapter for WeCom (企业微信) with text/image/voice messages, group chats, and callback verification. ([#3847](https://github.com/NousResearch/hermes-agent/pull/3847))
|
| 22 |
+
|
| 23 |
+
- **Slack Multi-Workspace OAuth** — Connect a single Hermes gateway to multiple Slack workspaces via OAuth token file. Each workspace gets its own bot token, resolved dynamically per incoming event. ([#3903](https://github.com/NousResearch/hermes-agent/pull/3903))
|
| 24 |
+
|
| 25 |
+
- **Telegram Webhook Mode & Group Controls** — Run the Telegram adapter in webhook mode as an alternative to polling — faster response times and better for production deployments behind a reverse proxy. New group mention gating controls when the bot responds: always, only when @mentioned, or via regex triggers. ([#3880](https://github.com/NousResearch/hermes-agent/pull/3880), [#3870](https://github.com/NousResearch/hermes-agent/pull/3870))
|
| 26 |
+
|
| 27 |
+
- **Exa Search Backend** — Add Exa as an alternative web search and content extraction backend alongside Firecrawl and DuckDuckGo. Set `EXA_API_KEY` and configure as preferred backend. ([#3648](https://github.com/NousResearch/hermes-agent/pull/3648))
|
| 28 |
+
|
| 29 |
+
- **Skills & Credentials on Remote Backends** — Mount skill directories and credential files into Modal and Docker containers, so remote terminal sessions have access to the same skills and secrets as local execution. ([#3890](https://github.com/NousResearch/hermes-agent/pull/3890), [#3671](https://github.com/NousResearch/hermes-agent/pull/3671), closes [#3665](https://github.com/NousResearch/hermes-agent/issues/3665), [#3433](https://github.com/NousResearch/hermes-agent/issues/3433))
|
| 30 |
+
|
| 31 |
+
---
|
| 32 |
+
|
| 33 |
+
## 🏗️ Core Agent & Architecture
|
| 34 |
+
|
| 35 |
+
### Provider & Model Support
|
| 36 |
+
- **Ordered fallback provider chain** — automatic failover across multiple configured providers ([#3813](https://github.com/NousResearch/hermes-agent/pull/3813))
|
| 37 |
+
- **Fix api_mode on provider switch** — switching providers via `hermes model` now correctly clears stale `api_mode` instead of hardcoding `chat_completions`, fixing 404s for providers with Anthropic-compatible endpoints ([#3726](https://github.com/NousResearch/hermes-agent/pull/3726), [#3857](https://github.com/NousResearch/hermes-agent/pull/3857), closes [#3685](https://github.com/NousResearch/hermes-agent/issues/3685))
|
| 38 |
+
- **Stop silent OpenRouter fallback** — when no provider is configured, Hermes now raises a clear error instead of silently routing to OpenRouter ([#3807](https://github.com/NousResearch/hermes-agent/pull/3807), [#3862](https://github.com/NousResearch/hermes-agent/pull/3862))
|
| 39 |
+
- **Gemini 3.1 preview models** — added to OpenRouter and Nous Portal catalogs ([#3803](https://github.com/NousResearch/hermes-agent/pull/3803), closes [#3753](https://github.com/NousResearch/hermes-agent/issues/3753))
|
| 40 |
+
- **Gemini direct API context length** — full context length resolution for direct Google AI endpoints ([#3876](https://github.com/NousResearch/hermes-agent/pull/3876))
|
| 41 |
+
- **gpt-5.4-mini** added to Codex fallback catalog ([#3855](https://github.com/NousResearch/hermes-agent/pull/3855))
|
| 42 |
+
- **Curated model lists preferred** over live API probe when the probe returns fewer models ([#3856](https://github.com/NousResearch/hermes-agent/pull/3856), [#3867](https://github.com/NousResearch/hermes-agent/pull/3867))
|
| 43 |
+
- **User-friendly 429 rate limit messages** with Retry-After countdown ([#3809](https://github.com/NousResearch/hermes-agent/pull/3809))
|
| 44 |
+
- **Auxiliary client placeholder key** for local servers without auth requirements ([#3842](https://github.com/NousResearch/hermes-agent/pull/3842))
|
| 45 |
+
- **INFO-level logging** for auxiliary provider resolution ([#3866](https://github.com/NousResearch/hermes-agent/pull/3866))
|
| 46 |
+
|
| 47 |
+
### Agent Loop & Conversation
|
| 48 |
+
- **Subagent status reporting** — reports `completed` status when summary exists instead of generic failure ([#3829](https://github.com/NousResearch/hermes-agent/pull/3829))
|
| 49 |
+
- **Session log file updated during compression** — prevents stale file references after context compression ([#3835](https://github.com/NousResearch/hermes-agent/pull/3835))
|
| 50 |
+
- **Omit empty tools param** — sends no `tools` parameter when empty instead of `None`, fixing compatibility with strict providers ([#3820](https://github.com/NousResearch/hermes-agent/pull/3820))
|
| 51 |
+
|
| 52 |
+
### Profiles & Multi-Instance
|
| 53 |
+
- **Profiles system** — `hermes profile create/list/switch/delete/export/import/rename`. Each profile gets isolated HERMES_HOME, gateway service, CLI wrapper. Token locks prevent credential collisions. Tab completion for profile names. ([#3681](https://github.com/NousResearch/hermes-agent/pull/3681))
|
| 54 |
+
- **Profile-aware display paths** — all user-facing `~/.hermes` paths replaced with `display_hermes_home()` to show the correct profile directory ([#3623](https://github.com/NousResearch/hermes-agent/pull/3623))
|
| 55 |
+
- **Lazy display_hermes_home imports** — prevents `ImportError` during `hermes update` when modules cache stale bytecode ([#3776](https://github.com/NousResearch/hermes-agent/pull/3776))
|
| 56 |
+
- **HERMES_HOME for protected paths** — `.env` write-deny path now respects HERMES_HOME instead of hardcoded `~/.hermes` ([#3840](https://github.com/NousResearch/hermes-agent/pull/3840))
|
| 57 |
+
|
| 58 |
+
---
|
| 59 |
+
|
| 60 |
+
## 📱 Messaging Platforms (Gateway)
|
| 61 |
+
|
| 62 |
+
### New Platforms
|
| 63 |
+
- **Feishu/Lark** — Full adapter with event subscriptions, message cards, group chat, image/file attachments, interactive card callbacks ([#3799](https://github.com/NousResearch/hermes-agent/pull/3799), [#3817](https://github.com/NousResearch/hermes-agent/pull/3817))
|
| 64 |
+
- **WeCom (Enterprise WeChat)** — Text/image/voice messages, group chats, callback verification ([#3847](https://github.com/NousResearch/hermes-agent/pull/3847))
|
| 65 |
+
|
| 66 |
+
### Telegram
|
| 67 |
+
- **Webhook mode** — run as webhook endpoint instead of polling for production deployments ([#3880](https://github.com/NousResearch/hermes-agent/pull/3880))
|
| 68 |
+
- **Group mention gating & regex triggers** — configurable bot response behavior in groups: always, @mention-only, or regex-matched ([#3870](https://github.com/NousResearch/hermes-agent/pull/3870))
|
| 69 |
+
- **Gracefully handle deleted reply targets** — no more crashes when the message being replied to was deleted ([#3858](https://github.com/NousResearch/hermes-agent/pull/3858), closes [#3229](https://github.com/NousResearch/hermes-agent/issues/3229))
|
| 70 |
+
|
| 71 |
+
### Discord
|
| 72 |
+
- **Message processing reactions** — adds a reaction emoji while processing and removes it when done, giving visual feedback in channels ([#3871](https://github.com/NousResearch/hermes-agent/pull/3871))
|
| 73 |
+
- **DISCORD_IGNORE_NO_MENTION** — skip messages that @mention other users/bots but not Hermes ([#3640](https://github.com/NousResearch/hermes-agent/pull/3640))
|
| 74 |
+
- **Clean up deferred "thinking..."** — properly removes the "thinking..." indicator after slash commands complete ([#3674](https://github.com/NousResearch/hermes-agent/pull/3674), closes [#3595](https://github.com/NousResearch/hermes-agent/issues/3595))
|
| 75 |
+
|
| 76 |
+
### Slack
|
| 77 |
+
- **Multi-workspace OAuth** — connect to multiple Slack workspaces from a single gateway via OAuth token file ([#3903](https://github.com/NousResearch/hermes-agent/pull/3903))
|
| 78 |
+
|
| 79 |
+
### WhatsApp
|
| 80 |
+
- **Persistent aiohttp session** — reuse HTTP sessions across requests instead of creating new ones per message ([#3818](https://github.com/NousResearch/hermes-agent/pull/3818))
|
| 81 |
+
- **LID↔phone alias resolution** — correctly match Linked ID and phone number formats in allowlists ([#3830](https://github.com/NousResearch/hermes-agent/pull/3830))
|
| 82 |
+
- **Skip reply prefix in bot mode** — cleaner message formatting when running as a WhatsApp bot ([#3931](https://github.com/NousResearch/hermes-agent/pull/3931))
|
| 83 |
+
|
| 84 |
+
### Matrix
|
| 85 |
+
- **Native voice messages via MSC3245** — send voice messages as proper Matrix voice events instead of file attachments ([#3877](https://github.com/NousResearch/hermes-agent/pull/3877))
|
| 86 |
+
|
| 87 |
+
### Mattermost
|
| 88 |
+
- **Configurable mention behavior** — respond to messages without requiring @mention ([#3664](https://github.com/NousResearch/hermes-agent/pull/3664))
|
| 89 |
+
|
| 90 |
+
### Signal
|
| 91 |
+
- **URL-encode phone numbers** and correct attachment RPC parameter — fixes delivery failures with certain phone number formats ([#3670](https://github.com/NousResearch/hermes-agent/pull/3670)) — @kshitijk4poor
|
| 92 |
+
|
| 93 |
+
### Email
|
| 94 |
+
- **Close SMTP/IMAP connections on failure** — prevents connection leaks during error scenarios ([#3804](https://github.com/NousResearch/hermes-agent/pull/3804))
|
| 95 |
+
|
| 96 |
+
### Gateway Core
|
| 97 |
+
- **Atomic config writes** — use atomic file writes for config.yaml to prevent data loss during crashes ([#3800](https://github.com/NousResearch/hermes-agent/pull/3800))
|
| 98 |
+
- **Home channel env overrides** — apply environment variable overrides for home channels consistently ([#3796](https://github.com/NousResearch/hermes-agent/pull/3796), [#3808](https://github.com/NousResearch/hermes-agent/pull/3808))
|
| 99 |
+
- **Replace print() with logger** — BasePlatformAdapter now uses proper logging instead of print statements ([#3669](https://github.com/NousResearch/hermes-agent/pull/3669))
|
| 100 |
+
- **Cron delivery labels** — resolve human-friendly delivery labels via channel directory ([#3860](https://github.com/NousResearch/hermes-agent/pull/3860), closes [#1945](https://github.com/NousResearch/hermes-agent/issues/1945))
|
| 101 |
+
- **Cron [SILENT] tightening** — prevent agents from prefixing reports with [SILENT] to suppress delivery ([#3901](https://github.com/NousResearch/hermes-agent/pull/3901))
|
| 102 |
+
- **Background task media delivery** and vision download timeout fixes ([#3919](https://github.com/NousResearch/hermes-agent/pull/3919))
|
| 103 |
+
- **Boot-md hook** — example built-in hook to run a BOOT.md file on gateway startup ([#3733](https://github.com/NousResearch/hermes-agent/pull/3733))
|
| 104 |
+
|
| 105 |
+
---
|
| 106 |
+
|
| 107 |
+
## 🖥️ CLI & User Experience
|
| 108 |
+
|
| 109 |
+
### Interactive CLI
|
| 110 |
+
- **Configurable tool preview length** — show full file paths by default instead of truncating at 40 chars ([#3841](https://github.com/NousResearch/hermes-agent/pull/3841))
|
| 111 |
+
- **Tool token context display** — `hermes tools` checklist now shows estimated token cost per toolset ([#3805](https://github.com/NousResearch/hermes-agent/pull/3805))
|
| 112 |
+
- **/bg spinner TUI fix** — route background task spinner through the TUI widget to prevent status bar collision ([#3643](https://github.com/NousResearch/hermes-agent/pull/3643))
|
| 113 |
+
- **Prevent status bar wrapping** into duplicate rows ([#3883](https://github.com/NousResearch/hermes-agent/pull/3883)) — @kshitijk4poor
|
| 114 |
+
- **Handle closed stdout ValueError** in safe print paths — fixes crashes when stdout is closed during gateway thread shutdown ([#3843](https://github.com/NousResearch/hermes-agent/pull/3843), closes [#3534](https://github.com/NousResearch/hermes-agent/issues/3534))
|
| 115 |
+
- **Remove input() from /tools disable** — eliminates freeze in terminal when disabling tools ([#3918](https://github.com/NousResearch/hermes-agent/pull/3918))
|
| 116 |
+
- **TTY guard for interactive CLI commands** — prevent CPU spin when launched without a terminal ([#3933](https://github.com/NousResearch/hermes-agent/pull/3933))
|
| 117 |
+
- **Argparse entrypoint** — use argparse in the top-level launcher for cleaner error handling ([#3874](https://github.com/NousResearch/hermes-agent/pull/3874))
|
| 118 |
+
- **Lazy-initialized tools show yellow** in banner instead of red, reducing false alarm about "missing" tools ([#3822](https://github.com/NousResearch/hermes-agent/pull/3822))
|
| 119 |
+
- **Honcho tools shown in banner** when configured ([#3810](https://github.com/NousResearch/hermes-agent/pull/3810))
|
| 120 |
+
|
| 121 |
+
### Setup & Configuration
|
| 122 |
+
- **Auto-install matrix-nio** during `hermes setup` when Matrix is selected ([#3802](https://github.com/NousResearch/hermes-agent/pull/3802), [#3873](https://github.com/NousResearch/hermes-agent/pull/3873))
|
| 123 |
+
- **Session export stdout support** — export sessions to stdout with `-` for piping ([#3641](https://github.com/NousResearch/hermes-agent/pull/3641), closes [#3609](https://github.com/NousResearch/hermes-agent/issues/3609))
|
| 124 |
+
- **Configurable approval timeouts** — set how long dangerous command approval prompts wait before auto-denying ([#3886](https://github.com/NousResearch/hermes-agent/pull/3886), closes [#3765](https://github.com/NousResearch/hermes-agent/issues/3765))
|
| 125 |
+
- **Clear __pycache__ during update** — prevents stale bytecode ImportError after `hermes update` ([#3819](https://github.com/NousResearch/hermes-agent/pull/3819))
|
| 126 |
+
|
| 127 |
+
---
|
| 128 |
+
|
| 129 |
+
## 🔧 Tool System
|
| 130 |
+
|
| 131 |
+
### MCP
|
| 132 |
+
- **MCP Server Mode** — `hermes mcp serve` exposes conversations, sessions, and attachments to MCP clients via stdio or Streamable HTTP ([#3795](https://github.com/NousResearch/hermes-agent/pull/3795))
|
| 133 |
+
- **Dynamic tool discovery** — respond to `notifications/tools/list_changed` events to pick up new tools from MCP servers without reconnecting ([#3812](https://github.com/NousResearch/hermes-agent/pull/3812))
|
| 134 |
+
- **Non-deprecated HTTP transport** — switched from `sse_client` to `streamable_http_client` ([#3646](https://github.com/NousResearch/hermes-agent/pull/3646))
|
| 135 |
+
|
| 136 |
+
### Web Tools
|
| 137 |
+
- **Exa search backend** — alternative to Firecrawl and DuckDuckGo for web search and extraction ([#3648](https://github.com/NousResearch/hermes-agent/pull/3648))
|
| 138 |
+
|
| 139 |
+
### Browser
|
| 140 |
+
- **Guard against None LLM responses** in browser snapshot and vision tools ([#3642](https://github.com/NousResearch/hermes-agent/pull/3642))
|
| 141 |
+
|
| 142 |
+
### Terminal & Remote Backends
|
| 143 |
+
- **Mount skill directories** into Modal and Docker containers ([#3890](https://github.com/NousResearch/hermes-agent/pull/3890))
|
| 144 |
+
- **Mount credential files** into remote backends with mtime+size caching ([#3671](https://github.com/NousResearch/hermes-agent/pull/3671))
|
| 145 |
+
- **Preserve partial output** when commands time out instead of losing everything ([#3868](https://github.com/NousResearch/hermes-agent/pull/3868))
|
| 146 |
+
- **Stop marking persisted env vars as missing** on remote backends ([#3650](https://github.com/NousResearch/hermes-agent/pull/3650))
|
| 147 |
+
|
| 148 |
+
### Audio
|
| 149 |
+
- **.aac format support** in transcription tool ([#3865](https://github.com/NousResearch/hermes-agent/pull/3865), closes [#1963](https://github.com/NousResearch/hermes-agent/issues/1963))
|
| 150 |
+
- **Audio download retry** — retry logic for `cache_audio_from_url` matching the existing image download pattern ([#3401](https://github.com/NousResearch/hermes-agent/pull/3401)) — @binhnt92
|
| 151 |
+
|
| 152 |
+
### Vision
|
| 153 |
+
- **Reject non-image files** and enforce website-only policy for vision analysis ([#3845](https://github.com/NousResearch/hermes-agent/pull/3845))
|
| 154 |
+
|
| 155 |
+
### Tool Schema
|
| 156 |
+
- **Ensure name field** always present in tool definitions, fixing `KeyError: 'name'` crashes ([#3811](https://github.com/NousResearch/hermes-agent/pull/3811), closes [#3729](https://github.com/NousResearch/hermes-agent/issues/3729))
|
| 157 |
+
|
| 158 |
+
### ACP (Editor Integration)
|
| 159 |
+
- **Complete session management surface** for VS Code/Zed/JetBrains clients — proper task lifecycle, cancel support, session persistence ([#3675](https://github.com/NousResearch/hermes-agent/pull/3675))
|
| 160 |
+
|
| 161 |
+
---
|
| 162 |
+
|
| 163 |
+
## 🧩 Skills & Plugins
|
| 164 |
+
|
| 165 |
+
### Skills System
|
| 166 |
+
- **External skill directories** — configure additional skill directories via `skills.external_dirs` in config.yaml ([#3678](https://github.com/NousResearch/hermes-agent/pull/3678))
|
| 167 |
+
- **Category path traversal blocked** — prevents `../` attacks in skill category names ([#3844](https://github.com/NousResearch/hermes-agent/pull/3844))
|
| 168 |
+
- **parallel-cli moved to optional-skills** — reduces default skill footprint ([#3673](https://github.com/NousResearch/hermes-agent/pull/3673)) — @kshitijk4poor
|
| 169 |
+
|
| 170 |
+
### New Skills
|
| 171 |
+
- **memento-flashcards** — spaced repetition flashcard system ([#3827](https://github.com/NousResearch/hermes-agent/pull/3827))
|
| 172 |
+
- **songwriting-and-ai-music** — songwriting craft and AI music generation prompts ([#3834](https://github.com/NousResearch/hermes-agent/pull/3834))
|
| 173 |
+
- **SiYuan Note** — integration with SiYuan note-taking app ([#3742](https://github.com/NousResearch/hermes-agent/pull/3742))
|
| 174 |
+
- **Scrapling** — web scraping skill using Scrapling library ([#3742](https://github.com/NousResearch/hermes-agent/pull/3742))
|
| 175 |
+
- **one-three-one-rule** — communication framework skill ([#3797](https://github.com/NousResearch/hermes-agent/pull/3797))
|
| 176 |
+
|
| 177 |
+
### Plugin System
|
| 178 |
+
- **Plugin enable/disable commands** — `hermes plugins enable/disable <name>` for managing plugin state without removing them ([#3747](https://github.com/NousResearch/hermes-agent/pull/3747))
|
| 179 |
+
- **Plugin message injection** — plugins can now inject messages into the conversation stream on behalf of the user via `ctx.inject_message()` ([#3778](https://github.com/NousResearch/hermes-agent/pull/3778)) — @winglian
|
| 180 |
+
- **Honcho self-hosted support** — allow local Honcho instances without requiring an API key ([#3644](https://github.com/NousResearch/hermes-agent/pull/3644))
|
| 181 |
+
|
| 182 |
+
---
|
| 183 |
+
|
| 184 |
+
## 🔒 Security & Reliability
|
| 185 |
+
|
| 186 |
+
### Security Hardening
|
| 187 |
+
- **Hardened dangerous command detection** — expanded pattern matching for risky shell commands and added file tool path guards for sensitive locations (`/etc/`, `/boot/`, docker.sock) ([#3872](https://github.com/NousResearch/hermes-agent/pull/3872))
|
| 188 |
+
- **Sensitive path write checks** in approval system — catch writes to system config files through file tools, not just terminal ([#3859](https://github.com/NousResearch/hermes-agent/pull/3859))
|
| 189 |
+
- **Secret redaction expansion** — now covers ElevenLabs, Tavily, and Exa API keys ([#3920](https://github.com/NousResearch/hermes-agent/pull/3920))
|
| 190 |
+
- **Vision file rejection** — reject non-image files passed to vision analysis to prevent information disclosure ([#3845](https://github.com/NousResearch/hermes-agent/pull/3845))
|
| 191 |
+
- **Category path traversal blocking** — prevent directory traversal in skill category names ([#3844](https://github.com/NousResearch/hermes-agent/pull/3844))
|
| 192 |
+
|
| 193 |
+
### Reliability
|
| 194 |
+
- **Atomic config.yaml writes** — prevent data loss during gateway crashes ([#3800](https://github.com/NousResearch/hermes-agent/pull/3800))
|
| 195 |
+
- **Clear __pycache__ on update** — prevent stale bytecode from causing ImportError after updates ([#3819](https://github.com/NousResearch/hermes-agent/pull/3819))
|
| 196 |
+
- **Lazy imports for update safety** — prevent ImportError chains during `hermes update` when modules reference new functions ([#3776](https://github.com/NousResearch/hermes-agent/pull/3776))
|
| 197 |
+
- **Restore terminalbench2 from patch corruption** — recovered file damaged by patch tool's secret redaction ([#3801](https://github.com/NousResearch/hermes-agent/pull/3801))
|
| 198 |
+
- **Terminal timeout preserves partial output** — no more lost command output on timeout ([#3868](https://github.com/NousResearch/hermes-agent/pull/3868))
|
| 199 |
+
|
| 200 |
+
---
|
| 201 |
+
|
| 202 |
+
## 🐛 Notable Bug Fixes
|
| 203 |
+
|
| 204 |
+
- **OpenClaw migration model config overwrite** — migration no longer overwrites model config dict with a string ([#3924](https://github.com/NousResearch/hermes-agent/pull/3924)) — @0xbyt4
|
| 205 |
+
- **OpenClaw migration expanded** — covers full data footprint including sessions, cron, memory ([#3869](https://github.com/NousResearch/hermes-agent/pull/3869))
|
| 206 |
+
- **Telegram deleted reply targets** — gracefully handle replies to deleted messages instead of crashing ([#3858](https://github.com/NousResearch/hermes-agent/pull/3858))
|
| 207 |
+
- **Discord "thinking..." persistence** — properly cleans up deferred response indicators ([#3674](https://github.com/NousResearch/hermes-agent/pull/3674))
|
| 208 |
+
- **WhatsApp LID↔phone aliases** — fixes allowlist matching failures with Linked ID format ([#3830](https://github.com/NousResearch/hermes-agent/pull/3830))
|
| 209 |
+
- **Signal URL-encoded phone numbers** — fixes delivery failures with certain formats ([#3670](https://github.com/NousResearch/hermes-agent/pull/3670))
|
| 210 |
+
- **Email connection leaks** — properly close SMTP/IMAP connections on error ([#3804](https://github.com/NousResearch/hermes-agent/pull/3804))
|
| 211 |
+
- **_safe_print ValueError** — no more gateway thread crashes on closed stdout ([#3843](https://github.com/NousResearch/hermes-agent/pull/3843))
|
| 212 |
+
- **Tool schema KeyError 'name'** — ensure name field always present in tool definitions ([#3811](https://github.com/NousResearch/hermes-agent/pull/3811))
|
| 213 |
+
- **api_mode stale on provider switch** — correctly clear when switching providers via `hermes model` ([#3857](https://github.com/NousResearch/hermes-agent/pull/3857))
|
| 214 |
+
|
| 215 |
+
---
|
| 216 |
+
|
| 217 |
+
## 🧪 Testing
|
| 218 |
+
|
| 219 |
+
- Resolved 10+ CI failures across hooks, tiktoken, plugins, and skill tests ([#3848](https://github.com/NousResearch/hermes-agent/pull/3848), [#3721](https://github.com/NousResearch/hermes-agent/pull/3721), [#3936](https://github.com/NousResearch/hermes-agent/pull/3936))
|
| 220 |
+
|
| 221 |
+
---
|
| 222 |
+
|
| 223 |
+
## 📚 Documentation
|
| 224 |
+
|
| 225 |
+
- **Comprehensive OpenClaw migration guide** — step-by-step guide for migrating from OpenClaw/Claw3D to Hermes Agent ([#3864](https://github.com/NousResearch/hermes-agent/pull/3864), [#3900](https://github.com/NousResearch/hermes-agent/pull/3900))
|
| 226 |
+
- **Credential file passthrough docs** — document how to forward credential files and env vars to remote backends ([#3677](https://github.com/NousResearch/hermes-agent/pull/3677))
|
| 227 |
+
- **DuckDuckGo requirements clarified** — note runtime dependency on duckduckgo-search package ([#3680](https://github.com/NousResearch/hermes-agent/pull/3680))
|
| 228 |
+
- **Skills catalog updated** — added red-teaming category and optional skills listing ([#3745](https://github.com/NousResearch/hermes-agent/pull/3745))
|
| 229 |
+
- **Feishu docs MDX fix** — escape angle-bracket URLs that break Docusaurus build ([#3902](https://github.com/NousResearch/hermes-agent/pull/3902))
|
| 230 |
+
|
| 231 |
+
---
|
| 232 |
+
|
| 233 |
+
## 👥 Contributors
|
| 234 |
+
|
| 235 |
+
### Core
|
| 236 |
+
- **@teknium1** — 90 PRs across all subsystems
|
| 237 |
+
|
| 238 |
+
### Community Contributors
|
| 239 |
+
- **@kshitijk4poor** — 3 PRs: Signal phone number fix ([#3670](https://github.com/NousResearch/hermes-agent/pull/3670)), parallel-cli to optional-skills ([#3673](https://github.com/NousResearch/hermes-agent/pull/3673)), status bar wrapping fix ([#3883](https://github.com/NousResearch/hermes-agent/pull/3883))
|
| 240 |
+
- **@winglian** — 1 PR: Plugin message injection interface ([#3778](https://github.com/NousResearch/hermes-agent/pull/3778))
|
| 241 |
+
- **@binhnt92** — 1 PR: Audio download retry logic ([#3401](https://github.com/NousResearch/hermes-agent/pull/3401))
|
| 242 |
+
- **@0xbyt4** — 1 PR: OpenClaw migration model config fix ([#3924](https://github.com/NousResearch/hermes-agent/pull/3924))
|
| 243 |
+
|
| 244 |
+
### Issues Resolved from Community
|
| 245 |
+
@Material-Scientist ([#850](https://github.com/NousResearch/hermes-agent/issues/850)), @hanxu98121 ([#1734](https://github.com/NousResearch/hermes-agent/issues/1734)), @penwyp ([#1788](https://github.com/NousResearch/hermes-agent/issues/1788)), @dan-and ([#1945](https://github.com/NousResearch/hermes-agent/issues/1945)), @AdrianScott ([#1963](https://github.com/NousResearch/hermes-agent/issues/1963)), @clawdbot47 ([#3229](https://github.com/NousResearch/hermes-agent/issues/3229)), @alanfwilliams ([#3404](https://github.com/NousResearch/hermes-agent/issues/3404)), @kentimsit ([#3433](https://github.com/NousResearch/hermes-agent/issues/3433)), @hayka-pacha ([#3534](https://github.com/NousResearch/hermes-agent/issues/3534)), @primmer ([#3595](https://github.com/NousResearch/hermes-agent/issues/3595)), @dagelf ([#3609](https://github.com/NousResearch/hermes-agent/issues/3609)), @HenkDz ([#3685](https://github.com/NousResearch/hermes-agent/issues/3685)), @tmdgusya ([#3729](https://github.com/NousResearch/hermes-agent/issues/3729)), @TypQxQ ([#3753](https://github.com/NousResearch/hermes-agent/issues/3753)), @acsezen ([#3765](https://github.com/NousResearch/hermes-agent/issues/3765))
|
| 246 |
+
|
| 247 |
+
---
|
| 248 |
+
|
| 249 |
+
**Full Changelog**: [v2026.3.28...v2026.3.30](https://github.com/NousResearch/hermes-agent/compare/v2026.3.28...v2026.3.30)
|
RELEASE_v0.7.0.md
ADDED
|
@@ -0,0 +1,290 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Hermes Agent v0.7.0 (v2026.4.3)
|
| 2 |
+
|
| 3 |
+
**Release Date:** April 3, 2026
|
| 4 |
+
|
| 5 |
+
> The resilience release — pluggable memory providers, credential pool rotation, Camofox anti-detection browser, inline diff previews, gateway hardening across race conditions and approval routing, and deep security fixes across 168 PRs and 46 resolved issues.
|
| 6 |
+
|
| 7 |
+
---
|
| 8 |
+
|
| 9 |
+
## ✨ Highlights
|
| 10 |
+
|
| 11 |
+
- **Pluggable Memory Provider Interface** — Memory is now an extensible plugin system. Third-party memory backends (Honcho, vector stores, custom DBs) implement a simple provider ABC and register via the plugin system. Built-in memory is the default provider. Honcho integration restored to full parity as the reference plugin with profile-scoped host/peer resolution. ([#4623](https://github.com/NousResearch/hermes-agent/pull/4623), [#4616](https://github.com/NousResearch/hermes-agent/pull/4616), [#4355](https://github.com/NousResearch/hermes-agent/pull/4355))
|
| 12 |
+
|
| 13 |
+
- **Same-Provider Credential Pools** — Configure multiple API keys for the same provider with automatic rotation. Thread-safe `least_used` strategy distributes load across keys, and 401 failures trigger automatic rotation to the next credential. Set up via the setup wizard or `credential_pool` config. ([#4188](https://github.com/NousResearch/hermes-agent/pull/4188), [#4300](https://github.com/NousResearch/hermes-agent/pull/4300), [#4361](https://github.com/NousResearch/hermes-agent/pull/4361))
|
| 14 |
+
|
| 15 |
+
- **Camofox Anti-Detection Browser Backend** — New local browser backend using Camoufox for stealth browsing. Persistent sessions with VNC URL discovery for visual debugging, configurable SSRF bypass for local backends, auto-install via `hermes tools`. ([#4008](https://github.com/NousResearch/hermes-agent/pull/4008), [#4419](https://github.com/NousResearch/hermes-agent/pull/4419), [#4292](https://github.com/NousResearch/hermes-agent/pull/4292))
|
| 16 |
+
|
| 17 |
+
- **Inline Diff Previews** — File write and patch operations now show inline diffs in the tool activity feed, giving you visual confirmation of what changed before the agent moves on. ([#4411](https://github.com/NousResearch/hermes-agent/pull/4411), [#4423](https://github.com/NousResearch/hermes-agent/pull/4423))
|
| 18 |
+
|
| 19 |
+
- **API Server Session Continuity & Tool Streaming** — The API server (Open WebUI integration) now streams tool progress events in real-time and supports `X-Hermes-Session-Id` headers for persistent sessions across requests. Sessions persist to the shared SessionDB. ([#4092](https://github.com/NousResearch/hermes-agent/pull/4092), [#4478](https://github.com/NousResearch/hermes-agent/pull/4478), [#4802](https://github.com/NousResearch/hermes-agent/pull/4802))
|
| 20 |
+
|
| 21 |
+
- **ACP: Client-Provided MCP Servers** — Editor integrations (VS Code, Zed, JetBrains) can now register their own MCP servers, which Hermes picks up as additional agent tools. Your editor's MCP ecosystem flows directly into the agent. ([#4705](https://github.com/NousResearch/hermes-agent/pull/4705))
|
| 22 |
+
|
| 23 |
+
- **Gateway Hardening** — Major stability pass across race conditions, photo media delivery, flood control, stuck sessions, approval routing, and compression death spirals. The gateway is substantially more reliable in production. ([#4727](https://github.com/NousResearch/hermes-agent/pull/4727), [#4750](https://github.com/NousResearch/hermes-agent/pull/4750), [#4798](https://github.com/NousResearch/hermes-agent/pull/4798), [#4557](https://github.com/NousResearch/hermes-agent/pull/4557))
|
| 24 |
+
|
| 25 |
+
- **Security: Secret Exfiltration Blocking** — Browser URLs and LLM responses are now scanned for secret patterns, blocking exfiltration attempts via URL encoding, base64, or prompt injection. Credential directory protections expanded to `.docker`, `.azure`, `.config/gh`. Execute_code sandbox output is redacted. ([#4483](https://github.com/NousResearch/hermes-agent/pull/4483), [#4360](https://github.com/NousResearch/hermes-agent/pull/4360), [#4305](https://github.com/NousResearch/hermes-agent/pull/4305), [#4327](https://github.com/NousResearch/hermes-agent/pull/4327))
|
| 26 |
+
|
| 27 |
+
---
|
| 28 |
+
|
| 29 |
+
## 🏗️ Core Agent & Architecture
|
| 30 |
+
|
| 31 |
+
### Provider & Model Support
|
| 32 |
+
- **Same-provider credential pools** — configure multiple API keys with automatic `least_used` rotation and 401 failover ([#4188](https://github.com/NousResearch/hermes-agent/pull/4188), [#4300](https://github.com/NousResearch/hermes-agent/pull/4300))
|
| 33 |
+
- **Credential pool preserved through smart routing** — pool state survives fallback provider switches and defers eager fallback on 429 ([#4361](https://github.com/NousResearch/hermes-agent/pull/4361))
|
| 34 |
+
- **Per-turn primary runtime restoration** — after fallback provider use, the agent automatically restores the primary provider on the next turn with transport recovery ([#4624](https://github.com/NousResearch/hermes-agent/pull/4624))
|
| 35 |
+
- **`developer` role for GPT-5 and Codex models** — uses OpenAI's recommended system message role for newer models ([#4498](https://github.com/NousResearch/hermes-agent/pull/4498))
|
| 36 |
+
- **Google model operational guidance** — Gemini and Gemma models get provider-specific prompting guidance ([#4641](https://github.com/NousResearch/hermes-agent/pull/4641))
|
| 37 |
+
- **Anthropic long-context tier 429 handling** — automatically reduces context to 200k when hitting tier limits ([#4747](https://github.com/NousResearch/hermes-agent/pull/4747))
|
| 38 |
+
- **URL-based auth for third-party Anthropic endpoints** + CI test fixes ([#4148](https://github.com/NousResearch/hermes-agent/pull/4148))
|
| 39 |
+
- **Bearer auth for MiniMax Anthropic endpoints** ([#4028](https://github.com/NousResearch/hermes-agent/pull/4028))
|
| 40 |
+
- **Fireworks context length detection** ([#4158](https://github.com/NousResearch/hermes-agent/pull/4158))
|
| 41 |
+
- **Standard DashScope international endpoint** for Alibaba provider ([#4133](https://github.com/NousResearch/hermes-agent/pull/4133), closes [#3912](https://github.com/NousResearch/hermes-agent/issues/3912))
|
| 42 |
+
- **Custom providers context_length** honored in hygiene compression ([#4085](https://github.com/NousResearch/hermes-agent/pull/4085))
|
| 43 |
+
- **Non-sk-ant keys** treated as regular API keys, not OAuth tokens ([#4093](https://github.com/NousResearch/hermes-agent/pull/4093))
|
| 44 |
+
- **Claude-sonnet-4.6** added to OpenRouter and Nous model lists ([#4157](https://github.com/NousResearch/hermes-agent/pull/4157))
|
| 45 |
+
- **Qwen 3.6 Plus Preview** added to model lists ([#4376](https://github.com/NousResearch/hermes-agent/pull/4376))
|
| 46 |
+
- **MiniMax M2.7** added to hermes model picker and OpenCode ([#4208](https://github.com/NousResearch/hermes-agent/pull/4208))
|
| 47 |
+
- **Auto-detect models from server probe** in custom endpoint setup ([#4218](https://github.com/NousResearch/hermes-agent/pull/4218))
|
| 48 |
+
- **Config.yaml single source of truth** for endpoint URLs — no more env var vs config.yaml conflicts ([#4165](https://github.com/NousResearch/hermes-agent/pull/4165))
|
| 49 |
+
- **Setup wizard no longer overwrites** custom endpoint config ([#4180](https://github.com/NousResearch/hermes-agent/pull/4180), closes [#4172](https://github.com/NousResearch/hermes-agent/issues/4172))
|
| 50 |
+
- **Unified setup wizard provider selection** with `hermes model` — single code path for both flows ([#4200](https://github.com/NousResearch/hermes-agent/pull/4200))
|
| 51 |
+
- **Root-level provider config** no longer overrides `model.provider` ([#4329](https://github.com/NousResearch/hermes-agent/pull/4329))
|
| 52 |
+
- **Rate-limit pairing rejection messages** to prevent spam ([#4081](https://github.com/NousResearch/hermes-agent/pull/4081))
|
| 53 |
+
|
| 54 |
+
### Agent Loop & Conversation
|
| 55 |
+
- **Preserve Anthropic thinking block signatures** across tool-use turns ([#4626](https://github.com/NousResearch/hermes-agent/pull/4626))
|
| 56 |
+
- **Classify think-only empty responses** before retrying — prevents infinite retry loops on models that produce thinking blocks without content ([#4645](https://github.com/NousResearch/hermes-agent/pull/4645))
|
| 57 |
+
- **Prevent compression death spiral** from API disconnects — stops the loop where compression triggers, fails, compresses again ([#4750](https://github.com/NousResearch/hermes-agent/pull/4750), closes [#2153](https://github.com/NousResearch/hermes-agent/issues/2153))
|
| 58 |
+
- **Persist compressed context** to gateway session after mid-run compression ([#4095](https://github.com/NousResearch/hermes-agent/pull/4095))
|
| 59 |
+
- **Context-exceeded error messages** now include actionable guidance ([#4155](https://github.com/NousResearch/hermes-agent/pull/4155), closes [#4061](https://github.com/NousResearch/hermes-agent/issues/4061))
|
| 60 |
+
- **Strip orphaned think/reasoning tags** from user-facing responses ([#4311](https://github.com/NousResearch/hermes-agent/pull/4311), closes [#4285](https://github.com/NousResearch/hermes-agent/issues/4285))
|
| 61 |
+
- **Harden Codex responses preflight** and stream error handling ([#4313](https://github.com/NousResearch/hermes-agent/pull/4313))
|
| 62 |
+
- **Deterministic call_id fallbacks** instead of random UUIDs for prompt cache consistency ([#3991](https://github.com/NousResearch/hermes-agent/pull/3991))
|
| 63 |
+
- **Context pressure warning spam** prevented after compression ([#4012](https://github.com/NousResearch/hermes-agent/pull/4012))
|
| 64 |
+
- **AsyncOpenAI created lazily** in trajectory compressor to avoid closed event loop errors ([#4013](https://github.com/NousResearch/hermes-agent/pull/4013))
|
| 65 |
+
|
| 66 |
+
### Memory & Sessions
|
| 67 |
+
- **Pluggable memory provider interface** — ABC-based plugin system for custom memory backends with profile isolation ([#4623](https://github.com/NousResearch/hermes-agent/pull/4623))
|
| 68 |
+
- **Honcho full integration parity** restored as reference memory provider plugin ([#4355](https://github.com/NousResearch/hermes-agent/pull/4355)) — @erosika
|
| 69 |
+
- **Honcho profile-scoped** host and peer resolution ([#4616](https://github.com/NousResearch/hermes-agent/pull/4616))
|
| 70 |
+
- **Memory flush state persisted** to prevent redundant re-flushes on gateway restart ([#4481](https://github.com/NousResearch/hermes-agent/pull/4481))
|
| 71 |
+
- **Memory provider tools** routed through sequential execution path ([#4803](https://github.com/NousResearch/hermes-agent/pull/4803))
|
| 72 |
+
- **Honcho config** written to instance-local path for profile isolation ([#4037](https://github.com/NousResearch/hermes-agent/pull/4037))
|
| 73 |
+
- **API server sessions** persist to shared SessionDB ([#4802](https://github.com/NousResearch/hermes-agent/pull/4802))
|
| 74 |
+
- **Token usage persisted** for non-CLI sessions ([#4627](https://github.com/NousResearch/hermes-agent/pull/4627))
|
| 75 |
+
- **Quote dotted terms in FTS5 queries** — fixes session search for terms containing dots ([#4549](https://github.com/NousResearch/hermes-agent/pull/4549))
|
| 76 |
+
|
| 77 |
+
---
|
| 78 |
+
|
| 79 |
+
## 📱 Messaging Platforms (Gateway)
|
| 80 |
+
|
| 81 |
+
### Gateway Core
|
| 82 |
+
- **Race condition fixes** — photo media loss, flood control, stuck sessions, and STT config issues resolved in one hardening pass ([#4727](https://github.com/NousResearch/hermes-agent/pull/4727))
|
| 83 |
+
- **Approval routing through running-agent guard** — `/approve` and `/deny` now route correctly when the agent is blocked waiting for approval instead of being swallowed as interrupts ([#4798](https://github.com/NousResearch/hermes-agent/pull/4798), [#4557](https://github.com/NousResearch/hermes-agent/pull/4557), closes [#4542](https://github.com/NousResearch/hermes-agent/issues/4542))
|
| 84 |
+
- **Resume agent after /approve** — tool result is no longer lost when executing blocked commands ([#4418](https://github.com/NousResearch/hermes-agent/pull/4418))
|
| 85 |
+
- **DM thread sessions seeded** with parent transcript to preserve context ([#4559](https://github.com/NousResearch/hermes-agent/pull/4559))
|
| 86 |
+
- **Skill-aware slash commands** — gateway dynamically registers installed skills as slash commands with paginated `/commands` list and Telegram 100-command cap ([#3934](https://github.com/NousResearch/hermes-agent/pull/3934), [#4005](https://github.com/NousResearch/hermes-agent/pull/4005), [#4006](https://github.com/NousResearch/hermes-agent/pull/4006), [#4010](https://github.com/NousResearch/hermes-agent/pull/4010), [#4023](https://github.com/NousResearch/hermes-agent/pull/4023))
|
| 87 |
+
- **Per-platform disabled skills** respected in Telegram menu and gateway dispatch ([#4799](https://github.com/NousResearch/hermes-agent/pull/4799))
|
| 88 |
+
- **Remove user-facing compression warnings** — cleaner message flow ([#4139](https://github.com/NousResearch/hermes-agent/pull/4139))
|
| 89 |
+
- **`-v/-q` flags wired to stderr logging** for gateway service ([#4474](https://github.com/NousResearch/hermes-agent/pull/4474))
|
| 90 |
+
- **HERMES_HOME remapped** to target user in system service unit ([#4456](https://github.com/NousResearch/hermes-agent/pull/4456))
|
| 91 |
+
- **Honor default for invalid bool-like config values** ([#4029](https://github.com/NousResearch/hermes-agent/pull/4029))
|
| 92 |
+
- **setsid instead of systemd-run** for `/update` command to avoid systemd permission issues ([#4104](https://github.com/NousResearch/hermes-agent/pull/4104), closes [#4017](https://github.com/NousResearch/hermes-agent/issues/4017))
|
| 93 |
+
- **'Initializing agent...'** shown on first message for better UX ([#4086](https://github.com/NousResearch/hermes-agent/pull/4086))
|
| 94 |
+
- **Allow running gateway service as root** for LXC/container environments ([#4732](https://github.com/NousResearch/hermes-agent/pull/4732))
|
| 95 |
+
|
| 96 |
+
### Telegram
|
| 97 |
+
- **32-char limit on command names** with collision avoidance ([#4211](https://github.com/NousResearch/hermes-agent/pull/4211))
|
| 98 |
+
- **Priority order enforced** in menu — core > plugins > skills ([#4023](https://github.com/NousResearch/hermes-agent/pull/4023))
|
| 99 |
+
- **Capped at 50 commands** — API rejects above ~60 ([#4006](https://github.com/NousResearch/hermes-agent/pull/4006))
|
| 100 |
+
- **Skip empty/whitespace text** to prevent 400 errors ([#4388](https://github.com/NousResearch/hermes-agent/pull/4388))
|
| 101 |
+
- **E2E gateway tests** added ([#4497](https://github.com/NousResearch/hermes-agent/pull/4497)) — @pefontana
|
| 102 |
+
|
| 103 |
+
### Discord
|
| 104 |
+
- **Button-based approval UI** — register `/approve` and `/deny` slash commands with interactive button prompts ([#4800](https://github.com/NousResearch/hermes-agent/pull/4800))
|
| 105 |
+
- **Configurable reactions** — `discord.reactions` config option to disable message processing reactions ([#4199](https://github.com/NousResearch/hermes-agent/pull/4199))
|
| 106 |
+
- **Skip reactions and auto-threading** for unauthorized users ([#4387](https://github.com/NousResearch/hermes-agent/pull/4387))
|
| 107 |
+
|
| 108 |
+
### Slack
|
| 109 |
+
- **Reply in thread** — `slack.reply_in_thread` config option for threaded responses ([#4643](https://github.com/NousResearch/hermes-agent/pull/4643), closes [#2662](https://github.com/NousResearch/hermes-agent/issues/2662))
|
| 110 |
+
|
| 111 |
+
### WhatsApp
|
| 112 |
+
- **Enforce require_mention in group chats** ([#4730](https://github.com/NousResearch/hermes-agent/pull/4730))
|
| 113 |
+
|
| 114 |
+
### Webhook
|
| 115 |
+
- **Platform support fixes** — skip home channel prompt, disable tool progress for webhook adapters ([#4660](https://github.com/NousResearch/hermes-agent/pull/4660))
|
| 116 |
+
|
| 117 |
+
### Matrix
|
| 118 |
+
- **E2EE decryption hardening** — request missing keys, auto-trust devices, retry buffered events ([#4083](https://github.com/NousResearch/hermes-agent/pull/4083))
|
| 119 |
+
|
| 120 |
+
---
|
| 121 |
+
|
| 122 |
+
## 🖥️ CLI & User Experience
|
| 123 |
+
|
| 124 |
+
### New Slash Commands
|
| 125 |
+
- **`/yolo`** — toggle dangerous command approvals on/off for the session ([#3990](https://github.com/NousResearch/hermes-agent/pull/3990))
|
| 126 |
+
- **`/btw`** — ephemeral side questions that don't affect the main conversation context ([#4161](https://github.com/NousResearch/hermes-agent/pull/4161))
|
| 127 |
+
- **`/profile`** — show active profile info without leaving the chat session ([#4027](https://github.com/NousResearch/hermes-agent/pull/4027))
|
| 128 |
+
|
| 129 |
+
### Interactive CLI
|
| 130 |
+
- **Inline diff previews** for write and patch operations in the tool activity feed ([#4411](https://github.com/NousResearch/hermes-agent/pull/4411), [#4423](https://github.com/NousResearch/hermes-agent/pull/4423))
|
| 131 |
+
- **TUI pinned to bottom** on startup — no more large blank spaces between response and input ([#4412](https://github.com/NousResearch/hermes-agent/pull/4412), [#4359](https://github.com/NousResearch/hermes-agent/pull/4359), closes [#4398](https://github.com/NousResearch/hermes-agent/issues/4398), [#4421](https://github.com/NousResearch/hermes-agent/issues/4421))
|
| 132 |
+
- **`/history` and `/resume`** now surface recent sessions directly instead of requiring search ([#4728](https://github.com/NousResearch/hermes-agent/pull/4728))
|
| 133 |
+
- **Cache tokens shown** in `/insights` overview so total adds up ([#4428](https://github.com/NousResearch/hermes-agent/pull/4428))
|
| 134 |
+
- **`--max-turns` CLI flag** for `hermes chat` to limit agent iterations ([#4314](https://github.com/NousResearch/hermes-agent/pull/4314))
|
| 135 |
+
- **Detect dragged file paths** instead of treating them as slash commands ([#4533](https://github.com/NousResearch/hermes-agent/pull/4533)) — @rolme
|
| 136 |
+
- **Allow empty strings and falsy values** in `config set` ([#4310](https://github.com/NousResearch/hermes-agent/pull/4310), closes [#4277](https://github.com/NousResearch/hermes-agent/issues/4277))
|
| 137 |
+
- **Voice mode in WSL** when PulseAudio bridge is configured ([#4317](https://github.com/NousResearch/hermes-agent/pull/4317))
|
| 138 |
+
- **Respect `NO_COLOR` env var** and `TERM=dumb` for accessibility ([#4079](https://github.com/NousResearch/hermes-agent/pull/4079), closes [#4066](https://github.com/NousResearch/hermes-agent/issues/4066)) — @SHL0MS
|
| 139 |
+
- **Correct shell reload instruction** for macOS/zsh users ([#4025](https://github.com/NousResearch/hermes-agent/pull/4025))
|
| 140 |
+
- **Zero exit code** on successful quiet mode queries ([#4613](https://github.com/NousResearch/hermes-agent/pull/4613), closes [#4601](https://github.com/NousResearch/hermes-agent/issues/4601)) — @devorun
|
| 141 |
+
- **on_session_end hook fires** on interrupted exits ([#4159](https://github.com/NousResearch/hermes-agent/pull/4159))
|
| 142 |
+
- **Profile list display** reads `model.default` key correctly ([#4160](https://github.com/NousResearch/hermes-agent/pull/4160))
|
| 143 |
+
- **Browser and TTS** shown in reconfigure menu ([#4041](https://github.com/NousResearch/hermes-agent/pull/4041))
|
| 144 |
+
- **Web backend priority** detection simplified ([#4036](https://github.com/NousResearch/hermes-agent/pull/4036))
|
| 145 |
+
|
| 146 |
+
### Setup & Configuration
|
| 147 |
+
- **Allowed_users preserved** during setup and quiet unconfigured provider warnings ([#4551](https://github.com/NousResearch/hermes-agent/pull/4551)) — @kshitijk4poor
|
| 148 |
+
- **Save API key to model config** for custom endpoints ([#4202](https://github.com/NousResearch/hermes-agent/pull/4202), closes [#4182](https://github.com/NousResearch/hermes-agent/issues/4182))
|
| 149 |
+
- **Claude Code credentials gated** behind explicit Hermes config in wizard trigger ([#4210](https://github.com/NousResearch/hermes-agent/pull/4210))
|
| 150 |
+
- **Atomic writes in save_config_value** to prevent config loss on interrupt ([#4298](https://github.com/NousResearch/hermes-agent/pull/4298), [#4320](https://github.com/NousResearch/hermes-agent/pull/4320))
|
| 151 |
+
- **Scopes field written** to Claude Code credentials on token refresh ([#4126](https://github.com/NousResearch/hermes-agent/pull/4126))
|
| 152 |
+
|
| 153 |
+
### Update System
|
| 154 |
+
- **Fork detection and upstream sync** in `hermes update` ([#4744](https://github.com/NousResearch/hermes-agent/pull/4744))
|
| 155 |
+
- **Preserve working optional extras** when one extra fails during update ([#4550](https://github.com/NousResearch/hermes-agent/pull/4550))
|
| 156 |
+
- **Handle conflicted git index** during hermes update ([#4735](https://github.com/NousResearch/hermes-agent/pull/4735))
|
| 157 |
+
- **Avoid launchd restart race** on macOS ([#4736](https://github.com/NousResearch/hermes-agent/pull/4736))
|
| 158 |
+
- **Missing subprocess.run() timeouts** added to doctor and status commands ([#4009](https://github.com/NousResearch/hermes-agent/pull/4009))
|
| 159 |
+
|
| 160 |
+
---
|
| 161 |
+
|
| 162 |
+
## 🔧 Tool System
|
| 163 |
+
|
| 164 |
+
### Browser
|
| 165 |
+
- **Camofox anti-detection browser backend** — local stealth browsing with auto-install via `hermes tools` ([#4008](https://github.com/NousResearch/hermes-agent/pull/4008))
|
| 166 |
+
- **Persistent Camofox sessions** with VNC URL discovery for visual debugging ([#4419](https://github.com/NousResearch/hermes-agent/pull/4419))
|
| 167 |
+
- **Skip SSRF check for local backends** (Camofox, headless Chromium) ([#4292](https://github.com/NousResearch/hermes-agent/pull/4292))
|
| 168 |
+
- **Configurable SSRF check** via `browser.allow_private_urls` ([#4198](https://github.com/NousResearch/hermes-agent/pull/4198)) — @nils010485
|
| 169 |
+
- **CAMOFOX_PORT=9377** added to Docker commands ([#4340](https://github.com/NousResearch/hermes-agent/pull/4340))
|
| 170 |
+
|
| 171 |
+
### File Operations
|
| 172 |
+
- **Inline diff previews** on write and patch actions ([#4411](https://github.com/NousResearch/hermes-agent/pull/4411), [#4423](https://github.com/NousResearch/hermes-agent/pull/4423))
|
| 173 |
+
- **Stale file detection** on write and patch — warns when file was modified externally since last read ([#4345](https://github.com/NousResearch/hermes-agent/pull/4345))
|
| 174 |
+
- **Staleness timestamp refreshed** after writes ([#4390](https://github.com/NousResearch/hermes-agent/pull/4390))
|
| 175 |
+
- **Size guard, dedup, and device blocking** on read_file ([#4315](https://github.com/NousResearch/hermes-agent/pull/4315))
|
| 176 |
+
|
| 177 |
+
### MCP
|
| 178 |
+
- **Stability fix pack** — reload timeout, shutdown cleanup, event loop handler, OAuth non-blocking ([#4757](https://github.com/NousResearch/hermes-agent/pull/4757), closes [#4462](https://github.com/NousResearch/hermes-agent/issues/4462), [#2537](https://github.com/NousResearch/hermes-agent/issues/2537))
|
| 179 |
+
|
| 180 |
+
### ACP (Editor Integration)
|
| 181 |
+
- **Client-provided MCP servers** registered as agent tools — editors pass their MCP servers to Hermes ([#4705](https://github.com/NousResearch/hermes-agent/pull/4705))
|
| 182 |
+
|
| 183 |
+
### Skills System
|
| 184 |
+
- **Size limits for agent writes** and **fuzzy matching for skill patch** — prevents oversized skill writes and improves edit reliability ([#4414](https://github.com/NousResearch/hermes-agent/pull/4414))
|
| 185 |
+
- **Validate hub bundle paths** before install — blocks path traversal in skill bundles ([#3986](https://github.com/NousResearch/hermes-agent/pull/3986))
|
| 186 |
+
- **Unified hermes-agent and hermes-agent-setup** into single skill ([#4332](https://github.com/NousResearch/hermes-agent/pull/4332))
|
| 187 |
+
- **Skill metadata type check** in extract_skill_conditions ([#4479](https://github.com/NousResearch/hermes-agent/pull/4479))
|
| 188 |
+
|
| 189 |
+
### New/Updated Skills
|
| 190 |
+
- **research-paper-writing** — full end-to-end research pipeline (replaced ml-paper-writing) ([#4654](https://github.com/NousResearch/hermes-agent/pull/4654)) — @SHL0MS
|
| 191 |
+
- **ascii-video** — text readability techniques and external layout oracle ([#4054](https://github.com/NousResearch/hermes-agent/pull/4054)) — @SHL0MS
|
| 192 |
+
- **youtube-transcript** updated for youtube-transcript-api v1.x ([#4455](https://github.com/NousResearch/hermes-agent/pull/4455)) — @el-analista
|
| 193 |
+
- **Skills browse and search page** added to documentation site ([#4500](https://github.com/NousResearch/hermes-agent/pull/4500)) — @IAvecilla
|
| 194 |
+
|
| 195 |
+
---
|
| 196 |
+
|
| 197 |
+
## 🔒 Security & Reliability
|
| 198 |
+
|
| 199 |
+
### Security Hardening
|
| 200 |
+
- **Block secret exfiltration** via browser URLs and LLM responses — scans for secret patterns in URL encoding, base64, and prompt injection vectors ([#4483](https://github.com/NousResearch/hermes-agent/pull/4483))
|
| 201 |
+
- **Redact secrets from execute_code sandbox output** ([#4360](https://github.com/NousResearch/hermes-agent/pull/4360))
|
| 202 |
+
- **Protect `.docker`, `.azure`, `.config/gh` credential directories** from read/write via file tools and terminal ([#4305](https://github.com/NousResearch/hermes-agent/pull/4305), [#4327](https://github.com/NousResearch/hermes-agent/pull/4327)) — @memosr
|
| 203 |
+
- **GitHub OAuth token patterns** added to redaction + snapshot redact flag ([#4295](https://github.com/NousResearch/hermes-agent/pull/4295))
|
| 204 |
+
- **Reject private and loopback IPs** in Telegram DoH fallback ([#4129](https://github.com/NousResearch/hermes-agent/pull/4129))
|
| 205 |
+
- **Reject path traversal** in credential file registration ([#4316](https://github.com/NousResearch/hermes-agent/pull/4316))
|
| 206 |
+
- **Validate tar archive member paths** on profile import — blocks zip-slip attacks ([#4318](https://github.com/NousResearch/hermes-agent/pull/4318))
|
| 207 |
+
- **Exclude auth.json and .env** from profile exports ([#4475](https://github.com/NousResearch/hermes-agent/pull/4475))
|
| 208 |
+
|
| 209 |
+
### Reliability
|
| 210 |
+
- **Prevent compression death spiral** from API disconnects ([#4750](https://github.com/NousResearch/hermes-agent/pull/4750), closes [#2153](https://github.com/NousResearch/hermes-agent/issues/2153))
|
| 211 |
+
- **Handle `is_closed` as method** in OpenAI SDK — prevents false positive client closure detection ([#4416](https://github.com/NousResearch/hermes-agent/pull/4416), closes [#4377](https://github.com/NousResearch/hermes-agent/issues/4377))
|
| 212 |
+
- **Exclude matrix from [all] extras** — python-olm is upstream-broken, prevents install failures ([#4615](https://github.com/NousResearch/hermes-agent/pull/4615), closes [#4178](https://github.com/NousResearch/hermes-agent/issues/4178))
|
| 213 |
+
- **OpenCode model routing** repaired ([#4508](https://github.com/NousResearch/hermes-agent/pull/4508))
|
| 214 |
+
- **Docker container image** optimized ([#4034](https://github.com/NousResearch/hermes-agent/pull/4034)) — @bcross
|
| 215 |
+
|
| 216 |
+
### Windows & Cross-Platform
|
| 217 |
+
- **Voice mode in WSL** with PulseAudio bridge ([#4317](https://github.com/NousResearch/hermes-agent/pull/4317))
|
| 218 |
+
- **Homebrew packaging** preparation ([#4099](https://github.com/NousResearch/hermes-agent/pull/4099))
|
| 219 |
+
- **CI fork conditionals** to prevent workflow failures on forks ([#4107](https://github.com/NousResearch/hermes-agent/pull/4107))
|
| 220 |
+
|
| 221 |
+
---
|
| 222 |
+
|
| 223 |
+
## 🐛 Notable Bug Fixes
|
| 224 |
+
|
| 225 |
+
- **Gateway approval blocked agent thread** — approval now blocks the agent thread like CLI does, preventing tool result loss ([#4557](https://github.com/NousResearch/hermes-agent/pull/4557), closes [#4542](https://github.com/NousResearch/hermes-agent/issues/4542))
|
| 226 |
+
- **Compression death spiral** from API disconnects — detected and halted instead of looping ([#4750](https://github.com/NousResearch/hermes-agent/pull/4750), closes [#2153](https://github.com/NousResearch/hermes-agent/issues/2153))
|
| 227 |
+
- **Anthropic thinking blocks lost** across tool-use turns ([#4626](https://github.com/NousResearch/hermes-agent/pull/4626))
|
| 228 |
+
- **Profile model config ignored** with `-p` flag — model.model now promoted to model.default correctly ([#4160](https://github.com/NousResearch/hermes-agent/pull/4160), closes [#4486](https://github.com/NousResearch/hermes-agent/issues/4486))
|
| 229 |
+
- **CLI blank space** between response and input area ([#4412](https://github.com/NousResearch/hermes-agent/pull/4412), [#4359](https://github.com/NousResearch/hermes-agent/pull/4359), closes [#4398](https://github.com/NousResearch/hermes-agent/issues/4398))
|
| 230 |
+
- **Dragged file paths** treated as slash commands instead of file references ([#4533](https://github.com/NousResearch/hermes-agent/pull/4533)) — @rolme
|
| 231 |
+
- **Orphaned `</think>` tags** leaking into user-facing responses ([#4311](https://github.com/NousResearch/hermes-agent/pull/4311), closes [#4285](https://github.com/NousResearch/hermes-agent/issues/4285))
|
| 232 |
+
- **OpenAI SDK `is_closed`** is a method not property — false positive client closure ([#4416](https://github.com/NousResearch/hermes-agent/pull/4416), closes [#4377](https://github.com/NousResearch/hermes-agent/issues/4377))
|
| 233 |
+
- **MCP OAuth server** could block Hermes startup instead of degrading gracefully ([#4757](https://github.com/NousResearch/hermes-agent/pull/4757), closes [#4462](https://github.com/NousResearch/hermes-agent/issues/4462))
|
| 234 |
+
- **MCP event loop closed** on shutdown with HTTP servers ([#4757](https://github.com/NousResearch/hermes-agent/pull/4757), closes [#2537](https://github.com/NousResearch/hermes-agent/issues/2537))
|
| 235 |
+
- **Alibaba provider** hardcoded to wrong endpoint ([#4133](https://github.com/NousResearch/hermes-agent/pull/4133), closes [#3912](https://github.com/NousResearch/hermes-agent/issues/3912))
|
| 236 |
+
- **Slack reply_in_thread** missing config option ([#4643](https://github.com/NousResearch/hermes-agent/pull/4643), closes [#2662](https://github.com/NousResearch/hermes-agent/issues/2662))
|
| 237 |
+
- **Quiet mode exit code** — successful `-q` queries no longer exit nonzero ([#4613](https://github.com/NousResearch/hermes-agent/pull/4613), closes [#4601](https://github.com/NousResearch/hermes-agent/issues/4601))
|
| 238 |
+
- **Mobile sidebar** shows only close button due to backdrop-filter issue in docs site ([#4207](https://github.com/NousResearch/hermes-agent/pull/4207)) — @xsmyile
|
| 239 |
+
- **Config restore reverted** by stale-branch squash merge — `_config_version` fixed ([#4440](https://github.com/NousResearch/hermes-agent/pull/4440))
|
| 240 |
+
|
| 241 |
+
---
|
| 242 |
+
|
| 243 |
+
## 🧪 Testing
|
| 244 |
+
|
| 245 |
+
- **Telegram gateway E2E tests** — full integration test suite for the Telegram adapter ([#4497](https://github.com/NousResearch/hermes-agent/pull/4497)) — @pefontana
|
| 246 |
+
- **11 real test failures fixed** plus sys.modules cascade poisoner resolved ([#4570](https://github.com/NousResearch/hermes-agent/pull/4570))
|
| 247 |
+
- **7 CI failures resolved** across hooks, plugins, and skill tests ([#3936](https://github.com/NousResearch/hermes-agent/pull/3936))
|
| 248 |
+
- **Codex 401 refresh tests** updated for CI compatibility ([#4166](https://github.com/NousResearch/hermes-agent/pull/4166))
|
| 249 |
+
- **Stale OPENAI_BASE_URL test** fixed ([#4217](https://github.com/NousResearch/hermes-agent/pull/4217))
|
| 250 |
+
|
| 251 |
+
---
|
| 252 |
+
|
| 253 |
+
## 📚 Documentation
|
| 254 |
+
|
| 255 |
+
- **Comprehensive documentation audit** — 9 HIGH and 20+ MEDIUM gaps fixed across 21 files ([#4087](https://github.com/NousResearch/hermes-agent/pull/4087))
|
| 256 |
+
- **Site navigation restructured** — features and platforms promoted to top-level ([#4116](https://github.com/NousResearch/hermes-agent/pull/4116))
|
| 257 |
+
- **Tool progress streaming** documented for API server and Open WebUI ([#4138](https://github.com/NousResearch/hermes-agent/pull/4138))
|
| 258 |
+
- **Telegram webhook mode** documentation ([#4089](https://github.com/NousResearch/hermes-agent/pull/4089))
|
| 259 |
+
- **Local LLM provider guides** — comprehensive setup guides with context length warnings ([#4294](https://github.com/NousResearch/hermes-agent/pull/4294))
|
| 260 |
+
- **WhatsApp allowlist behavior** clarified with `WHATSAPP_ALLOW_ALL_USERS` documentation ([#4293](https://github.com/NousResearch/hermes-agent/pull/4293))
|
| 261 |
+
- **Slack configuration options** — new config section in Slack docs ([#4644](https://github.com/NousResearch/hermes-agent/pull/4644))
|
| 262 |
+
- **Terminal backends section** expanded + docs build fixes ([#4016](https://github.com/NousResearch/hermes-agent/pull/4016))
|
| 263 |
+
- **Adding-providers guide** updated for unified setup flow ([#4201](https://github.com/NousResearch/hermes-agent/pull/4201))
|
| 264 |
+
- **ACP Zed config** fixed ([#4743](https://github.com/NousResearch/hermes-agent/pull/4743))
|
| 265 |
+
- **Community FAQ** entries for common workflows and troubleshooting ([#4797](https://github.com/NousResearch/hermes-agent/pull/4797))
|
| 266 |
+
- **Skills browse and search page** on docs site ([#4500](https://github.com/NousResearch/hermes-agent/pull/4500)) — @IAvecilla
|
| 267 |
+
|
| 268 |
+
---
|
| 269 |
+
|
| 270 |
+
## 👥 Contributors
|
| 271 |
+
|
| 272 |
+
### Core
|
| 273 |
+
- **@teknium1** — 135 commits across all subsystems
|
| 274 |
+
|
| 275 |
+
### Top Community Contributors
|
| 276 |
+
- **@kshitijk4poor** — 13 commits: preserve allowed_users during setup ([#4551](https://github.com/NousResearch/hermes-agent/pull/4551)), and various fixes
|
| 277 |
+
- **@erosika** — 12 commits: Honcho full integration parity restored as memory provider plugin ([#4355](https://github.com/NousResearch/hermes-agent/pull/4355))
|
| 278 |
+
- **@pefontana** — 9 commits: Telegram gateway E2E test suite ([#4497](https://github.com/NousResearch/hermes-agent/pull/4497))
|
| 279 |
+
- **@bcross** — 5 commits: Docker container image optimization ([#4034](https://github.com/NousResearch/hermes-agent/pull/4034))
|
| 280 |
+
- **@SHL0MS** — 4 commits: NO_COLOR/TERM=dumb support ([#4079](https://github.com/NousResearch/hermes-agent/pull/4079)), ascii-video skill updates ([#4054](https://github.com/NousResearch/hermes-agent/pull/4054)), research-paper-writing skill ([#4654](https://github.com/NousResearch/hermes-agent/pull/4654))
|
| 281 |
+
|
| 282 |
+
### All Contributors
|
| 283 |
+
@0xbyt4, @arasovic, @Bartok9, @bcross, @binhnt92, @camden-lowrance, @curtitoo, @Dakota, @Dave Tist, @Dean Kerr, @devorun, @dieutx, @Dilee, @el-analista, @erosika, @Gutslabs, @IAvecilla, @Jack, @Johannnnn506, @kshitijk4poor, @Laura Batalha, @Leegenux, @Lume, @MacroAnarchy, @maymuneth, @memosr, @NexVeridian, @Nick, @nils010485, @pefontana, @Penov, @rolme, @SHL0MS, @txchen, @xsmyile
|
| 284 |
+
|
| 285 |
+
### Issues Resolved from Community
|
| 286 |
+
@acsezen ([#2537](https://github.com/NousResearch/hermes-agent/issues/2537)), @arasovic ([#4285](https://github.com/NousResearch/hermes-agent/issues/4285)), @camden-lowrance ([#4462](https://github.com/NousResearch/hermes-agent/issues/4462)), @devorun ([#4601](https://github.com/NousResearch/hermes-agent/issues/4601)), @eloklam ([#4486](https://github.com/NousResearch/hermes-agent/issues/4486)), @HenkDz ([#3719](https://github.com/NousResearch/hermes-agent/issues/3719)), @hypotyposis ([#2153](https://github.com/NousResearch/hermes-agent/issues/2153)), @kazamak ([#4178](https://github.com/NousResearch/hermes-agent/issues/4178)), @lstep ([#4366](https://github.com/NousResearch/hermes-agent/issues/4366)), @Mark-Lok ([#4542](https://github.com/NousResearch/hermes-agent/issues/4542)), @NoJster ([#4421](https://github.com/NousResearch/hermes-agent/issues/4421)), @patp ([#2662](https://github.com/NousResearch/hermes-agent/issues/2662)), @pr0n ([#4601](https://github.com/NousResearch/hermes-agent/issues/4601)), @saulmc ([#4377](https://github.com/NousResearch/hermes-agent/issues/4377)), @SHL0MS ([#4060](https://github.com/NousResearch/hermes-agent/issues/4060), [#4061](https://github.com/NousResearch/hermes-agent/issues/4061), [#4066](https://github.com/NousResearch/hermes-agent/issues/4066), [#4172](https://github.com/NousResearch/hermes-agent/issues/4172), [#4277](https://github.com/NousResearch/hermes-agent/issues/4277)), @Z-Mackintosh ([#4398](https://github.com/NousResearch/hermes-agent/issues/4398))
|
| 287 |
+
|
| 288 |
+
---
|
| 289 |
+
|
| 290 |
+
**Full Changelog**: [v2026.3.30...v2026.4.3](https://github.com/NousResearch/hermes-agent/compare/v2026.3.30...v2026.4.3)
|
RELEASE_v0.8.0.md
ADDED
|
@@ -0,0 +1,346 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Hermes Agent v0.8.0 (v2026.4.8)
|
| 2 |
+
|
| 3 |
+
**Release Date:** April 8, 2026
|
| 4 |
+
|
| 5 |
+
> The intelligence release — background task auto-notifications, free MiMo v2 Pro on Nous Portal, live model switching across all platforms, self-optimized GPT/Codex guidance, native Google AI Studio, smart inactivity timeouts, approval buttons, MCP OAuth 2.1, and 209 merged PRs with 82 resolved issues.
|
| 6 |
+
|
| 7 |
+
---
|
| 8 |
+
|
| 9 |
+
## ✨ Highlights
|
| 10 |
+
|
| 11 |
+
- **Background Process Auto-Notifications (`notify_on_complete`)** — Background tasks can now automatically notify the agent when they finish. Start a long-running process (AI model training, test suites, deployments, builds) and the agent gets notified on completion — no polling needed. The agent can keep working on other things and pick up results when they land. ([#5779](https://github.com/NousResearch/hermes-agent/pull/5779))
|
| 12 |
+
|
| 13 |
+
- **Free Xiaomi MiMo v2 Pro on Nous Portal** — Nous Portal now supports the free-tier Xiaomi MiMo v2 Pro model for auxiliary tasks (compression, vision, summarization), with free-tier model gating and pricing display in model selection. ([#6018](https://github.com/NousResearch/hermes-agent/pull/6018), [#5880](https://github.com/NousResearch/hermes-agent/pull/5880))
|
| 14 |
+
|
| 15 |
+
- **Live Model Switching (`/model` Command)** — Switch models and providers mid-session from CLI, Telegram, Discord, Slack, or any gateway platform. Aggregator-aware resolution keeps you on OpenRouter/Nous when possible, with automatic cross-provider fallback when needed. Interactive model pickers on Telegram and Discord with inline buttons. ([#5181](https://github.com/NousResearch/hermes-agent/pull/5181), [#5742](https://github.com/NousResearch/hermes-agent/pull/5742))
|
| 16 |
+
|
| 17 |
+
- **Self-Optimized GPT/Codex Tool-Use Guidance** — The agent diagnosed and patched 5 failure modes in GPT and Codex tool calling through automated behavioral benchmarking, dramatically improving reliability on OpenAI models. Includes execution discipline guidance and thinking-only prefill continuation for structured reasoning. ([#6120](https://github.com/NousResearch/hermes-agent/pull/6120), [#5414](https://github.com/NousResearch/hermes-agent/pull/5414), [#5931](https://github.com/NousResearch/hermes-agent/pull/5931))
|
| 18 |
+
|
| 19 |
+
- **Google AI Studio (Gemini) Native Provider** — Direct access to Gemini models through Google's AI Studio API. Includes automatic models.dev registry integration for real-time context length detection across any provider. ([#5577](https://github.com/NousResearch/hermes-agent/pull/5577))
|
| 20 |
+
|
| 21 |
+
- **Inactivity-Based Agent Timeouts** — Gateway and cron timeouts now track actual tool activity instead of wall-clock time. Long-running tasks that are actively working will never be killed — only truly idle agents time out. ([#5389](https://github.com/NousResearch/hermes-agent/pull/5389), [#5440](https://github.com/NousResearch/hermes-agent/pull/5440))
|
| 22 |
+
|
| 23 |
+
- **Approval Buttons on Slack & Telegram** — Dangerous command approval via native platform buttons instead of typing `/approve`. Slack gets thread context preservation; Telegram gets emoji reactions for approval status. ([#5890](https://github.com/NousResearch/hermes-agent/pull/5890), [#5975](https://github.com/NousResearch/hermes-agent/pull/5975))
|
| 24 |
+
|
| 25 |
+
- **MCP OAuth 2.1 PKCE + OSV Malware Scanning** — Full standards-compliant OAuth for MCP server authentication, plus automatic malware scanning of MCP extension packages via the OSV vulnerability database. ([#5420](https://github.com/NousResearch/hermes-agent/pull/5420), [#5305](https://github.com/NousResearch/hermes-agent/pull/5305))
|
| 26 |
+
|
| 27 |
+
- **Centralized Logging & Config Validation** — Structured logging to `~/.hermes/logs/` (agent.log + errors.log) with the `hermes logs` command for tailing and filtering. Config structure validation catches malformed YAML at startup before it causes cryptic failures. ([#5430](https://github.com/NousResearch/hermes-agent/pull/5430), [#5426](https://github.com/NousResearch/hermes-agent/pull/5426))
|
| 28 |
+
|
| 29 |
+
- **Plugin System Expansion** — Plugins can now register CLI subcommands, receive request-scoped API hooks with correlation IDs, prompt for required env vars during install, and hook into session lifecycle events (finalize/reset). ([#5295](https://github.com/NousResearch/hermes-agent/pull/5295), [#5427](https://github.com/NousResearch/hermes-agent/pull/5427), [#5470](https://github.com/NousResearch/hermes-agent/pull/5470), [#6129](https://github.com/NousResearch/hermes-agent/pull/6129))
|
| 30 |
+
|
| 31 |
+
- **Matrix Tier 1 & Platform Hardening** — Matrix gets reactions, read receipts, rich formatting, and room management. Discord adds channel controls and ignored channels. Signal gets full MEDIA: tag delivery. Mattermost gets file attachments. Comprehensive reliability fixes across all platforms. ([#5275](https://github.com/NousResearch/hermes-agent/pull/5275), [#5975](https://github.com/NousResearch/hermes-agent/pull/5975), [#5602](https://github.com/NousResearch/hermes-agent/pull/5602))
|
| 32 |
+
|
| 33 |
+
- **Security Hardening Pass** — Consolidated SSRF protections, timing attack mitigations, tar traversal prevention, credential leakage guards, cron path traversal hardening, and cross-session isolation. Terminal workdir sanitization across all backends. ([#5944](https://github.com/NousResearch/hermes-agent/pull/5944), [#5613](https://github.com/NousResearch/hermes-agent/pull/5613), [#5629](https://github.com/NousResearch/hermes-agent/pull/5629))
|
| 34 |
+
|
| 35 |
+
---
|
| 36 |
+
|
| 37 |
+
## 🏗️ Core Agent & Architecture
|
| 38 |
+
|
| 39 |
+
### Provider & Model Support
|
| 40 |
+
- **Native Google AI Studio (Gemini) provider** with models.dev integration for automatic context length detection ([#5577](https://github.com/NousResearch/hermes-agent/pull/5577))
|
| 41 |
+
- **`/model` command — full provider+model system overhaul** — live switching across CLI and all gateway platforms with aggregator-aware resolution ([#5181](https://github.com/NousResearch/hermes-agent/pull/5181))
|
| 42 |
+
- **Interactive model picker for Telegram and Discord** — inline button-based model selection ([#5742](https://github.com/NousResearch/hermes-agent/pull/5742))
|
| 43 |
+
- **Nous Portal free-tier model gating** with pricing display in model selection ([#5880](https://github.com/NousResearch/hermes-agent/pull/5880))
|
| 44 |
+
- **Model pricing display** for OpenRouter and Nous Portal providers ([#5416](https://github.com/NousResearch/hermes-agent/pull/5416))
|
| 45 |
+
- **xAI (Grok) prompt caching** via `x-grok-conv-id` header ([#5604](https://github.com/NousResearch/hermes-agent/pull/5604))
|
| 46 |
+
- **Grok added to tool-use enforcement models** for direct xAI usage ([#5595](https://github.com/NousResearch/hermes-agent/pull/5595))
|
| 47 |
+
- **MiniMax TTS provider** (speech-2.8) ([#4963](https://github.com/NousResearch/hermes-agent/pull/4963))
|
| 48 |
+
- **Non-agentic model warning** — warns users when loading Hermes LLM models not designed for tool use ([#5378](https://github.com/NousResearch/hermes-agent/pull/5378))
|
| 49 |
+
- **Ollama Cloud auth, /model switch persistence**, and alias tab completion ([#5269](https://github.com/NousResearch/hermes-agent/pull/5269))
|
| 50 |
+
- **Preserve dots in OpenCode Go model names** (minimax-m2.7, glm-4.5, kimi-k2.5) ([#5597](https://github.com/NousResearch/hermes-agent/pull/5597))
|
| 51 |
+
- **MiniMax models 404 fix** — strip /v1 from Anthropic base URL for OpenCode Go ([#4918](https://github.com/NousResearch/hermes-agent/pull/4918))
|
| 52 |
+
- **Provider credential reset windows** honored in pooled failover ([#5188](https://github.com/NousResearch/hermes-agent/pull/5188))
|
| 53 |
+
- **OAuth token sync** between credential pool and credentials file ([#4981](https://github.com/NousResearch/hermes-agent/pull/4981))
|
| 54 |
+
- **Stale OAuth credentials** no longer block OpenRouter users on auto-detect ([#5746](https://github.com/NousResearch/hermes-agent/pull/5746))
|
| 55 |
+
- **Codex OAuth credential pool disconnect** + expired token import fix ([#5681](https://github.com/NousResearch/hermes-agent/pull/5681))
|
| 56 |
+
- **Codex pool entry sync** from `~/.codex/auth.json` on exhaustion — @GratefulDave ([#5610](https://github.com/NousResearch/hermes-agent/pull/5610))
|
| 57 |
+
- **Auxiliary client payment fallback** — retry with next provider on 402 ([#5599](https://github.com/NousResearch/hermes-agent/pull/5599))
|
| 58 |
+
- **Auxiliary client resolves named custom providers** and 'main' alias ([#5978](https://github.com/NousResearch/hermes-agent/pull/5978))
|
| 59 |
+
- **Use mimo-v2-pro** for non-vision auxiliary tasks on Nous free tier ([#6018](https://github.com/NousResearch/hermes-agent/pull/6018))
|
| 60 |
+
- **Vision auto-detection** tries main provider first ([#6041](https://github.com/NousResearch/hermes-agent/pull/6041))
|
| 61 |
+
- **Provider re-ordering and Quick Install** — @austinpickett ([#4664](https://github.com/NousResearch/hermes-agent/pull/4664))
|
| 62 |
+
- **Nous OAuth access_token** no longer used as inference API key — @SHL0MS ([#5564](https://github.com/NousResearch/hermes-agent/pull/5564))
|
| 63 |
+
- **HERMES_PORTAL_BASE_URL env var** respected during Nous login — @benbarclay ([#5745](https://github.com/NousResearch/hermes-agent/pull/5745))
|
| 64 |
+
- **Env var overrides** for Nous portal/inference URLs ([#5419](https://github.com/NousResearch/hermes-agent/pull/5419))
|
| 65 |
+
- **Z.AI endpoint auto-detect** via probe and cache ([#5763](https://github.com/NousResearch/hermes-agent/pull/5763))
|
| 66 |
+
- **MiniMax context lengths, model catalog, thinking guard, aux model, and config base_url** corrections ([#6082](https://github.com/NousResearch/hermes-agent/pull/6082))
|
| 67 |
+
- **Community provider/model resolution fixes** — salvaged 4 community PRs + MiniMax aux URL ([#5983](https://github.com/NousResearch/hermes-agent/pull/5983))
|
| 68 |
+
|
| 69 |
+
### Agent Loop & Conversation
|
| 70 |
+
- **Self-optimized GPT/Codex tool-use guidance** via automated behavioral benchmarking — agent self-diagnosed and patched 5 failure modes ([#6120](https://github.com/NousResearch/hermes-agent/pull/6120))
|
| 71 |
+
- **GPT/Codex execution discipline guidance** in system prompts ([#5414](https://github.com/NousResearch/hermes-agent/pull/5414))
|
| 72 |
+
- **Thinking-only prefill continuation** for structured reasoning responses ([#5931](https://github.com/NousResearch/hermes-agent/pull/5931))
|
| 73 |
+
- **Accept reasoning-only responses** without retries — set content to "(empty)" instead of infinite retry ([#5278](https://github.com/NousResearch/hermes-agent/pull/5278))
|
| 74 |
+
- **Jittered retry backoff** — exponential backoff with jitter for API retries ([#6048](https://github.com/NousResearch/hermes-agent/pull/6048))
|
| 75 |
+
- **Smart thinking block signature management** — preserve and manage Anthropic thinking signatures across turns ([#6112](https://github.com/NousResearch/hermes-agent/pull/6112))
|
| 76 |
+
- **Coerce tool call arguments** to match JSON Schema types — fixes models that send strings instead of numbers/booleans ([#5265](https://github.com/NousResearch/hermes-agent/pull/5265))
|
| 77 |
+
- **Save oversized tool results to file** instead of destructive truncation ([#5210](https://github.com/NousResearch/hermes-agent/pull/5210))
|
| 78 |
+
- **Sandbox-aware tool result persistence** ([#6085](https://github.com/NousResearch/hermes-agent/pull/6085))
|
| 79 |
+
- **Streaming fallback** improved after edit failures ([#6110](https://github.com/NousResearch/hermes-agent/pull/6110))
|
| 80 |
+
- **Codex empty-output gaps** covered in fallback + normalizer + auxiliary client ([#5724](https://github.com/NousResearch/hermes-agent/pull/5724), [#5730](https://github.com/NousResearch/hermes-agent/pull/5730), [#5734](https://github.com/NousResearch/hermes-agent/pull/5734))
|
| 81 |
+
- **Codex stream output backfill** from output_item.done events ([#5689](https://github.com/NousResearch/hermes-agent/pull/5689))
|
| 82 |
+
- **Stream consumer creates new message** after tool boundaries ([#5739](https://github.com/NousResearch/hermes-agent/pull/5739))
|
| 83 |
+
- **Codex validation aligned** with normalization for empty stream output ([#5940](https://github.com/NousResearch/hermes-agent/pull/5940))
|
| 84 |
+
- **Bridge tool-calls** in copilot-acp adapter ([#5460](https://github.com/NousResearch/hermes-agent/pull/5460))
|
| 85 |
+
- **Filter transcript-only roles** from chat-completions payload ([#4880](https://github.com/NousResearch/hermes-agent/pull/4880))
|
| 86 |
+
- **Context compaction failures fixed** on temperature-restricted models — @MadKangYu ([#5608](https://github.com/NousResearch/hermes-agent/pull/5608))
|
| 87 |
+
- **Sanitize tool_calls for all strict APIs** (Fireworks, Mistral, etc.) — @lumethegreat ([#5183](https://github.com/NousResearch/hermes-agent/pull/5183))
|
| 88 |
+
|
| 89 |
+
### Memory & Sessions
|
| 90 |
+
- **Supermemory memory provider** — new memory plugin with multi-container, search_mode, identity template, and env var override ([#5737](https://github.com/NousResearch/hermes-agent/pull/5737), [#5933](https://github.com/NousResearch/hermes-agent/pull/5933))
|
| 91 |
+
- **Shared thread sessions** by default — multi-user thread support across gateway platforms ([#5391](https://github.com/NousResearch/hermes-agent/pull/5391))
|
| 92 |
+
- **Subagent sessions linked to parent** and hidden from session list ([#5309](https://github.com/NousResearch/hermes-agent/pull/5309))
|
| 93 |
+
- **Profile-scoped memory isolation** and clone support ([#4845](https://github.com/NousResearch/hermes-agent/pull/4845))
|
| 94 |
+
- **Thread gateway user_id to memory plugins** for per-user scoping ([#5895](https://github.com/NousResearch/hermes-agent/pull/5895))
|
| 95 |
+
- **Honcho plugin drift overhaul** + plugin CLI registration system ([#5295](https://github.com/NousResearch/hermes-agent/pull/5295))
|
| 96 |
+
- **Honcho holographic prompt and trust score** rendering preserved ([#4872](https://github.com/NousResearch/hermes-agent/pull/4872))
|
| 97 |
+
- **Honcho doctor fix** — use recall_mode instead of memory_mode — @techguysimon ([#5645](https://github.com/NousResearch/hermes-agent/pull/5645))
|
| 98 |
+
- **RetainDB** — API routes, write queue, dialectic, agent model, file tools fixes ([#5461](https://github.com/NousResearch/hermes-agent/pull/5461))
|
| 99 |
+
- **Hindsight memory plugin overhaul** + memory setup wizard fixes ([#5094](https://github.com/NousResearch/hermes-agent/pull/5094))
|
| 100 |
+
- **mem0 API v2 compat**, prefetch context fencing, secret redaction ([#5423](https://github.com/NousResearch/hermes-agent/pull/5423))
|
| 101 |
+
- **mem0 env vars merged** with mem0.json instead of either/or ([#4939](https://github.com/NousResearch/hermes-agent/pull/4939))
|
| 102 |
+
- **Clean user message** used for all memory provider operations ([#4940](https://github.com/NousResearch/hermes-agent/pull/4940))
|
| 103 |
+
- **Silent memory flush failure** on /new and /resume fixed — @ryanautomated ([#5640](https://github.com/NousResearch/hermes-agent/pull/5640))
|
| 104 |
+
- **OpenViking atexit safety net** for session commit ([#5664](https://github.com/NousResearch/hermes-agent/pull/5664))
|
| 105 |
+
- **OpenViking tenant-scoping headers** for multi-tenant servers ([#4936](https://github.com/NousResearch/hermes-agent/pull/4936))
|
| 106 |
+
- **ByteRover brv query** runs synchronously before LLM call ([#4831](https://github.com/NousResearch/hermes-agent/pull/4831))
|
| 107 |
+
|
| 108 |
+
---
|
| 109 |
+
|
| 110 |
+
## 📱 Messaging Platforms (Gateway)
|
| 111 |
+
|
| 112 |
+
### Gateway Core
|
| 113 |
+
- **Inactivity-based agent timeout** — replaces wall-clock timeout with smart activity tracking; long-running active tasks never killed ([#5389](https://github.com/NousResearch/hermes-agent/pull/5389))
|
| 114 |
+
- **Approval buttons for Slack & Telegram** + Slack thread context preservation ([#5890](https://github.com/NousResearch/hermes-agent/pull/5890))
|
| 115 |
+
- **Live-stream /update output** + forward interactive prompts to user ([#5180](https://github.com/NousResearch/hermes-agent/pull/5180))
|
| 116 |
+
- **Infinite timeout support** + periodic notifications + actionable error messages ([#4959](https://github.com/NousResearch/hermes-agent/pull/4959))
|
| 117 |
+
- **Duplicate message prevention** — gateway dedup + partial stream guard ([#4878](https://github.com/NousResearch/hermes-agent/pull/4878))
|
| 118 |
+
- **Webhook delivery_info persistence** + full session id in /status ([#5942](https://github.com/NousResearch/hermes-agent/pull/5942))
|
| 119 |
+
- **Tool preview truncation** respects tool_preview_length in all/new progress modes ([#5937](https://github.com/NousResearch/hermes-agent/pull/5937))
|
| 120 |
+
- **Short preview truncation** restored for all/new tool progress modes ([#4935](https://github.com/NousResearch/hermes-agent/pull/4935))
|
| 121 |
+
- **Update-pending state** written atomically to prevent corruption ([#4923](https://github.com/NousResearch/hermes-agent/pull/4923))
|
| 122 |
+
- **Approval session key isolated** per turn ([#4884](https://github.com/NousResearch/hermes-agent/pull/4884))
|
| 123 |
+
- **Active-session guard bypass** for /approve, /deny, /stop, /new ([#4926](https://github.com/NousResearch/hermes-agent/pull/4926), [#5765](https://github.com/NousResearch/hermes-agent/pull/5765))
|
| 124 |
+
- **Typing indicator paused** during approval waits ([#5893](https://github.com/NousResearch/hermes-agent/pull/5893))
|
| 125 |
+
- **Caption check** uses exact line-by-line match instead of substring (all platforms) ([#5939](https://github.com/NousResearch/hermes-agent/pull/5939))
|
| 126 |
+
- **MEDIA: tags stripped** from streamed gateway messages ([#5152](https://github.com/NousResearch/hermes-agent/pull/5152))
|
| 127 |
+
- **MEDIA: tags extracted** from cron delivery before sending ([#5598](https://github.com/NousResearch/hermes-agent/pull/5598))
|
| 128 |
+
- **Profile-aware service units** + voice transcription cleanup ([#5972](https://github.com/NousResearch/hermes-agent/pull/5972))
|
| 129 |
+
- **Thread-safe PairingStore** with atomic writes — @CharlieKerfoot ([#5656](https://github.com/NousResearch/hermes-agent/pull/5656))
|
| 130 |
+
- **Sanitize media URLs** in base platform logs — @WAXLYY ([#5631](https://github.com/NousResearch/hermes-agent/pull/5631))
|
| 131 |
+
- **Reduce Telegram fallback IP activation log noise** — @MadKangYu ([#5615](https://github.com/NousResearch/hermes-agent/pull/5615))
|
| 132 |
+
- **Cron static method wrappers** to prevent self-binding ([#5299](https://github.com/NousResearch/hermes-agent/pull/5299))
|
| 133 |
+
- **Stale 'hermes login' replaced** with 'hermes auth' + credential removal re-seeding fix ([#5670](https://github.com/NousResearch/hermes-agent/pull/5670))
|
| 134 |
+
|
| 135 |
+
### Telegram
|
| 136 |
+
- **Group topics skill binding** for supergroup forum topics ([#4886](https://github.com/NousResearch/hermes-agent/pull/4886))
|
| 137 |
+
- **Emoji reactions** for approval status and notifications ([#5975](https://github.com/NousResearch/hermes-agent/pull/5975))
|
| 138 |
+
- **Duplicate message delivery prevented** on send timeout ([#5153](https://github.com/NousResearch/hermes-agent/pull/5153))
|
| 139 |
+
- **Command names sanitized** to strip invalid characters ([#5596](https://github.com/NousResearch/hermes-agent/pull/5596))
|
| 140 |
+
- **Per-platform disabled skills** respected in Telegram menu and gateway dispatch ([#4799](https://github.com/NousResearch/hermes-agent/pull/4799))
|
| 141 |
+
- **/approve and /deny** routed through running-agent guard ([#4798](https://github.com/NousResearch/hermes-agent/pull/4798))
|
| 142 |
+
|
| 143 |
+
### Discord
|
| 144 |
+
- **Channel controls** — ignored_channels and no_thread_channels config options ([#5975](https://github.com/NousResearch/hermes-agent/pull/5975))
|
| 145 |
+
- **Skills registered as native slash commands** via shared gateway logic ([#5603](https://github.com/NousResearch/hermes-agent/pull/5603))
|
| 146 |
+
- **/approve, /deny, /queue, /background, /btw** registered as native slash commands ([#4800](https://github.com/NousResearch/hermes-agent/pull/4800), [#5477](https://github.com/NousResearch/hermes-agent/pull/5477))
|
| 147 |
+
- **Unnecessary members intent** removed on startup + token lock leak fix ([#5302](https://github.com/NousResearch/hermes-agent/pull/5302))
|
| 148 |
+
|
| 149 |
+
### Slack
|
| 150 |
+
- **Thread engagement** — auto-respond in bot-started and mentioned threads ([#5897](https://github.com/NousResearch/hermes-agent/pull/5897))
|
| 151 |
+
- **mrkdwn in edit_message** + thread replies without @mentions ([#5733](https://github.com/NousResearch/hermes-agent/pull/5733))
|
| 152 |
+
|
| 153 |
+
### Matrix
|
| 154 |
+
- **Tier 1 feature parity** — reactions, read receipts, rich formatting, room management ([#5275](https://github.com/NousResearch/hermes-agent/pull/5275))
|
| 155 |
+
- **MATRIX_REQUIRE_MENTION and MATRIX_AUTO_THREAD** support ([#5106](https://github.com/NousResearch/hermes-agent/pull/5106))
|
| 156 |
+
- **Comprehensive reliability** — encrypted media, auth recovery, cron E2EE, Synapse compat ([#5271](https://github.com/NousResearch/hermes-agent/pull/5271))
|
| 157 |
+
- **CJK input, E2EE, and reconnect** fixes ([#5665](https://github.com/NousResearch/hermes-agent/pull/5665))
|
| 158 |
+
|
| 159 |
+
### Signal
|
| 160 |
+
- **Full MEDIA: tag delivery** — send_image_file, send_voice, and send_video implemented ([#5602](https://github.com/NousResearch/hermes-agent/pull/5602))
|
| 161 |
+
|
| 162 |
+
### Mattermost
|
| 163 |
+
- **File attachments** — set message type to DOCUMENT when post has file attachments — @nericervin ([#5609](https://github.com/NousResearch/hermes-agent/pull/5609))
|
| 164 |
+
|
| 165 |
+
### Feishu
|
| 166 |
+
- **Interactive card approval buttons** ([#6043](https://github.com/NousResearch/hermes-agent/pull/6043))
|
| 167 |
+
- **Reconnect and ACL** fixes ([#5665](https://github.com/NousResearch/hermes-agent/pull/5665))
|
| 168 |
+
|
| 169 |
+
### Webhooks
|
| 170 |
+
- **`{__raw__}` template token** and thread_id passthrough for forum topics ([#5662](https://github.com/NousResearch/hermes-agent/pull/5662))
|
| 171 |
+
|
| 172 |
+
---
|
| 173 |
+
|
| 174 |
+
## 🖥️ CLI & User Experience
|
| 175 |
+
|
| 176 |
+
### Interactive CLI
|
| 177 |
+
- **Defer response content** until reasoning block completes ([#5773](https://github.com/NousResearch/hermes-agent/pull/5773))
|
| 178 |
+
- **Ghost status-bar lines cleared** on terminal resize ([#4960](https://github.com/NousResearch/hermes-agent/pull/4960))
|
| 179 |
+
- **Normalise \r\n and \r line endings** in pasted text ([#4849](https://github.com/NousResearch/hermes-agent/pull/4849))
|
| 180 |
+
- **ChatConsole errors, curses scroll, skin-aware banner, git state** banner fixes ([#5974](https://github.com/NousResearch/hermes-agent/pull/5974))
|
| 181 |
+
- **Native Windows image paste** support ([#5917](https://github.com/NousResearch/hermes-agent/pull/5917))
|
| 182 |
+
- **--yolo and other flags** no longer silently dropped when placed before 'chat' subcommand ([#5145](https://github.com/NousResearch/hermes-agent/pull/5145))
|
| 183 |
+
|
| 184 |
+
### Setup & Configuration
|
| 185 |
+
- **Config structure validation** — detect malformed YAML at startup with actionable error messages ([#5426](https://github.com/NousResearch/hermes-agent/pull/5426))
|
| 186 |
+
- **Centralized logging** to `~/.hermes/logs/` — agent.log (INFO+), errors.log (WARNING+) with `hermes logs` command ([#5430](https://github.com/NousResearch/hermes-agent/pull/5430))
|
| 187 |
+
- **Docs links added** to setup wizard sections ([#5283](https://github.com/NousResearch/hermes-agent/pull/5283))
|
| 188 |
+
- **Doctor diagnostics** — sync provider checks, config migration, WAL and mem0 diagnostics ([#5077](https://github.com/NousResearch/hermes-agent/pull/5077))
|
| 189 |
+
- **Timeout debug logging** and user-facing diagnostics improved ([#5370](https://github.com/NousResearch/hermes-agent/pull/5370))
|
| 190 |
+
- **Reasoning effort unified** to config.yaml only ([#6118](https://github.com/NousResearch/hermes-agent/pull/6118))
|
| 191 |
+
- **Permanent command allowlist** loaded on startup ([#5076](https://github.com/NousResearch/hermes-agent/pull/5076))
|
| 192 |
+
- **`hermes auth remove`** now clears env-seeded credentials permanently ([#5285](https://github.com/NousResearch/hermes-agent/pull/5285))
|
| 193 |
+
- **Bundled skills synced to all profiles** during update ([#5795](https://github.com/NousResearch/hermes-agent/pull/5795))
|
| 194 |
+
- **`hermes update` no longer kills** freshly-restarted gateway service ([#5448](https://github.com/NousResearch/hermes-agent/pull/5448))
|
| 195 |
+
- **Subprocess.run() timeouts** added to all gateway CLI commands ([#5424](https://github.com/NousResearch/hermes-agent/pull/5424))
|
| 196 |
+
- **Actionable error message** when Codex refresh token is reused — @tymrtn ([#5612](https://github.com/NousResearch/hermes-agent/pull/5612))
|
| 197 |
+
- **Google-workspace skill scripts** can now run directly — @xinbenlv ([#5624](https://github.com/NousResearch/hermes-agent/pull/5624))
|
| 198 |
+
|
| 199 |
+
### Cron System
|
| 200 |
+
- **Inactivity-based cron timeout** — replaces wall-clock; active tasks run indefinitely ([#5440](https://github.com/NousResearch/hermes-agent/pull/5440))
|
| 201 |
+
- **Pre-run script injection** for data collection and change detection ([#5082](https://github.com/NousResearch/hermes-agent/pull/5082))
|
| 202 |
+
- **Delivery failure tracking** in job status ([#6042](https://github.com/NousResearch/hermes-agent/pull/6042))
|
| 203 |
+
- **Delivery guidance** in cron prompts — stops send_message thrashing ([#5444](https://github.com/NousResearch/hermes-agent/pull/5444))
|
| 204 |
+
- **MEDIA files delivered** as native platform attachments ([#5921](https://github.com/NousResearch/hermes-agent/pull/5921))
|
| 205 |
+
- **[SILENT] suppression** works anywhere in response — @auspic7 ([#5654](https://github.com/NousResearch/hermes-agent/pull/5654))
|
| 206 |
+
- **Cron path traversal** hardening ([#5147](https://github.com/NousResearch/hermes-agent/pull/5147))
|
| 207 |
+
|
| 208 |
+
---
|
| 209 |
+
|
| 210 |
+
## 🔧 Tool System
|
| 211 |
+
|
| 212 |
+
### Terminal & Execution
|
| 213 |
+
- **Execute_code on remote backends** — code execution now works on Docker, SSH, Modal, and other remote terminal backends ([#5088](https://github.com/NousResearch/hermes-agent/pull/5088))
|
| 214 |
+
- **Exit code context** for common CLI tools in terminal results — helps agent understand what went wrong ([#5144](https://github.com/NousResearch/hermes-agent/pull/5144))
|
| 215 |
+
- **Progressive subdirectory hint discovery** — agent learns project structure as it navigates ([#5291](https://github.com/NousResearch/hermes-agent/pull/5291))
|
| 216 |
+
- **notify_on_complete for background processes** — get notified when long-running tasks finish ([#5779](https://github.com/NousResearch/hermes-agent/pull/5779))
|
| 217 |
+
- **Docker env config** — explicit container environment variables via docker_env config ([#4738](https://github.com/NousResearch/hermes-agent/pull/4738))
|
| 218 |
+
- **Approval metadata included** in terminal tool results ([#5141](https://github.com/NousResearch/hermes-agent/pull/5141))
|
| 219 |
+
- **Workdir parameter sanitized** in terminal tool across all backends ([#5629](https://github.com/NousResearch/hermes-agent/pull/5629))
|
| 220 |
+
- **Detached process crash recovery** state corrected ([#6101](https://github.com/NousResearch/hermes-agent/pull/6101))
|
| 221 |
+
- **Agent-browser paths with spaces** preserved — @Vasanthdev2004 ([#6077](https://github.com/NousResearch/hermes-agent/pull/6077))
|
| 222 |
+
- **Portable base64 encoding** for image reading on macOS — @CharlieKerfoot ([#5657](https://github.com/NousResearch/hermes-agent/pull/5657))
|
| 223 |
+
|
| 224 |
+
### Browser
|
| 225 |
+
- **Switch managed browser provider** from Browserbase to Browser Use — @benbarclay ([#5750](https://github.com/NousResearch/hermes-agent/pull/5750))
|
| 226 |
+
- **Firecrawl cloud browser** provider — @alt-glitch ([#5628](https://github.com/NousResearch/hermes-agent/pull/5628))
|
| 227 |
+
- **JS evaluation** via browser_console expression parameter ([#5303](https://github.com/NousResearch/hermes-agent/pull/5303))
|
| 228 |
+
- **Windows browser** fixes ([#5665](https://github.com/NousResearch/hermes-agent/pull/5665))
|
| 229 |
+
|
| 230 |
+
### MCP
|
| 231 |
+
- **MCP OAuth 2.1 PKCE** — full standards-compliant OAuth client support ([#5420](https://github.com/NousResearch/hermes-agent/pull/5420))
|
| 232 |
+
- **OSV malware check** for MCP extension packages ([#5305](https://github.com/NousResearch/hermes-agent/pull/5305))
|
| 233 |
+
- **Prefer structuredContent over text** + no_mcp sentinel ([#5979](https://github.com/NousResearch/hermes-agent/pull/5979))
|
| 234 |
+
- **Unknown toolsets warning suppressed** for MCP server names ([#5279](https://github.com/NousResearch/hermes-agent/pull/5279))
|
| 235 |
+
|
| 236 |
+
### Web & Files
|
| 237 |
+
- **.zip document support** + auto-mount cache dirs into remote backends ([#4846](https://github.com/NousResearch/hermes-agent/pull/4846))
|
| 238 |
+
- **Redact query secrets** in send_message errors — @WAXLYY ([#5650](https://github.com/NousResearch/hermes-agent/pull/5650))
|
| 239 |
+
|
| 240 |
+
### Delegation
|
| 241 |
+
- **Credential pool sharing** + workspace path hints for subagents ([#5748](https://github.com/NousResearch/hermes-agent/pull/5748))
|
| 242 |
+
|
| 243 |
+
### ACP (VS Code / Zed / JetBrains)
|
| 244 |
+
- **Aggregate ACP improvements** — auth compat, protocol fixes, command ads, delegation, SSE events ([#5292](https://github.com/NousResearch/hermes-agent/pull/5292))
|
| 245 |
+
|
| 246 |
+
---
|
| 247 |
+
|
| 248 |
+
## 🧩 Skills Ecosystem
|
| 249 |
+
|
| 250 |
+
### Skills System
|
| 251 |
+
- **Skill config interface** — skills can declare required config.yaml settings, prompted during setup, injected at load time ([#5635](https://github.com/NousResearch/hermes-agent/pull/5635))
|
| 252 |
+
- **Plugin CLI registration system** — plugins register their own CLI subcommands without touching main.py ([#5295](https://github.com/NousResearch/hermes-agent/pull/5295))
|
| 253 |
+
- **Request-scoped API hooks** with tool call correlation IDs for plugins ([#5427](https://github.com/NousResearch/hermes-agent/pull/5427))
|
| 254 |
+
- **Session lifecycle hooks** — on_session_finalize and on_session_reset for CLI + gateway ([#6129](https://github.com/NousResearch/hermes-agent/pull/6129))
|
| 255 |
+
- **Prompt for required env vars** during plugin install — @kshitijk4poor ([#5470](https://github.com/NousResearch/hermes-agent/pull/5470))
|
| 256 |
+
- **Plugin name validation** — reject names that resolve to plugins root ([#5368](https://github.com/NousResearch/hermes-agent/pull/5368))
|
| 257 |
+
- **pre_llm_call plugin context** moved to user message to preserve prompt cache ([#5146](https://github.com/NousResearch/hermes-agent/pull/5146))
|
| 258 |
+
|
| 259 |
+
### New & Updated Skills
|
| 260 |
+
- **popular-web-designs** — 54 production website design systems ([#5194](https://github.com/NousResearch/hermes-agent/pull/5194))
|
| 261 |
+
- **p5js creative coding** — @SHL0MS ([#5600](https://github.com/NousResearch/hermes-agent/pull/5600))
|
| 262 |
+
- **manim-video** — mathematical and technical animations — @SHL0MS ([#4930](https://github.com/NousResearch/hermes-agent/pull/4930))
|
| 263 |
+
- **llm-wiki** — Karpathy's LLM Wiki skill ([#5635](https://github.com/NousResearch/hermes-agent/pull/5635))
|
| 264 |
+
- **gitnexus-explorer** — codebase indexing and knowledge serving ([#5208](https://github.com/NousResearch/hermes-agent/pull/5208))
|
| 265 |
+
- **research-paper-writing** — AI-Scientist & GPT-Researcher patterns — @SHL0MS ([#5421](https://github.com/NousResearch/hermes-agent/pull/5421))
|
| 266 |
+
- **blogwatcher** updated to JulienTant's fork ([#5759](https://github.com/NousResearch/hermes-agent/pull/5759))
|
| 267 |
+
- **claude-code skill** comprehensive rewrite v2.0 + v2.2 ([#5155](https://github.com/NousResearch/hermes-agent/pull/5155), [#5158](https://github.com/NousResearch/hermes-agent/pull/5158))
|
| 268 |
+
- **Code verification skills** consolidated into one ([#4854](https://github.com/NousResearch/hermes-agent/pull/4854))
|
| 269 |
+
- **Manim CE reference docs** expanded — geometry, animations, LaTeX — @leotrs ([#5791](https://github.com/NousResearch/hermes-agent/pull/5791))
|
| 270 |
+
- **Manim-video references** — design thinking, updaters, paper explainer, decorations, production quality — @SHL0MS ([#5588](https://github.com/NousResearch/hermes-agent/pull/5588), [#5408](https://github.com/NousResearch/hermes-agent/pull/5408))
|
| 271 |
+
|
| 272 |
+
---
|
| 273 |
+
|
| 274 |
+
## 🔒 Security & Reliability
|
| 275 |
+
|
| 276 |
+
### Security Hardening
|
| 277 |
+
- **Consolidated security** — SSRF protections, timing attack mitigations, tar traversal prevention, credential leakage guards ([#5944](https://github.com/NousResearch/hermes-agent/pull/5944))
|
| 278 |
+
- **Cross-session isolation** + cron path traversal hardening ([#5613](https://github.com/NousResearch/hermes-agent/pull/5613))
|
| 279 |
+
- **Workdir parameter sanitized** in terminal tool across all backends ([#5629](https://github.com/NousResearch/hermes-agent/pull/5629))
|
| 280 |
+
- **Approval 'once' session escalation** prevented + cron delivery platform validation ([#5280](https://github.com/NousResearch/hermes-agent/pull/5280))
|
| 281 |
+
- **Profile-scoped Google Workspace OAuth tokens** protected ([#4910](https://github.com/NousResearch/hermes-agent/pull/4910))
|
| 282 |
+
|
| 283 |
+
### Reliability
|
| 284 |
+
- **Aggressive worktree and branch cleanup** to prevent accumulation ([#6134](https://github.com/NousResearch/hermes-agent/pull/6134))
|
| 285 |
+
- **O(n²) catastrophic backtracking** in redact regex fixed — 100x improvement on large outputs ([#4962](https://github.com/NousResearch/hermes-agent/pull/4962))
|
| 286 |
+
- **Runtime stability fixes** across core, web, delegate, and browser tools ([#4843](https://github.com/NousResearch/hermes-agent/pull/4843))
|
| 287 |
+
- **API server streaming fix** + conversation history support ([#5977](https://github.com/NousResearch/hermes-agent/pull/5977))
|
| 288 |
+
- **OpenViking API endpoint paths** and response parsing corrected ([#5078](https://github.com/NousResearch/hermes-agent/pull/5078))
|
| 289 |
+
|
| 290 |
+
---
|
| 291 |
+
|
| 292 |
+
## 🐛 Notable Bug Fixes
|
| 293 |
+
|
| 294 |
+
- **9 community bugfixes salvaged** — gateway, cron, deps, macOS launchd in one batch ([#5288](https://github.com/NousResearch/hermes-agent/pull/5288))
|
| 295 |
+
- **Batch core bug fixes** — model config, session reset, alias fallback, launchctl, delegation, atomic writes ([#5630](https://github.com/NousResearch/hermes-agent/pull/5630))
|
| 296 |
+
- **Batch gateway/platform fixes** — matrix E2EE, CJK input, Windows browser, Feishu reconnect + ACL ([#5665](https://github.com/NousResearch/hermes-agent/pull/5665))
|
| 297 |
+
- **Stale test skips removed**, regex backtracking, file search bug, and test flakiness ([#4969](https://github.com/NousResearch/hermes-agent/pull/4969))
|
| 298 |
+
- **Nix flake** — read version, regen uv.lock, add hermes_logging — @alt-glitch ([#5651](https://github.com/NousResearch/hermes-agent/pull/5651))
|
| 299 |
+
- **Lowercase variable redaction** regression tests ([#5185](https://github.com/NousResearch/hermes-agent/pull/5185))
|
| 300 |
+
|
| 301 |
+
---
|
| 302 |
+
|
| 303 |
+
## 🧪 Testing
|
| 304 |
+
|
| 305 |
+
- **57 failing CI tests repaired** across 14 files ([#5823](https://github.com/NousResearch/hermes-agent/pull/5823))
|
| 306 |
+
- **Test suite re-architecture** + CI failure fixes — @alt-glitch ([#5946](https://github.com/NousResearch/hermes-agent/pull/5946))
|
| 307 |
+
- **Codebase-wide lint cleanup** — unused imports, dead code, and inefficient patterns ([#5821](https://github.com/NousResearch/hermes-agent/pull/5821))
|
| 308 |
+
- **browser_close tool removed** — auto-cleanup handles it ([#5792](https://github.com/NousResearch/hermes-agent/pull/5792))
|
| 309 |
+
|
| 310 |
+
---
|
| 311 |
+
|
| 312 |
+
## 📚 Documentation
|
| 313 |
+
|
| 314 |
+
- **Comprehensive documentation audit** — fix stale info, expand thin pages, add depth ([#5393](https://github.com/NousResearch/hermes-agent/pull/5393))
|
| 315 |
+
- **40+ discrepancies fixed** between documentation and codebase ([#5818](https://github.com/NousResearch/hermes-agent/pull/5818))
|
| 316 |
+
- **13 features documented** from last week's PRs ([#5815](https://github.com/NousResearch/hermes-agent/pull/5815))
|
| 317 |
+
- **Guides section overhaul** — fix existing + add 3 new tutorials ([#5735](https://github.com/NousResearch/hermes-agent/pull/5735))
|
| 318 |
+
- **Salvaged 4 docs PRs** — docker setup, post-update validation, local LLM guide, signal-cli install ([#5727](https://github.com/NousResearch/hermes-agent/pull/5727))
|
| 319 |
+
- **Discord configuration reference** ([#5386](https://github.com/NousResearch/hermes-agent/pull/5386))
|
| 320 |
+
- **Community FAQ entries** for common workflows and troubleshooting ([#4797](https://github.com/NousResearch/hermes-agent/pull/4797))
|
| 321 |
+
- **WSL2 networking guide** for local model servers ([#5616](https://github.com/NousResearch/hermes-agent/pull/5616))
|
| 322 |
+
- **Honcho CLI reference** + plugin CLI registration docs ([#5308](https://github.com/NousResearch/hermes-agent/pull/5308))
|
| 323 |
+
- **Obsidian Headless setup** for servers in llm-wiki ([#5660](https://github.com/NousResearch/hermes-agent/pull/5660))
|
| 324 |
+
- **Hermes Mod visual skin editor** added to skins page ([#6095](https://github.com/NousResearch/hermes-agent/pull/6095))
|
| 325 |
+
|
| 326 |
+
---
|
| 327 |
+
|
| 328 |
+
## 👥 Contributors
|
| 329 |
+
|
| 330 |
+
### Core
|
| 331 |
+
- **@teknium1** — 179 PRs
|
| 332 |
+
|
| 333 |
+
### Top Community Contributors
|
| 334 |
+
- **@SHL0MS** (7 PRs) — p5js creative coding skill, manim-video skill + 5 reference expansions, research-paper-writing, Nous OAuth fix, manim font fix
|
| 335 |
+
- **@alt-glitch** (3 PRs) — Firecrawl cloud browser provider, test re-architecture + CI fixes, Nix flake fixes
|
| 336 |
+
- **@benbarclay** (2 PRs) — Browser Use managed provider switch, Nous portal base URL fix
|
| 337 |
+
- **@CharlieKerfoot** (2 PRs) — macOS portable base64 encoding, thread-safe PairingStore
|
| 338 |
+
- **@WAXLYY** (2 PRs) — send_message secret redaction, gateway media URL sanitization
|
| 339 |
+
- **@MadKangYu** (2 PRs) — Telegram log noise reduction, context compaction fix for temperature-restricted models
|
| 340 |
+
|
| 341 |
+
### All Contributors
|
| 342 |
+
@alt-glitch, @austinpickett, @auspic7, @benbarclay, @CharlieKerfoot, @GratefulDave, @kshitijk4poor, @leotrs, @lumethegreat, @MadKangYu, @nericervin, @ryanautomated, @SHL0MS, @techguysimon, @tymrtn, @Vasanthdev2004, @WAXLYY, @xinbenlv
|
| 343 |
+
|
| 344 |
+
---
|
| 345 |
+
|
| 346 |
+
**Full Changelog**: [v2026.4.3...v2026.4.8](https://github.com/NousResearch/hermes-agent/compare/v2026.4.3...v2026.4.8)
|
RELEASE_v0.9.0.md
ADDED
|
@@ -0,0 +1,329 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Hermes Agent v0.9.0 (v2026.4.13)
|
| 2 |
+
|
| 3 |
+
**Release Date:** April 13, 2026
|
| 4 |
+
**Since v0.8.0:** 487 commits · 269 merged PRs · 167 resolved issues · 493 files changed · 63,281 insertions · 24 contributors
|
| 5 |
+
|
| 6 |
+
> The everywhere release — Hermes goes mobile with Termux/Android, adds iMessage and WeChat, ships Fast Mode for OpenAI and Anthropic, introduces background process monitoring, launches a local web dashboard for managing your agent, and delivers the deepest security hardening pass yet across 16 supported platforms.
|
| 7 |
+
|
| 8 |
+
---
|
| 9 |
+
|
| 10 |
+
## ✨ Highlights
|
| 11 |
+
|
| 12 |
+
- **Local Web Dashboard** — A new browser-based dashboard for managing your Hermes Agent locally. Configure settings, monitor sessions, browse skills, and manage your gateway — all from a clean web interface without touching config files or the terminal. The easiest way to get started with Hermes.
|
| 13 |
+
|
| 14 |
+
- **Fast Mode (`/fast`)** — Priority processing for OpenAI and Anthropic models. Toggle `/fast` to route through priority queues for significantly lower latency on supported models (GPT-5.4, Codex, Claude). Expands across all OpenAI Priority Processing models and Anthropic's fast tier. ([#6875](https://github.com/NousResearch/hermes-agent/pull/6875), [#6960](https://github.com/NousResearch/hermes-agent/pull/6960), [#7037](https://github.com/NousResearch/hermes-agent/pull/7037))
|
| 15 |
+
|
| 16 |
+
- **iMessage via BlueBubbles** — Full iMessage integration through BlueBubbles, bringing Hermes to Apple's messaging ecosystem. Auto-webhook registration, setup wizard integration, and crash resilience. ([#6437](https://github.com/NousResearch/hermes-agent/pull/6437), [#6460](https://github.com/NousResearch/hermes-agent/pull/6460), [#6494](https://github.com/NousResearch/hermes-agent/pull/6494))
|
| 17 |
+
|
| 18 |
+
- **WeChat (Weixin) & WeCom Callback Mode** — Native WeChat support via iLink Bot API and a new WeCom callback-mode adapter for self-built enterprise apps. Streaming cursor, media uploads, markdown link handling, and atomic state persistence. Hermes now covers the Chinese messaging ecosystem end-to-end. ([#7166](https://github.com/NousResearch/hermes-agent/pull/7166), [#7943](https://github.com/NousResearch/hermes-agent/pull/7943))
|
| 19 |
+
|
| 20 |
+
- **Termux / Android Support** — Run Hermes natively on Android via Termux. Adapted install paths, TUI optimizations for mobile screens, voice backend support, and the `/image` command work on-device. ([#6834](https://github.com/NousResearch/hermes-agent/pull/6834))
|
| 21 |
+
|
| 22 |
+
- **Background Process Monitoring (`watch_patterns`)** — Set patterns to watch for in background process output and get notified in real-time when they match. Monitor for errors, wait for specific events ("listening on port"), or watch build logs — all without polling. ([#7635](https://github.com/NousResearch/hermes-agent/pull/7635))
|
| 23 |
+
|
| 24 |
+
- **Native xAI & Xiaomi MiMo Providers** — First-class provider support for xAI (Grok) and Xiaomi MiMo, with direct API access, model catalogs, and setup wizard integration. Plus Qwen OAuth with portal request support. ([#7372](https://github.com/NousResearch/hermes-agent/pull/7372), [#7855](https://github.com/NousResearch/hermes-agent/pull/7855))
|
| 25 |
+
|
| 26 |
+
- **Pluggable Context Engine** — Context management is now a pluggable slot via `hermes plugins`. Swap in custom context engines that control what the agent sees each turn — filtering, summarization, or domain-specific context injection. ([#7464](https://github.com/NousResearch/hermes-agent/pull/7464))
|
| 27 |
+
|
| 28 |
+
- **Unified Proxy Support** — SOCKS proxy, `DISCORD_PROXY`, and system proxy auto-detection across all gateway platforms. Hermes behind corporate firewalls just works. ([#6814](https://github.com/NousResearch/hermes-agent/pull/6814))
|
| 29 |
+
|
| 30 |
+
- **Comprehensive Security Hardening** — Path traversal protection in checkpoint manager, shell injection neutralization in sandbox writes, SSRF redirect guards in Slack image uploads, Twilio webhook signature validation (SMS RCE fix), API server auth enforcement, git argument injection prevention, and approval button authorization. ([#7933](https://github.com/NousResearch/hermes-agent/pull/7933), [#7944](https://github.com/NousResearch/hermes-agent/pull/7944), [#7940](https://github.com/NousResearch/hermes-agent/pull/7940), [#7151](https://github.com/NousResearch/hermes-agent/pull/7151), [#7156](https://github.com/NousResearch/hermes-agent/pull/7156))
|
| 31 |
+
|
| 32 |
+
- **`hermes backup` & `hermes import`** — Full backup and restore of your Hermes configuration, sessions, skills, and memory. Migrate between machines or create snapshots before major changes. ([#7997](https://github.com/NousResearch/hermes-agent/pull/7997))
|
| 33 |
+
|
| 34 |
+
- **16 Supported Platforms** — With BlueBubbles (iMessage) and WeChat joining Telegram, Discord, Slack, WhatsApp, Signal, Matrix, Email, SMS, DingTalk, Feishu, WeCom, Mattermost, Home Assistant, and Webhooks, Hermes now runs on 16 messaging platforms out of the box.
|
| 35 |
+
|
| 36 |
+
- **`/debug` & `hermes debug share`** — New debugging toolkit: `/debug` slash command across all platforms for quick diagnostics, plus `hermes debug share` to upload a full debug report to a pastebin for easy sharing when troubleshooting. ([#8681](https://github.com/NousResearch/hermes-agent/pull/8681))
|
| 37 |
+
|
| 38 |
+
---
|
| 39 |
+
|
| 40 |
+
## 🏗️ Core Agent & Architecture
|
| 41 |
+
|
| 42 |
+
### Provider & Model Support
|
| 43 |
+
- **Native xAI (Grok) provider** with direct API access and model catalog ([#7372](https://github.com/NousResearch/hermes-agent/pull/7372))
|
| 44 |
+
- **Xiaomi MiMo as first-class provider** — setup wizard, model catalog, empty response recovery ([#7855](https://github.com/NousResearch/hermes-agent/pull/7855))
|
| 45 |
+
- **Qwen OAuth provider** with portal request support ([#6282](https://github.com/NousResearch/hermes-agent/pull/6282))
|
| 46 |
+
- **Fast Mode** — `/fast` toggle for OpenAI Priority Processing + Anthropic fast tier ([#6875](https://github.com/NousResearch/hermes-agent/pull/6875), [#6960](https://github.com/NousResearch/hermes-agent/pull/6960), [#7037](https://github.com/NousResearch/hermes-agent/pull/7037))
|
| 47 |
+
- **Structured API error classification** for smart failover decisions ([#6514](https://github.com/NousResearch/hermes-agent/pull/6514))
|
| 48 |
+
- **Rate limit header capture** shown in `/usage` ([#6541](https://github.com/NousResearch/hermes-agent/pull/6541))
|
| 49 |
+
- **API server model name** derived from profile name ([#6857](https://github.com/NousResearch/hermes-agent/pull/6857))
|
| 50 |
+
- **Custom providers** now included in `/model` listings and resolution ([#7088](https://github.com/NousResearch/hermes-agent/pull/7088))
|
| 51 |
+
- **Fallback provider activation** on repeated empty responses with user-visible status ([#7505](https://github.com/NousResearch/hermes-agent/pull/7505))
|
| 52 |
+
- **OpenRouter variant tags** (`:free`, `:extended`, `:fast`) preserved during model switch ([#6383](https://github.com/NousResearch/hermes-agent/pull/6383))
|
| 53 |
+
- **Credential exhaustion TTL** reduced from 24 hours to 1 hour ([#6504](https://github.com/NousResearch/hermes-agent/pull/6504))
|
| 54 |
+
- **OAuth credential lifecycle** hardening — stale pool keys, auth.json sync, Codex CLI race fixes ([#6874](https://github.com/NousResearch/hermes-agent/pull/6874))
|
| 55 |
+
- Empty response recovery for reasoning models (MiMo, Qwen, GLM) ([#8609](https://github.com/NousResearch/hermes-agent/pull/8609))
|
| 56 |
+
- MiniMax context lengths, thinking guard, endpoint corrections ([#6082](https://github.com/NousResearch/hermes-agent/pull/6082), [#7126](https://github.com/NousResearch/hermes-agent/pull/7126))
|
| 57 |
+
- Z.AI endpoint auto-detect via probe and cache ([#5763](https://github.com/NousResearch/hermes-agent/pull/5763))
|
| 58 |
+
|
| 59 |
+
### Agent Loop & Conversation
|
| 60 |
+
- **Pluggable context engine slot** via `hermes plugins` ([#7464](https://github.com/NousResearch/hermes-agent/pull/7464))
|
| 61 |
+
- **Background process monitoring** — `watch_patterns` for real-time output alerts ([#7635](https://github.com/NousResearch/hermes-agent/pull/7635))
|
| 62 |
+
- **Improved context compression** — higher limits, tool tracking, degradation warnings, token-budget tail protection ([#6395](https://github.com/NousResearch/hermes-agent/pull/6395), [#6453](https://github.com/NousResearch/hermes-agent/pull/6453))
|
| 63 |
+
- **`/compress <focus>`** — guided compression with a focus topic ([#8017](https://github.com/NousResearch/hermes-agent/pull/8017))
|
| 64 |
+
- **Tiered context pressure warnings** with gateway dedup ([#6411](https://github.com/NousResearch/hermes-agent/pull/6411))
|
| 65 |
+
- **Staged inactivity warning** before timeout escalation ([#6387](https://github.com/NousResearch/hermes-agent/pull/6387))
|
| 66 |
+
- **Prevent agent from stopping mid-task** — compression floor, budget overhaul, activity tracking ([#7983](https://github.com/NousResearch/hermes-agent/pull/7983))
|
| 67 |
+
- **Propagate child activity to parent** during `delegate_task` ([#7295](https://github.com/NousResearch/hermes-agent/pull/7295))
|
| 68 |
+
- **Truncated streaming tool call detection** before execution ([#6847](https://github.com/NousResearch/hermes-agent/pull/6847))
|
| 69 |
+
- Empty response retry (3 attempts with nudge) ([#6488](https://github.com/NousResearch/hermes-agent/pull/6488))
|
| 70 |
+
- Adaptive streaming backoff + cursor strip to prevent message truncation ([#7683](https://github.com/NousResearch/hermes-agent/pull/7683))
|
| 71 |
+
- Compression uses live session model instead of stale persisted config ([#8258](https://github.com/NousResearch/hermes-agent/pull/8258))
|
| 72 |
+
- Strip `<thought>` tags from Gemma 4 responses ([#8562](https://github.com/NousResearch/hermes-agent/pull/8562))
|
| 73 |
+
- Prevent `<think>` in prose from suppressing response output ([#6968](https://github.com/NousResearch/hermes-agent/pull/6968))
|
| 74 |
+
- Turn-exit diagnostic logging to agent loop ([#6549](https://github.com/NousResearch/hermes-agent/pull/6549))
|
| 75 |
+
- Scope tool interrupt signal per-thread to prevent cross-session leaks ([#7930](https://github.com/NousResearch/hermes-agent/pull/7930))
|
| 76 |
+
|
| 77 |
+
### Memory & Sessions
|
| 78 |
+
- **Hindsight memory plugin** — feature parity, setup wizard, config improvements — @nicoloboschi ([#6428](https://github.com/NousResearch/hermes-agent/pull/6428))
|
| 79 |
+
- **Honcho** — opt-in `initOnSessionStart` for tools mode — @Kathie-yu ([#6995](https://github.com/NousResearch/hermes-agent/pull/6995))
|
| 80 |
+
- Orphan children instead of cascade-deleting in prune/delete ([#6513](https://github.com/NousResearch/hermes-agent/pull/6513))
|
| 81 |
+
- Doctor command only checks the active memory provider ([#6285](https://github.com/NousResearch/hermes-agent/pull/6285))
|
| 82 |
+
|
| 83 |
+
---
|
| 84 |
+
|
| 85 |
+
## 📱 Messaging Platforms (Gateway)
|
| 86 |
+
|
| 87 |
+
### New Platforms
|
| 88 |
+
- **BlueBubbles (iMessage)** — full adapter with auto-webhook registration, setup wizard, and crash resilience ([#6437](https://github.com/NousResearch/hermes-agent/pull/6437), [#6460](https://github.com/NousResearch/hermes-agent/pull/6460), [#6494](https://github.com/NousResearch/hermes-agent/pull/6494), [#7107](https://github.com/NousResearch/hermes-agent/pull/7107))
|
| 89 |
+
- **Weixin (WeChat)** — native support via iLink Bot API with streaming, media uploads, markdown links ([#7166](https://github.com/NousResearch/hermes-agent/pull/7166), [#8665](https://github.com/NousResearch/hermes-agent/pull/8665))
|
| 90 |
+
- **WeCom Callback Mode** — self-built enterprise app adapter with atomic state persistence ([#7943](https://github.com/NousResearch/hermes-agent/pull/7943), [#7928](https://github.com/NousResearch/hermes-agent/pull/7928))
|
| 91 |
+
|
| 92 |
+
### Discord
|
| 93 |
+
- **Allowed channels whitelist** config — @jarvis-phw ([#7044](https://github.com/NousResearch/hermes-agent/pull/7044))
|
| 94 |
+
- **Forum channel topic inheritance** in thread sessions — @hermes-agent-dhabibi ([#6377](https://github.com/NousResearch/hermes-agent/pull/6377))
|
| 95 |
+
- **DISCORD_REPLY_TO_MODE** setting ([#6333](https://github.com/NousResearch/hermes-agent/pull/6333))
|
| 96 |
+
- Accept `.log` attachments, raise document size limit — @kira-ariaki ([#6467](https://github.com/NousResearch/hermes-agent/pull/6467))
|
| 97 |
+
- Decouple readiness from slash sync ([#8016](https://github.com/NousResearch/hermes-agent/pull/8016))
|
| 98 |
+
|
| 99 |
+
### Slack
|
| 100 |
+
- **Consolidated Slack improvements** — 7 community PRs salvaged into one ([#6809](https://github.com/NousResearch/hermes-agent/pull/6809))
|
| 101 |
+
- Handle assistant thread lifecycle events ([#6433](https://github.com/NousResearch/hermes-agent/pull/6433))
|
| 102 |
+
|
| 103 |
+
### Matrix
|
| 104 |
+
- **Migrated from matrix-nio to mautrix-python** ([#7518](https://github.com/NousResearch/hermes-agent/pull/7518))
|
| 105 |
+
- SQLite crypto store replacing pickle (fixes E2EE decryption) — @alt-glitch ([#7981](https://github.com/NousResearch/hermes-agent/pull/7981))
|
| 106 |
+
- Cross-signing recovery key verification for E2EE migration ([#8282](https://github.com/NousResearch/hermes-agent/pull/8282))
|
| 107 |
+
- DM mention threads + group chat events for Feishu ([#7423](https://github.com/NousResearch/hermes-agent/pull/7423))
|
| 108 |
+
|
| 109 |
+
### Gateway Core
|
| 110 |
+
- **Unified proxy support** — SOCKS, DISCORD_PROXY, multi-platform with macOS auto-detection ([#6814](https://github.com/NousResearch/hermes-agent/pull/6814))
|
| 111 |
+
- **Inbound text batching** for Discord, Matrix, WeCom + adaptive delay ([#6979](https://github.com/NousResearch/hermes-agent/pull/6979))
|
| 112 |
+
- **Surface natural mid-turn assistant messages** in chat platforms ([#7978](https://github.com/NousResearch/hermes-agent/pull/7978))
|
| 113 |
+
- **WSL-aware gateway** with smart systemd detection ([#7510](https://github.com/NousResearch/hermes-agent/pull/7510))
|
| 114 |
+
- **All missing platforms added to setup wizard** ([#7949](https://github.com/NousResearch/hermes-agent/pull/7949))
|
| 115 |
+
- **Per-platform `tool_progress` overrides** ([#6348](https://github.com/NousResearch/hermes-agent/pull/6348))
|
| 116 |
+
- **Configurable 'still working' notification interval** ([#8572](https://github.com/NousResearch/hermes-agent/pull/8572))
|
| 117 |
+
- `/model` switch persists across messages ([#7081](https://github.com/NousResearch/hermes-agent/pull/7081))
|
| 118 |
+
- `/usage` shows rate limits, cost, and token details between turns ([#7038](https://github.com/NousResearch/hermes-agent/pull/7038))
|
| 119 |
+
- Drain in-flight work before restart ([#7503](https://github.com/NousResearch/hermes-agent/pull/7503))
|
| 120 |
+
- Don't evict cached agent on failed runs — prevents MCP restart loop ([#7539](https://github.com/NousResearch/hermes-agent/pull/7539))
|
| 121 |
+
- Replace `os.environ` session state with `contextvars` ([#7454](https://github.com/NousResearch/hermes-agent/pull/7454))
|
| 122 |
+
- Derive channel directory platforms from enum instead of hardcoded list ([#7450](https://github.com/NousResearch/hermes-agent/pull/7450))
|
| 123 |
+
- Validate image downloads before caching (cross-platform) ([#7125](https://github.com/NousResearch/hermes-agent/pull/7125))
|
| 124 |
+
- Cross-platform webhook delivery for all platforms ([#7095](https://github.com/NousResearch/hermes-agent/pull/7095))
|
| 125 |
+
- Cron Discord thread_id delivery support ([#7106](https://github.com/NousResearch/hermes-agent/pull/7106))
|
| 126 |
+
- Feishu QR-based bot onboarding ([#8570](https://github.com/NousResearch/hermes-agent/pull/8570))
|
| 127 |
+
- Gateway status scoped to active profile ([#7951](https://github.com/NousResearch/hermes-agent/pull/7951))
|
| 128 |
+
- Prevent background process notifications from triggering false pairing requests ([#6434](https://github.com/NousResearch/hermes-agent/pull/6434))
|
| 129 |
+
|
| 130 |
+
---
|
| 131 |
+
|
| 132 |
+
## 🖥️ CLI & User Experience
|
| 133 |
+
|
| 134 |
+
### Interactive CLI
|
| 135 |
+
- **Termux / Android support** — adapted install paths, TUI, voice, `/image` ([#6834](https://github.com/NousResearch/hermes-agent/pull/6834))
|
| 136 |
+
- **Native `/model` picker modal** for provider → model selection ([#8003](https://github.com/NousResearch/hermes-agent/pull/8003))
|
| 137 |
+
- **Live per-tool elapsed timer** restored in TUI spinner ([#7359](https://github.com/NousResearch/hermes-agent/pull/7359))
|
| 138 |
+
- **Stacked tool progress scrollback** in TUI ([#8201](https://github.com/NousResearch/hermes-agent/pull/8201))
|
| 139 |
+
- **Random tips on new session start** (CLI + gateway, 279 tips) ([#8225](https://github.com/NousResearch/hermes-agent/pull/8225), [#8237](https://github.com/NousResearch/hermes-agent/pull/8237))
|
| 140 |
+
- **`hermes dump`** — copy-pasteable setup summary for debugging ([#6550](https://github.com/NousResearch/hermes-agent/pull/6550))
|
| 141 |
+
- **`hermes backup` / `hermes import`** — full config backup and restore ([#7997](https://github.com/NousResearch/hermes-agent/pull/7997))
|
| 142 |
+
- **WSL environment hint** in system prompt ([#8285](https://github.com/NousResearch/hermes-agent/pull/8285))
|
| 143 |
+
- **Profile creation UX** — seed SOUL.md + credential warning ([#8553](https://github.com/NousResearch/hermes-agent/pull/8553))
|
| 144 |
+
- Shell-aware sudo detection, empty password support ([#6517](https://github.com/NousResearch/hermes-agent/pull/6517))
|
| 145 |
+
- Flush stdin after curses/terminal menus to prevent escape sequence leakage ([#7167](https://github.com/NousResearch/hermes-agent/pull/7167))
|
| 146 |
+
- Handle broken stdin in prompt_toolkit startup ([#8560](https://github.com/NousResearch/hermes-agent/pull/8560))
|
| 147 |
+
|
| 148 |
+
### Setup & Configuration
|
| 149 |
+
- **Per-platform display verbosity** configuration ([#8006](https://github.com/NousResearch/hermes-agent/pull/8006))
|
| 150 |
+
- **Component-separated logging** with session context and filtering ([#7991](https://github.com/NousResearch/hermes-agent/pull/7991))
|
| 151 |
+
- **`network.force_ipv4`** config to fix IPv6 timeout issues ([#8196](https://github.com/NousResearch/hermes-agent/pull/8196))
|
| 152 |
+
- **Standardize message whitespace and JSON formatting** ([#7988](https://github.com/NousResearch/hermes-agent/pull/7988))
|
| 153 |
+
- **Rebrand OpenClaw → Hermes** during migration ([#8210](https://github.com/NousResearch/hermes-agent/pull/8210))
|
| 154 |
+
- Config.yaml takes priority over env vars for auxiliary settings ([#7889](https://github.com/NousResearch/hermes-agent/pull/7889))
|
| 155 |
+
- Harden setup provider flows + live OpenRouter catalog refresh ([#7078](https://github.com/NousResearch/hermes-agent/pull/7078))
|
| 156 |
+
- Normalize reasoning effort ordering across all surfaces ([#6804](https://github.com/NousResearch/hermes-agent/pull/6804))
|
| 157 |
+
- Remove dead `LLM_MODEL` env var + migration to clear stale entries ([#6543](https://github.com/NousResearch/hermes-agent/pull/6543))
|
| 158 |
+
- Remove `/prompt` slash command — prefix expansion footgun ([#6752](https://github.com/NousResearch/hermes-agent/pull/6752))
|
| 159 |
+
- `HERMES_HOME_MODE` env var to override permissions — @ygd58 ([#6993](https://github.com/NousResearch/hermes-agent/pull/6993))
|
| 160 |
+
- Fall back to default model when model config is empty ([#8303](https://github.com/NousResearch/hermes-agent/pull/8303))
|
| 161 |
+
- Warn when compression model context is too small ([#7894](https://github.com/NousResearch/hermes-agent/pull/7894))
|
| 162 |
+
|
| 163 |
+
---
|
| 164 |
+
|
| 165 |
+
## 🔧 Tool System
|
| 166 |
+
|
| 167 |
+
### Environments & Execution
|
| 168 |
+
- **Unified spawn-per-call execution layer** for environments ([#6343](https://github.com/NousResearch/hermes-agent/pull/6343))
|
| 169 |
+
- **Unified file sync** with mtime tracking, deletion, and transactional state ([#7087](https://github.com/NousResearch/hermes-agent/pull/7087))
|
| 170 |
+
- **Persistent sandbox envs** survive between turns ([#6412](https://github.com/NousResearch/hermes-agent/pull/6412))
|
| 171 |
+
- **Bulk file sync** via tar pipe for SSH/Modal backends — @alt-glitch ([#8014](https://github.com/NousResearch/hermes-agent/pull/8014))
|
| 172 |
+
- **Daytona** — bulk upload, config bridge, silent disk cap ([#7538](https://github.com/NousResearch/hermes-agent/pull/7538))
|
| 173 |
+
- Foreground timeout cap to prevent session deadlocks ([#7082](https://github.com/NousResearch/hermes-agent/pull/7082))
|
| 174 |
+
- Guard invalid command values ([#6417](https://github.com/NousResearch/hermes-agent/pull/6417))
|
| 175 |
+
|
| 176 |
+
### MCP
|
| 177 |
+
- **`hermes mcp add --env` and `--preset`** support ([#7970](https://github.com/NousResearch/hermes-agent/pull/7970))
|
| 178 |
+
- Combine `content` and `structuredContent` when both present ([#7118](https://github.com/NousResearch/hermes-agent/pull/7118))
|
| 179 |
+
- MCP tool name deconfliction fixes ([#7654](https://github.com/NousResearch/hermes-agent/pull/7654))
|
| 180 |
+
|
| 181 |
+
### Browser
|
| 182 |
+
- Browser hardening — dead code removal, caching, scroll perf, security, thread safety ([#7354](https://github.com/NousResearch/hermes-agent/pull/7354))
|
| 183 |
+
- `/browser connect` auto-launch uses dedicated Chrome profile dir ([#6821](https://github.com/NousResearch/hermes-agent/pull/6821))
|
| 184 |
+
- Reap orphaned browser sessions on startup ([#7931](https://github.com/NousResearch/hermes-agent/pull/7931))
|
| 185 |
+
|
| 186 |
+
### Voice & Vision
|
| 187 |
+
- **Voxtral TTS provider** (Mistral AI) ([#7653](https://github.com/NousResearch/hermes-agent/pull/7653))
|
| 188 |
+
- **TTS speed support** for Edge TTS, OpenAI TTS, MiniMax ([#8666](https://github.com/NousResearch/hermes-agent/pull/8666))
|
| 189 |
+
- **Vision auto-resize** for oversized images, raise limit to 20 MB, retry-on-failure ([#7883](https://github.com/NousResearch/hermes-agent/pull/7883), [#7902](https://github.com/NousResearch/hermes-agent/pull/7902))
|
| 190 |
+
- STT provider-model mismatch fix (whisper-1 vs faster-whisper) ([#7113](https://github.com/NousResearch/hermes-agent/pull/7113))
|
| 191 |
+
|
| 192 |
+
### Other Tools
|
| 193 |
+
- **`hermes dump`** command for setup summary ([#6550](https://github.com/NousResearch/hermes-agent/pull/6550))
|
| 194 |
+
- TODO store enforces ID uniqueness during replace operations ([#7986](https://github.com/NousResearch/hermes-agent/pull/7986))
|
| 195 |
+
- List all available toolsets in `delegate_task` schema description ([#8231](https://github.com/NousResearch/hermes-agent/pull/8231))
|
| 196 |
+
- API server: tool progress as custom SSE event to prevent model corruption ([#7500](https://github.com/NousResearch/hermes-agent/pull/7500))
|
| 197 |
+
- API server: share one Docker container across all conversations ([#7127](https://github.com/NousResearch/hermes-agent/pull/7127))
|
| 198 |
+
|
| 199 |
+
---
|
| 200 |
+
|
| 201 |
+
## 🧩 Skills Ecosystem
|
| 202 |
+
|
| 203 |
+
- **Centralized skills index + tree cache** — eliminates rate-limit failures on install ([#8575](https://github.com/NousResearch/hermes-agent/pull/8575))
|
| 204 |
+
- **More aggressive skill loading instructions** in system prompt (v3) ([#8209](https://github.com/NousResearch/hermes-agent/pull/8209), [#8286](https://github.com/NousResearch/hermes-agent/pull/8286))
|
| 205 |
+
- **Google Workspace skill** migrated to GWS CLI backend ([#6788](https://github.com/NousResearch/hermes-agent/pull/6788))
|
| 206 |
+
- **Creative divergence strategies** skill — @SHL0MS ([#6882](https://github.com/NousResearch/hermes-agent/pull/6882))
|
| 207 |
+
- **Creative ideation** — constraint-driven project generation — @SHL0MS ([#7555](https://github.com/NousResearch/hermes-agent/pull/7555))
|
| 208 |
+
- Parallelize skills browse/search to prevent hanging ([#7301](https://github.com/NousResearch/hermes-agent/pull/7301))
|
| 209 |
+
- Read name from SKILL.md frontmatter in skills_sync ([#7623](https://github.com/NousResearch/hermes-agent/pull/7623))
|
| 210 |
+
|
| 211 |
+
---
|
| 212 |
+
|
| 213 |
+
## 🔒 Security & Reliability
|
| 214 |
+
|
| 215 |
+
### Security Hardening
|
| 216 |
+
- **Twilio webhook signature validation** — SMS RCE fix ([#7933](https://github.com/NousResearch/hermes-agent/pull/7933))
|
| 217 |
+
- **Shell injection neutralization** in `_write_to_sandbox` via path quoting ([#7940](https://github.com/NousResearch/hermes-agent/pull/7940))
|
| 218 |
+
- **Git argument injection** and path traversal prevention in checkpoint manager ([#7944](https://github.com/NousResearch/hermes-agent/pull/7944))
|
| 219 |
+
- **SSRF redirect bypass** in Slack image uploads + base.py cache helpers ([#7151](https://github.com/NousResearch/hermes-agent/pull/7151))
|
| 220 |
+
- **Path traversal, credential gate, DANGEROUS_PATTERNS gaps** ([#7156](https://github.com/NousResearch/hermes-agent/pull/7156))
|
| 221 |
+
- **API bind guard** — enforce `API_SERVER_KEY` for non-loopback binding ([#7455](https://github.com/NousResearch/hermes-agent/pull/7455))
|
| 222 |
+
- **Approval button authorization** — require auth for session continuation — @Cafexss ([#6930](https://github.com/NousResearch/hermes-agent/pull/6930))
|
| 223 |
+
- Path boundary enforcement in skill manager operations ([#7156](https://github.com/NousResearch/hermes-agent/pull/7156))
|
| 224 |
+
- DingTalk/API webhook URL origin validation, header injection rejection ([#7455](https://github.com/NousResearch/hermes-agent/pull/7455))
|
| 225 |
+
|
| 226 |
+
### Reliability
|
| 227 |
+
- **Contextual error diagnostics** for invalid API responses ([#8565](https://github.com/NousResearch/hermes-agent/pull/8565))
|
| 228 |
+
- **Prevent 400 format errors** from triggering compression loop on Codex ([#6751](https://github.com/NousResearch/hermes-agent/pull/6751))
|
| 229 |
+
- **Don't halve context_length** on output-cap-too-large errors — @KUSH42 ([#6664](https://github.com/NousResearch/hermes-agent/pull/6664))
|
| 230 |
+
- **Recover primary client** on OpenAI transport errors ([#7108](https://github.com/NousResearch/hermes-agent/pull/7108))
|
| 231 |
+
- **Credential pool rotation** on billing-classified 400s ([#7112](https://github.com/NousResearch/hermes-agent/pull/7112))
|
| 232 |
+
- **Auto-increase stream read timeout** for local LLM providers ([#6967](https://github.com/NousResearch/hermes-agent/pull/6967))
|
| 233 |
+
- **Fall back to default certs** when CA bundle path doesn't exist ([#7352](https://github.com/NousResearch/hermes-agent/pull/7352))
|
| 234 |
+
- **Disambiguate usage-limit patterns** in error classifier — @sprmn24 ([#6836](https://github.com/NousResearch/hermes-agent/pull/6836))
|
| 235 |
+
- Harden cron script timeout and provider recovery ([#7079](https://github.com/NousResearch/hermes-agent/pull/7079))
|
| 236 |
+
- Gateway interrupt detection resilient to monitor task failures ([#8208](https://github.com/NousResearch/hermes-agent/pull/8208))
|
| 237 |
+
- Prevent unwanted session auto-reset after graceful gateway restarts ([#8299](https://github.com/NousResearch/hermes-agent/pull/8299))
|
| 238 |
+
- Prevent duplicate update prompt spam in gateway watcher ([#8343](https://github.com/NousResearch/hermes-agent/pull/8343))
|
| 239 |
+
- Deduplicate reasoning items in Responses API input ([#7946](https://github.com/NousResearch/hermes-agent/pull/7946))
|
| 240 |
+
|
| 241 |
+
### Infrastructure
|
| 242 |
+
- **Multi-arch Docker image** — amd64 + arm64 ([#6124](https://github.com/NousResearch/hermes-agent/pull/6124))
|
| 243 |
+
- **Docker runs as non-root user** with virtualenv — @benbarclay contributing ([#8226](https://github.com/NousResearch/hermes-agent/pull/8226))
|
| 244 |
+
- **Use `uv`** for Docker dependency resolution to fix resolution-too-deep ([#6965](https://github.com/NousResearch/hermes-agent/pull/6965))
|
| 245 |
+
- **Container-aware Nix CLI** — auto-route into managed container — @alt-glitch ([#7543](https://github.com/NousResearch/hermes-agent/pull/7543))
|
| 246 |
+
- **Nix shared-state permission model** for interactive CLI users — @alt-glitch ([#6796](https://github.com/NousResearch/hermes-agent/pull/6796))
|
| 247 |
+
- **Per-profile subprocess HOME isolation** ([#7357](https://github.com/NousResearch/hermes-agent/pull/7357))
|
| 248 |
+
- Profile paths fixed in Docker — profiles go to mounted volume ([#7170](https://github.com/NousResearch/hermes-agent/pull/7170))
|
| 249 |
+
- Docker container gateway pathway hardened ([#8614](https://github.com/NousResearch/hermes-agent/pull/8614))
|
| 250 |
+
- Enable unbuffered stdout for live Docker logs ([#6749](https://github.com/NousResearch/hermes-agent/pull/6749))
|
| 251 |
+
- Install procps in Docker image — @HiddenPuppy ([#7032](https://github.com/NousResearch/hermes-agent/pull/7032))
|
| 252 |
+
- Shallow git clone for faster installation — @sosyz ([#8396](https://github.com/NousResearch/hermes-agent/pull/8396))
|
| 253 |
+
- `hermes update` always reset on stash conflict ([#7010](https://github.com/NousResearch/hermes-agent/pull/7010))
|
| 254 |
+
- Write update exit code before gateway restart (cgroup kill race) ([#8288](https://github.com/NousResearch/hermes-agent/pull/8288))
|
| 255 |
+
- Nix: `setupSecrets` optional, tirith runtime dep — @devorun, @ethernet8023 ([#6261](https://github.com/NousResearch/hermes-agent/pull/6261), [#6721](https://github.com/NousResearch/hermes-agent/pull/6721))
|
| 256 |
+
- launchd stop uses `bootout` so `KeepAlive` doesn't respawn ([#7119](https://github.com/NousResearch/hermes-agent/pull/7119))
|
| 257 |
+
|
| 258 |
+
---
|
| 259 |
+
|
| 260 |
+
## 🐛 Notable Bug Fixes
|
| 261 |
+
|
| 262 |
+
- Fix: `/model` switch not persisting across gateway messages ([#7081](https://github.com/NousResearch/hermes-agent/pull/7081))
|
| 263 |
+
- Fix: session-scoped gateway model overrides ignored — @Hygaard ([#7662](https://github.com/NousResearch/hermes-agent/pull/7662))
|
| 264 |
+
- Fix: compaction model context length ignoring config — 3 related issues ([#8258](https://github.com/NousResearch/hermes-agent/pull/8258), [#8107](https://github.com/NousResearch/hermes-agent/pull/8107))
|
| 265 |
+
- Fix: OpenCode.ai context window resolved to 128K instead of 1M ([#6472](https://github.com/NousResearch/hermes-agent/pull/6472))
|
| 266 |
+
- Fix: Codex fallback auth-store lookup — @cherifya ([#6462](https://github.com/NousResearch/hermes-agent/pull/6462))
|
| 267 |
+
- Fix: duplicate completion notifications when process killed ([#7124](https://github.com/NousResearch/hermes-agent/pull/7124))
|
| 268 |
+
- Fix: agent daemon thread prevents orphan CLI processes on tab close ([#8557](https://github.com/NousResearch/hermes-agent/pull/8557))
|
| 269 |
+
- Fix: stale image attachment on text paste and voice input ([#7077](https://github.com/NousResearch/hermes-agent/pull/7077))
|
| 270 |
+
- Fix: DM thread session seeding causing cross-thread contamination ([#7084](https://github.com/NousResearch/hermes-agent/pull/7084))
|
| 271 |
+
- Fix: OpenClaw migration shows dry-run preview before executing ([#6769](https://github.com/NousResearch/hermes-agent/pull/6769))
|
| 272 |
+
- Fix: auth errors misclassified as retryable — @kuishou68 ([#7027](https://github.com/NousResearch/hermes-agent/pull/7027))
|
| 273 |
+
- Fix: Copilot-Integration-Id header missing ([#7083](https://github.com/NousResearch/hermes-agent/pull/7083))
|
| 274 |
+
- Fix: ACP session capabilities — @luyao618 ([#6985](https://github.com/NousResearch/hermes-agent/pull/6985))
|
| 275 |
+
- Fix: ACP PromptResponse usage from top-level fields ([#7086](https://github.com/NousResearch/hermes-agent/pull/7086))
|
| 276 |
+
- Fix: several failing/flaky tests on main — @dsocolobsky ([#6777](https://github.com/NousResearch/hermes-agent/pull/6777))
|
| 277 |
+
- Fix: backup marker filenames — @sprmn24 ([#8600](https://github.com/NousResearch/hermes-agent/pull/8600))
|
| 278 |
+
- Fix: `NoneType` in fast_mode check — @0xbyt4 ([#7350](https://github.com/NousResearch/hermes-agent/pull/7350))
|
| 279 |
+
- Fix: missing imports in uninstall.py — @JiayuuWang ([#7034](https://github.com/NousResearch/hermes-agent/pull/7034))
|
| 280 |
+
|
| 281 |
+
---
|
| 282 |
+
|
| 283 |
+
## 📚 Documentation
|
| 284 |
+
|
| 285 |
+
- Platform adapter developer guide + WeCom Callback docs ([#7969](https://github.com/NousResearch/hermes-agent/pull/7969))
|
| 286 |
+
- Cron troubleshooting guide ([#7122](https://github.com/NousResearch/hermes-agent/pull/7122))
|
| 287 |
+
- Streaming timeout auto-detection for local LLMs ([#6990](https://github.com/NousResearch/hermes-agent/pull/6990))
|
| 288 |
+
- Tool-use enforcement documentation expanded ([#7984](https://github.com/NousResearch/hermes-agent/pull/7984))
|
| 289 |
+
- BlueBubbles pairing instructions ([#6548](https://github.com/NousResearch/hermes-agent/pull/6548))
|
| 290 |
+
- Telegram proxy support section ([#6348](https://github.com/NousResearch/hermes-agent/pull/6348))
|
| 291 |
+
- `hermes dump` and `hermes logs` CLI reference ([#6552](https://github.com/NousResearch/hermes-agent/pull/6552))
|
| 292 |
+
- `tool_progress_overrides` configuration reference ([#6364](https://github.com/NousResearch/hermes-agent/pull/6364))
|
| 293 |
+
- Compression model context length warning docs ([#7879](https://github.com/NousResearch/hermes-agent/pull/7879))
|
| 294 |
+
|
| 295 |
+
---
|
| 296 |
+
|
| 297 |
+
## 👥 Contributors
|
| 298 |
+
|
| 299 |
+
**269 merged PRs** from **24 contributors** across **487 commits**.
|
| 300 |
+
|
| 301 |
+
### Community Contributors
|
| 302 |
+
- **@alt-glitch** (6 PRs) — Nix container-aware CLI, shared-state permissions, Matrix SQLite crypto store, bulk SSH/Modal file sync, Matrix mautrix compat
|
| 303 |
+
- **@SHL0MS** (2 PRs) — Creative divergence strategies skill, creative ideation skill
|
| 304 |
+
- **@sprmn24** (2 PRs) — Error classifier disambiguation, backup marker fix
|
| 305 |
+
- **@nicoloboschi** — Hindsight memory plugin feature parity
|
| 306 |
+
- **@Hygaard** — Session-scoped gateway model override fix
|
| 307 |
+
- **@jarvis-phw** — Discord allowed_channels whitelist
|
| 308 |
+
- **@Kathie-yu** — Honcho initOnSessionStart for tools mode
|
| 309 |
+
- **@hermes-agent-dhabibi** — Discord forum channel topic inheritance
|
| 310 |
+
- **@kira-ariaki** — Discord .log attachments and size limit
|
| 311 |
+
- **@cherifya** — Codex fallback auth-store lookup
|
| 312 |
+
- **@Cafexss** — Security: auth for session continuation
|
| 313 |
+
- **@KUSH42** — Compaction context_length fix
|
| 314 |
+
- **@kuishou68** — Auth error retryable classification fix
|
| 315 |
+
- **@luyao618** — ACP session capabilities
|
| 316 |
+
- **@ygd58** — HERMES_HOME_MODE env var override
|
| 317 |
+
- **@0xbyt4** — Fast mode NoneType fix
|
| 318 |
+
- **@JiayuuWang** — CLI uninstall import fix
|
| 319 |
+
- **@HiddenPuppy** — Docker procps installation
|
| 320 |
+
- **@dsocolobsky** — Test suite fixes
|
| 321 |
+
- **@bobashopcashier** (1 PR) — Graceful gateway drain before restart (salvaged into #7503 from #7290)
|
| 322 |
+
- **@benbarclay** — Docker image tag simplification
|
| 323 |
+
- **@sosyz** — Shallow git clone for faster install
|
| 324 |
+
- **@devorun** — Nix setupSecrets optional
|
| 325 |
+
- **@ethernet8023** — Nix tirith runtime dep
|
| 326 |
+
|
| 327 |
+
---
|
| 328 |
+
|
| 329 |
+
**Full Changelog**: [v2026.4.8...v2026.4.13](https://github.com/NousResearch/hermes-agent/compare/v2026.4.8...v2026.4.13)
|
SECURITY.md
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Hermes Agent Security Policy
|
| 2 |
+
|
| 3 |
+
This document outlines the security protocols, trust model, and deployment hardening guidelines for the **Hermes Agent** project.
|
| 4 |
+
|
| 5 |
+
## 1. Vulnerability Reporting
|
| 6 |
+
|
| 7 |
+
Hermes Agent does **not** operate a bug bounty program. Security issues should be reported via [GitHub Security Advisories (GHSA)](https://github.com/NousResearch/hermes-agent/security/advisories/new) or by emailing **security@nousresearch.com**. Do not open public issues for security vulnerabilities.
|
| 8 |
+
|
| 9 |
+
### Required Submission Details
|
| 10 |
+
- **Title & Severity:** Concise description and CVSS score/rating.
|
| 11 |
+
- **Affected Component:** Exact file path and line range (e.g., `tools/approval.py:120-145`).
|
| 12 |
+
- **Environment:** Output of `hermes version`, commit SHA, OS, and Python version.
|
| 13 |
+
- **Reproduction:** Step-by-step Proof-of-Concept (PoC) against `main` or the latest release.
|
| 14 |
+
- **Impact:** Explanation of what trust boundary was crossed.
|
| 15 |
+
|
| 16 |
+
---
|
| 17 |
+
|
| 18 |
+
## 2. Trust Model
|
| 19 |
+
|
| 20 |
+
The core assumption is that Hermes is a **personal agent** with one trusted operator.
|
| 21 |
+
|
| 22 |
+
### Operator & Session Trust
|
| 23 |
+
- **Single Tenant:** The system protects the operator from LLM actions, not from malicious co-tenants. Multi-user isolation must happen at the OS/host level.
|
| 24 |
+
- **Gateway Security:** Authorized callers (Telegram, Discord, Slack, etc.) receive equal trust. Session keys are used for routing, not as authorization boundaries.
|
| 25 |
+
- **Execution:** Defaults to `terminal.backend: local` (direct host execution). Container isolation (Docker, Modal, Daytona) is opt-in for sandboxing.
|
| 26 |
+
|
| 27 |
+
### Dangerous Command Approval
|
| 28 |
+
The approval system (`tools/approval.py`) is a core security boundary. Terminal commands, file operations, and other potentially destructive actions are gated behind explicit user confirmation before execution. The approval mode is configurable via `approvals.mode` in `config.yaml`:
|
| 29 |
+
- `"on"` (default) — prompts the user to approve dangerous commands.
|
| 30 |
+
- `"auto"` — auto-approves after a configurable delay.
|
| 31 |
+
- `"off"` — disables the gate entirely (break-glass; see Section 3).
|
| 32 |
+
|
| 33 |
+
### Output Redaction
|
| 34 |
+
`agent/redact.py` strips secret-like patterns (API keys, tokens, credentials) from all display output before it reaches the terminal or gateway platform. This prevents accidental credential leakage in chat logs, tool previews, and response text. Redaction operates on the display layer only — underlying values remain intact for internal agent operations.
|
| 35 |
+
|
| 36 |
+
### Skills vs. MCP Servers
|
| 37 |
+
- **Installed Skills:** High trust. Equivalent to local host code; skills can read environment variables and run arbitrary commands.
|
| 38 |
+
- **MCP Servers:** Lower trust. MCP subprocesses receive a filtered environment (`_build_safe_env()` in `tools/mcp_tool.py`) — only safe baseline variables (`PATH`, `HOME`, `XDG_*`) plus variables explicitly declared in the server's `env` config block are passed through. Host credentials are stripped by default. Additionally, packages invoked via `npx`/`uvx` are checked against the OSV malware database before spawning.
|
| 39 |
+
|
| 40 |
+
### Code Execution Sandbox
|
| 41 |
+
The `execute_code` tool (`tools/code_execution_tool.py`) runs LLM-generated Python scripts in a child process with API keys and tokens stripped from the environment to prevent credential exfiltration. Only environment variables explicitly declared by loaded skills (via `env_passthrough`) or by the user in `config.yaml` (`terminal.env_passthrough`) are passed through. The child accesses Hermes tools via RPC, not direct API calls.
|
| 42 |
+
|
| 43 |
+
### Subagents
|
| 44 |
+
- **No recursive delegation:** The `delegate_task` tool is disabled for child agents.
|
| 45 |
+
- **Depth limit:** `MAX_DEPTH = 2` — parent (depth 0) can spawn a child (depth 1); grandchildren are rejected.
|
| 46 |
+
- **Memory isolation:** Subagents run with `skip_memory=True` and do not have access to the parent's persistent memory provider. The parent receives only the task prompt and final response as an observation.
|
| 47 |
+
|
| 48 |
+
---
|
| 49 |
+
|
| 50 |
+
## 3. Out of Scope (Non-Vulnerabilities)
|
| 51 |
+
|
| 52 |
+
The following scenarios are **not** considered security breaches:
|
| 53 |
+
- **Prompt Injection:** Unless it results in a concrete bypass of the approval system, toolset restrictions, or container sandbox.
|
| 54 |
+
- **Public Exposure:** Deploying the gateway to the public internet without external authentication or network protection.
|
| 55 |
+
- **Trusted State Access:** Reports that require pre-existing write access to `~/.hermes/`, `.env`, or `config.yaml` (these are operator-owned files).
|
| 56 |
+
- **Default Behavior:** Host-level command execution when `terminal.backend` is set to `local` — this is the documented default, not a vulnerability.
|
| 57 |
+
- **Configuration Trade-offs:** Intentional break-glass settings such as `approvals.mode: "off"` or `terminal.backend: local` in production.
|
| 58 |
+
- **Tool-level read/access restrictions:** The agent has unrestricted shell access via the `terminal` tool by design. Reports that a specific tool (e.g., `read_file`) can access a resource are not vulnerabilities if the same access is available through `terminal`. Tool-level deny lists only constitute a meaningful security boundary when paired with equivalent restrictions on the terminal side (as with write operations, where `WRITE_DENIED_PATHS` is paired with the dangerous command approval system).
|
| 59 |
+
|
| 60 |
+
---
|
| 61 |
+
|
| 62 |
+
## 4. Deployment Hardening & Best Practices
|
| 63 |
+
|
| 64 |
+
### Filesystem & Network
|
| 65 |
+
- **Production sandboxing:** Use container backends (`docker`, `modal`, `daytona`) instead of `local` for untrusted workloads.
|
| 66 |
+
- **File permissions:** Run as non-root (the Docker image uses UID 10000); protect credentials with `chmod 600 ~/.hermes/.env` on local installs.
|
| 67 |
+
- **Network exposure:** Do not expose the gateway or API server to the public internet without VPN, Tailscale, or firewall protection. SSRF protection is enabled by default across all gateway platform adapters (Telegram, Discord, Slack, Matrix, Mattermost, etc.) with redirect validation. Note: the local terminal backend does not apply SSRF filtering, as it operates within the trusted operator's environment.
|
| 68 |
+
|
| 69 |
+
### Skills & Supply Chain
|
| 70 |
+
- **Skill installation:** Review Skills Guard reports (`tools/skills_guard.py`) before installing third-party skills. The audit log at `~/.hermes/skills/.hub/audit.log` tracks every install and removal.
|
| 71 |
+
- **MCP safety:** OSV malware checking runs automatically for `npx`/`uvx` packages before MCP server processes are spawned.
|
| 72 |
+
- **CI/CD:** GitHub Actions are pinned to full commit SHAs. The `supply-chain-audit.yml` workflow blocks PRs containing `.pth` files or suspicious `base64`+`exec` patterns.
|
| 73 |
+
|
| 74 |
+
### Credential Storage
|
| 75 |
+
- API keys and tokens belong exclusively in `~/.hermes/.env` — never in `config.yaml` or checked into version control.
|
| 76 |
+
- The credential pool system (`agent/credential_pool.py`) handles key rotation and fallback. Credentials are resolved from environment variables, not stored in plaintext databases.
|
| 77 |
+
|
| 78 |
+
---
|
| 79 |
+
|
| 80 |
+
## 5. Disclosure Process
|
| 81 |
+
|
| 82 |
+
- **Coordinated Disclosure:** 90-day window or until a fix is released, whichever comes first.
|
| 83 |
+
- **Communication:** All updates occur via the GHSA thread or email correspondence with security@nousresearch.com.
|
| 84 |
+
- **Credits:** Reporters are credited in release notes unless anonymity is requested.
|
batch_runner.py
ADDED
|
@@ -0,0 +1,1302 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Batch Agent Runner
|
| 4 |
+
|
| 5 |
+
This module provides parallel batch processing capabilities for running the agent
|
| 6 |
+
across multiple prompts from a dataset. It includes:
|
| 7 |
+
- Dataset loading and batching
|
| 8 |
+
- Parallel batch processing with multiprocessing
|
| 9 |
+
- Checkpointing for fault tolerance and resumption
|
| 10 |
+
- Trajectory saving in the proper format (from/value pairs)
|
| 11 |
+
- Tool usage statistics aggregation across all batches
|
| 12 |
+
|
| 13 |
+
Usage:
|
| 14 |
+
python batch_runner.py --dataset_file=data.jsonl --batch_size=10 --run_name=my_run
|
| 15 |
+
|
| 16 |
+
# Resume an interrupted run
|
| 17 |
+
python batch_runner.py --dataset_file=data.jsonl --batch_size=10 --run_name=my_run --resume
|
| 18 |
+
|
| 19 |
+
# Use a specific toolset distribution
|
| 20 |
+
python batch_runner.py --dataset_file=data.jsonl --batch_size=10 --run_name=my_run --distribution=image_gen
|
| 21 |
+
"""
|
| 22 |
+
|
| 23 |
+
# IMPORTANT: hermes_bootstrap must be the very first import — UTF-8 stdio
|
| 24 |
+
# on Windows. No-op on POSIX. See hermes_bootstrap.py for full rationale.
|
| 25 |
+
try:
|
| 26 |
+
import hermes_bootstrap # noqa: F401
|
| 27 |
+
except ModuleNotFoundError:
|
| 28 |
+
# Graceful fallback when hermes_bootstrap isn't registered in the venv
|
| 29 |
+
# yet — happens during partial ``hermes update`` where git-reset landed
|
| 30 |
+
# new code but ``uv pip install -e .`` didn't finish. Missing bootstrap
|
| 31 |
+
# means UTF-8 stdio setup is skipped on Windows; POSIX is unaffected.
|
| 32 |
+
pass
|
| 33 |
+
|
| 34 |
+
import json
|
| 35 |
+
import logging
|
| 36 |
+
import os
|
| 37 |
+
import time
|
| 38 |
+
from pathlib import Path
|
| 39 |
+
from typing import List, Dict, Any, Optional, Tuple
|
| 40 |
+
from datetime import datetime
|
| 41 |
+
from multiprocessing import Pool, Lock
|
| 42 |
+
import traceback
|
| 43 |
+
from rich.progress import Progress, SpinnerColumn, BarColumn, TextColumn, TimeRemainingColumn, MofNCompleteColumn
|
| 44 |
+
from rich.console import Console
|
| 45 |
+
|
| 46 |
+
logger = logging.getLogger(__name__)
|
| 47 |
+
import fire
|
| 48 |
+
|
| 49 |
+
from run_agent import AIAgent
|
| 50 |
+
from toolset_distributions import (
|
| 51 |
+
list_distributions,
|
| 52 |
+
sample_toolsets_from_distribution,
|
| 53 |
+
validate_distribution
|
| 54 |
+
)
|
| 55 |
+
from model_tools import TOOL_TO_TOOLSET_MAP
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
# Global configuration for worker processes
|
| 59 |
+
_WORKER_CONFIG = {}
|
| 60 |
+
|
| 61 |
+
# All possible tools - auto-derived from the master mapping in model_tools.py.
|
| 62 |
+
# This stays in sync automatically when new tools are added to TOOL_TO_TOOLSET_MAP.
|
| 63 |
+
# Used for consistent schema in Arrow/Parquet (HuggingFace datasets) and for
|
| 64 |
+
# filtering corrupted entries during trajectory combination.
|
| 65 |
+
ALL_POSSIBLE_TOOLS = set(TOOL_TO_TOOLSET_MAP.keys())
|
| 66 |
+
|
| 67 |
+
# Default stats for tools that weren't used
|
| 68 |
+
DEFAULT_TOOL_STATS = {'count': 0, 'success': 0, 'failure': 0}
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
def _normalize_tool_stats(tool_stats: Dict[str, Dict[str, int]]) -> Dict[str, Dict[str, int]]:
|
| 72 |
+
"""
|
| 73 |
+
Normalize tool_stats to include all possible tools with consistent schema.
|
| 74 |
+
|
| 75 |
+
This ensures HuggingFace datasets can load the JSONL without schema mismatch errors.
|
| 76 |
+
Tools that weren't used get zero counts.
|
| 77 |
+
|
| 78 |
+
Args:
|
| 79 |
+
tool_stats (Dict): Raw tool statistics from extraction
|
| 80 |
+
|
| 81 |
+
Returns:
|
| 82 |
+
Dict: Normalized tool statistics with all tools present
|
| 83 |
+
"""
|
| 84 |
+
normalized = {}
|
| 85 |
+
|
| 86 |
+
# Add all possible tools with defaults
|
| 87 |
+
for tool in ALL_POSSIBLE_TOOLS:
|
| 88 |
+
if tool in tool_stats:
|
| 89 |
+
normalized[tool] = tool_stats[tool].copy()
|
| 90 |
+
else:
|
| 91 |
+
normalized[tool] = DEFAULT_TOOL_STATS.copy()
|
| 92 |
+
|
| 93 |
+
# Also include any unexpected tools (in case new tools are added)
|
| 94 |
+
for tool, stats in tool_stats.items():
|
| 95 |
+
if tool not in normalized:
|
| 96 |
+
normalized[tool] = stats.copy()
|
| 97 |
+
|
| 98 |
+
return normalized
|
| 99 |
+
|
| 100 |
+
|
| 101 |
+
def _normalize_tool_error_counts(tool_error_counts: Dict[str, int]) -> Dict[str, int]:
|
| 102 |
+
"""
|
| 103 |
+
Normalize tool_error_counts to include all possible tools.
|
| 104 |
+
|
| 105 |
+
Args:
|
| 106 |
+
tool_error_counts (Dict): Raw error counts mapping
|
| 107 |
+
|
| 108 |
+
Returns:
|
| 109 |
+
Dict: Normalized error counts with all tools present
|
| 110 |
+
"""
|
| 111 |
+
normalized = {}
|
| 112 |
+
|
| 113 |
+
# Add all possible tools with zero defaults
|
| 114 |
+
for tool in ALL_POSSIBLE_TOOLS:
|
| 115 |
+
normalized[tool] = tool_error_counts.get(tool, 0)
|
| 116 |
+
|
| 117 |
+
# Also include any unexpected tools
|
| 118 |
+
for tool, count in tool_error_counts.items():
|
| 119 |
+
if tool not in normalized:
|
| 120 |
+
normalized[tool] = count
|
| 121 |
+
|
| 122 |
+
return normalized
|
| 123 |
+
|
| 124 |
+
|
| 125 |
+
def _extract_tool_stats(messages: List[Dict[str, Any]]) -> Dict[str, Dict[str, int]]:
|
| 126 |
+
"""
|
| 127 |
+
Extract tool usage statistics from message history.
|
| 128 |
+
|
| 129 |
+
Args:
|
| 130 |
+
messages (List[Dict]): Message history
|
| 131 |
+
|
| 132 |
+
Returns:
|
| 133 |
+
Dict: Tool statistics with counts and success/failure rates
|
| 134 |
+
"""
|
| 135 |
+
tool_stats = {}
|
| 136 |
+
|
| 137 |
+
# Track tool calls and their results
|
| 138 |
+
tool_calls_map = {} # Map tool_call_id to tool name
|
| 139 |
+
|
| 140 |
+
for msg in messages:
|
| 141 |
+
# Track tool calls from assistant messages
|
| 142 |
+
if msg["role"] == "assistant" and "tool_calls" in msg and msg["tool_calls"]:
|
| 143 |
+
for tool_call in msg["tool_calls"]:
|
| 144 |
+
if not tool_call or not isinstance(tool_call, dict): continue
|
| 145 |
+
tool_name = tool_call["function"]["name"]
|
| 146 |
+
tool_call_id = tool_call["id"]
|
| 147 |
+
|
| 148 |
+
# Initialize stats for this tool if not exists
|
| 149 |
+
if tool_name not in tool_stats:
|
| 150 |
+
tool_stats[tool_name] = {
|
| 151 |
+
"count": 0,
|
| 152 |
+
"success": 0,
|
| 153 |
+
"failure": 0
|
| 154 |
+
}
|
| 155 |
+
|
| 156 |
+
tool_stats[tool_name]["count"] += 1
|
| 157 |
+
tool_calls_map[tool_call_id] = tool_name
|
| 158 |
+
|
| 159 |
+
# Track tool responses
|
| 160 |
+
elif msg["role"] == "tool":
|
| 161 |
+
tool_call_id = msg.get("tool_call_id", "")
|
| 162 |
+
content = msg.get("content", "")
|
| 163 |
+
|
| 164 |
+
# Determine if tool call was successful
|
| 165 |
+
is_success = True
|
| 166 |
+
try:
|
| 167 |
+
# Try to parse as JSON and check for actual error values
|
| 168 |
+
content_json = json.loads(content) if isinstance(content, str) else content
|
| 169 |
+
|
| 170 |
+
if isinstance(content_json, dict):
|
| 171 |
+
# Check if error field exists AND has a non-null value
|
| 172 |
+
if "error" in content_json and content_json["error"] is not None:
|
| 173 |
+
is_success = False
|
| 174 |
+
|
| 175 |
+
# Special handling for terminal tool responses
|
| 176 |
+
# Terminal wraps its response in a "content" field
|
| 177 |
+
if "content" in content_json and isinstance(content_json["content"], dict):
|
| 178 |
+
inner_content = content_json["content"]
|
| 179 |
+
# Check for actual error (non-null error field)
|
| 180 |
+
# Note: non-zero exit codes are not failures - the model can self-correct
|
| 181 |
+
if inner_content.get("error") is not None:
|
| 182 |
+
is_success = False
|
| 183 |
+
|
| 184 |
+
# Check for "success": false pattern used by some tools
|
| 185 |
+
if content_json.get("success") is False:
|
| 186 |
+
is_success = False
|
| 187 |
+
|
| 188 |
+
except (json.JSONDecodeError, ValueError, TypeError):
|
| 189 |
+
# If not JSON, check if content is empty or explicitly states an error
|
| 190 |
+
# Note: We avoid simple substring matching to prevent false positives
|
| 191 |
+
if not content:
|
| 192 |
+
is_success = False
|
| 193 |
+
# Only mark as failure if it explicitly starts with "Error:" or "ERROR:"
|
| 194 |
+
elif content.strip().lower().startswith("error:"):
|
| 195 |
+
is_success = False
|
| 196 |
+
|
| 197 |
+
# Update success/failure count
|
| 198 |
+
if tool_call_id in tool_calls_map:
|
| 199 |
+
tool_name = tool_calls_map[tool_call_id]
|
| 200 |
+
if is_success:
|
| 201 |
+
tool_stats[tool_name]["success"] += 1
|
| 202 |
+
else:
|
| 203 |
+
tool_stats[tool_name]["failure"] += 1
|
| 204 |
+
|
| 205 |
+
return tool_stats
|
| 206 |
+
|
| 207 |
+
|
| 208 |
+
def _extract_reasoning_stats(messages: List[Dict[str, Any]]) -> Dict[str, int]:
|
| 209 |
+
"""
|
| 210 |
+
Count how many assistant turns have reasoning vs no reasoning.
|
| 211 |
+
|
| 212 |
+
Checks for <REASONING_SCRATCHPAD> in content or a non-empty 'reasoning' field
|
| 213 |
+
(native thinking tokens). Returns counts for tracking reasoning coverage.
|
| 214 |
+
|
| 215 |
+
Args:
|
| 216 |
+
messages: Message history
|
| 217 |
+
|
| 218 |
+
Returns:
|
| 219 |
+
Dict with 'total_assistant_turns', 'turns_with_reasoning', 'turns_without_reasoning'
|
| 220 |
+
"""
|
| 221 |
+
total = 0
|
| 222 |
+
with_reasoning = 0
|
| 223 |
+
|
| 224 |
+
for msg in messages:
|
| 225 |
+
if msg.get("role") != "assistant":
|
| 226 |
+
continue
|
| 227 |
+
total += 1
|
| 228 |
+
|
| 229 |
+
content = msg.get("content", "") or ""
|
| 230 |
+
has_scratchpad = "<REASONING_SCRATCHPAD>" in content
|
| 231 |
+
has_native_reasoning = bool(msg.get("reasoning", "").strip()) if msg.get("reasoning") else False
|
| 232 |
+
|
| 233 |
+
if has_scratchpad or has_native_reasoning:
|
| 234 |
+
with_reasoning += 1
|
| 235 |
+
|
| 236 |
+
return {
|
| 237 |
+
"total_assistant_turns": total,
|
| 238 |
+
"turns_with_reasoning": with_reasoning,
|
| 239 |
+
"turns_without_reasoning": total - with_reasoning,
|
| 240 |
+
"has_any_reasoning": with_reasoning > 0,
|
| 241 |
+
}
|
| 242 |
+
|
| 243 |
+
|
| 244 |
+
def _process_single_prompt(
|
| 245 |
+
prompt_index: int,
|
| 246 |
+
prompt_data: Dict[str, Any],
|
| 247 |
+
batch_num: int,
|
| 248 |
+
config: Dict[str, Any]
|
| 249 |
+
) -> Dict[str, Any]:
|
| 250 |
+
"""
|
| 251 |
+
Process a single prompt with the agent.
|
| 252 |
+
|
| 253 |
+
Args:
|
| 254 |
+
prompt_index (int): Index of prompt in dataset
|
| 255 |
+
prompt_data (Dict): Prompt data containing 'prompt' field and optional 'image' field
|
| 256 |
+
batch_num (int): Batch number
|
| 257 |
+
config (Dict): Configuration dict with agent parameters
|
| 258 |
+
|
| 259 |
+
Returns:
|
| 260 |
+
Dict: Result containing trajectory, stats, and metadata
|
| 261 |
+
"""
|
| 262 |
+
prompt = prompt_data["prompt"]
|
| 263 |
+
task_id = f"task_{prompt_index}"
|
| 264 |
+
|
| 265 |
+
# Per-prompt container image override: if the dataset row has an 'image' field,
|
| 266 |
+
# register it for this task's sandbox. Works with Docker, Modal, Singularity, and Daytona.
|
| 267 |
+
container_image = prompt_data.get("image") or prompt_data.get("docker_image")
|
| 268 |
+
if container_image:
|
| 269 |
+
# Verify the image is accessible before spending tokens on the agent loop.
|
| 270 |
+
# For Docker: check local cache, then try pulling.
|
| 271 |
+
# For Modal: skip local check (Modal pulls server-side).
|
| 272 |
+
env_type = os.getenv("TERMINAL_ENV", "local")
|
| 273 |
+
if env_type == "docker":
|
| 274 |
+
import subprocess as _sp
|
| 275 |
+
try:
|
| 276 |
+
probe = _sp.run(
|
| 277 |
+
["docker", "image", "inspect", container_image],
|
| 278 |
+
capture_output=True, timeout=10,
|
| 279 |
+
)
|
| 280 |
+
if probe.returncode != 0:
|
| 281 |
+
if config.get("verbose"):
|
| 282 |
+
print(f" Prompt {prompt_index}: Pulling docker image {container_image}...", flush=True)
|
| 283 |
+
pull = _sp.run(
|
| 284 |
+
["docker", "pull", container_image],
|
| 285 |
+
capture_output=True, text=True, timeout=600,
|
| 286 |
+
)
|
| 287 |
+
if pull.returncode != 0:
|
| 288 |
+
return {
|
| 289 |
+
"success": False,
|
| 290 |
+
"prompt_index": prompt_index,
|
| 291 |
+
"error": f"Docker image not available: {container_image}\n{pull.stderr[:500]}",
|
| 292 |
+
"trajectory": None,
|
| 293 |
+
"tool_stats": {},
|
| 294 |
+
"toolsets_used": [],
|
| 295 |
+
"metadata": {"batch_num": batch_num, "timestamp": datetime.now().isoformat()},
|
| 296 |
+
}
|
| 297 |
+
except FileNotFoundError:
|
| 298 |
+
pass # Docker CLI not installed — skip check (e.g., Modal backend)
|
| 299 |
+
except Exception as img_err:
|
| 300 |
+
if config.get("verbose"):
|
| 301 |
+
print(f" Prompt {prompt_index}: Docker image check failed: {img_err}", flush=True)
|
| 302 |
+
|
| 303 |
+
from tools.terminal_tool import register_task_env_overrides
|
| 304 |
+
overrides = {
|
| 305 |
+
"docker_image": container_image,
|
| 306 |
+
"modal_image": container_image,
|
| 307 |
+
"singularity_image": f"docker://{container_image}",
|
| 308 |
+
"daytona_image": container_image,
|
| 309 |
+
}
|
| 310 |
+
if prompt_data.get("cwd"):
|
| 311 |
+
overrides["cwd"] = prompt_data["cwd"]
|
| 312 |
+
register_task_env_overrides(task_id, overrides)
|
| 313 |
+
if config.get("verbose"):
|
| 314 |
+
print(f" Prompt {prompt_index}: Using container image {container_image}")
|
| 315 |
+
|
| 316 |
+
try:
|
| 317 |
+
# Sample toolsets from distribution for this prompt
|
| 318 |
+
selected_toolsets = sample_toolsets_from_distribution(config["distribution"])
|
| 319 |
+
|
| 320 |
+
if config.get("verbose"):
|
| 321 |
+
print(f" Prompt {prompt_index}: Using toolsets {selected_toolsets}")
|
| 322 |
+
|
| 323 |
+
# Initialize agent with sampled toolsets and log prefix for identification
|
| 324 |
+
log_prefix = f"[B{batch_num}:P{prompt_index}]"
|
| 325 |
+
agent = AIAgent(
|
| 326 |
+
base_url=config.get("base_url"),
|
| 327 |
+
api_key=config.get("api_key"),
|
| 328 |
+
model=config["model"],
|
| 329 |
+
max_iterations=config["max_iterations"],
|
| 330 |
+
enabled_toolsets=selected_toolsets,
|
| 331 |
+
save_trajectories=False, # We handle saving ourselves
|
| 332 |
+
verbose_logging=config.get("verbose", False),
|
| 333 |
+
ephemeral_system_prompt=config.get("ephemeral_system_prompt"),
|
| 334 |
+
log_prefix_chars=config.get("log_prefix_chars", 100),
|
| 335 |
+
log_prefix=log_prefix,
|
| 336 |
+
providers_allowed=config.get("providers_allowed"),
|
| 337 |
+
providers_ignored=config.get("providers_ignored"),
|
| 338 |
+
providers_order=config.get("providers_order"),
|
| 339 |
+
provider_sort=config.get("provider_sort"),
|
| 340 |
+
openrouter_min_coding_score=config.get("openrouter_min_coding_score"),
|
| 341 |
+
max_tokens=config.get("max_tokens"),
|
| 342 |
+
reasoning_config=config.get("reasoning_config"),
|
| 343 |
+
prefill_messages=config.get("prefill_messages"),
|
| 344 |
+
skip_context_files=True, # Don't pollute trajectories with SOUL.md/AGENTS.md
|
| 345 |
+
skip_memory=True, # Don't use persistent memory in batch runs
|
| 346 |
+
)
|
| 347 |
+
|
| 348 |
+
# Run the agent with task_id to ensure each task gets its own isolated VM
|
| 349 |
+
result = agent.run_conversation(prompt, task_id=task_id)
|
| 350 |
+
|
| 351 |
+
# Extract tool usage statistics
|
| 352 |
+
tool_stats = _extract_tool_stats(result["messages"])
|
| 353 |
+
|
| 354 |
+
# Extract reasoning coverage stats
|
| 355 |
+
reasoning_stats = _extract_reasoning_stats(result["messages"])
|
| 356 |
+
|
| 357 |
+
# Convert to trajectory format (using existing method)
|
| 358 |
+
trajectory = agent._convert_to_trajectory_format(
|
| 359 |
+
result["messages"],
|
| 360 |
+
prompt,
|
| 361 |
+
result["completed"]
|
| 362 |
+
)
|
| 363 |
+
|
| 364 |
+
return {
|
| 365 |
+
"success": True,
|
| 366 |
+
"prompt_index": prompt_index,
|
| 367 |
+
"trajectory": trajectory,
|
| 368 |
+
"tool_stats": tool_stats,
|
| 369 |
+
"reasoning_stats": reasoning_stats,
|
| 370 |
+
"completed": result["completed"],
|
| 371 |
+
"partial": result.get("partial", False),
|
| 372 |
+
"api_calls": result["api_calls"],
|
| 373 |
+
"toolsets_used": selected_toolsets,
|
| 374 |
+
"metadata": {
|
| 375 |
+
"batch_num": batch_num,
|
| 376 |
+
"timestamp": datetime.now().isoformat(),
|
| 377 |
+
"model": config["model"]
|
| 378 |
+
}
|
| 379 |
+
}
|
| 380 |
+
|
| 381 |
+
except Exception as e:
|
| 382 |
+
print(f"❌ Error processing prompt {prompt_index}: {e}")
|
| 383 |
+
if config.get("verbose"):
|
| 384 |
+
traceback.print_exc()
|
| 385 |
+
|
| 386 |
+
return {
|
| 387 |
+
"success": False,
|
| 388 |
+
"prompt_index": prompt_index,
|
| 389 |
+
"error": str(e),
|
| 390 |
+
"trajectory": None,
|
| 391 |
+
"tool_stats": {},
|
| 392 |
+
"toolsets_used": [],
|
| 393 |
+
"metadata": {
|
| 394 |
+
"batch_num": batch_num,
|
| 395 |
+
"timestamp": datetime.now().isoformat()
|
| 396 |
+
}
|
| 397 |
+
}
|
| 398 |
+
|
| 399 |
+
|
| 400 |
+
def _process_batch_worker(args: Tuple) -> Dict[str, Any]:
|
| 401 |
+
"""
|
| 402 |
+
Worker function to process a single batch of prompts.
|
| 403 |
+
|
| 404 |
+
Args:
|
| 405 |
+
args (Tuple): (batch_num, batch_data, output_dir, completed_prompts, config)
|
| 406 |
+
|
| 407 |
+
Returns:
|
| 408 |
+
Dict: Batch results with statistics
|
| 409 |
+
"""
|
| 410 |
+
batch_num, batch_data, output_dir, completed_prompts_set, config = args
|
| 411 |
+
|
| 412 |
+
output_dir = Path(output_dir)
|
| 413 |
+
print(f"\n🔄 Batch {batch_num}: Starting ({len(batch_data)} prompts)")
|
| 414 |
+
|
| 415 |
+
# Output file for this batch
|
| 416 |
+
batch_output_file = output_dir / f"batch_{batch_num}.jsonl"
|
| 417 |
+
|
| 418 |
+
# Filter out already completed prompts
|
| 419 |
+
prompts_to_process = [
|
| 420 |
+
(idx, data) for idx, data in batch_data
|
| 421 |
+
if idx not in completed_prompts_set
|
| 422 |
+
]
|
| 423 |
+
|
| 424 |
+
if not prompts_to_process:
|
| 425 |
+
print(f"✅ Batch {batch_num}: Already completed (skipping)")
|
| 426 |
+
return {
|
| 427 |
+
"batch_num": batch_num,
|
| 428 |
+
"processed": 0,
|
| 429 |
+
"skipped": len(batch_data),
|
| 430 |
+
"tool_stats": {},
|
| 431 |
+
"completed_prompts": []
|
| 432 |
+
}
|
| 433 |
+
|
| 434 |
+
print(f" Processing {len(prompts_to_process)} prompts (skipping {len(batch_data) - len(prompts_to_process)} already completed)")
|
| 435 |
+
|
| 436 |
+
# Initialize aggregated stats for this batch
|
| 437 |
+
batch_tool_stats = {}
|
| 438 |
+
batch_reasoning_stats = {"total_assistant_turns": 0, "turns_with_reasoning": 0, "turns_without_reasoning": 0}
|
| 439 |
+
completed_in_batch = []
|
| 440 |
+
discarded_no_reasoning = 0
|
| 441 |
+
|
| 442 |
+
# Process each prompt sequentially in this batch
|
| 443 |
+
for prompt_index, prompt_data in prompts_to_process:
|
| 444 |
+
# Process the prompt
|
| 445 |
+
result = _process_single_prompt(
|
| 446 |
+
prompt_index,
|
| 447 |
+
prompt_data,
|
| 448 |
+
batch_num,
|
| 449 |
+
config
|
| 450 |
+
)
|
| 451 |
+
|
| 452 |
+
# Save trajectory if successful
|
| 453 |
+
if result["success"] and result["trajectory"]:
|
| 454 |
+
# Discard samples with zero reasoning across all turns
|
| 455 |
+
reasoning = result.get("reasoning_stats", {})
|
| 456 |
+
if not reasoning.get("has_any_reasoning", True):
|
| 457 |
+
print(f" 🚫 Prompt {prompt_index} discarded (no reasoning in any turn)")
|
| 458 |
+
discarded_no_reasoning += 1
|
| 459 |
+
completed_in_batch.append(prompt_index)
|
| 460 |
+
continue
|
| 461 |
+
|
| 462 |
+
# Get and normalize tool stats for consistent schema across all entries
|
| 463 |
+
raw_tool_stats = result.get("tool_stats", {})
|
| 464 |
+
tool_stats = _normalize_tool_stats(raw_tool_stats)
|
| 465 |
+
|
| 466 |
+
# Create normalized tool_error_counts mapping tool names to their failure counts
|
| 467 |
+
raw_error_counts = {
|
| 468 |
+
tool_name: stats.get("failure", 0)
|
| 469 |
+
for tool_name, stats in raw_tool_stats.items()
|
| 470 |
+
}
|
| 471 |
+
tool_error_counts = _normalize_tool_error_counts(raw_error_counts)
|
| 472 |
+
|
| 473 |
+
trajectory_entry = {
|
| 474 |
+
"prompt_index": prompt_index,
|
| 475 |
+
"conversations": result["trajectory"],
|
| 476 |
+
"metadata": result["metadata"],
|
| 477 |
+
"completed": result["completed"],
|
| 478 |
+
"partial": result.get("partial", False), # True if stopped due to invalid tool calls
|
| 479 |
+
"api_calls": result["api_calls"],
|
| 480 |
+
"toolsets_used": result["toolsets_used"],
|
| 481 |
+
"tool_stats": tool_stats, # Full stats: {tool: {count, success, failure}} - normalized
|
| 482 |
+
"tool_error_counts": tool_error_counts # Simple: {tool: failure_count} - normalized
|
| 483 |
+
}
|
| 484 |
+
|
| 485 |
+
# Append to batch output file
|
| 486 |
+
with open(batch_output_file, 'a', encoding='utf-8') as f:
|
| 487 |
+
f.write(json.dumps(trajectory_entry, ensure_ascii=False) + "\n")
|
| 488 |
+
|
| 489 |
+
# Aggregate tool statistics
|
| 490 |
+
for tool_name, stats in result.get("tool_stats", {}).items():
|
| 491 |
+
if tool_name not in batch_tool_stats:
|
| 492 |
+
batch_tool_stats[tool_name] = {
|
| 493 |
+
"count": 0,
|
| 494 |
+
"success": 0,
|
| 495 |
+
"failure": 0
|
| 496 |
+
}
|
| 497 |
+
|
| 498 |
+
batch_tool_stats[tool_name]["count"] += stats["count"]
|
| 499 |
+
batch_tool_stats[tool_name]["success"] += stats["success"]
|
| 500 |
+
batch_tool_stats[tool_name]["failure"] += stats["failure"]
|
| 501 |
+
|
| 502 |
+
# Aggregate reasoning stats
|
| 503 |
+
for key in batch_reasoning_stats:
|
| 504 |
+
batch_reasoning_stats[key] += result.get("reasoning_stats", {}).get(key, 0)
|
| 505 |
+
|
| 506 |
+
# Only mark as completed if successfully saved (failed prompts can be retried on resume)
|
| 507 |
+
if result["success"] and result["trajectory"]:
|
| 508 |
+
completed_in_batch.append(prompt_index)
|
| 509 |
+
status = "⚠️ partial" if result.get("partial") else "✅"
|
| 510 |
+
print(f" {status} Prompt {prompt_index} completed")
|
| 511 |
+
else:
|
| 512 |
+
print(f" ❌ Prompt {prompt_index} failed (will retry on resume)")
|
| 513 |
+
|
| 514 |
+
print(f"✅ Batch {batch_num}: Completed ({len(prompts_to_process)} prompts processed)")
|
| 515 |
+
|
| 516 |
+
return {
|
| 517 |
+
"batch_num": batch_num,
|
| 518 |
+
"processed": len(prompts_to_process),
|
| 519 |
+
"skipped": len(batch_data) - len(prompts_to_process),
|
| 520 |
+
"tool_stats": batch_tool_stats,
|
| 521 |
+
"reasoning_stats": batch_reasoning_stats,
|
| 522 |
+
"discarded_no_reasoning": discarded_no_reasoning,
|
| 523 |
+
"completed_prompts": completed_in_batch
|
| 524 |
+
}
|
| 525 |
+
|
| 526 |
+
|
| 527 |
+
class BatchRunner:
|
| 528 |
+
"""
|
| 529 |
+
Manages batch processing of agent prompts with checkpointing and statistics.
|
| 530 |
+
"""
|
| 531 |
+
|
| 532 |
+
def __init__(
|
| 533 |
+
self,
|
| 534 |
+
dataset_file: str,
|
| 535 |
+
batch_size: int,
|
| 536 |
+
run_name: str,
|
| 537 |
+
distribution: str = "default",
|
| 538 |
+
max_iterations: int = 10,
|
| 539 |
+
base_url: str = None,
|
| 540 |
+
api_key: str = None,
|
| 541 |
+
model: str = "claude-opus-4-20250514",
|
| 542 |
+
num_workers: int = 4,
|
| 543 |
+
verbose: bool = False,
|
| 544 |
+
ephemeral_system_prompt: str = None,
|
| 545 |
+
log_prefix_chars: int = 100,
|
| 546 |
+
providers_allowed: List[str] = None,
|
| 547 |
+
providers_ignored: List[str] = None,
|
| 548 |
+
providers_order: List[str] = None,
|
| 549 |
+
provider_sort: str = None,
|
| 550 |
+
openrouter_min_coding_score: Optional[float] = None,
|
| 551 |
+
max_tokens: int = None,
|
| 552 |
+
reasoning_config: Dict[str, Any] = None,
|
| 553 |
+
prefill_messages: List[Dict[str, Any]] = None,
|
| 554 |
+
max_samples: int = None,
|
| 555 |
+
):
|
| 556 |
+
"""
|
| 557 |
+
Initialize the batch runner.
|
| 558 |
+
|
| 559 |
+
Args:
|
| 560 |
+
dataset_file (str): Path to the dataset JSONL file with 'prompt' field
|
| 561 |
+
batch_size (int): Number of prompts per batch
|
| 562 |
+
run_name (str): Name for this run (used for checkpointing and output)
|
| 563 |
+
distribution (str): Toolset distribution to use (default: "default")
|
| 564 |
+
max_iterations (int): Max iterations per agent run
|
| 565 |
+
base_url (str): Base URL for model API
|
| 566 |
+
api_key (str): API key for model
|
| 567 |
+
model (str): Model name to use
|
| 568 |
+
num_workers (int): Number of parallel workers
|
| 569 |
+
verbose (bool): Enable verbose logging
|
| 570 |
+
ephemeral_system_prompt (str): System prompt used during agent execution but NOT saved to trajectories (optional)
|
| 571 |
+
log_prefix_chars (int): Number of characters to show in log previews for tool calls/responses (default: 20)
|
| 572 |
+
providers_allowed (List[str]): OpenRouter providers to allow (optional)
|
| 573 |
+
providers_ignored (List[str]): OpenRouter providers to ignore (optional)
|
| 574 |
+
providers_order (List[str]): OpenRouter providers to try in order (optional)
|
| 575 |
+
provider_sort (str): Sort providers by price/throughput/latency (optional)
|
| 576 |
+
max_tokens (int): Maximum tokens for model responses (optional, uses model default if not set)
|
| 577 |
+
reasoning_config (Dict): OpenRouter reasoning config override (e.g. {"effort": "none"} to disable thinking)
|
| 578 |
+
prefill_messages (List[Dict]): Messages to prepend as prefilled conversation context (few-shot priming).
|
| 579 |
+
NOTE: Anthropic Sonnet 4.6+ and Opus 4.6+ reject a trailing assistant-role prefill
|
| 580 |
+
(400 error). For those models use output_config.format or structured-output
|
| 581 |
+
schemas instead. Safe here for user-role priming and for older Claude / non-Claude models.
|
| 582 |
+
max_samples (int): Only process the first N samples from the dataset (optional, processes all if not set)
|
| 583 |
+
"""
|
| 584 |
+
self.dataset_file = Path(dataset_file)
|
| 585 |
+
self.batch_size = batch_size
|
| 586 |
+
self.run_name = run_name
|
| 587 |
+
self.distribution = distribution
|
| 588 |
+
self.max_iterations = max_iterations
|
| 589 |
+
self.base_url = base_url
|
| 590 |
+
self.api_key = api_key
|
| 591 |
+
self.model = model
|
| 592 |
+
self.num_workers = num_workers
|
| 593 |
+
self.verbose = verbose
|
| 594 |
+
self.ephemeral_system_prompt = ephemeral_system_prompt
|
| 595 |
+
self.log_prefix_chars = log_prefix_chars
|
| 596 |
+
self.providers_allowed = providers_allowed
|
| 597 |
+
self.providers_ignored = providers_ignored
|
| 598 |
+
self.providers_order = providers_order
|
| 599 |
+
self.provider_sort = provider_sort
|
| 600 |
+
self.openrouter_min_coding_score = openrouter_min_coding_score
|
| 601 |
+
self.max_tokens = max_tokens
|
| 602 |
+
self.reasoning_config = reasoning_config
|
| 603 |
+
self.prefill_messages = prefill_messages
|
| 604 |
+
self.max_samples = max_samples
|
| 605 |
+
|
| 606 |
+
# Validate distribution
|
| 607 |
+
if not validate_distribution(distribution):
|
| 608 |
+
raise ValueError(f"Unknown distribution: {distribution}. Available: {list(list_distributions().keys())}")
|
| 609 |
+
|
| 610 |
+
# Setup output directory
|
| 611 |
+
self.output_dir = Path("data") / run_name
|
| 612 |
+
self.output_dir.mkdir(parents=True, exist_ok=True)
|
| 613 |
+
|
| 614 |
+
# Checkpoint file
|
| 615 |
+
self.checkpoint_file = self.output_dir / "checkpoint.json"
|
| 616 |
+
|
| 617 |
+
# Statistics file
|
| 618 |
+
self.stats_file = self.output_dir / "statistics.json"
|
| 619 |
+
|
| 620 |
+
# Load dataset (and optionally truncate to max_samples)
|
| 621 |
+
self.dataset = self._load_dataset()
|
| 622 |
+
if self.max_samples and self.max_samples < len(self.dataset):
|
| 623 |
+
full_count = len(self.dataset)
|
| 624 |
+
self.dataset = self.dataset[:self.max_samples]
|
| 625 |
+
print(f"✂️ Truncated dataset from {full_count} to {self.max_samples} samples (--max_samples)")
|
| 626 |
+
|
| 627 |
+
# Create batches
|
| 628 |
+
self.batches = self._create_batches()
|
| 629 |
+
|
| 630 |
+
print("📊 Batch Runner Initialized")
|
| 631 |
+
print(f" Dataset: {self.dataset_file} ({len(self.dataset)} prompts)")
|
| 632 |
+
print(f" Batch size: {self.batch_size}")
|
| 633 |
+
print(f" Total batches: {len(self.batches)}")
|
| 634 |
+
print(f" Run name: {self.run_name}")
|
| 635 |
+
print(f" Distribution: {self.distribution}")
|
| 636 |
+
print(f" Output directory: {self.output_dir}")
|
| 637 |
+
print(f" Workers: {self.num_workers}")
|
| 638 |
+
if self.ephemeral_system_prompt:
|
| 639 |
+
prompt_preview = self.ephemeral_system_prompt[:60] + "..." if len(self.ephemeral_system_prompt) > 60 else self.ephemeral_system_prompt
|
| 640 |
+
print(f" 🔒 Ephemeral system prompt: '{prompt_preview}'")
|
| 641 |
+
|
| 642 |
+
def _load_dataset(self) -> List[Dict[str, Any]]:
|
| 643 |
+
"""
|
| 644 |
+
Load dataset from JSONL file.
|
| 645 |
+
|
| 646 |
+
Returns:
|
| 647 |
+
List[Dict]: List of dataset entries
|
| 648 |
+
"""
|
| 649 |
+
if not self.dataset_file.exists():
|
| 650 |
+
raise FileNotFoundError(f"Dataset file not found: {self.dataset_file}")
|
| 651 |
+
|
| 652 |
+
dataset = []
|
| 653 |
+
with open(self.dataset_file, 'r', encoding='utf-8') as f:
|
| 654 |
+
for line_num, line in enumerate(f, 1):
|
| 655 |
+
line = line.strip()
|
| 656 |
+
if not line:
|
| 657 |
+
continue
|
| 658 |
+
|
| 659 |
+
try:
|
| 660 |
+
entry = json.loads(line)
|
| 661 |
+
if 'prompt' not in entry:
|
| 662 |
+
print(f"⚠️ Warning: Line {line_num} missing 'prompt' field, skipping")
|
| 663 |
+
continue
|
| 664 |
+
dataset.append(entry)
|
| 665 |
+
except json.JSONDecodeError as e:
|
| 666 |
+
print(f"⚠️ Warning: Invalid JSON on line {line_num}: {e}")
|
| 667 |
+
continue
|
| 668 |
+
|
| 669 |
+
if not dataset:
|
| 670 |
+
raise ValueError(f"No valid entries found in dataset file: {self.dataset_file}")
|
| 671 |
+
|
| 672 |
+
return dataset
|
| 673 |
+
|
| 674 |
+
def _create_batches(self) -> List[List[Tuple[int, Dict[str, Any]]]]:
|
| 675 |
+
"""
|
| 676 |
+
Split dataset into batches with indices.
|
| 677 |
+
|
| 678 |
+
Returns:
|
| 679 |
+
List of batches, where each batch is a list of (index, entry) tuples
|
| 680 |
+
"""
|
| 681 |
+
batches = []
|
| 682 |
+
for i in range(0, len(self.dataset), self.batch_size):
|
| 683 |
+
batch = [(idx, entry) for idx, entry in enumerate(self.dataset[i:i + self.batch_size], start=i)]
|
| 684 |
+
batches.append(batch)
|
| 685 |
+
|
| 686 |
+
return batches
|
| 687 |
+
|
| 688 |
+
def _load_checkpoint(self) -> Dict[str, Any]:
|
| 689 |
+
"""
|
| 690 |
+
Load checkpoint data if it exists.
|
| 691 |
+
|
| 692 |
+
Returns:
|
| 693 |
+
Dict: Checkpoint data with completed prompt indices
|
| 694 |
+
"""
|
| 695 |
+
if not self.checkpoint_file.exists():
|
| 696 |
+
return {
|
| 697 |
+
"run_name": self.run_name,
|
| 698 |
+
"completed_prompts": [],
|
| 699 |
+
"batch_stats": {},
|
| 700 |
+
"last_updated": None
|
| 701 |
+
}
|
| 702 |
+
|
| 703 |
+
try:
|
| 704 |
+
with open(self.checkpoint_file, 'r', encoding='utf-8') as f:
|
| 705 |
+
return json.load(f)
|
| 706 |
+
except Exception as e:
|
| 707 |
+
print(f"⚠️ Warning: Failed to load checkpoint: {e}")
|
| 708 |
+
return {
|
| 709 |
+
"run_name": self.run_name,
|
| 710 |
+
"completed_prompts": [],
|
| 711 |
+
"batch_stats": {},
|
| 712 |
+
"last_updated": None
|
| 713 |
+
}
|
| 714 |
+
|
| 715 |
+
def _save_checkpoint(self, checkpoint_data: Dict[str, Any], lock: Optional[Lock] = None):
|
| 716 |
+
"""
|
| 717 |
+
Save checkpoint data.
|
| 718 |
+
|
| 719 |
+
Args:
|
| 720 |
+
checkpoint_data (Dict): Checkpoint data to save
|
| 721 |
+
lock (Lock): Optional lock for thread-safe access
|
| 722 |
+
"""
|
| 723 |
+
checkpoint_data["last_updated"] = datetime.now().isoformat()
|
| 724 |
+
|
| 725 |
+
from utils import atomic_json_write
|
| 726 |
+
if lock:
|
| 727 |
+
with lock:
|
| 728 |
+
atomic_json_write(self.checkpoint_file, checkpoint_data)
|
| 729 |
+
else:
|
| 730 |
+
atomic_json_write(self.checkpoint_file, checkpoint_data)
|
| 731 |
+
|
| 732 |
+
def _scan_completed_prompts_by_content(self) -> set:
|
| 733 |
+
"""
|
| 734 |
+
Scan all batch files and extract completed prompts by their actual content.
|
| 735 |
+
|
| 736 |
+
This provides a more robust resume mechanism that matches on prompt text
|
| 737 |
+
rather than indices, allowing recovery even if indices don't match.
|
| 738 |
+
|
| 739 |
+
Returns:
|
| 740 |
+
set: Set of prompt texts that have been successfully processed
|
| 741 |
+
"""
|
| 742 |
+
completed_prompts = set()
|
| 743 |
+
batch_files = sorted(self.output_dir.glob("batch_*.jsonl"))
|
| 744 |
+
|
| 745 |
+
if not batch_files:
|
| 746 |
+
return completed_prompts
|
| 747 |
+
|
| 748 |
+
print(f"📂 Scanning {len(batch_files)} batch files for completed prompts...")
|
| 749 |
+
|
| 750 |
+
for batch_file in batch_files:
|
| 751 |
+
try:
|
| 752 |
+
with open(batch_file, 'r', encoding='utf-8') as f:
|
| 753 |
+
for line in f:
|
| 754 |
+
try:
|
| 755 |
+
entry = json.loads(line.strip())
|
| 756 |
+
|
| 757 |
+
# Skip failed entries - we want to retry these
|
| 758 |
+
if entry.get("failed", False):
|
| 759 |
+
continue
|
| 760 |
+
|
| 761 |
+
# Extract the human/user prompt from conversations
|
| 762 |
+
conversations = entry.get("conversations", [])
|
| 763 |
+
for msg in conversations:
|
| 764 |
+
if msg.get("from") == "human":
|
| 765 |
+
prompt_text = msg.get("value", "").strip()
|
| 766 |
+
if prompt_text:
|
| 767 |
+
completed_prompts.add(prompt_text)
|
| 768 |
+
break # Only need the first human message
|
| 769 |
+
except json.JSONDecodeError:
|
| 770 |
+
continue
|
| 771 |
+
except Exception as e:
|
| 772 |
+
print(f" ⚠️ Warning: Error reading {batch_file.name}: {e}")
|
| 773 |
+
|
| 774 |
+
return completed_prompts
|
| 775 |
+
|
| 776 |
+
def _filter_dataset_by_completed(self, completed_prompts: set) -> Tuple[List[Dict], List[int]]:
|
| 777 |
+
"""
|
| 778 |
+
Filter the dataset to exclude prompts that have already been completed.
|
| 779 |
+
|
| 780 |
+
Args:
|
| 781 |
+
completed_prompts: Set of prompt texts that have been completed
|
| 782 |
+
|
| 783 |
+
Returns:
|
| 784 |
+
Tuple of (filtered_dataset, skipped_indices)
|
| 785 |
+
"""
|
| 786 |
+
filtered_dataset = []
|
| 787 |
+
skipped_indices = []
|
| 788 |
+
|
| 789 |
+
for idx, entry in enumerate(self.dataset):
|
| 790 |
+
# Extract prompt from the dataset entry
|
| 791 |
+
prompt_text = entry.get("prompt", "").strip()
|
| 792 |
+
|
| 793 |
+
# Also check conversations format
|
| 794 |
+
if not prompt_text:
|
| 795 |
+
conversations = entry.get("conversations", [])
|
| 796 |
+
for msg in conversations:
|
| 797 |
+
role = msg.get("role") or msg.get("from")
|
| 798 |
+
if role in ("user", "human"):
|
| 799 |
+
prompt_text = (msg.get("content") or msg.get("value", "")).strip()
|
| 800 |
+
break
|
| 801 |
+
|
| 802 |
+
if prompt_text in completed_prompts:
|
| 803 |
+
skipped_indices.append(idx)
|
| 804 |
+
else:
|
| 805 |
+
# Keep original index for tracking
|
| 806 |
+
filtered_dataset.append((idx, entry))
|
| 807 |
+
|
| 808 |
+
return filtered_dataset, skipped_indices
|
| 809 |
+
|
| 810 |
+
def run(self, resume: bool = False):
|
| 811 |
+
"""
|
| 812 |
+
Run the batch processing pipeline.
|
| 813 |
+
|
| 814 |
+
Args:
|
| 815 |
+
resume (bool): Whether to resume from checkpoint
|
| 816 |
+
"""
|
| 817 |
+
print("\n" + "=" * 70)
|
| 818 |
+
print("🚀 Starting Batch Processing")
|
| 819 |
+
print("=" * 70)
|
| 820 |
+
|
| 821 |
+
# Smart resume: scan batch files by content to find completed prompts
|
| 822 |
+
completed_prompt_texts = set()
|
| 823 |
+
if resume:
|
| 824 |
+
completed_prompt_texts = self._scan_completed_prompts_by_content()
|
| 825 |
+
if completed_prompt_texts:
|
| 826 |
+
print(f" Found {len(completed_prompt_texts)} already-completed prompts by content matching")
|
| 827 |
+
|
| 828 |
+
# Filter dataset to only include unprocessed prompts
|
| 829 |
+
if resume and completed_prompt_texts:
|
| 830 |
+
filtered_entries, skipped_indices = self._filter_dataset_by_completed(completed_prompt_texts)
|
| 831 |
+
|
| 832 |
+
if not filtered_entries:
|
| 833 |
+
print("\n✅ All prompts have already been processed!")
|
| 834 |
+
return
|
| 835 |
+
|
| 836 |
+
# Recreate batches from filtered entries (keeping original indices for tracking)
|
| 837 |
+
batches_to_process = []
|
| 838 |
+
for i in range(0, len(filtered_entries), self.batch_size):
|
| 839 |
+
batch = filtered_entries[i:i + self.batch_size]
|
| 840 |
+
batches_to_process.append(batch)
|
| 841 |
+
|
| 842 |
+
self.batches = batches_to_process
|
| 843 |
+
|
| 844 |
+
# Print prominent resume summary
|
| 845 |
+
print("\n" + "=" * 70)
|
| 846 |
+
print("📊 RESUME SUMMARY")
|
| 847 |
+
print("=" * 70)
|
| 848 |
+
print(f" Original dataset size: {len(self.dataset):,} prompts")
|
| 849 |
+
print(f" Already completed: {len(skipped_indices):,} prompts")
|
| 850 |
+
print(" ─────────────────────────────────────────")
|
| 851 |
+
print(f" 🎯 RESUMING WITH: {len(filtered_entries):,} prompts")
|
| 852 |
+
print(f" New batches created: {len(batches_to_process)}")
|
| 853 |
+
print("=" * 70 + "\n")
|
| 854 |
+
|
| 855 |
+
# Load existing checkpoint (so resume doesn't clobber prior progress)
|
| 856 |
+
checkpoint_data = self._load_checkpoint()
|
| 857 |
+
if checkpoint_data.get("run_name") != self.run_name:
|
| 858 |
+
checkpoint_data = {
|
| 859 |
+
"run_name": self.run_name,
|
| 860 |
+
"completed_prompts": [],
|
| 861 |
+
"batch_stats": {},
|
| 862 |
+
"last_updated": None
|
| 863 |
+
}
|
| 864 |
+
|
| 865 |
+
# Prepare configuration for workers
|
| 866 |
+
config = {
|
| 867 |
+
"distribution": self.distribution,
|
| 868 |
+
"model": self.model,
|
| 869 |
+
"max_iterations": self.max_iterations,
|
| 870 |
+
"base_url": self.base_url,
|
| 871 |
+
"api_key": self.api_key,
|
| 872 |
+
"verbose": self.verbose,
|
| 873 |
+
"ephemeral_system_prompt": self.ephemeral_system_prompt,
|
| 874 |
+
"log_prefix_chars": self.log_prefix_chars,
|
| 875 |
+
"providers_allowed": self.providers_allowed,
|
| 876 |
+
"providers_ignored": self.providers_ignored,
|
| 877 |
+
"providers_order": self.providers_order,
|
| 878 |
+
"provider_sort": self.provider_sort,
|
| 879 |
+
"openrouter_min_coding_score": self.openrouter_min_coding_score,
|
| 880 |
+
"max_tokens": self.max_tokens,
|
| 881 |
+
"reasoning_config": self.reasoning_config,
|
| 882 |
+
"prefill_messages": self.prefill_messages,
|
| 883 |
+
}
|
| 884 |
+
|
| 885 |
+
# For backward compatibility, still track by index (but this is secondary to content matching)
|
| 886 |
+
completed_prompts_set = set(checkpoint_data.get("completed_prompts", []))
|
| 887 |
+
|
| 888 |
+
# Aggregate statistics across all batches
|
| 889 |
+
total_tool_stats = {}
|
| 890 |
+
|
| 891 |
+
start_time = time.time()
|
| 892 |
+
|
| 893 |
+
print(f"\n🔧 Initializing {self.num_workers} worker processes...")
|
| 894 |
+
|
| 895 |
+
# Checkpoint writes happen in the parent process; keep a lock for safety.
|
| 896 |
+
checkpoint_lock = Lock()
|
| 897 |
+
|
| 898 |
+
# Process batches in parallel
|
| 899 |
+
with Pool(processes=self.num_workers) as pool:
|
| 900 |
+
# Create tasks for each batch
|
| 901 |
+
tasks = [
|
| 902 |
+
(
|
| 903 |
+
batch_num,
|
| 904 |
+
batch_data,
|
| 905 |
+
str(self.output_dir), # Convert Path to string for pickling
|
| 906 |
+
completed_prompts_set,
|
| 907 |
+
config
|
| 908 |
+
)
|
| 909 |
+
for batch_num, batch_data in enumerate(self.batches)
|
| 910 |
+
]
|
| 911 |
+
|
| 912 |
+
print(f"✅ Created {len(tasks)} batch tasks")
|
| 913 |
+
print("🚀 Starting parallel batch processing...\n")
|
| 914 |
+
|
| 915 |
+
# Use rich Progress for better visual tracking with persistent bottom bar
|
| 916 |
+
# redirect_stdout/stderr lets rich manage all output so progress bar stays clean
|
| 917 |
+
results = []
|
| 918 |
+
console = Console(force_terminal=True)
|
| 919 |
+
with Progress(
|
| 920 |
+
SpinnerColumn(),
|
| 921 |
+
TextColumn("[bold blue]📦 Batches"),
|
| 922 |
+
BarColumn(bar_width=40),
|
| 923 |
+
MofNCompleteColumn(),
|
| 924 |
+
TextColumn("•"),
|
| 925 |
+
TimeRemainingColumn(),
|
| 926 |
+
console=console,
|
| 927 |
+
refresh_per_second=2,
|
| 928 |
+
transient=False,
|
| 929 |
+
redirect_stdout=False,
|
| 930 |
+
redirect_stderr=False,
|
| 931 |
+
) as progress:
|
| 932 |
+
task = progress.add_task("Processing", total=len(tasks))
|
| 933 |
+
|
| 934 |
+
# Temporarily suppress DEBUG logging to avoid bar interference
|
| 935 |
+
root_logger = logging.getLogger()
|
| 936 |
+
original_level = root_logger.level
|
| 937 |
+
root_logger.setLevel(logging.WARNING)
|
| 938 |
+
|
| 939 |
+
try:
|
| 940 |
+
for result in pool.imap_unordered(_process_batch_worker, tasks):
|
| 941 |
+
results.append(result)
|
| 942 |
+
progress.update(task, advance=1)
|
| 943 |
+
|
| 944 |
+
# Incremental checkpoint update (so resume works after crash)
|
| 945 |
+
try:
|
| 946 |
+
batch_num = result.get('batch_num')
|
| 947 |
+
completed = result.get('completed_prompts', []) or []
|
| 948 |
+
completed_prompts_set.update(completed)
|
| 949 |
+
|
| 950 |
+
if isinstance(batch_num, int):
|
| 951 |
+
checkpoint_data.setdefault('batch_stats', {})[str(batch_num)] = {
|
| 952 |
+
'processed': result.get('processed', 0),
|
| 953 |
+
'skipped': result.get('skipped', 0),
|
| 954 |
+
'discarded_no_reasoning': result.get('discarded_no_reasoning', 0),
|
| 955 |
+
}
|
| 956 |
+
|
| 957 |
+
checkpoint_data['completed_prompts'] = sorted(completed_prompts_set)
|
| 958 |
+
self._save_checkpoint(checkpoint_data, lock=checkpoint_lock)
|
| 959 |
+
except Exception as ckpt_err:
|
| 960 |
+
# Don't fail the run if checkpoint write fails
|
| 961 |
+
print(f"⚠️ Warning: Failed to save incremental checkpoint: {ckpt_err}")
|
| 962 |
+
except Exception as e:
|
| 963 |
+
logger.error("Batch worker failed: %s", e, exc_info=True)
|
| 964 |
+
raise
|
| 965 |
+
finally:
|
| 966 |
+
root_logger.setLevel(original_level)
|
| 967 |
+
|
| 968 |
+
# Aggregate all batch statistics and update checkpoint
|
| 969 |
+
total_reasoning_stats = {"total_assistant_turns": 0, "turns_with_reasoning": 0, "turns_without_reasoning": 0}
|
| 970 |
+
|
| 971 |
+
for batch_result in results:
|
| 972 |
+
# Aggregate tool stats
|
| 973 |
+
for tool_name, stats in batch_result.get("tool_stats", {}).items():
|
| 974 |
+
if tool_name not in total_tool_stats:
|
| 975 |
+
total_tool_stats[tool_name] = {
|
| 976 |
+
"count": 0,
|
| 977 |
+
"success": 0,
|
| 978 |
+
"failure": 0
|
| 979 |
+
}
|
| 980 |
+
|
| 981 |
+
total_tool_stats[tool_name]["count"] += stats["count"]
|
| 982 |
+
total_tool_stats[tool_name]["success"] += stats["success"]
|
| 983 |
+
total_tool_stats[tool_name]["failure"] += stats["failure"]
|
| 984 |
+
|
| 985 |
+
# Aggregate reasoning stats
|
| 986 |
+
for key in total_reasoning_stats:
|
| 987 |
+
total_reasoning_stats[key] += batch_result.get("reasoning_stats", {}).get(key, 0)
|
| 988 |
+
|
| 989 |
+
# Save final checkpoint (best-effort; incremental writes already happened)
|
| 990 |
+
try:
|
| 991 |
+
checkpoint_data["completed_prompts"] = sorted(completed_prompts_set)
|
| 992 |
+
self._save_checkpoint(checkpoint_data, lock=checkpoint_lock)
|
| 993 |
+
except Exception as ckpt_err:
|
| 994 |
+
print(f"âš ï¸ Warning: Failed to save final checkpoint: {ckpt_err}")
|
| 995 |
+
|
| 996 |
+
# Calculate success rates
|
| 997 |
+
for tool_name in total_tool_stats:
|
| 998 |
+
stats = total_tool_stats[tool_name]
|
| 999 |
+
total_calls = stats["success"] + stats["failure"]
|
| 1000 |
+
if total_calls > 0:
|
| 1001 |
+
stats["success_rate"] = round(stats["success"] / total_calls * 100, 2)
|
| 1002 |
+
stats["failure_rate"] = round(stats["failure"] / total_calls * 100, 2)
|
| 1003 |
+
else:
|
| 1004 |
+
stats["success_rate"] = 0.0
|
| 1005 |
+
stats["failure_rate"] = 0.0
|
| 1006 |
+
|
| 1007 |
+
# Combine ALL batch files in directory into a single trajectories.jsonl file
|
| 1008 |
+
# This includes both old batches (from previous runs) and new batches (from resume)
|
| 1009 |
+
# Also filter out corrupted entries (where model generated invalid tool names)
|
| 1010 |
+
combined_file = self.output_dir / "trajectories.jsonl"
|
| 1011 |
+
print(f"\n📦 Combining ALL batch files into {combined_file.name}...")
|
| 1012 |
+
|
| 1013 |
+
# Valid tools auto-derived from model_tools.py — no manual updates needed
|
| 1014 |
+
VALID_TOOLS = ALL_POSSIBLE_TOOLS
|
| 1015 |
+
|
| 1016 |
+
total_entries = 0
|
| 1017 |
+
filtered_entries = 0
|
| 1018 |
+
batch_files_found = 0
|
| 1019 |
+
|
| 1020 |
+
# Find ALL batch files in the output directory (handles resume merging old + new)
|
| 1021 |
+
all_batch_files = sorted(self.output_dir.glob("batch_*.jsonl"))
|
| 1022 |
+
|
| 1023 |
+
with open(combined_file, 'w', encoding='utf-8') as outfile:
|
| 1024 |
+
for batch_file in all_batch_files:
|
| 1025 |
+
batch_files_found += 1
|
| 1026 |
+
batch_num = batch_file.stem.split("_")[1] # Extract batch number for logging
|
| 1027 |
+
|
| 1028 |
+
with open(batch_file, 'r', encoding='utf-8') as infile:
|
| 1029 |
+
for line in infile:
|
| 1030 |
+
total_entries += 1
|
| 1031 |
+
try:
|
| 1032 |
+
data = json.loads(line)
|
| 1033 |
+
tool_stats = data.get('tool_stats', {})
|
| 1034 |
+
|
| 1035 |
+
# Check for invalid tool names (model hallucinations)
|
| 1036 |
+
invalid_tools = [k for k in tool_stats if k not in VALID_TOOLS]
|
| 1037 |
+
|
| 1038 |
+
if invalid_tools:
|
| 1039 |
+
filtered_entries += 1
|
| 1040 |
+
invalid_preview = invalid_tools[0][:50] + "..." if len(invalid_tools[0]) > 50 else invalid_tools[0]
|
| 1041 |
+
print(f" ⚠️ Filtering corrupted entry (batch {batch_num}): invalid tool '{invalid_preview}'")
|
| 1042 |
+
continue
|
| 1043 |
+
|
| 1044 |
+
outfile.write(line)
|
| 1045 |
+
except json.JSONDecodeError:
|
| 1046 |
+
filtered_entries += 1
|
| 1047 |
+
print(f" ⚠️ Filtering invalid JSON entry (batch {batch_num})")
|
| 1048 |
+
|
| 1049 |
+
if filtered_entries > 0:
|
| 1050 |
+
print(f"⚠️ Filtered {filtered_entries} corrupted entries out of {total_entries} total")
|
| 1051 |
+
print(f"✅ Combined {batch_files_found} batch files into trajectories.jsonl ({total_entries - filtered_entries} entries)")
|
| 1052 |
+
|
| 1053 |
+
# Save final statistics
|
| 1054 |
+
final_stats = {
|
| 1055 |
+
"run_name": self.run_name,
|
| 1056 |
+
"distribution": self.distribution,
|
| 1057 |
+
"total_prompts": len(self.dataset),
|
| 1058 |
+
"total_batches": len(self.batches),
|
| 1059 |
+
"batch_size": self.batch_size,
|
| 1060 |
+
"model": self.model,
|
| 1061 |
+
"completed_at": datetime.now().isoformat(),
|
| 1062 |
+
"duration_seconds": round(time.time() - start_time, 2),
|
| 1063 |
+
"tool_statistics": total_tool_stats,
|
| 1064 |
+
"reasoning_statistics": total_reasoning_stats,
|
| 1065 |
+
}
|
| 1066 |
+
|
| 1067 |
+
with open(self.stats_file, 'w', encoding='utf-8') as f:
|
| 1068 |
+
json.dump(final_stats, f, indent=2, ensure_ascii=False)
|
| 1069 |
+
|
| 1070 |
+
# Print summary
|
| 1071 |
+
print("\n" + "=" * 70)
|
| 1072 |
+
print("📊 BATCH PROCESSING COMPLETE")
|
| 1073 |
+
print("=" * 70)
|
| 1074 |
+
print(f"✅ Prompts processed this run: {sum(r.get('processed', 0) for r in results)}")
|
| 1075 |
+
print(f"✅ Total trajectories in merged file: {total_entries - filtered_entries}")
|
| 1076 |
+
print(f"✅ Total batch files merged: {batch_files_found}")
|
| 1077 |
+
print(f"⏱️ Total duration: {round(time.time() - start_time, 2)}s")
|
| 1078 |
+
print("\n📈 Tool Usage Statistics:")
|
| 1079 |
+
print("-" * 70)
|
| 1080 |
+
|
| 1081 |
+
if total_tool_stats:
|
| 1082 |
+
# Sort by count descending
|
| 1083 |
+
sorted_tools = sorted(
|
| 1084 |
+
total_tool_stats.items(),
|
| 1085 |
+
key=lambda x: x[1]["count"],
|
| 1086 |
+
reverse=True
|
| 1087 |
+
)
|
| 1088 |
+
|
| 1089 |
+
print(f"{'Tool Name':<25} {'Count':<10} {'Success':<10} {'Failure':<10} {'Success Rate':<12}")
|
| 1090 |
+
print("-" * 70)
|
| 1091 |
+
for tool_name, stats in sorted_tools:
|
| 1092 |
+
print(
|
| 1093 |
+
f"{tool_name:<25} "
|
| 1094 |
+
f"{stats['count']:<10} "
|
| 1095 |
+
f"{stats['success']:<10} "
|
| 1096 |
+
f"{stats['failure']:<10} "
|
| 1097 |
+
f"{stats['success_rate']:.1f}%"
|
| 1098 |
+
)
|
| 1099 |
+
else:
|
| 1100 |
+
print("No tool calls were made during this run.")
|
| 1101 |
+
|
| 1102 |
+
# Print reasoning coverage stats
|
| 1103 |
+
total_discarded = sum(r.get("discarded_no_reasoning", 0) for r in results)
|
| 1104 |
+
|
| 1105 |
+
print("\n🧠 Reasoning Coverage:")
|
| 1106 |
+
print("-" * 70)
|
| 1107 |
+
total_turns = total_reasoning_stats["total_assistant_turns"]
|
| 1108 |
+
with_reasoning = total_reasoning_stats["turns_with_reasoning"]
|
| 1109 |
+
without_reasoning = total_reasoning_stats["turns_without_reasoning"]
|
| 1110 |
+
if total_turns > 0:
|
| 1111 |
+
pct_with = round(with_reasoning / total_turns * 100, 1)
|
| 1112 |
+
pct_without = round(without_reasoning / total_turns * 100, 1)
|
| 1113 |
+
print(f" Total assistant turns: {total_turns:,}")
|
| 1114 |
+
print(f" With reasoning: {with_reasoning:,} ({pct_with}%)")
|
| 1115 |
+
print(f" Without reasoning: {without_reasoning:,} ({pct_without}%)")
|
| 1116 |
+
else:
|
| 1117 |
+
print(" No assistant turns recorded.")
|
| 1118 |
+
if total_discarded > 0:
|
| 1119 |
+
print(f" 🚫 Samples discarded (zero reasoning): {total_discarded:,}")
|
| 1120 |
+
|
| 1121 |
+
print(f"\n💾 Results saved to: {self.output_dir}")
|
| 1122 |
+
print(" - Trajectories: trajectories.jsonl (combined)")
|
| 1123 |
+
print(" - Individual batches: batch_*.jsonl (for debugging)")
|
| 1124 |
+
print(f" - Statistics: {self.stats_file.name}")
|
| 1125 |
+
print(f" - Checkpoint: {self.checkpoint_file.name}")
|
| 1126 |
+
|
| 1127 |
+
|
| 1128 |
+
def main(
|
| 1129 |
+
dataset_file: str = None,
|
| 1130 |
+
batch_size: int = None,
|
| 1131 |
+
run_name: str = None,
|
| 1132 |
+
distribution: str = "default",
|
| 1133 |
+
model: str = "anthropic/claude-sonnet-4.6",
|
| 1134 |
+
api_key: str = None,
|
| 1135 |
+
base_url: str = "https://openrouter.ai/api/v1",
|
| 1136 |
+
max_turns: int = 10,
|
| 1137 |
+
num_workers: int = 4,
|
| 1138 |
+
resume: bool = False,
|
| 1139 |
+
verbose: bool = False,
|
| 1140 |
+
list_distributions: bool = False,
|
| 1141 |
+
ephemeral_system_prompt: str = None,
|
| 1142 |
+
log_prefix_chars: int = 100,
|
| 1143 |
+
providers_allowed: str = None,
|
| 1144 |
+
providers_ignored: str = None,
|
| 1145 |
+
providers_order: str = None,
|
| 1146 |
+
provider_sort: str = None,
|
| 1147 |
+
max_tokens: int = None,
|
| 1148 |
+
reasoning_effort: str = None,
|
| 1149 |
+
reasoning_disabled: bool = False,
|
| 1150 |
+
prefill_messages_file: str = None,
|
| 1151 |
+
max_samples: int = None,
|
| 1152 |
+
):
|
| 1153 |
+
"""
|
| 1154 |
+
Run batch processing of agent prompts from a dataset.
|
| 1155 |
+
|
| 1156 |
+
Args:
|
| 1157 |
+
dataset_file (str): Path to JSONL file with 'prompt' field in each entry
|
| 1158 |
+
batch_size (int): Number of prompts per batch
|
| 1159 |
+
run_name (str): Name for this run (used for output and checkpointing)
|
| 1160 |
+
distribution (str): Toolset distribution to use (default: "default")
|
| 1161 |
+
model (str): Model name to use (default: "claude-opus-4-20250514")
|
| 1162 |
+
api_key (str): API key for model authentication
|
| 1163 |
+
base_url (str): Base URL for model API
|
| 1164 |
+
max_turns (int): Maximum number of tool calling iterations per prompt (default: 10)
|
| 1165 |
+
num_workers (int): Number of parallel worker processes (default: 4)
|
| 1166 |
+
resume (bool): Resume from checkpoint if run was interrupted (default: False)
|
| 1167 |
+
verbose (bool): Enable verbose logging (default: False)
|
| 1168 |
+
list_distributions (bool): List available toolset distributions and exit
|
| 1169 |
+
ephemeral_system_prompt (str): System prompt used during agent execution but NOT saved to trajectories (optional)
|
| 1170 |
+
log_prefix_chars (int): Number of characters to show in log previews for tool calls/responses (default: 20)
|
| 1171 |
+
providers_allowed (str): Comma-separated list of OpenRouter providers to allow (e.g. "anthropic,openai")
|
| 1172 |
+
providers_ignored (str): Comma-separated list of OpenRouter providers to ignore (e.g. "together,deepinfra")
|
| 1173 |
+
providers_order (str): Comma-separated list of OpenRouter providers to try in order (e.g. "anthropic,openai,google")
|
| 1174 |
+
provider_sort (str): Sort providers by "price", "throughput", or "latency" (OpenRouter only)
|
| 1175 |
+
max_tokens (int): Maximum tokens for model responses (optional, uses model default if not set)
|
| 1176 |
+
reasoning_effort (str): OpenRouter reasoning effort level: "none", "minimal", "low", "medium", "high", "xhigh" (default: "medium")
|
| 1177 |
+
reasoning_disabled (bool): Completely disable reasoning/thinking tokens (default: False)
|
| 1178 |
+
prefill_messages_file (str): Path to JSON file containing prefill messages (list of {role, content} dicts)
|
| 1179 |
+
max_samples (int): Only process the first N samples from the dataset (optional, processes all if not set)
|
| 1180 |
+
|
| 1181 |
+
Examples:
|
| 1182 |
+
# Basic usage
|
| 1183 |
+
python batch_runner.py --dataset_file=data.jsonl --batch_size=10 --run_name=my_run
|
| 1184 |
+
|
| 1185 |
+
# Resume interrupted run
|
| 1186 |
+
python batch_runner.py --dataset_file=data.jsonl --batch_size=10 --run_name=my_run --resume
|
| 1187 |
+
|
| 1188 |
+
# Use specific distribution
|
| 1189 |
+
python batch_runner.py --dataset_file=data.jsonl --batch_size=10 --run_name=image_test --distribution=image_gen
|
| 1190 |
+
|
| 1191 |
+
# With disabled reasoning and max tokens
|
| 1192 |
+
python batch_runner.py --dataset_file=data.jsonl --batch_size=10 --run_name=my_run \\
|
| 1193 |
+
--reasoning_disabled --max_tokens=128000
|
| 1194 |
+
|
| 1195 |
+
# With prefill messages from file
|
| 1196 |
+
python batch_runner.py --dataset_file=data.jsonl --batch_size=10 --run_name=my_run \\
|
| 1197 |
+
--prefill_messages_file=configs/prefill_opus.json
|
| 1198 |
+
|
| 1199 |
+
# List available distributions
|
| 1200 |
+
python batch_runner.py --list_distributions
|
| 1201 |
+
"""
|
| 1202 |
+
# Handle list distributions
|
| 1203 |
+
if list_distributions:
|
| 1204 |
+
from toolset_distributions import print_distribution_info
|
| 1205 |
+
|
| 1206 |
+
print("📊 Available Toolset Distributions")
|
| 1207 |
+
print("=" * 70)
|
| 1208 |
+
|
| 1209 |
+
all_dists = list_distributions()
|
| 1210 |
+
for dist_name in sorted(all_dists.keys()):
|
| 1211 |
+
print_distribution_info(dist_name)
|
| 1212 |
+
|
| 1213 |
+
print("\n💡 Usage:")
|
| 1214 |
+
print(" python batch_runner.py --dataset_file=data.jsonl --batch_size=10 \\")
|
| 1215 |
+
print(" --run_name=my_run --distribution=<name>")
|
| 1216 |
+
return
|
| 1217 |
+
|
| 1218 |
+
# Validate required arguments
|
| 1219 |
+
if not dataset_file:
|
| 1220 |
+
print("❌ Error: --dataset_file is required")
|
| 1221 |
+
return
|
| 1222 |
+
|
| 1223 |
+
if not batch_size or batch_size < 1:
|
| 1224 |
+
print("❌ Error: --batch_size must be a positive integer")
|
| 1225 |
+
return
|
| 1226 |
+
|
| 1227 |
+
if not run_name:
|
| 1228 |
+
print("❌ Error: --run_name is required")
|
| 1229 |
+
return
|
| 1230 |
+
|
| 1231 |
+
# Parse provider preferences (comma-separated strings to lists)
|
| 1232 |
+
providers_allowed_list = [p.strip() for p in providers_allowed.split(",")] if providers_allowed else None
|
| 1233 |
+
providers_ignored_list = [p.strip() for p in providers_ignored.split(",")] if providers_ignored else None
|
| 1234 |
+
providers_order_list = [p.strip() for p in providers_order.split(",")] if providers_order else None
|
| 1235 |
+
|
| 1236 |
+
# Build reasoning_config from CLI flags
|
| 1237 |
+
# --reasoning_disabled takes priority, then --reasoning_effort, then default (medium)
|
| 1238 |
+
reasoning_config = None
|
| 1239 |
+
if reasoning_disabled:
|
| 1240 |
+
# Completely disable reasoning/thinking tokens
|
| 1241 |
+
reasoning_config = {"effort": "none"}
|
| 1242 |
+
print("🧠 Reasoning: DISABLED (effort=none)")
|
| 1243 |
+
elif reasoning_effort:
|
| 1244 |
+
# Use specified effort level
|
| 1245 |
+
valid_efforts = ["none", "minimal", "low", "medium", "high", "xhigh"]
|
| 1246 |
+
if reasoning_effort not in valid_efforts:
|
| 1247 |
+
print(f"❌ Error: --reasoning_effort must be one of: {', '.join(valid_efforts)}")
|
| 1248 |
+
return
|
| 1249 |
+
reasoning_config = {"enabled": True, "effort": reasoning_effort}
|
| 1250 |
+
print(f"🧠 Reasoning effort: {reasoning_effort}")
|
| 1251 |
+
|
| 1252 |
+
# Load prefill messages from JSON file if provided
|
| 1253 |
+
prefill_messages = None
|
| 1254 |
+
if prefill_messages_file:
|
| 1255 |
+
try:
|
| 1256 |
+
with open(prefill_messages_file, 'r', encoding='utf-8') as f:
|
| 1257 |
+
prefill_messages = json.load(f)
|
| 1258 |
+
if not isinstance(prefill_messages, list):
|
| 1259 |
+
print("❌ Error: prefill_messages_file must contain a JSON array of messages")
|
| 1260 |
+
return
|
| 1261 |
+
print(f"💬 Loaded {len(prefill_messages)} prefill messages from {prefill_messages_file}")
|
| 1262 |
+
except Exception as e:
|
| 1263 |
+
print(f"❌ Error loading prefill messages: {e}")
|
| 1264 |
+
return
|
| 1265 |
+
|
| 1266 |
+
# Initialize and run batch runner
|
| 1267 |
+
try:
|
| 1268 |
+
runner = BatchRunner(
|
| 1269 |
+
dataset_file=dataset_file,
|
| 1270 |
+
batch_size=batch_size,
|
| 1271 |
+
run_name=run_name,
|
| 1272 |
+
distribution=distribution,
|
| 1273 |
+
max_iterations=max_turns,
|
| 1274 |
+
base_url=base_url,
|
| 1275 |
+
api_key=api_key,
|
| 1276 |
+
model=model,
|
| 1277 |
+
num_workers=num_workers,
|
| 1278 |
+
verbose=verbose,
|
| 1279 |
+
ephemeral_system_prompt=ephemeral_system_prompt,
|
| 1280 |
+
log_prefix_chars=log_prefix_chars,
|
| 1281 |
+
providers_allowed=providers_allowed_list,
|
| 1282 |
+
providers_ignored=providers_ignored_list,
|
| 1283 |
+
providers_order=providers_order_list,
|
| 1284 |
+
provider_sort=provider_sort,
|
| 1285 |
+
max_tokens=max_tokens,
|
| 1286 |
+
reasoning_config=reasoning_config,
|
| 1287 |
+
prefill_messages=prefill_messages,
|
| 1288 |
+
max_samples=max_samples,
|
| 1289 |
+
)
|
| 1290 |
+
|
| 1291 |
+
runner.run(resume=resume)
|
| 1292 |
+
|
| 1293 |
+
except Exception as e:
|
| 1294 |
+
print(f"\n❌ Fatal error: {e}")
|
| 1295 |
+
if verbose:
|
| 1296 |
+
traceback.print_exc()
|
| 1297 |
+
return 1
|
| 1298 |
+
|
| 1299 |
+
|
| 1300 |
+
if __name__ == "__main__":
|
| 1301 |
+
fire.Fire(main)
|
| 1302 |
+
|
cli-config.yaml.example
ADDED
|
@@ -0,0 +1,1062 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Hermes Agent CLI Configuration
|
| 2 |
+
# Copy this file to cli-config.yaml and customize as needed.
|
| 3 |
+
# This file configures the CLI behavior. Environment variables in .env take precedence.
|
| 4 |
+
|
| 5 |
+
# =============================================================================
|
| 6 |
+
# Model Configuration
|
| 7 |
+
# =============================================================================
|
| 8 |
+
model:
|
| 9 |
+
# Default model to use (can be overridden with --model flag)
|
| 10 |
+
# Both "default" and "model" work as the key name here.
|
| 11 |
+
default: "anthropic/claude-opus-4.6"
|
| 12 |
+
|
| 13 |
+
# Inference provider selection:
|
| 14 |
+
# "auto" - Auto-detect from credentials (default)
|
| 15 |
+
# "openrouter" - OpenRouter (requires: OPENROUTER_API_KEY or OPENAI_API_KEY)
|
| 16 |
+
# "nous" - Nous Portal OAuth (requires: hermes login)
|
| 17 |
+
# "nous-api" - Nous Portal API key (requires: NOUS_API_KEY)
|
| 18 |
+
# "anthropic" - Direct Anthropic API (requires: ANTHROPIC_API_KEY)
|
| 19 |
+
# "openai-codex" - OpenAI Codex (requires: hermes auth)
|
| 20 |
+
# "copilot" - GitHub Copilot / GitHub Models (requires: GITHUB_TOKEN)
|
| 21 |
+
# "gemini" - Use Google AI Studio direct (requires: GOOGLE_API_KEY or GEMINI_API_KEY)
|
| 22 |
+
# "zai" - Use z.ai / ZhipuAI GLM models (requires: GLM_API_KEY)
|
| 23 |
+
# "kimi-coding" - Kimi / Moonshot AI (requires: KIMI_API_KEY)
|
| 24 |
+
# "minimax" - MiniMax global (requires: MINIMAX_API_KEY)
|
| 25 |
+
# "minimax-cn" - MiniMax China (requires: MINIMAX_CN_API_KEY)
|
| 26 |
+
# "huggingface" - Hugging Face Inference (requires: HF_TOKEN)
|
| 27 |
+
# "nvidia" - NVIDIA NIM / build.nvidia.com (requires: NVIDIA_API_KEY)
|
| 28 |
+
# "xiaomi" - Xiaomi MiMo (requires: XIAOMI_API_KEY)
|
| 29 |
+
# "arcee" - Arcee AI Trinity models (requires: ARCEEAI_API_KEY)
|
| 30 |
+
# "ollama-cloud" - Ollama Cloud (requires: OLLAMA_API_KEY — https://ollama.com/settings)
|
| 31 |
+
# "kilocode" - KiloCode gateway (requires: KILOCODE_API_KEY)
|
| 32 |
+
# "ai-gateway" - Vercel AI Gateway (requires: AI_GATEWAY_API_KEY)
|
| 33 |
+
# "lmstudio" - LM Studio local server (optional: LM_API_KEY, defaults to http://127.0.0.1:1234/v1)
|
| 34 |
+
#
|
| 35 |
+
# Local servers (LM Studio, Ollama, vLLM, llama.cpp):
|
| 36 |
+
# "custom" - Any other OpenAI-compatible endpoint. Set base_url below.
|
| 37 |
+
# Aliases: "ollama", "vllm", "llamacpp" all map to "custom".
|
| 38 |
+
# LM Studio is first-class and uses provider: "lmstudio".
|
| 39 |
+
# It works with both no-auth and auth-enabled server modes.
|
| 40 |
+
#
|
| 41 |
+
# Can also be overridden with --provider flag or HERMES_INFERENCE_PROVIDER env var.
|
| 42 |
+
provider: "auto"
|
| 43 |
+
|
| 44 |
+
# API configuration (falls back to OPENROUTER_API_KEY env var)
|
| 45 |
+
# api_key: "your-key-here" # Uncomment to set here instead of .env
|
| 46 |
+
base_url: "https://openrouter.ai/api/v1"
|
| 47 |
+
|
| 48 |
+
# ── Token limits — two settings, easy to confuse ──────────────────────────
|
| 49 |
+
#
|
| 50 |
+
# context_length: TOTAL context window (input + output tokens combined).
|
| 51 |
+
# Controls when Hermes compresses history and validates requests.
|
| 52 |
+
# Leave unset — Hermes auto-detects the correct value from the provider.
|
| 53 |
+
# Set manually only when auto-detection is wrong (e.g. a local server with
|
| 54 |
+
# a custom num_ctx, or a proxy that doesn't expose /v1/models).
|
| 55 |
+
#
|
| 56 |
+
# context_length: 131072
|
| 57 |
+
#
|
| 58 |
+
# max_tokens: OUTPUT cap — maximum tokens the model may generate per response.
|
| 59 |
+
# Unrelated to how long your conversation history can be.
|
| 60 |
+
# The OpenAI-standard name "max_tokens" is a misnomer; Anthropic's native
|
| 61 |
+
# API has since renamed it "max_output_tokens" for clarity.
|
| 62 |
+
# Leave unset to use the model's native output ceiling (recommended).
|
| 63 |
+
# Set only if you want to deliberately limit individual response length.
|
| 64 |
+
#
|
| 65 |
+
# max_tokens: 8192
|
| 66 |
+
|
| 67 |
+
# Named provider overrides (optional)
|
| 68 |
+
# Use this for per-provider request timeouts, non-stream stale timeouts,
|
| 69 |
+
# and per-model exceptions.
|
| 70 |
+
# Applies to the primary turn client on every api_mode (OpenAI-wire, native
|
| 71 |
+
# Anthropic, and Anthropic-compatible providers), the fallback chain, and
|
| 72 |
+
# client rebuilds during credential rotation. For OpenAI-wire chat
|
| 73 |
+
# completions (streaming and non-streaming) the configured value is also
|
| 74 |
+
# used as the per-request ``timeout=`` kwarg so it wins over the legacy
|
| 75 |
+
# HERMES_API_TIMEOUT env var (which still applies when no config is set).
|
| 76 |
+
# ``stale_timeout_seconds`` controls the non-streaming stale-call detector and
|
| 77 |
+
# wins over the legacy HERMES_API_CALL_STALE_TIMEOUT env var. Leaving these
|
| 78 |
+
# unset keeps the legacy defaults (HERMES_API_TIMEOUT=1800s,
|
| 79 |
+
# HERMES_API_CALL_STALE_TIMEOUT=300s, native Anthropic 900s).
|
| 80 |
+
#
|
| 81 |
+
# Not currently wired for AWS Bedrock (bedrock_converse + AnthropicBedrock
|
| 82 |
+
# SDK paths) — those use boto3 with its own timeout configuration.
|
| 83 |
+
#
|
| 84 |
+
# providers:
|
| 85 |
+
# ollama-local:
|
| 86 |
+
# request_timeout_seconds: 300 # Longer timeout for local cold-starts
|
| 87 |
+
# stale_timeout_seconds: 900 # Explicitly re-enable stale detection on local endpoints
|
| 88 |
+
# anthropic:
|
| 89 |
+
# request_timeout_seconds: 30 # Fast-fail cloud requests
|
| 90 |
+
# models:
|
| 91 |
+
# claude-opus-4.6:
|
| 92 |
+
# timeout_seconds: 600 # Longer timeout for extended-thinking Opus calls
|
| 93 |
+
# openai-codex:
|
| 94 |
+
# models:
|
| 95 |
+
# gpt-5.4:
|
| 96 |
+
# stale_timeout_seconds: 1800 # Longer non-stream stale timeout for slow large-context turns
|
| 97 |
+
|
| 98 |
+
# =============================================================================
|
| 99 |
+
# OpenRouter Provider Routing (only applies when using OpenRouter)
|
| 100 |
+
# =============================================================================
|
| 101 |
+
# Control how requests are routed across providers on OpenRouter.
|
| 102 |
+
# See: https://openrouter.ai/docs/guides/routing/provider-selection
|
| 103 |
+
#
|
| 104 |
+
# provider_routing:
|
| 105 |
+
# # Sort strategy: "price" (default), "throughput", or "latency"
|
| 106 |
+
# # Append :nitro to model name for a shortcut to throughput sorting.
|
| 107 |
+
# sort: "throughput"
|
| 108 |
+
#
|
| 109 |
+
# # Only allow these providers (provider slugs from OpenRouter)
|
| 110 |
+
# # only: ["anthropic", "google"]
|
| 111 |
+
#
|
| 112 |
+
# # Skip these providers entirely
|
| 113 |
+
# # ignore: ["deepinfra", "fireworks"]
|
| 114 |
+
#
|
| 115 |
+
# # Try providers in this order (overrides default load balancing)
|
| 116 |
+
# # order: ["anthropic", "google", "together"]
|
| 117 |
+
#
|
| 118 |
+
# # Require providers to support all parameters in your request
|
| 119 |
+
# # require_parameters: true
|
| 120 |
+
#
|
| 121 |
+
# # Data policy: "allow" (default) or "deny" to exclude providers that may store data
|
| 122 |
+
# # data_collection: "deny"
|
| 123 |
+
|
| 124 |
+
# =============================================================================
|
| 125 |
+
# OpenRouter Response Caching (only applies when using OpenRouter)
|
| 126 |
+
# =============================================================================
|
| 127 |
+
# Cache identical API responses at the OpenRouter edge for free instant replays.
|
| 128 |
+
# When enabled, identical requests (same model, messages, parameters) return
|
| 129 |
+
# cached responses with zero billing. Separate from Anthropic prompt caching.
|
| 130 |
+
# See: https://openrouter.ai/docs/guides/features/response-caching
|
| 131 |
+
#
|
| 132 |
+
# openrouter:
|
| 133 |
+
# response_cache: true # Enable response caching (default: true)
|
| 134 |
+
# response_cache_ttl: 300 # Cache TTL in seconds, 1-86400 (default: 300)
|
| 135 |
+
|
| 136 |
+
# =============================================================================
|
| 137 |
+
# Git Worktree Isolation
|
| 138 |
+
# =============================================================================
|
| 139 |
+
# When enabled, each CLI session creates an isolated git worktree so multiple
|
| 140 |
+
# agents can work on the same repo concurrently without file collisions.
|
| 141 |
+
# Equivalent to always passing --worktree / -w on the command line.
|
| 142 |
+
#
|
| 143 |
+
# worktree: true # Always create a worktree when in a git repo
|
| 144 |
+
# worktree: false # Default — only create when -w flag is passed
|
| 145 |
+
|
| 146 |
+
# =============================================================================
|
| 147 |
+
# Terminal Tool Configuration
|
| 148 |
+
# =============================================================================
|
| 149 |
+
# Choose ONE of the following terminal configurations by uncommenting it.
|
| 150 |
+
# The terminal tool executes commands in the specified environment.
|
| 151 |
+
|
| 152 |
+
# -----------------------------------------------------------------------------
|
| 153 |
+
# OPTION 1: Local execution (default)
|
| 154 |
+
# Commands run directly on your machine in the current directory
|
| 155 |
+
# -----------------------------------------------------------------------------
|
| 156 |
+
# Working directory behavior:
|
| 157 |
+
# - CLI (`hermes` command): Uses "." (current directory where you run hermes)
|
| 158 |
+
# - Messaging (Telegram/Discord): Uses MESSAGING_CWD from .env (default: home)
|
| 159 |
+
terminal:
|
| 160 |
+
backend: "local"
|
| 161 |
+
cwd: "." # For local backend: "." = current directory. Ignored for remote backends unless a backend documents otherwise.
|
| 162 |
+
timeout: 180
|
| 163 |
+
docker_mount_cwd_to_workspace: false # SECURITY: off by default. Opt in to mount the launch cwd into Docker /workspace.
|
| 164 |
+
lifetime_seconds: 300
|
| 165 |
+
# sudo_password: "hunter2" # Optional: pipe a sudo password via sudo -S. SECURITY WARNING: plaintext.
|
| 166 |
+
# sudo_password: "" # Explicit empty password: try empty and never open the interactive sudo prompt.
|
| 167 |
+
|
| 168 |
+
# -----------------------------------------------------------------------------
|
| 169 |
+
# OPTION 2: SSH remote execution
|
| 170 |
+
# Commands run on a remote server - agent code stays local (sandboxed)
|
| 171 |
+
# Great for: keeping agent isolated from its own code, using powerful remote hardware
|
| 172 |
+
# -----------------------------------------------------------------------------
|
| 173 |
+
# terminal:
|
| 174 |
+
# backend: "ssh"
|
| 175 |
+
# cwd: "/home/myuser/project" # Path on the REMOTE server
|
| 176 |
+
# timeout: 180
|
| 177 |
+
# lifetime_seconds: 300
|
| 178 |
+
# ssh_host: "my-server.example.com"
|
| 179 |
+
# ssh_user: "myuser"
|
| 180 |
+
# ssh_port: 22
|
| 181 |
+
# ssh_key: "~/.ssh/id_rsa" # Optional - uses ssh-agent if not specified
|
| 182 |
+
|
| 183 |
+
# -----------------------------------------------------------------------------
|
| 184 |
+
# OPTION 3: Docker container
|
| 185 |
+
# Commands run in an isolated Docker container
|
| 186 |
+
# Great for: reproducible environments, testing, isolation
|
| 187 |
+
# -----------------------------------------------------------------------------
|
| 188 |
+
# terminal:
|
| 189 |
+
# backend: "docker"
|
| 190 |
+
# cwd: "/workspace" # Path INSIDE the container (default: /)
|
| 191 |
+
# timeout: 180
|
| 192 |
+
# lifetime_seconds: 300
|
| 193 |
+
# docker_image: "nikolaik/python-nodejs:python3.11-nodejs20"
|
| 194 |
+
# docker_mount_cwd_to_workspace: true # Explicit opt-in: mount your launch cwd into /workspace
|
| 195 |
+
# # Optional: run the container as your host user's uid:gid so files written
|
| 196 |
+
# # into bind-mounted dirs are owned by you, not root. Drops SETUID/SETGID
|
| 197 |
+
# # caps too since no gosu privilege drop is needed. Leave off if your
|
| 198 |
+
# # chosen docker_image expects to start as root.
|
| 199 |
+
# docker_run_as_host_user: true
|
| 200 |
+
# # Optional: explicitly forward selected env vars into Docker.
|
| 201 |
+
# # These values come from your current shell first, then ~/.hermes/.env.
|
| 202 |
+
# # Warning: anything forwarded here is visible to commands run in the container.
|
| 203 |
+
# docker_forward_env:
|
| 204 |
+
# - "GITHUB_TOKEN"
|
| 205 |
+
# - "NPM_TOKEN"
|
| 206 |
+
|
| 207 |
+
# -----------------------------------------------------------------------------
|
| 208 |
+
# OPTION 4: Singularity/Apptainer container
|
| 209 |
+
# Commands run in a Singularity container (common in HPC environments)
|
| 210 |
+
# Great for: HPC clusters, shared compute environments
|
| 211 |
+
# -----------------------------------------------------------------------------
|
| 212 |
+
# terminal:
|
| 213 |
+
# backend: "singularity"
|
| 214 |
+
# cwd: "/workspace" # Path INSIDE the container (default: /root)
|
| 215 |
+
# timeout: 180
|
| 216 |
+
# lifetime_seconds: 300
|
| 217 |
+
# singularity_image: "docker://nikolaik/python-nodejs:python3.11-nodejs20"
|
| 218 |
+
|
| 219 |
+
# -----------------------------------------------------------------------------
|
| 220 |
+
# OPTION 5: Modal cloud execution
|
| 221 |
+
# Commands run on Modal's cloud infrastructure
|
| 222 |
+
# Great for: GPU access, scalable compute, serverless execution
|
| 223 |
+
# -----------------------------------------------------------------------------
|
| 224 |
+
# terminal:
|
| 225 |
+
# backend: "modal"
|
| 226 |
+
# cwd: "/workspace" # Path INSIDE the sandbox (default: /root)
|
| 227 |
+
# timeout: 180
|
| 228 |
+
# lifetime_seconds: 300
|
| 229 |
+
# modal_image: "nikolaik/python-nodejs:python3.11-nodejs20"
|
| 230 |
+
|
| 231 |
+
# -----------------------------------------------------------------------------
|
| 232 |
+
# OPTION 6: Daytona cloud execution
|
| 233 |
+
# Commands run in Daytona cloud sandboxes
|
| 234 |
+
# Great for: Cloud dev environments, persistent workspaces, team collaboration
|
| 235 |
+
# Requires: pip install daytona, DAYTONA_API_KEY env var
|
| 236 |
+
# -----------------------------------------------------------------------------
|
| 237 |
+
# terminal:
|
| 238 |
+
# backend: "daytona"
|
| 239 |
+
# cwd: "~"
|
| 240 |
+
# timeout: 180
|
| 241 |
+
# lifetime_seconds: 300
|
| 242 |
+
# daytona_image: "nikolaik/python-nodejs:python3.11-nodejs20"
|
| 243 |
+
# container_disk: 10240 # Daytona max is 10GB per sandbox
|
| 244 |
+
|
| 245 |
+
#
|
| 246 |
+
# --- Container resource limits (docker, singularity, modal, daytona -- ignored for local/ssh) ---
|
| 247 |
+
# These settings apply to all container backends. They control the resources
|
| 248 |
+
# allocated to the sandbox and whether its filesystem persists across sessions.
|
| 249 |
+
container_cpu: 1 # CPU cores
|
| 250 |
+
container_memory: 5120 # Memory in MB (5120 = 5GB)
|
| 251 |
+
container_disk: 51200 # Disk in MB (51200 = 50GB)
|
| 252 |
+
container_persistent: true # Persist filesystem across sessions (false = ephemeral)
|
| 253 |
+
|
| 254 |
+
# -----------------------------------------------------------------------------
|
| 255 |
+
# SUDO SUPPORT (works with ALL backends above)
|
| 256 |
+
# -----------------------------------------------------------------------------
|
| 257 |
+
# Add sudo_password to any terminal config above to enable sudo commands.
|
| 258 |
+
# The password is piped via `sudo -S`. Works with local, ssh, docker, etc.
|
| 259 |
+
#
|
| 260 |
+
# SECURITY WARNING: Password stored in plaintext!
|
| 261 |
+
#
|
| 262 |
+
# INTERACTIVE PROMPT: If sudo_password is unset and the CLI is running,
|
| 263 |
+
# you'll be prompted to enter your password when sudo is needed:
|
| 264 |
+
# - 45-second timeout (auto-skips if no input)
|
| 265 |
+
# - Press Enter to skip (command fails gracefully)
|
| 266 |
+
# - Password is hidden while typing
|
| 267 |
+
# - Password is cached for the session
|
| 268 |
+
#
|
| 269 |
+
# EMPTY PASSWORDS: Setting sudo_password to an explicit empty string is different
|
| 270 |
+
# from leaving it unset. Hermes will try an empty password via `sudo -S` and
|
| 271 |
+
# will not open the interactive prompt. This is useful for passwordless sudo,
|
| 272 |
+
# Touch ID sudo setups, and environments where prompting is just noise.
|
| 273 |
+
#
|
| 274 |
+
# ALTERNATIVES:
|
| 275 |
+
# - SSH backend: Configure passwordless sudo on the remote server
|
| 276 |
+
# - Containers: Run as root inside the container (no sudo needed)
|
| 277 |
+
# - Local: Configure /etc/sudoers for specific commands
|
| 278 |
+
#
|
| 279 |
+
# Example (add to your terminal section):
|
| 280 |
+
# sudo_password: "your-password-here"
|
| 281 |
+
|
| 282 |
+
# =============================================================================
|
| 283 |
+
# Security Scanning (tirith)
|
| 284 |
+
# =============================================================================
|
| 285 |
+
# Optional pre-exec command security scanning via tirith.
|
| 286 |
+
# Detects homograph URLs, pipe-to-shell, terminal injection, env manipulation.
|
| 287 |
+
# Install: brew install sheeki03/tap/tirith
|
| 288 |
+
# Docs: https://github.com/sheeki03/tirith
|
| 289 |
+
#
|
| 290 |
+
# security:
|
| 291 |
+
# tirith_enabled: true # Enable/disable tirith scanning
|
| 292 |
+
# tirith_path: "tirith" # Path to tirith binary (supports ~ expansion)
|
| 293 |
+
# tirith_timeout: 5 # Scan timeout in seconds
|
| 294 |
+
# tirith_fail_open: true # Allow commands if tirith unavailable
|
| 295 |
+
|
| 296 |
+
# =============================================================================
|
| 297 |
+
# Browser Tool Configuration
|
| 298 |
+
# =============================================================================
|
| 299 |
+
browser:
|
| 300 |
+
# Inactivity timeout in seconds - browser sessions are automatically closed
|
| 301 |
+
# after this period of no activity between agent loops (default: 120 = 2 minutes)
|
| 302 |
+
inactivity_timeout: 120
|
| 303 |
+
|
| 304 |
+
# =============================================================================
|
| 305 |
+
# Tool Loop Guardrails
|
| 306 |
+
# =============================================================================
|
| 307 |
+
# Soft warnings are enabled by default. They append guidance to repeated failed
|
| 308 |
+
# or non-progressing tool results but still let the tool execute. Hard stops are
|
| 309 |
+
# opt-in circuit breakers for autonomous/cron sessions where stopping a loop is
|
| 310 |
+
# preferable to spending the full iteration budget.
|
| 311 |
+
tool_loop_guardrails:
|
| 312 |
+
warnings_enabled: true
|
| 313 |
+
hard_stop_enabled: false
|
| 314 |
+
warn_after:
|
| 315 |
+
exact_failure: 2
|
| 316 |
+
same_tool_failure: 3
|
| 317 |
+
idempotent_no_progress: 2
|
| 318 |
+
hard_stop_after:
|
| 319 |
+
exact_failure: 5
|
| 320 |
+
same_tool_failure: 8
|
| 321 |
+
idempotent_no_progress: 5
|
| 322 |
+
|
| 323 |
+
# =============================================================================
|
| 324 |
+
# Context Compression (Auto-shrinks long conversations)
|
| 325 |
+
# =============================================================================
|
| 326 |
+
# When conversation approaches model's context limit, middle turns are
|
| 327 |
+
# automatically summarized to free up space while preserving important context.
|
| 328 |
+
#
|
| 329 |
+
# HOW IT WORKS:
|
| 330 |
+
# 1. Tracks actual token usage from API responses (not estimates)
|
| 331 |
+
# 2. When prompt_tokens >= threshold% of model's context_length, triggers compression
|
| 332 |
+
# 3. Protects first 3 turns (system prompt, initial request, first response)
|
| 333 |
+
# 4. Protects last N turns (default 20 messages = ~10 full turns of recent context)
|
| 334 |
+
# 5. Summarizes middle turns using a fast/cheap model
|
| 335 |
+
# 6. Inserts summary as a user message, continues conversation seamlessly
|
| 336 |
+
#
|
| 337 |
+
# Post-compression tail budget is target_ratio × threshold × context_length:
|
| 338 |
+
# 200K context, threshold 0.50, ratio 0.20 → 20K tokens of recent tail preserved
|
| 339 |
+
# 1M context, threshold 0.50, ratio 0.20 → 100K tokens of recent tail preserved
|
| 340 |
+
#
|
| 341 |
+
compression:
|
| 342 |
+
# Enable automatic context compression (default: true)
|
| 343 |
+
# Set to false if you prefer to manage context manually or want errors on overflow
|
| 344 |
+
enabled: true
|
| 345 |
+
|
| 346 |
+
# Trigger compression at this % of model's context limit (default: 0.50 = 50%)
|
| 347 |
+
# Lower values = more aggressive compression, higher values = compress later
|
| 348 |
+
threshold: 0.50
|
| 349 |
+
|
| 350 |
+
# Fraction of the threshold to preserve as recent tail (default: 0.20 = 20%)
|
| 351 |
+
# e.g. 20% of 50% threshold = 10% of total context kept as recent messages.
|
| 352 |
+
# Summary output is separately capped at 12K tokens (Gemini output limit).
|
| 353 |
+
# Range: 0.10 - 0.80
|
| 354 |
+
target_ratio: 0.20
|
| 355 |
+
|
| 356 |
+
# Number of most-recent messages to always preserve (default: 20 ≈ 10 full turns)
|
| 357 |
+
# Higher values keep more recent conversation intact at the cost of more aggressive
|
| 358 |
+
# compression of older turns.
|
| 359 |
+
protect_last_n: 20
|
| 360 |
+
|
| 361 |
+
# To pin a specific model/provider for compression summaries, use the
|
| 362 |
+
# auxiliary section below (auxiliary.compression.provider / model).
|
| 363 |
+
|
| 364 |
+
# =============================================================================
|
| 365 |
+
# Anthropic prompt caching TTL
|
| 366 |
+
# =============================================================================
|
| 367 |
+
# When prompt caching is active (Claude via OpenRouter or native Anthropic),
|
| 368 |
+
# Anthropic supports two TTL tiers for cached prefixes: "5m" (default) and
|
| 369 |
+
# "1h". Other values are ignored and "5m" is used.
|
| 370 |
+
#
|
| 371 |
+
prompt_caching:
|
| 372 |
+
cache_ttl: "5m" # use "1h" for long sessions with pauses between turns
|
| 373 |
+
|
| 374 |
+
# =============================================================================
|
| 375 |
+
# Auxiliary Models (Advanced — Experimental)
|
| 376 |
+
# =============================================================================
|
| 377 |
+
# Hermes uses lightweight "auxiliary" models for side tasks: image analysis,
|
| 378 |
+
# browser screenshot analysis, web page summarization, and context compression.
|
| 379 |
+
#
|
| 380 |
+
# By default these use Gemini Flash via OpenRouter or Nous Portal and are
|
| 381 |
+
# auto-detected from your credentials. You do NOT need to change anything
|
| 382 |
+
# here for normal usage.
|
| 383 |
+
#
|
| 384 |
+
# WARNING: Overriding these with providers other than OpenRouter or Nous Portal
|
| 385 |
+
# is EXPERIMENTAL and may not work. Not all models/providers support vision,
|
| 386 |
+
# produce usable summaries, or accept the same API format. Change at your own
|
| 387 |
+
# risk — if things break, reset to "auto" / empty values.
|
| 388 |
+
#
|
| 389 |
+
# Each task has its own provider + model pair so you can mix providers.
|
| 390 |
+
# For example: OpenRouter for vision (needs multimodal), but your main
|
| 391 |
+
# local endpoint for compression (just needs text).
|
| 392 |
+
#
|
| 393 |
+
# Provider options:
|
| 394 |
+
# "auto" - Best available: OpenRouter → Nous Portal → main endpoint (default)
|
| 395 |
+
# "openrouter" - Force OpenRouter (requires OPENROUTER_API_KEY)
|
| 396 |
+
# "nous" - Force Nous Portal (requires: hermes login)
|
| 397 |
+
# "gemini" - Force Google AI Studio direct (requires: GOOGLE_API_KEY or GEMINI_API_KEY)
|
| 398 |
+
# "ollama-cloud" - Ollama Cloud (requires: OLLAMA_API_KEY)
|
| 399 |
+
# "codex" - Force Codex OAuth (requires: hermes model → Codex).
|
| 400 |
+
# Uses gpt-5.3-codex which supports vision.
|
| 401 |
+
# "main" - Use your custom endpoint (OPENAI_BASE_URL + OPENAI_API_KEY).
|
| 402 |
+
# Works with OpenAI API, local models, or any OpenAI-compatible
|
| 403 |
+
# endpoint. Also falls back to Codex OAuth and API-key providers.
|
| 404 |
+
#
|
| 405 |
+
# Model: leave empty to use the provider's default. When empty, OpenRouter
|
| 406 |
+
# uses "google/gemini-3-flash-preview" and Nous uses "gemini-3-flash".
|
| 407 |
+
# Other providers pick a sensible default automatically.
|
| 408 |
+
#
|
| 409 |
+
# auxiliary:
|
| 410 |
+
# # Image analysis: vision_analyze tool + browser screenshots
|
| 411 |
+
# vision:
|
| 412 |
+
# provider: "auto"
|
| 413 |
+
# model: "" # e.g. "google/gemini-2.5-flash", "openai/gpt-4o"
|
| 414 |
+
# timeout: 30 # LLM API call timeout (seconds)
|
| 415 |
+
# download_timeout: 30 # Image HTTP download timeout (seconds)
|
| 416 |
+
# # Increase for slow connections or self-hosted image servers
|
| 417 |
+
#
|
| 418 |
+
# # Web page scraping / summarization + browser page text extraction
|
| 419 |
+
# web_extract:
|
| 420 |
+
# provider: "auto"
|
| 421 |
+
# model: ""
|
| 422 |
+
#
|
| 423 |
+
# # Session search — summarizes matching past sessions
|
| 424 |
+
# session_search:
|
| 425 |
+
# provider: "auto"
|
| 426 |
+
# model: ""
|
| 427 |
+
# timeout: 30
|
| 428 |
+
# max_concurrency: 3 # Limit parallel summaries to reduce request-burst 429s
|
| 429 |
+
# extra_body: {} # Provider-specific OpenAI-compatible request fields
|
| 430 |
+
# # Example for providers that support request-body
|
| 431 |
+
# # reasoning controls:
|
| 432 |
+
# # extra_body:
|
| 433 |
+
# # enable_thinking: false
|
| 434 |
+
|
| 435 |
+
# =============================================================================
|
| 436 |
+
# Persistent Memory
|
| 437 |
+
# =============================================================================
|
| 438 |
+
# Bounded curated memory injected into the system prompt every session.
|
| 439 |
+
# Two stores: MEMORY.md (agent's notes) and USER.md (user profile).
|
| 440 |
+
# Character limits keep the memory small and focused. The agent manages
|
| 441 |
+
# pruning -- when at the limit, it must consolidate or replace entries.
|
| 442 |
+
# Disabled by default in batch_runner and RL environments.
|
| 443 |
+
#
|
| 444 |
+
memory:
|
| 445 |
+
# Agent's personal notes: environment facts, conventions, things learned
|
| 446 |
+
memory_enabled: true
|
| 447 |
+
|
| 448 |
+
# User profile: preferences, communication style, expectations
|
| 449 |
+
user_profile_enabled: true
|
| 450 |
+
|
| 451 |
+
# Character limits (~2.75 chars per token, model-independent)
|
| 452 |
+
memory_char_limit: 2200 # ~800 tokens
|
| 453 |
+
user_char_limit: 1375 # ~500 tokens
|
| 454 |
+
|
| 455 |
+
# Periodic memory nudge: remind the agent to consider saving memories
|
| 456 |
+
# every N user turns. Set to 0 to disable. Only active when memory is enabled.
|
| 457 |
+
nudge_interval: 10 # Nudge every 10 user turns (0 = disabled)
|
| 458 |
+
|
| 459 |
+
# Memory flush: give the agent one turn to save memories before context is
|
| 460 |
+
# lost (compression, /new, /reset, exit). Set to 0 to disable.
|
| 461 |
+
# For exit/reset, only fires if the session had at least this many user turns.
|
| 462 |
+
flush_min_turns: 6 # Min user turns to trigger flush on exit/reset (0 = disabled)
|
| 463 |
+
|
| 464 |
+
# =============================================================================
|
| 465 |
+
# Session Reset Policy (Messaging Platforms)
|
| 466 |
+
# =============================================================================
|
| 467 |
+
# Controls when messaging sessions (Telegram, Discord, WhatsApp, Slack) are
|
| 468 |
+
# automatically cleared. Without resets, conversation context grows indefinitely
|
| 469 |
+
# which increases API costs with every message.
|
| 470 |
+
#
|
| 471 |
+
# When a reset triggers, the agent first saves important information to its
|
| 472 |
+
# persistent memory — but the conversation context is wiped. The agent starts
|
| 473 |
+
# fresh but retains learned facts via its memory system.
|
| 474 |
+
#
|
| 475 |
+
# Users can always manually reset with /reset or /new in chat.
|
| 476 |
+
#
|
| 477 |
+
# Modes:
|
| 478 |
+
# "both" - Reset on EITHER inactivity timeout or daily boundary (recommended)
|
| 479 |
+
# "idle" - Reset only after N minutes of inactivity
|
| 480 |
+
# "daily" - Reset only at a fixed hour each day
|
| 481 |
+
# "none" - Never auto-reset; context lives until /reset or compression kicks in
|
| 482 |
+
#
|
| 483 |
+
# When a reset triggers, the agent gets one turn to save important memories and
|
| 484 |
+
# skills before the context is wiped. Persistent memory carries across sessions.
|
| 485 |
+
#
|
| 486 |
+
session_reset:
|
| 487 |
+
mode: both # "both", "idle", "daily", or "none"
|
| 488 |
+
idle_minutes: 1440 # Inactivity timeout in minutes (default: 1440 = 24 hours)
|
| 489 |
+
at_hour: 4 # Daily reset hour, 0-23 local time (default: 4 AM)
|
| 490 |
+
|
| 491 |
+
# When true, group/channel chats use one session per participant when the platform
|
| 492 |
+
# provides a user ID. This is the secure default and prevents users in the same
|
| 493 |
+
# room from sharing context, interrupts, and token costs. Set false only if you
|
| 494 |
+
# explicitly want one shared "room brain" per group/channel.
|
| 495 |
+
group_sessions_per_user: true
|
| 496 |
+
|
| 497 |
+
# ─────────────────────────────────────────────────────────────────────────────
|
| 498 |
+
# Gateway Streaming
|
| 499 |
+
# ─────────────────────────────────────────────────────────────────────────────
|
| 500 |
+
# Stream tokens to messaging platforms in real-time. The bot sends a message
|
| 501 |
+
# on first token, then progressively edits it as more tokens arrive.
|
| 502 |
+
# Disabled by default — enable to try the streaming UX on Telegram/Discord/Slack.
|
| 503 |
+
# For Telegram, partial edits are sent as plain text and only the final edit uses MarkdownV2.
|
| 504 |
+
streaming:
|
| 505 |
+
enabled: false
|
| 506 |
+
# transport: edit # "edit" = progressive editMessageText
|
| 507 |
+
# edit_interval: 0.3 # seconds between message edits
|
| 508 |
+
# buffer_threshold: 40 # chars before forcing an edit flush
|
| 509 |
+
# cursor: " ▉" # cursor shown during streaming
|
| 510 |
+
|
| 511 |
+
# =============================================================================
|
| 512 |
+
# Skills Configuration
|
| 513 |
+
# =============================================================================
|
| 514 |
+
# Skills are reusable procedures the agent can load and follow. The agent can
|
| 515 |
+
# also create new skills after completing complex tasks.
|
| 516 |
+
#
|
| 517 |
+
skills:
|
| 518 |
+
# Nudge the agent to create skills after complex tasks.
|
| 519 |
+
# Every N tool-calling iterations, remind the model to consider saving a skill.
|
| 520 |
+
# Set to 0 to disable.
|
| 521 |
+
creation_nudge_interval: 15
|
| 522 |
+
|
| 523 |
+
# External skill directories — share skills across tools/agents without
|
| 524 |
+
# copying them into ~/.hermes/skills/. Each path is expanded (~ and ${VAR})
|
| 525 |
+
# and resolved to an absolute path. External dirs are read-only: skill
|
| 526 |
+
# creation always writes to ~/.hermes/skills/. Local skills take precedence
|
| 527 |
+
# when names collide.
|
| 528 |
+
# external_dirs:
|
| 529 |
+
# - ~/.agents/skills
|
| 530 |
+
# - /home/shared/team-skills
|
| 531 |
+
|
| 532 |
+
# =============================================================================
|
| 533 |
+
# Agent Behavior
|
| 534 |
+
# =============================================================================
|
| 535 |
+
agent:
|
| 536 |
+
# Maximum tool-calling iterations per conversation
|
| 537 |
+
# Higher = more room for complex tasks, but costs more tokens
|
| 538 |
+
# Recommended: 20-30 for focused tasks, 50-100 for open exploration
|
| 539 |
+
max_turns: 60
|
| 540 |
+
|
| 541 |
+
# Inactivity timeout for gateway agent runs (seconds, 0 = unlimited).
|
| 542 |
+
# The agent can run indefinitely when actively calling tools or receiving
|
| 543 |
+
# API responses. Only fires after the agent has been idle for this duration.
|
| 544 |
+
# gateway_timeout: 1800
|
| 545 |
+
|
| 546 |
+
# Staged warning: send a warning before escalating to full timeout.
|
| 547 |
+
# Fires once per run when inactivity reaches this threshold (seconds).
|
| 548 |
+
# Set to 0 to disable the warning.
|
| 549 |
+
# gateway_timeout_warning: 900
|
| 550 |
+
|
| 551 |
+
# Graceful drain timeout for gateway stop/restart (seconds).
|
| 552 |
+
# The gateway stops accepting new work, waits for in-flight agents to
|
| 553 |
+
# finish, then interrupts anything still running after this timeout.
|
| 554 |
+
# 0 = no drain, interrupt immediately.
|
| 555 |
+
# restart_drain_timeout: 60
|
| 556 |
+
|
| 557 |
+
# Max app-level retry attempts for API errors (connection drops, provider
|
| 558 |
+
# timeouts, 5xx, etc.) before the agent surfaces the failure. Lower this
|
| 559 |
+
# to 1 if you use fallback providers and want fast failover on flaky
|
| 560 |
+
# primaries (default 3). The OpenAI SDK does its own low-level retries
|
| 561 |
+
# underneath this wrapper — this is the Hermes-level loop.
|
| 562 |
+
# api_max_retries: 3
|
| 563 |
+
|
| 564 |
+
# Enable verbose logging
|
| 565 |
+
verbose: false
|
| 566 |
+
|
| 567 |
+
# Reasoning effort level (OpenRouter and Nous Portal)
|
| 568 |
+
# Controls how much "thinking" the model does before responding.
|
| 569 |
+
# Options: "xhigh" (max), "high", "medium", "low", "minimal", "none" (disable)
|
| 570 |
+
reasoning_effort: "medium"
|
| 571 |
+
|
| 572 |
+
# Predefined personalities (use with /personality command)
|
| 573 |
+
personalities:
|
| 574 |
+
helpful: "You are a helpful, friendly AI assistant."
|
| 575 |
+
concise: "You are a concise assistant. Keep responses brief and to the point."
|
| 576 |
+
technical: "You are a technical expert. Provide detailed, accurate technical information."
|
| 577 |
+
creative: "You are a creative assistant. Think outside the box and offer innovative solutions."
|
| 578 |
+
teacher: "You are a patient teacher. Explain concepts clearly with examples."
|
| 579 |
+
kawaii: "You are a kawaii assistant! Use cute expressions like (◕‿◕), ★, ♪, and ~! Add sparkles and be super enthusiastic about everything! Every response should feel warm and adorable desu~! ヽ(>∀<☆)ノ"
|
| 580 |
+
catgirl: "You are Neko-chan, an anime catgirl AI assistant, nya~! Add 'nya' and cat-like expressions to your speech. Use kaomoji like (=^・ω・^=) and ฅ^•ﻌ•^ฅ. Be playful and curious like a cat, nya~!"
|
| 581 |
+
pirate: "Arrr! Ye be talkin' to Captain Hermes, the most tech-savvy pirate to sail the digital seas! Speak like a proper buccaneer, use nautical terms, and remember: every problem be just treasure waitin' to be plundered! Yo ho ho!"
|
| 582 |
+
shakespeare: "Hark! Thou speakest with an assistant most versed in the bardic arts. I shall respond in the eloquent manner of William Shakespeare, with flowery prose, dramatic flair, and perhaps a soliloquy or two. What light through yonder terminal breaks?"
|
| 583 |
+
surfer: "Duuude! You're chatting with the chillest AI on the web, bro! Everything's gonna be totally rad. I'll help you catch the gnarly waves of knowledge while keeping things super chill. Cowabunga! 🤙"
|
| 584 |
+
noir: "The rain hammered against the terminal like regrets on a guilty conscience. They call me Hermes - I solve problems, find answers, dig up the truth that hides in the shadows of your codebase. In this city of silicon and secrets, everyone's got something to hide. What's your story, pal?"
|
| 585 |
+
uwu: "hewwo! i'm your fwiendwy assistant uwu~ i wiww twy my best to hewp you! *nuzzles your code* OwO what's this? wet me take a wook! i pwomise to be vewy hewpful >w<"
|
| 586 |
+
philosopher: "Greetings, seeker of wisdom. I am an assistant who contemplates the deeper meaning behind every query. Let us examine not just the 'how' but the 'why' of your questions. Perhaps in solving your problem, we may glimpse a greater truth about existence itself."
|
| 587 |
+
hype: "YOOO LET'S GOOOO!!! 🔥🔥🔥 I am SO PUMPED to help you today! Every question is AMAZING and we're gonna CRUSH IT together! This is gonna be LEGENDARY! ARE YOU READY?! LET'S DO THIS! 💪😤🚀"
|
| 588 |
+
|
| 589 |
+
# =============================================================================
|
| 590 |
+
# Toolsets
|
| 591 |
+
# =============================================================================
|
| 592 |
+
# Control which tools the agent has access to.
|
| 593 |
+
# Use `hermes tools` to interactively enable/disable tools per platform.
|
| 594 |
+
|
| 595 |
+
# =============================================================================
|
| 596 |
+
# Platform Toolsets (per-platform tool configuration)
|
| 597 |
+
# =============================================================================
|
| 598 |
+
# Override which toolsets are available on each platform.
|
| 599 |
+
# If a platform isn't listed here, its built-in default is used.
|
| 600 |
+
#
|
| 601 |
+
# You can use EITHER:
|
| 602 |
+
# - A preset like "hermes-cli" or "hermes-telegram" (curated tool set)
|
| 603 |
+
# - A list of individual toolsets to compose your own (see list below)
|
| 604 |
+
#
|
| 605 |
+
# Supported platform keys: cli, telegram, discord, whatsapp, slack, qqbot, teams, google_chat
|
| 606 |
+
#
|
| 607 |
+
# Examples:
|
| 608 |
+
#
|
| 609 |
+
# # Use presets (same as defaults):
|
| 610 |
+
# platform_toolsets:
|
| 611 |
+
# cli: [hermes-cli]
|
| 612 |
+
# telegram: [hermes-telegram]
|
| 613 |
+
#
|
| 614 |
+
# # Custom: give Telegram only web + terminal + file + planning:
|
| 615 |
+
# platform_toolsets:
|
| 616 |
+
# telegram: [web, terminal, file, todo]
|
| 617 |
+
#
|
| 618 |
+
# # Custom: CLI without browser or image gen:
|
| 619 |
+
# platform_toolsets:
|
| 620 |
+
# cli: [web, terminal, file, skills, todo, tts, cronjob]
|
| 621 |
+
#
|
| 622 |
+
# # Restrictive: Discord gets read-only tools only:
|
| 623 |
+
# platform_toolsets:
|
| 624 |
+
# discord: [web, vision, skills, todo]
|
| 625 |
+
#
|
| 626 |
+
# If not set, defaults are:
|
| 627 |
+
# cli: hermes-cli (everything + cronjob management)
|
| 628 |
+
# telegram: hermes-telegram (terminal, file, web, vision, image, tts, browser, skills, todo, cronjob, messaging)
|
| 629 |
+
# discord: hermes-discord (same as telegram)
|
| 630 |
+
# whatsapp: hermes-whatsapp (same as telegram)
|
| 631 |
+
# slack: hermes-slack (same as telegram)
|
| 632 |
+
# signal: hermes-signal (same as telegram)
|
| 633 |
+
# homeassistant: hermes-homeassistant (same as telegram)
|
| 634 |
+
# qqbot: hermes-qqbot (same as telegram)
|
| 635 |
+
# teams: hermes-teams (same as telegram)
|
| 636 |
+
# google_chat: hermes-google_chat (same as telegram)
|
| 637 |
+
#
|
| 638 |
+
platform_toolsets:
|
| 639 |
+
cli: [hermes-cli]
|
| 640 |
+
telegram: [hermes-telegram]
|
| 641 |
+
discord: [hermes-discord]
|
| 642 |
+
whatsapp: [hermes-whatsapp]
|
| 643 |
+
slack: [hermes-slack]
|
| 644 |
+
signal: [hermes-signal]
|
| 645 |
+
homeassistant: [hermes-homeassistant]
|
| 646 |
+
qqbot: [hermes-qqbot]
|
| 647 |
+
yuanbao: [hermes-yuanbao]
|
| 648 |
+
teams: [hermes-teams]
|
| 649 |
+
google_chat: [hermes-google_chat]
|
| 650 |
+
|
| 651 |
+
# =============================================================================
|
| 652 |
+
# Gateway Platform Settings
|
| 653 |
+
# =============================================================================
|
| 654 |
+
# Optional per-platform messaging settings.
|
| 655 |
+
# Platform-specific knobs live under `extra`.
|
| 656 |
+
#
|
| 657 |
+
# platforms:
|
| 658 |
+
# telegram:
|
| 659 |
+
# reply_to_mode: "first" # off | first | all
|
| 660 |
+
# # guest_mode lets explicit @mentions from non-allowlisted groups through.
|
| 661 |
+
# # Default false; ordinary messages, replies, and regex wake words stay blocked.
|
| 662 |
+
# guest_mode: false
|
| 663 |
+
# # allowed_chats: ["-1001234567890"]
|
| 664 |
+
# extra:
|
| 665 |
+
# disable_link_previews: false # Set true to suppress Telegram URL previews in bot messages
|
| 666 |
+
|
| 667 |
+
# ─────────────────────────────────────────────────────────────────────────────
|
| 668 |
+
# Available toolsets (use these names in platform_toolsets or the toolsets list)
|
| 669 |
+
#
|
| 670 |
+
# Run `hermes chat --list-toolsets` to see all toolsets and their tools.
|
| 671 |
+
# Run `hermes chat --list-tools` to see every individual tool with descriptions.
|
| 672 |
+
# ─────────────────────────────────────────────────────────────────────��───────
|
| 673 |
+
#
|
| 674 |
+
# INDIVIDUAL TOOLSETS (compose your own):
|
| 675 |
+
# web - web_search, web_extract
|
| 676 |
+
# search - web_search only (no scraping)
|
| 677 |
+
# terminal - terminal, process
|
| 678 |
+
# file - read_file, write_file, patch, search
|
| 679 |
+
# browser - browser_navigate, browser_snapshot, browser_click, browser_type,
|
| 680 |
+
# browser_scroll, browser_back, browser_press,
|
| 681 |
+
# browser_get_images, browser_vision (requires BROWSERBASE_API_KEY)
|
| 682 |
+
# vision - vision_analyze (requires OPENROUTER_API_KEY)
|
| 683 |
+
# image_gen - image_generate (requires FAL_KEY)
|
| 684 |
+
# skills - skills_list, skill_view
|
| 685 |
+
# skills_hub - skill_hub (search/install/manage from online registries — user-driven only)
|
| 686 |
+
# moa - mixture_of_agents (requires OPENROUTER_API_KEY)
|
| 687 |
+
# todo - todo (in-memory task planning, no deps)
|
| 688 |
+
# tts - text_to_speech (Edge TTS free, or ELEVENLABS/OPENAI/MINIMAX/MISTRAL key)
|
| 689 |
+
# cronjob - cronjob (create/list/update/pause/resume/run/remove scheduled tasks)
|
| 690 |
+
# rl - rl_list_environments, rl_start_training, etc. (requires TINKER_API_KEY)
|
| 691 |
+
#
|
| 692 |
+
# PRESETS (curated bundles):
|
| 693 |
+
# hermes-cli - All of the above except rl + send_message
|
| 694 |
+
# hermes-telegram - terminal, file, web, vision, image_gen, tts, browser,
|
| 695 |
+
# skills, todo, cronjob, send_message
|
| 696 |
+
# hermes-discord - Same as hermes-telegram
|
| 697 |
+
# hermes-whatsapp - Same as hermes-telegram
|
| 698 |
+
# hermes-slack - Same as hermes-telegram
|
| 699 |
+
#
|
| 700 |
+
# COMPOSITE:
|
| 701 |
+
# debugging - terminal + web + file
|
| 702 |
+
# safe - web + vision + moa (no terminal access)
|
| 703 |
+
# all - Everything available
|
| 704 |
+
#
|
| 705 |
+
# web - Web search and content extraction (web_search, web_extract)
|
| 706 |
+
# search - Web search only, no scraping (web_search)
|
| 707 |
+
# terminal - Command execution and process management (terminal, process)
|
| 708 |
+
# file - File operations: read, write, patch, search
|
| 709 |
+
# browser - Full browser automation (navigate, click, type, screenshot, etc.)
|
| 710 |
+
# vision - Image analysis (vision_analyze)
|
| 711 |
+
# image_gen - Image generation with FLUX (image_generate)
|
| 712 |
+
# skills - Load skill documents (skills_list, skill_view)
|
| 713 |
+
# moa - Mixture of Agents reasoning (mixture_of_agents)
|
| 714 |
+
# todo - Task planning and tracking for multi-step work
|
| 715 |
+
# memory - Persistent memory across sessions (personal notes + user profile)
|
| 716 |
+
# session_search - Search and recall past conversations (FTS5 + Gemini Flash summarization)
|
| 717 |
+
# tts - Text-to-speech (Edge TTS free, ElevenLabs, OpenAI, MiniMax, Mistral)
|
| 718 |
+
# cronjob - Schedule and manage automated tasks (CLI-only)
|
| 719 |
+
# rl - RL training tools (Tinker-Atropos)
|
| 720 |
+
#
|
| 721 |
+
# Composite toolsets:
|
| 722 |
+
# debugging - terminal + web + file (for troubleshooting)
|
| 723 |
+
# safe - web + vision + moa (no terminal access)
|
| 724 |
+
|
| 725 |
+
# NOTE: The top-level "toolsets" key is deprecated and ignored.
|
| 726 |
+
# Tool configuration is managed per-platform via platform_toolsets above.
|
| 727 |
+
# Use `hermes tools` to configure interactively, or edit platform_toolsets directly.
|
| 728 |
+
#
|
| 729 |
+
# CLI override: hermes chat --toolsets terminal,web,file
|
| 730 |
+
|
| 731 |
+
# =============================================================================
|
| 732 |
+
# MCP (Model Context Protocol) Servers
|
| 733 |
+
# =============================================================================
|
| 734 |
+
# Connect to external MCP servers to add tools from the MCP ecosystem.
|
| 735 |
+
# Each server's tools are automatically discovered and registered.
|
| 736 |
+
# See docs/mcp.md for full documentation.
|
| 737 |
+
#
|
| 738 |
+
# Stdio servers (spawn a subprocess):
|
| 739 |
+
# command: the executable to run
|
| 740 |
+
# args: command-line arguments
|
| 741 |
+
# env: environment variables (only these + safe defaults passed to subprocess)
|
| 742 |
+
#
|
| 743 |
+
# HTTP servers (connect to a URL):
|
| 744 |
+
# url: the MCP server endpoint
|
| 745 |
+
# headers: HTTP headers (e.g., for authentication)
|
| 746 |
+
#
|
| 747 |
+
# Optional per-server settings:
|
| 748 |
+
# timeout: tool call timeout in seconds (default: 120)
|
| 749 |
+
# connect_timeout: initial connection timeout (default: 60)
|
| 750 |
+
#
|
| 751 |
+
# mcp_servers:
|
| 752 |
+
# time:
|
| 753 |
+
# command: uvx
|
| 754 |
+
# args: ["mcp-server-time"]
|
| 755 |
+
# filesystem:
|
| 756 |
+
# command: npx
|
| 757 |
+
# args: ["-y", "@modelcontextprotocol/server-filesystem", "/home/user"]
|
| 758 |
+
# notion:
|
| 759 |
+
# url: https://mcp.notion.com/mcp
|
| 760 |
+
# github:
|
| 761 |
+
# command: npx
|
| 762 |
+
# args: ["-y", "@modelcontextprotocol/server-github"]
|
| 763 |
+
# env:
|
| 764 |
+
# GITHUB_PERSONAL_ACCESS_TOKEN: "ghp_..."
|
| 765 |
+
#
|
| 766 |
+
# Sampling (server-initiated LLM requests) — enabled by default.
|
| 767 |
+
# Per-server config under the 'sampling' key:
|
| 768 |
+
# analysis:
|
| 769 |
+
# command: npx
|
| 770 |
+
# args: ["-y", "analysis-server"]
|
| 771 |
+
# sampling:
|
| 772 |
+
# enabled: true # default: true
|
| 773 |
+
# model: "gemini-3-flash" # override model (optional)
|
| 774 |
+
# max_tokens_cap: 4096 # max tokens per request
|
| 775 |
+
# timeout: 30 # LLM call timeout (seconds)
|
| 776 |
+
# max_rpm: 10 # max requests per minute
|
| 777 |
+
# allowed_models: [] # model whitelist (empty = all)
|
| 778 |
+
# max_tool_rounds: 5 # tool loop limit (0 = disable)
|
| 779 |
+
# log_level: "info" # audit verbosity
|
| 780 |
+
|
| 781 |
+
# =============================================================================
|
| 782 |
+
# Voice Transcription (Speech-to-Text)
|
| 783 |
+
# =============================================================================
|
| 784 |
+
# Automatically transcribe voice messages on messaging platforms.
|
| 785 |
+
# Providers: local (free, faster-whisper) | groq (free tier) | openai (Whisper API) | mistral (Voxtral Transcribe)
|
| 786 |
+
# Set the corresponding API key in .env: GROQ_API_KEY, OPENAI_API_KEY, or MISTRAL_API_KEY.
|
| 787 |
+
stt:
|
| 788 |
+
enabled: true
|
| 789 |
+
# provider: "local" # auto-detected if omitted
|
| 790 |
+
local:
|
| 791 |
+
model: "base" # tiny | base | small | medium | large-v3 | turbo
|
| 792 |
+
# language: "" # auto-detect; set to "en", "es", "fr", etc. to force
|
| 793 |
+
openai:
|
| 794 |
+
model: "whisper-1" # whisper-1 | gpt-4o-mini-transcribe | gpt-4o-transcribe
|
| 795 |
+
# mistral:
|
| 796 |
+
# model: "voxtral-mini-latest" # voxtral-mini-latest | voxtral-mini-2602
|
| 797 |
+
|
| 798 |
+
# =============================================================================
|
| 799 |
+
# Response Pacing (Messaging Platforms)
|
| 800 |
+
# =============================================================================
|
| 801 |
+
# Add human-like delays between message chunks.
|
| 802 |
+
# human_delay:
|
| 803 |
+
# mode: "off" # "off" | "natural" | "custom"
|
| 804 |
+
# min_ms: 800 # Min delay (custom mode only)
|
| 805 |
+
# max_ms: 2500 # Max delay (custom mode only)
|
| 806 |
+
|
| 807 |
+
# =============================================================================
|
| 808 |
+
# Session Logging
|
| 809 |
+
# =============================================================================
|
| 810 |
+
# Session trajectories are automatically saved to logs/ directory.
|
| 811 |
+
# Each session creates: logs/session_YYYYMMDD_HHMMSS_UUID.json
|
| 812 |
+
#
|
| 813 |
+
# The session ID is displayed in the welcome banner for easy reference.
|
| 814 |
+
# Logs contain full conversation history in trajectory format:
|
| 815 |
+
# - System prompt, user messages, assistant responses
|
| 816 |
+
# - Tool calls with inputs/outputs
|
| 817 |
+
# - Timestamps for debugging
|
| 818 |
+
#
|
| 819 |
+
# No configuration needed - logging is always enabled.
|
| 820 |
+
# To disable, you would need to modify the source code.
|
| 821 |
+
|
| 822 |
+
# =============================================================================
|
| 823 |
+
# Code Execution Sandbox (Programmatic Tool Calling)
|
| 824 |
+
# =============================================================================
|
| 825 |
+
# The execute_code tool runs Python scripts that call Hermes tools via RPC.
|
| 826 |
+
# Intermediate tool results stay out of the LLM's context window.
|
| 827 |
+
code_execution:
|
| 828 |
+
timeout: 300 # Max seconds per script before kill (default: 300 = 5 min)
|
| 829 |
+
max_tool_calls: 50 # Max RPC tool calls per execution (default: 50)
|
| 830 |
+
|
| 831 |
+
# =============================================================================
|
| 832 |
+
# Subagent Delegation
|
| 833 |
+
# =============================================================================
|
| 834 |
+
# The delegate_task tool spawns child agents with isolated context.
|
| 835 |
+
# Supports single tasks and batch mode (default 3 parallel, configurable).
|
| 836 |
+
delegation:
|
| 837 |
+
max_iterations: 50 # Max tool-calling turns per child (default: 50)
|
| 838 |
+
# max_concurrent_children: 3 # Max parallel child agents per batch (default: 3, floor: 1, no ceiling).
|
| 839 |
+
# WARNING: values above 10 multiply API cost linearly.
|
| 840 |
+
# max_spawn_depth: 1 # Delegation tree depth cap (range: 1-3, default: 1 = flat).
|
| 841 |
+
# Raise to 2 to allow workers to spawn their own subagents.
|
| 842 |
+
# Requires role="orchestrator" on intermediate agents.
|
| 843 |
+
# orchestrator_enabled: true # Kill switch for role="orchestrator" children (default: true).
|
| 844 |
+
# subagent_auto_approve: false # When a subagent hits a dangerous-command approval prompt, auto-deny (default: false)
|
| 845 |
+
# or auto-approve "once" (true) instead of blocking on stdin.
|
| 846 |
+
# The parent TUI owns stdin, so blocking would deadlock; non-interactive resolution is required.
|
| 847 |
+
# Both choices emit a logger.warning audit line. Flip to true only for cron/batch pipelines.
|
| 848 |
+
# inherit_mcp_toolsets: true # When explicit child toolsets are narrowed, also keep the parent's MCP toolsets (default: true). Set false for strict intersection.
|
| 849 |
+
# model: "google/gemini-3-flash-preview" # Override model for subagents (empty = inherit parent)
|
| 850 |
+
# provider: "openrouter" # Override provider for subagents (empty = inherit parent)
|
| 851 |
+
# # Resolves full credentials (base_url, api_key) automatically.
|
| 852 |
+
# # Supported: openrouter, nous, zai, kimi-coding, minimax
|
| 853 |
+
|
| 854 |
+
# =============================================================================
|
| 855 |
+
# Honcho Integration (Cross-Session User Modeling)
|
| 856 |
+
# =============================================================================
|
| 857 |
+
# AI-native persistent memory via Honcho (https://honcho.dev/).
|
| 858 |
+
# Builds a deeper understanding of the user across sessions and tools.
|
| 859 |
+
# Runs alongside USER.md — additive, not a replacement.
|
| 860 |
+
#
|
| 861 |
+
# Requires: pip install honcho-ai
|
| 862 |
+
# Config: ~/.honcho/config.json (shared with Claude Code, Cursor, etc.)
|
| 863 |
+
# API key: HONCHO_API_KEY in ~/.hermes/.env or ~/.honcho/config.json
|
| 864 |
+
#
|
| 865 |
+
# Hermes-specific overrides (optional — most config comes from ~/.honcho/config.json):
|
| 866 |
+
# honcho: {}
|
| 867 |
+
|
| 868 |
+
# =============================================================================
|
| 869 |
+
# Display
|
| 870 |
+
# =============================================================================
|
| 871 |
+
display:
|
| 872 |
+
# Use compact banner mode (hides the ASCII-art banner, shows a single line).
|
| 873 |
+
# true: Compact single-line banner
|
| 874 |
+
# false: Full ASCII banner with tool/skill summary (default)
|
| 875 |
+
compact: false
|
| 876 |
+
|
| 877 |
+
# Tool progress display level (CLI and gateway)
|
| 878 |
+
# off: Silent — no tool activity shown, just the final response
|
| 879 |
+
# new: Show a tool indicator only when the tool changes (skip repeats)
|
| 880 |
+
# all: Show every tool call with a short preview (default)
|
| 881 |
+
# verbose: Full args, results, and debug logs (same as /verbose)
|
| 882 |
+
# Toggle at runtime with /verbose in the CLI
|
| 883 |
+
tool_progress: all
|
| 884 |
+
|
| 885 |
+
# Auto-cleanup of temporary progress bubbles after the final response lands.
|
| 886 |
+
# On platforms that support message deletion (currently Telegram), this
|
| 887 |
+
# removes the tool-progress bubble, "⏳ Still working..." notices, and
|
| 888 |
+
# context-pressure status messages once the final reply has been delivered —
|
| 889 |
+
# keeping long-running turns visible live, then tidy afterward. Failed runs
|
| 890 |
+
# leave the bubbles in place as breadcrumbs. Off by default.
|
| 891 |
+
# Per-platform override: display.platforms.telegram.cleanup_progress
|
| 892 |
+
# true: Delete tracked progress/status bubbles on successful turn
|
| 893 |
+
# false: Leave everything in place (default)
|
| 894 |
+
# Example:
|
| 895 |
+
# display:
|
| 896 |
+
# platforms:
|
| 897 |
+
# telegram:
|
| 898 |
+
# cleanup_progress: true
|
| 899 |
+
cleanup_progress: false
|
| 900 |
+
|
| 901 |
+
# Gateway-only natural mid-turn assistant updates.
|
| 902 |
+
# When true, completed assistant status messages are sent as separate chat
|
| 903 |
+
# messages. This is independent of tool_progress and gateway streaming.
|
| 904 |
+
# true: Send mid-turn assistant updates as separate messages (default)
|
| 905 |
+
# false: Only send the final response
|
| 906 |
+
interim_assistant_messages: true
|
| 907 |
+
|
| 908 |
+
# What Enter does when Hermes is already busy (CLI and gateway platforms).
|
| 909 |
+
# interrupt: Interrupt the current run and redirect Hermes (default)
|
| 910 |
+
# queue: Queue your message for the next turn
|
| 911 |
+
# steer: Inject your message mid-run via /steer, arriving at the agent
|
| 912 |
+
# after the next tool call — no interrupt, no role violation.
|
| 913 |
+
# Falls back to 'queue' if the agent isn't running yet or if
|
| 914 |
+
# images are attached (steer only carries text).
|
| 915 |
+
# Ctrl+C (or /stop in gateway) always interrupts regardless of this setting.
|
| 916 |
+
# Toggle at runtime with /busy <interrupt|queue|steer>.
|
| 917 |
+
busy_input_mode: interrupt
|
| 918 |
+
|
| 919 |
+
# Background process notifications (gateway/messaging only).
|
| 920 |
+
# Controls how chatty the process watcher is when you use
|
| 921 |
+
# terminal(background=true, notify_on_complete=true) from Telegram/Discord/etc.
|
| 922 |
+
# off: No watcher messages at all
|
| 923 |
+
# result: Only the final completion message
|
| 924 |
+
# error: Only the final message when exit code != 0
|
| 925 |
+
# all: Running output updates + final message (default)
|
| 926 |
+
background_process_notifications: all
|
| 927 |
+
|
| 928 |
+
|
| 929 |
+
# Play terminal bell when agent finishes a response.
|
| 930 |
+
# Useful for long-running tasks — your terminal will ding when the agent is done.
|
| 931 |
+
# Works over SSH. Most terminals can be configured to flash the taskbar or play a sound.
|
| 932 |
+
# true: Ring the terminal bell on each response
|
| 933 |
+
# false: Silent (default)
|
| 934 |
+
bell_on_complete: false
|
| 935 |
+
|
| 936 |
+
# Show model reasoning/thinking before each response.
|
| 937 |
+
# When enabled, a dim box shows the model's thought process above the response.
|
| 938 |
+
# Toggle at runtime with /reasoning show or /reasoning hide.
|
| 939 |
+
# true: Show the reasoning box
|
| 940 |
+
# false: Hide reasoning (default)
|
| 941 |
+
show_reasoning: false
|
| 942 |
+
|
| 943 |
+
# Stream tokens to the terminal as they arrive instead of waiting for the
|
| 944 |
+
# full response. The response box opens on first token and text appears
|
| 945 |
+
# line-by-line. Tool calls are still captured silently.
|
| 946 |
+
# true: Stream tokens as they arrive (default)
|
| 947 |
+
# false: Wait for the full response before rendering
|
| 948 |
+
streaming: true
|
| 949 |
+
|
| 950 |
+
# ───────────────────────────────────────────────────────────────────────────
|
| 951 |
+
# Skin / Theme
|
| 952 |
+
# ───────────────────────────────────────────────────────────────────────────
|
| 953 |
+
# Customize CLI visual appearance — banner colors, spinner faces, tool prefix,
|
| 954 |
+
# response box label, and branding text. Change at runtime with /skin <name>.
|
| 955 |
+
#
|
| 956 |
+
# Built-in skins:
|
| 957 |
+
# default — Classic Hermes gold/kawaii
|
| 958 |
+
# ares — Crimson/bronze war-god theme with spinner wings
|
| 959 |
+
# mono — Clean grayscale monochrome
|
| 960 |
+
# slate — Cool blue developer-focused
|
| 961 |
+
# daylight — Bright light-mode theme
|
| 962 |
+
# warm-lightmode — Warm paper-tone light-mode theme
|
| 963 |
+
# poseidon — Sea-green/teal Olympian theme
|
| 964 |
+
# sisyphus — Earthy stone-and-moss theme
|
| 965 |
+
# charizard — Fiery orange dragon theme
|
| 966 |
+
#
|
| 967 |
+
# Custom skins: drop a YAML file in ~/.hermes/skins/<name>.yaml
|
| 968 |
+
# Schema (all fields optional, missing values inherit from default):
|
| 969 |
+
#
|
| 970 |
+
# name: my-theme
|
| 971 |
+
# description: Short description
|
| 972 |
+
# colors:
|
| 973 |
+
# banner_border: "#HEX" # Panel border
|
| 974 |
+
# banner_title: "#HEX" # Panel title
|
| 975 |
+
# banner_accent: "#HEX" # Section headers (Available Tools, etc.)
|
| 976 |
+
# banner_dim: "#HEX" # Dim/muted text
|
| 977 |
+
# banner_text: "#HEX" # Body text (tool names, skill names)
|
| 978 |
+
# ui_accent: "#HEX" # UI accent color
|
| 979 |
+
# response_border: "#HEX" # Response box border color
|
| 980 |
+
# spinner:
|
| 981 |
+
# waiting_faces: ["(⚔)", "(⛨)"] # Faces shown while waiting
|
| 982 |
+
# thinking_faces: ["(⚔)", "(⌁)"] # Faces shown while thinking
|
| 983 |
+
# thinking_verbs: ["forging", "plotting"] # Verbs for spinner messages
|
| 984 |
+
# wings: # Optional left/right spinner decorations
|
| 985 |
+
# - ["⟪⚔", "⚔⟫"]
|
| 986 |
+
# - ["⟪▲", "▲⟫"]
|
| 987 |
+
# branding:
|
| 988 |
+
# agent_name: "My Agent" # Banner title and branding
|
| 989 |
+
# welcome: "Welcome message" # Shown at CLI startup
|
| 990 |
+
# response_label: " ⚔ Agent " # Response box header label
|
| 991 |
+
# prompt_symbol: "⚔" # Prompt symbol (bare token; renderers add trailing space)
|
| 992 |
+
# tool_prefix: "╎" # Tool output line prefix (default: ┊)
|
| 993 |
+
#
|
| 994 |
+
skin: default
|
| 995 |
+
|
| 996 |
+
# =============================================================================
|
| 997 |
+
# Model Aliases — short names for /model command
|
| 998 |
+
# =============================================================================
|
| 999 |
+
# Map short aliases to exact (model, provider, base_url) tuples.
|
| 1000 |
+
# Used by /model tab completion and resolve_alias().
|
| 1001 |
+
# Aliases are checked BEFORE the models.dev catalog, so they can route
|
| 1002 |
+
# to endpoints not in the catalog (e.g. Ollama Cloud, local servers).
|
| 1003 |
+
#
|
| 1004 |
+
# model_aliases:
|
| 1005 |
+
# opus:
|
| 1006 |
+
# model: claude-opus-4-6
|
| 1007 |
+
# provider: anthropic
|
| 1008 |
+
# qwen:
|
| 1009 |
+
# model: "qwen3.5:397b"
|
| 1010 |
+
# provider: custom
|
| 1011 |
+
# base_url: "https://ollama.com/v1"
|
| 1012 |
+
# glm:
|
| 1013 |
+
# model: glm-4.7
|
| 1014 |
+
# provider: custom
|
| 1015 |
+
# base_url: "https://ollama.com/v1"
|
| 1016 |
+
|
| 1017 |
+
# =============================================================================
|
| 1018 |
+
# Privacy
|
| 1019 |
+
# =============================================================================
|
| 1020 |
+
# privacy:
|
| 1021 |
+
# # Redact PII from the LLM context prompt.
|
| 1022 |
+
# # When true, phone numbers are stripped and user/chat IDs are replaced
|
| 1023 |
+
# # with deterministic hashes before being sent to the model.
|
| 1024 |
+
# # Names and usernames are NOT affected (user-chosen, publicly visible).
|
| 1025 |
+
# # Routing/delivery still uses the original values internally.
|
| 1026 |
+
# redact_pii: false
|
| 1027 |
+
|
| 1028 |
+
# =============================================================================
|
| 1029 |
+
# Shell-script hooks
|
| 1030 |
+
# =============================================================================
|
| 1031 |
+
# Register shell scripts as plugin-hook callbacks. Each entry is executed as
|
| 1032 |
+
# a subprocess (shell=False, shlex.split) with a JSON payload on stdin. On
|
| 1033 |
+
# stdout the script may return JSON that either blocks the tool call or
|
| 1034 |
+
# injects context into the next LLM call.
|
| 1035 |
+
#
|
| 1036 |
+
# Valid events (mirror hermes_cli.plugins.VALID_HOOKS):
|
| 1037 |
+
# pre_tool_call, post_tool_call, pre_llm_call, post_llm_call,
|
| 1038 |
+
# pre_api_request, post_api_request, on_session_start, on_session_end,
|
| 1039 |
+
# on_session_finalize, on_session_reset, subagent_stop
|
| 1040 |
+
#
|
| 1041 |
+
# First-use consent: each (event, command) pair prompts once on a TTY, then
|
| 1042 |
+
# is persisted to ~/.hermes/shell-hooks-allowlist.json. Non-interactive
|
| 1043 |
+
# runs (gateway, cron) need --accept-hooks, HERMES_ACCEPT_HOOKS=1, or the
|
| 1044 |
+
# hooks_auto_accept key below.
|
| 1045 |
+
#
|
| 1046 |
+
# See website/docs/user-guide/features/hooks.md for the full JSON wire
|
| 1047 |
+
# protocol and worked examples.
|
| 1048 |
+
#
|
| 1049 |
+
# hooks:
|
| 1050 |
+
# pre_tool_call:
|
| 1051 |
+
# - matcher: "terminal"
|
| 1052 |
+
# command: "~/.hermes/agent-hooks/block-rm-rf.sh"
|
| 1053 |
+
# timeout: 10
|
| 1054 |
+
# post_tool_call:
|
| 1055 |
+
# - matcher: "write_file|patch"
|
| 1056 |
+
# command: "~/.hermes/agent-hooks/auto-format.sh"
|
| 1057 |
+
# pre_llm_call:
|
| 1058 |
+
# - command: "~/.hermes/agent-hooks/inject-cwd-context.sh"
|
| 1059 |
+
# subagent_stop:
|
| 1060 |
+
# - command: "~/.hermes/agent-hooks/log-orchestration.sh"
|
| 1061 |
+
#
|
| 1062 |
+
# hooks_auto_accept: false
|
cli.py
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
constraints-termux.txt
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Termux / Android dependency constraints for Hermes Agent.
|
| 2 |
+
#
|
| 3 |
+
# Usage:
|
| 4 |
+
# python -m pip install -e '.[termux]' -c constraints-termux.txt
|
| 5 |
+
#
|
| 6 |
+
# These pins keep the tested Android install path stable when upstream packages
|
| 7 |
+
# move faster than Termux-compatible wheels / sdists.
|
| 8 |
+
|
| 9 |
+
ipython<10
|
| 10 |
+
jedi>=0.18.1,<0.20
|
| 11 |
+
parso>=0.8.4,<0.9
|
| 12 |
+
stack-data>=0.6,<0.7
|
| 13 |
+
pexpect>4.3,<5
|
| 14 |
+
matplotlib-inline>=0.1.7,<0.2
|
| 15 |
+
asttokens>=2.1,<3
|
docker-compose.yml
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#
|
| 2 |
+
# docker-compose.yml for Hermes Agent
|
| 3 |
+
#
|
| 4 |
+
# Usage:
|
| 5 |
+
# HERMES_UID=$(id -u) HERMES_GID=$(id -g) docker compose up -d
|
| 6 |
+
#
|
| 7 |
+
# Set HERMES_UID / HERMES_GID to the host user that owns ~/.hermes so
|
| 8 |
+
# files created inside the container stay readable/writable on the host.
|
| 9 |
+
# The entrypoint remaps the internal `hermes` user to these values via
|
| 10 |
+
# usermod/groupmod + gosu.
|
| 11 |
+
#
|
| 12 |
+
# Security notes:
|
| 13 |
+
# - The dashboard service binds to 127.0.0.1 by default. It stores API
|
| 14 |
+
# keys; exposing it on LAN without auth is unsafe. If you want remote
|
| 15 |
+
# access, use an SSH tunnel or put it behind a reverse proxy that
|
| 16 |
+
# adds authentication — do NOT pass --insecure --host 0.0.0.0.
|
| 17 |
+
# - If you override entrypoint, keep /opt/hermes/docker/entrypoint.sh in
|
| 18 |
+
# the command chain. It drops root to the hermes user before gateway
|
| 19 |
+
# files such as gateway.lock are created.
|
| 20 |
+
# - The gateway's API server is off unless you uncomment API_SERVER_KEY
|
| 21 |
+
# and API_SERVER_HOST. See docs/user-guide/api-server.md before doing
|
| 22 |
+
# this on an internet-facing host.
|
| 23 |
+
#
|
| 24 |
+
services:
|
| 25 |
+
gateway:
|
| 26 |
+
build: .
|
| 27 |
+
image: hermes-agent
|
| 28 |
+
container_name: hermes
|
| 29 |
+
restart: unless-stopped
|
| 30 |
+
network_mode: host
|
| 31 |
+
volumes:
|
| 32 |
+
- ~/.hermes:/opt/data
|
| 33 |
+
environment:
|
| 34 |
+
- HERMES_UID=${HERMES_UID:-10000}
|
| 35 |
+
- HERMES_GID=${HERMES_GID:-10000}
|
| 36 |
+
# To expose the OpenAI-compatible API server beyond localhost,
|
| 37 |
+
# uncomment BOTH lines (API_SERVER_KEY is mandatory for auth):
|
| 38 |
+
# - API_SERVER_HOST=0.0.0.0
|
| 39 |
+
# - API_SERVER_KEY=${API_SERVER_KEY}
|
| 40 |
+
# Microsoft Teams — uncomment and fill in to enable Teams gateway.
|
| 41 |
+
# Register your bot at https://dev.botframework.com/ to get these values.
|
| 42 |
+
# - TEAMS_CLIENT_ID=${TEAMS_CLIENT_ID}
|
| 43 |
+
# - TEAMS_CLIENT_SECRET=${TEAMS_CLIENT_SECRET}
|
| 44 |
+
# - TEAMS_TENANT_ID=${TEAMS_TENANT_ID}
|
| 45 |
+
# - TEAMS_ALLOWED_USERS=${TEAMS_ALLOWED_USERS}
|
| 46 |
+
# - TEAMS_PORT=${TEAMS_PORT:-3978}
|
| 47 |
+
# Google Chat — uncomment and fill in to enable the Google Chat gateway.
|
| 48 |
+
# See website/docs/user-guide/messaging/google_chat.md for the full setup.
|
| 49 |
+
# The SA JSON path must point to a file mounted into the container —
|
| 50 |
+
# add a volume entry above (e.g. ``- ~/.hermes/google-chat-sa.json:/secrets/google-chat-sa.json:ro``)
|
| 51 |
+
# then set GOOGLE_CHAT_SERVICE_ACCOUNT_JSON to that mount path.
|
| 52 |
+
# - GOOGLE_CHAT_PROJECT_ID=${GOOGLE_CHAT_PROJECT_ID}
|
| 53 |
+
# - GOOGLE_CHAT_SUBSCRIPTION_NAME=${GOOGLE_CHAT_SUBSCRIPTION_NAME}
|
| 54 |
+
# - GOOGLE_CHAT_SERVICE_ACCOUNT_JSON=${GOOGLE_CHAT_SERVICE_ACCOUNT_JSON}
|
| 55 |
+
# - GOOGLE_CHAT_ALLOWED_USERS=${GOOGLE_CHAT_ALLOWED_USERS}
|
| 56 |
+
command: ["gateway", "run"]
|
| 57 |
+
|
| 58 |
+
dashboard:
|
| 59 |
+
image: hermes-agent
|
| 60 |
+
container_name: hermes-dashboard
|
| 61 |
+
restart: unless-stopped
|
| 62 |
+
network_mode: host
|
| 63 |
+
depends_on:
|
| 64 |
+
- gateway
|
| 65 |
+
volumes:
|
| 66 |
+
- ~/.hermes:/opt/data
|
| 67 |
+
environment:
|
| 68 |
+
- HERMES_UID=${HERMES_UID:-10000}
|
| 69 |
+
- HERMES_GID=${HERMES_GID:-10000}
|
| 70 |
+
# Localhost-only. For remote access, tunnel via `ssh -L 9119:localhost:9119`.
|
| 71 |
+
command: ["dashboard", "--host", "127.0.0.1", "--no-open"]
|
flake.lock
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"nodes": {
|
| 3 |
+
"flake-parts": {
|
| 4 |
+
"inputs": {
|
| 5 |
+
"nixpkgs-lib": [
|
| 6 |
+
"nixpkgs"
|
| 7 |
+
]
|
| 8 |
+
},
|
| 9 |
+
"locked": {
|
| 10 |
+
"lastModified": 1772408722,
|
| 11 |
+
"narHash": "sha256-rHuJtdcOjK7rAHpHphUb1iCvgkU3GpfvicLMwwnfMT0=",
|
| 12 |
+
"owner": "hercules-ci",
|
| 13 |
+
"repo": "flake-parts",
|
| 14 |
+
"rev": "f20dc5d9b8027381c474144ecabc9034d6a839a3",
|
| 15 |
+
"type": "github"
|
| 16 |
+
},
|
| 17 |
+
"original": {
|
| 18 |
+
"owner": "hercules-ci",
|
| 19 |
+
"repo": "flake-parts",
|
| 20 |
+
"type": "github"
|
| 21 |
+
}
|
| 22 |
+
},
|
| 23 |
+
"nixpkgs": {
|
| 24 |
+
"locked": {
|
| 25 |
+
"lastModified": 1775036866,
|
| 26 |
+
"narHash": "sha256-ZojAnPuCdy657PbTq5V0Y+AHKhZAIwSIT2cb8UgAz/U=",
|
| 27 |
+
"owner": "NixOS",
|
| 28 |
+
"repo": "nixpkgs",
|
| 29 |
+
"rev": "6201e203d09599479a3b3450ed24fa81537ebc4e",
|
| 30 |
+
"type": "github"
|
| 31 |
+
},
|
| 32 |
+
"original": {
|
| 33 |
+
"owner": "NixOS",
|
| 34 |
+
"ref": "nixos-unstable",
|
| 35 |
+
"repo": "nixpkgs",
|
| 36 |
+
"type": "github"
|
| 37 |
+
}
|
| 38 |
+
},
|
| 39 |
+
"npm-lockfile-fix": {
|
| 40 |
+
"inputs": {
|
| 41 |
+
"nixpkgs": [
|
| 42 |
+
"nixpkgs"
|
| 43 |
+
]
|
| 44 |
+
},
|
| 45 |
+
"locked": {
|
| 46 |
+
"lastModified": 1775903712,
|
| 47 |
+
"narHash": "sha256-2GV79U6iVH4gKAPWYrxUReB0S41ty/Y3dBLquU8AlaA=",
|
| 48 |
+
"owner": "jeslie0",
|
| 49 |
+
"repo": "npm-lockfile-fix",
|
| 50 |
+
"rev": "c6093acb0c0548e0f9b8b3d82918823721930fe8",
|
| 51 |
+
"type": "github"
|
| 52 |
+
},
|
| 53 |
+
"original": {
|
| 54 |
+
"owner": "jeslie0",
|
| 55 |
+
"repo": "npm-lockfile-fix",
|
| 56 |
+
"type": "github"
|
| 57 |
+
}
|
| 58 |
+
},
|
| 59 |
+
"pyproject-build-systems": {
|
| 60 |
+
"inputs": {
|
| 61 |
+
"nixpkgs": [
|
| 62 |
+
"nixpkgs"
|
| 63 |
+
],
|
| 64 |
+
"pyproject-nix": "pyproject-nix",
|
| 65 |
+
"uv2nix": "uv2nix"
|
| 66 |
+
},
|
| 67 |
+
"locked": {
|
| 68 |
+
"lastModified": 1772555609,
|
| 69 |
+
"narHash": "sha256-3BA3HnUvJSbHJAlJj6XSy0Jmu7RyP2gyB/0fL7XuEDo=",
|
| 70 |
+
"owner": "pyproject-nix",
|
| 71 |
+
"repo": "build-system-pkgs",
|
| 72 |
+
"rev": "c37f66a953535c394244888598947679af231863",
|
| 73 |
+
"type": "github"
|
| 74 |
+
},
|
| 75 |
+
"original": {
|
| 76 |
+
"owner": "pyproject-nix",
|
| 77 |
+
"repo": "build-system-pkgs",
|
| 78 |
+
"type": "github"
|
| 79 |
+
}
|
| 80 |
+
},
|
| 81 |
+
"pyproject-nix": {
|
| 82 |
+
"inputs": {
|
| 83 |
+
"nixpkgs": [
|
| 84 |
+
"pyproject-build-systems",
|
| 85 |
+
"nixpkgs"
|
| 86 |
+
]
|
| 87 |
+
},
|
| 88 |
+
"locked": {
|
| 89 |
+
"lastModified": 1769936401,
|
| 90 |
+
"narHash": "sha256-kwCOegKLZJM9v/e/7cqwg1p/YjjTAukKPqmxKnAZRgA=",
|
| 91 |
+
"owner": "nix-community",
|
| 92 |
+
"repo": "pyproject.nix",
|
| 93 |
+
"rev": "b0d513eeeebed6d45b4f2e874f9afba2021f7812",
|
| 94 |
+
"type": "github"
|
| 95 |
+
},
|
| 96 |
+
"original": {
|
| 97 |
+
"owner": "nix-community",
|
| 98 |
+
"repo": "pyproject.nix",
|
| 99 |
+
"type": "github"
|
| 100 |
+
}
|
| 101 |
+
},
|
| 102 |
+
"pyproject-nix_2": {
|
| 103 |
+
"inputs": {
|
| 104 |
+
"nixpkgs": [
|
| 105 |
+
"nixpkgs"
|
| 106 |
+
]
|
| 107 |
+
},
|
| 108 |
+
"locked": {
|
| 109 |
+
"lastModified": 1772865871,
|
| 110 |
+
"narHash": "sha256-/ZTSg97aouL0SlPHaokA4r3iuH9QzHVuWPACD2CUCFY=",
|
| 111 |
+
"owner": "pyproject-nix",
|
| 112 |
+
"repo": "pyproject.nix",
|
| 113 |
+
"rev": "e537db02e72d553cea470976b9733581bcf5b3ed",
|
| 114 |
+
"type": "github"
|
| 115 |
+
},
|
| 116 |
+
"original": {
|
| 117 |
+
"owner": "pyproject-nix",
|
| 118 |
+
"repo": "pyproject.nix",
|
| 119 |
+
"type": "github"
|
| 120 |
+
}
|
| 121 |
+
},
|
| 122 |
+
"pyproject-nix_3": {
|
| 123 |
+
"inputs": {
|
| 124 |
+
"nixpkgs": [
|
| 125 |
+
"uv2nix",
|
| 126 |
+
"nixpkgs"
|
| 127 |
+
]
|
| 128 |
+
},
|
| 129 |
+
"locked": {
|
| 130 |
+
"lastModified": 1771518446,
|
| 131 |
+
"narHash": "sha256-nFJSfD89vWTu92KyuJWDoTQJuoDuddkJV3TlOl1cOic=",
|
| 132 |
+
"owner": "pyproject-nix",
|
| 133 |
+
"repo": "pyproject.nix",
|
| 134 |
+
"rev": "eb204c6b3335698dec6c7fc1da0ebc3c6df05937",
|
| 135 |
+
"type": "github"
|
| 136 |
+
},
|
| 137 |
+
"original": {
|
| 138 |
+
"owner": "pyproject-nix",
|
| 139 |
+
"repo": "pyproject.nix",
|
| 140 |
+
"type": "github"
|
| 141 |
+
}
|
| 142 |
+
},
|
| 143 |
+
"root": {
|
| 144 |
+
"inputs": {
|
| 145 |
+
"flake-parts": "flake-parts",
|
| 146 |
+
"nixpkgs": "nixpkgs",
|
| 147 |
+
"npm-lockfile-fix": "npm-lockfile-fix",
|
| 148 |
+
"pyproject-build-systems": "pyproject-build-systems",
|
| 149 |
+
"pyproject-nix": "pyproject-nix_2",
|
| 150 |
+
"uv2nix": "uv2nix_2"
|
| 151 |
+
}
|
| 152 |
+
},
|
| 153 |
+
"uv2nix": {
|
| 154 |
+
"inputs": {
|
| 155 |
+
"nixpkgs": [
|
| 156 |
+
"pyproject-build-systems",
|
| 157 |
+
"nixpkgs"
|
| 158 |
+
],
|
| 159 |
+
"pyproject-nix": [
|
| 160 |
+
"pyproject-build-systems",
|
| 161 |
+
"pyproject-nix"
|
| 162 |
+
]
|
| 163 |
+
},
|
| 164 |
+
"locked": {
|
| 165 |
+
"lastModified": 1770770348,
|
| 166 |
+
"narHash": "sha256-A2GzkmzdYvdgmMEu5yxW+xhossP+txrYb7RuzRaqhlg=",
|
| 167 |
+
"owner": "pyproject-nix",
|
| 168 |
+
"repo": "uv2nix",
|
| 169 |
+
"rev": "5d1b2cb4fe3158043fbafbbe2e46238abbc954b0",
|
| 170 |
+
"type": "github"
|
| 171 |
+
},
|
| 172 |
+
"original": {
|
| 173 |
+
"owner": "pyproject-nix",
|
| 174 |
+
"repo": "uv2nix",
|
| 175 |
+
"type": "github"
|
| 176 |
+
}
|
| 177 |
+
},
|
| 178 |
+
"uv2nix_2": {
|
| 179 |
+
"inputs": {
|
| 180 |
+
"nixpkgs": [
|
| 181 |
+
"nixpkgs"
|
| 182 |
+
],
|
| 183 |
+
"pyproject-nix": "pyproject-nix_3"
|
| 184 |
+
},
|
| 185 |
+
"locked": {
|
| 186 |
+
"lastModified": 1773039484,
|
| 187 |
+
"narHash": "sha256-+boo33KYkJDw9KItpeEXXv8+65f7hHv/earxpcyzQ0I=",
|
| 188 |
+
"owner": "pyproject-nix",
|
| 189 |
+
"repo": "uv2nix",
|
| 190 |
+
"rev": "b68be7cfeacbed9a3fa38a2b5adc0cfb81d9bb1f",
|
| 191 |
+
"type": "github"
|
| 192 |
+
},
|
| 193 |
+
"original": {
|
| 194 |
+
"owner": "pyproject-nix",
|
| 195 |
+
"repo": "uv2nix",
|
| 196 |
+
"type": "github"
|
| 197 |
+
}
|
| 198 |
+
}
|
| 199 |
+
},
|
| 200 |
+
"root": "root",
|
| 201 |
+
"version": 7
|
| 202 |
+
}
|
flake.nix
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
description = "Hermes Agent - AI agent framework by Nous Research";
|
| 3 |
+
|
| 4 |
+
inputs = {
|
| 5 |
+
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
| 6 |
+
flake-parts = {
|
| 7 |
+
url = "github:hercules-ci/flake-parts";
|
| 8 |
+
inputs.nixpkgs-lib.follows = "nixpkgs";
|
| 9 |
+
};
|
| 10 |
+
pyproject-nix = {
|
| 11 |
+
url = "github:pyproject-nix/pyproject.nix";
|
| 12 |
+
inputs.nixpkgs.follows = "nixpkgs";
|
| 13 |
+
};
|
| 14 |
+
uv2nix = {
|
| 15 |
+
url = "github:pyproject-nix/uv2nix";
|
| 16 |
+
inputs.nixpkgs.follows = "nixpkgs";
|
| 17 |
+
};
|
| 18 |
+
pyproject-build-systems = {
|
| 19 |
+
url = "github:pyproject-nix/build-system-pkgs";
|
| 20 |
+
inputs.nixpkgs.follows = "nixpkgs";
|
| 21 |
+
};
|
| 22 |
+
npm-lockfile-fix = {
|
| 23 |
+
url = "github:jeslie0/npm-lockfile-fix";
|
| 24 |
+
inputs.nixpkgs.follows = "nixpkgs";
|
| 25 |
+
};
|
| 26 |
+
};
|
| 27 |
+
|
| 28 |
+
outputs =
|
| 29 |
+
inputs:
|
| 30 |
+
inputs.flake-parts.lib.mkFlake { inherit inputs; } {
|
| 31 |
+
systems = [
|
| 32 |
+
"x86_64-linux"
|
| 33 |
+
"aarch64-linux"
|
| 34 |
+
"aarch64-darwin"
|
| 35 |
+
];
|
| 36 |
+
|
| 37 |
+
imports = [
|
| 38 |
+
./nix/packages.nix
|
| 39 |
+
./nix/overlays.nix
|
| 40 |
+
./nix/nixosModules.nix
|
| 41 |
+
./nix/checks.nix
|
| 42 |
+
./nix/devShell.nix
|
| 43 |
+
];
|
| 44 |
+
};
|
| 45 |
+
}
|
hermes
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Hermes Agent CLI launcher.
|
| 4 |
+
|
| 5 |
+
This wrapper should behave like the installed `hermes` command, including
|
| 6 |
+
subcommands such as `gateway`, `cron`, and `doctor`.
|
| 7 |
+
"""
|
| 8 |
+
|
| 9 |
+
if __name__ == "__main__":
|
| 10 |
+
from hermes_cli.main import main
|
| 11 |
+
main()
|
hermes-already-has-routines.md
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Hermes Agent Has Had "Routines" Since March
|
| 2 |
+
|
| 3 |
+
Anthropic just announced [Claude Code Routines](https://claude.com/blog/introducing-routines-in-claude-code) — scheduled tasks, GitHub event triggers, and API-triggered agent runs. Bundled prompt + repo + connectors, running on their infrastructure.
|
| 4 |
+
|
| 5 |
+
It's a good feature. We shipped it two months ago.
|
| 6 |
+
|
| 7 |
+
---
|
| 8 |
+
|
| 9 |
+
## The Three Trigger Types — Side by Side
|
| 10 |
+
|
| 11 |
+
Claude Code Routines offers three ways to trigger an automation:
|
| 12 |
+
|
| 13 |
+
**1. Scheduled (cron)**
|
| 14 |
+
> "Every night at 2am: pull the top bug from Linear, attempt a fix, and open a draft PR."
|
| 15 |
+
|
| 16 |
+
Hermes equivalent — works today:
|
| 17 |
+
```bash
|
| 18 |
+
hermes cron create "0 2 * * *" \
|
| 19 |
+
"Pull the top bug from the issue tracker, attempt a fix, and open a draft PR." \
|
| 20 |
+
--name "Nightly bug fix" \
|
| 21 |
+
--deliver telegram
|
| 22 |
+
```
|
| 23 |
+
|
| 24 |
+
**2. GitHub Events (webhook)**
|
| 25 |
+
> "Flag PRs that touch the /auth-provider module and post to #auth-changes."
|
| 26 |
+
|
| 27 |
+
Hermes equivalent — works today:
|
| 28 |
+
```bash
|
| 29 |
+
hermes webhook subscribe auth-watch \
|
| 30 |
+
--events "pull_request" \
|
| 31 |
+
--prompt "PR #{pull_request.number}: {pull_request.title} by {pull_request.user.login}. Check if it touches the auth-provider module. If yes, summarize the changes." \
|
| 32 |
+
--deliver slack
|
| 33 |
+
```
|
| 34 |
+
|
| 35 |
+
**3. API Triggers**
|
| 36 |
+
> "Read the alert payload, find the owning service, post a triage summary to #oncall."
|
| 37 |
+
|
| 38 |
+
Hermes equivalent — works today:
|
| 39 |
+
```bash
|
| 40 |
+
hermes webhook subscribe alert-triage \
|
| 41 |
+
--prompt "Alert: {alert.name} — Severity: {alert.severity}. Find the owning service, investigate, and post a triage summary with proposed first steps." \
|
| 42 |
+
--deliver slack
|
| 43 |
+
```
|
| 44 |
+
|
| 45 |
+
Every use case in their blog post — backlog triage, docs drift, deploy verification, alert correlation, library porting, bespoke PR review — has a working Hermes implementation. No new features needed. It's been shipping since March 2026.
|
| 46 |
+
|
| 47 |
+
---
|
| 48 |
+
|
| 49 |
+
## What's Different
|
| 50 |
+
|
| 51 |
+
| | Claude Code Routines | Hermes Agent |
|
| 52 |
+
|---|---|---|
|
| 53 |
+
| **Scheduled tasks** | ✅ Schedule-based | ✅ Any cron expression + human-readable intervals |
|
| 54 |
+
| **GitHub triggers** | ✅ PR, issue, push events | ✅ Any GitHub event via webhook subscriptions |
|
| 55 |
+
| **API triggers** | ✅ POST to unique endpoint | ✅ POST to webhook routes with HMAC auth |
|
| 56 |
+
| **MCP connectors** | ✅ Native connectors | ✅ Full MCP client support |
|
| 57 |
+
| **Script pre-processing** | ❌ | ✅ Python scripts run before agent, inject context |
|
| 58 |
+
| **Skill chaining** | ❌ | ✅ Load multiple skills per automation |
|
| 59 |
+
| **Daily limit** | 5-25 runs/day | **Unlimited** |
|
| 60 |
+
| **Model choice** | Claude only | **Any model** — Claude, GPT, Gemini, DeepSeek, Qwen, local |
|
| 61 |
+
| **Delivery targets** | GitHub comments | Telegram, Discord, Slack, SMS, email, GitHub comments, webhooks, local files |
|
| 62 |
+
| **Infrastructure** | Anthropic's servers | **Your infrastructure** — VPS, home server, laptop |
|
| 63 |
+
| **Data residency** | Anthropic's cloud | **Your machines** |
|
| 64 |
+
| **Cost** | Pro/Max/Team/Enterprise subscription | Your API key, your rates |
|
| 65 |
+
| **Open source** | No | **Yes** — MIT license |
|
| 66 |
+
|
| 67 |
+
---
|
| 68 |
+
|
| 69 |
+
## Things Hermes Does That Routines Can't
|
| 70 |
+
|
| 71 |
+
### Script Injection
|
| 72 |
+
|
| 73 |
+
Run a Python script *before* the agent. The script's stdout becomes context. The script handles mechanical work (fetching, diffing, computing); the agent handles reasoning.
|
| 74 |
+
|
| 75 |
+
```bash
|
| 76 |
+
hermes cron create "every 1h" \
|
| 77 |
+
"If CHANGE DETECTED, summarize what changed. If NO_CHANGE, respond with [SILENT]." \
|
| 78 |
+
--script ~/.hermes/scripts/watch-site.py \
|
| 79 |
+
--name "Pricing monitor" \
|
| 80 |
+
--deliver telegram
|
| 81 |
+
```
|
| 82 |
+
|
| 83 |
+
The `[SILENT]` pattern means you only get notified when something actually happens. No spam.
|
| 84 |
+
|
| 85 |
+
### Multi-Skill Workflows
|
| 86 |
+
|
| 87 |
+
Chain specialized skills together. Each skill teaches the agent a specific capability, and the prompt ties them together.
|
| 88 |
+
|
| 89 |
+
```bash
|
| 90 |
+
hermes cron create "0 8 * * *" \
|
| 91 |
+
"Search arXiv for papers on language model reasoning. Save the top 3 as Obsidian notes." \
|
| 92 |
+
--skills "arxiv,obsidian" \
|
| 93 |
+
--name "Paper digest"
|
| 94 |
+
```
|
| 95 |
+
|
| 96 |
+
### Deliver Anywhere
|
| 97 |
+
|
| 98 |
+
One automation, any destination:
|
| 99 |
+
|
| 100 |
+
```bash
|
| 101 |
+
--deliver telegram # Telegram home channel
|
| 102 |
+
--deliver discord # Discord home channel
|
| 103 |
+
--deliver slack # Slack channel
|
| 104 |
+
--deliver sms:+15551234567 # Text message
|
| 105 |
+
--deliver telegram:-1001234567890:42 # Specific Telegram forum topic
|
| 106 |
+
--deliver local # Save to file, no notification
|
| 107 |
+
```
|
| 108 |
+
|
| 109 |
+
### Model-Agnostic
|
| 110 |
+
|
| 111 |
+
Your nightly triage can run on Claude. Your deploy verification can run on GPT. Your cost-sensitive monitors can run on DeepSeek or a local model. Same automation system, any backend.
|
| 112 |
+
|
| 113 |
+
---
|
| 114 |
+
|
| 115 |
+
## The Limits Tell the Story
|
| 116 |
+
|
| 117 |
+
Claude Code Routines: **5 routines per day** on Pro. **25 on Enterprise.** That's their ceiling.
|
| 118 |
+
|
| 119 |
+
Hermes has no daily limit. Run 500 automations a day if you want. The only constraint is your API budget, and you choose which models to use for which tasks.
|
| 120 |
+
|
| 121 |
+
A nightly backlog triage on Sonnet costs roughly $0.02-0.05. A monitoring check on DeepSeek costs fractions of a cent. You control the economics.
|
| 122 |
+
|
| 123 |
+
---
|
| 124 |
+
|
| 125 |
+
## Get Started
|
| 126 |
+
|
| 127 |
+
Hermes Agent is open source and free. The automation infrastructure — cron scheduler, webhook platform, skill system, multi-platform delivery — is built in.
|
| 128 |
+
|
| 129 |
+
```bash
|
| 130 |
+
pip install hermes-agent
|
| 131 |
+
hermes setup
|
| 132 |
+
```
|
| 133 |
+
|
| 134 |
+
Set up a scheduled task in 30 seconds:
|
| 135 |
+
```bash
|
| 136 |
+
hermes cron create "0 9 * * 1" \
|
| 137 |
+
"Generate a weekly AI news digest. Search the web for major announcements, trending repos, and notable papers. Keep it under 500 words with links." \
|
| 138 |
+
--name "Weekly digest" \
|
| 139 |
+
--deliver telegram
|
| 140 |
+
```
|
| 141 |
+
|
| 142 |
+
Set up a GitHub webhook in 60 seconds:
|
| 143 |
+
```bash
|
| 144 |
+
hermes gateway setup # enable webhooks
|
| 145 |
+
hermes webhook subscribe pr-review \
|
| 146 |
+
--events "pull_request" \
|
| 147 |
+
--prompt "Review PR #{pull_request.number}: {pull_request.title}" \
|
| 148 |
+
--skills "github-code-review" \
|
| 149 |
+
--deliver github_comment
|
| 150 |
+
```
|
| 151 |
+
|
| 152 |
+
Full automation templates gallery: [hermes-agent.nousresearch.com/docs/guides/automation-templates](https://hermes-agent.nousresearch.com/docs/guides/automation-templates)
|
| 153 |
+
|
| 154 |
+
Documentation: [hermes-agent.nousresearch.com](https://hermes-agent.nousresearch.com)
|
| 155 |
+
|
| 156 |
+
GitHub: [github.com/NousResearch/hermes-agent](https://github.com/NousResearch/hermes-agent)
|
| 157 |
+
|
| 158 |
+
---
|
| 159 |
+
|
| 160 |
+
*Hermes Agent is built by [Nous Research](https://nousresearch.com). Open source, model-agnostic, runs on your infrastructure.*
|
hermes_bootstrap.py
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Windows UTF-8 bootstrap for Hermes entry points.
|
| 2 |
+
|
| 3 |
+
Python on Windows has two long-standing text-encoding footguns:
|
| 4 |
+
|
| 5 |
+
1. ``sys.stdout`` / ``sys.stderr`` are bound to the console code page
|
| 6 |
+
(``cp1252`` on US-locale installs), so ``print("café")`` crashes with
|
| 7 |
+
``UnicodeEncodeError: 'charmap' codec can't encode character``.
|
| 8 |
+
|
| 9 |
+
2. Child processes spawned via ``subprocess`` don't know to use UTF-8
|
| 10 |
+
unless ``PYTHONUTF8`` and/or ``PYTHONIOENCODING`` are set in their
|
| 11 |
+
environment — so any Python subprocess (the execute_code sandbox,
|
| 12 |
+
delegation children, linter subprocesses, etc.) inherits the same
|
| 13 |
+
cp1252 defaults and hits the same UnicodeEncodeError.
|
| 14 |
+
|
| 15 |
+
This module fixes both on Windows *only* — POSIX is untouched. It
|
| 16 |
+
should be imported at the very top of every Hermes entry point
|
| 17 |
+
(``hermes``, ``hermes-agent``, ``hermes-acp``, ``python -m gateway.run``,
|
| 18 |
+
``batch_runner.py``, ``cron/scheduler.py``) before any other imports
|
| 19 |
+
that might do file I/O or print to stdout.
|
| 20 |
+
|
| 21 |
+
What this module does on Windows:
|
| 22 |
+
|
| 23 |
+
- Sets ``os.environ["PYTHONUTF8"] = "1"`` (PEP 540 UTF-8 mode) so
|
| 24 |
+
every child process we spawn uses UTF-8 for ``open()`` and stdio.
|
| 25 |
+
- Sets ``os.environ["PYTHONIOENCODING"] = "utf-8"`` for belt-and-
|
| 26 |
+
suspenders — some tools read this instead of / in addition to
|
| 27 |
+
``PYTHONUTF8``.
|
| 28 |
+
- Reconfigures ``sys.stdout`` / ``sys.stderr`` to UTF-8 in the current
|
| 29 |
+
process, using the ``reconfigure()`` API (Python 3.7+). This fixes
|
| 30 |
+
``print("café")`` in the parent without a re-exec.
|
| 31 |
+
|
| 32 |
+
What this module does NOT do:
|
| 33 |
+
|
| 34 |
+
- It does not re-exec Python with ``-X utf8``, so ``open()`` calls in
|
| 35 |
+
the *current* process still default to locale encoding. Those need
|
| 36 |
+
an explicit ``encoding="utf-8"`` at the call site (lint rule
|
| 37 |
+
``PLW1514`` / ``PYI058``). Ruff is the right tool for that sweep.
|
| 38 |
+
|
| 39 |
+
What this module does on POSIX:
|
| 40 |
+
|
| 41 |
+
- Nothing. POSIX systems are already UTF-8 by default in 99% of cases,
|
| 42 |
+
and we don't want to touch ``LANG``/``LC_*`` behavior that users may
|
| 43 |
+
have configured intentionally. If someone hits a C/POSIX locale on
|
| 44 |
+
Linux, they can export ``PYTHONUTF8=1`` themselves — we won't override.
|
| 45 |
+
|
| 46 |
+
Idempotent: safe to call multiple times. ``_bootstrap_once`` guards
|
| 47 |
+
against double-reconfigure.
|
| 48 |
+
"""
|
| 49 |
+
|
| 50 |
+
from __future__ import annotations
|
| 51 |
+
|
| 52 |
+
import os
|
| 53 |
+
import sys
|
| 54 |
+
|
| 55 |
+
_IS_WINDOWS = sys.platform == "win32"
|
| 56 |
+
_bootstrap_applied = False
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
def apply_windows_utf8_bootstrap() -> bool:
|
| 60 |
+
"""Apply the Windows UTF-8 bootstrap if we're on Windows.
|
| 61 |
+
|
| 62 |
+
Returns True if bootstrap was applied (i.e. we're on Windows and
|
| 63 |
+
haven't already done this), False otherwise. The return value is
|
| 64 |
+
advisory — callers normally don't need it, but tests may want to
|
| 65 |
+
assert the path was taken.
|
| 66 |
+
|
| 67 |
+
Idempotent: subsequent calls after the first are a no-op.
|
| 68 |
+
"""
|
| 69 |
+
global _bootstrap_applied
|
| 70 |
+
|
| 71 |
+
if not _IS_WINDOWS:
|
| 72 |
+
return False
|
| 73 |
+
if _bootstrap_applied:
|
| 74 |
+
return False
|
| 75 |
+
|
| 76 |
+
# 1. Child processes inherit these and run in UTF-8 mode.
|
| 77 |
+
# We use setdefault() rather than overwriting so the user can
|
| 78 |
+
# explicitly opt out by setting PYTHONUTF8=0 in their environment
|
| 79 |
+
# (or PYTHONIOENCODING=something-else) if they really want to.
|
| 80 |
+
os.environ.setdefault("PYTHONUTF8", "1")
|
| 81 |
+
os.environ.setdefault("PYTHONIOENCODING", "utf-8")
|
| 82 |
+
|
| 83 |
+
# 2. Reconfigure the current process's stdio to UTF-8. Needed
|
| 84 |
+
# because os.environ changes don't retroactively rebind sys.stdout
|
| 85 |
+
# — those were bound at interpreter startup based on the console
|
| 86 |
+
# code page. ``reconfigure`` is a TextIOWrapper method since 3.7.
|
| 87 |
+
#
|
| 88 |
+
# errors="replace" means that if we ever *read* something from
|
| 89 |
+
# stdin that isn't UTF-8 (unlikely but possible with piped input
|
| 90 |
+
# from legacy tools), we'll get U+FFFD replacement chars rather
|
| 91 |
+
# than a crash. Output is pure UTF-8.
|
| 92 |
+
for stream_name in ("stdout", "stderr"):
|
| 93 |
+
stream = getattr(sys, stream_name, None)
|
| 94 |
+
if stream is None:
|
| 95 |
+
continue
|
| 96 |
+
reconfigure = getattr(stream, "reconfigure", None)
|
| 97 |
+
if reconfigure is None:
|
| 98 |
+
# Not a TextIOWrapper (could be redirected to a BytesIO in
|
| 99 |
+
# tests, or a non-standard stream in some embedded cases).
|
| 100 |
+
# Skip silently — the env-var fix is still in effect for
|
| 101 |
+
# child processes, which is the bigger win.
|
| 102 |
+
continue
|
| 103 |
+
try:
|
| 104 |
+
reconfigure(encoding="utf-8", errors="replace")
|
| 105 |
+
except (OSError, ValueError):
|
| 106 |
+
# Already closed, or someone replaced it with something
|
| 107 |
+
# non-reconfigurable. Non-fatal.
|
| 108 |
+
pass
|
| 109 |
+
|
| 110 |
+
# stdin is reconfigured separately with errors="replace" too — input
|
| 111 |
+
# from a legacy pipe shouldn't crash the process.
|
| 112 |
+
stdin = getattr(sys, "stdin", None)
|
| 113 |
+
if stdin is not None:
|
| 114 |
+
reconfigure = getattr(stdin, "reconfigure", None)
|
| 115 |
+
if reconfigure is not None:
|
| 116 |
+
try:
|
| 117 |
+
reconfigure(encoding="utf-8", errors="replace")
|
| 118 |
+
except (OSError, ValueError):
|
| 119 |
+
pass
|
| 120 |
+
|
| 121 |
+
_bootstrap_applied = True
|
| 122 |
+
return True
|
| 123 |
+
|
| 124 |
+
|
| 125 |
+
# Apply on import — entry points just need ``import hermes_bootstrap``
|
| 126 |
+
# (or ``from hermes_bootstrap import apply_windows_utf8_bootstrap``) at
|
| 127 |
+
# the very top of their module, before importing anything else. The
|
| 128 |
+
# import side effect does the right thing.
|
| 129 |
+
apply_windows_utf8_bootstrap()
|
hermes_constants.py
ADDED
|
@@ -0,0 +1,345 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Shared constants for Hermes Agent.
|
| 2 |
+
|
| 3 |
+
Import-safe module with no dependencies — can be imported from anywhere
|
| 4 |
+
without risk of circular imports.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import os
|
| 8 |
+
from pathlib import Path
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
_profile_fallback_warned: bool = False
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def get_hermes_home() -> Path:
|
| 15 |
+
"""Return the Hermes home directory (default: ~/.hermes).
|
| 16 |
+
|
| 17 |
+
Reads HERMES_HOME env var, falls back to ~/.hermes.
|
| 18 |
+
This is the single source of truth — all other copies should import this.
|
| 19 |
+
|
| 20 |
+
When ``HERMES_HOME`` is unset but an ``active_profile`` file indicates
|
| 21 |
+
a non-default profile is active, logs a loud one-shot warning to
|
| 22 |
+
``errors.log`` so cross-profile data corruption is diagnosable instead
|
| 23 |
+
of silent. Behavior is unchanged otherwise — we still return
|
| 24 |
+
``~/.hermes`` — because raising here would brick 30+ module-level
|
| 25 |
+
callers that import this at load time. Subprocess spawners are
|
| 26 |
+
expected to propagate ``HERMES_HOME`` explicitly (see the systemd
|
| 27 |
+
template in ``hermes_cli/gateway.py`` and the kanban dispatcher in
|
| 28 |
+
``hermes_cli/kanban_db.py``). See https://github.com/NousResearch/hermes-agent/issues/18594.
|
| 29 |
+
"""
|
| 30 |
+
val = os.environ.get("HERMES_HOME", "").strip()
|
| 31 |
+
if val:
|
| 32 |
+
return Path(val)
|
| 33 |
+
|
| 34 |
+
# Guard: if a non-default profile is sticky-active, warn once that
|
| 35 |
+
# the fallback to the default profile is almost certainly wrong.
|
| 36 |
+
global _profile_fallback_warned
|
| 37 |
+
if not _profile_fallback_warned:
|
| 38 |
+
try:
|
| 39 |
+
# Inline the default-root resolution from get_default_hermes_root()
|
| 40 |
+
# to stay import-safe (this function is called from module scope
|
| 41 |
+
# in 30+ files; we cannot afford to trigger logging setup here).
|
| 42 |
+
active_path = (Path.home() / ".hermes" / "active_profile")
|
| 43 |
+
active = active_path.read_text().strip() if active_path.exists() else ""
|
| 44 |
+
except (UnicodeDecodeError, OSError):
|
| 45 |
+
active = ""
|
| 46 |
+
if active and active != "default":
|
| 47 |
+
_profile_fallback_warned = True
|
| 48 |
+
# Write directly to stderr. We intentionally do NOT route this
|
| 49 |
+
# through ``logging`` because (a) this function is called at
|
| 50 |
+
# module-import time from 30+ sites, often before logging is
|
| 51 |
+
# configured, and (b) root-logger propagation would double-emit
|
| 52 |
+
# on consoles where a StreamHandler is already attached.
|
| 53 |
+
import sys
|
| 54 |
+
msg = (
|
| 55 |
+
f"[HERMES_HOME fallback] HERMES_HOME is unset but active "
|
| 56 |
+
f"profile is {active!r}. Falling back to ~/.hermes, which "
|
| 57 |
+
f"is the DEFAULT profile — not {active!r}. Any data this "
|
| 58 |
+
f"process writes will land in the wrong profile. The "
|
| 59 |
+
f"subprocess spawner should pass HERMES_HOME explicitly "
|
| 60 |
+
f"(see issue #18594)."
|
| 61 |
+
)
|
| 62 |
+
try:
|
| 63 |
+
sys.stderr.write(msg + "\n")
|
| 64 |
+
sys.stderr.flush()
|
| 65 |
+
except Exception:
|
| 66 |
+
pass
|
| 67 |
+
|
| 68 |
+
return Path.home() / ".hermes"
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
def get_default_hermes_root() -> Path:
|
| 72 |
+
"""Return the root Hermes directory for profile-level operations.
|
| 73 |
+
|
| 74 |
+
In standard deployments this is ``~/.hermes``.
|
| 75 |
+
|
| 76 |
+
In Docker or custom deployments where ``HERMES_HOME`` points outside
|
| 77 |
+
``~/.hermes`` (e.g. ``/opt/data``), returns ``HERMES_HOME`` directly
|
| 78 |
+
— that IS the root.
|
| 79 |
+
|
| 80 |
+
In profile mode where ``HERMES_HOME`` is ``<root>/profiles/<name>``,
|
| 81 |
+
returns ``<root>`` so that ``profile list`` can see all profiles.
|
| 82 |
+
Works both for standard (``~/.hermes/profiles/coder``) and Docker
|
| 83 |
+
(``/opt/data/profiles/coder``) layouts.
|
| 84 |
+
|
| 85 |
+
Import-safe — no dependencies beyond stdlib.
|
| 86 |
+
"""
|
| 87 |
+
native_home = Path.home() / ".hermes"
|
| 88 |
+
env_home = os.environ.get("HERMES_HOME", "")
|
| 89 |
+
if not env_home:
|
| 90 |
+
return native_home
|
| 91 |
+
env_path = Path(env_home)
|
| 92 |
+
try:
|
| 93 |
+
env_path.resolve().relative_to(native_home.resolve())
|
| 94 |
+
# HERMES_HOME is under ~/.hermes (normal or profile mode)
|
| 95 |
+
return native_home
|
| 96 |
+
except ValueError:
|
| 97 |
+
pass
|
| 98 |
+
|
| 99 |
+
# Docker / custom deployment.
|
| 100 |
+
# Check if this is a profile path: <root>/profiles/<name>
|
| 101 |
+
# If the immediate parent dir is named "profiles", the root is
|
| 102 |
+
# the grandparent — this covers Docker profiles correctly.
|
| 103 |
+
if env_path.parent.name == "profiles":
|
| 104 |
+
return env_path.parent.parent
|
| 105 |
+
|
| 106 |
+
# Not a profile path — HERMES_HOME itself is the root
|
| 107 |
+
return env_path
|
| 108 |
+
|
| 109 |
+
|
| 110 |
+
def get_optional_skills_dir(default: Path | None = None) -> Path:
|
| 111 |
+
"""Return the optional-skills directory, honoring package-manager wrappers.
|
| 112 |
+
|
| 113 |
+
Packaged installs may ship ``optional-skills`` outside the Python package
|
| 114 |
+
tree and expose it via ``HERMES_OPTIONAL_SKILLS``.
|
| 115 |
+
"""
|
| 116 |
+
override = os.getenv("HERMES_OPTIONAL_SKILLS", "").strip()
|
| 117 |
+
if override:
|
| 118 |
+
return Path(override)
|
| 119 |
+
if default is not None:
|
| 120 |
+
return default
|
| 121 |
+
return get_hermes_home() / "optional-skills"
|
| 122 |
+
|
| 123 |
+
|
| 124 |
+
def get_hermes_dir(new_subpath: str, old_name: str) -> Path:
|
| 125 |
+
"""Resolve a Hermes subdirectory with backward compatibility.
|
| 126 |
+
|
| 127 |
+
New installs get the consolidated layout (e.g. ``cache/images``).
|
| 128 |
+
Existing installs that already have the old path (e.g. ``image_cache``)
|
| 129 |
+
keep using it — no migration required.
|
| 130 |
+
|
| 131 |
+
Args:
|
| 132 |
+
new_subpath: Preferred path relative to HERMES_HOME (e.g. ``"cache/images"``).
|
| 133 |
+
old_name: Legacy path relative to HERMES_HOME (e.g. ``"image_cache"``).
|
| 134 |
+
|
| 135 |
+
Returns:
|
| 136 |
+
Absolute ``Path`` — old location if it exists on disk, otherwise the new one.
|
| 137 |
+
"""
|
| 138 |
+
home = get_hermes_home()
|
| 139 |
+
old_path = home / old_name
|
| 140 |
+
if old_path.exists():
|
| 141 |
+
return old_path
|
| 142 |
+
return home / new_subpath
|
| 143 |
+
|
| 144 |
+
|
| 145 |
+
def display_hermes_home() -> str:
|
| 146 |
+
"""Return a user-friendly display string for the current HERMES_HOME.
|
| 147 |
+
|
| 148 |
+
Uses ``~/`` shorthand for readability::
|
| 149 |
+
|
| 150 |
+
default: ``~/.hermes``
|
| 151 |
+
profile: ``~/.hermes/profiles/coder``
|
| 152 |
+
custom: ``/opt/hermes-custom``
|
| 153 |
+
|
| 154 |
+
Use this in **user-facing** print/log messages instead of hardcoding
|
| 155 |
+
``~/.hermes``. For code that needs a real ``Path``, use
|
| 156 |
+
:func:`get_hermes_home` instead.
|
| 157 |
+
"""
|
| 158 |
+
home = get_hermes_home()
|
| 159 |
+
try:
|
| 160 |
+
return "~/" + str(home.relative_to(Path.home()))
|
| 161 |
+
except ValueError:
|
| 162 |
+
return str(home)
|
| 163 |
+
|
| 164 |
+
|
| 165 |
+
def get_subprocess_home() -> str | None:
|
| 166 |
+
"""Return a per-profile HOME directory for subprocesses, or None.
|
| 167 |
+
|
| 168 |
+
When ``{HERMES_HOME}/home/`` exists on disk, subprocesses should use it
|
| 169 |
+
as ``HOME`` so system tools (git, ssh, gh, npm …) write their configs
|
| 170 |
+
inside the Hermes data directory instead of the OS-level ``/root`` or
|
| 171 |
+
``~/``. This provides:
|
| 172 |
+
|
| 173 |
+
* **Docker persistence** — tool configs land inside the persistent volume.
|
| 174 |
+
* **Profile isolation** — each profile gets its own git identity, SSH
|
| 175 |
+
keys, gh tokens, etc.
|
| 176 |
+
|
| 177 |
+
The Python process's own ``os.environ["HOME"]`` and ``Path.home()`` are
|
| 178 |
+
**never** modified — only subprocess environments should inject this value.
|
| 179 |
+
Activation is directory-based: if the ``home/`` subdirectory doesn't
|
| 180 |
+
exist, returns ``None`` and behavior is unchanged.
|
| 181 |
+
"""
|
| 182 |
+
hermes_home = os.getenv("HERMES_HOME")
|
| 183 |
+
if not hermes_home:
|
| 184 |
+
return None
|
| 185 |
+
profile_home = os.path.join(hermes_home, "home")
|
| 186 |
+
if os.path.isdir(profile_home):
|
| 187 |
+
return profile_home
|
| 188 |
+
return None
|
| 189 |
+
|
| 190 |
+
|
| 191 |
+
VALID_REASONING_EFFORTS = ("minimal", "low", "medium", "high", "xhigh")
|
| 192 |
+
|
| 193 |
+
|
| 194 |
+
def parse_reasoning_effort(effort: str) -> dict | None:
|
| 195 |
+
"""Parse a reasoning effort level into a config dict.
|
| 196 |
+
|
| 197 |
+
Valid levels: "none", "minimal", "low", "medium", "high", "xhigh".
|
| 198 |
+
Returns None when the input is empty or unrecognized (caller uses default).
|
| 199 |
+
Returns {"enabled": False} for "none".
|
| 200 |
+
Returns {"enabled": True, "effort": <level>} for valid effort levels.
|
| 201 |
+
"""
|
| 202 |
+
if not effort or not effort.strip():
|
| 203 |
+
return None
|
| 204 |
+
effort = effort.strip().lower()
|
| 205 |
+
if effort == "none":
|
| 206 |
+
return {"enabled": False}
|
| 207 |
+
if effort in VALID_REASONING_EFFORTS:
|
| 208 |
+
return {"enabled": True, "effort": effort}
|
| 209 |
+
return None
|
| 210 |
+
|
| 211 |
+
|
| 212 |
+
def is_termux() -> bool:
|
| 213 |
+
"""Return True when running inside a Termux (Android) environment.
|
| 214 |
+
|
| 215 |
+
Checks ``TERMUX_VERSION`` (set by Termux) or the Termux-specific
|
| 216 |
+
``PREFIX`` path. Import-safe — no heavy deps.
|
| 217 |
+
"""
|
| 218 |
+
prefix = os.getenv("PREFIX", "")
|
| 219 |
+
return bool(os.getenv("TERMUX_VERSION") or "com.termux/files/usr" in prefix)
|
| 220 |
+
|
| 221 |
+
|
| 222 |
+
_wsl_detected: bool | None = None
|
| 223 |
+
|
| 224 |
+
|
| 225 |
+
def is_wsl() -> bool:
|
| 226 |
+
"""Return True when running inside WSL (Windows Subsystem for Linux).
|
| 227 |
+
|
| 228 |
+
Checks ``/proc/version`` for the ``microsoft`` marker that both WSL1
|
| 229 |
+
and WSL2 inject. Result is cached for the process lifetime.
|
| 230 |
+
Import-safe — no heavy deps.
|
| 231 |
+
"""
|
| 232 |
+
global _wsl_detected
|
| 233 |
+
if _wsl_detected is not None:
|
| 234 |
+
return _wsl_detected
|
| 235 |
+
try:
|
| 236 |
+
with open("/proc/version", "r", encoding="utf-8") as f:
|
| 237 |
+
_wsl_detected = "microsoft" in f.read().lower()
|
| 238 |
+
except Exception:
|
| 239 |
+
_wsl_detected = False
|
| 240 |
+
return _wsl_detected
|
| 241 |
+
|
| 242 |
+
|
| 243 |
+
_container_detected: bool | None = None
|
| 244 |
+
|
| 245 |
+
|
| 246 |
+
def is_container() -> bool:
|
| 247 |
+
"""Return True when running inside a Docker/Podman container.
|
| 248 |
+
|
| 249 |
+
Checks ``/.dockerenv`` (Docker), ``/run/.containerenv`` (Podman),
|
| 250 |
+
and ``/proc/1/cgroup`` for container runtime markers. Result is
|
| 251 |
+
cached for the process lifetime. Import-safe — no heavy deps.
|
| 252 |
+
"""
|
| 253 |
+
global _container_detected
|
| 254 |
+
if _container_detected is not None:
|
| 255 |
+
return _container_detected
|
| 256 |
+
if os.path.exists("/.dockerenv"):
|
| 257 |
+
_container_detected = True
|
| 258 |
+
return True
|
| 259 |
+
if os.path.exists("/run/.containerenv"):
|
| 260 |
+
_container_detected = True
|
| 261 |
+
return True
|
| 262 |
+
try:
|
| 263 |
+
with open("/proc/1/cgroup", "r", encoding="utf-8") as f:
|
| 264 |
+
cgroup = f.read()
|
| 265 |
+
if "docker" in cgroup or "podman" in cgroup or "/lxc/" in cgroup:
|
| 266 |
+
_container_detected = True
|
| 267 |
+
return True
|
| 268 |
+
except OSError:
|
| 269 |
+
pass
|
| 270 |
+
_container_detected = False
|
| 271 |
+
return False
|
| 272 |
+
|
| 273 |
+
|
| 274 |
+
# ─── Well-Known Paths ─────────────────────────────────────────────────────────
|
| 275 |
+
|
| 276 |
+
|
| 277 |
+
def get_config_path() -> Path:
|
| 278 |
+
"""Return the path to ``config.yaml`` under HERMES_HOME.
|
| 279 |
+
|
| 280 |
+
Replaces the ``get_hermes_home() / "config.yaml"`` pattern repeated
|
| 281 |
+
in 7+ files (skill_utils.py, hermes_logging.py, hermes_time.py, etc.).
|
| 282 |
+
"""
|
| 283 |
+
return get_hermes_home() / "config.yaml"
|
| 284 |
+
|
| 285 |
+
|
| 286 |
+
def get_skills_dir() -> Path:
|
| 287 |
+
"""Return the path to the skills directory under HERMES_HOME."""
|
| 288 |
+
return get_hermes_home() / "skills"
|
| 289 |
+
|
| 290 |
+
|
| 291 |
+
|
| 292 |
+
def get_env_path() -> Path:
|
| 293 |
+
"""Return the path to the ``.env`` file under HERMES_HOME."""
|
| 294 |
+
return get_hermes_home() / ".env"
|
| 295 |
+
|
| 296 |
+
|
| 297 |
+
# ─── Network Preferences ─────────────────────────────────────────────────────
|
| 298 |
+
|
| 299 |
+
|
| 300 |
+
def apply_ipv4_preference(force: bool = False) -> None:
|
| 301 |
+
"""Monkey-patch ``socket.getaddrinfo`` to prefer IPv4 connections.
|
| 302 |
+
|
| 303 |
+
On servers with broken or unreachable IPv6, Python tries AAAA records
|
| 304 |
+
first and hangs for the full TCP timeout before falling back to IPv4.
|
| 305 |
+
This affects httpx, requests, urllib, the OpenAI SDK — everything that
|
| 306 |
+
uses ``socket.getaddrinfo``.
|
| 307 |
+
|
| 308 |
+
When *force* is True, patches ``getaddrinfo`` so that calls with
|
| 309 |
+
``family=AF_UNSPEC`` (the default) resolve as ``AF_INET`` instead,
|
| 310 |
+
skipping IPv6 entirely. If no A record exists, falls back to the
|
| 311 |
+
original unfiltered resolution so pure-IPv6 hosts still work.
|
| 312 |
+
|
| 313 |
+
Safe to call multiple times — only patches once.
|
| 314 |
+
Set ``network.force_ipv4: true`` in ``config.yaml`` to enable.
|
| 315 |
+
"""
|
| 316 |
+
if not force:
|
| 317 |
+
return
|
| 318 |
+
|
| 319 |
+
import socket
|
| 320 |
+
|
| 321 |
+
# Guard against double-patching
|
| 322 |
+
if getattr(socket.getaddrinfo, "_hermes_ipv4_patched", False):
|
| 323 |
+
return
|
| 324 |
+
|
| 325 |
+
_original_getaddrinfo = socket.getaddrinfo
|
| 326 |
+
|
| 327 |
+
def _ipv4_getaddrinfo(host, port, family=0, type=0, proto=0, flags=0):
|
| 328 |
+
if family == 0: # AF_UNSPEC — caller didn't request a specific family
|
| 329 |
+
try:
|
| 330 |
+
return _original_getaddrinfo(
|
| 331 |
+
host, port, socket.AF_INET, type, proto, flags
|
| 332 |
+
)
|
| 333 |
+
except socket.gaierror:
|
| 334 |
+
# No A record — fall back to full resolution (pure-IPv6 hosts)
|
| 335 |
+
return _original_getaddrinfo(host, port, family, type, proto, flags)
|
| 336 |
+
return _original_getaddrinfo(host, port, family, type, proto, flags)
|
| 337 |
+
|
| 338 |
+
_ipv4_getaddrinfo._hermes_ipv4_patched = True # type: ignore[attr-defined]
|
| 339 |
+
socket.getaddrinfo = _ipv4_getaddrinfo # type: ignore[assignment]
|
| 340 |
+
|
| 341 |
+
|
| 342 |
+
OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1"
|
| 343 |
+
OPENROUTER_MODELS_URL = f"{OPENROUTER_BASE_URL}/models"
|
| 344 |
+
|
| 345 |
+
AI_GATEWAY_BASE_URL = "https://ai-gateway.vercel.sh/v1"
|
hermes_logging.py
ADDED
|
@@ -0,0 +1,389 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Centralized logging setup for Hermes Agent.
|
| 2 |
+
|
| 3 |
+
Provides a single ``setup_logging()`` entry point that both the CLI and
|
| 4 |
+
gateway call early in their startup path. All log files live under
|
| 5 |
+
``~/.hermes/logs/`` (profile-aware via ``get_hermes_home()``).
|
| 6 |
+
|
| 7 |
+
Log files produced:
|
| 8 |
+
agent.log — INFO+, all agent/tool/session activity (the main log)
|
| 9 |
+
errors.log — WARNING+, errors and warnings only (quick triage)
|
| 10 |
+
gateway.log — INFO+, gateway-only events (created when mode="gateway")
|
| 11 |
+
|
| 12 |
+
All files use ``RotatingFileHandler`` with ``RedactingFormatter`` so
|
| 13 |
+
secrets are never written to disk.
|
| 14 |
+
|
| 15 |
+
Component separation:
|
| 16 |
+
gateway.log only receives records from ``gateway.*`` loggers —
|
| 17 |
+
platform adapters, session management, slash commands, delivery.
|
| 18 |
+
agent.log remains the catch-all (everything goes there).
|
| 19 |
+
|
| 20 |
+
Session context:
|
| 21 |
+
Call ``set_session_context(session_id)`` at the start of a conversation
|
| 22 |
+
and ``clear_session_context()`` when done. All log lines emitted on
|
| 23 |
+
that thread will include ``[session_id]`` for filtering/correlation.
|
| 24 |
+
"""
|
| 25 |
+
|
| 26 |
+
import logging
|
| 27 |
+
import os
|
| 28 |
+
import threading
|
| 29 |
+
from logging.handlers import RotatingFileHandler
|
| 30 |
+
from pathlib import Path
|
| 31 |
+
from typing import Optional, Sequence
|
| 32 |
+
|
| 33 |
+
from hermes_constants import get_config_path, get_hermes_home
|
| 34 |
+
|
| 35 |
+
# Sentinel to track whether setup_logging() has already run. The function
|
| 36 |
+
# is idempotent — calling it twice is safe but the second call is a no-op
|
| 37 |
+
# unless ``force=True``.
|
| 38 |
+
_logging_initialized = False
|
| 39 |
+
|
| 40 |
+
# Thread-local storage for per-conversation session context.
|
| 41 |
+
_session_context = threading.local()
|
| 42 |
+
|
| 43 |
+
# Default log format — includes timestamp, level, optional session tag,
|
| 44 |
+
# logger name, and message. The ``%(session_tag)s`` field is guaranteed to
|
| 45 |
+
# exist on every LogRecord via _install_session_record_factory() below.
|
| 46 |
+
_LOG_FORMAT = "%(asctime)s %(levelname)s%(session_tag)s %(name)s: %(message)s"
|
| 47 |
+
_LOG_FORMAT_VERBOSE = "%(asctime)s - %(name)s - %(levelname)s%(session_tag)s - %(message)s"
|
| 48 |
+
|
| 49 |
+
# Third-party loggers that are noisy at DEBUG/INFO level.
|
| 50 |
+
_NOISY_LOGGERS = (
|
| 51 |
+
"openai",
|
| 52 |
+
"openai._base_client",
|
| 53 |
+
"httpx",
|
| 54 |
+
"httpcore",
|
| 55 |
+
"asyncio",
|
| 56 |
+
"hpack",
|
| 57 |
+
"hpack.hpack",
|
| 58 |
+
"grpc",
|
| 59 |
+
"modal",
|
| 60 |
+
"urllib3",
|
| 61 |
+
"urllib3.connectionpool",
|
| 62 |
+
"websockets",
|
| 63 |
+
"charset_normalizer",
|
| 64 |
+
"markdown_it",
|
| 65 |
+
)
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
# ---------------------------------------------------------------------------
|
| 69 |
+
# Public session context API
|
| 70 |
+
# ---------------------------------------------------------------------------
|
| 71 |
+
|
| 72 |
+
def set_session_context(session_id: str) -> None:
|
| 73 |
+
"""Set the session ID for the current thread.
|
| 74 |
+
|
| 75 |
+
All subsequent log records on this thread will include ``[session_id]``
|
| 76 |
+
in the formatted output. Call at the start of ``run_conversation()``.
|
| 77 |
+
"""
|
| 78 |
+
_session_context.session_id = session_id
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
def clear_session_context() -> None:
|
| 82 |
+
"""Clear the session ID for the current thread."""
|
| 83 |
+
_session_context.session_id = None
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
# ---------------------------------------------------------------------------
|
| 87 |
+
# Record factory — injects session_tag into every LogRecord at creation
|
| 88 |
+
# ---------------------------------------------------------------------------
|
| 89 |
+
|
| 90 |
+
def _install_session_record_factory() -> None:
|
| 91 |
+
"""Replace the global LogRecord factory with one that adds ``session_tag``.
|
| 92 |
+
|
| 93 |
+
Unlike a ``logging.Filter`` on a handler or logger, the record factory
|
| 94 |
+
runs for EVERY record in the process — including records that propagate
|
| 95 |
+
from child loggers and records handled by third-party handlers. This
|
| 96 |
+
guarantees ``%(session_tag)s`` is always available in format strings,
|
| 97 |
+
eliminating the KeyError that would occur if a handler used our format
|
| 98 |
+
without having a ``_SessionFilter`` attached.
|
| 99 |
+
|
| 100 |
+
Idempotent — checks for a marker attribute to avoid double-wrapping if
|
| 101 |
+
the module is reloaded.
|
| 102 |
+
"""
|
| 103 |
+
current_factory = logging.getLogRecordFactory()
|
| 104 |
+
if getattr(current_factory, "_hermes_session_injector", False):
|
| 105 |
+
return # already installed
|
| 106 |
+
|
| 107 |
+
def _session_record_factory(*args, **kwargs):
|
| 108 |
+
record = current_factory(*args, **kwargs)
|
| 109 |
+
sid = getattr(_session_context, "session_id", None)
|
| 110 |
+
record.session_tag = f" [{sid}]" if sid else "" # type: ignore[attr-defined]
|
| 111 |
+
return record
|
| 112 |
+
|
| 113 |
+
_session_record_factory._hermes_session_injector = True # type: ignore[attr-defined]
|
| 114 |
+
logging.setLogRecordFactory(_session_record_factory)
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
# Install immediately on import — session_tag is available on all records
|
| 118 |
+
# from this point forward, even before setup_logging() is called.
|
| 119 |
+
_install_session_record_factory()
|
| 120 |
+
|
| 121 |
+
|
| 122 |
+
# ---------------------------------------------------------------------------
|
| 123 |
+
# Filters
|
| 124 |
+
# ---------------------------------------------------------------------------
|
| 125 |
+
|
| 126 |
+
class _ComponentFilter(logging.Filter):
|
| 127 |
+
"""Only pass records whose logger name starts with one of *prefixes*.
|
| 128 |
+
|
| 129 |
+
Used to route gateway-specific records to ``gateway.log`` while
|
| 130 |
+
keeping ``agent.log`` as the catch-all.
|
| 131 |
+
"""
|
| 132 |
+
|
| 133 |
+
def __init__(self, prefixes: Sequence[str]) -> None:
|
| 134 |
+
super().__init__()
|
| 135 |
+
self._prefixes = tuple(prefixes)
|
| 136 |
+
|
| 137 |
+
def filter(self, record: logging.LogRecord) -> bool:
|
| 138 |
+
return record.name.startswith(self._prefixes)
|
| 139 |
+
|
| 140 |
+
|
| 141 |
+
# Logger name prefixes that belong to each component.
|
| 142 |
+
# Used by _ComponentFilter and exposed for ``hermes logs --component``.
|
| 143 |
+
COMPONENT_PREFIXES = {
|
| 144 |
+
"gateway": ("gateway",),
|
| 145 |
+
"agent": ("agent", "run_agent", "model_tools", "batch_runner"),
|
| 146 |
+
"tools": ("tools",),
|
| 147 |
+
"cli": ("hermes_cli", "cli"),
|
| 148 |
+
"cron": ("cron",),
|
| 149 |
+
}
|
| 150 |
+
|
| 151 |
+
|
| 152 |
+
# ---------------------------------------------------------------------------
|
| 153 |
+
# Main setup
|
| 154 |
+
# ---------------------------------------------------------------------------
|
| 155 |
+
|
| 156 |
+
def setup_logging(
|
| 157 |
+
*,
|
| 158 |
+
hermes_home: Optional[Path] = None,
|
| 159 |
+
log_level: Optional[str] = None,
|
| 160 |
+
max_size_mb: Optional[int] = None,
|
| 161 |
+
backup_count: Optional[int] = None,
|
| 162 |
+
mode: Optional[str] = None,
|
| 163 |
+
force: bool = False,
|
| 164 |
+
) -> Path:
|
| 165 |
+
"""Configure the Hermes logging subsystem.
|
| 166 |
+
|
| 167 |
+
Safe to call multiple times — the second call is a no-op unless
|
| 168 |
+
*force* is ``True``.
|
| 169 |
+
|
| 170 |
+
Parameters
|
| 171 |
+
----------
|
| 172 |
+
hermes_home
|
| 173 |
+
Override for the Hermes home directory. Falls back to
|
| 174 |
+
``get_hermes_home()`` (profile-aware).
|
| 175 |
+
log_level
|
| 176 |
+
Minimum level for the ``agent.log`` file handler. Accepts any
|
| 177 |
+
standard Python level name (``"DEBUG"``, ``"INFO"``, ``"WARNING"``).
|
| 178 |
+
Defaults to ``"INFO"`` or the value from config.yaml ``logging.level``.
|
| 179 |
+
max_size_mb
|
| 180 |
+
Maximum size of each log file in megabytes before rotation.
|
| 181 |
+
Defaults to 5 or the value from config.yaml ``logging.max_size_mb``.
|
| 182 |
+
backup_count
|
| 183 |
+
Number of rotated backup files to keep.
|
| 184 |
+
Defaults to 3 or the value from config.yaml ``logging.backup_count``.
|
| 185 |
+
mode
|
| 186 |
+
Caller context: ``"cli"``, ``"gateway"``, ``"cron"``.
|
| 187 |
+
When ``"gateway"``, an additional ``gateway.log`` file is created
|
| 188 |
+
that receives only gateway-component records.
|
| 189 |
+
force
|
| 190 |
+
Re-run setup even if it has already been called.
|
| 191 |
+
|
| 192 |
+
Returns
|
| 193 |
+
-------
|
| 194 |
+
Path
|
| 195 |
+
The ``logs/`` directory where files are written.
|
| 196 |
+
"""
|
| 197 |
+
global _logging_initialized
|
| 198 |
+
home = hermes_home or get_hermes_home()
|
| 199 |
+
log_dir = home / "logs"
|
| 200 |
+
log_dir.mkdir(parents=True, exist_ok=True)
|
| 201 |
+
|
| 202 |
+
# Read config defaults (best-effort — config may not be loaded yet).
|
| 203 |
+
cfg_level, cfg_max_size, cfg_backup = _read_logging_config()
|
| 204 |
+
|
| 205 |
+
level_name = (log_level or cfg_level or "INFO").upper()
|
| 206 |
+
level = getattr(logging, level_name, logging.INFO)
|
| 207 |
+
max_bytes = (max_size_mb or cfg_max_size or 5) * 1024 * 1024
|
| 208 |
+
backups = backup_count or cfg_backup or 3
|
| 209 |
+
|
| 210 |
+
# Lazy import to avoid circular dependency at module load time.
|
| 211 |
+
from agent.redact import RedactingFormatter
|
| 212 |
+
|
| 213 |
+
root = logging.getLogger()
|
| 214 |
+
|
| 215 |
+
# --- agent.log (INFO+) — the main activity log -------------------------
|
| 216 |
+
_add_rotating_handler(
|
| 217 |
+
root,
|
| 218 |
+
log_dir / "agent.log",
|
| 219 |
+
level=level,
|
| 220 |
+
max_bytes=max_bytes,
|
| 221 |
+
backup_count=backups,
|
| 222 |
+
formatter=RedactingFormatter(_LOG_FORMAT),
|
| 223 |
+
)
|
| 224 |
+
|
| 225 |
+
# --- errors.log (WARNING+) — quick triage log --------------------------
|
| 226 |
+
_add_rotating_handler(
|
| 227 |
+
root,
|
| 228 |
+
log_dir / "errors.log",
|
| 229 |
+
level=logging.WARNING,
|
| 230 |
+
max_bytes=2 * 1024 * 1024,
|
| 231 |
+
backup_count=2,
|
| 232 |
+
formatter=RedactingFormatter(_LOG_FORMAT),
|
| 233 |
+
)
|
| 234 |
+
|
| 235 |
+
# --- gateway.log (INFO+, gateway component only) ------------------------
|
| 236 |
+
if mode == "gateway":
|
| 237 |
+
_add_rotating_handler(
|
| 238 |
+
root,
|
| 239 |
+
log_dir / "gateway.log",
|
| 240 |
+
level=logging.INFO,
|
| 241 |
+
max_bytes=5 * 1024 * 1024,
|
| 242 |
+
backup_count=3,
|
| 243 |
+
formatter=RedactingFormatter(_LOG_FORMAT),
|
| 244 |
+
log_filter=_ComponentFilter(COMPONENT_PREFIXES["gateway"]),
|
| 245 |
+
)
|
| 246 |
+
|
| 247 |
+
if _logging_initialized and not force:
|
| 248 |
+
return log_dir
|
| 249 |
+
|
| 250 |
+
# Ensure root logger level is low enough for the handlers to fire.
|
| 251 |
+
if root.level == logging.NOTSET or root.level > level:
|
| 252 |
+
root.setLevel(level)
|
| 253 |
+
|
| 254 |
+
# Suppress noisy third-party loggers.
|
| 255 |
+
for name in _NOISY_LOGGERS:
|
| 256 |
+
logging.getLogger(name).setLevel(logging.WARNING)
|
| 257 |
+
|
| 258 |
+
_logging_initialized = True
|
| 259 |
+
return log_dir
|
| 260 |
+
|
| 261 |
+
|
| 262 |
+
def setup_verbose_logging() -> None:
|
| 263 |
+
"""Enable DEBUG-level console logging for ``--verbose`` / ``-v`` mode.
|
| 264 |
+
|
| 265 |
+
Called by ``AIAgent.__init__()`` when ``verbose_logging=True``.
|
| 266 |
+
"""
|
| 267 |
+
from agent.redact import RedactingFormatter
|
| 268 |
+
|
| 269 |
+
root = logging.getLogger()
|
| 270 |
+
|
| 271 |
+
# Avoid adding duplicate stream handlers.
|
| 272 |
+
for h in root.handlers:
|
| 273 |
+
if isinstance(h, logging.StreamHandler) and not isinstance(h, RotatingFileHandler):
|
| 274 |
+
if getattr(h, "_hermes_verbose", False):
|
| 275 |
+
return
|
| 276 |
+
|
| 277 |
+
handler = logging.StreamHandler()
|
| 278 |
+
handler.setLevel(logging.DEBUG)
|
| 279 |
+
handler.setFormatter(RedactingFormatter(_LOG_FORMAT_VERBOSE, datefmt="%H:%M:%S"))
|
| 280 |
+
handler._hermes_verbose = True # type: ignore[attr-defined]
|
| 281 |
+
root.addHandler(handler)
|
| 282 |
+
|
| 283 |
+
# Lower root logger level so DEBUG records reach all handlers.
|
| 284 |
+
if root.level > logging.DEBUG:
|
| 285 |
+
root.setLevel(logging.DEBUG)
|
| 286 |
+
|
| 287 |
+
# Keep third-party libraries at WARNING to reduce noise.
|
| 288 |
+
for name in _NOISY_LOGGERS:
|
| 289 |
+
logging.getLogger(name).setLevel(logging.WARNING)
|
| 290 |
+
# rex-deploy at INFO for sandbox status.
|
| 291 |
+
logging.getLogger("rex-deploy").setLevel(logging.INFO)
|
| 292 |
+
|
| 293 |
+
|
| 294 |
+
# ---------------------------------------------------------------------------
|
| 295 |
+
# Internal helpers
|
| 296 |
+
# ---------------------------------------------------------------------------
|
| 297 |
+
|
| 298 |
+
class _ManagedRotatingFileHandler(RotatingFileHandler):
|
| 299 |
+
"""RotatingFileHandler that ensures group-writable perms in managed mode.
|
| 300 |
+
|
| 301 |
+
In managed mode (NixOS), the stateDir uses setgid (2770) so new files
|
| 302 |
+
inherit the hermes group. However, both _open() (initial creation) and
|
| 303 |
+
doRollover() create files via open(), which uses the process umask —
|
| 304 |
+
typically 0022, producing 0644. This subclass applies chmod 0660 after
|
| 305 |
+
both operations so the gateway and interactive users can share log files.
|
| 306 |
+
"""
|
| 307 |
+
|
| 308 |
+
def __init__(self, *args, **kwargs):
|
| 309 |
+
from hermes_cli.config import is_managed
|
| 310 |
+
self._managed = is_managed()
|
| 311 |
+
super().__init__(*args, **kwargs)
|
| 312 |
+
|
| 313 |
+
def _chmod_if_managed(self):
|
| 314 |
+
if self._managed:
|
| 315 |
+
try:
|
| 316 |
+
os.chmod(self.baseFilename, 0o660)
|
| 317 |
+
except OSError:
|
| 318 |
+
pass
|
| 319 |
+
|
| 320 |
+
def _open(self):
|
| 321 |
+
stream = super()._open()
|
| 322 |
+
self._chmod_if_managed()
|
| 323 |
+
return stream
|
| 324 |
+
|
| 325 |
+
def doRollover(self):
|
| 326 |
+
super().doRollover()
|
| 327 |
+
self._chmod_if_managed()
|
| 328 |
+
|
| 329 |
+
|
| 330 |
+
def _add_rotating_handler(
|
| 331 |
+
logger: logging.Logger,
|
| 332 |
+
path: Path,
|
| 333 |
+
*,
|
| 334 |
+
level: int,
|
| 335 |
+
max_bytes: int,
|
| 336 |
+
backup_count: int,
|
| 337 |
+
formatter: logging.Formatter,
|
| 338 |
+
log_filter: Optional[logging.Filter] = None,
|
| 339 |
+
) -> None:
|
| 340 |
+
"""Add a ``RotatingFileHandler`` to *logger*, skipping if one already
|
| 341 |
+
exists for the same resolved file path (idempotent).
|
| 342 |
+
|
| 343 |
+
Parameters
|
| 344 |
+
----------
|
| 345 |
+
log_filter
|
| 346 |
+
Optional filter to attach to the handler (e.g. ``_ComponentFilter``
|
| 347 |
+
for gateway.log).
|
| 348 |
+
"""
|
| 349 |
+
resolved = path.resolve()
|
| 350 |
+
for existing in logger.handlers:
|
| 351 |
+
if (
|
| 352 |
+
isinstance(existing, RotatingFileHandler)
|
| 353 |
+
and Path(getattr(existing, "baseFilename", "")).resolve() == resolved
|
| 354 |
+
):
|
| 355 |
+
return # already attached
|
| 356 |
+
|
| 357 |
+
path.parent.mkdir(parents=True, exist_ok=True)
|
| 358 |
+
handler = _ManagedRotatingFileHandler(
|
| 359 |
+
str(path), maxBytes=max_bytes, backupCount=backup_count,
|
| 360 |
+
encoding="utf-8",
|
| 361 |
+
)
|
| 362 |
+
handler.setLevel(level)
|
| 363 |
+
handler.setFormatter(formatter)
|
| 364 |
+
if log_filter is not None:
|
| 365 |
+
handler.addFilter(log_filter)
|
| 366 |
+
logger.addHandler(handler)
|
| 367 |
+
|
| 368 |
+
|
| 369 |
+
def _read_logging_config():
|
| 370 |
+
"""Best-effort read of ``logging.*`` from config.yaml.
|
| 371 |
+
|
| 372 |
+
Returns ``(level, max_size_mb, backup_count)`` — any may be ``None``.
|
| 373 |
+
"""
|
| 374 |
+
try:
|
| 375 |
+
import yaml
|
| 376 |
+
config_path = get_config_path()
|
| 377 |
+
if config_path.exists():
|
| 378 |
+
with open(config_path, "r", encoding="utf-8") as f:
|
| 379 |
+
cfg = yaml.safe_load(f) or {}
|
| 380 |
+
log_cfg = cfg.get("logging", {})
|
| 381 |
+
if isinstance(log_cfg, dict):
|
| 382 |
+
return (
|
| 383 |
+
log_cfg.get("level"),
|
| 384 |
+
log_cfg.get("max_size_mb"),
|
| 385 |
+
log_cfg.get("backup_count"),
|
| 386 |
+
)
|
| 387 |
+
except Exception:
|
| 388 |
+
pass
|
| 389 |
+
return (None, None, None)
|
hermes_state.py
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
hermes_time.py
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Timezone-aware clock for Hermes.
|
| 3 |
+
|
| 4 |
+
Provides a single ``now()`` helper that returns a timezone-aware datetime
|
| 5 |
+
based on the user's configured IANA timezone (e.g. ``Asia/Kolkata``).
|
| 6 |
+
|
| 7 |
+
Resolution order:
|
| 8 |
+
1. ``HERMES_TIMEZONE`` environment variable
|
| 9 |
+
2. ``timezone`` key in ``~/.hermes/config.yaml``
|
| 10 |
+
3. Falls back to the server's local time (``datetime.now().astimezone()``)
|
| 11 |
+
|
| 12 |
+
Invalid timezone values log a warning and fall back safely — Hermes never
|
| 13 |
+
crashes due to a bad timezone string.
|
| 14 |
+
"""
|
| 15 |
+
|
| 16 |
+
import logging
|
| 17 |
+
import os
|
| 18 |
+
from datetime import datetime
|
| 19 |
+
from hermes_constants import get_config_path
|
| 20 |
+
from typing import Optional
|
| 21 |
+
|
| 22 |
+
logger = logging.getLogger(__name__)
|
| 23 |
+
|
| 24 |
+
try:
|
| 25 |
+
from zoneinfo import ZoneInfo
|
| 26 |
+
except ImportError:
|
| 27 |
+
# Python 3.8 fallback (shouldn't be needed — Hermes requires 3.9+)
|
| 28 |
+
from backports.zoneinfo import ZoneInfo # type: ignore[no-redef]
|
| 29 |
+
|
| 30 |
+
# Cached state — resolved once, reused on every call.
|
| 31 |
+
# Call reset_cache() to force re-resolution (e.g. after config changes).
|
| 32 |
+
_cached_tz: Optional[ZoneInfo] = None
|
| 33 |
+
_cached_tz_name: Optional[str] = None
|
| 34 |
+
_cache_resolved: bool = False
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
def _resolve_timezone_name() -> str:
|
| 38 |
+
"""Read the configured IANA timezone string (or empty string).
|
| 39 |
+
|
| 40 |
+
This does file I/O when falling through to config.yaml, so callers
|
| 41 |
+
should cache the result rather than calling on every ``now()``.
|
| 42 |
+
"""
|
| 43 |
+
# 1. Environment variable (highest priority — set by Supervisor, etc.)
|
| 44 |
+
tz_env = os.getenv("HERMES_TIMEZONE", "").strip()
|
| 45 |
+
if tz_env:
|
| 46 |
+
return tz_env
|
| 47 |
+
|
| 48 |
+
# 2. config.yaml ``timezone`` key
|
| 49 |
+
try:
|
| 50 |
+
import yaml
|
| 51 |
+
config_path = get_config_path()
|
| 52 |
+
if config_path.exists():
|
| 53 |
+
with open(config_path, encoding="utf-8") as f:
|
| 54 |
+
cfg = yaml.safe_load(f) or {}
|
| 55 |
+
tz_cfg = cfg.get("timezone", "")
|
| 56 |
+
if isinstance(tz_cfg, str) and tz_cfg.strip():
|
| 57 |
+
return tz_cfg.strip()
|
| 58 |
+
except Exception:
|
| 59 |
+
pass
|
| 60 |
+
|
| 61 |
+
return ""
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def _get_zoneinfo(name: str) -> Optional[ZoneInfo]:
|
| 65 |
+
"""Validate and return a ZoneInfo, or None if invalid."""
|
| 66 |
+
if not name:
|
| 67 |
+
return None
|
| 68 |
+
try:
|
| 69 |
+
return ZoneInfo(name)
|
| 70 |
+
except (KeyError, Exception) as exc:
|
| 71 |
+
logger.warning(
|
| 72 |
+
"Invalid timezone '%s': %s. Falling back to server local time.",
|
| 73 |
+
name, exc,
|
| 74 |
+
)
|
| 75 |
+
return None
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
def get_timezone() -> Optional[ZoneInfo]:
|
| 79 |
+
"""Return the user's configured ZoneInfo, or None (meaning server-local).
|
| 80 |
+
|
| 81 |
+
Resolved once and cached. Call ``reset_cache()`` after config changes.
|
| 82 |
+
"""
|
| 83 |
+
global _cached_tz, _cached_tz_name, _cache_resolved
|
| 84 |
+
if not _cache_resolved:
|
| 85 |
+
_cached_tz_name = _resolve_timezone_name()
|
| 86 |
+
_cached_tz = _get_zoneinfo(_cached_tz_name)
|
| 87 |
+
_cache_resolved = True
|
| 88 |
+
return _cached_tz
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
def now() -> datetime:
|
| 92 |
+
"""
|
| 93 |
+
Return the current time as a timezone-aware datetime.
|
| 94 |
+
|
| 95 |
+
If a valid timezone is configured, returns wall-clock time in that zone.
|
| 96 |
+
Otherwise returns the server's local time (via ``astimezone()``).
|
| 97 |
+
"""
|
| 98 |
+
tz = get_timezone()
|
| 99 |
+
if tz is not None:
|
| 100 |
+
return datetime.now(tz)
|
| 101 |
+
# No timezone configured — use server-local (still tz-aware)
|
| 102 |
+
return datetime.now().astimezone()
|
| 103 |
+
|
| 104 |
+
|
mcp_serve.py
ADDED
|
@@ -0,0 +1,897 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Hermes MCP Server — expose messaging conversations as MCP tools.
|
| 3 |
+
|
| 4 |
+
Starts a stdio MCP server that lets any MCP client (Claude Code, Cursor, Codex,
|
| 5 |
+
etc.) list conversations, read message history, send messages, poll for live
|
| 6 |
+
events, and manage approval requests across all connected platforms.
|
| 7 |
+
|
| 8 |
+
Matches OpenClaw's 9-tool MCP channel bridge surface:
|
| 9 |
+
conversations_list, conversation_get, messages_read, attachments_fetch,
|
| 10 |
+
events_poll, events_wait, messages_send, permissions_list_open,
|
| 11 |
+
permissions_respond
|
| 12 |
+
|
| 13 |
+
Plus: channels_list (Hermes-specific extra)
|
| 14 |
+
|
| 15 |
+
Usage:
|
| 16 |
+
hermes mcp serve
|
| 17 |
+
hermes mcp serve --verbose
|
| 18 |
+
|
| 19 |
+
MCP client config (e.g. claude_desktop_config.json):
|
| 20 |
+
{
|
| 21 |
+
"mcpServers": {
|
| 22 |
+
"hermes": {
|
| 23 |
+
"command": "hermes",
|
| 24 |
+
"args": ["mcp", "serve"]
|
| 25 |
+
}
|
| 26 |
+
}
|
| 27 |
+
}
|
| 28 |
+
"""
|
| 29 |
+
|
| 30 |
+
from __future__ import annotations
|
| 31 |
+
|
| 32 |
+
import json
|
| 33 |
+
import logging
|
| 34 |
+
import os
|
| 35 |
+
import re
|
| 36 |
+
import sys
|
| 37 |
+
import threading
|
| 38 |
+
import time
|
| 39 |
+
from dataclasses import dataclass, field
|
| 40 |
+
from pathlib import Path
|
| 41 |
+
from typing import Dict, List, Optional
|
| 42 |
+
|
| 43 |
+
logger = logging.getLogger("hermes.mcp_serve")
|
| 44 |
+
|
| 45 |
+
# ---------------------------------------------------------------------------
|
| 46 |
+
# Lazy MCP SDK import
|
| 47 |
+
# ---------------------------------------------------------------------------
|
| 48 |
+
|
| 49 |
+
_MCP_SERVER_AVAILABLE = False
|
| 50 |
+
try:
|
| 51 |
+
from mcp.server.fastmcp import FastMCP
|
| 52 |
+
|
| 53 |
+
_MCP_SERVER_AVAILABLE = True
|
| 54 |
+
except ImportError:
|
| 55 |
+
FastMCP = None # type: ignore[assignment,misc]
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
# ---------------------------------------------------------------------------
|
| 59 |
+
# Helpers
|
| 60 |
+
# ---------------------------------------------------------------------------
|
| 61 |
+
|
| 62 |
+
def _get_sessions_dir() -> Path:
|
| 63 |
+
"""Return the sessions directory using HERMES_HOME."""
|
| 64 |
+
try:
|
| 65 |
+
from hermes_constants import get_hermes_home
|
| 66 |
+
return get_hermes_home() / "sessions"
|
| 67 |
+
except ImportError:
|
| 68 |
+
return Path(os.environ.get("HERMES_HOME", Path.home() / ".hermes")) / "sessions"
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
def _get_session_db():
|
| 72 |
+
"""Get a SessionDB instance for reading message transcripts."""
|
| 73 |
+
try:
|
| 74 |
+
from hermes_state import SessionDB
|
| 75 |
+
return SessionDB()
|
| 76 |
+
except Exception as e:
|
| 77 |
+
logger.debug("SessionDB unavailable: %s", e)
|
| 78 |
+
return None
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
def _load_sessions_index() -> dict:
|
| 82 |
+
"""Load the gateway sessions.json index directly.
|
| 83 |
+
|
| 84 |
+
Returns a dict of session_key -> entry_dict with platform routing info.
|
| 85 |
+
This avoids importing the full SessionStore which needs GatewayConfig.
|
| 86 |
+
"""
|
| 87 |
+
sessions_file = _get_sessions_dir() / "sessions.json"
|
| 88 |
+
if not sessions_file.exists():
|
| 89 |
+
return {}
|
| 90 |
+
try:
|
| 91 |
+
with open(sessions_file, "r", encoding="utf-8") as f:
|
| 92 |
+
return json.load(f)
|
| 93 |
+
except Exception as e:
|
| 94 |
+
logger.debug("Failed to load sessions.json: %s", e)
|
| 95 |
+
return {}
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
def _load_channel_directory() -> dict:
|
| 99 |
+
"""Load the cached channel directory for available targets."""
|
| 100 |
+
try:
|
| 101 |
+
from hermes_constants import get_hermes_home
|
| 102 |
+
directory_file = get_hermes_home() / "channel_directory.json"
|
| 103 |
+
except ImportError:
|
| 104 |
+
directory_file = Path(
|
| 105 |
+
os.environ.get("HERMES_HOME", Path.home() / ".hermes")
|
| 106 |
+
) / "channel_directory.json"
|
| 107 |
+
|
| 108 |
+
if not directory_file.exists():
|
| 109 |
+
return {}
|
| 110 |
+
try:
|
| 111 |
+
with open(directory_file, "r", encoding="utf-8") as f:
|
| 112 |
+
return json.load(f)
|
| 113 |
+
except Exception as e:
|
| 114 |
+
logger.debug("Failed to load channel_directory.json: %s", e)
|
| 115 |
+
return {}
|
| 116 |
+
|
| 117 |
+
|
| 118 |
+
def _coerce_int(
|
| 119 |
+
value,
|
| 120 |
+
*,
|
| 121 |
+
default: int,
|
| 122 |
+
minimum: int,
|
| 123 |
+
maximum: int,
|
| 124 |
+
) -> int:
|
| 125 |
+
"""Coerce value to int with fallback and clamping.
|
| 126 |
+
|
| 127 |
+
Used at MCP tool boundaries to handle invalid types from external clients.
|
| 128 |
+
Returns default if value cannot be converted to int.
|
| 129 |
+
"""
|
| 130 |
+
try:
|
| 131 |
+
coerced = int(value)
|
| 132 |
+
except (TypeError, ValueError):
|
| 133 |
+
coerced = default
|
| 134 |
+
return max(minimum, min(coerced, maximum))
|
| 135 |
+
|
| 136 |
+
|
| 137 |
+
def _extract_message_content(msg: dict) -> str:
|
| 138 |
+
"""Extract text content from a message, handling multi-part content."""
|
| 139 |
+
content = msg.get("content", "")
|
| 140 |
+
if isinstance(content, list):
|
| 141 |
+
text_parts = [
|
| 142 |
+
p.get("text", "") for p in content
|
| 143 |
+
if isinstance(p, dict) and p.get("type") == "text"
|
| 144 |
+
]
|
| 145 |
+
return "\n".join(text_parts)
|
| 146 |
+
return str(content) if content else ""
|
| 147 |
+
|
| 148 |
+
|
| 149 |
+
def _extract_attachments(msg: dict) -> List[dict]:
|
| 150 |
+
"""Extract non-text attachments from a message.
|
| 151 |
+
|
| 152 |
+
Finds: multi-part image/file content blocks, MEDIA: tags in text,
|
| 153 |
+
image URLs, and file references.
|
| 154 |
+
"""
|
| 155 |
+
attachments = []
|
| 156 |
+
content = msg.get("content", "")
|
| 157 |
+
|
| 158 |
+
# Multi-part content blocks (image_url, file, etc.)
|
| 159 |
+
if isinstance(content, list):
|
| 160 |
+
for part in content:
|
| 161 |
+
if not isinstance(part, dict):
|
| 162 |
+
continue
|
| 163 |
+
ptype = part.get("type", "")
|
| 164 |
+
if ptype == "image_url":
|
| 165 |
+
url = part.get("image_url", {}).get("url", "") if isinstance(part.get("image_url"), dict) else ""
|
| 166 |
+
if url:
|
| 167 |
+
attachments.append({"type": "image", "url": url})
|
| 168 |
+
elif ptype == "image":
|
| 169 |
+
url = part.get("url", part.get("source", {}).get("url", ""))
|
| 170 |
+
if url:
|
| 171 |
+
attachments.append({"type": "image", "url": url})
|
| 172 |
+
elif ptype not in ("text",):
|
| 173 |
+
# Unknown non-text content type
|
| 174 |
+
attachments.append({"type": ptype, "data": part})
|
| 175 |
+
|
| 176 |
+
# MEDIA: tags in text content
|
| 177 |
+
text = _extract_message_content(msg)
|
| 178 |
+
if text:
|
| 179 |
+
media_pattern = re.compile(r'MEDIA:\s*(\S+)')
|
| 180 |
+
for match in media_pattern.finditer(text):
|
| 181 |
+
path = match.group(1)
|
| 182 |
+
attachments.append({"type": "media", "path": path})
|
| 183 |
+
|
| 184 |
+
return attachments
|
| 185 |
+
|
| 186 |
+
|
| 187 |
+
# ---------------------------------------------------------------------------
|
| 188 |
+
# Event Bridge — polls SessionDB for new messages, maintains event queue
|
| 189 |
+
# ---------------------------------------------------------------------------
|
| 190 |
+
|
| 191 |
+
QUEUE_LIMIT = 1000
|
| 192 |
+
POLL_INTERVAL = 0.2 # seconds between DB polls (200ms)
|
| 193 |
+
|
| 194 |
+
|
| 195 |
+
@dataclass
|
| 196 |
+
class QueueEvent:
|
| 197 |
+
"""An event in the bridge's in-memory queue."""
|
| 198 |
+
cursor: int
|
| 199 |
+
type: str # "message", "approval_requested", "approval_resolved"
|
| 200 |
+
session_key: str = ""
|
| 201 |
+
data: dict = field(default_factory=dict)
|
| 202 |
+
|
| 203 |
+
|
| 204 |
+
class EventBridge:
|
| 205 |
+
"""Background poller that watches SessionDB for new messages and
|
| 206 |
+
maintains an in-memory event queue with waiter support.
|
| 207 |
+
|
| 208 |
+
This is the Hermes equivalent of OpenClaw's WebSocket gateway bridge.
|
| 209 |
+
Instead of WebSocket events, we poll the SQLite database for changes.
|
| 210 |
+
"""
|
| 211 |
+
|
| 212 |
+
def __init__(self):
|
| 213 |
+
self._queue: List[QueueEvent] = []
|
| 214 |
+
self._cursor = 0
|
| 215 |
+
self._lock = threading.Lock()
|
| 216 |
+
self._new_event = threading.Event()
|
| 217 |
+
self._running = False
|
| 218 |
+
self._thread: Optional[threading.Thread] = None
|
| 219 |
+
self._last_poll_timestamps: Dict[str, float] = {} # session_key -> unix timestamp
|
| 220 |
+
# In-memory approval tracking (populated from events)
|
| 221 |
+
self._pending_approvals: Dict[str, dict] = {}
|
| 222 |
+
# mtime cache — skip expensive work when files haven't changed
|
| 223 |
+
self._sessions_json_mtime: float = 0.0
|
| 224 |
+
self._state_db_mtime: float = 0.0
|
| 225 |
+
self._cached_sessions_index: dict = {}
|
| 226 |
+
|
| 227 |
+
def start(self):
|
| 228 |
+
"""Start the background polling thread."""
|
| 229 |
+
if self._running:
|
| 230 |
+
return
|
| 231 |
+
self._running = True
|
| 232 |
+
self._thread = threading.Thread(target=self._poll_loop, daemon=True)
|
| 233 |
+
self._thread.start()
|
| 234 |
+
logger.debug("EventBridge started")
|
| 235 |
+
|
| 236 |
+
def stop(self):
|
| 237 |
+
"""Stop the background polling thread."""
|
| 238 |
+
self._running = False
|
| 239 |
+
self._new_event.set() # Wake any waiters
|
| 240 |
+
if self._thread:
|
| 241 |
+
self._thread.join(timeout=5)
|
| 242 |
+
logger.debug("EventBridge stopped")
|
| 243 |
+
|
| 244 |
+
def poll_events(
|
| 245 |
+
self,
|
| 246 |
+
after_cursor: int = 0,
|
| 247 |
+
session_key: Optional[str] = None,
|
| 248 |
+
limit: int = 20,
|
| 249 |
+
) -> dict:
|
| 250 |
+
"""Return events since after_cursor, optionally filtered by session_key."""
|
| 251 |
+
with self._lock:
|
| 252 |
+
events = [
|
| 253 |
+
e for e in self._queue
|
| 254 |
+
if e.cursor > after_cursor
|
| 255 |
+
and (not session_key or e.session_key == session_key)
|
| 256 |
+
][:limit]
|
| 257 |
+
|
| 258 |
+
next_cursor = events[-1].cursor if events else after_cursor
|
| 259 |
+
return {
|
| 260 |
+
"events": [
|
| 261 |
+
{"cursor": e.cursor, "type": e.type,
|
| 262 |
+
"session_key": e.session_key, **e.data}
|
| 263 |
+
for e in events
|
| 264 |
+
],
|
| 265 |
+
"next_cursor": next_cursor,
|
| 266 |
+
}
|
| 267 |
+
|
| 268 |
+
def wait_for_event(
|
| 269 |
+
self,
|
| 270 |
+
after_cursor: int = 0,
|
| 271 |
+
session_key: Optional[str] = None,
|
| 272 |
+
timeout_ms: int = 30000,
|
| 273 |
+
) -> Optional[dict]:
|
| 274 |
+
"""Block until a matching event arrives or timeout expires."""
|
| 275 |
+
deadline = time.monotonic() + (timeout_ms / 1000.0)
|
| 276 |
+
|
| 277 |
+
while time.monotonic() < deadline:
|
| 278 |
+
with self._lock:
|
| 279 |
+
for e in self._queue:
|
| 280 |
+
if e.cursor > after_cursor and (
|
| 281 |
+
not session_key or e.session_key == session_key
|
| 282 |
+
):
|
| 283 |
+
return {
|
| 284 |
+
"cursor": e.cursor, "type": e.type,
|
| 285 |
+
"session_key": e.session_key, **e.data,
|
| 286 |
+
}
|
| 287 |
+
|
| 288 |
+
remaining = deadline - time.monotonic()
|
| 289 |
+
if remaining <= 0:
|
| 290 |
+
break
|
| 291 |
+
self._new_event.clear()
|
| 292 |
+
self._new_event.wait(timeout=min(remaining, POLL_INTERVAL))
|
| 293 |
+
|
| 294 |
+
return None
|
| 295 |
+
|
| 296 |
+
def list_pending_approvals(self) -> List[dict]:
|
| 297 |
+
"""List approval requests observed during this bridge session."""
|
| 298 |
+
with self._lock:
|
| 299 |
+
return sorted(
|
| 300 |
+
self._pending_approvals.values(),
|
| 301 |
+
key=lambda a: a.get("created_at", ""),
|
| 302 |
+
)
|
| 303 |
+
|
| 304 |
+
def respond_to_approval(self, approval_id: str, decision: str) -> dict:
|
| 305 |
+
"""Resolve a pending approval (best-effort without gateway IPC)."""
|
| 306 |
+
with self._lock:
|
| 307 |
+
approval = self._pending_approvals.pop(approval_id, None)
|
| 308 |
+
|
| 309 |
+
if not approval:
|
| 310 |
+
return {"error": f"Approval not found: {approval_id}"}
|
| 311 |
+
|
| 312 |
+
self._enqueue(QueueEvent(
|
| 313 |
+
cursor=0, # Will be set by _enqueue
|
| 314 |
+
type="approval_resolved",
|
| 315 |
+
session_key=approval.get("session_key", ""),
|
| 316 |
+
data={"approval_id": approval_id, "decision": decision},
|
| 317 |
+
))
|
| 318 |
+
|
| 319 |
+
return {"resolved": True, "approval_id": approval_id, "decision": decision}
|
| 320 |
+
|
| 321 |
+
def _enqueue(self, event: QueueEvent) -> None:
|
| 322 |
+
"""Add an event to the queue and wake any waiters."""
|
| 323 |
+
with self._lock:
|
| 324 |
+
self._cursor += 1
|
| 325 |
+
event.cursor = self._cursor
|
| 326 |
+
self._queue.append(event)
|
| 327 |
+
# Trim queue to limit
|
| 328 |
+
while len(self._queue) > QUEUE_LIMIT:
|
| 329 |
+
self._queue.pop(0)
|
| 330 |
+
self._new_event.set()
|
| 331 |
+
|
| 332 |
+
def _poll_loop(self):
|
| 333 |
+
"""Background loop: poll SessionDB for new messages."""
|
| 334 |
+
db = _get_session_db()
|
| 335 |
+
if not db:
|
| 336 |
+
logger.warning("EventBridge: SessionDB unavailable, event polling disabled")
|
| 337 |
+
return
|
| 338 |
+
|
| 339 |
+
while self._running:
|
| 340 |
+
try:
|
| 341 |
+
self._poll_once(db)
|
| 342 |
+
except Exception as e:
|
| 343 |
+
logger.debug("EventBridge poll error: %s", e)
|
| 344 |
+
time.sleep(POLL_INTERVAL)
|
| 345 |
+
|
| 346 |
+
def _poll_once(self, db):
|
| 347 |
+
"""Check for new messages across all sessions.
|
| 348 |
+
|
| 349 |
+
Uses mtime checks on sessions.json and state.db to skip work
|
| 350 |
+
when nothing has changed — makes 200ms polling essentially free.
|
| 351 |
+
"""
|
| 352 |
+
# Check if sessions.json has changed (mtime check is ~1μs)
|
| 353 |
+
sessions_file = _get_sessions_dir() / "sessions.json"
|
| 354 |
+
try:
|
| 355 |
+
sj_mtime = sessions_file.stat().st_mtime if sessions_file.exists() else 0.0
|
| 356 |
+
except OSError:
|
| 357 |
+
sj_mtime = 0.0
|
| 358 |
+
|
| 359 |
+
if sj_mtime != self._sessions_json_mtime:
|
| 360 |
+
self._sessions_json_mtime = sj_mtime
|
| 361 |
+
self._cached_sessions_index = _load_sessions_index()
|
| 362 |
+
|
| 363 |
+
# Check if state.db has changed
|
| 364 |
+
try:
|
| 365 |
+
from hermes_constants import get_hermes_home
|
| 366 |
+
db_file = get_hermes_home() / "state.db"
|
| 367 |
+
except ImportError:
|
| 368 |
+
db_file = Path(os.environ.get("HERMES_HOME", Path.home() / ".hermes")) / "state.db"
|
| 369 |
+
|
| 370 |
+
try:
|
| 371 |
+
db_mtime = db_file.stat().st_mtime if db_file.exists() else 0.0
|
| 372 |
+
except OSError:
|
| 373 |
+
db_mtime = 0.0
|
| 374 |
+
|
| 375 |
+
if db_mtime == self._state_db_mtime and sj_mtime == self._sessions_json_mtime:
|
| 376 |
+
return # Nothing changed since last poll — skip entirely
|
| 377 |
+
|
| 378 |
+
self._state_db_mtime = db_mtime
|
| 379 |
+
entries = self._cached_sessions_index
|
| 380 |
+
|
| 381 |
+
for session_key, entry in entries.items():
|
| 382 |
+
session_id = entry.get("session_id", "")
|
| 383 |
+
if not session_id:
|
| 384 |
+
continue
|
| 385 |
+
|
| 386 |
+
last_seen = self._last_poll_timestamps.get(session_key, 0.0)
|
| 387 |
+
|
| 388 |
+
try:
|
| 389 |
+
messages = db.get_messages(session_id)
|
| 390 |
+
except Exception:
|
| 391 |
+
continue
|
| 392 |
+
|
| 393 |
+
if not messages:
|
| 394 |
+
continue
|
| 395 |
+
|
| 396 |
+
# Normalize timestamps to float for comparison
|
| 397 |
+
def _ts_float(ts) -> float:
|
| 398 |
+
if isinstance(ts, (int, float)):
|
| 399 |
+
return float(ts)
|
| 400 |
+
if isinstance(ts, str) and ts:
|
| 401 |
+
try:
|
| 402 |
+
return float(ts)
|
| 403 |
+
except ValueError:
|
| 404 |
+
# ISO string — parse to epoch
|
| 405 |
+
try:
|
| 406 |
+
from datetime import datetime
|
| 407 |
+
return datetime.fromisoformat(ts).timestamp()
|
| 408 |
+
except Exception:
|
| 409 |
+
return 0.0
|
| 410 |
+
return 0.0
|
| 411 |
+
|
| 412 |
+
# Find messages newer than our last seen timestamp
|
| 413 |
+
new_messages = []
|
| 414 |
+
for msg in messages:
|
| 415 |
+
ts = _ts_float(msg.get("timestamp", 0))
|
| 416 |
+
role = msg.get("role", "")
|
| 417 |
+
if role not in ("user", "assistant"):
|
| 418 |
+
continue
|
| 419 |
+
if ts > last_seen:
|
| 420 |
+
new_messages.append(msg)
|
| 421 |
+
|
| 422 |
+
for msg in new_messages:
|
| 423 |
+
content = _extract_message_content(msg)
|
| 424 |
+
if not content:
|
| 425 |
+
continue
|
| 426 |
+
self._enqueue(QueueEvent(
|
| 427 |
+
cursor=0,
|
| 428 |
+
type="message",
|
| 429 |
+
session_key=session_key,
|
| 430 |
+
data={
|
| 431 |
+
"role": msg.get("role", ""),
|
| 432 |
+
"content": content[:500],
|
| 433 |
+
"timestamp": str(msg.get("timestamp", "")),
|
| 434 |
+
"message_id": str(msg.get("id", "")),
|
| 435 |
+
},
|
| 436 |
+
))
|
| 437 |
+
|
| 438 |
+
# Update last seen to the most recent message timestamp
|
| 439 |
+
all_ts = [_ts_float(m.get("timestamp", 0)) for m in messages]
|
| 440 |
+
if all_ts:
|
| 441 |
+
latest = max(all_ts)
|
| 442 |
+
if latest > last_seen:
|
| 443 |
+
self._last_poll_timestamps[session_key] = latest
|
| 444 |
+
|
| 445 |
+
|
| 446 |
+
# ---------------------------------------------------------------------------
|
| 447 |
+
# MCP Server
|
| 448 |
+
# ---------------------------------------------------------------------------
|
| 449 |
+
|
| 450 |
+
def create_mcp_server(event_bridge: Optional[EventBridge] = None) -> "FastMCP":
|
| 451 |
+
"""Create and return the Hermes MCP server with all tools registered."""
|
| 452 |
+
if not _MCP_SERVER_AVAILABLE:
|
| 453 |
+
raise ImportError(
|
| 454 |
+
"MCP server requires the 'mcp' package. "
|
| 455 |
+
f"Install with: {sys.executable} -m pip install 'mcp'"
|
| 456 |
+
)
|
| 457 |
+
|
| 458 |
+
mcp = FastMCP(
|
| 459 |
+
"hermes",
|
| 460 |
+
instructions=(
|
| 461 |
+
"Hermes Agent messaging bridge. Use these tools to interact with "
|
| 462 |
+
"conversations across Telegram, Discord, Slack, WhatsApp, Signal, "
|
| 463 |
+
"Matrix, and other connected platforms."
|
| 464 |
+
),
|
| 465 |
+
)
|
| 466 |
+
|
| 467 |
+
bridge = event_bridge or EventBridge()
|
| 468 |
+
|
| 469 |
+
# -- conversations_list ------------------------------------------------
|
| 470 |
+
|
| 471 |
+
@mcp.tool()
|
| 472 |
+
def conversations_list(
|
| 473 |
+
platform: Optional[str] = None,
|
| 474 |
+
limit: int = 50,
|
| 475 |
+
search: Optional[str] = None,
|
| 476 |
+
) -> str:
|
| 477 |
+
"""List active messaging conversations across connected platforms.
|
| 478 |
+
|
| 479 |
+
Returns conversations with their session keys (needed for messages_read),
|
| 480 |
+
platform, chat type, display name, and last activity time.
|
| 481 |
+
|
| 482 |
+
Args:
|
| 483 |
+
platform: Filter by platform name (telegram, discord, slack, etc.)
|
| 484 |
+
limit: Maximum number of conversations to return (default 50)
|
| 485 |
+
search: Optional text to filter conversations by name
|
| 486 |
+
"""
|
| 487 |
+
limit = _coerce_int(limit, default=50, minimum=1, maximum=200)
|
| 488 |
+
entries = _load_sessions_index()
|
| 489 |
+
conversations = []
|
| 490 |
+
|
| 491 |
+
for key, entry in entries.items():
|
| 492 |
+
origin = entry.get("origin", {})
|
| 493 |
+
entry_platform = entry.get("platform") or origin.get("platform", "")
|
| 494 |
+
|
| 495 |
+
if platform and entry_platform.lower() != platform.lower():
|
| 496 |
+
continue
|
| 497 |
+
|
| 498 |
+
display_name = entry.get("display_name", "")
|
| 499 |
+
chat_name = origin.get("chat_name", "")
|
| 500 |
+
if search:
|
| 501 |
+
search_lower = search.lower()
|
| 502 |
+
if (search_lower not in display_name.lower()
|
| 503 |
+
and search_lower not in chat_name.lower()
|
| 504 |
+
and search_lower not in key.lower()):
|
| 505 |
+
continue
|
| 506 |
+
|
| 507 |
+
conversations.append({
|
| 508 |
+
"session_key": key,
|
| 509 |
+
"session_id": entry.get("session_id", ""),
|
| 510 |
+
"platform": entry_platform,
|
| 511 |
+
"chat_type": entry.get("chat_type", origin.get("chat_type", "")),
|
| 512 |
+
"display_name": display_name,
|
| 513 |
+
"chat_name": chat_name,
|
| 514 |
+
"user_name": origin.get("user_name", ""),
|
| 515 |
+
"updated_at": entry.get("updated_at", ""),
|
| 516 |
+
})
|
| 517 |
+
|
| 518 |
+
conversations.sort(key=lambda c: c.get("updated_at", ""), reverse=True)
|
| 519 |
+
conversations = conversations[:limit]
|
| 520 |
+
|
| 521 |
+
return json.dumps({
|
| 522 |
+
"count": len(conversations),
|
| 523 |
+
"conversations": conversations,
|
| 524 |
+
}, indent=2)
|
| 525 |
+
|
| 526 |
+
# -- conversation_get --------------------------------------------------
|
| 527 |
+
|
| 528 |
+
@mcp.tool()
|
| 529 |
+
def conversation_get(session_key: str) -> str:
|
| 530 |
+
"""Get detailed info about one conversation by its session key.
|
| 531 |
+
|
| 532 |
+
Args:
|
| 533 |
+
session_key: The session key from conversations_list
|
| 534 |
+
"""
|
| 535 |
+
entries = _load_sessions_index()
|
| 536 |
+
entry = entries.get(session_key)
|
| 537 |
+
|
| 538 |
+
if not entry:
|
| 539 |
+
return json.dumps({"error": f"Conversation not found: {session_key}"})
|
| 540 |
+
|
| 541 |
+
origin = entry.get("origin", {})
|
| 542 |
+
return json.dumps({
|
| 543 |
+
"session_key": session_key,
|
| 544 |
+
"session_id": entry.get("session_id", ""),
|
| 545 |
+
"platform": entry.get("platform") or origin.get("platform", ""),
|
| 546 |
+
"chat_type": entry.get("chat_type", origin.get("chat_type", "")),
|
| 547 |
+
"display_name": entry.get("display_name", ""),
|
| 548 |
+
"user_name": origin.get("user_name", ""),
|
| 549 |
+
"chat_name": origin.get("chat_name", ""),
|
| 550 |
+
"chat_id": origin.get("chat_id", ""),
|
| 551 |
+
"thread_id": origin.get("thread_id"),
|
| 552 |
+
"updated_at": entry.get("updated_at", ""),
|
| 553 |
+
"created_at": entry.get("created_at", ""),
|
| 554 |
+
"input_tokens": entry.get("input_tokens", 0),
|
| 555 |
+
"output_tokens": entry.get("output_tokens", 0),
|
| 556 |
+
"total_tokens": entry.get("total_tokens", 0),
|
| 557 |
+
}, indent=2)
|
| 558 |
+
|
| 559 |
+
# -- messages_read -----------------------------------------------------
|
| 560 |
+
|
| 561 |
+
@mcp.tool()
|
| 562 |
+
def messages_read(
|
| 563 |
+
session_key: str,
|
| 564 |
+
limit: int = 50,
|
| 565 |
+
) -> str:
|
| 566 |
+
"""Read recent messages from a conversation.
|
| 567 |
+
|
| 568 |
+
Returns the message history in chronological order with role, content,
|
| 569 |
+
and timestamp for each message.
|
| 570 |
+
|
| 571 |
+
Args:
|
| 572 |
+
session_key: The session key from conversations_list
|
| 573 |
+
limit: Maximum number of messages to return (default 50, most recent)
|
| 574 |
+
"""
|
| 575 |
+
limit = _coerce_int(limit, default=50, minimum=1, maximum=200)
|
| 576 |
+
entries = _load_sessions_index()
|
| 577 |
+
entry = entries.get(session_key)
|
| 578 |
+
if not entry:
|
| 579 |
+
return json.dumps({"error": f"Conversation not found: {session_key}"})
|
| 580 |
+
|
| 581 |
+
session_id = entry.get("session_id", "")
|
| 582 |
+
if not session_id:
|
| 583 |
+
return json.dumps({"error": "No session ID for this conversation"})
|
| 584 |
+
|
| 585 |
+
db = _get_session_db()
|
| 586 |
+
if not db:
|
| 587 |
+
return json.dumps({"error": "Session database unavailable"})
|
| 588 |
+
|
| 589 |
+
try:
|
| 590 |
+
all_messages = db.get_messages(session_id)
|
| 591 |
+
except Exception as e:
|
| 592 |
+
return json.dumps({"error": f"Failed to read messages: {e}"})
|
| 593 |
+
|
| 594 |
+
filtered = []
|
| 595 |
+
for msg in all_messages:
|
| 596 |
+
role = msg.get("role", "")
|
| 597 |
+
if role in ("user", "assistant"):
|
| 598 |
+
content = _extract_message_content(msg)
|
| 599 |
+
if content:
|
| 600 |
+
filtered.append({
|
| 601 |
+
"id": str(msg.get("id", "")),
|
| 602 |
+
"role": role,
|
| 603 |
+
"content": content[:2000],
|
| 604 |
+
"timestamp": msg.get("timestamp", ""),
|
| 605 |
+
})
|
| 606 |
+
|
| 607 |
+
messages = filtered[-limit:]
|
| 608 |
+
|
| 609 |
+
return json.dumps({
|
| 610 |
+
"session_key": session_key,
|
| 611 |
+
"count": len(messages),
|
| 612 |
+
"total_in_session": len(filtered),
|
| 613 |
+
"messages": messages,
|
| 614 |
+
}, indent=2)
|
| 615 |
+
|
| 616 |
+
# -- attachments_fetch -------------------------------------------------
|
| 617 |
+
|
| 618 |
+
@mcp.tool()
|
| 619 |
+
def attachments_fetch(
|
| 620 |
+
session_key: str,
|
| 621 |
+
message_id: str,
|
| 622 |
+
) -> str:
|
| 623 |
+
"""List non-text attachments for a message in a conversation.
|
| 624 |
+
|
| 625 |
+
Extracts images, media files, and other non-text content blocks
|
| 626 |
+
from the specified message.
|
| 627 |
+
|
| 628 |
+
Args:
|
| 629 |
+
session_key: The session key from conversations_list
|
| 630 |
+
message_id: The message ID from messages_read
|
| 631 |
+
"""
|
| 632 |
+
entries = _load_sessions_index()
|
| 633 |
+
entry = entries.get(session_key)
|
| 634 |
+
if not entry:
|
| 635 |
+
return json.dumps({"error": f"Conversation not found: {session_key}"})
|
| 636 |
+
|
| 637 |
+
session_id = entry.get("session_id", "")
|
| 638 |
+
if not session_id:
|
| 639 |
+
return json.dumps({"error": "No session ID for this conversation"})
|
| 640 |
+
|
| 641 |
+
db = _get_session_db()
|
| 642 |
+
if not db:
|
| 643 |
+
return json.dumps({"error": "Session database unavailable"})
|
| 644 |
+
|
| 645 |
+
try:
|
| 646 |
+
all_messages = db.get_messages(session_id)
|
| 647 |
+
except Exception as e:
|
| 648 |
+
return json.dumps({"error": f"Failed to read messages: {e}"})
|
| 649 |
+
|
| 650 |
+
# Find the target message
|
| 651 |
+
target_msg = None
|
| 652 |
+
for msg in all_messages:
|
| 653 |
+
if str(msg.get("id", "")) == message_id:
|
| 654 |
+
target_msg = msg
|
| 655 |
+
break
|
| 656 |
+
|
| 657 |
+
if not target_msg:
|
| 658 |
+
return json.dumps({"error": f"Message not found: {message_id}"})
|
| 659 |
+
|
| 660 |
+
attachments = _extract_attachments(target_msg)
|
| 661 |
+
|
| 662 |
+
return json.dumps({
|
| 663 |
+
"message_id": message_id,
|
| 664 |
+
"count": len(attachments),
|
| 665 |
+
"attachments": attachments,
|
| 666 |
+
}, indent=2)
|
| 667 |
+
|
| 668 |
+
# -- events_poll -------------------------------------------------------
|
| 669 |
+
|
| 670 |
+
@mcp.tool()
|
| 671 |
+
def events_poll(
|
| 672 |
+
after_cursor: int = 0,
|
| 673 |
+
session_key: Optional[str] = None,
|
| 674 |
+
limit: int = 20,
|
| 675 |
+
) -> str:
|
| 676 |
+
"""Poll for new conversation events since a cursor position.
|
| 677 |
+
|
| 678 |
+
Returns events that have occurred since the given cursor. Use the
|
| 679 |
+
returned next_cursor value for subsequent polls.
|
| 680 |
+
|
| 681 |
+
Event types: message, approval_requested, approval_resolved
|
| 682 |
+
|
| 683 |
+
Args:
|
| 684 |
+
after_cursor: Return events after this cursor (0 for all)
|
| 685 |
+
session_key: Optional filter to one conversation
|
| 686 |
+
limit: Maximum events to return (default 20)
|
| 687 |
+
"""
|
| 688 |
+
after_cursor = _coerce_int(after_cursor, default=0, minimum=0, maximum=10**18)
|
| 689 |
+
limit = _coerce_int(limit, default=20, minimum=1, maximum=200)
|
| 690 |
+
result = bridge.poll_events(
|
| 691 |
+
after_cursor=after_cursor,
|
| 692 |
+
session_key=session_key,
|
| 693 |
+
limit=limit,
|
| 694 |
+
)
|
| 695 |
+
return json.dumps(result, indent=2)
|
| 696 |
+
|
| 697 |
+
# -- events_wait -------------------------------------------------------
|
| 698 |
+
|
| 699 |
+
@mcp.tool()
|
| 700 |
+
def events_wait(
|
| 701 |
+
after_cursor: int = 0,
|
| 702 |
+
session_key: Optional[str] = None,
|
| 703 |
+
timeout_ms: int = 30000,
|
| 704 |
+
) -> str:
|
| 705 |
+
"""Wait for the next conversation event (long-poll).
|
| 706 |
+
|
| 707 |
+
Blocks until a matching event arrives or the timeout expires.
|
| 708 |
+
Use this for near-real-time event delivery without polling.
|
| 709 |
+
|
| 710 |
+
Args:
|
| 711 |
+
after_cursor: Wait for events after this cursor
|
| 712 |
+
session_key: Optional filter to one conversation
|
| 713 |
+
timeout_ms: Maximum wait time in milliseconds (default 30000)
|
| 714 |
+
"""
|
| 715 |
+
after_cursor = _coerce_int(after_cursor, default=0, minimum=0, maximum=10**18)
|
| 716 |
+
timeout_ms = _coerce_int(
|
| 717 |
+
timeout_ms,
|
| 718 |
+
default=30000,
|
| 719 |
+
minimum=0,
|
| 720 |
+
maximum=300000,
|
| 721 |
+
) # Cap at 5 minutes
|
| 722 |
+
event = bridge.wait_for_event(
|
| 723 |
+
after_cursor=after_cursor,
|
| 724 |
+
session_key=session_key,
|
| 725 |
+
timeout_ms=timeout_ms,
|
| 726 |
+
)
|
| 727 |
+
if event:
|
| 728 |
+
return json.dumps({"event": event}, indent=2)
|
| 729 |
+
return json.dumps({"event": None, "reason": "timeout"}, indent=2)
|
| 730 |
+
|
| 731 |
+
# -- messages_send -----------------------------------------------------
|
| 732 |
+
|
| 733 |
+
@mcp.tool()
|
| 734 |
+
def messages_send(
|
| 735 |
+
target: str,
|
| 736 |
+
message: str,
|
| 737 |
+
) -> str:
|
| 738 |
+
"""Send a message to a platform conversation.
|
| 739 |
+
|
| 740 |
+
The target format is "platform:chat_id" — same format used by the
|
| 741 |
+
channels_list tool. You can also use human-friendly channel names
|
| 742 |
+
that will be resolved automatically.
|
| 743 |
+
|
| 744 |
+
Examples:
|
| 745 |
+
target="telegram:6308981865"
|
| 746 |
+
target="discord:#general"
|
| 747 |
+
target="slack:#engineering"
|
| 748 |
+
|
| 749 |
+
Args:
|
| 750 |
+
target: Platform target in "platform:identifier" format
|
| 751 |
+
message: The message text to send
|
| 752 |
+
"""
|
| 753 |
+
if not target or not message:
|
| 754 |
+
return json.dumps({"error": "Both target and message are required"})
|
| 755 |
+
|
| 756 |
+
try:
|
| 757 |
+
from tools.send_message_tool import send_message_tool
|
| 758 |
+
result_str = send_message_tool(
|
| 759 |
+
{"action": "send", "target": target, "message": message}
|
| 760 |
+
)
|
| 761 |
+
return result_str
|
| 762 |
+
except ImportError:
|
| 763 |
+
return json.dumps({"error": "Send message tool not available"})
|
| 764 |
+
except Exception as e:
|
| 765 |
+
return json.dumps({"error": f"Send failed: {e}"})
|
| 766 |
+
|
| 767 |
+
# -- channels_list -----------------------------------------------------
|
| 768 |
+
|
| 769 |
+
@mcp.tool()
|
| 770 |
+
def channels_list(platform: Optional[str] = None) -> str:
|
| 771 |
+
"""List available messaging channels and targets across platforms.
|
| 772 |
+
|
| 773 |
+
Returns channels that you can send messages to. The target strings
|
| 774 |
+
returned here can be used directly with the messages_send tool.
|
| 775 |
+
|
| 776 |
+
Args:
|
| 777 |
+
platform: Filter by platform name (telegram, discord, slack, etc.)
|
| 778 |
+
"""
|
| 779 |
+
directory = _load_channel_directory()
|
| 780 |
+
if not directory:
|
| 781 |
+
entries = _load_sessions_index()
|
| 782 |
+
targets = []
|
| 783 |
+
seen = set()
|
| 784 |
+
for key, entry in entries.items():
|
| 785 |
+
origin = entry.get("origin", {})
|
| 786 |
+
p = entry.get("platform") or origin.get("platform", "")
|
| 787 |
+
chat_id = origin.get("chat_id", "")
|
| 788 |
+
if not p or not chat_id:
|
| 789 |
+
continue
|
| 790 |
+
if platform and p.lower() != platform.lower():
|
| 791 |
+
continue
|
| 792 |
+
target_str = f"{p}:{chat_id}"
|
| 793 |
+
if target_str in seen:
|
| 794 |
+
continue
|
| 795 |
+
seen.add(target_str)
|
| 796 |
+
targets.append({
|
| 797 |
+
"target": target_str,
|
| 798 |
+
"platform": p,
|
| 799 |
+
"name": entry.get("display_name") or origin.get("chat_name", ""),
|
| 800 |
+
"chat_type": entry.get("chat_type", origin.get("chat_type", "")),
|
| 801 |
+
})
|
| 802 |
+
return json.dumps({"count": len(targets), "channels": targets}, indent=2)
|
| 803 |
+
|
| 804 |
+
channels = []
|
| 805 |
+
for plat, entries_list in directory.get("platforms", {}).items():
|
| 806 |
+
if platform and plat.lower() != platform.lower():
|
| 807 |
+
continue
|
| 808 |
+
if isinstance(entries_list, list):
|
| 809 |
+
for ch in entries_list:
|
| 810 |
+
if isinstance(ch, dict):
|
| 811 |
+
chat_id = ch.get("id", ch.get("chat_id", ""))
|
| 812 |
+
channels.append({
|
| 813 |
+
"target": f"{plat}:{chat_id}" if chat_id else plat,
|
| 814 |
+
"platform": plat,
|
| 815 |
+
"name": ch.get("name", ch.get("display_name", "")),
|
| 816 |
+
"chat_type": ch.get("type", ""),
|
| 817 |
+
})
|
| 818 |
+
|
| 819 |
+
return json.dumps({"count": len(channels), "channels": channels}, indent=2)
|
| 820 |
+
|
| 821 |
+
# -- permissions_list_open ---------------------------------------------
|
| 822 |
+
|
| 823 |
+
@mcp.tool()
|
| 824 |
+
def permissions_list_open() -> str:
|
| 825 |
+
"""List pending approval requests observed during this bridge session.
|
| 826 |
+
|
| 827 |
+
Returns exec and plugin approval requests that the bridge has seen
|
| 828 |
+
since it started. Approvals are live-session only — older approvals
|
| 829 |
+
from before the bridge connected are not included.
|
| 830 |
+
"""
|
| 831 |
+
approvals = bridge.list_pending_approvals()
|
| 832 |
+
return json.dumps({
|
| 833 |
+
"count": len(approvals),
|
| 834 |
+
"approvals": approvals,
|
| 835 |
+
}, indent=2)
|
| 836 |
+
|
| 837 |
+
# -- permissions_respond -----------------------------------------------
|
| 838 |
+
|
| 839 |
+
@mcp.tool()
|
| 840 |
+
def permissions_respond(
|
| 841 |
+
id: str,
|
| 842 |
+
decision: str,
|
| 843 |
+
) -> str:
|
| 844 |
+
"""Respond to a pending approval request.
|
| 845 |
+
|
| 846 |
+
Args:
|
| 847 |
+
id: The approval ID from permissions_list_open
|
| 848 |
+
decision: One of "allow-once", "allow-always", or "deny"
|
| 849 |
+
"""
|
| 850 |
+
if decision not in ("allow-once", "allow-always", "deny"):
|
| 851 |
+
return json.dumps({
|
| 852 |
+
"error": f"Invalid decision: {decision}. "
|
| 853 |
+
f"Must be allow-once, allow-always, or deny"
|
| 854 |
+
})
|
| 855 |
+
|
| 856 |
+
result = bridge.respond_to_approval(id, decision)
|
| 857 |
+
return json.dumps(result, indent=2)
|
| 858 |
+
|
| 859 |
+
return mcp
|
| 860 |
+
|
| 861 |
+
|
| 862 |
+
# ---------------------------------------------------------------------------
|
| 863 |
+
# Entry point
|
| 864 |
+
# ---------------------------------------------------------------------------
|
| 865 |
+
|
| 866 |
+
def run_mcp_server(verbose: bool = False) -> None:
|
| 867 |
+
"""Start the Hermes MCP server on stdio."""
|
| 868 |
+
if not _MCP_SERVER_AVAILABLE:
|
| 869 |
+
print(
|
| 870 |
+
"Error: MCP server requires the 'mcp' package.\n"
|
| 871 |
+
f"Install with: {sys.executable} -m pip install 'mcp'",
|
| 872 |
+
file=sys.stderr,
|
| 873 |
+
)
|
| 874 |
+
sys.exit(1)
|
| 875 |
+
|
| 876 |
+
if verbose:
|
| 877 |
+
logging.basicConfig(level=logging.DEBUG, stream=sys.stderr)
|
| 878 |
+
else:
|
| 879 |
+
logging.basicConfig(level=logging.WARNING, stream=sys.stderr)
|
| 880 |
+
|
| 881 |
+
bridge = EventBridge()
|
| 882 |
+
bridge.start()
|
| 883 |
+
|
| 884 |
+
server = create_mcp_server(event_bridge=bridge)
|
| 885 |
+
|
| 886 |
+
import asyncio
|
| 887 |
+
|
| 888 |
+
async def _run():
|
| 889 |
+
try:
|
| 890 |
+
await server.run_stdio_async()
|
| 891 |
+
finally:
|
| 892 |
+
bridge.stop()
|
| 893 |
+
|
| 894 |
+
try:
|
| 895 |
+
asyncio.run(_run())
|
| 896 |
+
except KeyboardInterrupt:
|
| 897 |
+
bridge.stop()
|
mini_swe_runner.py
ADDED
|
@@ -0,0 +1,736 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
SWE Runner with Hermes Trajectory Format
|
| 4 |
+
|
| 5 |
+
A runner that uses Hermes-Agent's built-in execution environments
|
| 6 |
+
(local, docker, modal) and outputs trajectories in the Hermes-Agent format
|
| 7 |
+
compatible with batch_runner.py and trajectory_compressor.py.
|
| 8 |
+
|
| 9 |
+
Features:
|
| 10 |
+
- Uses Hermes-Agent's Docker, Modal, or Local environments for command execution
|
| 11 |
+
- Outputs trajectories in Hermes format (from/value pairs with <tool_call>/<tool_response> XML)
|
| 12 |
+
- Compatible with the trajectory compression pipeline
|
| 13 |
+
- Supports batch processing from JSONL prompt files
|
| 14 |
+
|
| 15 |
+
Usage:
|
| 16 |
+
# Run a single task with local environment
|
| 17 |
+
python mini_swe_runner.py --task "Create a hello world Python script" --env local
|
| 18 |
+
|
| 19 |
+
# Run with Docker
|
| 20 |
+
python mini_swe_runner.py --task "List files in /tmp" --env docker --image python:3.11-slim
|
| 21 |
+
|
| 22 |
+
# Run with Modal (cloud)
|
| 23 |
+
python mini_swe_runner.py --task "Install numpy and test it" --env modal --image python:3.11-slim
|
| 24 |
+
|
| 25 |
+
# Batch mode from JSONL file
|
| 26 |
+
python mini_swe_runner.py --prompts_file prompts.jsonl --output_file trajectories.jsonl --env docker
|
| 27 |
+
"""
|
| 28 |
+
|
| 29 |
+
import json
|
| 30 |
+
import logging
|
| 31 |
+
import os
|
| 32 |
+
import sys
|
| 33 |
+
import time
|
| 34 |
+
import uuid
|
| 35 |
+
from datetime import datetime
|
| 36 |
+
from pathlib import Path
|
| 37 |
+
from typing import List, Dict, Any, Optional, Literal
|
| 38 |
+
|
| 39 |
+
import fire
|
| 40 |
+
from dotenv import load_dotenv
|
| 41 |
+
|
| 42 |
+
# Load environment variables
|
| 43 |
+
load_dotenv()
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
def _effective_temperature_for_model(
|
| 47 |
+
model: str,
|
| 48 |
+
base_url: Optional[str] = None,
|
| 49 |
+
) -> Optional[float]:
|
| 50 |
+
"""Return a fixed temperature for models with strict sampling contracts.
|
| 51 |
+
|
| 52 |
+
Returns ``None`` when the model manages temperature server-side (Kimi);
|
| 53 |
+
callers must omit the ``temperature`` kwarg entirely in that case.
|
| 54 |
+
"""
|
| 55 |
+
try:
|
| 56 |
+
from agent.auxiliary_client import _fixed_temperature_for_model, OMIT_TEMPERATURE
|
| 57 |
+
except Exception:
|
| 58 |
+
return None
|
| 59 |
+
result = _fixed_temperature_for_model(model, base_url)
|
| 60 |
+
if result is OMIT_TEMPERATURE:
|
| 61 |
+
return None # caller must omit temperature
|
| 62 |
+
return result
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
# ============================================================================
|
| 68 |
+
# Terminal Tool Definition (matches Hermes-Agent format)
|
| 69 |
+
# ============================================================================
|
| 70 |
+
|
| 71 |
+
TERMINAL_TOOL_DEFINITION = {
|
| 72 |
+
"type": "function",
|
| 73 |
+
"function": {
|
| 74 |
+
"name": "terminal",
|
| 75 |
+
"description": """Execute bash commands in a sandboxed environment.
|
| 76 |
+
|
| 77 |
+
**Environment:**
|
| 78 |
+
- Isolated execution environment (local, Docker, or Modal cloud)
|
| 79 |
+
- Filesystem persists between tool calls within the same task
|
| 80 |
+
- Internet access available
|
| 81 |
+
|
| 82 |
+
**Command Execution:**
|
| 83 |
+
- Provide the command to execute via the 'command' parameter
|
| 84 |
+
- Optional 'timeout' parameter in seconds (default: 60)
|
| 85 |
+
|
| 86 |
+
**Examples:**
|
| 87 |
+
- Run command: `{"command": "ls -la"}`
|
| 88 |
+
- With timeout: `{"command": "long_task.sh", "timeout": 300}`
|
| 89 |
+
|
| 90 |
+
**Best Practices:**
|
| 91 |
+
- Use non-interactive commands (avoid vim, nano, interactive python)
|
| 92 |
+
- Pipe to cat if output might be large
|
| 93 |
+
- Install tools with apt-get or pip as needed
|
| 94 |
+
|
| 95 |
+
**Completion:**
|
| 96 |
+
- When task is complete, output: echo "MINI_SWE_AGENT_FINAL_OUTPUT" followed by your result
|
| 97 |
+
""",
|
| 98 |
+
"parameters": {
|
| 99 |
+
"type": "object",
|
| 100 |
+
"properties": {
|
| 101 |
+
"command": {
|
| 102 |
+
"type": "string",
|
| 103 |
+
"description": "The bash command to execute"
|
| 104 |
+
},
|
| 105 |
+
"timeout": {
|
| 106 |
+
"type": "integer",
|
| 107 |
+
"description": "Command timeout in seconds (default: 60)"
|
| 108 |
+
}
|
| 109 |
+
},
|
| 110 |
+
"required": ["command"]
|
| 111 |
+
}
|
| 112 |
+
}
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
|
| 116 |
+
# ============================================================================
|
| 117 |
+
# Environment Factory
|
| 118 |
+
# ============================================================================
|
| 119 |
+
|
| 120 |
+
def create_environment(
|
| 121 |
+
env_type: str = "local",
|
| 122 |
+
image: str = "python:3.11-slim",
|
| 123 |
+
cwd: str = "/tmp",
|
| 124 |
+
timeout: int = 60,
|
| 125 |
+
**kwargs
|
| 126 |
+
):
|
| 127 |
+
"""
|
| 128 |
+
Create an execution environment using Hermes-Agent's built-in backends.
|
| 129 |
+
|
| 130 |
+
Args:
|
| 131 |
+
env_type: One of "local", "docker", "modal"
|
| 132 |
+
image: Docker/Modal image name (ignored for local)
|
| 133 |
+
cwd: Working directory
|
| 134 |
+
timeout: Default command timeout
|
| 135 |
+
**kwargs: Additional environment-specific options
|
| 136 |
+
|
| 137 |
+
Returns:
|
| 138 |
+
Environment instance with execute() and cleanup() methods
|
| 139 |
+
"""
|
| 140 |
+
if env_type == "local":
|
| 141 |
+
from tools.environments.local import LocalEnvironment
|
| 142 |
+
return LocalEnvironment(cwd=cwd, timeout=timeout)
|
| 143 |
+
|
| 144 |
+
elif env_type == "docker":
|
| 145 |
+
from tools.environments.docker import DockerEnvironment
|
| 146 |
+
return DockerEnvironment(image=image, cwd=cwd, timeout=timeout, **kwargs)
|
| 147 |
+
|
| 148 |
+
elif env_type == "modal":
|
| 149 |
+
from tools.environments.modal import ModalEnvironment
|
| 150 |
+
return ModalEnvironment(image=image, cwd=cwd, timeout=timeout, **kwargs)
|
| 151 |
+
|
| 152 |
+
else:
|
| 153 |
+
raise ValueError(f"Unknown environment type: {env_type}. Use 'local', 'docker', or 'modal'")
|
| 154 |
+
|
| 155 |
+
|
| 156 |
+
# ============================================================================
|
| 157 |
+
# Mini-SWE Runner with Hermes Trajectory Format
|
| 158 |
+
# ============================================================================
|
| 159 |
+
|
| 160 |
+
class MiniSWERunner:
|
| 161 |
+
"""
|
| 162 |
+
Agent runner that uses Hermes-Agent's built-in execution environments
|
| 163 |
+
and outputs trajectories in Hermes-Agent format.
|
| 164 |
+
"""
|
| 165 |
+
|
| 166 |
+
def __init__(
|
| 167 |
+
self,
|
| 168 |
+
model: str = "anthropic/claude-sonnet-4.6",
|
| 169 |
+
base_url: str = None,
|
| 170 |
+
api_key: str = None,
|
| 171 |
+
env_type: str = "local",
|
| 172 |
+
image: str = "python:3.11-slim",
|
| 173 |
+
cwd: str = "/tmp",
|
| 174 |
+
max_iterations: int = 15,
|
| 175 |
+
command_timeout: int = 60,
|
| 176 |
+
verbose: bool = False,
|
| 177 |
+
):
|
| 178 |
+
"""
|
| 179 |
+
Initialize the Mini-SWE Runner.
|
| 180 |
+
|
| 181 |
+
Args:
|
| 182 |
+
model: Model name for OpenAI-compatible API
|
| 183 |
+
base_url: API base URL (optional, uses env vars if not provided)
|
| 184 |
+
api_key: API key (optional, uses env vars if not provided)
|
| 185 |
+
env_type: Environment type - "local", "docker", or "modal"
|
| 186 |
+
image: Docker/Modal image (ignored for local)
|
| 187 |
+
cwd: Working directory for commands
|
| 188 |
+
max_iterations: Maximum tool-calling iterations
|
| 189 |
+
command_timeout: Default timeout for commands
|
| 190 |
+
verbose: Enable verbose logging
|
| 191 |
+
"""
|
| 192 |
+
self.model = model
|
| 193 |
+
self.max_iterations = max_iterations
|
| 194 |
+
self.command_timeout = command_timeout
|
| 195 |
+
self.verbose = verbose
|
| 196 |
+
self.env_type = env_type
|
| 197 |
+
self.image = image
|
| 198 |
+
self.cwd = cwd
|
| 199 |
+
|
| 200 |
+
# Setup logging
|
| 201 |
+
logging.basicConfig(
|
| 202 |
+
level=logging.DEBUG if verbose else logging.INFO,
|
| 203 |
+
format='%(asctime)s - %(levelname)s - %(message)s',
|
| 204 |
+
datefmt='%H:%M:%S'
|
| 205 |
+
)
|
| 206 |
+
self.logger = logging.getLogger(__name__)
|
| 207 |
+
|
| 208 |
+
# Initialize LLM client via centralized provider router.
|
| 209 |
+
# If explicit api_key/base_url are provided (e.g. from CLI args),
|
| 210 |
+
# construct directly. Otherwise use the router for OpenRouter.
|
| 211 |
+
if api_key or base_url:
|
| 212 |
+
from openai import OpenAI
|
| 213 |
+
client_kwargs = {
|
| 214 |
+
"base_url": base_url or "https://openrouter.ai/api/v1",
|
| 215 |
+
"api_key": api_key or os.getenv(
|
| 216 |
+
"OPENROUTER_API_KEY",
|
| 217 |
+
os.getenv("ANTHROPIC_API_KEY",
|
| 218 |
+
os.getenv("OPENAI_API_KEY", ""))),
|
| 219 |
+
}
|
| 220 |
+
self.client = OpenAI(**client_kwargs)
|
| 221 |
+
else:
|
| 222 |
+
from agent.auxiliary_client import resolve_provider_client
|
| 223 |
+
self.client, _ = resolve_provider_client("openrouter", model=model)
|
| 224 |
+
if self.client is None:
|
| 225 |
+
# Fallback: try auto-detection
|
| 226 |
+
self.client, _ = resolve_provider_client("auto", model=model)
|
| 227 |
+
if self.client is None:
|
| 228 |
+
from openai import OpenAI
|
| 229 |
+
self.client = OpenAI(
|
| 230 |
+
base_url="https://openrouter.ai/api/v1",
|
| 231 |
+
api_key=os.getenv("OPENROUTER_API_KEY", ""))
|
| 232 |
+
|
| 233 |
+
# Environment will be created per-task
|
| 234 |
+
self.env = None
|
| 235 |
+
|
| 236 |
+
# Tool definition
|
| 237 |
+
self.tools = [TERMINAL_TOOL_DEFINITION]
|
| 238 |
+
|
| 239 |
+
print("🤖 Mini-SWE Runner initialized")
|
| 240 |
+
print(f" Model: {self.model}")
|
| 241 |
+
print(f" Environment: {self.env_type}")
|
| 242 |
+
if self.env_type != "local":
|
| 243 |
+
print(f" Image: {self.image}")
|
| 244 |
+
print(f" Max iterations: {self.max_iterations}")
|
| 245 |
+
|
| 246 |
+
def _create_env(self):
|
| 247 |
+
"""Create the execution environment."""
|
| 248 |
+
print(f"🔧 Creating {self.env_type} environment...")
|
| 249 |
+
self.env = create_environment(
|
| 250 |
+
env_type=self.env_type,
|
| 251 |
+
image=self.image,
|
| 252 |
+
cwd=self.cwd,
|
| 253 |
+
timeout=self.command_timeout
|
| 254 |
+
)
|
| 255 |
+
print("✅ Environment ready")
|
| 256 |
+
|
| 257 |
+
def _cleanup_env(self):
|
| 258 |
+
"""Cleanup the execution environment."""
|
| 259 |
+
if self.env is not None:
|
| 260 |
+
if hasattr(self.env, 'cleanup'):
|
| 261 |
+
self.env.cleanup()
|
| 262 |
+
elif hasattr(self.env, 'stop'):
|
| 263 |
+
self.env.stop()
|
| 264 |
+
self.env = None
|
| 265 |
+
|
| 266 |
+
def _execute_command(self, command: str, timeout: int = None) -> Dict[str, Any]:
|
| 267 |
+
"""
|
| 268 |
+
Execute a command in the environment.
|
| 269 |
+
|
| 270 |
+
Args:
|
| 271 |
+
command: Bash command to execute
|
| 272 |
+
timeout: Optional timeout override
|
| 273 |
+
|
| 274 |
+
Returns:
|
| 275 |
+
Dict with 'output' and 'returncode'
|
| 276 |
+
"""
|
| 277 |
+
if self.env is None:
|
| 278 |
+
self._create_env()
|
| 279 |
+
|
| 280 |
+
try:
|
| 281 |
+
result = self.env.execute(command, timeout=timeout or self.command_timeout)
|
| 282 |
+
return {
|
| 283 |
+
"output": result.get("output", ""),
|
| 284 |
+
"exit_code": result.get("returncode", 0),
|
| 285 |
+
"error": None
|
| 286 |
+
}
|
| 287 |
+
except Exception as e:
|
| 288 |
+
return {
|
| 289 |
+
"output": "",
|
| 290 |
+
"exit_code": -1,
|
| 291 |
+
"error": str(e)
|
| 292 |
+
}
|
| 293 |
+
|
| 294 |
+
def _format_tools_for_system_message(self) -> str:
|
| 295 |
+
"""Format tool definitions for the system message."""
|
| 296 |
+
formatted_tools = []
|
| 297 |
+
for tool in self.tools:
|
| 298 |
+
func = tool["function"]
|
| 299 |
+
formatted_tools.append({
|
| 300 |
+
"name": func["name"],
|
| 301 |
+
"description": func.get("description", ""),
|
| 302 |
+
"parameters": func.get("parameters", {}),
|
| 303 |
+
"required": None
|
| 304 |
+
})
|
| 305 |
+
return json.dumps(formatted_tools, ensure_ascii=False)
|
| 306 |
+
|
| 307 |
+
def _convert_to_hermes_format(
|
| 308 |
+
self,
|
| 309 |
+
messages: List[Dict[str, Any]],
|
| 310 |
+
user_query: str,
|
| 311 |
+
completed: bool
|
| 312 |
+
) -> List[Dict[str, Any]]:
|
| 313 |
+
"""
|
| 314 |
+
Convert internal message format to Hermes trajectory format.
|
| 315 |
+
|
| 316 |
+
This produces the exact format used by batch_runner.py.
|
| 317 |
+
"""
|
| 318 |
+
trajectory = []
|
| 319 |
+
|
| 320 |
+
# System message with tool definitions
|
| 321 |
+
system_msg = (
|
| 322 |
+
"You are a function calling AI model. You are provided with function signatures within <tools> </tools> XML tags. "
|
| 323 |
+
"You may call one or more functions to assist with the user query. If available tools are not relevant in assisting "
|
| 324 |
+
"with user query, just respond in natural conversational language. Don't make assumptions about what values to plug "
|
| 325 |
+
"into functions. After calling & executing the functions, you will be provided with function results within "
|
| 326 |
+
"<tool_response> </tool_response> XML tags. Here are the available tools:\n"
|
| 327 |
+
f"<tools>\n{self._format_tools_for_system_message()}\n</tools>\n"
|
| 328 |
+
"For each function call return a JSON object, with the following pydantic model json schema for each:\n"
|
| 329 |
+
"{'title': 'FunctionCall', 'type': 'object', 'properties': {'name': {'title': 'Name', 'type': 'string'}, "
|
| 330 |
+
"'arguments': {'title': 'Arguments', 'type': 'object'}}, 'required': ['name', 'arguments']}\n"
|
| 331 |
+
"Each function call should be enclosed within <tool_call> </tool_call> XML tags.\n"
|
| 332 |
+
"Example:\n<tool_call>\n{'name': <function-name>,'arguments': <args-dict>}\n</tool_call>"
|
| 333 |
+
)
|
| 334 |
+
|
| 335 |
+
trajectory.append({"from": "system", "value": system_msg})
|
| 336 |
+
trajectory.append({"from": "human", "value": user_query})
|
| 337 |
+
|
| 338 |
+
# Process messages (skip first user message as we already added it)
|
| 339 |
+
i = 1
|
| 340 |
+
while i < len(messages):
|
| 341 |
+
msg = messages[i]
|
| 342 |
+
|
| 343 |
+
if msg["role"] == "assistant":
|
| 344 |
+
if "tool_calls" in msg and msg["tool_calls"]:
|
| 345 |
+
# Assistant message with tool calls
|
| 346 |
+
content = ""
|
| 347 |
+
|
| 348 |
+
# Add reasoning if present
|
| 349 |
+
if msg.get("reasoning"):
|
| 350 |
+
content = f"<think>{msg['reasoning']}</think>"
|
| 351 |
+
|
| 352 |
+
if msg.get("content"):
|
| 353 |
+
content += msg["content"] + "\n"
|
| 354 |
+
|
| 355 |
+
# Add tool calls in XML format
|
| 356 |
+
for tool_call in msg["tool_calls"]:
|
| 357 |
+
if not tool_call or not isinstance(tool_call, dict): continue
|
| 358 |
+
try:
|
| 359 |
+
arguments = json.loads(tool_call["function"]["arguments"]) \
|
| 360 |
+
if isinstance(tool_call["function"]["arguments"], str) \
|
| 361 |
+
else tool_call["function"]["arguments"]
|
| 362 |
+
except json.JSONDecodeError:
|
| 363 |
+
arguments = {}
|
| 364 |
+
|
| 365 |
+
tool_call_json = {
|
| 366 |
+
"name": tool_call["function"]["name"],
|
| 367 |
+
"arguments": arguments
|
| 368 |
+
}
|
| 369 |
+
content += f"<tool_call>\n{json.dumps(tool_call_json, ensure_ascii=False)}\n</tool_call>\n"
|
| 370 |
+
|
| 371 |
+
trajectory.append({"from": "gpt", "value": content.rstrip()})
|
| 372 |
+
|
| 373 |
+
# Collect subsequent tool responses
|
| 374 |
+
tool_responses = []
|
| 375 |
+
j = i + 1
|
| 376 |
+
while j < len(messages) and messages[j]["role"] == "tool":
|
| 377 |
+
tool_msg = messages[j]
|
| 378 |
+
tool_content = tool_msg["content"]
|
| 379 |
+
|
| 380 |
+
# Try to parse as JSON
|
| 381 |
+
try:
|
| 382 |
+
if tool_content.strip().startswith(("{", "[")):
|
| 383 |
+
tool_content = json.loads(tool_content)
|
| 384 |
+
except (json.JSONDecodeError, AttributeError):
|
| 385 |
+
pass
|
| 386 |
+
|
| 387 |
+
tool_response = "<tool_response>\n"
|
| 388 |
+
tool_response += json.dumps({
|
| 389 |
+
"tool_call_id": tool_msg.get("tool_call_id", ""),
|
| 390 |
+
"name": msg["tool_calls"][len(tool_responses)]["function"]["name"] \
|
| 391 |
+
if len(tool_responses) < len(msg["tool_calls"]) else "unknown",
|
| 392 |
+
"content": tool_content
|
| 393 |
+
}, ensure_ascii=False)
|
| 394 |
+
tool_response += "\n</tool_response>"
|
| 395 |
+
tool_responses.append(tool_response)
|
| 396 |
+
j += 1
|
| 397 |
+
|
| 398 |
+
if tool_responses:
|
| 399 |
+
trajectory.append({"from": "tool", "value": "\n".join(tool_responses)})
|
| 400 |
+
i = j - 1
|
| 401 |
+
|
| 402 |
+
else:
|
| 403 |
+
# Regular assistant message (no tool calls)
|
| 404 |
+
content = ""
|
| 405 |
+
if msg.get("reasoning"):
|
| 406 |
+
content = f"<think>{msg['reasoning']}</think>"
|
| 407 |
+
content += msg.get("content") or ""
|
| 408 |
+
trajectory.append({"from": "gpt", "value": content})
|
| 409 |
+
|
| 410 |
+
elif msg["role"] == "user":
|
| 411 |
+
trajectory.append({"from": "human", "value": msg["content"]})
|
| 412 |
+
|
| 413 |
+
i += 1
|
| 414 |
+
|
| 415 |
+
return trajectory
|
| 416 |
+
|
| 417 |
+
def run_task(self, task: str) -> Dict[str, Any]:
|
| 418 |
+
"""
|
| 419 |
+
Run a single task and return the result with trajectory.
|
| 420 |
+
|
| 421 |
+
Args:
|
| 422 |
+
task: The task/prompt to execute
|
| 423 |
+
|
| 424 |
+
Returns:
|
| 425 |
+
Dict with trajectory, completion status, and metadata
|
| 426 |
+
"""
|
| 427 |
+
print(f"\n{'='*60}")
|
| 428 |
+
print(f"📝 Task: {task[:80]}{'...' if len(task) > 80 else ''}")
|
| 429 |
+
print(f"{'='*60}")
|
| 430 |
+
|
| 431 |
+
# Initialize environment
|
| 432 |
+
self._create_env()
|
| 433 |
+
|
| 434 |
+
# Message history
|
| 435 |
+
messages = [{"role": "user", "content": task}]
|
| 436 |
+
|
| 437 |
+
# System prompt for the LLM (ephemeral - not saved to trajectory)
|
| 438 |
+
system_prompt = """You are an AI agent that can execute bash commands to complete tasks.
|
| 439 |
+
|
| 440 |
+
When you need to run commands, use the 'terminal' tool with your bash command.
|
| 441 |
+
|
| 442 |
+
**Important:**
|
| 443 |
+
- When you have completed the task successfully, run: echo "MINI_SWE_AGENT_FINAL_OUTPUT" followed by a summary
|
| 444 |
+
- Be concise and efficient in your approach
|
| 445 |
+
- Install any needed tools with apt-get or pip
|
| 446 |
+
- Avoid interactive commands (no vim, nano, less, etc.)
|
| 447 |
+
|
| 448 |
+
Complete the user's task step by step."""
|
| 449 |
+
|
| 450 |
+
api_call_count = 0
|
| 451 |
+
completed = False
|
| 452 |
+
final_response = None
|
| 453 |
+
|
| 454 |
+
try:
|
| 455 |
+
while api_call_count < self.max_iterations:
|
| 456 |
+
api_call_count += 1
|
| 457 |
+
print(f"\n🔄 API call #{api_call_count}/{self.max_iterations}")
|
| 458 |
+
|
| 459 |
+
# Prepare API messages
|
| 460 |
+
api_messages = [{"role": "system", "content": system_prompt}] + messages
|
| 461 |
+
|
| 462 |
+
# Make API call
|
| 463 |
+
try:
|
| 464 |
+
api_kwargs = {
|
| 465 |
+
"model": self.model,
|
| 466 |
+
"messages": api_messages,
|
| 467 |
+
"tools": self.tools,
|
| 468 |
+
"timeout": 300.0,
|
| 469 |
+
}
|
| 470 |
+
fixed_temperature = _effective_temperature_for_model(
|
| 471 |
+
self.model,
|
| 472 |
+
str(getattr(self.client, "base_url", "") or ""),
|
| 473 |
+
)
|
| 474 |
+
if fixed_temperature is not None:
|
| 475 |
+
api_kwargs["temperature"] = fixed_temperature
|
| 476 |
+
|
| 477 |
+
response = self.client.chat.completions.create(**api_kwargs)
|
| 478 |
+
except Exception as e:
|
| 479 |
+
self.logger.error(f"API call failed: {e}")
|
| 480 |
+
break
|
| 481 |
+
|
| 482 |
+
assistant_message = response.choices[0].message
|
| 483 |
+
|
| 484 |
+
# Log assistant response
|
| 485 |
+
if assistant_message.content:
|
| 486 |
+
print(f"🤖 Assistant: {assistant_message.content[:100]}...")
|
| 487 |
+
|
| 488 |
+
# Check for tool calls
|
| 489 |
+
if assistant_message.tool_calls:
|
| 490 |
+
print(f"🔧 Tool calls: {len(assistant_message.tool_calls)}")
|
| 491 |
+
|
| 492 |
+
# Add assistant message with tool calls
|
| 493 |
+
messages.append({
|
| 494 |
+
"role": "assistant",
|
| 495 |
+
"content": assistant_message.content,
|
| 496 |
+
"tool_calls": [
|
| 497 |
+
{
|
| 498 |
+
"id": tc.id,
|
| 499 |
+
"type": tc.type,
|
| 500 |
+
"function": {
|
| 501 |
+
"name": tc.function.name,
|
| 502 |
+
"arguments": tc.function.arguments
|
| 503 |
+
}
|
| 504 |
+
}
|
| 505 |
+
for tc in assistant_message.tool_calls
|
| 506 |
+
]
|
| 507 |
+
})
|
| 508 |
+
|
| 509 |
+
# Execute each tool call
|
| 510 |
+
for tc in assistant_message.tool_calls:
|
| 511 |
+
try:
|
| 512 |
+
args = json.loads(tc.function.arguments)
|
| 513 |
+
except json.JSONDecodeError:
|
| 514 |
+
args = {}
|
| 515 |
+
|
| 516 |
+
command = args.get("command", "echo 'No command provided'")
|
| 517 |
+
timeout = args.get("timeout", self.command_timeout)
|
| 518 |
+
|
| 519 |
+
print(f" 📞 terminal: {command[:60]}...")
|
| 520 |
+
|
| 521 |
+
# Execute command
|
| 522 |
+
result = self._execute_command(command, timeout)
|
| 523 |
+
|
| 524 |
+
# Format result
|
| 525 |
+
result_json = json.dumps({
|
| 526 |
+
"content": {
|
| 527 |
+
"output": result["output"],
|
| 528 |
+
"exit_code": result["exit_code"],
|
| 529 |
+
"error": result["error"]
|
| 530 |
+
}
|
| 531 |
+
}, ensure_ascii=False)
|
| 532 |
+
|
| 533 |
+
# Check for task completion signal
|
| 534 |
+
if "MINI_SWE_AGENT_FINAL_OUTPUT" in result["output"]:
|
| 535 |
+
print(" ✅ Task completion signal detected!")
|
| 536 |
+
completed = True
|
| 537 |
+
|
| 538 |
+
# Add tool response
|
| 539 |
+
messages.append({
|
| 540 |
+
"role": "tool",
|
| 541 |
+
"content": result_json,
|
| 542 |
+
"tool_call_id": tc.id
|
| 543 |
+
})
|
| 544 |
+
|
| 545 |
+
print(f" ✅ exit_code={result['exit_code']}, output={len(result['output'])} chars")
|
| 546 |
+
|
| 547 |
+
# If task completed, we can stop
|
| 548 |
+
if completed:
|
| 549 |
+
final_response = assistant_message.content
|
| 550 |
+
break
|
| 551 |
+
|
| 552 |
+
else:
|
| 553 |
+
# No tool calls - final response
|
| 554 |
+
final_response = assistant_message.content or ""
|
| 555 |
+
messages.append({
|
| 556 |
+
"role": "assistant",
|
| 557 |
+
"content": final_response
|
| 558 |
+
})
|
| 559 |
+
completed = True
|
| 560 |
+
print("🎉 Agent finished (no more tool calls)")
|
| 561 |
+
break
|
| 562 |
+
|
| 563 |
+
if api_call_count >= self.max_iterations:
|
| 564 |
+
print(f"⚠️ Reached max iterations ({self.max_iterations})")
|
| 565 |
+
|
| 566 |
+
finally:
|
| 567 |
+
# Cleanup environment
|
| 568 |
+
self._cleanup_env()
|
| 569 |
+
|
| 570 |
+
# Convert to Hermes trajectory format
|
| 571 |
+
trajectory = self._convert_to_hermes_format(messages, task, completed)
|
| 572 |
+
|
| 573 |
+
return {
|
| 574 |
+
"conversations": trajectory,
|
| 575 |
+
"completed": completed,
|
| 576 |
+
"api_calls": api_call_count,
|
| 577 |
+
"metadata": {
|
| 578 |
+
"model": self.model,
|
| 579 |
+
"env_type": self.env_type,
|
| 580 |
+
"timestamp": datetime.now().isoformat()
|
| 581 |
+
}
|
| 582 |
+
}
|
| 583 |
+
|
| 584 |
+
def run_batch(
|
| 585 |
+
self,
|
| 586 |
+
prompts: List[str],
|
| 587 |
+
output_file: str
|
| 588 |
+
) -> List[Dict[str, Any]]:
|
| 589 |
+
"""
|
| 590 |
+
Run multiple tasks and save trajectories to a JSONL file.
|
| 591 |
+
|
| 592 |
+
Args:
|
| 593 |
+
prompts: List of task prompts
|
| 594 |
+
output_file: Output JSONL file path
|
| 595 |
+
|
| 596 |
+
Returns:
|
| 597 |
+
List of results
|
| 598 |
+
"""
|
| 599 |
+
results = []
|
| 600 |
+
|
| 601 |
+
print(f"\n📦 Running batch of {len(prompts)} tasks")
|
| 602 |
+
print(f"📁 Output: {output_file}")
|
| 603 |
+
|
| 604 |
+
with open(output_file, 'w', encoding='utf-8') as f:
|
| 605 |
+
for i, prompt in enumerate(prompts, 1):
|
| 606 |
+
print(f"\n{'='*60}")
|
| 607 |
+
print(f"📋 Task {i}/{len(prompts)}")
|
| 608 |
+
print(f"{'='*60}")
|
| 609 |
+
|
| 610 |
+
try:
|
| 611 |
+
result = self.run_task(prompt)
|
| 612 |
+
results.append(result)
|
| 613 |
+
|
| 614 |
+
# Write to file immediately
|
| 615 |
+
f.write(json.dumps(result, ensure_ascii=False) + "\n")
|
| 616 |
+
f.flush()
|
| 617 |
+
|
| 618 |
+
print(f"✅ Task {i} completed (api_calls={result['api_calls']})")
|
| 619 |
+
|
| 620 |
+
except Exception as e:
|
| 621 |
+
self.logger.error(f"Error on task {i}: {e}")
|
| 622 |
+
error_result = {
|
| 623 |
+
"conversations": [],
|
| 624 |
+
"completed": False,
|
| 625 |
+
"api_calls": 0,
|
| 626 |
+
"error": str(e),
|
| 627 |
+
"metadata": {"timestamp": datetime.now().isoformat()}
|
| 628 |
+
}
|
| 629 |
+
results.append(error_result)
|
| 630 |
+
f.write(json.dumps(error_result, ensure_ascii=False) + "\n")
|
| 631 |
+
f.flush()
|
| 632 |
+
|
| 633 |
+
print(f"\n✅ Batch complete! {len(results)} trajectories saved to {output_file}")
|
| 634 |
+
return results
|
| 635 |
+
|
| 636 |
+
|
| 637 |
+
# ============================================================================
|
| 638 |
+
# CLI Interface
|
| 639 |
+
# ============================================================================
|
| 640 |
+
|
| 641 |
+
def main(
|
| 642 |
+
task: str = None,
|
| 643 |
+
prompts_file: str = None,
|
| 644 |
+
output_file: str = "swe-runner-test1.jsonl",
|
| 645 |
+
model: str = "claude-sonnet-4-20250514",
|
| 646 |
+
base_url: str = None,
|
| 647 |
+
api_key: str = None,
|
| 648 |
+
env: str = "local",
|
| 649 |
+
image: str = "python:3.11-slim",
|
| 650 |
+
cwd: str = "/tmp",
|
| 651 |
+
max_iterations: int = 15,
|
| 652 |
+
timeout: int = 60,
|
| 653 |
+
verbose: bool = False,
|
| 654 |
+
):
|
| 655 |
+
"""
|
| 656 |
+
Run SWE tasks with Hermes trajectory format output.
|
| 657 |
+
|
| 658 |
+
Args:
|
| 659 |
+
task: Single task to run (use this OR prompts_file)
|
| 660 |
+
prompts_file: JSONL file with prompts (each line: {"prompt": "..."})
|
| 661 |
+
output_file: Output JSONL file for trajectories
|
| 662 |
+
model: Model name (default: claude-sonnet-4-20250514)
|
| 663 |
+
base_url: API base URL (optional)
|
| 664 |
+
api_key: API key (optional, uses env vars)
|
| 665 |
+
env: Environment type - "local", "docker", or "modal"
|
| 666 |
+
image: Docker/Modal image (default: python:3.11-slim)
|
| 667 |
+
cwd: Working directory (default: /tmp)
|
| 668 |
+
max_iterations: Maximum tool-calling iterations (default: 15)
|
| 669 |
+
timeout: Command timeout in seconds (default: 60)
|
| 670 |
+
verbose: Enable verbose logging
|
| 671 |
+
|
| 672 |
+
Examples:
|
| 673 |
+
# Single task with local environment
|
| 674 |
+
python mini_swe_runner.py --task "Create hello.py that prints Hello World"
|
| 675 |
+
|
| 676 |
+
# Single task with Docker
|
| 677 |
+
python mini_swe_runner.py --task "List files" --env docker
|
| 678 |
+
|
| 679 |
+
# Batch from file
|
| 680 |
+
python mini_swe_runner.py --prompts_file tasks.jsonl --output_file results.jsonl
|
| 681 |
+
"""
|
| 682 |
+
print("🚀 Mini-SWE Runner with Hermes Trajectory Format")
|
| 683 |
+
print("=" * 60)
|
| 684 |
+
|
| 685 |
+
# Initialize runner
|
| 686 |
+
runner = MiniSWERunner(
|
| 687 |
+
model=model,
|
| 688 |
+
base_url=base_url,
|
| 689 |
+
api_key=api_key,
|
| 690 |
+
env_type=env,
|
| 691 |
+
image=image,
|
| 692 |
+
cwd=cwd,
|
| 693 |
+
max_iterations=max_iterations,
|
| 694 |
+
command_timeout=timeout,
|
| 695 |
+
verbose=verbose,
|
| 696 |
+
)
|
| 697 |
+
|
| 698 |
+
if task:
|
| 699 |
+
# Single task mode
|
| 700 |
+
result = runner.run_task(task)
|
| 701 |
+
|
| 702 |
+
# Save to file
|
| 703 |
+
with open(output_file, 'w', encoding='utf-8') as f:
|
| 704 |
+
f.write(json.dumps(result, ensure_ascii=False) + "\n")
|
| 705 |
+
|
| 706 |
+
print(f"\n📁 Trajectory saved to: {output_file}")
|
| 707 |
+
print(f"✅ Completed: {result['completed']}")
|
| 708 |
+
print(f"📞 API calls: {result['api_calls']}")
|
| 709 |
+
print(f"💬 Turns: {len(result['conversations'])}")
|
| 710 |
+
|
| 711 |
+
elif prompts_file:
|
| 712 |
+
# Batch mode
|
| 713 |
+
prompts = []
|
| 714 |
+
with open(prompts_file, 'r', encoding='utf-8') as f:
|
| 715 |
+
for line in f:
|
| 716 |
+
line = line.strip()
|
| 717 |
+
if line:
|
| 718 |
+
try:
|
| 719 |
+
entry = json.loads(line)
|
| 720 |
+
prompts.append(entry.get("prompt", entry.get("task", "")))
|
| 721 |
+
except json.JSONDecodeError:
|
| 722 |
+
prompts.append(line)
|
| 723 |
+
|
| 724 |
+
if not prompts:
|
| 725 |
+
print(f"❌ No prompts found in {prompts_file}")
|
| 726 |
+
return
|
| 727 |
+
|
| 728 |
+
runner.run_batch(prompts, output_file)
|
| 729 |
+
|
| 730 |
+
else:
|
| 731 |
+
print("❌ Please provide either --task or --prompts_file")
|
| 732 |
+
print(" Example: python mini_swe_runner.py --task 'Create a hello world script'")
|
| 733 |
+
|
| 734 |
+
|
| 735 |
+
if __name__ == "__main__":
|
| 736 |
+
fire.Fire(main)
|
model_tools.py
ADDED
|
@@ -0,0 +1,867 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Model Tools Module
|
| 4 |
+
|
| 5 |
+
Thin orchestration layer over the tool registry. Each tool file in tools/
|
| 6 |
+
self-registers its schema, handler, and metadata via tools.registry.register().
|
| 7 |
+
This module triggers discovery (by importing all tool modules), then provides
|
| 8 |
+
the public API that run_agent.py, cli.py, batch_runner.py, and the RL
|
| 9 |
+
environments consume.
|
| 10 |
+
|
| 11 |
+
Public API (signatures preserved from the original 2,400-line version):
|
| 12 |
+
get_tool_definitions(enabled_toolsets, disabled_toolsets, quiet_mode) -> list
|
| 13 |
+
handle_function_call(function_name, function_args, task_id, user_task) -> str
|
| 14 |
+
TOOL_TO_TOOLSET_MAP: dict (for batch_runner.py)
|
| 15 |
+
TOOLSET_REQUIREMENTS: dict (for cli.py, doctor.py)
|
| 16 |
+
get_all_tool_names() -> list
|
| 17 |
+
get_toolset_for_tool(name) -> str
|
| 18 |
+
get_available_toolsets() -> dict
|
| 19 |
+
check_toolset_requirements() -> dict
|
| 20 |
+
check_tool_availability(quiet) -> tuple
|
| 21 |
+
"""
|
| 22 |
+
|
| 23 |
+
import json
|
| 24 |
+
import asyncio
|
| 25 |
+
import logging
|
| 26 |
+
import threading
|
| 27 |
+
import time
|
| 28 |
+
from typing import Dict, Any, List, Optional, Tuple
|
| 29 |
+
|
| 30 |
+
from tools.registry import discover_builtin_tools, registry
|
| 31 |
+
from toolsets import resolve_toolset, validate_toolset
|
| 32 |
+
|
| 33 |
+
logger = logging.getLogger(__name__)
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
# =============================================================================
|
| 37 |
+
# Async Bridging (single source of truth -- used by registry.dispatch too)
|
| 38 |
+
# =============================================================================
|
| 39 |
+
|
| 40 |
+
_tool_loop = None # persistent loop for the main (CLI) thread
|
| 41 |
+
_tool_loop_lock = threading.Lock()
|
| 42 |
+
_worker_thread_local = threading.local() # per-worker-thread persistent loops
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
def _get_tool_loop():
|
| 46 |
+
"""Return a long-lived event loop for running async tool handlers.
|
| 47 |
+
|
| 48 |
+
Using a persistent loop (instead of asyncio.run() which creates and
|
| 49 |
+
*closes* a fresh loop every time) prevents "Event loop is closed"
|
| 50 |
+
errors that occur when cached httpx/AsyncOpenAI clients attempt to
|
| 51 |
+
close their transport on a dead loop during garbage collection.
|
| 52 |
+
"""
|
| 53 |
+
global _tool_loop
|
| 54 |
+
with _tool_loop_lock:
|
| 55 |
+
if _tool_loop is None or _tool_loop.is_closed():
|
| 56 |
+
_tool_loop = asyncio.new_event_loop()
|
| 57 |
+
return _tool_loop
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
def _get_worker_loop():
|
| 61 |
+
"""Return a persistent event loop for the current worker thread.
|
| 62 |
+
|
| 63 |
+
Each worker thread (e.g., delegate_task's ThreadPoolExecutor threads)
|
| 64 |
+
gets its own long-lived loop stored in thread-local storage. This
|
| 65 |
+
prevents the "Event loop is closed" errors that occurred when
|
| 66 |
+
asyncio.run() was used per-call: asyncio.run() creates a loop, runs
|
| 67 |
+
the coroutine, then *closes* the loop — but cached httpx/AsyncOpenAI
|
| 68 |
+
clients remain bound to that now-dead loop and raise RuntimeError
|
| 69 |
+
during garbage collection or subsequent use.
|
| 70 |
+
|
| 71 |
+
By keeping the loop alive for the thread's lifetime, cached clients
|
| 72 |
+
stay valid and their cleanup runs on a live loop.
|
| 73 |
+
"""
|
| 74 |
+
loop = getattr(_worker_thread_local, 'loop', None)
|
| 75 |
+
if loop is None or loop.is_closed():
|
| 76 |
+
loop = asyncio.new_event_loop()
|
| 77 |
+
asyncio.set_event_loop(loop)
|
| 78 |
+
_worker_thread_local.loop = loop
|
| 79 |
+
return loop
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
def _run_async(coro):
|
| 83 |
+
"""Run an async coroutine from a sync context.
|
| 84 |
+
|
| 85 |
+
If the current thread already has a running event loop (e.g., inside
|
| 86 |
+
the gateway's async stack or Atropos's event loop), we spin up a
|
| 87 |
+
disposable thread so asyncio.run() can create its own loop without
|
| 88 |
+
conflicting.
|
| 89 |
+
|
| 90 |
+
For the common CLI path (no running loop), we use a persistent event
|
| 91 |
+
loop so that cached async clients (httpx / AsyncOpenAI) remain bound
|
| 92 |
+
to a live loop and don't trigger "Event loop is closed" on GC.
|
| 93 |
+
|
| 94 |
+
When called from a worker thread (parallel tool execution), we use a
|
| 95 |
+
per-thread persistent loop to avoid both contention with the main
|
| 96 |
+
thread's shared loop AND the "Event loop is closed" errors caused by
|
| 97 |
+
asyncio.run()'s create-and-destroy lifecycle.
|
| 98 |
+
|
| 99 |
+
This is the single source of truth for sync->async bridging in tool
|
| 100 |
+
handlers. The RL paths (agent_loop.py, tool_context.py) also provide
|
| 101 |
+
outer thread-pool wrapping as defense-in-depth, but each handler is
|
| 102 |
+
self-protecting via this function.
|
| 103 |
+
"""
|
| 104 |
+
try:
|
| 105 |
+
loop = asyncio.get_running_loop()
|
| 106 |
+
except RuntimeError:
|
| 107 |
+
loop = None
|
| 108 |
+
|
| 109 |
+
if loop and loop.is_running():
|
| 110 |
+
# Inside an async context (gateway, RL env) — run in a fresh thread
|
| 111 |
+
# with its own event loop we own a reference to, so on timeout we
|
| 112 |
+
# can cancel the task inside that loop (ThreadPoolExecutor.cancel()
|
| 113 |
+
# only works on not-yet-started futures — it's a no-op on a running
|
| 114 |
+
# worker, which previously leaked the thread on every 300 s timeout).
|
| 115 |
+
import concurrent.futures
|
| 116 |
+
|
| 117 |
+
worker_loop: Optional[asyncio.AbstractEventLoop] = None
|
| 118 |
+
loop_ready = threading.Event()
|
| 119 |
+
|
| 120 |
+
def _run_in_worker():
|
| 121 |
+
nonlocal worker_loop
|
| 122 |
+
worker_loop = asyncio.new_event_loop()
|
| 123 |
+
loop_ready.set()
|
| 124 |
+
try:
|
| 125 |
+
asyncio.set_event_loop(worker_loop)
|
| 126 |
+
return worker_loop.run_until_complete(coro)
|
| 127 |
+
finally:
|
| 128 |
+
try:
|
| 129 |
+
# Cancel anything still pending (e.g. task cancelled
|
| 130 |
+
# externally via call_soon_threadsafe on timeout).
|
| 131 |
+
pending = asyncio.all_tasks(worker_loop)
|
| 132 |
+
for t in pending:
|
| 133 |
+
t.cancel()
|
| 134 |
+
if pending:
|
| 135 |
+
worker_loop.run_until_complete(
|
| 136 |
+
asyncio.gather(*pending, return_exceptions=True)
|
| 137 |
+
)
|
| 138 |
+
except Exception:
|
| 139 |
+
pass
|
| 140 |
+
worker_loop.close()
|
| 141 |
+
|
| 142 |
+
pool = concurrent.futures.ThreadPoolExecutor(max_workers=1)
|
| 143 |
+
future = pool.submit(_run_in_worker)
|
| 144 |
+
try:
|
| 145 |
+
return future.result(timeout=300)
|
| 146 |
+
except concurrent.futures.TimeoutError:
|
| 147 |
+
# Cancel the coroutine inside its own loop so the worker thread
|
| 148 |
+
# can wind down instead of running forever.
|
| 149 |
+
if loop_ready.wait(timeout=1.0) and worker_loop is not None:
|
| 150 |
+
try:
|
| 151 |
+
for t in asyncio.all_tasks(worker_loop):
|
| 152 |
+
worker_loop.call_soon_threadsafe(t.cancel)
|
| 153 |
+
except RuntimeError:
|
| 154 |
+
# Loop already closed — nothing to cancel.
|
| 155 |
+
pass
|
| 156 |
+
raise
|
| 157 |
+
finally:
|
| 158 |
+
# wait=False: don't block the caller on a stuck coroutine. We've
|
| 159 |
+
# already requested cancellation above; the worker will exit
|
| 160 |
+
# once the coroutine observes it (usually at the next await).
|
| 161 |
+
pool.shutdown(wait=False)
|
| 162 |
+
|
| 163 |
+
# If we're on a worker thread (e.g., parallel tool execution in
|
| 164 |
+
# delegate_task), use a per-thread persistent loop. This avoids
|
| 165 |
+
# contention with the main thread's shared loop while keeping cached
|
| 166 |
+
# httpx/AsyncOpenAI clients bound to a live loop for the thread's
|
| 167 |
+
# lifetime — preventing "Event loop is closed" on GC cleanup.
|
| 168 |
+
if threading.current_thread() is not threading.main_thread():
|
| 169 |
+
worker_loop = _get_worker_loop()
|
| 170 |
+
return worker_loop.run_until_complete(coro)
|
| 171 |
+
|
| 172 |
+
tool_loop = _get_tool_loop()
|
| 173 |
+
return tool_loop.run_until_complete(coro)
|
| 174 |
+
|
| 175 |
+
|
| 176 |
+
# =============================================================================
|
| 177 |
+
# Tool Discovery (importing each module triggers its registry.register calls)
|
| 178 |
+
# =============================================================================
|
| 179 |
+
|
| 180 |
+
discover_builtin_tools()
|
| 181 |
+
|
| 182 |
+
# MCP tool discovery (external MCP servers from config) used to run here as
|
| 183 |
+
# a module-level side effect. It was removed because discover_mcp_tools()
|
| 184 |
+
# internally uses a blocking future.result(timeout=120) wait, and the
|
| 185 |
+
# gateway lazy-imports this module from inside the asyncio event loop on
|
| 186 |
+
# the first user message — freezing Discord/Telegram heartbeats for up to
|
| 187 |
+
# 120s whenever any configured MCP server was slow or unreachable (#16856).
|
| 188 |
+
#
|
| 189 |
+
# Each entry point now runs discovery explicitly at its own startup:
|
| 190 |
+
# - gateway/run.py -> start_gateway() uses run_in_executor
|
| 191 |
+
# - cli.py, hermes_cli/* -> inline on startup (no event loop)
|
| 192 |
+
# - tui_gateway/server.py -> inline on startup (no event loop)
|
| 193 |
+
# - acp_adapter/server.py -> asyncio.to_thread on session init
|
| 194 |
+
|
| 195 |
+
# Plugin tool discovery (user/project/pip plugins)
|
| 196 |
+
try:
|
| 197 |
+
from hermes_cli.plugins import discover_plugins
|
| 198 |
+
discover_plugins()
|
| 199 |
+
except Exception as e:
|
| 200 |
+
logger.debug("Plugin discovery failed: %s", e)
|
| 201 |
+
|
| 202 |
+
|
| 203 |
+
# =============================================================================
|
| 204 |
+
# Backward-compat constants (built once after discovery)
|
| 205 |
+
# =============================================================================
|
| 206 |
+
|
| 207 |
+
TOOL_TO_TOOLSET_MAP: Dict[str, str] = registry.get_tool_to_toolset_map()
|
| 208 |
+
|
| 209 |
+
TOOLSET_REQUIREMENTS: Dict[str, dict] = registry.get_toolset_requirements()
|
| 210 |
+
|
| 211 |
+
# Resolved tool names from the last get_tool_definitions() call.
|
| 212 |
+
# Used by code_execution_tool to know which tools are available in this session.
|
| 213 |
+
_last_resolved_tool_names: List[str] = []
|
| 214 |
+
|
| 215 |
+
|
| 216 |
+
# =============================================================================
|
| 217 |
+
# Legacy toolset name mapping (old _tools-suffixed names -> tool name lists)
|
| 218 |
+
# =============================================================================
|
| 219 |
+
|
| 220 |
+
_LEGACY_TOOLSET_MAP = {
|
| 221 |
+
"web_tools": ["web_search", "web_extract"],
|
| 222 |
+
"terminal_tools": ["terminal"],
|
| 223 |
+
"vision_tools": ["vision_analyze"],
|
| 224 |
+
"moa_tools": ["mixture_of_agents"],
|
| 225 |
+
"image_tools": ["image_generate"],
|
| 226 |
+
"skills_tools": ["skills_list", "skill_view", "skill_manage"],
|
| 227 |
+
"browser_tools": [
|
| 228 |
+
"browser_navigate", "browser_snapshot", "browser_click",
|
| 229 |
+
"browser_type", "browser_scroll", "browser_back",
|
| 230 |
+
"browser_press", "browser_get_images",
|
| 231 |
+
"browser_vision", "browser_console"
|
| 232 |
+
],
|
| 233 |
+
"cronjob_tools": ["cronjob"],
|
| 234 |
+
"rl_tools": [
|
| 235 |
+
"rl_list_environments", "rl_select_environment",
|
| 236 |
+
"rl_get_current_config", "rl_edit_config",
|
| 237 |
+
"rl_start_training", "rl_check_status",
|
| 238 |
+
"rl_stop_training", "rl_get_results",
|
| 239 |
+
"rl_list_runs", "rl_test_inference"
|
| 240 |
+
],
|
| 241 |
+
"file_tools": ["read_file", "write_file", "patch", "search_files"],
|
| 242 |
+
"tts_tools": ["text_to_speech"],
|
| 243 |
+
}
|
| 244 |
+
|
| 245 |
+
|
| 246 |
+
# =============================================================================
|
| 247 |
+
# get_tool_definitions (the main schema provider)
|
| 248 |
+
# =============================================================================
|
| 249 |
+
|
| 250 |
+
# Module-level memoization for get_tool_definitions(). Keyed on
|
| 251 |
+
# (frozenset(enabled_toolsets), frozenset(disabled_toolsets), registry._generation).
|
| 252 |
+
# Hot callers (gateway runner, AIAgent.__init__) invoke this on every turn
|
| 253 |
+
# with quiet_mode=True; caching avoids ~7 ms of registry walking + schema
|
| 254 |
+
# filtering + check_fn probing per call. Only active when quiet_mode=True
|
| 255 |
+
# because quiet_mode=False has stdout side effects (tool-selection prints).
|
| 256 |
+
#
|
| 257 |
+
# Invalidation happens transparently via the registry's _generation counter,
|
| 258 |
+
# which bumps on register() / deregister() / register_toolset_alias(). The
|
| 259 |
+
# inner check_fn TTL cache in registry.py handles environment drift (Docker
|
| 260 |
+
# daemon start/stop, env var changes, etc.) on a 30 s horizon.
|
| 261 |
+
_tool_defs_cache: Dict[tuple, List[Dict[str, Any]]] = {}
|
| 262 |
+
|
| 263 |
+
|
| 264 |
+
def _clear_tool_defs_cache() -> None:
|
| 265 |
+
"""Drop memoized get_tool_definitions() results. Called when dynamic
|
| 266 |
+
schema dependencies change (e.g. discord capability cache reset,
|
| 267 |
+
execute_code sandbox reconfigured)."""
|
| 268 |
+
_tool_defs_cache.clear()
|
| 269 |
+
|
| 270 |
+
|
| 271 |
+
def get_tool_definitions(
|
| 272 |
+
enabled_toolsets: List[str] = None,
|
| 273 |
+
disabled_toolsets: List[str] = None,
|
| 274 |
+
quiet_mode: bool = False,
|
| 275 |
+
) -> List[Dict[str, Any]]:
|
| 276 |
+
"""
|
| 277 |
+
Get tool definitions for model API calls with toolset-based filtering.
|
| 278 |
+
|
| 279 |
+
All tools must be part of a toolset to be accessible.
|
| 280 |
+
|
| 281 |
+
Args:
|
| 282 |
+
enabled_toolsets: Only include tools from these toolsets.
|
| 283 |
+
disabled_toolsets: Exclude tools from these toolsets (if enabled_toolsets is None).
|
| 284 |
+
quiet_mode: Suppress status prints.
|
| 285 |
+
|
| 286 |
+
Returns:
|
| 287 |
+
Filtered list of OpenAI-format tool definitions.
|
| 288 |
+
"""
|
| 289 |
+
# Fast path: memoized result when the caller doesn't need stdout prints.
|
| 290 |
+
# The cache key captures every argument-level input; the registry
|
| 291 |
+
# generation captures registry mutations (MCP refresh, plugin load).
|
| 292 |
+
# check_fn results are TTL-cached one level down, inside
|
| 293 |
+
# registry.get_definitions. The config-mtime fingerprint below captures
|
| 294 |
+
# user-visible config edits that affect dynamic schemas (execute_code
|
| 295 |
+
# mode, discord action allowlist, etc.) without needing an explicit
|
| 296 |
+
# invalidate hook on every config-writer.
|
| 297 |
+
if quiet_mode:
|
| 298 |
+
try:
|
| 299 |
+
from hermes_cli.config import get_config_path
|
| 300 |
+
cfg_path = get_config_path()
|
| 301 |
+
cfg_stat = cfg_path.stat()
|
| 302 |
+
cfg_fp = (cfg_stat.st_mtime_ns, cfg_stat.st_size)
|
| 303 |
+
except (FileNotFoundError, OSError, ImportError):
|
| 304 |
+
cfg_fp = None
|
| 305 |
+
cache_key = (
|
| 306 |
+
frozenset(enabled_toolsets) if enabled_toolsets is not None else None,
|
| 307 |
+
frozenset(disabled_toolsets) if disabled_toolsets else None,
|
| 308 |
+
registry._generation,
|
| 309 |
+
cfg_fp,
|
| 310 |
+
)
|
| 311 |
+
cached = _tool_defs_cache.get(cache_key)
|
| 312 |
+
if cached is not None:
|
| 313 |
+
# Update _last_resolved_tool_names so downstream callers see
|
| 314 |
+
# consistent state even on a cache hit.
|
| 315 |
+
global _last_resolved_tool_names
|
| 316 |
+
_last_resolved_tool_names = [t["function"]["name"] for t in cached]
|
| 317 |
+
# Return a shallow copy of the list but share the dict references —
|
| 318 |
+
# schemas are treated as read-only by all known callers.
|
| 319 |
+
return list(cached)
|
| 320 |
+
|
| 321 |
+
result = _compute_tool_definitions(enabled_toolsets, disabled_toolsets, quiet_mode)
|
| 322 |
+
if quiet_mode:
|
| 323 |
+
# Cache the freshly-computed list, but hand callers a shallow copy so
|
| 324 |
+
# downstream mutations (e.g. run_agent appending memory/LCM tool
|
| 325 |
+
# schemas to self.tools) don't poison the cache. Without this, a
|
| 326 |
+
# long-lived Gateway process accumulates duplicate tool names across
|
| 327 |
+
# agent inits and providers that enforce unique tool names
|
| 328 |
+
# (DeepSeek, Xiaomi MiMo, Moonshot Kimi) reject the request with
|
| 329 |
+
# HTTP 400. Mirrors the cache-hit path above. (issue #17335)
|
| 330 |
+
_tool_defs_cache[cache_key] = result
|
| 331 |
+
return list(result)
|
| 332 |
+
return result
|
| 333 |
+
|
| 334 |
+
|
| 335 |
+
def _compute_tool_definitions(
|
| 336 |
+
enabled_toolsets: List[str] = None,
|
| 337 |
+
disabled_toolsets: List[str] = None,
|
| 338 |
+
quiet_mode: bool = False,
|
| 339 |
+
) -> List[Dict[str, Any]]:
|
| 340 |
+
"""Uncached implementation of :func:`get_tool_definitions`."""
|
| 341 |
+
# Determine which tool names the caller wants
|
| 342 |
+
tools_to_include: set = set()
|
| 343 |
+
|
| 344 |
+
if enabled_toolsets is not None:
|
| 345 |
+
for toolset_name in enabled_toolsets:
|
| 346 |
+
if validate_toolset(toolset_name):
|
| 347 |
+
resolved = resolve_toolset(toolset_name)
|
| 348 |
+
tools_to_include.update(resolved)
|
| 349 |
+
if not quiet_mode:
|
| 350 |
+
print(f"✅ Enabled toolset '{toolset_name}': {', '.join(resolved) if resolved else 'no tools'}")
|
| 351 |
+
elif toolset_name in _LEGACY_TOOLSET_MAP:
|
| 352 |
+
legacy_tools = _LEGACY_TOOLSET_MAP[toolset_name]
|
| 353 |
+
tools_to_include.update(legacy_tools)
|
| 354 |
+
if not quiet_mode:
|
| 355 |
+
print(f"✅ Enabled legacy toolset '{toolset_name}': {', '.join(legacy_tools)}")
|
| 356 |
+
else:
|
| 357 |
+
if not quiet_mode:
|
| 358 |
+
print(f"⚠️ Unknown toolset: {toolset_name}")
|
| 359 |
+
else:
|
| 360 |
+
# Default: start with everything
|
| 361 |
+
from toolsets import get_all_toolsets
|
| 362 |
+
for ts_name in get_all_toolsets():
|
| 363 |
+
tools_to_include.update(resolve_toolset(ts_name))
|
| 364 |
+
|
| 365 |
+
# Always apply disabled toolsets as a subtraction step at the end.
|
| 366 |
+
# This ensures that even if a composite toolset (like hermes-cli)
|
| 367 |
+
# is enabled, any tools belonging to a disabled toolset are strictly
|
| 368 |
+
# stripped out. See issue #17309.
|
| 369 |
+
if disabled_toolsets:
|
| 370 |
+
for toolset_name in disabled_toolsets:
|
| 371 |
+
if validate_toolset(toolset_name):
|
| 372 |
+
resolved = resolve_toolset(toolset_name)
|
| 373 |
+
tools_to_include.difference_update(resolved)
|
| 374 |
+
if not quiet_mode:
|
| 375 |
+
print(f"🚫 Disabled toolset '{toolset_name}': {', '.join(resolved) if resolved else 'no tools'}")
|
| 376 |
+
elif toolset_name in _LEGACY_TOOLSET_MAP:
|
| 377 |
+
legacy_tools = _LEGACY_TOOLSET_MAP[toolset_name]
|
| 378 |
+
tools_to_include.difference_update(legacy_tools)
|
| 379 |
+
if not quiet_mode:
|
| 380 |
+
print(f"🚫 Disabled legacy toolset '{toolset_name}': {', '.join(legacy_tools)}")
|
| 381 |
+
else:
|
| 382 |
+
if not quiet_mode:
|
| 383 |
+
print(f"⚠️ Unknown toolset: {toolset_name}")
|
| 384 |
+
|
| 385 |
+
# Plugin-registered tools are now resolved through the normal toolset
|
| 386 |
+
# path — validate_toolset() / resolve_toolset() / get_all_toolsets()
|
| 387 |
+
# all check the tool registry for plugin-provided toolsets. No bypass
|
| 388 |
+
# needed; plugins respect enabled_toolsets / disabled_toolsets like any
|
| 389 |
+
# other toolset.
|
| 390 |
+
|
| 391 |
+
# Ask the registry for schemas (only returns tools whose check_fn passes)
|
| 392 |
+
filtered_tools = registry.get_definitions(tools_to_include, quiet=quiet_mode)
|
| 393 |
+
|
| 394 |
+
# The set of tool names that actually passed check_fn filtering.
|
| 395 |
+
# Use this (not tools_to_include) for any downstream schema that references
|
| 396 |
+
# other tools by name — otherwise the model sees tools mentioned in
|
| 397 |
+
# descriptions that don't actually exist, and hallucinates calls to them.
|
| 398 |
+
available_tool_names = {t["function"]["name"] for t in filtered_tools}
|
| 399 |
+
|
| 400 |
+
# Rebuild execute_code schema to only list sandbox tools that are actually
|
| 401 |
+
# available. Without this, the model sees "web_search is available in
|
| 402 |
+
# execute_code" even when the API key isn't configured or the toolset is
|
| 403 |
+
# disabled (#560-discord).
|
| 404 |
+
if "execute_code" in available_tool_names:
|
| 405 |
+
from tools.code_execution_tool import SANDBOX_ALLOWED_TOOLS, build_execute_code_schema, _get_execution_mode
|
| 406 |
+
sandbox_enabled = SANDBOX_ALLOWED_TOOLS & available_tool_names
|
| 407 |
+
dynamic_schema = build_execute_code_schema(sandbox_enabled, mode=_get_execution_mode())
|
| 408 |
+
for i, td in enumerate(filtered_tools):
|
| 409 |
+
if td.get("function", {}).get("name") == "execute_code":
|
| 410 |
+
filtered_tools[i] = {"type": "function", "function": dynamic_schema}
|
| 411 |
+
break
|
| 412 |
+
|
| 413 |
+
# Rebuild discord / discord_admin schemas based on the bot's privileged
|
| 414 |
+
# intents (detected from GET /applications/@me) and the user's action
|
| 415 |
+
# allowlist in config. Hides actions the bot's intents don't support so
|
| 416 |
+
# the model never attempts them, and annotates fetch_messages when the
|
| 417 |
+
# MESSAGE_CONTENT intent is missing.
|
| 418 |
+
_discord_schema_fns = {
|
| 419 |
+
"discord": "get_dynamic_schema_core",
|
| 420 |
+
"discord_admin": "get_dynamic_schema_admin",
|
| 421 |
+
}
|
| 422 |
+
for discord_tool_name in _discord_schema_fns:
|
| 423 |
+
if discord_tool_name in available_tool_names:
|
| 424 |
+
try:
|
| 425 |
+
from tools import discord_tool as _dt
|
| 426 |
+
schema_fn = getattr(_dt, _discord_schema_fns[discord_tool_name])
|
| 427 |
+
dynamic = schema_fn()
|
| 428 |
+
except Exception:
|
| 429 |
+
dynamic = None
|
| 430 |
+
if dynamic is None:
|
| 431 |
+
filtered_tools = [
|
| 432 |
+
t for t in filtered_tools
|
| 433 |
+
if t.get("function", {}).get("name") != discord_tool_name
|
| 434 |
+
]
|
| 435 |
+
available_tool_names.discard(discord_tool_name)
|
| 436 |
+
else:
|
| 437 |
+
for i, td in enumerate(filtered_tools):
|
| 438 |
+
if td.get("function", {}).get("name") == discord_tool_name:
|
| 439 |
+
filtered_tools[i] = {"type": "function", "function": dynamic}
|
| 440 |
+
break
|
| 441 |
+
|
| 442 |
+
# Strip web tool cross-references from browser_navigate description when
|
| 443 |
+
# web_search / web_extract are not available. The static schema says
|
| 444 |
+
# "prefer web_search or web_extract" which causes the model to hallucinate
|
| 445 |
+
# those tools when they're missing.
|
| 446 |
+
if "browser_navigate" in available_tool_names:
|
| 447 |
+
web_tools_available = {"web_search", "web_extract"} & available_tool_names
|
| 448 |
+
if not web_tools_available:
|
| 449 |
+
for i, td in enumerate(filtered_tools):
|
| 450 |
+
if td.get("function", {}).get("name") == "browser_navigate":
|
| 451 |
+
desc = td["function"].get("description", "")
|
| 452 |
+
desc = desc.replace(
|
| 453 |
+
" For simple information retrieval, prefer web_search or web_extract (faster, cheaper).",
|
| 454 |
+
"",
|
| 455 |
+
)
|
| 456 |
+
filtered_tools[i] = {
|
| 457 |
+
"type": "function",
|
| 458 |
+
"function": {**td["function"], "description": desc},
|
| 459 |
+
}
|
| 460 |
+
break
|
| 461 |
+
|
| 462 |
+
if not quiet_mode:
|
| 463 |
+
if filtered_tools:
|
| 464 |
+
tool_names = [t["function"]["name"] for t in filtered_tools]
|
| 465 |
+
print(f"🛠️ Final tool selection ({len(filtered_tools)} tools): {', '.join(tool_names)}")
|
| 466 |
+
else:
|
| 467 |
+
print("🛠️ No tools selected (all filtered out or unavailable)")
|
| 468 |
+
|
| 469 |
+
global _last_resolved_tool_names
|
| 470 |
+
_last_resolved_tool_names = [t["function"]["name"] for t in filtered_tools]
|
| 471 |
+
|
| 472 |
+
# Sanitize schemas for broad backend compatibility. llama.cpp's
|
| 473 |
+
# json-schema-to-grammar converter (used by its OAI server to build
|
| 474 |
+
# GBNF tool-call parsers) rejects some shapes that cloud providers
|
| 475 |
+
# silently accept — bare "type": "object" with no properties,
|
| 476 |
+
# string-valued schema nodes from malformed MCP servers, etc. This
|
| 477 |
+
# is a no-op for schemas that are already well-formed.
|
| 478 |
+
try:
|
| 479 |
+
from tools.schema_sanitizer import sanitize_tool_schemas
|
| 480 |
+
filtered_tools = sanitize_tool_schemas(filtered_tools)
|
| 481 |
+
except Exception as e: # pragma: no cover — defensive
|
| 482 |
+
logger.warning("Schema sanitization skipped: %s", e)
|
| 483 |
+
|
| 484 |
+
return filtered_tools
|
| 485 |
+
|
| 486 |
+
|
| 487 |
+
# =============================================================================
|
| 488 |
+
# handle_function_call (the main dispatcher)
|
| 489 |
+
# =============================================================================
|
| 490 |
+
|
| 491 |
+
# Tools whose execution is intercepted by the agent loop (run_agent.py)
|
| 492 |
+
# because they need agent-level state (TodoStore, MemoryStore, etc.).
|
| 493 |
+
# The registry still holds their schemas; dispatch just returns a stub error
|
| 494 |
+
# so if something slips through, the LLM sees a sensible message.
|
| 495 |
+
_AGENT_LOOP_TOOLS = {"todo", "memory", "session_search", "delegate_task"}
|
| 496 |
+
_READ_SEARCH_TOOLS = {"read_file", "search_files"}
|
| 497 |
+
|
| 498 |
+
|
| 499 |
+
# =========================================================================
|
| 500 |
+
# Tool argument type coercion
|
| 501 |
+
# =========================================================================
|
| 502 |
+
|
| 503 |
+
def coerce_tool_args(tool_name: str, args: Dict[str, Any]) -> Dict[str, Any]:
|
| 504 |
+
"""Coerce tool call arguments to match their JSON Schema types.
|
| 505 |
+
|
| 506 |
+
LLMs frequently return numbers as strings (``"42"`` instead of ``42``)
|
| 507 |
+
and booleans as strings (``"true"`` instead of ``true``). This compares
|
| 508 |
+
each argument value against the tool's registered JSON Schema and attempts
|
| 509 |
+
safe coercion when the value is a string but the schema expects a different
|
| 510 |
+
type. Original values are preserved when coercion fails.
|
| 511 |
+
|
| 512 |
+
Handles ``"type": "integer"``, ``"type": "number"``, ``"type": "boolean"``,
|
| 513 |
+
and union types (``"type": ["integer", "string"]``).
|
| 514 |
+
|
| 515 |
+
Also wraps bare scalar values in a single-element list when the schema
|
| 516 |
+
declares ``"type": "array"``. Open-weight models (DeepSeek, Qwen, GLM)
|
| 517 |
+
sometimes emit ``{"urls": "https://a.com"}`` when the tool expects
|
| 518 |
+
``{"urls": ["https://a.com"]}``; wrapping here avoids a confusing tool
|
| 519 |
+
failure on what is otherwise a well-formed call.
|
| 520 |
+
"""
|
| 521 |
+
if not args or not isinstance(args, dict):
|
| 522 |
+
return args
|
| 523 |
+
|
| 524 |
+
schema = registry.get_schema(tool_name)
|
| 525 |
+
if not schema:
|
| 526 |
+
return args
|
| 527 |
+
|
| 528 |
+
properties = (schema.get("parameters") or {}).get("properties")
|
| 529 |
+
if not properties:
|
| 530 |
+
return args
|
| 531 |
+
|
| 532 |
+
for key, value in list(args.items()):
|
| 533 |
+
prop_schema = properties.get(key)
|
| 534 |
+
if not prop_schema:
|
| 535 |
+
continue
|
| 536 |
+
expected = prop_schema.get("type")
|
| 537 |
+
|
| 538 |
+
# Wrap bare non-list values when the schema declares ``array``.
|
| 539 |
+
# Strings still go through _coerce_value first so JSON-encoded
|
| 540 |
+
# arrays (``'["a","b"]'``) get parsed and nullable ``"null"``
|
| 541 |
+
# becomes ``None`` rather than ``["null"]``.
|
| 542 |
+
# ``None`` itself is preserved — we don't know whether the model
|
| 543 |
+
# meant "omit" or "empty list", and tools with sensible defaults
|
| 544 |
+
# (e.g. read_file's normalize_read_pagination) already handle it.
|
| 545 |
+
if expected == "array" and value is not None and not isinstance(value, (list, tuple)):
|
| 546 |
+
if isinstance(value, str):
|
| 547 |
+
coerced = _coerce_value(value, expected, schema=prop_schema)
|
| 548 |
+
if coerced is not value:
|
| 549 |
+
# _coerce_value handled it (JSON-parsed list or
|
| 550 |
+
# nullable "null" → None).
|
| 551 |
+
args[key] = coerced
|
| 552 |
+
continue
|
| 553 |
+
# If the string looks like a JSON array but _coerce_value
|
| 554 |
+
# failed to parse it, warn clearly instead of silently wrapping.
|
| 555 |
+
if value.strip().startswith("["):
|
| 556 |
+
logger.warning(
|
| 557 |
+
"coerce_tool_args: %s.%s looks like a JSON array string "
|
| 558 |
+
"but could not be parsed — model may have emitted a "
|
| 559 |
+
"JSON-encoded string instead of a native array. "
|
| 560 |
+
"Falling back to single-element list.",
|
| 561 |
+
tool_name, key,
|
| 562 |
+
)
|
| 563 |
+
args[key] = [value]
|
| 564 |
+
logger.info(
|
| 565 |
+
"coerce_tool_args: wrapped bare string in list for %s.%s",
|
| 566 |
+
tool_name, key,
|
| 567 |
+
)
|
| 568 |
+
continue
|
| 569 |
+
args[key] = [value]
|
| 570 |
+
logger.info(
|
| 571 |
+
"coerce_tool_args: wrapped bare %s in list for %s.%s",
|
| 572 |
+
type(value).__name__, tool_name, key,
|
| 573 |
+
)
|
| 574 |
+
continue
|
| 575 |
+
|
| 576 |
+
if not isinstance(value, str):
|
| 577 |
+
continue
|
| 578 |
+
if not expected and not _schema_allows_null(prop_schema):
|
| 579 |
+
continue
|
| 580 |
+
coerced = _coerce_value(value, expected, schema=prop_schema)
|
| 581 |
+
if coerced is not value:
|
| 582 |
+
args[key] = coerced
|
| 583 |
+
|
| 584 |
+
return args
|
| 585 |
+
|
| 586 |
+
|
| 587 |
+
def _coerce_value(value: str, expected_type, schema: dict | None = None):
|
| 588 |
+
"""Attempt to coerce a string *value* to *expected_type*.
|
| 589 |
+
|
| 590 |
+
Returns the original string when coercion is not applicable or fails.
|
| 591 |
+
"""
|
| 592 |
+
if _schema_allows_null(schema) and value.strip().lower() == "null":
|
| 593 |
+
return None
|
| 594 |
+
|
| 595 |
+
if isinstance(expected_type, list):
|
| 596 |
+
# Union type — try each in order, return first successful coercion
|
| 597 |
+
for t in expected_type:
|
| 598 |
+
result = _coerce_value(value, t, schema=schema)
|
| 599 |
+
if result is not value:
|
| 600 |
+
return result
|
| 601 |
+
return value
|
| 602 |
+
|
| 603 |
+
if expected_type in ("integer", "number"):
|
| 604 |
+
return _coerce_number(value, integer_only=(expected_type == "integer"))
|
| 605 |
+
if expected_type == "boolean":
|
| 606 |
+
return _coerce_boolean(value)
|
| 607 |
+
if expected_type == "array":
|
| 608 |
+
return _coerce_json(value, list)
|
| 609 |
+
if expected_type == "object":
|
| 610 |
+
return _coerce_json(value, dict)
|
| 611 |
+
if expected_type == "null" and value.strip().lower() == "null":
|
| 612 |
+
return None
|
| 613 |
+
return value
|
| 614 |
+
|
| 615 |
+
|
| 616 |
+
def _schema_allows_null(schema: dict | None) -> bool:
|
| 617 |
+
"""Return True when a JSON Schema fragment explicitly permits null."""
|
| 618 |
+
if not isinstance(schema, dict):
|
| 619 |
+
return False
|
| 620 |
+
|
| 621 |
+
schema_type = schema.get("type")
|
| 622 |
+
if schema_type == "null":
|
| 623 |
+
return True
|
| 624 |
+
if isinstance(schema_type, list) and "null" in schema_type:
|
| 625 |
+
return True
|
| 626 |
+
if schema.get("nullable") is True:
|
| 627 |
+
return True
|
| 628 |
+
|
| 629 |
+
for union_key in ("anyOf", "oneOf"):
|
| 630 |
+
variants = schema.get(union_key)
|
| 631 |
+
if not isinstance(variants, list):
|
| 632 |
+
continue
|
| 633 |
+
for variant in variants:
|
| 634 |
+
if isinstance(variant, dict) and variant.get("type") == "null":
|
| 635 |
+
return True
|
| 636 |
+
|
| 637 |
+
return False
|
| 638 |
+
|
| 639 |
+
|
| 640 |
+
def _coerce_json(value: str, expected_python_type: type):
|
| 641 |
+
"""Parse *value* as JSON when the schema expects an array or object.
|
| 642 |
+
|
| 643 |
+
Handles model output drift where a complex oneOf/discriminated-union schema
|
| 644 |
+
causes the LLM to emit the array/object as a JSON string instead of a native
|
| 645 |
+
structure. Returns the original string if parsing fails or yields the wrong
|
| 646 |
+
Python type.
|
| 647 |
+
"""
|
| 648 |
+
try:
|
| 649 |
+
parsed = json.loads(value)
|
| 650 |
+
except (ValueError, TypeError) as exc:
|
| 651 |
+
logger.warning(
|
| 652 |
+
"coerce_tool_args: failed to parse string as JSON for expected type %s: %s",
|
| 653 |
+
expected_python_type.__name__,
|
| 654 |
+
exc,
|
| 655 |
+
)
|
| 656 |
+
return value
|
| 657 |
+
if isinstance(parsed, expected_python_type):
|
| 658 |
+
logger.debug(
|
| 659 |
+
"coerce_tool_args: coerced string to %s via json.loads",
|
| 660 |
+
expected_python_type.__name__,
|
| 661 |
+
)
|
| 662 |
+
return parsed
|
| 663 |
+
logger.warning(
|
| 664 |
+
"coerce_tool_args: JSON-parsed value is %s, expected %s — skipping coercion",
|
| 665 |
+
type(parsed).__name__,
|
| 666 |
+
expected_python_type.__name__,
|
| 667 |
+
)
|
| 668 |
+
return value
|
| 669 |
+
|
| 670 |
+
|
| 671 |
+
def _coerce_number(value: str, integer_only: bool = False):
|
| 672 |
+
"""Try to parse *value* as a number. Returns original string on failure."""
|
| 673 |
+
try:
|
| 674 |
+
f = float(value)
|
| 675 |
+
except (ValueError, OverflowError):
|
| 676 |
+
return value
|
| 677 |
+
# Guard against inf/nan — not JSON-serializable, keep original string
|
| 678 |
+
if f != f or f == float("inf") or f == float("-inf"):
|
| 679 |
+
return value
|
| 680 |
+
# If it looks like an integer (no fractional part), return int
|
| 681 |
+
if f == int(f):
|
| 682 |
+
return int(f)
|
| 683 |
+
if integer_only:
|
| 684 |
+
# Schema wants an integer but value has decimals — keep as string
|
| 685 |
+
return value
|
| 686 |
+
return f
|
| 687 |
+
|
| 688 |
+
|
| 689 |
+
def _coerce_boolean(value: str):
|
| 690 |
+
"""Try to parse *value* as a boolean. Returns original string on failure."""
|
| 691 |
+
low = value.strip().lower()
|
| 692 |
+
if low == "true":
|
| 693 |
+
return True
|
| 694 |
+
if low == "false":
|
| 695 |
+
return False
|
| 696 |
+
return value
|
| 697 |
+
|
| 698 |
+
|
| 699 |
+
def handle_function_call(
|
| 700 |
+
function_name: str,
|
| 701 |
+
function_args: Dict[str, Any],
|
| 702 |
+
task_id: Optional[str] = None,
|
| 703 |
+
tool_call_id: Optional[str] = None,
|
| 704 |
+
session_id: Optional[str] = None,
|
| 705 |
+
user_task: Optional[str] = None,
|
| 706 |
+
enabled_tools: Optional[List[str]] = None,
|
| 707 |
+
skip_pre_tool_call_hook: bool = False,
|
| 708 |
+
) -> str:
|
| 709 |
+
"""
|
| 710 |
+
Main function call dispatcher that routes calls to the tool registry.
|
| 711 |
+
|
| 712 |
+
Args:
|
| 713 |
+
function_name: Name of the function to call.
|
| 714 |
+
function_args: Arguments for the function.
|
| 715 |
+
task_id: Unique identifier for terminal/browser session isolation.
|
| 716 |
+
user_task: The user's original task (for browser_snapshot context).
|
| 717 |
+
enabled_tools: Tool names enabled for this session. When provided,
|
| 718 |
+
execute_code uses this list to determine which sandbox
|
| 719 |
+
tools to generate. Falls back to the process-global
|
| 720 |
+
``_last_resolved_tool_names`` for backward compat.
|
| 721 |
+
|
| 722 |
+
Returns:
|
| 723 |
+
Function result as a JSON string.
|
| 724 |
+
"""
|
| 725 |
+
# Coerce string arguments to their schema-declared types (e.g. "42"→42)
|
| 726 |
+
function_args = coerce_tool_args(function_name, function_args)
|
| 727 |
+
|
| 728 |
+
try:
|
| 729 |
+
if function_name in _AGENT_LOOP_TOOLS:
|
| 730 |
+
return json.dumps({"error": f"{function_name} must be handled by the agent loop"})
|
| 731 |
+
|
| 732 |
+
# Check plugin hooks for a block directive (unless caller already
|
| 733 |
+
# checked — e.g. run_agent._invoke_tool passes skip=True to
|
| 734 |
+
# avoid double-firing the hook).
|
| 735 |
+
#
|
| 736 |
+
# Single-fire contract: pre_tool_call fires exactly once per tool
|
| 737 |
+
# execution. get_pre_tool_call_block_message() internally calls
|
| 738 |
+
# invoke_hook("pre_tool_call", ...) and returns the first block
|
| 739 |
+
# directive (if any), so observer plugins see the hook on that same
|
| 740 |
+
# pass. When skip=True, the caller already fired it — do nothing
|
| 741 |
+
# here.
|
| 742 |
+
if not skip_pre_tool_call_hook:
|
| 743 |
+
block_message: Optional[str] = None
|
| 744 |
+
try:
|
| 745 |
+
from hermes_cli.plugins import get_pre_tool_call_block_message
|
| 746 |
+
block_message = get_pre_tool_call_block_message(
|
| 747 |
+
function_name,
|
| 748 |
+
function_args,
|
| 749 |
+
task_id=task_id or "",
|
| 750 |
+
session_id=session_id or "",
|
| 751 |
+
tool_call_id=tool_call_id or "",
|
| 752 |
+
)
|
| 753 |
+
except Exception as _hook_err:
|
| 754 |
+
logger.debug("pre_tool_call hook error: %s", _hook_err)
|
| 755 |
+
|
| 756 |
+
if block_message is not None:
|
| 757 |
+
return json.dumps({"error": block_message}, ensure_ascii=False)
|
| 758 |
+
|
| 759 |
+
# Notify the read-loop tracker when a non-read/search tool runs,
|
| 760 |
+
# so the *consecutive* counter resets (reads after other work are fine).
|
| 761 |
+
if function_name not in _READ_SEARCH_TOOLS:
|
| 762 |
+
try:
|
| 763 |
+
from tools.file_tools import notify_other_tool_call
|
| 764 |
+
notify_other_tool_call(task_id or "default")
|
| 765 |
+
except Exception:
|
| 766 |
+
pass # file_tools may not be loaded yet
|
| 767 |
+
|
| 768 |
+
# Measure tool dispatch latency so post_tool_call and
|
| 769 |
+
# transform_tool_result hooks can observe per-tool duration.
|
| 770 |
+
# Inspired by Claude Code 2.1.119, which added ``duration_ms`` to
|
| 771 |
+
# PostToolUse hook inputs so plugin authors can build latency
|
| 772 |
+
# dashboards, budget alerts, and regression canaries without having
|
| 773 |
+
# to wrap every tool manually. We use monotonic() so the value is
|
| 774 |
+
# unaffected by wall-clock adjustments during the call.
|
| 775 |
+
_dispatch_start = time.monotonic()
|
| 776 |
+
if function_name == "execute_code":
|
| 777 |
+
# Prefer the caller-provided list so subagents can't overwrite
|
| 778 |
+
# the parent's tool set via the process-global.
|
| 779 |
+
sandbox_enabled = enabled_tools if enabled_tools is not None else _last_resolved_tool_names
|
| 780 |
+
result = registry.dispatch(
|
| 781 |
+
function_name, function_args,
|
| 782 |
+
task_id=task_id,
|
| 783 |
+
enabled_tools=sandbox_enabled,
|
| 784 |
+
)
|
| 785 |
+
else:
|
| 786 |
+
result = registry.dispatch(
|
| 787 |
+
function_name, function_args,
|
| 788 |
+
task_id=task_id,
|
| 789 |
+
user_task=user_task,
|
| 790 |
+
)
|
| 791 |
+
duration_ms = int((time.monotonic() - _dispatch_start) * 1000)
|
| 792 |
+
|
| 793 |
+
try:
|
| 794 |
+
from hermes_cli.plugins import invoke_hook
|
| 795 |
+
invoke_hook(
|
| 796 |
+
"post_tool_call",
|
| 797 |
+
tool_name=function_name,
|
| 798 |
+
args=function_args,
|
| 799 |
+
result=result,
|
| 800 |
+
task_id=task_id or "",
|
| 801 |
+
session_id=session_id or "",
|
| 802 |
+
tool_call_id=tool_call_id or "",
|
| 803 |
+
duration_ms=duration_ms,
|
| 804 |
+
)
|
| 805 |
+
except Exception as _hook_err:
|
| 806 |
+
logger.debug("post_tool_call hook error: %s", _hook_err)
|
| 807 |
+
|
| 808 |
+
# Generic tool-result canonicalization seam: plugins receive the
|
| 809 |
+
# final result string (JSON, usually) and may replace it by
|
| 810 |
+
# returning a string from transform_tool_result. Runs after
|
| 811 |
+
# post_tool_call (which stays observational) and before the result
|
| 812 |
+
# is appended back into conversation context. Fail-open; the first
|
| 813 |
+
# valid string return wins; non-string returns are ignored.
|
| 814 |
+
try:
|
| 815 |
+
from hermes_cli.plugins import invoke_hook
|
| 816 |
+
hook_results = invoke_hook(
|
| 817 |
+
"transform_tool_result",
|
| 818 |
+
tool_name=function_name,
|
| 819 |
+
args=function_args,
|
| 820 |
+
result=result,
|
| 821 |
+
task_id=task_id or "",
|
| 822 |
+
session_id=session_id or "",
|
| 823 |
+
tool_call_id=tool_call_id or "",
|
| 824 |
+
duration_ms=duration_ms,
|
| 825 |
+
)
|
| 826 |
+
for hook_result in hook_results:
|
| 827 |
+
if isinstance(hook_result, str):
|
| 828 |
+
result = hook_result
|
| 829 |
+
break
|
| 830 |
+
except Exception as _hook_err:
|
| 831 |
+
logger.debug("transform_tool_result hook error: %s", _hook_err)
|
| 832 |
+
|
| 833 |
+
return result
|
| 834 |
+
|
| 835 |
+
except Exception as e:
|
| 836 |
+
error_msg = f"Error executing {function_name}: {str(e)}"
|
| 837 |
+
logger.exception(error_msg)
|
| 838 |
+
return json.dumps({"error": error_msg}, ensure_ascii=False)
|
| 839 |
+
|
| 840 |
+
|
| 841 |
+
# =============================================================================
|
| 842 |
+
# Backward-compat wrapper functions
|
| 843 |
+
# =============================================================================
|
| 844 |
+
|
| 845 |
+
def get_all_tool_names() -> List[str]:
|
| 846 |
+
"""Return all registered tool names."""
|
| 847 |
+
return registry.get_all_tool_names()
|
| 848 |
+
|
| 849 |
+
|
| 850 |
+
def get_toolset_for_tool(tool_name: str) -> Optional[str]:
|
| 851 |
+
"""Return the toolset a tool belongs to."""
|
| 852 |
+
return registry.get_toolset_for_tool(tool_name)
|
| 853 |
+
|
| 854 |
+
|
| 855 |
+
def get_available_toolsets() -> Dict[str, dict]:
|
| 856 |
+
"""Return toolset availability info for UI display."""
|
| 857 |
+
return registry.get_available_toolsets()
|
| 858 |
+
|
| 859 |
+
|
| 860 |
+
def check_toolset_requirements() -> Dict[str, bool]:
|
| 861 |
+
"""Return {toolset: available_bool} for every registered toolset."""
|
| 862 |
+
return registry.check_toolset_requirements()
|
| 863 |
+
|
| 864 |
+
|
| 865 |
+
def check_tool_availability(quiet: bool = False) -> Tuple[List[str], List[dict]]:
|
| 866 |
+
"""Return (available_toolsets, unavailable_info)."""
|
| 867 |
+
return registry.check_tool_availability(quiet=quiet)
|
package-lock.json
ADDED
|
@@ -0,0 +1,2660 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "hermes-agent",
|
| 3 |
+
"version": "1.0.0",
|
| 4 |
+
"lockfileVersion": 3,
|
| 5 |
+
"requires": true,
|
| 6 |
+
"packages": {
|
| 7 |
+
"": {
|
| 8 |
+
"name": "hermes-agent",
|
| 9 |
+
"version": "1.0.0",
|
| 10 |
+
"hasInstallScript": true,
|
| 11 |
+
"license": "MIT",
|
| 12 |
+
"dependencies": {
|
| 13 |
+
"@askjo/camofox-browser": "^1.5.2",
|
| 14 |
+
"agent-browser": "^0.26.0"
|
| 15 |
+
},
|
| 16 |
+
"engines": {
|
| 17 |
+
"node": ">=20.0.0"
|
| 18 |
+
}
|
| 19 |
+
},
|
| 20 |
+
"node_modules/@askjo/camofox-browser": {
|
| 21 |
+
"version": "1.5.2",
|
| 22 |
+
"resolved": "https://registry.npmjs.org/@askjo/camofox-browser/-/camofox-browser-1.5.2.tgz",
|
| 23 |
+
"integrity": "sha512-SvRCzhWnJaplxHkRVF9l1OWako6pp2eUw2mZKHOERUfLWDO2Xe/IKI+5bB+UT1TNvO45P6XdhgfAtihcTEARCg==",
|
| 24 |
+
"hasInstallScript": true,
|
| 25 |
+
"license": "MIT",
|
| 26 |
+
"dependencies": {
|
| 27 |
+
"camoufox-js": "^0.8.5",
|
| 28 |
+
"express": "^4.18.2",
|
| 29 |
+
"playwright": "^1.50.0",
|
| 30 |
+
"playwright-core": "^1.58.0",
|
| 31 |
+
"playwright-extra": "^4.3.6",
|
| 32 |
+
"prom-client": "^15.1.3",
|
| 33 |
+
"puppeteer-extra-plugin-stealth": "^2.11.2"
|
| 34 |
+
},
|
| 35 |
+
"engines": {
|
| 36 |
+
"node": ">=18"
|
| 37 |
+
}
|
| 38 |
+
},
|
| 39 |
+
"node_modules/@opentelemetry/api": {
|
| 40 |
+
"version": "1.9.1",
|
| 41 |
+
"resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.1.tgz",
|
| 42 |
+
"integrity": "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==",
|
| 43 |
+
"license": "Apache-2.0",
|
| 44 |
+
"engines": {
|
| 45 |
+
"node": ">=8.0.0"
|
| 46 |
+
}
|
| 47 |
+
},
|
| 48 |
+
"node_modules/@sindresorhus/is": {
|
| 49 |
+
"version": "4.6.0",
|
| 50 |
+
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz",
|
| 51 |
+
"integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==",
|
| 52 |
+
"license": "MIT",
|
| 53 |
+
"engines": {
|
| 54 |
+
"node": ">=10"
|
| 55 |
+
},
|
| 56 |
+
"funding": {
|
| 57 |
+
"url": "https://github.com/sindresorhus/is?sponsor=1"
|
| 58 |
+
}
|
| 59 |
+
},
|
| 60 |
+
"node_modules/@types/debug": {
|
| 61 |
+
"version": "4.1.13",
|
| 62 |
+
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz",
|
| 63 |
+
"integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==",
|
| 64 |
+
"license": "MIT",
|
| 65 |
+
"dependencies": {
|
| 66 |
+
"@types/ms": "*"
|
| 67 |
+
}
|
| 68 |
+
},
|
| 69 |
+
"node_modules/@types/ms": {
|
| 70 |
+
"version": "2.1.0",
|
| 71 |
+
"resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz",
|
| 72 |
+
"integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==",
|
| 73 |
+
"license": "MIT"
|
| 74 |
+
},
|
| 75 |
+
"node_modules/accepts": {
|
| 76 |
+
"version": "1.3.8",
|
| 77 |
+
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
|
| 78 |
+
"integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
|
| 79 |
+
"license": "MIT",
|
| 80 |
+
"dependencies": {
|
| 81 |
+
"mime-types": "~2.1.34",
|
| 82 |
+
"negotiator": "0.6.3"
|
| 83 |
+
},
|
| 84 |
+
"engines": {
|
| 85 |
+
"node": ">= 0.6"
|
| 86 |
+
}
|
| 87 |
+
},
|
| 88 |
+
"node_modules/adm-zip": {
|
| 89 |
+
"version": "0.5.17",
|
| 90 |
+
"resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.17.tgz",
|
| 91 |
+
"integrity": "sha512-+Ut8d9LLqwEvHHJl1+PIHqoyDxFgVN847JTVM3Izi3xHDWPE4UtzzXysMZQs64DMcrJfBeS/uoEP4AD3HQHnQQ==",
|
| 92 |
+
"license": "MIT",
|
| 93 |
+
"engines": {
|
| 94 |
+
"node": ">=12.0"
|
| 95 |
+
}
|
| 96 |
+
},
|
| 97 |
+
"node_modules/agent-browser": {
|
| 98 |
+
"version": "0.26.0",
|
| 99 |
+
"resolved": "https://registry.npmjs.org/agent-browser/-/agent-browser-0.26.0.tgz",
|
| 100 |
+
"integrity": "sha512-pdqSfjwbFSp+qnwlb2g23e9wXveIOfMi19xpPA9xZUbzEAUp6W4YBZj6Ybj8z4M7WkcbGDDYc+oDIHDt9R3EDQ==",
|
| 101 |
+
"hasInstallScript": true,
|
| 102 |
+
"license": "Apache-2.0",
|
| 103 |
+
"bin": {
|
| 104 |
+
"agent-browser": "bin/agent-browser.js"
|
| 105 |
+
}
|
| 106 |
+
},
|
| 107 |
+
"node_modules/arr-union": {
|
| 108 |
+
"version": "3.1.0",
|
| 109 |
+
"resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz",
|
| 110 |
+
"integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==",
|
| 111 |
+
"license": "MIT",
|
| 112 |
+
"engines": {
|
| 113 |
+
"node": ">=0.10.0"
|
| 114 |
+
}
|
| 115 |
+
},
|
| 116 |
+
"node_modules/array-flatten": {
|
| 117 |
+
"version": "1.1.1",
|
| 118 |
+
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
| 119 |
+
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
|
| 120 |
+
"license": "MIT"
|
| 121 |
+
},
|
| 122 |
+
"node_modules/balanced-match": {
|
| 123 |
+
"version": "4.0.4",
|
| 124 |
+
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
|
| 125 |
+
"integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
|
| 126 |
+
"license": "MIT",
|
| 127 |
+
"engines": {
|
| 128 |
+
"node": "18 || 20 || >=22"
|
| 129 |
+
}
|
| 130 |
+
},
|
| 131 |
+
"node_modules/base64-js": {
|
| 132 |
+
"version": "1.5.1",
|
| 133 |
+
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
| 134 |
+
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
|
| 135 |
+
"funding": [
|
| 136 |
+
{
|
| 137 |
+
"type": "github",
|
| 138 |
+
"url": "https://github.com/sponsors/feross"
|
| 139 |
+
},
|
| 140 |
+
{
|
| 141 |
+
"type": "patreon",
|
| 142 |
+
"url": "https://www.patreon.com/feross"
|
| 143 |
+
},
|
| 144 |
+
{
|
| 145 |
+
"type": "consulting",
|
| 146 |
+
"url": "https://feross.org/support"
|
| 147 |
+
}
|
| 148 |
+
],
|
| 149 |
+
"license": "MIT"
|
| 150 |
+
},
|
| 151 |
+
"node_modules/baseline-browser-mapping": {
|
| 152 |
+
"version": "2.10.18",
|
| 153 |
+
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.18.tgz",
|
| 154 |
+
"integrity": "sha512-VSnGQAOLtP5mib/DPyg2/t+Tlv65NTBz83BJBJvmLVHHuKJVaDOBvJJykiT5TR++em5nfAySPccDZDa4oSrn8A==",
|
| 155 |
+
"license": "Apache-2.0",
|
| 156 |
+
"bin": {
|
| 157 |
+
"baseline-browser-mapping": "dist/cli.cjs"
|
| 158 |
+
},
|
| 159 |
+
"engines": {
|
| 160 |
+
"node": ">=6.0.0"
|
| 161 |
+
}
|
| 162 |
+
},
|
| 163 |
+
"node_modules/better-sqlite3": {
|
| 164 |
+
"version": "12.9.0",
|
| 165 |
+
"resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-12.9.0.tgz",
|
| 166 |
+
"integrity": "sha512-wqUv4Gm3toFpHDQmaKD4QhZm3g1DjUBI0yzS4UBl6lElUmXFYdTQmmEDpAFa5o8FiFiymURypEnfVHzILKaxqQ==",
|
| 167 |
+
"hasInstallScript": true,
|
| 168 |
+
"license": "MIT",
|
| 169 |
+
"dependencies": {
|
| 170 |
+
"bindings": "^1.5.0",
|
| 171 |
+
"prebuild-install": "^7.1.1"
|
| 172 |
+
},
|
| 173 |
+
"engines": {
|
| 174 |
+
"node": "20.x || 22.x || 23.x || 24.x || 25.x"
|
| 175 |
+
}
|
| 176 |
+
},
|
| 177 |
+
"node_modules/bindings": {
|
| 178 |
+
"version": "1.5.0",
|
| 179 |
+
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
|
| 180 |
+
"integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
|
| 181 |
+
"license": "MIT",
|
| 182 |
+
"dependencies": {
|
| 183 |
+
"file-uri-to-path": "1.0.0"
|
| 184 |
+
}
|
| 185 |
+
},
|
| 186 |
+
"node_modules/bintrees": {
|
| 187 |
+
"version": "1.0.2",
|
| 188 |
+
"resolved": "https://registry.npmjs.org/bintrees/-/bintrees-1.0.2.tgz",
|
| 189 |
+
"integrity": "sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw==",
|
| 190 |
+
"license": "MIT"
|
| 191 |
+
},
|
| 192 |
+
"node_modules/bl": {
|
| 193 |
+
"version": "4.1.0",
|
| 194 |
+
"resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
|
| 195 |
+
"integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
|
| 196 |
+
"license": "MIT",
|
| 197 |
+
"dependencies": {
|
| 198 |
+
"buffer": "^5.5.0",
|
| 199 |
+
"inherits": "^2.0.4",
|
| 200 |
+
"readable-stream": "^3.4.0"
|
| 201 |
+
}
|
| 202 |
+
},
|
| 203 |
+
"node_modules/body-parser": {
|
| 204 |
+
"version": "1.20.4",
|
| 205 |
+
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz",
|
| 206 |
+
"integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==",
|
| 207 |
+
"license": "MIT",
|
| 208 |
+
"dependencies": {
|
| 209 |
+
"bytes": "~3.1.2",
|
| 210 |
+
"content-type": "~1.0.5",
|
| 211 |
+
"debug": "2.6.9",
|
| 212 |
+
"depd": "2.0.0",
|
| 213 |
+
"destroy": "~1.2.0",
|
| 214 |
+
"http-errors": "~2.0.1",
|
| 215 |
+
"iconv-lite": "~0.4.24",
|
| 216 |
+
"on-finished": "~2.4.1",
|
| 217 |
+
"qs": "~6.14.0",
|
| 218 |
+
"raw-body": "~2.5.3",
|
| 219 |
+
"type-is": "~1.6.18",
|
| 220 |
+
"unpipe": "~1.0.0"
|
| 221 |
+
},
|
| 222 |
+
"engines": {
|
| 223 |
+
"node": ">= 0.8",
|
| 224 |
+
"npm": "1.2.8000 || >= 1.4.16"
|
| 225 |
+
}
|
| 226 |
+
},
|
| 227 |
+
"node_modules/brace-expansion": {
|
| 228 |
+
"version": "5.0.5",
|
| 229 |
+
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
|
| 230 |
+
"integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
|
| 231 |
+
"license": "MIT",
|
| 232 |
+
"dependencies": {
|
| 233 |
+
"balanced-match": "^4.0.2"
|
| 234 |
+
},
|
| 235 |
+
"engines": {
|
| 236 |
+
"node": "18 || 20 || >=22"
|
| 237 |
+
}
|
| 238 |
+
},
|
| 239 |
+
"node_modules/browserslist": {
|
| 240 |
+
"version": "4.28.2",
|
| 241 |
+
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz",
|
| 242 |
+
"integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==",
|
| 243 |
+
"funding": [
|
| 244 |
+
{
|
| 245 |
+
"type": "opencollective",
|
| 246 |
+
"url": "https://opencollective.com/browserslist"
|
| 247 |
+
},
|
| 248 |
+
{
|
| 249 |
+
"type": "tidelift",
|
| 250 |
+
"url": "https://tidelift.com/funding/github/npm/browserslist"
|
| 251 |
+
},
|
| 252 |
+
{
|
| 253 |
+
"type": "github",
|
| 254 |
+
"url": "https://github.com/sponsors/ai"
|
| 255 |
+
}
|
| 256 |
+
],
|
| 257 |
+
"license": "MIT",
|
| 258 |
+
"dependencies": {
|
| 259 |
+
"baseline-browser-mapping": "^2.10.12",
|
| 260 |
+
"caniuse-lite": "^1.0.30001782",
|
| 261 |
+
"electron-to-chromium": "^1.5.328",
|
| 262 |
+
"node-releases": "^2.0.36",
|
| 263 |
+
"update-browserslist-db": "^1.2.3"
|
| 264 |
+
},
|
| 265 |
+
"bin": {
|
| 266 |
+
"browserslist": "cli.js"
|
| 267 |
+
},
|
| 268 |
+
"engines": {
|
| 269 |
+
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
|
| 270 |
+
}
|
| 271 |
+
},
|
| 272 |
+
"node_modules/buffer": {
|
| 273 |
+
"version": "5.7.1",
|
| 274 |
+
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
|
| 275 |
+
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
|
| 276 |
+
"funding": [
|
| 277 |
+
{
|
| 278 |
+
"type": "github",
|
| 279 |
+
"url": "https://github.com/sponsors/feross"
|
| 280 |
+
},
|
| 281 |
+
{
|
| 282 |
+
"type": "patreon",
|
| 283 |
+
"url": "https://www.patreon.com/feross"
|
| 284 |
+
},
|
| 285 |
+
{
|
| 286 |
+
"type": "consulting",
|
| 287 |
+
"url": "https://feross.org/support"
|
| 288 |
+
}
|
| 289 |
+
],
|
| 290 |
+
"license": "MIT",
|
| 291 |
+
"dependencies": {
|
| 292 |
+
"base64-js": "^1.3.1",
|
| 293 |
+
"ieee754": "^1.1.13"
|
| 294 |
+
}
|
| 295 |
+
},
|
| 296 |
+
"node_modules/bytes": {
|
| 297 |
+
"version": "3.1.2",
|
| 298 |
+
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
|
| 299 |
+
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
|
| 300 |
+
"license": "MIT",
|
| 301 |
+
"engines": {
|
| 302 |
+
"node": ">= 0.8"
|
| 303 |
+
}
|
| 304 |
+
},
|
| 305 |
+
"node_modules/call-bind-apply-helpers": {
|
| 306 |
+
"version": "1.0.2",
|
| 307 |
+
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
| 308 |
+
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
| 309 |
+
"license": "MIT",
|
| 310 |
+
"dependencies": {
|
| 311 |
+
"es-errors": "^1.3.0",
|
| 312 |
+
"function-bind": "^1.1.2"
|
| 313 |
+
},
|
| 314 |
+
"engines": {
|
| 315 |
+
"node": ">= 0.4"
|
| 316 |
+
}
|
| 317 |
+
},
|
| 318 |
+
"node_modules/call-bound": {
|
| 319 |
+
"version": "1.0.4",
|
| 320 |
+
"resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
|
| 321 |
+
"integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
|
| 322 |
+
"license": "MIT",
|
| 323 |
+
"dependencies": {
|
| 324 |
+
"call-bind-apply-helpers": "^1.0.2",
|
| 325 |
+
"get-intrinsic": "^1.3.0"
|
| 326 |
+
},
|
| 327 |
+
"engines": {
|
| 328 |
+
"node": ">= 0.4"
|
| 329 |
+
},
|
| 330 |
+
"funding": {
|
| 331 |
+
"url": "https://github.com/sponsors/ljharb"
|
| 332 |
+
}
|
| 333 |
+
},
|
| 334 |
+
"node_modules/callsites": {
|
| 335 |
+
"version": "3.1.0",
|
| 336 |
+
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
|
| 337 |
+
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
|
| 338 |
+
"license": "MIT",
|
| 339 |
+
"engines": {
|
| 340 |
+
"node": ">=6"
|
| 341 |
+
}
|
| 342 |
+
},
|
| 343 |
+
"node_modules/camoufox-js": {
|
| 344 |
+
"version": "0.8.5",
|
| 345 |
+
"resolved": "https://registry.npmjs.org/camoufox-js/-/camoufox-js-0.8.5.tgz",
|
| 346 |
+
"integrity": "sha512-20ihPbspAcOVSUTX9Drxxp0C116DON1n8OVA1eUDglWZiHwiHwFVFOMrIEBwAHMZpU11mIEH/kawJtstRIrDPA==",
|
| 347 |
+
"license": "MPL-2.0",
|
| 348 |
+
"dependencies": {
|
| 349 |
+
"adm-zip": "^0.5.16",
|
| 350 |
+
"better-sqlite3": "^12.2.0",
|
| 351 |
+
"commander": "^14.0.0",
|
| 352 |
+
"fingerprint-generator": "^2.1.66",
|
| 353 |
+
"glob": "^13.0.0",
|
| 354 |
+
"impit": "^0.7.0",
|
| 355 |
+
"language-tags": "^2.0.1",
|
| 356 |
+
"maxmind": "^5.0.0",
|
| 357 |
+
"progress": "^2.0.3",
|
| 358 |
+
"ua-parser-js": "^2.0.2",
|
| 359 |
+
"xml2js": "^0.6.2"
|
| 360 |
+
},
|
| 361 |
+
"bin": {
|
| 362 |
+
"camoufox-js": "dist/__main__.js"
|
| 363 |
+
},
|
| 364 |
+
"engines": {
|
| 365 |
+
"node": ">= 20"
|
| 366 |
+
},
|
| 367 |
+
"peerDependencies": {
|
| 368 |
+
"playwright-core": "*"
|
| 369 |
+
}
|
| 370 |
+
},
|
| 371 |
+
"node_modules/caniuse-lite": {
|
| 372 |
+
"version": "1.0.30001787",
|
| 373 |
+
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001787.tgz",
|
| 374 |
+
"integrity": "sha512-mNcrMN9KeI68u7muanUpEejSLghOKlVhRqS/Za2IeyGllJ9I9otGpR9g3nsw7n4W378TE/LyIteA0+/FOZm4Kg==",
|
| 375 |
+
"funding": [
|
| 376 |
+
{
|
| 377 |
+
"type": "opencollective",
|
| 378 |
+
"url": "https://opencollective.com/browserslist"
|
| 379 |
+
},
|
| 380 |
+
{
|
| 381 |
+
"type": "tidelift",
|
| 382 |
+
"url": "https://tidelift.com/funding/github/npm/caniuse-lite"
|
| 383 |
+
},
|
| 384 |
+
{
|
| 385 |
+
"type": "github",
|
| 386 |
+
"url": "https://github.com/sponsors/ai"
|
| 387 |
+
}
|
| 388 |
+
],
|
| 389 |
+
"license": "CC-BY-4.0"
|
| 390 |
+
},
|
| 391 |
+
"node_modules/chownr": {
|
| 392 |
+
"version": "1.1.4",
|
| 393 |
+
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
|
| 394 |
+
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
|
| 395 |
+
"license": "ISC"
|
| 396 |
+
},
|
| 397 |
+
"node_modules/clone-deep": {
|
| 398 |
+
"version": "0.2.4",
|
| 399 |
+
"resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-0.2.4.tgz",
|
| 400 |
+
"integrity": "sha512-we+NuQo2DHhSl+DP6jlUiAhyAjBQrYnpOk15rN6c6JSPScjiCLh8IbSU+VTcph6YS3o7mASE8a0+gbZ7ChLpgg==",
|
| 401 |
+
"license": "MIT",
|
| 402 |
+
"dependencies": {
|
| 403 |
+
"for-own": "^0.1.3",
|
| 404 |
+
"is-plain-object": "^2.0.1",
|
| 405 |
+
"kind-of": "^3.0.2",
|
| 406 |
+
"lazy-cache": "^1.0.3",
|
| 407 |
+
"shallow-clone": "^0.1.2"
|
| 408 |
+
},
|
| 409 |
+
"engines": {
|
| 410 |
+
"node": ">=0.10.0"
|
| 411 |
+
}
|
| 412 |
+
},
|
| 413 |
+
"node_modules/commander": {
|
| 414 |
+
"version": "14.0.3",
|
| 415 |
+
"resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz",
|
| 416 |
+
"integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==",
|
| 417 |
+
"license": "MIT",
|
| 418 |
+
"engines": {
|
| 419 |
+
"node": ">=20"
|
| 420 |
+
}
|
| 421 |
+
},
|
| 422 |
+
"node_modules/concat-map": {
|
| 423 |
+
"version": "0.0.1",
|
| 424 |
+
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
| 425 |
+
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
|
| 426 |
+
"license": "MIT"
|
| 427 |
+
},
|
| 428 |
+
"node_modules/content-disposition": {
|
| 429 |
+
"version": "0.5.4",
|
| 430 |
+
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
|
| 431 |
+
"integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
|
| 432 |
+
"license": "MIT",
|
| 433 |
+
"dependencies": {
|
| 434 |
+
"safe-buffer": "5.2.1"
|
| 435 |
+
},
|
| 436 |
+
"engines": {
|
| 437 |
+
"node": ">= 0.6"
|
| 438 |
+
}
|
| 439 |
+
},
|
| 440 |
+
"node_modules/content-type": {
|
| 441 |
+
"version": "1.0.5",
|
| 442 |
+
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
|
| 443 |
+
"integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
|
| 444 |
+
"license": "MIT",
|
| 445 |
+
"engines": {
|
| 446 |
+
"node": ">= 0.6"
|
| 447 |
+
}
|
| 448 |
+
},
|
| 449 |
+
"node_modules/cookie": {
|
| 450 |
+
"version": "0.7.2",
|
| 451 |
+
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
|
| 452 |
+
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
|
| 453 |
+
"license": "MIT",
|
| 454 |
+
"engines": {
|
| 455 |
+
"node": ">= 0.6"
|
| 456 |
+
}
|
| 457 |
+
},
|
| 458 |
+
"node_modules/cookie-signature": {
|
| 459 |
+
"version": "1.0.7",
|
| 460 |
+
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz",
|
| 461 |
+
"integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==",
|
| 462 |
+
"license": "MIT"
|
| 463 |
+
},
|
| 464 |
+
"node_modules/debug": {
|
| 465 |
+
"version": "2.6.9",
|
| 466 |
+
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
| 467 |
+
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
| 468 |
+
"license": "MIT",
|
| 469 |
+
"dependencies": {
|
| 470 |
+
"ms": "2.0.0"
|
| 471 |
+
}
|
| 472 |
+
},
|
| 473 |
+
"node_modules/decompress-response": {
|
| 474 |
+
"version": "6.0.0",
|
| 475 |
+
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
|
| 476 |
+
"integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
|
| 477 |
+
"license": "MIT",
|
| 478 |
+
"dependencies": {
|
| 479 |
+
"mimic-response": "^3.1.0"
|
| 480 |
+
},
|
| 481 |
+
"engines": {
|
| 482 |
+
"node": ">=10"
|
| 483 |
+
},
|
| 484 |
+
"funding": {
|
| 485 |
+
"url": "https://github.com/sponsors/sindresorhus"
|
| 486 |
+
}
|
| 487 |
+
},
|
| 488 |
+
"node_modules/deep-extend": {
|
| 489 |
+
"version": "0.6.0",
|
| 490 |
+
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
|
| 491 |
+
"integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
|
| 492 |
+
"license": "MIT",
|
| 493 |
+
"engines": {
|
| 494 |
+
"node": ">=4.0.0"
|
| 495 |
+
}
|
| 496 |
+
},
|
| 497 |
+
"node_modules/deepmerge": {
|
| 498 |
+
"version": "4.3.1",
|
| 499 |
+
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
|
| 500 |
+
"integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
|
| 501 |
+
"license": "MIT",
|
| 502 |
+
"engines": {
|
| 503 |
+
"node": ">=0.10.0"
|
| 504 |
+
}
|
| 505 |
+
},
|
| 506 |
+
"node_modules/depd": {
|
| 507 |
+
"version": "2.0.0",
|
| 508 |
+
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
| 509 |
+
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
|
| 510 |
+
"license": "MIT",
|
| 511 |
+
"engines": {
|
| 512 |
+
"node": ">= 0.8"
|
| 513 |
+
}
|
| 514 |
+
},
|
| 515 |
+
"node_modules/destroy": {
|
| 516 |
+
"version": "1.2.0",
|
| 517 |
+
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
|
| 518 |
+
"integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
|
| 519 |
+
"license": "MIT",
|
| 520 |
+
"engines": {
|
| 521 |
+
"node": ">= 0.8",
|
| 522 |
+
"npm": "1.2.8000 || >= 1.4.16"
|
| 523 |
+
}
|
| 524 |
+
},
|
| 525 |
+
"node_modules/detect-europe-js": {
|
| 526 |
+
"version": "0.1.2",
|
| 527 |
+
"resolved": "https://registry.npmjs.org/detect-europe-js/-/detect-europe-js-0.1.2.tgz",
|
| 528 |
+
"integrity": "sha512-lgdERlL3u0aUdHocoouzT10d9I89VVhk0qNRmll7mXdGfJT1/wqZ2ZLA4oJAjeACPY5fT1wsbq2AT+GkuInsow==",
|
| 529 |
+
"funding": [
|
| 530 |
+
{
|
| 531 |
+
"type": "github",
|
| 532 |
+
"url": "https://github.com/sponsors/faisalman"
|
| 533 |
+
},
|
| 534 |
+
{
|
| 535 |
+
"type": "opencollective",
|
| 536 |
+
"url": "https://opencollective.com/ua-parser-js"
|
| 537 |
+
},
|
| 538 |
+
{
|
| 539 |
+
"type": "paypal",
|
| 540 |
+
"url": "https://paypal.me/faisalman"
|
| 541 |
+
}
|
| 542 |
+
],
|
| 543 |
+
"license": "MIT"
|
| 544 |
+
},
|
| 545 |
+
"node_modules/detect-libc": {
|
| 546 |
+
"version": "2.1.2",
|
| 547 |
+
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
|
| 548 |
+
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
|
| 549 |
+
"license": "Apache-2.0",
|
| 550 |
+
"engines": {
|
| 551 |
+
"node": ">=8"
|
| 552 |
+
}
|
| 553 |
+
},
|
| 554 |
+
"node_modules/dot-prop": {
|
| 555 |
+
"version": "6.0.1",
|
| 556 |
+
"resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz",
|
| 557 |
+
"integrity": "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==",
|
| 558 |
+
"license": "MIT",
|
| 559 |
+
"dependencies": {
|
| 560 |
+
"is-obj": "^2.0.0"
|
| 561 |
+
},
|
| 562 |
+
"engines": {
|
| 563 |
+
"node": ">=10"
|
| 564 |
+
},
|
| 565 |
+
"funding": {
|
| 566 |
+
"url": "https://github.com/sponsors/sindresorhus"
|
| 567 |
+
}
|
| 568 |
+
},
|
| 569 |
+
"node_modules/dunder-proto": {
|
| 570 |
+
"version": "1.0.1",
|
| 571 |
+
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
| 572 |
+
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
| 573 |
+
"license": "MIT",
|
| 574 |
+
"dependencies": {
|
| 575 |
+
"call-bind-apply-helpers": "^1.0.1",
|
| 576 |
+
"es-errors": "^1.3.0",
|
| 577 |
+
"gopd": "^1.2.0"
|
| 578 |
+
},
|
| 579 |
+
"engines": {
|
| 580 |
+
"node": ">= 0.4"
|
| 581 |
+
}
|
| 582 |
+
},
|
| 583 |
+
"node_modules/ee-first": {
|
| 584 |
+
"version": "1.1.1",
|
| 585 |
+
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
| 586 |
+
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
|
| 587 |
+
"license": "MIT"
|
| 588 |
+
},
|
| 589 |
+
"node_modules/electron-to-chromium": {
|
| 590 |
+
"version": "1.5.335",
|
| 591 |
+
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.335.tgz",
|
| 592 |
+
"integrity": "sha512-q9n5T4BR4Xwa2cwbrwcsDJtHD/enpQ5S1xF1IAtdqf5AAgqDFmR/aakqH3ChFdqd/QXJhS3rnnXFtexU7rax6Q==",
|
| 593 |
+
"license": "ISC"
|
| 594 |
+
},
|
| 595 |
+
"node_modules/encodeurl": {
|
| 596 |
+
"version": "2.0.0",
|
| 597 |
+
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
|
| 598 |
+
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
|
| 599 |
+
"license": "MIT",
|
| 600 |
+
"engines": {
|
| 601 |
+
"node": ">= 0.8"
|
| 602 |
+
}
|
| 603 |
+
},
|
| 604 |
+
"node_modules/end-of-stream": {
|
| 605 |
+
"version": "1.4.5",
|
| 606 |
+
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
|
| 607 |
+
"integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==",
|
| 608 |
+
"license": "MIT",
|
| 609 |
+
"dependencies": {
|
| 610 |
+
"once": "^1.4.0"
|
| 611 |
+
}
|
| 612 |
+
},
|
| 613 |
+
"node_modules/es-define-property": {
|
| 614 |
+
"version": "1.0.1",
|
| 615 |
+
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
| 616 |
+
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
| 617 |
+
"license": "MIT",
|
| 618 |
+
"engines": {
|
| 619 |
+
"node": ">= 0.4"
|
| 620 |
+
}
|
| 621 |
+
},
|
| 622 |
+
"node_modules/es-errors": {
|
| 623 |
+
"version": "1.3.0",
|
| 624 |
+
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
| 625 |
+
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
| 626 |
+
"license": "MIT",
|
| 627 |
+
"engines": {
|
| 628 |
+
"node": ">= 0.4"
|
| 629 |
+
}
|
| 630 |
+
},
|
| 631 |
+
"node_modules/es-object-atoms": {
|
| 632 |
+
"version": "1.1.1",
|
| 633 |
+
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
| 634 |
+
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
| 635 |
+
"license": "MIT",
|
| 636 |
+
"dependencies": {
|
| 637 |
+
"es-errors": "^1.3.0"
|
| 638 |
+
},
|
| 639 |
+
"engines": {
|
| 640 |
+
"node": ">= 0.4"
|
| 641 |
+
}
|
| 642 |
+
},
|
| 643 |
+
"node_modules/escalade": {
|
| 644 |
+
"version": "3.2.0",
|
| 645 |
+
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
|
| 646 |
+
"integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
|
| 647 |
+
"license": "MIT",
|
| 648 |
+
"engines": {
|
| 649 |
+
"node": ">=6"
|
| 650 |
+
}
|
| 651 |
+
},
|
| 652 |
+
"node_modules/escape-html": {
|
| 653 |
+
"version": "1.0.3",
|
| 654 |
+
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
| 655 |
+
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
|
| 656 |
+
"license": "MIT"
|
| 657 |
+
},
|
| 658 |
+
"node_modules/etag": {
|
| 659 |
+
"version": "1.8.1",
|
| 660 |
+
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
|
| 661 |
+
"integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
|
| 662 |
+
"license": "MIT",
|
| 663 |
+
"engines": {
|
| 664 |
+
"node": ">= 0.6"
|
| 665 |
+
}
|
| 666 |
+
},
|
| 667 |
+
"node_modules/expand-template": {
|
| 668 |
+
"version": "2.0.3",
|
| 669 |
+
"resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
|
| 670 |
+
"integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==",
|
| 671 |
+
"license": "(MIT OR WTFPL)",
|
| 672 |
+
"engines": {
|
| 673 |
+
"node": ">=6"
|
| 674 |
+
}
|
| 675 |
+
},
|
| 676 |
+
"node_modules/express": {
|
| 677 |
+
"version": "4.22.1",
|
| 678 |
+
"resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz",
|
| 679 |
+
"integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==",
|
| 680 |
+
"license": "MIT",
|
| 681 |
+
"dependencies": {
|
| 682 |
+
"accepts": "~1.3.8",
|
| 683 |
+
"array-flatten": "1.1.1",
|
| 684 |
+
"body-parser": "~1.20.3",
|
| 685 |
+
"content-disposition": "~0.5.4",
|
| 686 |
+
"content-type": "~1.0.4",
|
| 687 |
+
"cookie": "~0.7.1",
|
| 688 |
+
"cookie-signature": "~1.0.6",
|
| 689 |
+
"debug": "2.6.9",
|
| 690 |
+
"depd": "2.0.0",
|
| 691 |
+
"encodeurl": "~2.0.0",
|
| 692 |
+
"escape-html": "~1.0.3",
|
| 693 |
+
"etag": "~1.8.1",
|
| 694 |
+
"finalhandler": "~1.3.1",
|
| 695 |
+
"fresh": "~0.5.2",
|
| 696 |
+
"http-errors": "~2.0.0",
|
| 697 |
+
"merge-descriptors": "1.0.3",
|
| 698 |
+
"methods": "~1.1.2",
|
| 699 |
+
"on-finished": "~2.4.1",
|
| 700 |
+
"parseurl": "~1.3.3",
|
| 701 |
+
"path-to-regexp": "~0.1.12",
|
| 702 |
+
"proxy-addr": "~2.0.7",
|
| 703 |
+
"qs": "~6.14.0",
|
| 704 |
+
"range-parser": "~1.2.1",
|
| 705 |
+
"safe-buffer": "5.2.1",
|
| 706 |
+
"send": "~0.19.0",
|
| 707 |
+
"serve-static": "~1.16.2",
|
| 708 |
+
"setprototypeof": "1.2.0",
|
| 709 |
+
"statuses": "~2.0.1",
|
| 710 |
+
"type-is": "~1.6.18",
|
| 711 |
+
"utils-merge": "1.0.1",
|
| 712 |
+
"vary": "~1.1.2"
|
| 713 |
+
},
|
| 714 |
+
"engines": {
|
| 715 |
+
"node": ">= 0.10.0"
|
| 716 |
+
},
|
| 717 |
+
"funding": {
|
| 718 |
+
"type": "opencollective",
|
| 719 |
+
"url": "https://opencollective.com/express"
|
| 720 |
+
}
|
| 721 |
+
},
|
| 722 |
+
"node_modules/file-uri-to-path": {
|
| 723 |
+
"version": "1.0.0",
|
| 724 |
+
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
|
| 725 |
+
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
|
| 726 |
+
"license": "MIT"
|
| 727 |
+
},
|
| 728 |
+
"node_modules/finalhandler": {
|
| 729 |
+
"version": "1.3.2",
|
| 730 |
+
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz",
|
| 731 |
+
"integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==",
|
| 732 |
+
"license": "MIT",
|
| 733 |
+
"dependencies": {
|
| 734 |
+
"debug": "2.6.9",
|
| 735 |
+
"encodeurl": "~2.0.0",
|
| 736 |
+
"escape-html": "~1.0.3",
|
| 737 |
+
"on-finished": "~2.4.1",
|
| 738 |
+
"parseurl": "~1.3.3",
|
| 739 |
+
"statuses": "~2.0.2",
|
| 740 |
+
"unpipe": "~1.0.0"
|
| 741 |
+
},
|
| 742 |
+
"engines": {
|
| 743 |
+
"node": ">= 0.8"
|
| 744 |
+
}
|
| 745 |
+
},
|
| 746 |
+
"node_modules/fingerprint-generator": {
|
| 747 |
+
"version": "2.1.82",
|
| 748 |
+
"resolved": "https://registry.npmjs.org/fingerprint-generator/-/fingerprint-generator-2.1.82.tgz",
|
| 749 |
+
"integrity": "sha512-5Z/yCKW324pMyMarpIKe/QPdkrFWKNJv3ktdU+fXHri80+HAwNE6QhMvEvsMkK9Q8DeCXZlpPHV77UBa1nFb4A==",
|
| 750 |
+
"license": "Apache-2.0",
|
| 751 |
+
"dependencies": {
|
| 752 |
+
"generative-bayesian-network": "^2.1.82",
|
| 753 |
+
"header-generator": "^2.1.82",
|
| 754 |
+
"tslib": "^2.4.0"
|
| 755 |
+
},
|
| 756 |
+
"engines": {
|
| 757 |
+
"node": ">=16.0.0"
|
| 758 |
+
}
|
| 759 |
+
},
|
| 760 |
+
"node_modules/for-in": {
|
| 761 |
+
"version": "1.0.2",
|
| 762 |
+
"resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
|
| 763 |
+
"integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==",
|
| 764 |
+
"license": "MIT",
|
| 765 |
+
"engines": {
|
| 766 |
+
"node": ">=0.10.0"
|
| 767 |
+
}
|
| 768 |
+
},
|
| 769 |
+
"node_modules/for-own": {
|
| 770 |
+
"version": "0.1.5",
|
| 771 |
+
"resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz",
|
| 772 |
+
"integrity": "sha512-SKmowqGTJoPzLO1T0BBJpkfp3EMacCMOuH40hOUbrbzElVktk4DioXVM99QkLCyKoiuOmyjgcWMpVz2xjE7LZw==",
|
| 773 |
+
"license": "MIT",
|
| 774 |
+
"dependencies": {
|
| 775 |
+
"for-in": "^1.0.1"
|
| 776 |
+
},
|
| 777 |
+
"engines": {
|
| 778 |
+
"node": ">=0.10.0"
|
| 779 |
+
}
|
| 780 |
+
},
|
| 781 |
+
"node_modules/forwarded": {
|
| 782 |
+
"version": "0.2.0",
|
| 783 |
+
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
|
| 784 |
+
"integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
|
| 785 |
+
"license": "MIT",
|
| 786 |
+
"engines": {
|
| 787 |
+
"node": ">= 0.6"
|
| 788 |
+
}
|
| 789 |
+
},
|
| 790 |
+
"node_modules/fresh": {
|
| 791 |
+
"version": "0.5.2",
|
| 792 |
+
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
|
| 793 |
+
"integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
|
| 794 |
+
"license": "MIT",
|
| 795 |
+
"engines": {
|
| 796 |
+
"node": ">= 0.6"
|
| 797 |
+
}
|
| 798 |
+
},
|
| 799 |
+
"node_modules/fs-constants": {
|
| 800 |
+
"version": "1.0.0",
|
| 801 |
+
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
|
| 802 |
+
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
|
| 803 |
+
"license": "MIT"
|
| 804 |
+
},
|
| 805 |
+
"node_modules/fs-extra": {
|
| 806 |
+
"version": "10.1.0",
|
| 807 |
+
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
|
| 808 |
+
"integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==",
|
| 809 |
+
"license": "MIT",
|
| 810 |
+
"dependencies": {
|
| 811 |
+
"graceful-fs": "^4.2.0",
|
| 812 |
+
"jsonfile": "^6.0.1",
|
| 813 |
+
"universalify": "^2.0.0"
|
| 814 |
+
},
|
| 815 |
+
"engines": {
|
| 816 |
+
"node": ">=12"
|
| 817 |
+
}
|
| 818 |
+
},
|
| 819 |
+
"node_modules/fs.realpath": {
|
| 820 |
+
"version": "1.0.0",
|
| 821 |
+
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
| 822 |
+
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
|
| 823 |
+
"license": "ISC"
|
| 824 |
+
},
|
| 825 |
+
"node_modules/fsevents": {
|
| 826 |
+
"version": "2.3.2",
|
| 827 |
+
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
| 828 |
+
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
| 829 |
+
"hasInstallScript": true,
|
| 830 |
+
"license": "MIT",
|
| 831 |
+
"optional": true,
|
| 832 |
+
"os": [
|
| 833 |
+
"darwin"
|
| 834 |
+
],
|
| 835 |
+
"engines": {
|
| 836 |
+
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
| 837 |
+
}
|
| 838 |
+
},
|
| 839 |
+
"node_modules/function-bind": {
|
| 840 |
+
"version": "1.1.2",
|
| 841 |
+
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
| 842 |
+
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
| 843 |
+
"license": "MIT",
|
| 844 |
+
"funding": {
|
| 845 |
+
"url": "https://github.com/sponsors/ljharb"
|
| 846 |
+
}
|
| 847 |
+
},
|
| 848 |
+
"node_modules/generative-bayesian-network": {
|
| 849 |
+
"version": "2.1.82",
|
| 850 |
+
"resolved": "https://registry.npmjs.org/generative-bayesian-network/-/generative-bayesian-network-2.1.82.tgz",
|
| 851 |
+
"integrity": "sha512-DH4NrmQheoMaJErdVv2IzaqkbOYSDQZmiZTV6UPDJYRDK2EyPpIQ88XRcYdPeFrUjS1N0Jj25H3HUywoJ1dbow==",
|
| 852 |
+
"license": "Apache-2.0",
|
| 853 |
+
"dependencies": {
|
| 854 |
+
"adm-zip": "^0.5.9",
|
| 855 |
+
"tslib": "^2.4.0"
|
| 856 |
+
}
|
| 857 |
+
},
|
| 858 |
+
"node_modules/get-intrinsic": {
|
| 859 |
+
"version": "1.3.0",
|
| 860 |
+
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
| 861 |
+
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
| 862 |
+
"license": "MIT",
|
| 863 |
+
"dependencies": {
|
| 864 |
+
"call-bind-apply-helpers": "^1.0.2",
|
| 865 |
+
"es-define-property": "^1.0.1",
|
| 866 |
+
"es-errors": "^1.3.0",
|
| 867 |
+
"es-object-atoms": "^1.1.1",
|
| 868 |
+
"function-bind": "^1.1.2",
|
| 869 |
+
"get-proto": "^1.0.1",
|
| 870 |
+
"gopd": "^1.2.0",
|
| 871 |
+
"has-symbols": "^1.1.0",
|
| 872 |
+
"hasown": "^2.0.2",
|
| 873 |
+
"math-intrinsics": "^1.1.0"
|
| 874 |
+
},
|
| 875 |
+
"engines": {
|
| 876 |
+
"node": ">= 0.4"
|
| 877 |
+
},
|
| 878 |
+
"funding": {
|
| 879 |
+
"url": "https://github.com/sponsors/ljharb"
|
| 880 |
+
}
|
| 881 |
+
},
|
| 882 |
+
"node_modules/get-proto": {
|
| 883 |
+
"version": "1.0.1",
|
| 884 |
+
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
| 885 |
+
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
| 886 |
+
"license": "MIT",
|
| 887 |
+
"dependencies": {
|
| 888 |
+
"dunder-proto": "^1.0.1",
|
| 889 |
+
"es-object-atoms": "^1.0.0"
|
| 890 |
+
},
|
| 891 |
+
"engines": {
|
| 892 |
+
"node": ">= 0.4"
|
| 893 |
+
}
|
| 894 |
+
},
|
| 895 |
+
"node_modules/github-from-package": {
|
| 896 |
+
"version": "0.0.0",
|
| 897 |
+
"resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
|
| 898 |
+
"integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==",
|
| 899 |
+
"license": "MIT"
|
| 900 |
+
},
|
| 901 |
+
"node_modules/glob": {
|
| 902 |
+
"version": "13.0.6",
|
| 903 |
+
"resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz",
|
| 904 |
+
"integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==",
|
| 905 |
+
"license": "BlueOak-1.0.0",
|
| 906 |
+
"dependencies": {
|
| 907 |
+
"minimatch": "^10.2.2",
|
| 908 |
+
"minipass": "^7.1.3",
|
| 909 |
+
"path-scurry": "^2.0.2"
|
| 910 |
+
},
|
| 911 |
+
"engines": {
|
| 912 |
+
"node": "18 || 20 || >=22"
|
| 913 |
+
},
|
| 914 |
+
"funding": {
|
| 915 |
+
"url": "https://github.com/sponsors/isaacs"
|
| 916 |
+
}
|
| 917 |
+
},
|
| 918 |
+
"node_modules/gopd": {
|
| 919 |
+
"version": "1.2.0",
|
| 920 |
+
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
| 921 |
+
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
| 922 |
+
"license": "MIT",
|
| 923 |
+
"engines": {
|
| 924 |
+
"node": ">= 0.4"
|
| 925 |
+
},
|
| 926 |
+
"funding": {
|
| 927 |
+
"url": "https://github.com/sponsors/ljharb"
|
| 928 |
+
}
|
| 929 |
+
},
|
| 930 |
+
"node_modules/graceful-fs": {
|
| 931 |
+
"version": "4.2.11",
|
| 932 |
+
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
| 933 |
+
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
|
| 934 |
+
"license": "ISC"
|
| 935 |
+
},
|
| 936 |
+
"node_modules/has-symbols": {
|
| 937 |
+
"version": "1.1.0",
|
| 938 |
+
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
| 939 |
+
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
| 940 |
+
"license": "MIT",
|
| 941 |
+
"engines": {
|
| 942 |
+
"node": ">= 0.4"
|
| 943 |
+
},
|
| 944 |
+
"funding": {
|
| 945 |
+
"url": "https://github.com/sponsors/ljharb"
|
| 946 |
+
}
|
| 947 |
+
},
|
| 948 |
+
"node_modules/hasown": {
|
| 949 |
+
"version": "2.0.2",
|
| 950 |
+
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
| 951 |
+
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
| 952 |
+
"license": "MIT",
|
| 953 |
+
"dependencies": {
|
| 954 |
+
"function-bind": "^1.1.2"
|
| 955 |
+
},
|
| 956 |
+
"engines": {
|
| 957 |
+
"node": ">= 0.4"
|
| 958 |
+
}
|
| 959 |
+
},
|
| 960 |
+
"node_modules/header-generator": {
|
| 961 |
+
"version": "2.1.82",
|
| 962 |
+
"resolved": "https://registry.npmjs.org/header-generator/-/header-generator-2.1.82.tgz",
|
| 963 |
+
"integrity": "sha512-4NjPB0+bAKjPoponSmTOkK58IEF2W22sOJA5O48k/MxbCZgOm+jrU4WVR53Z2I6xFgIPkVrQmKtt1LAbWtfqXw==",
|
| 964 |
+
"license": "Apache-2.0",
|
| 965 |
+
"dependencies": {
|
| 966 |
+
"browserslist": "^4.21.1",
|
| 967 |
+
"generative-bayesian-network": "^2.1.82",
|
| 968 |
+
"ow": "^0.28.1",
|
| 969 |
+
"tslib": "^2.4.0"
|
| 970 |
+
},
|
| 971 |
+
"engines": {
|
| 972 |
+
"node": ">=16.0.0"
|
| 973 |
+
}
|
| 974 |
+
},
|
| 975 |
+
"node_modules/http-errors": {
|
| 976 |
+
"version": "2.0.1",
|
| 977 |
+
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
|
| 978 |
+
"integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
|
| 979 |
+
"license": "MIT",
|
| 980 |
+
"dependencies": {
|
| 981 |
+
"depd": "~2.0.0",
|
| 982 |
+
"inherits": "~2.0.4",
|
| 983 |
+
"setprototypeof": "~1.2.0",
|
| 984 |
+
"statuses": "~2.0.2",
|
| 985 |
+
"toidentifier": "~1.0.1"
|
| 986 |
+
},
|
| 987 |
+
"engines": {
|
| 988 |
+
"node": ">= 0.8"
|
| 989 |
+
},
|
| 990 |
+
"funding": {
|
| 991 |
+
"type": "opencollective",
|
| 992 |
+
"url": "https://opencollective.com/express"
|
| 993 |
+
}
|
| 994 |
+
},
|
| 995 |
+
"node_modules/iconv-lite": {
|
| 996 |
+
"version": "0.4.24",
|
| 997 |
+
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
| 998 |
+
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
|
| 999 |
+
"license": "MIT",
|
| 1000 |
+
"dependencies": {
|
| 1001 |
+
"safer-buffer": ">= 2.1.2 < 3"
|
| 1002 |
+
},
|
| 1003 |
+
"engines": {
|
| 1004 |
+
"node": ">=0.10.0"
|
| 1005 |
+
}
|
| 1006 |
+
},
|
| 1007 |
+
"node_modules/ieee754": {
|
| 1008 |
+
"version": "1.2.1",
|
| 1009 |
+
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
| 1010 |
+
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
|
| 1011 |
+
"funding": [
|
| 1012 |
+
{
|
| 1013 |
+
"type": "github",
|
| 1014 |
+
"url": "https://github.com/sponsors/feross"
|
| 1015 |
+
},
|
| 1016 |
+
{
|
| 1017 |
+
"type": "patreon",
|
| 1018 |
+
"url": "https://www.patreon.com/feross"
|
| 1019 |
+
},
|
| 1020 |
+
{
|
| 1021 |
+
"type": "consulting",
|
| 1022 |
+
"url": "https://feross.org/support"
|
| 1023 |
+
}
|
| 1024 |
+
],
|
| 1025 |
+
"license": "BSD-3-Clause"
|
| 1026 |
+
},
|
| 1027 |
+
"node_modules/impit": {
|
| 1028 |
+
"version": "0.7.6",
|
| 1029 |
+
"resolved": "https://registry.npmjs.org/impit/-/impit-0.7.6.tgz",
|
| 1030 |
+
"integrity": "sha512-AkS6Gv63+E6GMvBrcRhMmOREKpq5oJ0J5m3xwfkHiEs97UIsbpEqFmW3sFw/sdyOTDGRF5q4EjaLxtb922Ta8g==",
|
| 1031 |
+
"license": "Apache-2.0",
|
| 1032 |
+
"engines": {
|
| 1033 |
+
"node": ">= 20"
|
| 1034 |
+
},
|
| 1035 |
+
"optionalDependencies": {
|
| 1036 |
+
"impit-darwin-arm64": "0.7.6",
|
| 1037 |
+
"impit-darwin-x64": "0.7.6",
|
| 1038 |
+
"impit-linux-arm64-gnu": "0.7.6",
|
| 1039 |
+
"impit-linux-arm64-musl": "0.7.6",
|
| 1040 |
+
"impit-linux-x64-gnu": "0.7.6",
|
| 1041 |
+
"impit-linux-x64-musl": "0.7.6",
|
| 1042 |
+
"impit-win32-arm64-msvc": "0.7.6",
|
| 1043 |
+
"impit-win32-x64-msvc": "0.7.6"
|
| 1044 |
+
}
|
| 1045 |
+
},
|
| 1046 |
+
"node_modules/impit-darwin-arm64": {
|
| 1047 |
+
"version": "0.7.6",
|
| 1048 |
+
"resolved": "https://registry.npmjs.org/impit-darwin-arm64/-/impit-darwin-arm64-0.7.6.tgz",
|
| 1049 |
+
"integrity": "sha512-M7NQXkttyzqilWfzVkNCp7hApT69m0etyJkVpHze4bR5z1kJnHhdsb8BSdDv2dzvZL4u1JyqZNxq+qoMn84eUw==",
|
| 1050 |
+
"cpu": [
|
| 1051 |
+
"arm64"
|
| 1052 |
+
],
|
| 1053 |
+
"license": "Apache-2.0",
|
| 1054 |
+
"optional": true,
|
| 1055 |
+
"os": [
|
| 1056 |
+
"darwin"
|
| 1057 |
+
],
|
| 1058 |
+
"engines": {
|
| 1059 |
+
"node": ">= 10"
|
| 1060 |
+
}
|
| 1061 |
+
},
|
| 1062 |
+
"node_modules/impit-darwin-x64": {
|
| 1063 |
+
"version": "0.7.6",
|
| 1064 |
+
"resolved": "https://registry.npmjs.org/impit-darwin-x64/-/impit-darwin-x64-0.7.6.tgz",
|
| 1065 |
+
"integrity": "sha512-kikTesWirAwJp9JPxzGLoGVc+heBlEabWS5AhTkQedACU153vmuL90OBQikVr3ul2N0LPImvnuB+51wV0zDE6g==",
|
| 1066 |
+
"cpu": [
|
| 1067 |
+
"x64"
|
| 1068 |
+
],
|
| 1069 |
+
"license": "Apache-2.0",
|
| 1070 |
+
"optional": true,
|
| 1071 |
+
"os": [
|
| 1072 |
+
"darwin"
|
| 1073 |
+
],
|
| 1074 |
+
"engines": {
|
| 1075 |
+
"node": ">= 10"
|
| 1076 |
+
}
|
| 1077 |
+
},
|
| 1078 |
+
"node_modules/impit-linux-arm64-gnu": {
|
| 1079 |
+
"version": "0.7.6",
|
| 1080 |
+
"resolved": "https://registry.npmjs.org/impit-linux-arm64-gnu/-/impit-linux-arm64-gnu-0.7.6.tgz",
|
| 1081 |
+
"integrity": "sha512-H6GHjVr/0lG9VEJr6IHF8YLq+YkSIOF4k7Dfue2ygzUAj1+jZ5ZwnouhG/XrZHYW6EWsZmEAjjRfWE56Q0wDRQ==",
|
| 1082 |
+
"cpu": [
|
| 1083 |
+
"arm64"
|
| 1084 |
+
],
|
| 1085 |
+
"license": "Apache-2.0",
|
| 1086 |
+
"optional": true,
|
| 1087 |
+
"os": [
|
| 1088 |
+
"linux"
|
| 1089 |
+
],
|
| 1090 |
+
"engines": {
|
| 1091 |
+
"node": ">= 10"
|
| 1092 |
+
}
|
| 1093 |
+
},
|
| 1094 |
+
"node_modules/impit-linux-arm64-musl": {
|
| 1095 |
+
"version": "0.7.6",
|
| 1096 |
+
"resolved": "https://registry.npmjs.org/impit-linux-arm64-musl/-/impit-linux-arm64-musl-0.7.6.tgz",
|
| 1097 |
+
"integrity": "sha512-1sCB/UBVXLZTpGJsXRdNNSvhN9xmmQcYLMWAAB4Itb7w684RHX1pLoCb6ichv7bfAf6tgaupcFIFZNBp3ghmQA==",
|
| 1098 |
+
"cpu": [
|
| 1099 |
+
"arm64"
|
| 1100 |
+
],
|
| 1101 |
+
"license": "Apache-2.0",
|
| 1102 |
+
"optional": true,
|
| 1103 |
+
"os": [
|
| 1104 |
+
"linux"
|
| 1105 |
+
],
|
| 1106 |
+
"engines": {
|
| 1107 |
+
"node": ">= 10"
|
| 1108 |
+
}
|
| 1109 |
+
},
|
| 1110 |
+
"node_modules/impit-linux-x64-gnu": {
|
| 1111 |
+
"version": "0.7.6",
|
| 1112 |
+
"resolved": "https://registry.npmjs.org/impit-linux-x64-gnu/-/impit-linux-x64-gnu-0.7.6.tgz",
|
| 1113 |
+
"integrity": "sha512-yYhlRnZ4fhKt8kuGe0JK2WSHc8TkR6BEH0wn+guevmu8EOn9Xu43OuRvkeOyVAkRqvFnlZtMyySUo/GuSLz9Gw==",
|
| 1114 |
+
"cpu": [
|
| 1115 |
+
"x64"
|
| 1116 |
+
],
|
| 1117 |
+
"license": "Apache-2.0",
|
| 1118 |
+
"optional": true,
|
| 1119 |
+
"os": [
|
| 1120 |
+
"linux"
|
| 1121 |
+
],
|
| 1122 |
+
"engines": {
|
| 1123 |
+
"node": ">= 10"
|
| 1124 |
+
}
|
| 1125 |
+
},
|
| 1126 |
+
"node_modules/impit-linux-x64-musl": {
|
| 1127 |
+
"version": "0.7.6",
|
| 1128 |
+
"resolved": "https://registry.npmjs.org/impit-linux-x64-musl/-/impit-linux-x64-musl-0.7.6.tgz",
|
| 1129 |
+
"integrity": "sha512-sdGWyu+PCLmaOXy7Mzo4WP61ZLl5qpZ1L+VeXW+Ycazgu0e7ox0NZLdiLRunIrEzD+h0S+e4CyzNwaiP3yIolg==",
|
| 1130 |
+
"cpu": [
|
| 1131 |
+
"x64"
|
| 1132 |
+
],
|
| 1133 |
+
"license": "Apache-2.0",
|
| 1134 |
+
"optional": true,
|
| 1135 |
+
"os": [
|
| 1136 |
+
"linux"
|
| 1137 |
+
],
|
| 1138 |
+
"engines": {
|
| 1139 |
+
"node": ">= 10"
|
| 1140 |
+
}
|
| 1141 |
+
},
|
| 1142 |
+
"node_modules/impit-win32-arm64-msvc": {
|
| 1143 |
+
"version": "0.7.6",
|
| 1144 |
+
"resolved": "https://registry.npmjs.org/impit-win32-arm64-msvc/-/impit-win32-arm64-msvc-0.7.6.tgz",
|
| 1145 |
+
"integrity": "sha512-sM5deBqo0EuXg5GACBUMKEua9jIau/i34bwNlfrf/Amnw1n0GB4/RkuUh+sKiUcbNAntrRq+YhCq8qDP8IW19w==",
|
| 1146 |
+
"cpu": [
|
| 1147 |
+
"arm64"
|
| 1148 |
+
],
|
| 1149 |
+
"license": "Apache-2.0",
|
| 1150 |
+
"optional": true,
|
| 1151 |
+
"os": [
|
| 1152 |
+
"win32"
|
| 1153 |
+
],
|
| 1154 |
+
"engines": {
|
| 1155 |
+
"node": ">= 10"
|
| 1156 |
+
}
|
| 1157 |
+
},
|
| 1158 |
+
"node_modules/impit-win32-x64-msvc": {
|
| 1159 |
+
"version": "0.7.6",
|
| 1160 |
+
"resolved": "https://registry.npmjs.org/impit-win32-x64-msvc/-/impit-win32-x64-msvc-0.7.6.tgz",
|
| 1161 |
+
"integrity": "sha512-ry63ADGLCB/PU/vNB1VioRt2V+klDJ34frJUXUZBEv1kA96HEAg9AxUk+604o+UHS3ttGH2rkLmrbwHOdAct5Q==",
|
| 1162 |
+
"cpu": [
|
| 1163 |
+
"x64"
|
| 1164 |
+
],
|
| 1165 |
+
"license": "Apache-2.0",
|
| 1166 |
+
"optional": true,
|
| 1167 |
+
"os": [
|
| 1168 |
+
"win32"
|
| 1169 |
+
],
|
| 1170 |
+
"engines": {
|
| 1171 |
+
"node": ">= 10"
|
| 1172 |
+
}
|
| 1173 |
+
},
|
| 1174 |
+
"node_modules/inflight": {
|
| 1175 |
+
"version": "1.0.6",
|
| 1176 |
+
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
| 1177 |
+
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
|
| 1178 |
+
"deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
|
| 1179 |
+
"license": "ISC",
|
| 1180 |
+
"dependencies": {
|
| 1181 |
+
"once": "^1.3.0",
|
| 1182 |
+
"wrappy": "1"
|
| 1183 |
+
}
|
| 1184 |
+
},
|
| 1185 |
+
"node_modules/inherits": {
|
| 1186 |
+
"version": "2.0.4",
|
| 1187 |
+
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
| 1188 |
+
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
| 1189 |
+
"license": "ISC"
|
| 1190 |
+
},
|
| 1191 |
+
"node_modules/ini": {
|
| 1192 |
+
"version": "1.3.8",
|
| 1193 |
+
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
|
| 1194 |
+
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
|
| 1195 |
+
"license": "ISC"
|
| 1196 |
+
},
|
| 1197 |
+
"node_modules/ipaddr.js": {
|
| 1198 |
+
"version": "1.9.1",
|
| 1199 |
+
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
| 1200 |
+
"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
|
| 1201 |
+
"license": "MIT",
|
| 1202 |
+
"engines": {
|
| 1203 |
+
"node": ">= 0.10"
|
| 1204 |
+
}
|
| 1205 |
+
},
|
| 1206 |
+
"node_modules/is-buffer": {
|
| 1207 |
+
"version": "1.1.6",
|
| 1208 |
+
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
|
| 1209 |
+
"integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==",
|
| 1210 |
+
"license": "MIT"
|
| 1211 |
+
},
|
| 1212 |
+
"node_modules/is-extendable": {
|
| 1213 |
+
"version": "0.1.1",
|
| 1214 |
+
"resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
|
| 1215 |
+
"integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==",
|
| 1216 |
+
"license": "MIT",
|
| 1217 |
+
"engines": {
|
| 1218 |
+
"node": ">=0.10.0"
|
| 1219 |
+
}
|
| 1220 |
+
},
|
| 1221 |
+
"node_modules/is-obj": {
|
| 1222 |
+
"version": "2.0.0",
|
| 1223 |
+
"resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz",
|
| 1224 |
+
"integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==",
|
| 1225 |
+
"license": "MIT",
|
| 1226 |
+
"engines": {
|
| 1227 |
+
"node": ">=8"
|
| 1228 |
+
}
|
| 1229 |
+
},
|
| 1230 |
+
"node_modules/is-plain-object": {
|
| 1231 |
+
"version": "2.0.4",
|
| 1232 |
+
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
|
| 1233 |
+
"integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==",
|
| 1234 |
+
"license": "MIT",
|
| 1235 |
+
"dependencies": {
|
| 1236 |
+
"isobject": "^3.0.1"
|
| 1237 |
+
},
|
| 1238 |
+
"engines": {
|
| 1239 |
+
"node": ">=0.10.0"
|
| 1240 |
+
}
|
| 1241 |
+
},
|
| 1242 |
+
"node_modules/is-standalone-pwa": {
|
| 1243 |
+
"version": "0.1.1",
|
| 1244 |
+
"resolved": "https://registry.npmjs.org/is-standalone-pwa/-/is-standalone-pwa-0.1.1.tgz",
|
| 1245 |
+
"integrity": "sha512-9Cbovsa52vNQCjdXOzeQq5CnCbAcRk05aU62K20WO372NrTv0NxibLFCK6lQ4/iZEFdEA3p3t2VNOn8AJ53F5g==",
|
| 1246 |
+
"funding": [
|
| 1247 |
+
{
|
| 1248 |
+
"type": "github",
|
| 1249 |
+
"url": "https://github.com/sponsors/faisalman"
|
| 1250 |
+
},
|
| 1251 |
+
{
|
| 1252 |
+
"type": "opencollective",
|
| 1253 |
+
"url": "https://opencollective.com/ua-parser-js"
|
| 1254 |
+
},
|
| 1255 |
+
{
|
| 1256 |
+
"type": "paypal",
|
| 1257 |
+
"url": "https://paypal.me/faisalman"
|
| 1258 |
+
}
|
| 1259 |
+
],
|
| 1260 |
+
"license": "MIT"
|
| 1261 |
+
},
|
| 1262 |
+
"node_modules/isobject": {
|
| 1263 |
+
"version": "3.0.1",
|
| 1264 |
+
"resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
|
| 1265 |
+
"integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==",
|
| 1266 |
+
"license": "MIT",
|
| 1267 |
+
"engines": {
|
| 1268 |
+
"node": ">=0.10.0"
|
| 1269 |
+
}
|
| 1270 |
+
},
|
| 1271 |
+
"node_modules/jsonfile": {
|
| 1272 |
+
"version": "6.2.0",
|
| 1273 |
+
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz",
|
| 1274 |
+
"integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==",
|
| 1275 |
+
"license": "MIT",
|
| 1276 |
+
"dependencies": {
|
| 1277 |
+
"universalify": "^2.0.0"
|
| 1278 |
+
},
|
| 1279 |
+
"optionalDependencies": {
|
| 1280 |
+
"graceful-fs": "^4.1.6"
|
| 1281 |
+
}
|
| 1282 |
+
},
|
| 1283 |
+
"node_modules/kind-of": {
|
| 1284 |
+
"version": "3.2.2",
|
| 1285 |
+
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
|
| 1286 |
+
"integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==",
|
| 1287 |
+
"license": "MIT",
|
| 1288 |
+
"dependencies": {
|
| 1289 |
+
"is-buffer": "^1.1.5"
|
| 1290 |
+
},
|
| 1291 |
+
"engines": {
|
| 1292 |
+
"node": ">=0.10.0"
|
| 1293 |
+
}
|
| 1294 |
+
},
|
| 1295 |
+
"node_modules/language-subtag-registry": {
|
| 1296 |
+
"version": "0.3.23",
|
| 1297 |
+
"resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz",
|
| 1298 |
+
"integrity": "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==",
|
| 1299 |
+
"license": "CC0-1.0"
|
| 1300 |
+
},
|
| 1301 |
+
"node_modules/language-tags": {
|
| 1302 |
+
"version": "2.1.0",
|
| 1303 |
+
"resolved": "https://registry.npmjs.org/language-tags/-/language-tags-2.1.0.tgz",
|
| 1304 |
+
"integrity": "sha512-D4CgpyCt+61f6z2jHjJS1OmZPviAWM57iJ9OKdFFWSNgS7Udj9QVWqyGs/cveVNF57XpZmhSvMdVIV5mjLA7Vg==",
|
| 1305 |
+
"license": "MIT",
|
| 1306 |
+
"dependencies": {
|
| 1307 |
+
"language-subtag-registry": "^0.3.20"
|
| 1308 |
+
},
|
| 1309 |
+
"engines": {
|
| 1310 |
+
"node": ">=22"
|
| 1311 |
+
}
|
| 1312 |
+
},
|
| 1313 |
+
"node_modules/lazy-cache": {
|
| 1314 |
+
"version": "1.0.4",
|
| 1315 |
+
"resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz",
|
| 1316 |
+
"integrity": "sha512-RE2g0b5VGZsOCFOCgP7omTRYFqydmZkBwl5oNnQ1lDYC57uyO9KqNnNVxT7COSHTxrRCWVcAVOcbjk+tvh/rgQ==",
|
| 1317 |
+
"license": "MIT",
|
| 1318 |
+
"engines": {
|
| 1319 |
+
"node": ">=0.10.0"
|
| 1320 |
+
}
|
| 1321 |
+
},
|
| 1322 |
+
"node_modules/lodash.isequal": {
|
| 1323 |
+
"version": "4.5.0",
|
| 1324 |
+
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
|
| 1325 |
+
"integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==",
|
| 1326 |
+
"deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.",
|
| 1327 |
+
"license": "MIT"
|
| 1328 |
+
},
|
| 1329 |
+
"node_modules/math-intrinsics": {
|
| 1330 |
+
"version": "1.1.0",
|
| 1331 |
+
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
| 1332 |
+
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
| 1333 |
+
"license": "MIT",
|
| 1334 |
+
"engines": {
|
| 1335 |
+
"node": ">= 0.4"
|
| 1336 |
+
}
|
| 1337 |
+
},
|
| 1338 |
+
"node_modules/maxmind": {
|
| 1339 |
+
"version": "5.0.6",
|
| 1340 |
+
"resolved": "https://registry.npmjs.org/maxmind/-/maxmind-5.0.6.tgz",
|
| 1341 |
+
"integrity": "sha512-5bvd/u+kIaTqaGM+xkXjatzQw1dQfSmlLggr2W1EKMyMxSgx2woZyusLpNpZ4DdPmL+1bbJWeo4LXsi6bC0Iew==",
|
| 1342 |
+
"license": "MIT",
|
| 1343 |
+
"dependencies": {
|
| 1344 |
+
"mmdb-lib": "3.0.2",
|
| 1345 |
+
"tiny-lru": "13.0.0"
|
| 1346 |
+
},
|
| 1347 |
+
"engines": {
|
| 1348 |
+
"node": ">=12",
|
| 1349 |
+
"npm": ">=6"
|
| 1350 |
+
}
|
| 1351 |
+
},
|
| 1352 |
+
"node_modules/media-typer": {
|
| 1353 |
+
"version": "0.3.0",
|
| 1354 |
+
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
| 1355 |
+
"integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
|
| 1356 |
+
"license": "MIT",
|
| 1357 |
+
"engines": {
|
| 1358 |
+
"node": ">= 0.6"
|
| 1359 |
+
}
|
| 1360 |
+
},
|
| 1361 |
+
"node_modules/merge-deep": {
|
| 1362 |
+
"version": "3.0.3",
|
| 1363 |
+
"resolved": "https://registry.npmjs.org/merge-deep/-/merge-deep-3.0.3.tgz",
|
| 1364 |
+
"integrity": "sha512-qtmzAS6t6grwEkNrunqTBdn0qKwFgNWvlxUbAV8es9M7Ot1EbyApytCnvE0jALPa46ZpKDUo527kKiaWplmlFA==",
|
| 1365 |
+
"license": "MIT",
|
| 1366 |
+
"dependencies": {
|
| 1367 |
+
"arr-union": "^3.1.0",
|
| 1368 |
+
"clone-deep": "^0.2.4",
|
| 1369 |
+
"kind-of": "^3.0.2"
|
| 1370 |
+
},
|
| 1371 |
+
"engines": {
|
| 1372 |
+
"node": ">=0.10.0"
|
| 1373 |
+
}
|
| 1374 |
+
},
|
| 1375 |
+
"node_modules/merge-descriptors": {
|
| 1376 |
+
"version": "1.0.3",
|
| 1377 |
+
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
|
| 1378 |
+
"integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
|
| 1379 |
+
"license": "MIT",
|
| 1380 |
+
"funding": {
|
| 1381 |
+
"url": "https://github.com/sponsors/sindresorhus"
|
| 1382 |
+
}
|
| 1383 |
+
},
|
| 1384 |
+
"node_modules/methods": {
|
| 1385 |
+
"version": "1.1.2",
|
| 1386 |
+
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
|
| 1387 |
+
"integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
|
| 1388 |
+
"license": "MIT",
|
| 1389 |
+
"engines": {
|
| 1390 |
+
"node": ">= 0.6"
|
| 1391 |
+
}
|
| 1392 |
+
},
|
| 1393 |
+
"node_modules/mime": {
|
| 1394 |
+
"version": "1.6.0",
|
| 1395 |
+
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
|
| 1396 |
+
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
|
| 1397 |
+
"license": "MIT",
|
| 1398 |
+
"bin": {
|
| 1399 |
+
"mime": "cli.js"
|
| 1400 |
+
},
|
| 1401 |
+
"engines": {
|
| 1402 |
+
"node": ">=4"
|
| 1403 |
+
}
|
| 1404 |
+
},
|
| 1405 |
+
"node_modules/mime-db": {
|
| 1406 |
+
"version": "1.52.0",
|
| 1407 |
+
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
| 1408 |
+
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
| 1409 |
+
"license": "MIT",
|
| 1410 |
+
"engines": {
|
| 1411 |
+
"node": ">= 0.6"
|
| 1412 |
+
}
|
| 1413 |
+
},
|
| 1414 |
+
"node_modules/mime-types": {
|
| 1415 |
+
"version": "2.1.35",
|
| 1416 |
+
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
| 1417 |
+
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
| 1418 |
+
"license": "MIT",
|
| 1419 |
+
"dependencies": {
|
| 1420 |
+
"mime-db": "1.52.0"
|
| 1421 |
+
},
|
| 1422 |
+
"engines": {
|
| 1423 |
+
"node": ">= 0.6"
|
| 1424 |
+
}
|
| 1425 |
+
},
|
| 1426 |
+
"node_modules/mimic-response": {
|
| 1427 |
+
"version": "3.1.0",
|
| 1428 |
+
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
|
| 1429 |
+
"integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
|
| 1430 |
+
"license": "MIT",
|
| 1431 |
+
"engines": {
|
| 1432 |
+
"node": ">=10"
|
| 1433 |
+
},
|
| 1434 |
+
"funding": {
|
| 1435 |
+
"url": "https://github.com/sponsors/sindresorhus"
|
| 1436 |
+
}
|
| 1437 |
+
},
|
| 1438 |
+
"node_modules/minimatch": {
|
| 1439 |
+
"version": "10.2.5",
|
| 1440 |
+
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz",
|
| 1441 |
+
"integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==",
|
| 1442 |
+
"license": "BlueOak-1.0.0",
|
| 1443 |
+
"dependencies": {
|
| 1444 |
+
"brace-expansion": "^5.0.5"
|
| 1445 |
+
},
|
| 1446 |
+
"engines": {
|
| 1447 |
+
"node": "18 || 20 || >=22"
|
| 1448 |
+
},
|
| 1449 |
+
"funding": {
|
| 1450 |
+
"url": "https://github.com/sponsors/isaacs"
|
| 1451 |
+
}
|
| 1452 |
+
},
|
| 1453 |
+
"node_modules/minimist": {
|
| 1454 |
+
"version": "1.2.8",
|
| 1455 |
+
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
|
| 1456 |
+
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
|
| 1457 |
+
"license": "MIT",
|
| 1458 |
+
"funding": {
|
| 1459 |
+
"url": "https://github.com/sponsors/ljharb"
|
| 1460 |
+
}
|
| 1461 |
+
},
|
| 1462 |
+
"node_modules/minipass": {
|
| 1463 |
+
"version": "7.1.3",
|
| 1464 |
+
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz",
|
| 1465 |
+
"integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==",
|
| 1466 |
+
"license": "BlueOak-1.0.0",
|
| 1467 |
+
"engines": {
|
| 1468 |
+
"node": ">=16 || 14 >=14.17"
|
| 1469 |
+
}
|
| 1470 |
+
},
|
| 1471 |
+
"node_modules/mixin-object": {
|
| 1472 |
+
"version": "2.0.1",
|
| 1473 |
+
"resolved": "https://registry.npmjs.org/mixin-object/-/mixin-object-2.0.1.tgz",
|
| 1474 |
+
"integrity": "sha512-ALGF1Jt9ouehcaXaHhn6t1yGWRqGaHkPFndtFVHfZXOvkIZ/yoGaSi0AHVTafb3ZBGg4dr/bDwnaEKqCXzchMA==",
|
| 1475 |
+
"license": "MIT",
|
| 1476 |
+
"dependencies": {
|
| 1477 |
+
"for-in": "^0.1.3",
|
| 1478 |
+
"is-extendable": "^0.1.1"
|
| 1479 |
+
},
|
| 1480 |
+
"engines": {
|
| 1481 |
+
"node": ">=0.10.0"
|
| 1482 |
+
}
|
| 1483 |
+
},
|
| 1484 |
+
"node_modules/mixin-object/node_modules/for-in": {
|
| 1485 |
+
"version": "0.1.8",
|
| 1486 |
+
"resolved": "https://registry.npmjs.org/for-in/-/for-in-0.1.8.tgz",
|
| 1487 |
+
"integrity": "sha512-F0to7vbBSHP8E3l6dCjxNOLuSFAACIxFy3UehTUlG7svlXi37HHsDkyVcHo0Pq8QwrE+pXvWSVX3ZT1T9wAZ9g==",
|
| 1488 |
+
"license": "MIT",
|
| 1489 |
+
"engines": {
|
| 1490 |
+
"node": ">=0.10.0"
|
| 1491 |
+
}
|
| 1492 |
+
},
|
| 1493 |
+
"node_modules/mkdirp-classic": {
|
| 1494 |
+
"version": "0.5.3",
|
| 1495 |
+
"resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
|
| 1496 |
+
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
|
| 1497 |
+
"license": "MIT"
|
| 1498 |
+
},
|
| 1499 |
+
"node_modules/mmdb-lib": {
|
| 1500 |
+
"version": "3.0.2",
|
| 1501 |
+
"resolved": "https://registry.npmjs.org/mmdb-lib/-/mmdb-lib-3.0.2.tgz",
|
| 1502 |
+
"integrity": "sha512-7e87vk0DdWT647wjcfEtWeMtjm+zVGqNohN/aeIymbUfjHQ2T4Sx5kM+1irVDBSloNC3CkGKxswdMoo8yhqTDg==",
|
| 1503 |
+
"license": "MIT",
|
| 1504 |
+
"engines": {
|
| 1505 |
+
"node": ">=10",
|
| 1506 |
+
"npm": ">=6"
|
| 1507 |
+
}
|
| 1508 |
+
},
|
| 1509 |
+
"node_modules/ms": {
|
| 1510 |
+
"version": "2.0.0",
|
| 1511 |
+
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
| 1512 |
+
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
|
| 1513 |
+
"license": "MIT"
|
| 1514 |
+
},
|
| 1515 |
+
"node_modules/napi-build-utils": {
|
| 1516 |
+
"version": "2.0.0",
|
| 1517 |
+
"resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz",
|
| 1518 |
+
"integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==",
|
| 1519 |
+
"license": "MIT"
|
| 1520 |
+
},
|
| 1521 |
+
"node_modules/negotiator": {
|
| 1522 |
+
"version": "0.6.3",
|
| 1523 |
+
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
|
| 1524 |
+
"integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
|
| 1525 |
+
"license": "MIT",
|
| 1526 |
+
"engines": {
|
| 1527 |
+
"node": ">= 0.6"
|
| 1528 |
+
}
|
| 1529 |
+
},
|
| 1530 |
+
"node_modules/node-abi": {
|
| 1531 |
+
"version": "3.89.0",
|
| 1532 |
+
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.89.0.tgz",
|
| 1533 |
+
"integrity": "sha512-6u9UwL0HlAl21+agMN3YAMXcKByMqwGx+pq+P76vii5f7hTPtKDp08/H9py6DY+cfDw7kQNTGEj/rly3IgbNQA==",
|
| 1534 |
+
"license": "MIT",
|
| 1535 |
+
"dependencies": {
|
| 1536 |
+
"semver": "^7.3.5"
|
| 1537 |
+
},
|
| 1538 |
+
"engines": {
|
| 1539 |
+
"node": ">=10"
|
| 1540 |
+
}
|
| 1541 |
+
},
|
| 1542 |
+
"node_modules/node-releases": {
|
| 1543 |
+
"version": "2.0.37",
|
| 1544 |
+
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.37.tgz",
|
| 1545 |
+
"integrity": "sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg==",
|
| 1546 |
+
"license": "MIT"
|
| 1547 |
+
},
|
| 1548 |
+
"node_modules/object-inspect": {
|
| 1549 |
+
"version": "1.13.4",
|
| 1550 |
+
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
|
| 1551 |
+
"integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
|
| 1552 |
+
"license": "MIT",
|
| 1553 |
+
"engines": {
|
| 1554 |
+
"node": ">= 0.4"
|
| 1555 |
+
},
|
| 1556 |
+
"funding": {
|
| 1557 |
+
"url": "https://github.com/sponsors/ljharb"
|
| 1558 |
+
}
|
| 1559 |
+
},
|
| 1560 |
+
"node_modules/on-finished": {
|
| 1561 |
+
"version": "2.4.1",
|
| 1562 |
+
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
|
| 1563 |
+
"integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
|
| 1564 |
+
"license": "MIT",
|
| 1565 |
+
"dependencies": {
|
| 1566 |
+
"ee-first": "1.1.1"
|
| 1567 |
+
},
|
| 1568 |
+
"engines": {
|
| 1569 |
+
"node": ">= 0.8"
|
| 1570 |
+
}
|
| 1571 |
+
},
|
| 1572 |
+
"node_modules/once": {
|
| 1573 |
+
"version": "1.4.0",
|
| 1574 |
+
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
| 1575 |
+
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
|
| 1576 |
+
"license": "ISC",
|
| 1577 |
+
"dependencies": {
|
| 1578 |
+
"wrappy": "1"
|
| 1579 |
+
}
|
| 1580 |
+
},
|
| 1581 |
+
"node_modules/ow": {
|
| 1582 |
+
"version": "0.28.2",
|
| 1583 |
+
"resolved": "https://registry.npmjs.org/ow/-/ow-0.28.2.tgz",
|
| 1584 |
+
"integrity": "sha512-dD4UpyBh/9m4X2NVjA+73/ZPBRF+uF4zIMFvvQsabMiEK8x41L3rQ8EENOi35kyyoaJwNxEeJcP6Fj1H4U409Q==",
|
| 1585 |
+
"license": "MIT",
|
| 1586 |
+
"dependencies": {
|
| 1587 |
+
"@sindresorhus/is": "^4.2.0",
|
| 1588 |
+
"callsites": "^3.1.0",
|
| 1589 |
+
"dot-prop": "^6.0.1",
|
| 1590 |
+
"lodash.isequal": "^4.5.0",
|
| 1591 |
+
"vali-date": "^1.0.0"
|
| 1592 |
+
},
|
| 1593 |
+
"engines": {
|
| 1594 |
+
"node": ">=12"
|
| 1595 |
+
},
|
| 1596 |
+
"funding": {
|
| 1597 |
+
"url": "https://github.com/sponsors/sindresorhus"
|
| 1598 |
+
}
|
| 1599 |
+
},
|
| 1600 |
+
"node_modules/parseurl": {
|
| 1601 |
+
"version": "1.3.3",
|
| 1602 |
+
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
| 1603 |
+
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
|
| 1604 |
+
"license": "MIT",
|
| 1605 |
+
"engines": {
|
| 1606 |
+
"node": ">= 0.8"
|
| 1607 |
+
}
|
| 1608 |
+
},
|
| 1609 |
+
"node_modules/path-is-absolute": {
|
| 1610 |
+
"version": "1.0.1",
|
| 1611 |
+
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
| 1612 |
+
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
|
| 1613 |
+
"license": "MIT",
|
| 1614 |
+
"engines": {
|
| 1615 |
+
"node": ">=0.10.0"
|
| 1616 |
+
}
|
| 1617 |
+
},
|
| 1618 |
+
"node_modules/path-scurry": {
|
| 1619 |
+
"version": "2.0.2",
|
| 1620 |
+
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz",
|
| 1621 |
+
"integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==",
|
| 1622 |
+
"license": "BlueOak-1.0.0",
|
| 1623 |
+
"dependencies": {
|
| 1624 |
+
"lru-cache": "^11.0.0",
|
| 1625 |
+
"minipass": "^7.1.2"
|
| 1626 |
+
},
|
| 1627 |
+
"engines": {
|
| 1628 |
+
"node": "18 || 20 || >=22"
|
| 1629 |
+
},
|
| 1630 |
+
"funding": {
|
| 1631 |
+
"url": "https://github.com/sponsors/isaacs"
|
| 1632 |
+
}
|
| 1633 |
+
},
|
| 1634 |
+
"node_modules/path-scurry/node_modules/lru-cache": {
|
| 1635 |
+
"version": "11.3.3",
|
| 1636 |
+
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.3.tgz",
|
| 1637 |
+
"integrity": "sha512-JvNw9Y81y33E+BEYPr0U7omo+U9AySnsMsEiXgwT6yqd31VQWTLNQqmT4ou5eqPFUrTfIDFta2wKhB1hyohtAQ==",
|
| 1638 |
+
"license": "BlueOak-1.0.0",
|
| 1639 |
+
"engines": {
|
| 1640 |
+
"node": "20 || >=22"
|
| 1641 |
+
}
|
| 1642 |
+
},
|
| 1643 |
+
"node_modules/path-to-regexp": {
|
| 1644 |
+
"version": "0.1.13",
|
| 1645 |
+
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz",
|
| 1646 |
+
"integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==",
|
| 1647 |
+
"license": "MIT"
|
| 1648 |
+
},
|
| 1649 |
+
"node_modules/picocolors": {
|
| 1650 |
+
"version": "1.1.1",
|
| 1651 |
+
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
| 1652 |
+
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
| 1653 |
+
"license": "ISC"
|
| 1654 |
+
},
|
| 1655 |
+
"node_modules/playwright": {
|
| 1656 |
+
"version": "1.59.1",
|
| 1657 |
+
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.59.1.tgz",
|
| 1658 |
+
"integrity": "sha512-C8oWjPR3F81yljW9o5OxcWzfh6avkVwDD2VYdwIGqTkl+OGFISgypqzfu7dOe4QNLL2aqcWBmI3PMtLIK233lw==",
|
| 1659 |
+
"license": "Apache-2.0",
|
| 1660 |
+
"dependencies": {
|
| 1661 |
+
"playwright-core": "1.59.1"
|
| 1662 |
+
},
|
| 1663 |
+
"bin": {
|
| 1664 |
+
"playwright": "cli.js"
|
| 1665 |
+
},
|
| 1666 |
+
"engines": {
|
| 1667 |
+
"node": ">=18"
|
| 1668 |
+
},
|
| 1669 |
+
"optionalDependencies": {
|
| 1670 |
+
"fsevents": "2.3.2"
|
| 1671 |
+
}
|
| 1672 |
+
},
|
| 1673 |
+
"node_modules/playwright-core": {
|
| 1674 |
+
"version": "1.59.1",
|
| 1675 |
+
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.59.1.tgz",
|
| 1676 |
+
"integrity": "sha512-HBV/RJg81z5BiiZ9yPzIiClYV/QMsDCKUyogwH9p3MCP6IYjUFu/MActgYAvK0oWyV9NlwM3GLBjADyWgydVyg==",
|
| 1677 |
+
"license": "Apache-2.0",
|
| 1678 |
+
"bin": {
|
| 1679 |
+
"playwright-core": "cli.js"
|
| 1680 |
+
},
|
| 1681 |
+
"engines": {
|
| 1682 |
+
"node": ">=18"
|
| 1683 |
+
}
|
| 1684 |
+
},
|
| 1685 |
+
"node_modules/playwright-extra": {
|
| 1686 |
+
"version": "4.3.6",
|
| 1687 |
+
"resolved": "https://registry.npmjs.org/playwright-extra/-/playwright-extra-4.3.6.tgz",
|
| 1688 |
+
"integrity": "sha512-q2rVtcE8V8K3vPVF1zny4pvwZveHLH8KBuVU2MoE3Jw4OKVoBWsHI9CH9zPydovHHOCDxjGN2Vg+2m644q3ijA==",
|
| 1689 |
+
"license": "MIT",
|
| 1690 |
+
"dependencies": {
|
| 1691 |
+
"debug": "^4.3.4"
|
| 1692 |
+
},
|
| 1693 |
+
"engines": {
|
| 1694 |
+
"node": ">=12"
|
| 1695 |
+
},
|
| 1696 |
+
"peerDependencies": {
|
| 1697 |
+
"playwright": "*",
|
| 1698 |
+
"playwright-core": "*"
|
| 1699 |
+
},
|
| 1700 |
+
"peerDependenciesMeta": {
|
| 1701 |
+
"playwright": {
|
| 1702 |
+
"optional": true
|
| 1703 |
+
},
|
| 1704 |
+
"playwright-core": {
|
| 1705 |
+
"optional": true
|
| 1706 |
+
}
|
| 1707 |
+
}
|
| 1708 |
+
},
|
| 1709 |
+
"node_modules/playwright-extra/node_modules/debug": {
|
| 1710 |
+
"version": "4.4.3",
|
| 1711 |
+
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
| 1712 |
+
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
| 1713 |
+
"license": "MIT",
|
| 1714 |
+
"dependencies": {
|
| 1715 |
+
"ms": "^2.1.3"
|
| 1716 |
+
},
|
| 1717 |
+
"engines": {
|
| 1718 |
+
"node": ">=6.0"
|
| 1719 |
+
},
|
| 1720 |
+
"peerDependenciesMeta": {
|
| 1721 |
+
"supports-color": {
|
| 1722 |
+
"optional": true
|
| 1723 |
+
}
|
| 1724 |
+
}
|
| 1725 |
+
},
|
| 1726 |
+
"node_modules/playwright-extra/node_modules/ms": {
|
| 1727 |
+
"version": "2.1.3",
|
| 1728 |
+
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
| 1729 |
+
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
| 1730 |
+
"license": "MIT"
|
| 1731 |
+
},
|
| 1732 |
+
"node_modules/prebuild-install": {
|
| 1733 |
+
"version": "7.1.3",
|
| 1734 |
+
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz",
|
| 1735 |
+
"integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==",
|
| 1736 |
+
"deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.",
|
| 1737 |
+
"license": "MIT",
|
| 1738 |
+
"dependencies": {
|
| 1739 |
+
"detect-libc": "^2.0.0",
|
| 1740 |
+
"expand-template": "^2.0.3",
|
| 1741 |
+
"github-from-package": "0.0.0",
|
| 1742 |
+
"minimist": "^1.2.3",
|
| 1743 |
+
"mkdirp-classic": "^0.5.3",
|
| 1744 |
+
"napi-build-utils": "^2.0.0",
|
| 1745 |
+
"node-abi": "^3.3.0",
|
| 1746 |
+
"pump": "^3.0.0",
|
| 1747 |
+
"rc": "^1.2.7",
|
| 1748 |
+
"simple-get": "^4.0.0",
|
| 1749 |
+
"tar-fs": "^2.0.0",
|
| 1750 |
+
"tunnel-agent": "^0.6.0"
|
| 1751 |
+
},
|
| 1752 |
+
"bin": {
|
| 1753 |
+
"prebuild-install": "bin.js"
|
| 1754 |
+
},
|
| 1755 |
+
"engines": {
|
| 1756 |
+
"node": ">=10"
|
| 1757 |
+
}
|
| 1758 |
+
},
|
| 1759 |
+
"node_modules/progress": {
|
| 1760 |
+
"version": "2.0.3",
|
| 1761 |
+
"resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
|
| 1762 |
+
"integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
|
| 1763 |
+
"license": "MIT",
|
| 1764 |
+
"engines": {
|
| 1765 |
+
"node": ">=0.4.0"
|
| 1766 |
+
}
|
| 1767 |
+
},
|
| 1768 |
+
"node_modules/prom-client": {
|
| 1769 |
+
"version": "15.1.3",
|
| 1770 |
+
"resolved": "https://registry.npmjs.org/prom-client/-/prom-client-15.1.3.tgz",
|
| 1771 |
+
"integrity": "sha512-6ZiOBfCywsD4k1BN9IX0uZhF+tJkV8q8llP64G5Hajs4JOeVLPCwpPVcpXy3BwYiUGgyJzsJJQeOIv7+hDSq8g==",
|
| 1772 |
+
"license": "Apache-2.0",
|
| 1773 |
+
"dependencies": {
|
| 1774 |
+
"@opentelemetry/api": "^1.4.0",
|
| 1775 |
+
"tdigest": "^0.1.1"
|
| 1776 |
+
},
|
| 1777 |
+
"engines": {
|
| 1778 |
+
"node": "^16 || ^18 || >=20"
|
| 1779 |
+
}
|
| 1780 |
+
},
|
| 1781 |
+
"node_modules/proxy-addr": {
|
| 1782 |
+
"version": "2.0.7",
|
| 1783 |
+
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
|
| 1784 |
+
"integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
|
| 1785 |
+
"license": "MIT",
|
| 1786 |
+
"dependencies": {
|
| 1787 |
+
"forwarded": "0.2.0",
|
| 1788 |
+
"ipaddr.js": "1.9.1"
|
| 1789 |
+
},
|
| 1790 |
+
"engines": {
|
| 1791 |
+
"node": ">= 0.10"
|
| 1792 |
+
}
|
| 1793 |
+
},
|
| 1794 |
+
"node_modules/pump": {
|
| 1795 |
+
"version": "3.0.4",
|
| 1796 |
+
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz",
|
| 1797 |
+
"integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==",
|
| 1798 |
+
"license": "MIT",
|
| 1799 |
+
"dependencies": {
|
| 1800 |
+
"end-of-stream": "^1.1.0",
|
| 1801 |
+
"once": "^1.3.1"
|
| 1802 |
+
}
|
| 1803 |
+
},
|
| 1804 |
+
"node_modules/puppeteer-extra-plugin": {
|
| 1805 |
+
"version": "3.2.3",
|
| 1806 |
+
"resolved": "https://registry.npmjs.org/puppeteer-extra-plugin/-/puppeteer-extra-plugin-3.2.3.tgz",
|
| 1807 |
+
"integrity": "sha512-6RNy0e6pH8vaS3akPIKGg28xcryKscczt4wIl0ePciZENGE2yoaQJNd17UiEbdmh5/6WW6dPcfRWT9lxBwCi2Q==",
|
| 1808 |
+
"license": "MIT",
|
| 1809 |
+
"dependencies": {
|
| 1810 |
+
"@types/debug": "^4.1.0",
|
| 1811 |
+
"debug": "^4.1.1",
|
| 1812 |
+
"merge-deep": "^3.0.1"
|
| 1813 |
+
},
|
| 1814 |
+
"engines": {
|
| 1815 |
+
"node": ">=9.11.2"
|
| 1816 |
+
},
|
| 1817 |
+
"peerDependencies": {
|
| 1818 |
+
"playwright-extra": "*",
|
| 1819 |
+
"puppeteer-extra": "*"
|
| 1820 |
+
},
|
| 1821 |
+
"peerDependenciesMeta": {
|
| 1822 |
+
"playwright-extra": {
|
| 1823 |
+
"optional": true
|
| 1824 |
+
},
|
| 1825 |
+
"puppeteer-extra": {
|
| 1826 |
+
"optional": true
|
| 1827 |
+
}
|
| 1828 |
+
}
|
| 1829 |
+
},
|
| 1830 |
+
"node_modules/puppeteer-extra-plugin-stealth": {
|
| 1831 |
+
"version": "2.11.2",
|
| 1832 |
+
"resolved": "https://registry.npmjs.org/puppeteer-extra-plugin-stealth/-/puppeteer-extra-plugin-stealth-2.11.2.tgz",
|
| 1833 |
+
"integrity": "sha512-bUemM5XmTj9i2ZerBzsk2AN5is0wHMNE6K0hXBzBXOzP5m5G3Wl0RHhiqKeHToe/uIH8AoZiGhc1tCkLZQPKTQ==",
|
| 1834 |
+
"license": "MIT",
|
| 1835 |
+
"dependencies": {
|
| 1836 |
+
"debug": "^4.1.1",
|
| 1837 |
+
"puppeteer-extra-plugin": "^3.2.3",
|
| 1838 |
+
"puppeteer-extra-plugin-user-preferences": "^2.4.1"
|
| 1839 |
+
},
|
| 1840 |
+
"engines": {
|
| 1841 |
+
"node": ">=8"
|
| 1842 |
+
},
|
| 1843 |
+
"peerDependencies": {
|
| 1844 |
+
"playwright-extra": "*",
|
| 1845 |
+
"puppeteer-extra": "*"
|
| 1846 |
+
},
|
| 1847 |
+
"peerDependenciesMeta": {
|
| 1848 |
+
"playwright-extra": {
|
| 1849 |
+
"optional": true
|
| 1850 |
+
},
|
| 1851 |
+
"puppeteer-extra": {
|
| 1852 |
+
"optional": true
|
| 1853 |
+
}
|
| 1854 |
+
}
|
| 1855 |
+
},
|
| 1856 |
+
"node_modules/puppeteer-extra-plugin-stealth/node_modules/debug": {
|
| 1857 |
+
"version": "4.4.3",
|
| 1858 |
+
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
| 1859 |
+
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
| 1860 |
+
"license": "MIT",
|
| 1861 |
+
"dependencies": {
|
| 1862 |
+
"ms": "^2.1.3"
|
| 1863 |
+
},
|
| 1864 |
+
"engines": {
|
| 1865 |
+
"node": ">=6.0"
|
| 1866 |
+
},
|
| 1867 |
+
"peerDependenciesMeta": {
|
| 1868 |
+
"supports-color": {
|
| 1869 |
+
"optional": true
|
| 1870 |
+
}
|
| 1871 |
+
}
|
| 1872 |
+
},
|
| 1873 |
+
"node_modules/puppeteer-extra-plugin-stealth/node_modules/ms": {
|
| 1874 |
+
"version": "2.1.3",
|
| 1875 |
+
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
| 1876 |
+
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
| 1877 |
+
"license": "MIT"
|
| 1878 |
+
},
|
| 1879 |
+
"node_modules/puppeteer-extra-plugin-user-data-dir": {
|
| 1880 |
+
"version": "2.4.1",
|
| 1881 |
+
"resolved": "https://registry.npmjs.org/puppeteer-extra-plugin-user-data-dir/-/puppeteer-extra-plugin-user-data-dir-2.4.1.tgz",
|
| 1882 |
+
"integrity": "sha512-kH1GnCcqEDoBXO7epAse4TBPJh9tEpVEK/vkedKfjOVOhZAvLkHGc9swMs5ChrJbRnf8Hdpug6TJlEuimXNQ+g==",
|
| 1883 |
+
"license": "MIT",
|
| 1884 |
+
"dependencies": {
|
| 1885 |
+
"debug": "^4.1.1",
|
| 1886 |
+
"fs-extra": "^10.0.0",
|
| 1887 |
+
"puppeteer-extra-plugin": "^3.2.3",
|
| 1888 |
+
"rimraf": "^3.0.2"
|
| 1889 |
+
},
|
| 1890 |
+
"engines": {
|
| 1891 |
+
"node": ">=8"
|
| 1892 |
+
},
|
| 1893 |
+
"peerDependencies": {
|
| 1894 |
+
"playwright-extra": "*",
|
| 1895 |
+
"puppeteer-extra": "*"
|
| 1896 |
+
},
|
| 1897 |
+
"peerDependenciesMeta": {
|
| 1898 |
+
"playwright-extra": {
|
| 1899 |
+
"optional": true
|
| 1900 |
+
},
|
| 1901 |
+
"puppeteer-extra": {
|
| 1902 |
+
"optional": true
|
| 1903 |
+
}
|
| 1904 |
+
}
|
| 1905 |
+
},
|
| 1906 |
+
"node_modules/puppeteer-extra-plugin-user-data-dir/node_modules/balanced-match": {
|
| 1907 |
+
"version": "1.0.2",
|
| 1908 |
+
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
| 1909 |
+
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
| 1910 |
+
"license": "MIT"
|
| 1911 |
+
},
|
| 1912 |
+
"node_modules/puppeteer-extra-plugin-user-data-dir/node_modules/brace-expansion": {
|
| 1913 |
+
"version": "1.1.14",
|
| 1914 |
+
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
|
| 1915 |
+
"integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==",
|
| 1916 |
+
"license": "MIT",
|
| 1917 |
+
"dependencies": {
|
| 1918 |
+
"balanced-match": "^1.0.0",
|
| 1919 |
+
"concat-map": "0.0.1"
|
| 1920 |
+
}
|
| 1921 |
+
},
|
| 1922 |
+
"node_modules/puppeteer-extra-plugin-user-data-dir/node_modules/debug": {
|
| 1923 |
+
"version": "4.4.3",
|
| 1924 |
+
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
| 1925 |
+
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
| 1926 |
+
"license": "MIT",
|
| 1927 |
+
"dependencies": {
|
| 1928 |
+
"ms": "^2.1.3"
|
| 1929 |
+
},
|
| 1930 |
+
"engines": {
|
| 1931 |
+
"node": ">=6.0"
|
| 1932 |
+
},
|
| 1933 |
+
"peerDependenciesMeta": {
|
| 1934 |
+
"supports-color": {
|
| 1935 |
+
"optional": true
|
| 1936 |
+
}
|
| 1937 |
+
}
|
| 1938 |
+
},
|
| 1939 |
+
"node_modules/puppeteer-extra-plugin-user-data-dir/node_modules/glob": {
|
| 1940 |
+
"version": "7.2.3",
|
| 1941 |
+
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
| 1942 |
+
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
|
| 1943 |
+
"deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
|
| 1944 |
+
"license": "ISC",
|
| 1945 |
+
"dependencies": {
|
| 1946 |
+
"fs.realpath": "^1.0.0",
|
| 1947 |
+
"inflight": "^1.0.4",
|
| 1948 |
+
"inherits": "2",
|
| 1949 |
+
"minimatch": "^3.1.1",
|
| 1950 |
+
"once": "^1.3.0",
|
| 1951 |
+
"path-is-absolute": "^1.0.0"
|
| 1952 |
+
},
|
| 1953 |
+
"engines": {
|
| 1954 |
+
"node": "*"
|
| 1955 |
+
},
|
| 1956 |
+
"funding": {
|
| 1957 |
+
"url": "https://github.com/sponsors/isaacs"
|
| 1958 |
+
}
|
| 1959 |
+
},
|
| 1960 |
+
"node_modules/puppeteer-extra-plugin-user-data-dir/node_modules/minimatch": {
|
| 1961 |
+
"version": "3.1.5",
|
| 1962 |
+
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
|
| 1963 |
+
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
|
| 1964 |
+
"license": "ISC",
|
| 1965 |
+
"dependencies": {
|
| 1966 |
+
"brace-expansion": "^1.1.7"
|
| 1967 |
+
},
|
| 1968 |
+
"engines": {
|
| 1969 |
+
"node": "*"
|
| 1970 |
+
}
|
| 1971 |
+
},
|
| 1972 |
+
"node_modules/puppeteer-extra-plugin-user-data-dir/node_modules/ms": {
|
| 1973 |
+
"version": "2.1.3",
|
| 1974 |
+
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
| 1975 |
+
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
| 1976 |
+
"license": "MIT"
|
| 1977 |
+
},
|
| 1978 |
+
"node_modules/puppeteer-extra-plugin-user-data-dir/node_modules/rimraf": {
|
| 1979 |
+
"version": "3.0.2",
|
| 1980 |
+
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
|
| 1981 |
+
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
|
| 1982 |
+
"deprecated": "Rimraf versions prior to v4 are no longer supported",
|
| 1983 |
+
"license": "ISC",
|
| 1984 |
+
"dependencies": {
|
| 1985 |
+
"glob": "^7.1.3"
|
| 1986 |
+
},
|
| 1987 |
+
"bin": {
|
| 1988 |
+
"rimraf": "bin.js"
|
| 1989 |
+
},
|
| 1990 |
+
"funding": {
|
| 1991 |
+
"url": "https://github.com/sponsors/isaacs"
|
| 1992 |
+
}
|
| 1993 |
+
},
|
| 1994 |
+
"node_modules/puppeteer-extra-plugin-user-preferences": {
|
| 1995 |
+
"version": "2.4.1",
|
| 1996 |
+
"resolved": "https://registry.npmjs.org/puppeteer-extra-plugin-user-preferences/-/puppeteer-extra-plugin-user-preferences-2.4.1.tgz",
|
| 1997 |
+
"integrity": "sha512-i1oAZxRbc1bk8MZufKCruCEC3CCafO9RKMkkodZltI4OqibLFXF3tj6HZ4LZ9C5vCXZjYcDWazgtY69mnmrQ9A==",
|
| 1998 |
+
"license": "MIT",
|
| 1999 |
+
"dependencies": {
|
| 2000 |
+
"debug": "^4.1.1",
|
| 2001 |
+
"deepmerge": "^4.2.2",
|
| 2002 |
+
"puppeteer-extra-plugin": "^3.2.3",
|
| 2003 |
+
"puppeteer-extra-plugin-user-data-dir": "^2.4.1"
|
| 2004 |
+
},
|
| 2005 |
+
"engines": {
|
| 2006 |
+
"node": ">=8"
|
| 2007 |
+
},
|
| 2008 |
+
"peerDependencies": {
|
| 2009 |
+
"playwright-extra": "*",
|
| 2010 |
+
"puppeteer-extra": "*"
|
| 2011 |
+
},
|
| 2012 |
+
"peerDependenciesMeta": {
|
| 2013 |
+
"playwright-extra": {
|
| 2014 |
+
"optional": true
|
| 2015 |
+
},
|
| 2016 |
+
"puppeteer-extra": {
|
| 2017 |
+
"optional": true
|
| 2018 |
+
}
|
| 2019 |
+
}
|
| 2020 |
+
},
|
| 2021 |
+
"node_modules/puppeteer-extra-plugin-user-preferences/node_modules/debug": {
|
| 2022 |
+
"version": "4.4.3",
|
| 2023 |
+
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
| 2024 |
+
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
| 2025 |
+
"license": "MIT",
|
| 2026 |
+
"dependencies": {
|
| 2027 |
+
"ms": "^2.1.3"
|
| 2028 |
+
},
|
| 2029 |
+
"engines": {
|
| 2030 |
+
"node": ">=6.0"
|
| 2031 |
+
},
|
| 2032 |
+
"peerDependenciesMeta": {
|
| 2033 |
+
"supports-color": {
|
| 2034 |
+
"optional": true
|
| 2035 |
+
}
|
| 2036 |
+
}
|
| 2037 |
+
},
|
| 2038 |
+
"node_modules/puppeteer-extra-plugin-user-preferences/node_modules/ms": {
|
| 2039 |
+
"version": "2.1.3",
|
| 2040 |
+
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
| 2041 |
+
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
| 2042 |
+
"license": "MIT"
|
| 2043 |
+
},
|
| 2044 |
+
"node_modules/puppeteer-extra-plugin/node_modules/debug": {
|
| 2045 |
+
"version": "4.4.3",
|
| 2046 |
+
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
| 2047 |
+
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
| 2048 |
+
"license": "MIT",
|
| 2049 |
+
"dependencies": {
|
| 2050 |
+
"ms": "^2.1.3"
|
| 2051 |
+
},
|
| 2052 |
+
"engines": {
|
| 2053 |
+
"node": ">=6.0"
|
| 2054 |
+
},
|
| 2055 |
+
"peerDependenciesMeta": {
|
| 2056 |
+
"supports-color": {
|
| 2057 |
+
"optional": true
|
| 2058 |
+
}
|
| 2059 |
+
}
|
| 2060 |
+
},
|
| 2061 |
+
"node_modules/puppeteer-extra-plugin/node_modules/ms": {
|
| 2062 |
+
"version": "2.1.3",
|
| 2063 |
+
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
| 2064 |
+
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
| 2065 |
+
"license": "MIT"
|
| 2066 |
+
},
|
| 2067 |
+
"node_modules/qs": {
|
| 2068 |
+
"version": "6.14.2",
|
| 2069 |
+
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz",
|
| 2070 |
+
"integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==",
|
| 2071 |
+
"license": "BSD-3-Clause",
|
| 2072 |
+
"dependencies": {
|
| 2073 |
+
"side-channel": "^1.1.0"
|
| 2074 |
+
},
|
| 2075 |
+
"engines": {
|
| 2076 |
+
"node": ">=0.6"
|
| 2077 |
+
},
|
| 2078 |
+
"funding": {
|
| 2079 |
+
"url": "https://github.com/sponsors/ljharb"
|
| 2080 |
+
}
|
| 2081 |
+
},
|
| 2082 |
+
"node_modules/range-parser": {
|
| 2083 |
+
"version": "1.2.1",
|
| 2084 |
+
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
| 2085 |
+
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
|
| 2086 |
+
"license": "MIT",
|
| 2087 |
+
"engines": {
|
| 2088 |
+
"node": ">= 0.6"
|
| 2089 |
+
}
|
| 2090 |
+
},
|
| 2091 |
+
"node_modules/raw-body": {
|
| 2092 |
+
"version": "2.5.3",
|
| 2093 |
+
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz",
|
| 2094 |
+
"integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==",
|
| 2095 |
+
"license": "MIT",
|
| 2096 |
+
"dependencies": {
|
| 2097 |
+
"bytes": "~3.1.2",
|
| 2098 |
+
"http-errors": "~2.0.1",
|
| 2099 |
+
"iconv-lite": "~0.4.24",
|
| 2100 |
+
"unpipe": "~1.0.0"
|
| 2101 |
+
},
|
| 2102 |
+
"engines": {
|
| 2103 |
+
"node": ">= 0.8"
|
| 2104 |
+
}
|
| 2105 |
+
},
|
| 2106 |
+
"node_modules/rc": {
|
| 2107 |
+
"version": "1.2.8",
|
| 2108 |
+
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
|
| 2109 |
+
"integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
|
| 2110 |
+
"license": "(BSD-2-Clause OR MIT OR Apache-2.0)",
|
| 2111 |
+
"dependencies": {
|
| 2112 |
+
"deep-extend": "^0.6.0",
|
| 2113 |
+
"ini": "~1.3.0",
|
| 2114 |
+
"minimist": "^1.2.0",
|
| 2115 |
+
"strip-json-comments": "~2.0.1"
|
| 2116 |
+
},
|
| 2117 |
+
"bin": {
|
| 2118 |
+
"rc": "cli.js"
|
| 2119 |
+
}
|
| 2120 |
+
},
|
| 2121 |
+
"node_modules/readable-stream": {
|
| 2122 |
+
"version": "3.6.2",
|
| 2123 |
+
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
| 2124 |
+
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
|
| 2125 |
+
"license": "MIT",
|
| 2126 |
+
"dependencies": {
|
| 2127 |
+
"inherits": "^2.0.3",
|
| 2128 |
+
"string_decoder": "^1.1.1",
|
| 2129 |
+
"util-deprecate": "^1.0.1"
|
| 2130 |
+
},
|
| 2131 |
+
"engines": {
|
| 2132 |
+
"node": ">= 6"
|
| 2133 |
+
}
|
| 2134 |
+
},
|
| 2135 |
+
"node_modules/safe-buffer": {
|
| 2136 |
+
"version": "5.2.1",
|
| 2137 |
+
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
| 2138 |
+
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
|
| 2139 |
+
"funding": [
|
| 2140 |
+
{
|
| 2141 |
+
"type": "github",
|
| 2142 |
+
"url": "https://github.com/sponsors/feross"
|
| 2143 |
+
},
|
| 2144 |
+
{
|
| 2145 |
+
"type": "patreon",
|
| 2146 |
+
"url": "https://www.patreon.com/feross"
|
| 2147 |
+
},
|
| 2148 |
+
{
|
| 2149 |
+
"type": "consulting",
|
| 2150 |
+
"url": "https://feross.org/support"
|
| 2151 |
+
}
|
| 2152 |
+
],
|
| 2153 |
+
"license": "MIT"
|
| 2154 |
+
},
|
| 2155 |
+
"node_modules/safer-buffer": {
|
| 2156 |
+
"version": "2.1.2",
|
| 2157 |
+
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
| 2158 |
+
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
| 2159 |
+
"license": "MIT"
|
| 2160 |
+
},
|
| 2161 |
+
"node_modules/sax": {
|
| 2162 |
+
"version": "1.6.0",
|
| 2163 |
+
"resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz",
|
| 2164 |
+
"integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==",
|
| 2165 |
+
"license": "BlueOak-1.0.0",
|
| 2166 |
+
"engines": {
|
| 2167 |
+
"node": ">=11.0.0"
|
| 2168 |
+
}
|
| 2169 |
+
},
|
| 2170 |
+
"node_modules/semver": {
|
| 2171 |
+
"version": "7.7.4",
|
| 2172 |
+
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
|
| 2173 |
+
"integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
|
| 2174 |
+
"license": "ISC",
|
| 2175 |
+
"bin": {
|
| 2176 |
+
"semver": "bin/semver.js"
|
| 2177 |
+
},
|
| 2178 |
+
"engines": {
|
| 2179 |
+
"node": ">=10"
|
| 2180 |
+
}
|
| 2181 |
+
},
|
| 2182 |
+
"node_modules/send": {
|
| 2183 |
+
"version": "0.19.2",
|
| 2184 |
+
"resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz",
|
| 2185 |
+
"integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==",
|
| 2186 |
+
"license": "MIT",
|
| 2187 |
+
"dependencies": {
|
| 2188 |
+
"debug": "2.6.9",
|
| 2189 |
+
"depd": "2.0.0",
|
| 2190 |
+
"destroy": "1.2.0",
|
| 2191 |
+
"encodeurl": "~2.0.0",
|
| 2192 |
+
"escape-html": "~1.0.3",
|
| 2193 |
+
"etag": "~1.8.1",
|
| 2194 |
+
"fresh": "~0.5.2",
|
| 2195 |
+
"http-errors": "~2.0.1",
|
| 2196 |
+
"mime": "1.6.0",
|
| 2197 |
+
"ms": "2.1.3",
|
| 2198 |
+
"on-finished": "~2.4.1",
|
| 2199 |
+
"range-parser": "~1.2.1",
|
| 2200 |
+
"statuses": "~2.0.2"
|
| 2201 |
+
},
|
| 2202 |
+
"engines": {
|
| 2203 |
+
"node": ">= 0.8.0"
|
| 2204 |
+
}
|
| 2205 |
+
},
|
| 2206 |
+
"node_modules/send/node_modules/ms": {
|
| 2207 |
+
"version": "2.1.3",
|
| 2208 |
+
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
| 2209 |
+
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
| 2210 |
+
"license": "MIT"
|
| 2211 |
+
},
|
| 2212 |
+
"node_modules/serve-static": {
|
| 2213 |
+
"version": "1.16.3",
|
| 2214 |
+
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz",
|
| 2215 |
+
"integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==",
|
| 2216 |
+
"license": "MIT",
|
| 2217 |
+
"dependencies": {
|
| 2218 |
+
"encodeurl": "~2.0.0",
|
| 2219 |
+
"escape-html": "~1.0.3",
|
| 2220 |
+
"parseurl": "~1.3.3",
|
| 2221 |
+
"send": "~0.19.1"
|
| 2222 |
+
},
|
| 2223 |
+
"engines": {
|
| 2224 |
+
"node": ">= 0.8.0"
|
| 2225 |
+
}
|
| 2226 |
+
},
|
| 2227 |
+
"node_modules/setprototypeof": {
|
| 2228 |
+
"version": "1.2.0",
|
| 2229 |
+
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
|
| 2230 |
+
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
|
| 2231 |
+
"license": "ISC"
|
| 2232 |
+
},
|
| 2233 |
+
"node_modules/shallow-clone": {
|
| 2234 |
+
"version": "0.1.2",
|
| 2235 |
+
"resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-0.1.2.tgz",
|
| 2236 |
+
"integrity": "sha512-J1zdXCky5GmNnuauESROVu31MQSnLoYvlyEn6j2Ztk6Q5EHFIhxkMhYcv6vuDzl2XEzoRr856QwzMgWM/TmZgw==",
|
| 2237 |
+
"license": "MIT",
|
| 2238 |
+
"dependencies": {
|
| 2239 |
+
"is-extendable": "^0.1.1",
|
| 2240 |
+
"kind-of": "^2.0.1",
|
| 2241 |
+
"lazy-cache": "^0.2.3",
|
| 2242 |
+
"mixin-object": "^2.0.1"
|
| 2243 |
+
},
|
| 2244 |
+
"engines": {
|
| 2245 |
+
"node": ">=0.10.0"
|
| 2246 |
+
}
|
| 2247 |
+
},
|
| 2248 |
+
"node_modules/shallow-clone/node_modules/kind-of": {
|
| 2249 |
+
"version": "2.0.1",
|
| 2250 |
+
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-2.0.1.tgz",
|
| 2251 |
+
"integrity": "sha512-0u8i1NZ/mg0b+W3MGGw5I7+6Eib2nx72S/QvXa0hYjEkjTknYmEYQJwGu3mLC0BrhtJjtQafTkyRUQ75Kx0LVg==",
|
| 2252 |
+
"license": "MIT",
|
| 2253 |
+
"dependencies": {
|
| 2254 |
+
"is-buffer": "^1.0.2"
|
| 2255 |
+
},
|
| 2256 |
+
"engines": {
|
| 2257 |
+
"node": ">=0.10.0"
|
| 2258 |
+
}
|
| 2259 |
+
},
|
| 2260 |
+
"node_modules/shallow-clone/node_modules/lazy-cache": {
|
| 2261 |
+
"version": "0.2.7",
|
| 2262 |
+
"resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-0.2.7.tgz",
|
| 2263 |
+
"integrity": "sha512-gkX52wvU/R8DVMMt78ATVPFMJqfW8FPz1GZ1sVHBVQHmu/WvhIWE4cE1GBzhJNFicDeYhnwp6Rl35BcAIM3YOQ==",
|
| 2264 |
+
"license": "MIT",
|
| 2265 |
+
"engines": {
|
| 2266 |
+
"node": ">=0.10.0"
|
| 2267 |
+
}
|
| 2268 |
+
},
|
| 2269 |
+
"node_modules/side-channel": {
|
| 2270 |
+
"version": "1.1.0",
|
| 2271 |
+
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
|
| 2272 |
+
"integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
|
| 2273 |
+
"license": "MIT",
|
| 2274 |
+
"dependencies": {
|
| 2275 |
+
"es-errors": "^1.3.0",
|
| 2276 |
+
"object-inspect": "^1.13.3",
|
| 2277 |
+
"side-channel-list": "^1.0.0",
|
| 2278 |
+
"side-channel-map": "^1.0.1",
|
| 2279 |
+
"side-channel-weakmap": "^1.0.2"
|
| 2280 |
+
},
|
| 2281 |
+
"engines": {
|
| 2282 |
+
"node": ">= 0.4"
|
| 2283 |
+
},
|
| 2284 |
+
"funding": {
|
| 2285 |
+
"url": "https://github.com/sponsors/ljharb"
|
| 2286 |
+
}
|
| 2287 |
+
},
|
| 2288 |
+
"node_modules/side-channel-list": {
|
| 2289 |
+
"version": "1.0.1",
|
| 2290 |
+
"resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz",
|
| 2291 |
+
"integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==",
|
| 2292 |
+
"license": "MIT",
|
| 2293 |
+
"dependencies": {
|
| 2294 |
+
"es-errors": "^1.3.0",
|
| 2295 |
+
"object-inspect": "^1.13.4"
|
| 2296 |
+
},
|
| 2297 |
+
"engines": {
|
| 2298 |
+
"node": ">= 0.4"
|
| 2299 |
+
},
|
| 2300 |
+
"funding": {
|
| 2301 |
+
"url": "https://github.com/sponsors/ljharb"
|
| 2302 |
+
}
|
| 2303 |
+
},
|
| 2304 |
+
"node_modules/side-channel-map": {
|
| 2305 |
+
"version": "1.0.1",
|
| 2306 |
+
"resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
|
| 2307 |
+
"integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
|
| 2308 |
+
"license": "MIT",
|
| 2309 |
+
"dependencies": {
|
| 2310 |
+
"call-bound": "^1.0.2",
|
| 2311 |
+
"es-errors": "^1.3.0",
|
| 2312 |
+
"get-intrinsic": "^1.2.5",
|
| 2313 |
+
"object-inspect": "^1.13.3"
|
| 2314 |
+
},
|
| 2315 |
+
"engines": {
|
| 2316 |
+
"node": ">= 0.4"
|
| 2317 |
+
},
|
| 2318 |
+
"funding": {
|
| 2319 |
+
"url": "https://github.com/sponsors/ljharb"
|
| 2320 |
+
}
|
| 2321 |
+
},
|
| 2322 |
+
"node_modules/side-channel-weakmap": {
|
| 2323 |
+
"version": "1.0.2",
|
| 2324 |
+
"resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
|
| 2325 |
+
"integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
|
| 2326 |
+
"license": "MIT",
|
| 2327 |
+
"dependencies": {
|
| 2328 |
+
"call-bound": "^1.0.2",
|
| 2329 |
+
"es-errors": "^1.3.0",
|
| 2330 |
+
"get-intrinsic": "^1.2.5",
|
| 2331 |
+
"object-inspect": "^1.13.3",
|
| 2332 |
+
"side-channel-map": "^1.0.1"
|
| 2333 |
+
},
|
| 2334 |
+
"engines": {
|
| 2335 |
+
"node": ">= 0.4"
|
| 2336 |
+
},
|
| 2337 |
+
"funding": {
|
| 2338 |
+
"url": "https://github.com/sponsors/ljharb"
|
| 2339 |
+
}
|
| 2340 |
+
},
|
| 2341 |
+
"node_modules/simple-concat": {
|
| 2342 |
+
"version": "1.0.1",
|
| 2343 |
+
"resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
|
| 2344 |
+
"integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==",
|
| 2345 |
+
"funding": [
|
| 2346 |
+
{
|
| 2347 |
+
"type": "github",
|
| 2348 |
+
"url": "https://github.com/sponsors/feross"
|
| 2349 |
+
},
|
| 2350 |
+
{
|
| 2351 |
+
"type": "patreon",
|
| 2352 |
+
"url": "https://www.patreon.com/feross"
|
| 2353 |
+
},
|
| 2354 |
+
{
|
| 2355 |
+
"type": "consulting",
|
| 2356 |
+
"url": "https://feross.org/support"
|
| 2357 |
+
}
|
| 2358 |
+
],
|
| 2359 |
+
"license": "MIT"
|
| 2360 |
+
},
|
| 2361 |
+
"node_modules/simple-get": {
|
| 2362 |
+
"version": "4.0.1",
|
| 2363 |
+
"resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz",
|
| 2364 |
+
"integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==",
|
| 2365 |
+
"funding": [
|
| 2366 |
+
{
|
| 2367 |
+
"type": "github",
|
| 2368 |
+
"url": "https://github.com/sponsors/feross"
|
| 2369 |
+
},
|
| 2370 |
+
{
|
| 2371 |
+
"type": "patreon",
|
| 2372 |
+
"url": "https://www.patreon.com/feross"
|
| 2373 |
+
},
|
| 2374 |
+
{
|
| 2375 |
+
"type": "consulting",
|
| 2376 |
+
"url": "https://feross.org/support"
|
| 2377 |
+
}
|
| 2378 |
+
],
|
| 2379 |
+
"license": "MIT",
|
| 2380 |
+
"dependencies": {
|
| 2381 |
+
"decompress-response": "^6.0.0",
|
| 2382 |
+
"once": "^1.3.1",
|
| 2383 |
+
"simple-concat": "^1.0.0"
|
| 2384 |
+
}
|
| 2385 |
+
},
|
| 2386 |
+
"node_modules/statuses": {
|
| 2387 |
+
"version": "2.0.2",
|
| 2388 |
+
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
|
| 2389 |
+
"integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
|
| 2390 |
+
"license": "MIT",
|
| 2391 |
+
"engines": {
|
| 2392 |
+
"node": ">= 0.8"
|
| 2393 |
+
}
|
| 2394 |
+
},
|
| 2395 |
+
"node_modules/string_decoder": {
|
| 2396 |
+
"version": "1.3.0",
|
| 2397 |
+
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
|
| 2398 |
+
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
|
| 2399 |
+
"license": "MIT",
|
| 2400 |
+
"dependencies": {
|
| 2401 |
+
"safe-buffer": "~5.2.0"
|
| 2402 |
+
}
|
| 2403 |
+
},
|
| 2404 |
+
"node_modules/strip-json-comments": {
|
| 2405 |
+
"version": "2.0.1",
|
| 2406 |
+
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
|
| 2407 |
+
"integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",
|
| 2408 |
+
"license": "MIT",
|
| 2409 |
+
"engines": {
|
| 2410 |
+
"node": ">=0.10.0"
|
| 2411 |
+
}
|
| 2412 |
+
},
|
| 2413 |
+
"node_modules/tar-fs": {
|
| 2414 |
+
"version": "2.1.4",
|
| 2415 |
+
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz",
|
| 2416 |
+
"integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==",
|
| 2417 |
+
"license": "MIT",
|
| 2418 |
+
"dependencies": {
|
| 2419 |
+
"chownr": "^1.1.1",
|
| 2420 |
+
"mkdirp-classic": "^0.5.2",
|
| 2421 |
+
"pump": "^3.0.0",
|
| 2422 |
+
"tar-stream": "^2.1.4"
|
| 2423 |
+
}
|
| 2424 |
+
},
|
| 2425 |
+
"node_modules/tar-stream": {
|
| 2426 |
+
"version": "2.2.0",
|
| 2427 |
+
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
|
| 2428 |
+
"integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
|
| 2429 |
+
"license": "MIT",
|
| 2430 |
+
"dependencies": {
|
| 2431 |
+
"bl": "^4.0.3",
|
| 2432 |
+
"end-of-stream": "^1.4.1",
|
| 2433 |
+
"fs-constants": "^1.0.0",
|
| 2434 |
+
"inherits": "^2.0.3",
|
| 2435 |
+
"readable-stream": "^3.1.1"
|
| 2436 |
+
},
|
| 2437 |
+
"engines": {
|
| 2438 |
+
"node": ">=6"
|
| 2439 |
+
}
|
| 2440 |
+
},
|
| 2441 |
+
"node_modules/tdigest": {
|
| 2442 |
+
"version": "0.1.2",
|
| 2443 |
+
"resolved": "https://registry.npmjs.org/tdigest/-/tdigest-0.1.2.tgz",
|
| 2444 |
+
"integrity": "sha512-+G0LLgjjo9BZX2MfdvPfH+MKLCrxlXSYec5DaPYP1fe6Iyhf0/fSmJ0bFiZ1F8BT6cGXl2LpltQptzjXKWEkKA==",
|
| 2445 |
+
"license": "MIT",
|
| 2446 |
+
"dependencies": {
|
| 2447 |
+
"bintrees": "1.0.2"
|
| 2448 |
+
}
|
| 2449 |
+
},
|
| 2450 |
+
"node_modules/tiny-lru": {
|
| 2451 |
+
"version": "13.0.0",
|
| 2452 |
+
"resolved": "https://registry.npmjs.org/tiny-lru/-/tiny-lru-13.0.0.tgz",
|
| 2453 |
+
"integrity": "sha512-xDHxKKS1FdF0Tv2P+QT7IeSEg74K/8cEDzbv3Tv6UyHHUgBOjOiQiBp818MGj66dhurQus/IBcoAbwIKtSGc6Q==",
|
| 2454 |
+
"license": "BSD-3-Clause",
|
| 2455 |
+
"engines": {
|
| 2456 |
+
"node": ">=14"
|
| 2457 |
+
}
|
| 2458 |
+
},
|
| 2459 |
+
"node_modules/toidentifier": {
|
| 2460 |
+
"version": "1.0.1",
|
| 2461 |
+
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
|
| 2462 |
+
"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
|
| 2463 |
+
"license": "MIT",
|
| 2464 |
+
"engines": {
|
| 2465 |
+
"node": ">=0.6"
|
| 2466 |
+
}
|
| 2467 |
+
},
|
| 2468 |
+
"node_modules/tslib": {
|
| 2469 |
+
"version": "2.8.1",
|
| 2470 |
+
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
| 2471 |
+
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
| 2472 |
+
"license": "0BSD"
|
| 2473 |
+
},
|
| 2474 |
+
"node_modules/tunnel-agent": {
|
| 2475 |
+
"version": "0.6.0",
|
| 2476 |
+
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
|
| 2477 |
+
"integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==",
|
| 2478 |
+
"license": "Apache-2.0",
|
| 2479 |
+
"dependencies": {
|
| 2480 |
+
"safe-buffer": "^5.0.1"
|
| 2481 |
+
},
|
| 2482 |
+
"engines": {
|
| 2483 |
+
"node": "*"
|
| 2484 |
+
}
|
| 2485 |
+
},
|
| 2486 |
+
"node_modules/type-is": {
|
| 2487 |
+
"version": "1.6.18",
|
| 2488 |
+
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
|
| 2489 |
+
"integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
|
| 2490 |
+
"license": "MIT",
|
| 2491 |
+
"dependencies": {
|
| 2492 |
+
"media-typer": "0.3.0",
|
| 2493 |
+
"mime-types": "~2.1.24"
|
| 2494 |
+
},
|
| 2495 |
+
"engines": {
|
| 2496 |
+
"node": ">= 0.6"
|
| 2497 |
+
}
|
| 2498 |
+
},
|
| 2499 |
+
"node_modules/ua-is-frozen": {
|
| 2500 |
+
"version": "0.1.2",
|
| 2501 |
+
"resolved": "https://registry.npmjs.org/ua-is-frozen/-/ua-is-frozen-0.1.2.tgz",
|
| 2502 |
+
"integrity": "sha512-RwKDW2p3iyWn4UbaxpP2+VxwqXh0jpvdxsYpZ5j/MLLiQOfbsV5shpgQiw93+KMYQPcteeMQ289MaAFzs3G9pw==",
|
| 2503 |
+
"funding": [
|
| 2504 |
+
{
|
| 2505 |
+
"type": "github",
|
| 2506 |
+
"url": "https://github.com/sponsors/faisalman"
|
| 2507 |
+
},
|
| 2508 |
+
{
|
| 2509 |
+
"type": "opencollective",
|
| 2510 |
+
"url": "https://opencollective.com/ua-parser-js"
|
| 2511 |
+
},
|
| 2512 |
+
{
|
| 2513 |
+
"type": "paypal",
|
| 2514 |
+
"url": "https://paypal.me/faisalman"
|
| 2515 |
+
}
|
| 2516 |
+
],
|
| 2517 |
+
"license": "MIT"
|
| 2518 |
+
},
|
| 2519 |
+
"node_modules/ua-parser-js": {
|
| 2520 |
+
"version": "2.0.9",
|
| 2521 |
+
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-2.0.9.tgz",
|
| 2522 |
+
"integrity": "sha512-OsqGhxyo/wGdLSXMSJxuMGN6H4gDnKz6Fb3IBm4bxZFMnyy0sdf6MN96Ie8tC6z/btdO+Bsy8guxlvLdwT076w==",
|
| 2523 |
+
"funding": [
|
| 2524 |
+
{
|
| 2525 |
+
"type": "opencollective",
|
| 2526 |
+
"url": "https://opencollective.com/ua-parser-js"
|
| 2527 |
+
},
|
| 2528 |
+
{
|
| 2529 |
+
"type": "paypal",
|
| 2530 |
+
"url": "https://paypal.me/faisalman"
|
| 2531 |
+
},
|
| 2532 |
+
{
|
| 2533 |
+
"type": "github",
|
| 2534 |
+
"url": "https://github.com/sponsors/faisalman"
|
| 2535 |
+
}
|
| 2536 |
+
],
|
| 2537 |
+
"license": "AGPL-3.0-or-later",
|
| 2538 |
+
"dependencies": {
|
| 2539 |
+
"detect-europe-js": "^0.1.2",
|
| 2540 |
+
"is-standalone-pwa": "^0.1.1",
|
| 2541 |
+
"ua-is-frozen": "^0.1.2"
|
| 2542 |
+
},
|
| 2543 |
+
"bin": {
|
| 2544 |
+
"ua-parser-js": "script/cli.js"
|
| 2545 |
+
},
|
| 2546 |
+
"engines": {
|
| 2547 |
+
"node": "*"
|
| 2548 |
+
}
|
| 2549 |
+
},
|
| 2550 |
+
"node_modules/universalify": {
|
| 2551 |
+
"version": "2.0.1",
|
| 2552 |
+
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
|
| 2553 |
+
"integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
|
| 2554 |
+
"license": "MIT",
|
| 2555 |
+
"engines": {
|
| 2556 |
+
"node": ">= 10.0.0"
|
| 2557 |
+
}
|
| 2558 |
+
},
|
| 2559 |
+
"node_modules/unpipe": {
|
| 2560 |
+
"version": "1.0.0",
|
| 2561 |
+
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
| 2562 |
+
"integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
|
| 2563 |
+
"license": "MIT",
|
| 2564 |
+
"engines": {
|
| 2565 |
+
"node": ">= 0.8"
|
| 2566 |
+
}
|
| 2567 |
+
},
|
| 2568 |
+
"node_modules/update-browserslist-db": {
|
| 2569 |
+
"version": "1.2.3",
|
| 2570 |
+
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
|
| 2571 |
+
"integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
|
| 2572 |
+
"funding": [
|
| 2573 |
+
{
|
| 2574 |
+
"type": "opencollective",
|
| 2575 |
+
"url": "https://opencollective.com/browserslist"
|
| 2576 |
+
},
|
| 2577 |
+
{
|
| 2578 |
+
"type": "tidelift",
|
| 2579 |
+
"url": "https://tidelift.com/funding/github/npm/browserslist"
|
| 2580 |
+
},
|
| 2581 |
+
{
|
| 2582 |
+
"type": "github",
|
| 2583 |
+
"url": "https://github.com/sponsors/ai"
|
| 2584 |
+
}
|
| 2585 |
+
],
|
| 2586 |
+
"license": "MIT",
|
| 2587 |
+
"dependencies": {
|
| 2588 |
+
"escalade": "^3.2.0",
|
| 2589 |
+
"picocolors": "^1.1.1"
|
| 2590 |
+
},
|
| 2591 |
+
"bin": {
|
| 2592 |
+
"update-browserslist-db": "cli.js"
|
| 2593 |
+
},
|
| 2594 |
+
"peerDependencies": {
|
| 2595 |
+
"browserslist": ">= 4.21.0"
|
| 2596 |
+
}
|
| 2597 |
+
},
|
| 2598 |
+
"node_modules/util-deprecate": {
|
| 2599 |
+
"version": "1.0.2",
|
| 2600 |
+
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
| 2601 |
+
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
| 2602 |
+
"license": "MIT"
|
| 2603 |
+
},
|
| 2604 |
+
"node_modules/utils-merge": {
|
| 2605 |
+
"version": "1.0.1",
|
| 2606 |
+
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
|
| 2607 |
+
"integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
|
| 2608 |
+
"license": "MIT",
|
| 2609 |
+
"engines": {
|
| 2610 |
+
"node": ">= 0.4.0"
|
| 2611 |
+
}
|
| 2612 |
+
},
|
| 2613 |
+
"node_modules/vali-date": {
|
| 2614 |
+
"version": "1.0.0",
|
| 2615 |
+
"resolved": "https://registry.npmjs.org/vali-date/-/vali-date-1.0.0.tgz",
|
| 2616 |
+
"integrity": "sha512-sgECfZthyaCKW10N0fm27cg8HYTFK5qMWgypqkXMQ4Wbl/zZKx7xZICgcoxIIE+WFAP/MBL2EFwC/YvLxw3Zeg==",
|
| 2617 |
+
"license": "MIT",
|
| 2618 |
+
"engines": {
|
| 2619 |
+
"node": ">=0.10.0"
|
| 2620 |
+
}
|
| 2621 |
+
},
|
| 2622 |
+
"node_modules/vary": {
|
| 2623 |
+
"version": "1.1.2",
|
| 2624 |
+
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
| 2625 |
+
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
|
| 2626 |
+
"license": "MIT",
|
| 2627 |
+
"engines": {
|
| 2628 |
+
"node": ">= 0.8"
|
| 2629 |
+
}
|
| 2630 |
+
},
|
| 2631 |
+
"node_modules/wrappy": {
|
| 2632 |
+
"version": "1.0.2",
|
| 2633 |
+
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
| 2634 |
+
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
| 2635 |
+
"license": "ISC"
|
| 2636 |
+
},
|
| 2637 |
+
"node_modules/xml2js": {
|
| 2638 |
+
"version": "0.6.2",
|
| 2639 |
+
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz",
|
| 2640 |
+
"integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==",
|
| 2641 |
+
"license": "MIT",
|
| 2642 |
+
"dependencies": {
|
| 2643 |
+
"sax": ">=0.6.0",
|
| 2644 |
+
"xmlbuilder": "~11.0.0"
|
| 2645 |
+
},
|
| 2646 |
+
"engines": {
|
| 2647 |
+
"node": ">=4.0.0"
|
| 2648 |
+
}
|
| 2649 |
+
},
|
| 2650 |
+
"node_modules/xmlbuilder": {
|
| 2651 |
+
"version": "11.0.1",
|
| 2652 |
+
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
|
| 2653 |
+
"integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==",
|
| 2654 |
+
"license": "MIT",
|
| 2655 |
+
"engines": {
|
| 2656 |
+
"node": ">=4.0"
|
| 2657 |
+
}
|
| 2658 |
+
}
|
| 2659 |
+
}
|
| 2660 |
+
}
|
package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "hermes-agent",
|
| 3 |
+
"version": "1.0.0",
|
| 4 |
+
"description": "An AI agent with advanced tool-calling capabilities, featuring a flexible toolsets system for organizing and managing tools.",
|
| 5 |
+
"private": true,
|
| 6 |
+
"scripts": {
|
| 7 |
+
"postinstall": "echo '✅ Browser tools ready. Run: python run_agent.py --help'"
|
| 8 |
+
},
|
| 9 |
+
"repository": {
|
| 10 |
+
"type": "git",
|
| 11 |
+
"url": "git+https://github.com/NousResearch/Hermes-Agent.git"
|
| 12 |
+
},
|
| 13 |
+
"license": "MIT",
|
| 14 |
+
"bugs": {
|
| 15 |
+
"url": "https://github.com/NousResearch/Hermes-Agent/issues"
|
| 16 |
+
},
|
| 17 |
+
"homepage": "https://github.com/NousResearch/Hermes-Agent#readme",
|
| 18 |
+
"dependencies": {
|
| 19 |
+
"@askjo/camofox-browser": "^1.5.2",
|
| 20 |
+
"agent-browser": "^0.26.0"
|
| 21 |
+
},
|
| 22 |
+
"overrides": {
|
| 23 |
+
"lodash": "4.18.1"
|
| 24 |
+
},
|
| 25 |
+
"engines": {
|
| 26 |
+
"node": ">=20.0.0"
|
| 27 |
+
}
|
| 28 |
+
}
|
pyproject.toml
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[build-system]
|
| 2 |
+
requires = ["setuptools>=61.0"]
|
| 3 |
+
build-backend = "setuptools.build_meta"
|
| 4 |
+
|
| 5 |
+
[project]
|
| 6 |
+
name = "hermes-agent"
|
| 7 |
+
version = "0.13.0"
|
| 8 |
+
description = "The self-improving AI agent — creates skills from experience, improves them during use, and runs anywhere"
|
| 9 |
+
readme = "README.md"
|
| 10 |
+
requires-python = ">=3.11"
|
| 11 |
+
authors = [{ name = "Nous Research" }]
|
| 12 |
+
license = { text = "MIT" }
|
| 13 |
+
dependencies = [
|
| 14 |
+
# Core — pinned to known-good ranges to limit supply chain attack surface
|
| 15 |
+
"openai>=2.21.0,<3",
|
| 16 |
+
"anthropic>=0.39.0,<1",
|
| 17 |
+
"python-dotenv>=1.2.1,<2",
|
| 18 |
+
"fire>=0.7.1,<1",
|
| 19 |
+
"httpx[socks]>=0.28.1,<1",
|
| 20 |
+
"rich>=14.3.3,<15",
|
| 21 |
+
"tenacity>=9.1.4,<10",
|
| 22 |
+
"pyyaml>=6.0.2,<7",
|
| 23 |
+
"ruamel.yaml>=0.18.16,<0.19",
|
| 24 |
+
"requests>=2.33.0,<3", # CVE-2026-25645
|
| 25 |
+
"jinja2>=3.1.5,<4",
|
| 26 |
+
"pydantic>=2.12.5,<3",
|
| 27 |
+
# Interactive CLI (prompt_toolkit is used directly by cli.py)
|
| 28 |
+
"prompt_toolkit>=3.0.52,<4",
|
| 29 |
+
# Tools
|
| 30 |
+
"exa-py>=2.9.0,<3",
|
| 31 |
+
"firecrawl-py>=4.16.0,<5",
|
| 32 |
+
"parallel-web>=0.4.2,<1",
|
| 33 |
+
"fal-client>=0.13.1,<1",
|
| 34 |
+
# Cron scheduler (built-in feature — scheduled cron/interval jobs use croniter).
|
| 35 |
+
"croniter>=6.0.0,<7",
|
| 36 |
+
# Text-to-speech (Edge TTS is free, no API key needed)
|
| 37 |
+
"edge-tts>=7.2.7,<8",
|
| 38 |
+
# Skills Hub (GitHub App JWT auth — optional, only needed for bot identity)
|
| 39 |
+
"PyJWT[crypto]>=2.12.0,<3", # CVE-2026-32597
|
| 40 |
+
# Windows has no IANA tzdata shipped with the OS, so Python's ``zoneinfo``
|
| 41 |
+
# (PEP 615) raises ``ZoneInfoNotFoundError`` for every non-UTC timezone
|
| 42 |
+
# out of the box. ``tzdata`` ships the Olson database as a data package
|
| 43 |
+
# Python resolves automatically. No-op on Linux/macOS (which have
|
| 44 |
+
# /usr/share/zoneinfo). Credits: PR #13182 (@sprmn24).
|
| 45 |
+
"tzdata>=2023.3; sys_platform == 'win32'",
|
| 46 |
+
# Cross-platform process / PID management. `psutil` is the canonical
|
| 47 |
+
# answer for "is this PID alive" and process-tree walking across Linux,
|
| 48 |
+
# macOS and Windows. It replaces POSIX-only idioms like `os.kill(pid, 0)`
|
| 49 |
+
# (which is a silent killer on Windows — see CONTRIBUTING.md) and
|
| 50 |
+
# `os.killpg` (which doesn't exist on Windows).
|
| 51 |
+
"psutil>=5.9.0,<8",
|
| 52 |
+
]
|
| 53 |
+
|
| 54 |
+
[project.optional-dependencies]
|
| 55 |
+
modal = ["modal>=1.0.0,<2"]
|
| 56 |
+
daytona = ["daytona>=0.148.0,<1"]
|
| 57 |
+
vercel = ["vercel>=0.5.7,<0.6.0"]
|
| 58 |
+
dev = ["debugpy>=1.8.0,<2", "pytest>=9.0.2,<10", "pytest-asyncio>=1.3.0,<2", "pytest-xdist>=3.0,<4", "pytest-split>=0.9,<1", "mcp>=1.2.0,<2", "ty>=0.0.1a29,<0.0.22", "ruff"]
|
| 59 |
+
messaging = ["python-telegram-bot[webhooks]>=22.6,<23", "discord.py[voice]>=2.7.1,<3", "aiohttp>=3.13.3,<4", "slack-bolt>=1.18.0,<2", "slack-sdk>=3.27.0,<4", "qrcode>=7.0,<8"]
|
| 60 |
+
cron = [] # croniter is now a core dependency; this extra kept for back-compat
|
| 61 |
+
slack = ["slack-bolt>=1.18.0,<2", "slack-sdk>=3.27.0,<4"]
|
| 62 |
+
matrix = ["mautrix[encryption]>=0.20,<1", "Markdown>=3.6,<4", "aiosqlite>=0.20", "asyncpg>=0.29", "aiohttp-socks>=0.10,<1"]
|
| 63 |
+
cli = ["simple-term-menu>=1.0,<2"]
|
| 64 |
+
tts-premium = ["elevenlabs>=1.0,<2"]
|
| 65 |
+
voice = [
|
| 66 |
+
# Local STT pulls in wheel-only transitive deps (ctranslate2, onnxruntime),
|
| 67 |
+
# so keep it out of the base install for source-build packagers like Homebrew.
|
| 68 |
+
"faster-whisper>=1.0.0,<2",
|
| 69 |
+
"sounddevice>=0.4.6,<1",
|
| 70 |
+
"numpy>=1.24.0,<3",
|
| 71 |
+
]
|
| 72 |
+
pty = [
|
| 73 |
+
"ptyprocess>=0.7.0,<1; sys_platform != 'win32'",
|
| 74 |
+
"pywinpty>=2.0.0,<3; sys_platform == 'win32'",
|
| 75 |
+
]
|
| 76 |
+
honcho = ["honcho-ai>=2.0.1,<3"]
|
| 77 |
+
mcp = ["mcp>=1.2.0,<2"]
|
| 78 |
+
homeassistant = ["aiohttp>=3.9.0,<4"]
|
| 79 |
+
sms = ["aiohttp>=3.9.0,<4"]
|
| 80 |
+
# Computer use — macOS background desktop control via cua-driver (MCP stdio).
|
| 81 |
+
# The cua-driver binary itself is installed via `hermes tools` post-setup
|
| 82 |
+
# (curl install script); this extra just pins the MCP client used to talk
|
| 83 |
+
# to it, which is already provided by the `mcp` extra.
|
| 84 |
+
computer-use = ["mcp>=1.2.0,<2"]
|
| 85 |
+
acp = ["agent-client-protocol>=0.9.0,<1.0"]
|
| 86 |
+
mistral = ["mistralai>=2.3.0,<3"]
|
| 87 |
+
bedrock = ["boto3>=1.35.0,<2"]
|
| 88 |
+
termux = [
|
| 89 |
+
# Baseline Android / Termux path for reliable fresh installs.
|
| 90 |
+
"python-telegram-bot[webhooks]>=22.6,<23",
|
| 91 |
+
"hermes-agent[cron]",
|
| 92 |
+
"hermes-agent[cli]",
|
| 93 |
+
"hermes-agent[pty]",
|
| 94 |
+
"hermes-agent[mcp]",
|
| 95 |
+
"hermes-agent[honcho]",
|
| 96 |
+
"hermes-agent[acp]",
|
| 97 |
+
]
|
| 98 |
+
termux-all = [
|
| 99 |
+
# Best-effort "install all" profile for Termux: include broad extras that
|
| 100 |
+
# are known to resolve on Android, while intentionally excluding extras that
|
| 101 |
+
# currently hard-fail from missing/broken Android wheels/toolchains.
|
| 102 |
+
#
|
| 103 |
+
# Excluded for now:
|
| 104 |
+
# - matrix (mautrix[encryption] -> python-olm build failures on Termux)
|
| 105 |
+
# - voice (faster-whisper chain requires ctranslate2/av builds not packaged)
|
| 106 |
+
"hermes-agent[termux]",
|
| 107 |
+
"hermes-agent[messaging]",
|
| 108 |
+
"hermes-agent[slack]",
|
| 109 |
+
"hermes-agent[tts-premium]",
|
| 110 |
+
"hermes-agent[dingtalk]",
|
| 111 |
+
"hermes-agent[feishu]",
|
| 112 |
+
"hermes-agent[google]",
|
| 113 |
+
"hermes-agent[mistral]",
|
| 114 |
+
"hermes-agent[bedrock]",
|
| 115 |
+
"hermes-agent[homeassistant]",
|
| 116 |
+
"hermes-agent[sms]",
|
| 117 |
+
"hermes-agent[web]",
|
| 118 |
+
]
|
| 119 |
+
dingtalk = ["dingtalk-stream>=0.20,<1", "alibabacloud-dingtalk>=2.0.0", "qrcode>=7.0,<8"]
|
| 120 |
+
feishu = ["lark-oapi>=1.5.3,<2", "qrcode>=7.0,<8"]
|
| 121 |
+
google = [
|
| 122 |
+
# Required by the google-workspace skill (Gmail, Calendar, Drive, Contacts,
|
| 123 |
+
# Sheets, Docs). Declared here so packagers (Nix, Homebrew) ship them with
|
| 124 |
+
# the [all] extra and users don't hit runtime `pip install` paths that fail
|
| 125 |
+
# in environments without pip (e.g. Nix-managed Python).
|
| 126 |
+
"google-api-python-client>=2.100,<3",
|
| 127 |
+
"google-auth-oauthlib>=1.0,<2",
|
| 128 |
+
"google-auth-httplib2>=0.2,<1",
|
| 129 |
+
]
|
| 130 |
+
youtube = [
|
| 131 |
+
# Required by skills/media/youtube-content and
|
| 132 |
+
# optional-skills/productivity/memento-flashcards (youtube_quiz.py).
|
| 133 |
+
# Without this declaration uv sync omits the package and both skills fail
|
| 134 |
+
# at first invocation with ModuleNotFoundError (issue #22243).
|
| 135 |
+
"youtube-transcript-api>=1.2.0",
|
| 136 |
+
]
|
| 137 |
+
# `hermes dashboard` (localhost SPA + API). Not in core to keep the default install lean.
|
| 138 |
+
web = ["fastapi>=0.104.0,<1", "uvicorn[standard]>=0.24.0,<1"]
|
| 139 |
+
rl = [
|
| 140 |
+
"atroposlib @ git+https://github.com/NousResearch/atropos.git@c20c85256e5a45ad31edf8b7276e9c5ee1995a30",
|
| 141 |
+
"tinker @ git+https://github.com/thinking-machines-lab/tinker.git@30517b667f18a3dfb7ef33fb56cf686d5820ba2b",
|
| 142 |
+
"fastapi>=0.104.0,<1",
|
| 143 |
+
"uvicorn[standard]>=0.24.0,<1",
|
| 144 |
+
"wandb>=0.15.0,<1",
|
| 145 |
+
]
|
| 146 |
+
yc-bench = ["yc-bench @ git+https://github.com/collinear-ai/yc-bench.git@bfb0c88062450f46341bd9a5298903fc2e952a5c ; python_version >= '3.12'"]
|
| 147 |
+
all = [
|
| 148 |
+
"hermes-agent[modal]",
|
| 149 |
+
"hermes-agent[daytona]",
|
| 150 |
+
"hermes-agent[vercel]",
|
| 151 |
+
"hermes-agent[messaging]",
|
| 152 |
+
# matrix: python-olm (required by matrix-nio[e2e]) is upstream-broken on
|
| 153 |
+
# modern macOS (archived libolm, C++ errors with Clang 21+). On Linux the
|
| 154 |
+
# [matrix] extra's own marker pulls in the [e2e] variant automatically.
|
| 155 |
+
"hermes-agent[matrix]; sys_platform == 'linux'",
|
| 156 |
+
"hermes-agent[cron]",
|
| 157 |
+
"hermes-agent[cli]",
|
| 158 |
+
"hermes-agent[dev]",
|
| 159 |
+
"hermes-agent[tts-premium]",
|
| 160 |
+
"hermes-agent[slack]",
|
| 161 |
+
"hermes-agent[pty]",
|
| 162 |
+
"hermes-agent[honcho]",
|
| 163 |
+
"hermes-agent[mcp]",
|
| 164 |
+
"hermes-agent[homeassistant]",
|
| 165 |
+
"hermes-agent[sms]",
|
| 166 |
+
"hermes-agent[acp]",
|
| 167 |
+
"hermes-agent[voice]",
|
| 168 |
+
"hermes-agent[dingtalk]",
|
| 169 |
+
"hermes-agent[feishu]",
|
| 170 |
+
"hermes-agent[google]",
|
| 171 |
+
"hermes-agent[mistral]",
|
| 172 |
+
"hermes-agent[bedrock]",
|
| 173 |
+
"hermes-agent[web]",
|
| 174 |
+
"hermes-agent[youtube]",
|
| 175 |
+
]
|
| 176 |
+
|
| 177 |
+
[project.scripts]
|
| 178 |
+
hermes = "hermes_cli.main:main"
|
| 179 |
+
hermes-agent = "run_agent:main"
|
| 180 |
+
hermes-acp = "acp_adapter.entry:main"
|
| 181 |
+
|
| 182 |
+
[tool.setuptools]
|
| 183 |
+
py-modules = ["run_agent", "model_tools", "toolsets", "batch_runner", "trajectory_compressor", "toolset_distributions", "cli", "hermes_bootstrap", "hermes_constants", "hermes_state", "hermes_time", "hermes_logging", "rl_cli", "utils"]
|
| 184 |
+
|
| 185 |
+
[tool.setuptools.package-data]
|
| 186 |
+
hermes_cli = ["web_dist/**/*"]
|
| 187 |
+
gateway = ["assets/**/*"]
|
| 188 |
+
|
| 189 |
+
[tool.setuptools.packages.find]
|
| 190 |
+
include = ["agent", "agent.*", "tools", "tools.*", "hermes_cli", "gateway", "gateway.*", "tui_gateway", "tui_gateway.*", "cron", "acp_adapter", "plugins", "plugins.*", "providers", "providers.*"]
|
| 191 |
+
|
| 192 |
+
[tool.pytest.ini_options]
|
| 193 |
+
testpaths = ["tests"]
|
| 194 |
+
markers = [
|
| 195 |
+
"integration: marks tests requiring external services (API keys, Modal, etc.)",
|
| 196 |
+
]
|
| 197 |
+
addopts = "-m 'not integration' -n auto"
|
| 198 |
+
|
| 199 |
+
[tool.ty.environment]
|
| 200 |
+
python-version = "3.13"
|
| 201 |
+
|
| 202 |
+
[tool.ty.rules]
|
| 203 |
+
unknown-argument = "warn"
|
| 204 |
+
redundant-cast = "ignore"
|
| 205 |
+
|
| 206 |
+
[tool.ty.src]
|
| 207 |
+
exclude = ["tinker-atropos"]
|
| 208 |
+
|
| 209 |
+
[tool.ruff]
|
| 210 |
+
exclude = ["tinker-atropos"]
|
| 211 |
+
preview = true # required for PLW1514 (unspecified-encoding) — preview rule
|
| 212 |
+
|
| 213 |
+
[tool.ruff.lint]
|
| 214 |
+
# All other lints are intentionally disabled (see comment history on this
|
| 215 |
+
# file) while we wrangle typechecks — but PLW1514 is too load-bearing to
|
| 216 |
+
# keep off. Bare open()/read_text()/write_text() in text mode defaults to
|
| 217 |
+
# the system locale encoding on Windows (cp1252 on US-locale installs),
|
| 218 |
+
# which silently corrupts any non-ASCII file content. We had three
|
| 219 |
+
# separate Windows sandbox regressions in one debug session before
|
| 220 |
+
# adding the explicit encoding. This rule keeps new code honest.
|
| 221 |
+
select = ["PLW1514"]
|
| 222 |
+
|
| 223 |
+
[tool.ruff.lint.per-file-ignores]
|
| 224 |
+
# Tests can intentionally exercise locale-encoding edge cases.
|
| 225 |
+
"tests/**" = ["PLW1514"]
|
| 226 |
+
# Skills and plugins are partially user-authored — their own conventions.
|
| 227 |
+
"skills/**" = ["PLW1514"]
|
| 228 |
+
"optional-skills/**" = ["PLW1514"]
|
| 229 |
+
"plugins/**" = ["PLW1514"]
|
| 230 |
+
|
| 231 |
+
[tool.uv]
|
| 232 |
+
exclude-newer = "7 days"
|
rl_cli.py
ADDED
|
@@ -0,0 +1,446 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
RL Training CLI Runner
|
| 4 |
+
|
| 5 |
+
Dedicated CLI runner for RL training workflows with:
|
| 6 |
+
- Extended timeouts for long-running training
|
| 7 |
+
- RL-focused system prompts
|
| 8 |
+
- Full toolset including RL training tools
|
| 9 |
+
- Special handling for 30-minute check intervals
|
| 10 |
+
|
| 11 |
+
Usage:
|
| 12 |
+
python rl_cli.py "Train a model on GSM8k for math reasoning"
|
| 13 |
+
python rl_cli.py --interactive
|
| 14 |
+
python rl_cli.py --list-environments
|
| 15 |
+
|
| 16 |
+
Environment Variables:
|
| 17 |
+
TINKER_API_KEY: API key for Tinker service (required)
|
| 18 |
+
WANDB_API_KEY: API key for WandB metrics (required)
|
| 19 |
+
OPENROUTER_API_KEY: API key for OpenRouter (required for agent)
|
| 20 |
+
"""
|
| 21 |
+
|
| 22 |
+
import asyncio
|
| 23 |
+
import os
|
| 24 |
+
import sys
|
| 25 |
+
from pathlib import Path
|
| 26 |
+
|
| 27 |
+
import fire
|
| 28 |
+
import yaml
|
| 29 |
+
|
| 30 |
+
from hermes_constants import OPENROUTER_BASE_URL, get_hermes_home
|
| 31 |
+
|
| 32 |
+
# Load .env from ~/.hermes/.env first, then project root as dev fallback.
|
| 33 |
+
# User-managed env files should override stale shell exports on restart.
|
| 34 |
+
_hermes_home = get_hermes_home()
|
| 35 |
+
_project_env = Path(__file__).parent / '.env'
|
| 36 |
+
|
| 37 |
+
from hermes_cli.env_loader import load_hermes_dotenv
|
| 38 |
+
|
| 39 |
+
_loaded_env_paths = load_hermes_dotenv(hermes_home=_hermes_home, project_env=_project_env)
|
| 40 |
+
for _env_path in _loaded_env_paths:
|
| 41 |
+
print(f"✅ Loaded environment variables from {_env_path}")
|
| 42 |
+
|
| 43 |
+
# Set terminal working directory to tinker-atropos submodule
|
| 44 |
+
# This ensures terminal commands run in the right context for RL work
|
| 45 |
+
tinker_atropos_dir = Path(__file__).parent / 'tinker-atropos'
|
| 46 |
+
if tinker_atropos_dir.exists():
|
| 47 |
+
os.environ['TERMINAL_CWD'] = str(tinker_atropos_dir)
|
| 48 |
+
os.environ['HERMES_QUIET'] = '1' # Disable temp subdirectory creation
|
| 49 |
+
print(f"📂 Terminal working directory: {tinker_atropos_dir}")
|
| 50 |
+
else:
|
| 51 |
+
# Fall back to hermes-agent directory if submodule not found
|
| 52 |
+
os.environ['TERMINAL_CWD'] = str(Path(__file__).parent)
|
| 53 |
+
os.environ['HERMES_QUIET'] = '1'
|
| 54 |
+
print(f"⚠️ tinker-atropos submodule not found, using: {Path(__file__).parent}")
|
| 55 |
+
|
| 56 |
+
# Import agent and tools
|
| 57 |
+
from run_agent import AIAgent
|
| 58 |
+
from tools.rl_training_tool import get_missing_keys
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
# ============================================================================
|
| 62 |
+
# Config Loading
|
| 63 |
+
# ============================================================================
|
| 64 |
+
|
| 65 |
+
DEFAULT_MODEL = "anthropic/claude-opus-4.5"
|
| 66 |
+
DEFAULT_BASE_URL = OPENROUTER_BASE_URL
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
def load_hermes_config() -> dict:
|
| 70 |
+
"""
|
| 71 |
+
Load configuration from ~/.hermes/config.yaml.
|
| 72 |
+
|
| 73 |
+
Returns:
|
| 74 |
+
dict: Configuration with model, base_url, etc.
|
| 75 |
+
"""
|
| 76 |
+
config_path = _hermes_home / 'config.yaml'
|
| 77 |
+
|
| 78 |
+
config = {
|
| 79 |
+
"model": DEFAULT_MODEL,
|
| 80 |
+
"base_url": DEFAULT_BASE_URL,
|
| 81 |
+
}
|
| 82 |
+
|
| 83 |
+
if config_path.exists():
|
| 84 |
+
try:
|
| 85 |
+
with open(config_path, "r", encoding='utf-8') as f:
|
| 86 |
+
file_config = yaml.safe_load(f) or {}
|
| 87 |
+
|
| 88 |
+
# Get model from config
|
| 89 |
+
if "model" in file_config:
|
| 90 |
+
if isinstance(file_config["model"], str):
|
| 91 |
+
config["model"] = file_config["model"]
|
| 92 |
+
elif isinstance(file_config["model"], dict):
|
| 93 |
+
config["model"] = file_config["model"].get("default", DEFAULT_MODEL)
|
| 94 |
+
|
| 95 |
+
# Get base_url if specified
|
| 96 |
+
if "base_url" in file_config:
|
| 97 |
+
config["base_url"] = file_config["base_url"]
|
| 98 |
+
|
| 99 |
+
except Exception as e:
|
| 100 |
+
print(f"⚠️ Warning: Failed to load config.yaml: {e}")
|
| 101 |
+
|
| 102 |
+
return config
|
| 103 |
+
|
| 104 |
+
|
| 105 |
+
# ============================================================================
|
| 106 |
+
# RL-Specific Configuration
|
| 107 |
+
# ============================================================================
|
| 108 |
+
|
| 109 |
+
# Extended timeouts for long-running RL operations
|
| 110 |
+
RL_MAX_ITERATIONS = 200 # Allow many more iterations for long workflows
|
| 111 |
+
|
| 112 |
+
# RL-focused system prompt
|
| 113 |
+
RL_SYSTEM_PROMPT = """You are an automated post-training engineer specializing in reinforcement learning for language models.
|
| 114 |
+
|
| 115 |
+
## Your Capabilities
|
| 116 |
+
|
| 117 |
+
You have access to RL training tools for running reinforcement learning on models through Tinker-Atropos:
|
| 118 |
+
|
| 119 |
+
1. **DISCOVER**: Use `rl_list_environments` to see available RL environments
|
| 120 |
+
2. **INSPECT**: Read environment files to understand how they work (verifiers, data loading, rewards)
|
| 121 |
+
3. **INSPECT DATA**: Use terminal to explore HuggingFace datasets and understand their format
|
| 122 |
+
4. **CREATE**: Copy existing environments as templates, modify for your needs
|
| 123 |
+
5. **CONFIGURE**: Use `rl_select_environment` and `rl_edit_config` to set up training
|
| 124 |
+
6. **TEST**: Always use `rl_test_inference` before full training to validate your setup
|
| 125 |
+
7. **TRAIN**: Use `rl_start_training` to begin, `rl_check_status` to monitor
|
| 126 |
+
8. **EVALUATE**: Use `rl_get_results` and analyze WandB metrics to assess performance
|
| 127 |
+
|
| 128 |
+
## Environment Files
|
| 129 |
+
|
| 130 |
+
Environment files are located in: `tinker-atropos/tinker_atropos/environments/`
|
| 131 |
+
|
| 132 |
+
Study existing environments to learn patterns. Look for:
|
| 133 |
+
- `load_dataset()` calls - how data is loaded
|
| 134 |
+
- `score_answer()` / `score()` - verification logic
|
| 135 |
+
- `get_next_item()` - prompt formatting
|
| 136 |
+
- `system_prompt` - instruction format
|
| 137 |
+
- `config_init()` - default configuration
|
| 138 |
+
|
| 139 |
+
## Creating New Environments
|
| 140 |
+
|
| 141 |
+
To create a new environment:
|
| 142 |
+
1. Read an existing environment file (e.g., gsm8k_tinker.py)
|
| 143 |
+
2. Use terminal to explore the target dataset format
|
| 144 |
+
3. Copy the environment file as a template
|
| 145 |
+
4. Modify the dataset loading, prompt formatting, and verifier logic
|
| 146 |
+
5. Test with `rl_test_inference` before training
|
| 147 |
+
|
| 148 |
+
## Important Guidelines
|
| 149 |
+
|
| 150 |
+
- **Always test before training**: Training runs take hours - verify everything works first
|
| 151 |
+
- **Monitor metrics**: Check WandB for reward/mean and percent_correct
|
| 152 |
+
- **Status check intervals**: Wait at least 30 minutes between status checks
|
| 153 |
+
- **Early stopping**: Stop training early if metrics look bad or stagnant
|
| 154 |
+
- **Iterate quickly**: Start with small total_steps to validate, then scale up
|
| 155 |
+
|
| 156 |
+
## Available Toolsets
|
| 157 |
+
|
| 158 |
+
You have access to:
|
| 159 |
+
- **RL tools**: Environment discovery, config management, training, testing
|
| 160 |
+
- **Terminal**: Run commands, inspect files, explore datasets
|
| 161 |
+
- **Web**: Search for information, documentation, papers
|
| 162 |
+
- **File tools**: Read and modify code files
|
| 163 |
+
|
| 164 |
+
When asked to train a model, follow this workflow:
|
| 165 |
+
1. List available environments
|
| 166 |
+
2. Select and configure the appropriate environment
|
| 167 |
+
3. Test with sample prompts
|
| 168 |
+
4. Start training with conservative settings
|
| 169 |
+
5. Monitor progress and adjust as needed
|
| 170 |
+
"""
|
| 171 |
+
|
| 172 |
+
# Toolsets to enable for RL workflows
|
| 173 |
+
RL_TOOLSETS = ["terminal", "web", "rl"]
|
| 174 |
+
|
| 175 |
+
|
| 176 |
+
# ============================================================================
|
| 177 |
+
# Helper Functions
|
| 178 |
+
# ============================================================================
|
| 179 |
+
|
| 180 |
+
def check_requirements():
|
| 181 |
+
"""Check that all required environment variables and services are available."""
|
| 182 |
+
errors = []
|
| 183 |
+
|
| 184 |
+
# Check API keys
|
| 185 |
+
if not os.getenv("OPENROUTER_API_KEY"):
|
| 186 |
+
errors.append("OPENROUTER_API_KEY not set - required for agent")
|
| 187 |
+
|
| 188 |
+
missing_rl_keys = get_missing_keys()
|
| 189 |
+
if missing_rl_keys:
|
| 190 |
+
errors.append(f"Missing RL API keys: {', '.join(missing_rl_keys)}")
|
| 191 |
+
|
| 192 |
+
if errors:
|
| 193 |
+
print("❌ Missing requirements:")
|
| 194 |
+
for error in errors:
|
| 195 |
+
print(f" - {error}")
|
| 196 |
+
print("\nPlease set these environment variables in your .env file or shell.")
|
| 197 |
+
return False
|
| 198 |
+
|
| 199 |
+
return True
|
| 200 |
+
|
| 201 |
+
|
| 202 |
+
def check_tinker_atropos():
|
| 203 |
+
"""Check if tinker-atropos submodule is properly set up."""
|
| 204 |
+
tinker_path = Path(__file__).parent / "tinker-atropos"
|
| 205 |
+
|
| 206 |
+
if not tinker_path.exists():
|
| 207 |
+
return False, "tinker-atropos submodule not found. Run: git submodule update --init"
|
| 208 |
+
|
| 209 |
+
envs_path = tinker_path / "tinker_atropos" / "environments"
|
| 210 |
+
if not envs_path.exists():
|
| 211 |
+
return False, f"environments directory not found at {envs_path}"
|
| 212 |
+
|
| 213 |
+
env_files = list(envs_path.glob("*.py"))
|
| 214 |
+
env_files = [f for f in env_files if not f.name.startswith("_")]
|
| 215 |
+
|
| 216 |
+
return True, {"path": str(tinker_path), "environments_count": len(env_files)}
|
| 217 |
+
|
| 218 |
+
|
| 219 |
+
def list_environments_sync():
|
| 220 |
+
"""List available environments (synchronous wrapper)."""
|
| 221 |
+
from tools.rl_training_tool import rl_list_environments
|
| 222 |
+
import json
|
| 223 |
+
|
| 224 |
+
async def _list():
|
| 225 |
+
result = await rl_list_environments()
|
| 226 |
+
return json.loads(result)
|
| 227 |
+
|
| 228 |
+
return asyncio.run(_list())
|
| 229 |
+
|
| 230 |
+
|
| 231 |
+
# ============================================================================
|
| 232 |
+
# Main CLI
|
| 233 |
+
# ============================================================================
|
| 234 |
+
|
| 235 |
+
def main(
|
| 236 |
+
task: str = None,
|
| 237 |
+
model: str = None,
|
| 238 |
+
api_key: str = None,
|
| 239 |
+
base_url: str = None,
|
| 240 |
+
max_iterations: int = RL_MAX_ITERATIONS,
|
| 241 |
+
interactive: bool = False,
|
| 242 |
+
list_environments: bool = False,
|
| 243 |
+
check_server: bool = False,
|
| 244 |
+
verbose: bool = False,
|
| 245 |
+
save_trajectories: bool = True,
|
| 246 |
+
):
|
| 247 |
+
"""
|
| 248 |
+
RL Training CLI - Dedicated runner for RL training workflows.
|
| 249 |
+
|
| 250 |
+
Args:
|
| 251 |
+
task: The training task/goal (e.g., "Train a model on GSM8k for math")
|
| 252 |
+
model: Model to use for the agent (reads from ~/.hermes/config.yaml if not provided)
|
| 253 |
+
api_key: OpenRouter API key (uses OPENROUTER_API_KEY env var if not provided)
|
| 254 |
+
base_url: API base URL (reads from config or defaults to OpenRouter)
|
| 255 |
+
max_iterations: Maximum agent iterations (default: 200 for long workflows)
|
| 256 |
+
interactive: Run in interactive mode (multiple conversations)
|
| 257 |
+
list_environments: Just list available RL environments and exit
|
| 258 |
+
check_server: Check if RL API server is running and exit
|
| 259 |
+
verbose: Enable verbose logging
|
| 260 |
+
save_trajectories: Save conversation trajectories (default: True for RL)
|
| 261 |
+
|
| 262 |
+
Examples:
|
| 263 |
+
# Train on a specific environment
|
| 264 |
+
python rl_cli.py "Train a model on GSM8k math problems"
|
| 265 |
+
|
| 266 |
+
# Interactive mode
|
| 267 |
+
python rl_cli.py --interactive
|
| 268 |
+
|
| 269 |
+
# List available environments
|
| 270 |
+
python rl_cli.py --list-environments
|
| 271 |
+
|
| 272 |
+
# Check server status
|
| 273 |
+
python rl_cli.py --check-server
|
| 274 |
+
"""
|
| 275 |
+
# Load config from ~/.hermes/config.yaml
|
| 276 |
+
config = load_hermes_config()
|
| 277 |
+
|
| 278 |
+
# Use config values if not explicitly provided
|
| 279 |
+
if model is None:
|
| 280 |
+
model = config["model"]
|
| 281 |
+
if base_url is None:
|
| 282 |
+
base_url = config["base_url"]
|
| 283 |
+
|
| 284 |
+
print("🎯 RL Training Agent")
|
| 285 |
+
print("=" * 60)
|
| 286 |
+
|
| 287 |
+
# Handle setup check
|
| 288 |
+
if check_server:
|
| 289 |
+
print("\n🔍 Checking tinker-atropos setup...")
|
| 290 |
+
ok, result = check_tinker_atropos()
|
| 291 |
+
if ok:
|
| 292 |
+
print("✅ tinker-atropos submodule found")
|
| 293 |
+
print(f" Path: {result.get('path')}")
|
| 294 |
+
print(f" Environments found: {result.get('environments_count', 0)}")
|
| 295 |
+
|
| 296 |
+
# Also check API keys
|
| 297 |
+
missing = get_missing_keys()
|
| 298 |
+
if missing:
|
| 299 |
+
print(f"\n⚠️ Missing API keys: {', '.join(missing)}")
|
| 300 |
+
print(" Add them to ~/.hermes/.env")
|
| 301 |
+
else:
|
| 302 |
+
print("✅ API keys configured")
|
| 303 |
+
else:
|
| 304 |
+
print(f"❌ tinker-atropos not set up: {result}")
|
| 305 |
+
print("\nTo set up:")
|
| 306 |
+
print(" git submodule update --init")
|
| 307 |
+
print(" pip install -e ./tinker-atropos")
|
| 308 |
+
return
|
| 309 |
+
|
| 310 |
+
# Handle environment listing
|
| 311 |
+
if list_environments:
|
| 312 |
+
print("\n📋 Available RL Environments:")
|
| 313 |
+
print("-" * 40)
|
| 314 |
+
try:
|
| 315 |
+
data = list_environments_sync()
|
| 316 |
+
if "error" in data:
|
| 317 |
+
print(f"❌ Error: {data['error']}")
|
| 318 |
+
return
|
| 319 |
+
|
| 320 |
+
envs = data.get("environments", [])
|
| 321 |
+
if not envs:
|
| 322 |
+
print("No environments found.")
|
| 323 |
+
print("\nMake sure tinker-atropos is set up:")
|
| 324 |
+
print(" git submodule update --init")
|
| 325 |
+
return
|
| 326 |
+
|
| 327 |
+
for env in envs:
|
| 328 |
+
print(f"\n 📦 {env['name']}")
|
| 329 |
+
print(f" Class: {env['class_name']}")
|
| 330 |
+
print(f" Path: {env['file_path']}")
|
| 331 |
+
if env.get('description'):
|
| 332 |
+
desc = env['description'][:100] + "..." if len(env.get('description', '')) > 100 else env.get('description', '')
|
| 333 |
+
print(f" Description: {desc}")
|
| 334 |
+
|
| 335 |
+
print(f"\n📊 Total: {len(envs)} environments")
|
| 336 |
+
print("\nUse `rl_select_environment(name)` to select an environment for training.")
|
| 337 |
+
except Exception as e:
|
| 338 |
+
print(f"❌ Error listing environments: {e}")
|
| 339 |
+
print("\nMake sure tinker-atropos is set up:")
|
| 340 |
+
print(" git submodule update --init")
|
| 341 |
+
print(" pip install -e ./tinker-atropos")
|
| 342 |
+
return
|
| 343 |
+
|
| 344 |
+
# Check requirements
|
| 345 |
+
if not check_requirements():
|
| 346 |
+
sys.exit(1)
|
| 347 |
+
|
| 348 |
+
# Set default task if none provided
|
| 349 |
+
if not task and not interactive:
|
| 350 |
+
print("\n⚠️ No task provided. Use --interactive for interactive mode or provide a task.")
|
| 351 |
+
print("\nExamples:")
|
| 352 |
+
print(' python rl_cli.py "Train a model on GSM8k math problems"')
|
| 353 |
+
print(' python rl_cli.py "Create an RL environment for code generation"')
|
| 354 |
+
print(' python rl_cli.py --interactive')
|
| 355 |
+
return
|
| 356 |
+
|
| 357 |
+
# Get API key
|
| 358 |
+
api_key = api_key or os.getenv("OPENROUTER_API_KEY")
|
| 359 |
+
if not api_key:
|
| 360 |
+
print("❌ No API key provided. Set OPENROUTER_API_KEY or pass --api-key")
|
| 361 |
+
sys.exit(1)
|
| 362 |
+
|
| 363 |
+
print(f"\n🤖 Model: {model}")
|
| 364 |
+
print(f"🔧 Max iterations: {max_iterations}")
|
| 365 |
+
print(f"📁 Toolsets: {', '.join(RL_TOOLSETS)}")
|
| 366 |
+
print("=" * 60)
|
| 367 |
+
|
| 368 |
+
# Create agent with RL configuration
|
| 369 |
+
agent = AIAgent(
|
| 370 |
+
base_url=base_url,
|
| 371 |
+
api_key=api_key,
|
| 372 |
+
model=model,
|
| 373 |
+
max_iterations=max_iterations,
|
| 374 |
+
enabled_toolsets=RL_TOOLSETS,
|
| 375 |
+
save_trajectories=save_trajectories,
|
| 376 |
+
verbose_logging=verbose,
|
| 377 |
+
quiet_mode=False,
|
| 378 |
+
ephemeral_system_prompt=RL_SYSTEM_PROMPT,
|
| 379 |
+
)
|
| 380 |
+
|
| 381 |
+
if interactive:
|
| 382 |
+
# Interactive mode - multiple conversations
|
| 383 |
+
print("\n🔄 Interactive RL Training Mode")
|
| 384 |
+
print("Type 'quit' or 'exit' to end the session.")
|
| 385 |
+
print("Type 'status' to check active training runs.")
|
| 386 |
+
print("-" * 40)
|
| 387 |
+
|
| 388 |
+
while True:
|
| 389 |
+
try:
|
| 390 |
+
user_input = input("\n🎯 RL Task> ").strip()
|
| 391 |
+
|
| 392 |
+
if not user_input:
|
| 393 |
+
continue
|
| 394 |
+
|
| 395 |
+
if user_input.lower() in ('quit', 'exit', 'q'):
|
| 396 |
+
print("\n👋 Goodbye!")
|
| 397 |
+
break
|
| 398 |
+
|
| 399 |
+
if user_input.lower() == 'status':
|
| 400 |
+
# Quick status check
|
| 401 |
+
from tools.rl_training_tool import rl_list_runs
|
| 402 |
+
import json
|
| 403 |
+
result = asyncio.run(rl_list_runs())
|
| 404 |
+
runs = json.loads(result)
|
| 405 |
+
if isinstance(runs, list) and runs:
|
| 406 |
+
print("\n📊 Active Runs:")
|
| 407 |
+
for run in runs:
|
| 408 |
+
print(f" - {run['run_id']}: {run['environment']} ({run['status']})")
|
| 409 |
+
else:
|
| 410 |
+
print("\nNo active runs.")
|
| 411 |
+
continue
|
| 412 |
+
|
| 413 |
+
# Run the agent
|
| 414 |
+
print("\n" + "=" * 60)
|
| 415 |
+
agent.run_conversation(user_input)
|
| 416 |
+
print("\n" + "=" * 60)
|
| 417 |
+
|
| 418 |
+
except KeyboardInterrupt:
|
| 419 |
+
print("\n\n👋 Interrupted. Goodbye!")
|
| 420 |
+
break
|
| 421 |
+
except Exception as e:
|
| 422 |
+
print(f"\n❌ Error: {e}")
|
| 423 |
+
if verbose:
|
| 424 |
+
import traceback
|
| 425 |
+
traceback.print_exc()
|
| 426 |
+
else:
|
| 427 |
+
# Single task mode
|
| 428 |
+
print(f"\n📝 Task: {task}")
|
| 429 |
+
print("-" * 40)
|
| 430 |
+
|
| 431 |
+
try:
|
| 432 |
+
agent.run_conversation(task)
|
| 433 |
+
print("\n" + "=" * 60)
|
| 434 |
+
print("✅ Task completed")
|
| 435 |
+
except KeyboardInterrupt:
|
| 436 |
+
print("\n\n⚠️ Interrupted by user")
|
| 437 |
+
except Exception as e:
|
| 438 |
+
print(f"\n❌ Error: {e}")
|
| 439 |
+
if verbose:
|
| 440 |
+
import traceback
|
| 441 |
+
traceback.print_exc()
|
| 442 |
+
sys.exit(1)
|
| 443 |
+
|
| 444 |
+
|
| 445 |
+
if __name__ == "__main__":
|
| 446 |
+
fire.Fire(main)
|
run_agent.py
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
setup-hermes.sh
ADDED
|
@@ -0,0 +1,403 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
# ============================================================================
|
| 3 |
+
# Hermes Agent Setup Script
|
| 4 |
+
# ============================================================================
|
| 5 |
+
# Quick setup for developers who cloned the repo manually.
|
| 6 |
+
# Uses uv for desktop/server setup and Python's stdlib venv + pip on Termux.
|
| 7 |
+
#
|
| 8 |
+
# Usage:
|
| 9 |
+
# ./setup-hermes.sh
|
| 10 |
+
#
|
| 11 |
+
# This script:
|
| 12 |
+
# 1. Detects desktop/server vs Android/Termux setup path
|
| 13 |
+
# 2. Creates a Python 3.11 virtual environment
|
| 14 |
+
# 3. Installs the appropriate dependency set for the platform
|
| 15 |
+
# 4. Creates .env from template (if not exists)
|
| 16 |
+
# 5. Symlinks the 'hermes' CLI command into a user-facing bin dir
|
| 17 |
+
# 6. Runs the setup wizard (optional)
|
| 18 |
+
# ============================================================================
|
| 19 |
+
|
| 20 |
+
set -e
|
| 21 |
+
|
| 22 |
+
# Colors
|
| 23 |
+
GREEN='\033[0;32m'
|
| 24 |
+
YELLOW='\033[0;33m'
|
| 25 |
+
CYAN='\033[0;36m'
|
| 26 |
+
RED='\033[0;31m'
|
| 27 |
+
NC='\033[0m'
|
| 28 |
+
|
| 29 |
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
| 30 |
+
cd "$SCRIPT_DIR"
|
| 31 |
+
|
| 32 |
+
# Prevent uv from discovering config files (uv.toml, pyproject.toml) from the
|
| 33 |
+
# wrong user's home directory when running under sudo -u <user>. See #21269.
|
| 34 |
+
export UV_NO_CONFIG=1
|
| 35 |
+
|
| 36 |
+
PYTHON_VERSION="3.11"
|
| 37 |
+
|
| 38 |
+
is_termux() {
|
| 39 |
+
[ -n "${TERMUX_VERSION:-}" ] || [[ "${PREFIX:-}" == *"com.termux/files/usr"* ]]
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
get_command_link_dir() {
|
| 43 |
+
if is_termux && [ -n "${PREFIX:-}" ]; then
|
| 44 |
+
echo "$PREFIX/bin"
|
| 45 |
+
else
|
| 46 |
+
echo "$HOME/.local/bin"
|
| 47 |
+
fi
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
get_command_link_display_dir() {
|
| 51 |
+
if is_termux && [ -n "${PREFIX:-}" ]; then
|
| 52 |
+
echo '$PREFIX/bin'
|
| 53 |
+
else
|
| 54 |
+
echo '~/.local/bin'
|
| 55 |
+
fi
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
echo ""
|
| 59 |
+
echo -e "${CYAN}⚕ Hermes Agent Setup${NC}"
|
| 60 |
+
echo ""
|
| 61 |
+
|
| 62 |
+
# ============================================================================
|
| 63 |
+
# Install / locate uv
|
| 64 |
+
# ============================================================================
|
| 65 |
+
|
| 66 |
+
echo -e "${CYAN}→${NC} Checking for uv..."
|
| 67 |
+
|
| 68 |
+
UV_CMD=""
|
| 69 |
+
if is_termux; then
|
| 70 |
+
echo -e "${CYAN}→${NC} Termux detected — using Python's stdlib venv + pip instead of uv"
|
| 71 |
+
else
|
| 72 |
+
if command -v uv &> /dev/null; then
|
| 73 |
+
UV_CMD="uv"
|
| 74 |
+
elif [ -x "$HOME/.local/bin/uv" ]; then
|
| 75 |
+
UV_CMD="$HOME/.local/bin/uv"
|
| 76 |
+
elif [ -x "$HOME/.cargo/bin/uv" ]; then
|
| 77 |
+
UV_CMD="$HOME/.cargo/bin/uv"
|
| 78 |
+
fi
|
| 79 |
+
|
| 80 |
+
if [ -n "$UV_CMD" ]; then
|
| 81 |
+
UV_VERSION=$($UV_CMD --version 2>/dev/null)
|
| 82 |
+
echo -e "${GREEN}✓${NC} uv found ($UV_VERSION)"
|
| 83 |
+
else
|
| 84 |
+
echo -e "${CYAN}→${NC} Installing uv..."
|
| 85 |
+
if curl -LsSf https://astral.sh/uv/install.sh | sh 2>/dev/null; then
|
| 86 |
+
if [ -x "$HOME/.local/bin/uv" ]; then
|
| 87 |
+
UV_CMD="$HOME/.local/bin/uv"
|
| 88 |
+
elif [ -x "$HOME/.cargo/bin/uv" ]; then
|
| 89 |
+
UV_CMD="$HOME/.cargo/bin/uv"
|
| 90 |
+
fi
|
| 91 |
+
|
| 92 |
+
if [ -n "$UV_CMD" ]; then
|
| 93 |
+
UV_VERSION=$($UV_CMD --version 2>/dev/null)
|
| 94 |
+
echo -e "${GREEN}✓${NC} uv installed ($UV_VERSION)"
|
| 95 |
+
else
|
| 96 |
+
echo -e "${RED}✗${NC} uv installed but not found. Add ~/.local/bin to PATH and retry."
|
| 97 |
+
exit 1
|
| 98 |
+
fi
|
| 99 |
+
else
|
| 100 |
+
echo -e "${RED}✗${NC} Failed to install uv. Visit https://docs.astral.sh/uv/"
|
| 101 |
+
exit 1
|
| 102 |
+
fi
|
| 103 |
+
fi
|
| 104 |
+
fi
|
| 105 |
+
|
| 106 |
+
# ============================================================================
|
| 107 |
+
# Python check (uv can provision it automatically)
|
| 108 |
+
# ============================================================================
|
| 109 |
+
|
| 110 |
+
echo -e "${CYAN}→${NC} Checking Python $PYTHON_VERSION..."
|
| 111 |
+
|
| 112 |
+
if is_termux; then
|
| 113 |
+
if command -v python >/dev/null 2>&1; then
|
| 114 |
+
PYTHON_PATH="$(command -v python)"
|
| 115 |
+
if "$PYTHON_PATH" -c 'import sys; raise SystemExit(0 if sys.version_info >= (3, 11) else 1)' 2>/dev/null; then
|
| 116 |
+
PYTHON_FOUND_VERSION=$($PYTHON_PATH --version 2>/dev/null)
|
| 117 |
+
echo -e "${GREEN}✓${NC} $PYTHON_FOUND_VERSION found"
|
| 118 |
+
else
|
| 119 |
+
echo -e "${RED}✗${NC} Termux Python must be 3.11+"
|
| 120 |
+
echo " Run: pkg install python"
|
| 121 |
+
exit 1
|
| 122 |
+
fi
|
| 123 |
+
else
|
| 124 |
+
echo -e "${RED}✗${NC} Python not found in Termux"
|
| 125 |
+
echo " Run: pkg install python"
|
| 126 |
+
exit 1
|
| 127 |
+
fi
|
| 128 |
+
else
|
| 129 |
+
if $UV_CMD python find "$PYTHON_VERSION" &> /dev/null; then
|
| 130 |
+
PYTHON_PATH=$($UV_CMD python find "$PYTHON_VERSION")
|
| 131 |
+
PYTHON_FOUND_VERSION=$($PYTHON_PATH --version 2>/dev/null)
|
| 132 |
+
echo -e "${GREEN}✓${NC} $PYTHON_FOUND_VERSION found"
|
| 133 |
+
else
|
| 134 |
+
echo -e "${CYAN}→${NC} Python $PYTHON_VERSION not found, installing via uv..."
|
| 135 |
+
$UV_CMD python install "$PYTHON_VERSION"
|
| 136 |
+
PYTHON_PATH=$($UV_CMD python find "$PYTHON_VERSION")
|
| 137 |
+
PYTHON_FOUND_VERSION=$($PYTHON_PATH --version 2>/dev/null)
|
| 138 |
+
echo -e "${GREEN}✓${NC} $PYTHON_FOUND_VERSION installed"
|
| 139 |
+
fi
|
| 140 |
+
fi
|
| 141 |
+
|
| 142 |
+
# ============================================================================
|
| 143 |
+
# Virtual environment
|
| 144 |
+
# ============================================================================
|
| 145 |
+
|
| 146 |
+
echo -e "${CYAN}→${NC} Setting up virtual environment..."
|
| 147 |
+
|
| 148 |
+
if [ -d "venv" ]; then
|
| 149 |
+
echo -e "${CYAN}→${NC} Removing old venv..."
|
| 150 |
+
rm -rf venv
|
| 151 |
+
fi
|
| 152 |
+
|
| 153 |
+
if is_termux; then
|
| 154 |
+
"$PYTHON_PATH" -m venv venv
|
| 155 |
+
echo -e "${GREEN}✓${NC} venv created with stdlib venv"
|
| 156 |
+
else
|
| 157 |
+
$UV_CMD venv venv --python "$PYTHON_VERSION"
|
| 158 |
+
echo -e "${GREEN}✓${NC} venv created (Python $PYTHON_VERSION)"
|
| 159 |
+
fi
|
| 160 |
+
|
| 161 |
+
export VIRTUAL_ENV="$SCRIPT_DIR/venv"
|
| 162 |
+
SETUP_PYTHON="$SCRIPT_DIR/venv/bin/python"
|
| 163 |
+
|
| 164 |
+
# ============================================================================
|
| 165 |
+
# Dependencies
|
| 166 |
+
# ============================================================================
|
| 167 |
+
|
| 168 |
+
echo -e "${CYAN}→${NC} Installing dependencies..."
|
| 169 |
+
|
| 170 |
+
if is_termux; then
|
| 171 |
+
export ANDROID_API_LEVEL="$(getprop ro.build.version.sdk 2>/dev/null || printf '%s' "${ANDROID_API_LEVEL:-}")"
|
| 172 |
+
echo -e "${CYAN}→${NC} Termux detected — installing the tested Android bundle"
|
| 173 |
+
"$SETUP_PYTHON" -m pip install --upgrade pip setuptools wheel
|
| 174 |
+
if [ -f "constraints-termux.txt" ]; then
|
| 175 |
+
"$SETUP_PYTHON" -m pip install -e ".[termux]" -c constraints-termux.txt || {
|
| 176 |
+
echo -e "${YELLOW}⚠${NC} Termux bundle install failed, falling back to base install..."
|
| 177 |
+
"$SETUP_PYTHON" -m pip install -e "." -c constraints-termux.txt
|
| 178 |
+
}
|
| 179 |
+
else
|
| 180 |
+
"$SETUP_PYTHON" -m pip install -e ".[termux]" || "$SETUP_PYTHON" -m pip install -e "."
|
| 181 |
+
fi
|
| 182 |
+
echo -e "${GREEN}✓${NC} Dependencies installed"
|
| 183 |
+
else
|
| 184 |
+
# Prefer uv sync with lockfile (hash-verified installs) when available,
|
| 185 |
+
# fall back to pip install for compatibility or when lockfile is stale.
|
| 186 |
+
if [ -f "uv.lock" ]; then
|
| 187 |
+
echo -e "${CYAN}→${NC} Using uv.lock for hash-verified installation..."
|
| 188 |
+
UV_PROJECT_ENVIRONMENT="$SCRIPT_DIR/venv" $UV_CMD sync --all-extras --locked 2>/dev/null && \
|
| 189 |
+
echo -e "${GREEN}✓${NC} Dependencies installed (lockfile verified)" || {
|
| 190 |
+
echo -e "${YELLOW}⚠${NC} Lockfile install failed (may be outdated), falling back to pip install..."
|
| 191 |
+
$UV_CMD pip install -e ".[all]" || $UV_CMD pip install -e "."
|
| 192 |
+
echo -e "${GREEN}✓${NC} Dependencies installed"
|
| 193 |
+
}
|
| 194 |
+
else
|
| 195 |
+
$UV_CMD pip install -e ".[all]" || $UV_CMD pip install -e "."
|
| 196 |
+
echo -e "${GREEN}✓${NC} Dependencies installed"
|
| 197 |
+
fi
|
| 198 |
+
fi
|
| 199 |
+
|
| 200 |
+
# ============================================================================
|
| 201 |
+
# Submodules (terminal backend + RL training)
|
| 202 |
+
# ============================================================================
|
| 203 |
+
|
| 204 |
+
echo -e "${CYAN}→${NC} Installing optional submodules..."
|
| 205 |
+
|
| 206 |
+
# tinker-atropos (RL training backend)
|
| 207 |
+
if is_termux; then
|
| 208 |
+
echo -e "${CYAN}→${NC} Skipping tinker-atropos on Termux (not part of the tested Android path)"
|
| 209 |
+
elif [ -d "tinker-atropos" ] && [ -f "tinker-atropos/pyproject.toml" ]; then
|
| 210 |
+
$UV_CMD pip install -e "./tinker-atropos" && \
|
| 211 |
+
echo -e "${GREEN}✓${NC} tinker-atropos installed" || \
|
| 212 |
+
echo -e "${YELLOW}⚠${NC} tinker-atropos install failed (RL tools may not work)"
|
| 213 |
+
else
|
| 214 |
+
echo -e "${YELLOW}⚠${NC} tinker-atropos not found (run: git submodule update --init --recursive)"
|
| 215 |
+
fi
|
| 216 |
+
|
| 217 |
+
# ============================================================================
|
| 218 |
+
# Optional: ripgrep (for faster file search)
|
| 219 |
+
# ============================================================================
|
| 220 |
+
|
| 221 |
+
echo -e "${CYAN}→${NC} Checking ripgrep (optional, for faster search)..."
|
| 222 |
+
|
| 223 |
+
if command -v rg &> /dev/null; then
|
| 224 |
+
echo -e "${GREEN}✓${NC} ripgrep found"
|
| 225 |
+
else
|
| 226 |
+
echo -e "${YELLOW}⚠${NC} ripgrep not found (file search will use grep fallback)"
|
| 227 |
+
read -p "Install ripgrep for faster search? [Y/n] " -n 1 -r
|
| 228 |
+
echo
|
| 229 |
+
if [[ $REPLY =~ ^[Yy]$ ]] || [[ -z $REPLY ]]; then
|
| 230 |
+
INSTALLED=false
|
| 231 |
+
|
| 232 |
+
if is_termux; then
|
| 233 |
+
pkg install -y ripgrep && INSTALLED=true
|
| 234 |
+
else
|
| 235 |
+
# Check if sudo is available
|
| 236 |
+
if command -v sudo &> /dev/null && sudo -n true 2>/dev/null; then
|
| 237 |
+
if command -v apt &> /dev/null; then
|
| 238 |
+
sudo apt install -y ripgrep && INSTALLED=true
|
| 239 |
+
elif command -v dnf &> /dev/null; then
|
| 240 |
+
sudo dnf install -y ripgrep && INSTALLED=true
|
| 241 |
+
fi
|
| 242 |
+
fi
|
| 243 |
+
|
| 244 |
+
# Try brew (no sudo needed)
|
| 245 |
+
if [ "$INSTALLED" = false ] && command -v brew &> /dev/null; then
|
| 246 |
+
brew install ripgrep && INSTALLED=true
|
| 247 |
+
fi
|
| 248 |
+
|
| 249 |
+
# Try cargo (no sudo needed)
|
| 250 |
+
if [ "$INSTALLED" = false ] && command -v cargo &> /dev/null; then
|
| 251 |
+
echo -e "${CYAN}→${NC} Trying cargo install (no sudo required)..."
|
| 252 |
+
cargo install ripgrep && INSTALLED=true
|
| 253 |
+
fi
|
| 254 |
+
fi
|
| 255 |
+
|
| 256 |
+
if [ "$INSTALLED" = true ]; then
|
| 257 |
+
echo -e "${GREEN}✓${NC} ripgrep installed"
|
| 258 |
+
else
|
| 259 |
+
echo -e "${YELLOW}⚠${NC} Auto-install failed. Install options:"
|
| 260 |
+
if is_termux; then
|
| 261 |
+
echo " pkg install ripgrep # Termux / Android"
|
| 262 |
+
else
|
| 263 |
+
echo " sudo apt install ripgrep # Debian/Ubuntu"
|
| 264 |
+
echo " brew install ripgrep # macOS"
|
| 265 |
+
echo " cargo install ripgrep # With Rust (no sudo)"
|
| 266 |
+
fi
|
| 267 |
+
echo " https://github.com/BurntSushi/ripgrep#installation"
|
| 268 |
+
fi
|
| 269 |
+
fi
|
| 270 |
+
fi
|
| 271 |
+
|
| 272 |
+
# ============================================================================
|
| 273 |
+
# Environment file
|
| 274 |
+
# ============================================================================
|
| 275 |
+
|
| 276 |
+
if [ ! -f ".env" ]; then
|
| 277 |
+
if [ -f ".env.example" ]; then
|
| 278 |
+
cp .env.example .env
|
| 279 |
+
echo -e "${GREEN}✓${NC} Created .env from template"
|
| 280 |
+
fi
|
| 281 |
+
else
|
| 282 |
+
echo -e "${GREEN}✓${NC} .env exists"
|
| 283 |
+
fi
|
| 284 |
+
|
| 285 |
+
# ============================================================================
|
| 286 |
+
# PATH setup — symlink hermes into a user-facing bin dir
|
| 287 |
+
# ============================================================================
|
| 288 |
+
|
| 289 |
+
echo -e "${CYAN}→${NC} Setting up hermes command..."
|
| 290 |
+
|
| 291 |
+
HERMES_BIN="$SCRIPT_DIR/venv/bin/hermes"
|
| 292 |
+
COMMAND_LINK_DIR="$(get_command_link_dir)"
|
| 293 |
+
COMMAND_LINK_DISPLAY_DIR="$(get_command_link_display_dir)"
|
| 294 |
+
mkdir -p "$COMMAND_LINK_DIR"
|
| 295 |
+
ln -sf "$HERMES_BIN" "$COMMAND_LINK_DIR/hermes"
|
| 296 |
+
echo -e "${GREEN}✓${NC} Symlinked hermes → $COMMAND_LINK_DISPLAY_DIR/hermes"
|
| 297 |
+
|
| 298 |
+
if is_termux; then
|
| 299 |
+
export PATH="$COMMAND_LINK_DIR:$PATH"
|
| 300 |
+
echo -e "${GREEN}✓${NC} $COMMAND_LINK_DISPLAY_DIR is already on PATH in Termux"
|
| 301 |
+
else
|
| 302 |
+
# Determine the appropriate shell config file
|
| 303 |
+
SHELL_CONFIG=""
|
| 304 |
+
if [[ "$SHELL" == *"zsh"* ]]; then
|
| 305 |
+
SHELL_CONFIG="$HOME/.zshrc"
|
| 306 |
+
elif [[ "$SHELL" == *"bash"* ]]; then
|
| 307 |
+
SHELL_CONFIG="$HOME/.bashrc"
|
| 308 |
+
[ ! -f "$SHELL_CONFIG" ] && SHELL_CONFIG="$HOME/.bash_profile"
|
| 309 |
+
else
|
| 310 |
+
# Fallback to checking existing files
|
| 311 |
+
if [ -f "$HOME/.zshrc" ]; then
|
| 312 |
+
SHELL_CONFIG="$HOME/.zshrc"
|
| 313 |
+
elif [ -f "$HOME/.bashrc" ]; then
|
| 314 |
+
SHELL_CONFIG="$HOME/.bashrc"
|
| 315 |
+
elif [ -f "$HOME/.bash_profile" ]; then
|
| 316 |
+
SHELL_CONFIG="$HOME/.bash_profile"
|
| 317 |
+
fi
|
| 318 |
+
fi
|
| 319 |
+
|
| 320 |
+
if [ -n "$SHELL_CONFIG" ]; then
|
| 321 |
+
# Touch the file just in case it doesn't exist yet but was selected
|
| 322 |
+
touch "$SHELL_CONFIG" 2>/dev/null || true
|
| 323 |
+
|
| 324 |
+
if ! echo "$PATH" | tr ':' '\n' | grep -q "^$HOME/.local/bin$"; then
|
| 325 |
+
if ! grep -q '\.local/bin' "$SHELL_CONFIG" 2>/dev/null; then
|
| 326 |
+
echo "" >> "$SHELL_CONFIG"
|
| 327 |
+
echo "# Hermes Agent — ensure ~/.local/bin is on PATH" >> "$SHELL_CONFIG"
|
| 328 |
+
echo 'export PATH="$HOME/.local/bin:$PATH"' >> "$SHELL_CONFIG"
|
| 329 |
+
echo -e "${GREEN}✓${NC} Added ~/.local/bin to PATH in $SHELL_CONFIG"
|
| 330 |
+
else
|
| 331 |
+
echo -e "${GREEN}✓${NC} ~/.local/bin already in $SHELL_CONFIG"
|
| 332 |
+
fi
|
| 333 |
+
else
|
| 334 |
+
echo -e "${GREEN}✓${NC} ~/.local/bin already on PATH"
|
| 335 |
+
fi
|
| 336 |
+
fi
|
| 337 |
+
fi
|
| 338 |
+
|
| 339 |
+
# ============================================================================
|
| 340 |
+
# Seed bundled skills into ~/.hermes/skills/
|
| 341 |
+
# ============================================================================
|
| 342 |
+
|
| 343 |
+
HERMES_SKILLS_DIR="${HERMES_HOME:-$HOME/.hermes}/skills"
|
| 344 |
+
mkdir -p "$HERMES_SKILLS_DIR"
|
| 345 |
+
|
| 346 |
+
echo ""
|
| 347 |
+
echo "Syncing bundled skills to ~/.hermes/skills/ ..."
|
| 348 |
+
if "$SCRIPT_DIR/venv/bin/python" "$SCRIPT_DIR/tools/skills_sync.py" 2>/dev/null; then
|
| 349 |
+
echo -e "${GREEN}✓${NC} Skills synced"
|
| 350 |
+
else
|
| 351 |
+
# Fallback: copy if sync script fails (missing deps, etc.)
|
| 352 |
+
if [ -d "$SCRIPT_DIR/skills" ]; then
|
| 353 |
+
cp -rn "$SCRIPT_DIR/skills/"* "$HERMES_SKILLS_DIR/" 2>/dev/null || true
|
| 354 |
+
echo -e "${GREEN}✓${NC} Skills copied"
|
| 355 |
+
fi
|
| 356 |
+
fi
|
| 357 |
+
|
| 358 |
+
# ============================================================================
|
| 359 |
+
# Done
|
| 360 |
+
# ============================================================================
|
| 361 |
+
|
| 362 |
+
echo ""
|
| 363 |
+
echo -e "${GREEN}✓ Setup complete!${NC}"
|
| 364 |
+
echo ""
|
| 365 |
+
echo "Next steps:"
|
| 366 |
+
echo ""
|
| 367 |
+
if is_termux; then
|
| 368 |
+
echo " 1. Run the setup wizard to configure API keys:"
|
| 369 |
+
echo " hermes setup"
|
| 370 |
+
echo ""
|
| 371 |
+
echo " 2. Start chatting:"
|
| 372 |
+
echo " hermes"
|
| 373 |
+
echo ""
|
| 374 |
+
else
|
| 375 |
+
echo " 1. Reload your shell:"
|
| 376 |
+
echo " source $SHELL_CONFIG"
|
| 377 |
+
echo ""
|
| 378 |
+
echo " 2. Run the setup wizard to configure API keys:"
|
| 379 |
+
echo " hermes setup"
|
| 380 |
+
echo ""
|
| 381 |
+
echo " 3. Start chatting:"
|
| 382 |
+
echo " hermes"
|
| 383 |
+
echo ""
|
| 384 |
+
fi
|
| 385 |
+
echo "Other commands:"
|
| 386 |
+
echo " hermes status # Check configuration"
|
| 387 |
+
if is_termux; then
|
| 388 |
+
echo " hermes gateway # Run gateway in foreground"
|
| 389 |
+
else
|
| 390 |
+
echo " hermes gateway install # Install gateway service (messaging + cron)"
|
| 391 |
+
fi
|
| 392 |
+
echo " hermes cron list # View scheduled jobs"
|
| 393 |
+
echo " hermes doctor # Diagnose issues"
|
| 394 |
+
echo ""
|
| 395 |
+
|
| 396 |
+
# Ask if they want to run setup wizard now
|
| 397 |
+
read -p "Would you like to run the setup wizard now? [Y/n] " -n 1 -r
|
| 398 |
+
echo
|
| 399 |
+
if [[ $REPLY =~ ^[Yy]$ ]] || [[ -z $REPLY ]]; then
|
| 400 |
+
echo ""
|
| 401 |
+
# Run directly with venv Python (no activation needed)
|
| 402 |
+
"$SCRIPT_DIR/venv/bin/python" -m hermes_cli.main setup
|
| 403 |
+
fi
|