Compare commits
62 Commits
burn/20260
...
feat/resea
| Author | SHA1 | Date | |
|---|---|---|---|
| 6c358ae603 | |||
| 8450c4a8f5 | |||
| 0677bbd9d7 | |||
|
|
9b950bb897 | ||
| a3452d2c66 | |||
| 039d2ab88c | |||
| 6b036a76fc | |||
| c2eff61025 | |||
| 250e79b8c9 | |||
| abe321736e | |||
| ccc2fc3280 | |||
| 455b0c87b1 | |||
| 669c25b2bb | |||
| 4f2e75f228 | |||
| 6da28ef92d | |||
| bb905d3bf9 | |||
| 51c20bb6c6 | |||
| df8e87bf7c | |||
| 8495bff72f | |||
| 90c9549408 | |||
| ee1ce608b2 | |||
| 32f0065ad0 | |||
| 703e3f2676 | |||
| 4d78858180 | |||
| e8cf56b25b | |||
| 6e846fa082 | |||
| b1faef42f6 | |||
| aa71670f8d | |||
| f2159d4103 | |||
| 359ca0491f | |||
| eae08e8c01 | |||
| 4c3dbfe51f | |||
| d3e92f2b2d | |||
| e301dd97e5 | |||
| 26a41b84b6 | |||
| f6d2f36a34 | |||
| 986076b808 | |||
| 47c510c6f3 | |||
|
|
a318c389fe | ||
| 851f5601cf | |||
|
|
cdde3b27c1 | ||
| 9e96e51afd | |||
|
|
5e13fd2a5f | ||
| 04c017bcb3 | |||
| 4c2ac7b644 | |||
| 8202649ca0 | |||
| f5f028d981 | |||
|
|
a703fb823c | ||
| a89dae9942 | |||
|
|
f85c07551a | ||
| f81c60a5b3 | |||
| 01977f28fb | |||
| a055e68ebf | |||
| f6c9ecb893 | |||
| 549431bb81 | |||
| 43dc2d21f2 | |||
| 2948d010b7 | |||
|
|
0d92b9ad15 | ||
|
|
2e37ff638a | ||
|
|
511eacb573 | ||
| 2a6045a76a | |||
| 4ef7b5fc46 |
@@ -1,2 +0,0 @@
|
||||
{"created_at_ms":1775533542734,"session_id":"session-1775533542734-0","type":"session_meta","updated_at_ms":1775533542734,"version":1}
|
||||
{"message":{"blocks":[{"text":"You are Code Claw running as the Gitea user claw-code.\n\nRepository: Timmy_Foundation/hermes-agent\nIssue: #126 — P2: Validate Documentation Audit & Apply to Our Fork\nBranch: claw-code/issue-126\n\nRead the issue and recent comments, then implement the smallest correct change.\nYou are in a git repo checkout already.\n\nIssue body:\n## Context\n\nCommit `43d468ce` is a comprehensive documentation audit — fixes stale info, expands thin pages, adds depth across all docs.\n\n## Acceptance Criteria\n\n- [ ] **Catalog all doc changes**: Run `git show 43d468ce --stat` to list all files changed, then review each for what was fixed/expanded\n- [ ] **Verify key docs are accurate**: Pick 3 docs that were previously thin (setup, deployment, plugin development), confirm they now have comprehensive content\n- [ ] **Identify stale info that was corrected**: Note at least 3 pieces of stale information that were removed or updated\n- [ ] **Apply fixes to our fork if needed**: Check if any of the doc fixes apply to our `Timmy_Foundation/hermes-agent` fork (Timmy-specific references, custom config sections)\n\n## Why This Matters\n\nAccurate documentation is critical for onboarding new agents and maintaining the fleet. Stale docs cost more debugging time than writing them initially.\n\n## Hints\n\n- Run `cd ~/.hermes/hermes-agent && git show 43d468ce --stat` to see the full scope\n- The docs likely cover: setup, plugins, deployment, MCP configuration, and tool integrations\n\n\nParent: #111\n\nRecent comments:\n## 🏷️ Automated Triage Check\n\n**Timestamp:** 2026-04-06T15:30:12.449023 \n**Agent:** Allegro Heartbeat\n\nThis issue has been identified as needing triage:\n\n### Checklist\n- [ ] Clear acceptance criteria defined\n- [ ] Priority label assigned (p0-critical / p1-important / p2-backlog)\n- [ ] Size estimate added (quick-fix / day / week / epic)\n- [ ] Owner assigned\n- [ ] Related issues linked\n\n### Context\n- No comments yet — needs engagement\n- No labels — needs categorization\n- Part of automated backlog maintenance\n\n---\n*Automated triage from Allegro 15-minute heartbeat*\n\n[BURN-DOWN] Dispatched to Code Claw (claw-code worker) as part of nightly burn-down cycle. Heartbeat active.\n\n🟠 Code Claw (OpenRouter qwen/qwen3.6-plus:free) picking up this issue via 15-minute heartbeat.\n\nTimestamp: 2026-04-07T03:45:37Z\n\nRules:\n- Make focused code/config/doc changes only if they directly address the issue.\n- Prefer the smallest proof-oriented fix.\n- Run relevant verification commands if obvious.\n- Do NOT create PRs yourself; the outer worker handles commit/push/PR.\n- If the task is too large or not code-fit, leave the tree unchanged.\n","type":"text"}],"role":"user"},"type":"message"}
|
||||
@@ -1,2 +0,0 @@
|
||||
{"created_at_ms":1775534636684,"session_id":"session-1775534636684-0","type":"session_meta","updated_at_ms":1775534636684,"version":1}
|
||||
{"message":{"blocks":[{"text":"You are Code Claw running as the Gitea user claw-code.\n\nRepository: Timmy_Foundation/hermes-agent\nIssue: #151 — [CONFIG] Add Kimi model to fallback chain for Allegro and Bezalel\nBranch: claw-code/issue-151\n\nRead the issue and recent comments, then implement the smallest correct change.\nYou are in a git repo checkout already.\n\nIssue body:\n## Problem\nAllegro and Bezalel are choking because the Kimi model code is not on their fallback chain. When primary models fail or rate-limit, Kimi should be available as a fallback option but is currently missing.\n\n## Expected Behavior\nKimi model code should be at the front of the fallback chain for both Allegro and Bezalel, so they can remain responsive when primary models are unavailable.\n\n## Context\nThis was reported in Telegram by Alexander Whitestone after observing both agents becoming unresponsive. Ezra was asked to investigate the fallback chain configuration.\n\n## Related\n- timmy-config #302: [ARCH] Fallback Portfolio Runtime Wiring (general fallback framework)\n- hermes-agent #150: [BEZALEL][AUDIT] Telegram Request-to-Gitea Tracking Audit\n\n## Acceptance Criteria\n- [ ] Kimi model code is added to Allegro fallback chain\n- [ ] Kimi model code is added to Bezalel fallback chain\n- [ ] Fallback ordering places Kimi appropriately (front of chain as requested)\n- [ ] Test and confirm both agents can successfully fall back to Kimi\n- [ ] Document the fallback chain configuration for both agents\n\n/assign @ezra\n\nRecent comments:\n[BURN-DOWN] Dispatched to Code Claw (claw-code worker) as part of nightly burn-down cycle. Heartbeat active.\n\n🟠 Code Claw (OpenRouter qwen/qwen3.6-plus:free) picking up this issue via 15-minute heartbeat.\n\nTimestamp: 2026-04-07T04:03:49Z\n\nRules:\n- Make focused code/config/doc changes only if they directly address the issue.\n- Prefer the smallest proof-oriented fix.\n- Run relevant verification commands if obvious.\n- Do NOT create PRs yourself; the outer worker handles commit/push/PR.\n- If the task is too large or not code-fit, leave the tree unchanged.\n","type":"text"}],"role":"user"},"type":"message"}
|
||||
@@ -13,7 +13,7 @@ concurrency:
|
||||
jobs:
|
||||
smoke-and-build:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
run: |
|
||||
uv venv .venv --python 3.11
|
||||
source .venv/bin/activate
|
||||
uv pip install -e ".[all,dev]"
|
||||
uv pip install -e ".[dev]"
|
||||
|
||||
- name: Smoke tests
|
||||
run: |
|
||||
@@ -55,7 +55,7 @@ jobs:
|
||||
- name: Green-path E2E
|
||||
run: |
|
||||
source .venv/bin/activate
|
||||
python -m pytest tests/test_green_path_e2e.py -q --tb=short
|
||||
python -m pytest tests/test_green_path_e2e.py -q --tb=short -p no:xdist
|
||||
env:
|
||||
OPENROUTER_API_KEY: ""
|
||||
OPENAI_API_KEY: ""
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -58,3 +58,6 @@ mini-swe-agent/
|
||||
# Nix
|
||||
.direnv/
|
||||
result
|
||||
|
||||
# OpenClaw artifacts (banned)
|
||||
.claw/
|
||||
|
||||
2073
CHANGELOG.md
Normal file
2073
CHANGELOG.md
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,383 +0,0 @@
|
||||
# Hermes Agent v0.2.0 (v2026.3.12)
|
||||
|
||||
**Release Date:** March 12, 2026
|
||||
|
||||
> 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**.
|
||||
|
||||
---
|
||||
|
||||
## ✨ Highlights
|
||||
|
||||
- **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.
|
||||
|
||||
- **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))
|
||||
|
||||
- **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)
|
||||
|
||||
- **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))
|
||||
|
||||
- **ACP Server** — VS Code, Zed, and JetBrains editor integration via the Agent Communication Protocol standard. ([#949](https://github.com/NousResearch/hermes-agent/pull/949))
|
||||
|
||||
- **CLI Skin/Theme Engine** — Data-driven visual customization: banners, spinners, colors, branding. 7 built-in skins + custom YAML skins.
|
||||
|
||||
- **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))
|
||||
|
||||
- **Filesystem Checkpoints & Rollback** — Automatic snapshots before destructive operations with `/rollback` to restore. ([#824](https://github.com/NousResearch/hermes-agent/pull/824))
|
||||
|
||||
- **3,289 Tests** — From near-zero test coverage to a comprehensive test suite covering agent, gateway, tools, cron, and CLI.
|
||||
|
||||
---
|
||||
|
||||
## 🏗️ Core Agent & Architecture
|
||||
|
||||
### Provider & Model Support
|
||||
- Centralized provider router with `resolve_provider_client()` + `call_llm()` API ([#1003](https://github.com/NousResearch/hermes-agent/pull/1003))
|
||||
- Nous Portal as first-class provider in setup ([#644](https://github.com/NousResearch/hermes-agent/issues/644))
|
||||
- OpenAI Codex (Responses API) with ChatGPT subscription support ([#43](https://github.com/NousResearch/hermes-agent/pull/43)) — @grp06
|
||||
- Codex OAuth vision support + multimodal content adapter
|
||||
- Validate `/model` against live API instead of hardcoded lists
|
||||
- Self-hosted Firecrawl support ([#460](https://github.com/NousResearch/hermes-agent/pull/460)) — @caentzminger
|
||||
- Kimi Code API support ([#635](https://github.com/NousResearch/hermes-agent/pull/635)) — @christomitov
|
||||
- MiniMax model ID update ([#473](https://github.com/NousResearch/hermes-agent/pull/473)) — @tars90percent
|
||||
- OpenRouter provider routing configuration (provider_preferences)
|
||||
- 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
|
||||
- z.ai/GLM, Kimi/Moonshot, MiniMax, Azure OpenAI as first-class providers
|
||||
- Unified `/model` and `/provider` into single view
|
||||
|
||||
### Agent Loop & Conversation
|
||||
- Simple fallback model for provider resilience ([#740](https://github.com/NousResearch/hermes-agent/pull/740))
|
||||
- Shared iteration budget across parent + subagent delegation
|
||||
- Iteration budget pressure via tool result injection
|
||||
- Configurable subagent provider/model with full credential resolution
|
||||
- Handle 413 payload-too-large via compression instead of aborting ([#153](https://github.com/NousResearch/hermes-agent/pull/153)) — @tekelala
|
||||
- Retry with rebuilt payload after compression ([#616](https://github.com/NousResearch/hermes-agent/pull/616)) — @tripledoublev
|
||||
- Auto-compress pathologically large gateway sessions ([#628](https://github.com/NousResearch/hermes-agent/issues/628))
|
||||
- Tool call repair middleware — auto-lowercase and invalid tool handler
|
||||
- Reasoning effort configuration and `/reasoning` command ([#921](https://github.com/NousResearch/hermes-agent/pull/921))
|
||||
- Detect and block file re-read/search loops after context compression ([#705](https://github.com/NousResearch/hermes-agent/pull/705)) — @0xbyt4
|
||||
|
||||
### Session & Memory
|
||||
- Session naming with unique titles, auto-lineage, rich listing, and resume by name ([#720](https://github.com/NousResearch/hermes-agent/pull/720))
|
||||
- Interactive session browser with search filtering ([#733](https://github.com/NousResearch/hermes-agent/pull/733))
|
||||
- Display previous messages when resuming a session ([#734](https://github.com/NousResearch/hermes-agent/pull/734))
|
||||
- Honcho AI-native cross-session user modeling ([#38](https://github.com/NousResearch/hermes-agent/pull/38)) — @erosika
|
||||
- Proactive async memory flush on session expiry
|
||||
- Smart context length probing with persistent caching + banner display
|
||||
- `/resume` command for switching to named sessions in gateway
|
||||
- Session reset policy for messaging platforms
|
||||
|
||||
---
|
||||
|
||||
## 📱 Messaging Platforms (Gateway)
|
||||
|
||||
### Telegram
|
||||
- Native file attachments: send_document + send_video
|
||||
- Document file processing for PDF, text, and Office files — @tekelala
|
||||
- Forum topic session isolation ([#766](https://github.com/NousResearch/hermes-agent/pull/766)) — @spanishflu-est1918
|
||||
- Browser screenshot sharing via MEDIA: protocol ([#657](https://github.com/NousResearch/hermes-agent/pull/657))
|
||||
- Location support for find-nearby skill
|
||||
- TTS voice message accumulation fix ([#176](https://github.com/NousResearch/hermes-agent/pull/176)) — @Bartok9
|
||||
- Improved error handling and logging ([#763](https://github.com/NousResearch/hermes-agent/pull/763)) — @aydnOktay
|
||||
- Italic regex newline fix + 43 format tests ([#204](https://github.com/NousResearch/hermes-agent/pull/204)) — @0xbyt4
|
||||
|
||||
### Discord
|
||||
- Channel topic included in session context ([#248](https://github.com/NousResearch/hermes-agent/pull/248)) — @Bartok9
|
||||
- DISCORD_ALLOW_BOTS config for bot message filtering ([#758](https://github.com/NousResearch/hermes-agent/pull/758))
|
||||
- Document and video support ([#784](https://github.com/NousResearch/hermes-agent/pull/784))
|
||||
- Improved error handling and logging ([#761](https://github.com/NousResearch/hermes-agent/pull/761)) — @aydnOktay
|
||||
|
||||
### Slack
|
||||
- App_mention 404 fix + document/video support ([#784](https://github.com/NousResearch/hermes-agent/pull/784))
|
||||
- Structured logging replacing print statements — @aydnOktay
|
||||
|
||||
### WhatsApp
|
||||
- Native media sending — images, videos, documents ([#292](https://github.com/NousResearch/hermes-agent/pull/292)) — @satelerd
|
||||
- Multi-user session isolation ([#75](https://github.com/NousResearch/hermes-agent/pull/75)) — @satelerd
|
||||
- Cross-platform port cleanup replacing Linux-only fuser ([#433](https://github.com/NousResearch/hermes-agent/pull/433)) — @Farukest
|
||||
- DM interrupt key mismatch fix ([#350](https://github.com/NousResearch/hermes-agent/pull/350)) — @Farukest
|
||||
|
||||
### Signal
|
||||
- Full Signal messenger gateway via signal-cli-rest-api ([#405](https://github.com/NousResearch/hermes-agent/issues/405))
|
||||
- Media URL support in message events ([#871](https://github.com/NousResearch/hermes-agent/pull/871))
|
||||
|
||||
### Email (IMAP/SMTP)
|
||||
- New email gateway platform — @0xbyt4
|
||||
|
||||
### Home Assistant
|
||||
- REST tools + WebSocket gateway integration ([#184](https://github.com/NousResearch/hermes-agent/pull/184)) — @0xbyt4
|
||||
- Service discovery and enhanced setup
|
||||
- Toolset mapping fix ([#538](https://github.com/NousResearch/hermes-agent/pull/538)) — @Himess
|
||||
|
||||
### Gateway Core
|
||||
- Expose subagent tool calls and thinking to users ([#186](https://github.com/NousResearch/hermes-agent/pull/186)) — @cutepawss
|
||||
- Configurable background process watcher notifications ([#840](https://github.com/NousResearch/hermes-agent/pull/840))
|
||||
- `edit_message()` for Telegram/Discord/Slack with fallback
|
||||
- `/compress`, `/usage`, `/update` slash commands
|
||||
- Eliminated 3x SQLite message duplication in gateway sessions ([#873](https://github.com/NousResearch/hermes-agent/pull/873))
|
||||
- Stabilize system prompt across gateway turns for cache hits ([#754](https://github.com/NousResearch/hermes-agent/pull/754))
|
||||
- MCP server shutdown on gateway exit ([#796](https://github.com/NousResearch/hermes-agent/pull/796)) — @0xbyt4
|
||||
- Pass session_db to AIAgent, fixing session_search error ([#108](https://github.com/NousResearch/hermes-agent/pull/108)) — @Bartok9
|
||||
- Persist transcript changes in /retry, /undo; fix /reset attribute ([#217](https://github.com/NousResearch/hermes-agent/pull/217)) — @Farukest
|
||||
- UTF-8 encoding fix preventing Windows crashes ([#369](https://github.com/NousResearch/hermes-agent/pull/369)) — @ch3ronsa
|
||||
|
||||
---
|
||||
|
||||
## 🖥️ CLI & User Experience
|
||||
|
||||
### Interactive CLI
|
||||
- Data-driven skin/theme engine — 7 built-in skins (default, ares, mono, slate, poseidon, sisyphus, charizard) + custom YAML skins
|
||||
- `/personality` command with custom personality + disable support ([#773](https://github.com/NousResearch/hermes-agent/pull/773)) — @teyrebaz33
|
||||
- User-defined quick commands that bypass the agent loop ([#746](https://github.com/NousResearch/hermes-agent/pull/746)) — @teyrebaz33
|
||||
- `/reasoning` command for effort level and display toggle ([#921](https://github.com/NousResearch/hermes-agent/pull/921))
|
||||
- `/verbose` slash command to toggle debug at runtime ([#94](https://github.com/NousResearch/hermes-agent/pull/94)) — @cesareth
|
||||
- `/insights` command — usage analytics, cost estimation & activity patterns ([#552](https://github.com/NousResearch/hermes-agent/pull/552))
|
||||
- `/background` command for managing background processes
|
||||
- `/help` formatting with command categories
|
||||
- Bell-on-complete — terminal bell when agent finishes ([#738](https://github.com/NousResearch/hermes-agent/pull/738))
|
||||
- Up/down arrow history navigation
|
||||
- Clipboard image paste (Alt+V / Ctrl+V)
|
||||
- Loading indicators for slow slash commands ([#882](https://github.com/NousResearch/hermes-agent/pull/882))
|
||||
- Spinner flickering fix under patch_stdout ([#91](https://github.com/NousResearch/hermes-agent/pull/91)) — @0xbyt4
|
||||
- `--quiet/-Q` flag for programmatic single-query mode
|
||||
- `--fuck-it-ship-it` flag to bypass all approval prompts ([#724](https://github.com/NousResearch/hermes-agent/pull/724)) — @dmahan93
|
||||
- Tools summary flag ([#767](https://github.com/NousResearch/hermes-agent/pull/767)) — @luisv-1
|
||||
- Terminal blinking fix on SSH ([#284](https://github.com/NousResearch/hermes-agent/pull/284)) — @ygd58
|
||||
- Multi-line paste detection fix ([#84](https://github.com/NousResearch/hermes-agent/pull/84)) — @0xbyt4
|
||||
|
||||
### Setup & Configuration
|
||||
- Modular setup wizard with section subcommands and tool-first UX
|
||||
- Container resource configuration prompts
|
||||
- Backend validation for required binaries
|
||||
- Config migration system (currently v7)
|
||||
- API keys properly routed to .env instead of config.yaml ([#469](https://github.com/NousResearch/hermes-agent/pull/469)) — @ygd58
|
||||
- Atomic write for .env to prevent API key loss on crash ([#954](https://github.com/NousResearch/hermes-agent/pull/954))
|
||||
- `hermes tools` — per-platform tool enable/disable with curses UI
|
||||
- `hermes doctor` for health checks across all configured providers
|
||||
- `hermes update` with auto-restart for gateway service
|
||||
- Show update-available notice in CLI banner
|
||||
- Multiple named custom providers
|
||||
- Shell config detection improvement for PATH setup ([#317](https://github.com/NousResearch/hermes-agent/pull/317)) — @mehmetkr-31
|
||||
- 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
|
||||
- Docker backend fix on macOS + subagent auth for Nous Portal ([#46](https://github.com/NousResearch/hermes-agent/pull/46)) — @rsavitt
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Tool System
|
||||
|
||||
### MCP (Model Context Protocol)
|
||||
- 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))
|
||||
- Sampling support — server-initiated LLM requests ([#753](https://github.com/NousResearch/hermes-agent/pull/753))
|
||||
- Resource and prompt discovery
|
||||
- Automatic reconnection and security hardening
|
||||
- Banner integration, `/reload-mcp` command
|
||||
- `hermes tools` UI integration
|
||||
|
||||
### Browser
|
||||
- Local browser backend — zero-cost headless Chromium (no Browserbase needed)
|
||||
- Console/errors tool, annotated screenshots, auto-recording, dogfood QA skill ([#745](https://github.com/NousResearch/hermes-agent/pull/745))
|
||||
- Screenshot sharing via MEDIA: on all messaging platforms ([#657](https://github.com/NousResearch/hermes-agent/pull/657))
|
||||
|
||||
### Terminal & Execution
|
||||
- `execute_code` sandbox with json_parse, shell_quote, retry helpers
|
||||
- Docker: custom volume mounts ([#158](https://github.com/NousResearch/hermes-agent/pull/158)) — @Indelwin
|
||||
- Daytona cloud sandbox backend ([#451](https://github.com/NousResearch/hermes-agent/pull/451)) — @rovle
|
||||
- SSH backend fix ([#59](https://github.com/NousResearch/hermes-agent/pull/59)) — @deankerr
|
||||
- Shell noise filtering and login shell execution for environment consistency
|
||||
- Head+tail truncation for execute_code stdout overflow
|
||||
- Configurable background process notification modes
|
||||
|
||||
### File Operations
|
||||
- Filesystem checkpoints and `/rollback` command ([#824](https://github.com/NousResearch/hermes-agent/pull/824))
|
||||
- Structured tool result hints (next-action guidance) for patch and search_files ([#722](https://github.com/NousResearch/hermes-agent/issues/722))
|
||||
- Docker volumes passed to sandbox container config ([#687](https://github.com/NousResearch/hermes-agent/pull/687)) — @manuelschipper
|
||||
|
||||
---
|
||||
|
||||
## 🧩 Skills Ecosystem
|
||||
|
||||
### Skills System
|
||||
- Per-platform skill enable/disable ([#743](https://github.com/NousResearch/hermes-agent/pull/743)) — @teyrebaz33
|
||||
- Conditional skill activation based on tool availability ([#785](https://github.com/NousResearch/hermes-agent/pull/785)) — @teyrebaz33
|
||||
- Skill prerequisites — hide skills with unmet dependencies ([#659](https://github.com/NousResearch/hermes-agent/pull/659)) — @kshitijk4poor
|
||||
- Optional skills — shipped but not activated by default
|
||||
- `hermes skills browse` — paginated hub browsing
|
||||
- Skills sub-category organization
|
||||
- Platform-conditional skill loading
|
||||
- Atomic skill file writes ([#551](https://github.com/NousResearch/hermes-agent/pull/551)) — @aydnOktay
|
||||
- Skills sync data loss prevention ([#563](https://github.com/NousResearch/hermes-agent/pull/563)) — @0xbyt4
|
||||
- Dynamic skill slash commands for CLI and gateway
|
||||
|
||||
### New Skills (selected)
|
||||
- **ASCII Art** — pyfiglet (571 fonts), cowsay, image-to-ascii ([#209](https://github.com/NousResearch/hermes-agent/pull/209)) — @0xbyt4
|
||||
- **ASCII Video** — Full production pipeline ([#854](https://github.com/NousResearch/hermes-agent/pull/854)) — @SHL0MS
|
||||
- **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
|
||||
- **Solana Blockchain** — Wallet balances, USD pricing, token names ([#212](https://github.com/NousResearch/hermes-agent/pull/212)) — @gizdusum
|
||||
- **AgentMail** — Agent-owned email inboxes ([#330](https://github.com/NousResearch/hermes-agent/pull/330)) — @teyrebaz33
|
||||
- **Polymarket** — Prediction market data (read-only) ([#629](https://github.com/NousResearch/hermes-agent/pull/629))
|
||||
- **OpenClaw Migration** — Official migration tool ([#570](https://github.com/NousResearch/hermes-agent/pull/570)) — @unmodeled-tyler
|
||||
- **Domain Intelligence** — Passive recon: subdomains, SSL, WHOIS, DNS ([#136](https://github.com/NousResearch/hermes-agent/pull/136)) — @FurkanL0
|
||||
- **Superpowers** — Software development skills ([#137](https://github.com/NousResearch/hermes-agent/pull/137)) — @kaos35
|
||||
- **Hermes-Atropos** — RL environment development skill ([#815](https://github.com/NousResearch/hermes-agent/pull/815))
|
||||
- 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
|
||||
|
||||
---
|
||||
|
||||
## 🔒 Security & Reliability
|
||||
|
||||
### Security Hardening
|
||||
- Path traversal fix in skill_view — prevented reading arbitrary files ([#220](https://github.com/NousResearch/hermes-agent/issues/220)) — @Farukest
|
||||
- Shell injection prevention in sudo password piping ([#65](https://github.com/NousResearch/hermes-agent/pull/65)) — @leonsgithub
|
||||
- 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
|
||||
- Symlink boundary check fix in skills_guard ([#386](https://github.com/NousResearch/hermes-agent/pull/386)) — @Farukest
|
||||
- Symlink bypass fix in write deny list on macOS ([#61](https://github.com/NousResearch/hermes-agent/pull/61)) — @0xbyt4
|
||||
- Multi-word prompt injection bypass prevention ([#192](https://github.com/NousResearch/hermes-agent/pull/192)) — @0xbyt4
|
||||
- Cron prompt injection scanner bypass fix ([#63](https://github.com/NousResearch/hermes-agent/pull/63)) — @0xbyt4
|
||||
- Enforce 0600/0700 file permissions on sensitive files ([#757](https://github.com/NousResearch/hermes-agent/pull/757))
|
||||
- .env file permissions restricted to owner-only ([#529](https://github.com/NousResearch/hermes-agent/pull/529)) — @Himess
|
||||
- `--force` flag properly blocked from overriding dangerous verdicts ([#388](https://github.com/NousResearch/hermes-agent/pull/388)) — @Farukest
|
||||
- FTS5 query sanitization + DB connection leak fix ([#565](https://github.com/NousResearch/hermes-agent/pull/565)) — @0xbyt4
|
||||
- Expand secret redaction patterns + config toggle to disable
|
||||
- In-memory permanent allowlist to prevent data leak ([#600](https://github.com/NousResearch/hermes-agent/pull/600)) — @alireza78a
|
||||
|
||||
### Atomic Writes (data loss prevention)
|
||||
- sessions.json ([#611](https://github.com/NousResearch/hermes-agent/pull/611)) — @alireza78a
|
||||
- Cron jobs ([#146](https://github.com/NousResearch/hermes-agent/pull/146)) — @alireza78a
|
||||
- .env config ([#954](https://github.com/NousResearch/hermes-agent/pull/954))
|
||||
- Process checkpoints ([#298](https://github.com/NousResearch/hermes-agent/pull/298)) — @aydnOktay
|
||||
- Batch runner ([#297](https://github.com/NousResearch/hermes-agent/pull/297)) — @aydnOktay
|
||||
- Skill files ([#551](https://github.com/NousResearch/hermes-agent/pull/551)) — @aydnOktay
|
||||
|
||||
### Reliability
|
||||
- Guard all print() against OSError for systemd/headless environments ([#963](https://github.com/NousResearch/hermes-agent/pull/963))
|
||||
- Reset all retry counters at start of run_conversation ([#607](https://github.com/NousResearch/hermes-agent/pull/607)) — @0xbyt4
|
||||
- Return deny on approval callback timeout instead of None ([#603](https://github.com/NousResearch/hermes-agent/pull/603)) — @0xbyt4
|
||||
- Fix None message content crashes across codebase ([#277](https://github.com/NousResearch/hermes-agent/pull/277))
|
||||
- Fix context overrun crash with local LLM backends ([#403](https://github.com/NousResearch/hermes-agent/pull/403)) — @ch3ronsa
|
||||
- Prevent `_flush_sentinel` from leaking to external APIs ([#227](https://github.com/NousResearch/hermes-agent/pull/227)) — @Farukest
|
||||
- Prevent conversation_history mutation in callers ([#229](https://github.com/NousResearch/hermes-agent/pull/229)) — @Farukest
|
||||
- Fix systemd restart loop ([#614](https://github.com/NousResearch/hermes-agent/pull/614)) — @voidborne-d
|
||||
- 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)
|
||||
- Prevent data loss in clipboard PNG conversion ([#602](https://github.com/NousResearch/hermes-agent/pull/602)) — @0xbyt4
|
||||
- Eliminate shell noise from terminal output ([#293](https://github.com/NousResearch/hermes-agent/pull/293)) — @0xbyt4
|
||||
- Timezone-aware now() for prompt, cron, and execute_code ([#309](https://github.com/NousResearch/hermes-agent/pull/309)) — @areu01or00
|
||||
|
||||
### Windows Compatibility
|
||||
- Guard POSIX-only process functions ([#219](https://github.com/NousResearch/hermes-agent/pull/219)) — @Farukest
|
||||
- Windows native support via Git Bash + ZIP-based update fallback
|
||||
- pywinpty for PTY support ([#457](https://github.com/NousResearch/hermes-agent/pull/457)) — @shitcoinsherpa
|
||||
- Explicit UTF-8 encoding on all config/data file I/O ([#458](https://github.com/NousResearch/hermes-agent/pull/458)) — @shitcoinsherpa
|
||||
- Windows-compatible path handling ([#354](https://github.com/NousResearch/hermes-agent/pull/354), [#390](https://github.com/NousResearch/hermes-agent/pull/390)) — @Farukest
|
||||
- Regex-based search output parsing for drive-letter paths ([#533](https://github.com/NousResearch/hermes-agent/pull/533)) — @Himess
|
||||
- Auth store file lock for Windows ([#455](https://github.com/NousResearch/hermes-agent/pull/455)) — @shitcoinsherpa
|
||||
|
||||
---
|
||||
|
||||
## 🐛 Notable Bug Fixes
|
||||
|
||||
- Fix DeepSeek V3 tool call parser silently dropping multi-line JSON arguments ([#444](https://github.com/NousResearch/hermes-agent/pull/444)) — @PercyDikec
|
||||
- Fix gateway transcript losing 1 message per turn due to offset mismatch ([#395](https://github.com/NousResearch/hermes-agent/pull/395)) — @PercyDikec
|
||||
- Fix /retry command silently discarding the agent's final response ([#441](https://github.com/NousResearch/hermes-agent/pull/441)) — @PercyDikec
|
||||
- Fix max-iterations retry returning empty string after think-block stripping ([#438](https://github.com/NousResearch/hermes-agent/pull/438)) — @PercyDikec
|
||||
- Fix max-iterations retry using hardcoded max_tokens ([#436](https://github.com/NousResearch/hermes-agent/pull/436)) — @Farukest
|
||||
- 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
|
||||
- Strip \<think\> blocks from final user-facing responses ([#174](https://github.com/NousResearch/hermes-agent/pull/174)) — @Bartok9
|
||||
- Fix \<think\> block regex stripping visible content when model discusses tags literally ([#786](https://github.com/NousResearch/hermes-agent/issues/786))
|
||||
- Fix Mistral 422 errors from leftover finish_reason in assistant messages ([#253](https://github.com/NousResearch/hermes-agent/pull/253)) — @Sertug17
|
||||
- Fix OPENROUTER_API_KEY resolution order across all code paths ([#295](https://github.com/NousResearch/hermes-agent/pull/295)) — @0xbyt4
|
||||
- Fix OPENAI_BASE_URL API key priority ([#420](https://github.com/NousResearch/hermes-agent/pull/420)) — @manuelschipper
|
||||
- Fix Anthropic "prompt is too long" 400 error not detected as context length error ([#813](https://github.com/NousResearch/hermes-agent/issues/813))
|
||||
- Fix SQLite session transcript accumulating duplicate messages — 3-4x token inflation ([#860](https://github.com/NousResearch/hermes-agent/issues/860))
|
||||
- Fix setup wizard skipping API key prompts on first install ([#748](https://github.com/NousResearch/hermes-agent/pull/748))
|
||||
- Fix setup wizard showing OpenRouter model list for Nous Portal ([#575](https://github.com/NousResearch/hermes-agent/pull/575)) — @PercyDikec
|
||||
- Fix provider selection not persisting when switching via hermes model ([#881](https://github.com/NousResearch/hermes-agent/pull/881))
|
||||
- Fix Docker backend failing when docker not in PATH on macOS ([#889](https://github.com/NousResearch/hermes-agent/pull/889))
|
||||
- Fix ClawHub Skills Hub adapter for API endpoint changes ([#286](https://github.com/NousResearch/hermes-agent/pull/286)) — @BP602
|
||||
- Fix Honcho auto-enable when API key is present ([#243](https://github.com/NousResearch/hermes-agent/pull/243)) — @Bartok9
|
||||
- Fix duplicate 'skills' subparser crash on Python 3.11+ ([#898](https://github.com/NousResearch/hermes-agent/issues/898))
|
||||
- Fix memory tool entry parsing when content contains section sign ([#162](https://github.com/NousResearch/hermes-agent/pull/162)) — @aydnOktay
|
||||
- Fix piped install silently aborting when interactive prompts fail ([#72](https://github.com/NousResearch/hermes-agent/pull/72)) — @cutepawss
|
||||
- Fix false positives in recursive delete detection ([#68](https://github.com/NousResearch/hermes-agent/pull/68)) — @cutepawss
|
||||
- Fix Ruff lint warnings across codebase ([#608](https://github.com/NousResearch/hermes-agent/pull/608)) — @JackTheGit
|
||||
- Fix Anthropic native base URL fail-fast ([#173](https://github.com/NousResearch/hermes-agent/pull/173)) — @adavyas
|
||||
- Fix install.sh creating ~/.hermes before moving Node.js directory ([#53](https://github.com/NousResearch/hermes-agent/pull/53)) — @JoshuaMart
|
||||
- Fix SystemExit traceback during atexit cleanup on Ctrl+C ([#55](https://github.com/NousResearch/hermes-agent/pull/55)) — @bierlingm
|
||||
- Restore missing MIT license file ([#620](https://github.com/NousResearch/hermes-agent/pull/620)) — @stablegenius49
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Testing
|
||||
|
||||
- **3,289 tests** across agent, gateway, tools, cron, and CLI
|
||||
- Parallelized test suite with pytest-xdist ([#802](https://github.com/NousResearch/hermes-agent/pull/802)) — @OutThisLife
|
||||
- Unit tests batch 1: 8 core modules ([#60](https://github.com/NousResearch/hermes-agent/pull/60)) — @0xbyt4
|
||||
- Unit tests batch 2: 8 more modules ([#62](https://github.com/NousResearch/hermes-agent/pull/62)) — @0xbyt4
|
||||
- Unit tests batch 3: 8 untested modules ([#191](https://github.com/NousResearch/hermes-agent/pull/191)) — @0xbyt4
|
||||
- Unit tests batch 4: 5 security/logic-critical modules ([#193](https://github.com/NousResearch/hermes-agent/pull/193)) — @0xbyt4
|
||||
- AIAgent (run_agent.py) unit tests ([#67](https://github.com/NousResearch/hermes-agent/pull/67)) — @0xbyt4
|
||||
- Trajectory compressor tests ([#203](https://github.com/NousResearch/hermes-agent/pull/203)) — @0xbyt4
|
||||
- Clarify tool tests ([#121](https://github.com/NousResearch/hermes-agent/pull/121)) — @Bartok9
|
||||
- Telegram format tests — 43 tests for italic/bold/code rendering ([#204](https://github.com/NousResearch/hermes-agent/pull/204)) — @0xbyt4
|
||||
- Vision tools type hints + 42 tests ([#792](https://github.com/NousResearch/hermes-agent/pull/792))
|
||||
- Compressor tool-call boundary regression tests ([#648](https://github.com/NousResearch/hermes-agent/pull/648)) — @intertwine
|
||||
- Test structure reorganization ([#34](https://github.com/NousResearch/hermes-agent/pull/34)) — @0xbyt4
|
||||
- Shell noise elimination + fix 36 test failures ([#293](https://github.com/NousResearch/hermes-agent/pull/293)) — @0xbyt4
|
||||
|
||||
---
|
||||
|
||||
## 🔬 RL & Evaluation Environments
|
||||
|
||||
- WebResearchEnv — Multi-step web research RL environment ([#434](https://github.com/NousResearch/hermes-agent/pull/434)) — @jackx707
|
||||
- Modal sandbox concurrency limits to avoid deadlocks ([#621](https://github.com/NousResearch/hermes-agent/pull/621)) — @voteblake
|
||||
- Hermes-atropos-environments bundled skill ([#815](https://github.com/NousResearch/hermes-agent/pull/815))
|
||||
- Local vLLM instance support for evaluation — @dmahan93
|
||||
- YC-Bench long-horizon agent benchmark environment
|
||||
- OpenThoughts-TBLite evaluation environment and scripts
|
||||
|
||||
---
|
||||
|
||||
## 📚 Documentation
|
||||
|
||||
- Full documentation website (Docusaurus) with 37+ pages
|
||||
- Comprehensive platform setup guides for Telegram, Discord, Slack, WhatsApp, Signal, Email
|
||||
- AGENTS.md — development guide for AI coding assistants
|
||||
- CONTRIBUTING.md ([#117](https://github.com/NousResearch/hermes-agent/pull/117)) — @Bartok9
|
||||
- Slash commands reference ([#142](https://github.com/NousResearch/hermes-agent/pull/142)) — @Bartok9
|
||||
- Comprehensive AGENTS.md accuracy audit ([#732](https://github.com/NousResearch/hermes-agent/pull/732))
|
||||
- Skin/theme system documentation
|
||||
- MCP documentation and examples
|
||||
- Docs accuracy audit — 35+ corrections
|
||||
- Documentation typo fixes ([#825](https://github.com/NousResearch/hermes-agent/pull/825), [#439](https://github.com/NousResearch/hermes-agent/pull/439)) — @JackTheGit
|
||||
- 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
|
||||
- Telegram token regex documentation ([#713](https://github.com/NousResearch/hermes-agent/pull/713)) — @VolodymyrBg
|
||||
|
||||
---
|
||||
|
||||
## 👥 Contributors
|
||||
|
||||
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.
|
||||
|
||||
### Core
|
||||
- **@teknium1** — 43 PRs: Project lead, core architecture, provider router, sessions, skills, CLI, documentation
|
||||
|
||||
### Top Community Contributors
|
||||
- **@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
|
||||
- **@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
|
||||
- **@aydnOktay** — 11 PRs: Atomic writes (process checkpoints, batch runner, skill files), error handling improvements across Telegram, Discord, code execution, transcription, TTS, and skills
|
||||
- **@Bartok9** — 9 PRs: CONTRIBUTING.md, slash commands reference, Discord channel topics, think-block stripping, TTS fix, Honcho fix, session count fix, clarify tests
|
||||
- **@PercyDikec** — 7 PRs: DeepSeek V3 parser fix, /retry response discard, gateway transcript offset, Codex status/visibility, max-iterations retry, setup wizard fix
|
||||
- **@teyrebaz33** — 5 PRs: Skills enable/disable system, quick commands, personality customization, conditional skill activation
|
||||
- **@alireza78a** — 5 PRs: Atomic writes (cron, sessions), fd leak prevention, security allowlist, code execution socket cleanup
|
||||
- **@shitcoinsherpa** — 3 PRs: Windows support (pywinpty, UTF-8 encoding, auth store lock)
|
||||
- **@Himess** — 3 PRs: Cron/HomeAssistant/Daytona fix, Windows drive-letter parsing, .env permissions
|
||||
- **@satelerd** — 2 PRs: WhatsApp native media, multi-user session isolation
|
||||
- **@rovle** — 1 PR: Daytona cloud sandbox backend (4 commits)
|
||||
- **@erosika** — 1 PR: Honcho AI-native memory integration
|
||||
- **@dmahan93** — 1 PR: --fuck-it-ship-it flag + RL environment work
|
||||
- **@SHL0MS** — 1 PR: ASCII video skill
|
||||
|
||||
### All Contributors
|
||||
@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
|
||||
|
||||
---
|
||||
|
||||
**Full Changelog**: [v0.1.0...v2026.3.12](https://github.com/NousResearch/hermes-agent/compare/v0.1.0...v2026.3.12)
|
||||
@@ -1,377 +0,0 @@
|
||||
# Hermes Agent v0.3.0 (v2026.3.17)
|
||||
|
||||
**Release Date:** March 17, 2026
|
||||
|
||||
> 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.
|
||||
|
||||
---
|
||||
|
||||
## ✨ Highlights
|
||||
|
||||
- **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))
|
||||
|
||||
- **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))
|
||||
|
||||
- **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))
|
||||
|
||||
- **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))
|
||||
|
||||
- **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))
|
||||
|
||||
- **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))
|
||||
|
||||
- **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))
|
||||
|
||||
- **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))
|
||||
|
||||
- **`/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))
|
||||
|
||||
- **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))
|
||||
|
||||
- **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))
|
||||
|
||||
- **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))
|
||||
|
||||
- **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))
|
||||
|
||||
- **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))
|
||||
|
||||
---
|
||||
|
||||
## 🏗️ Core Agent & Architecture
|
||||
|
||||
### Provider & Model Support
|
||||
- **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))
|
||||
- **Vercel AI Gateway** provider support ([#1628](https://github.com/NousResearch/hermes-agent/pull/1628))
|
||||
- **Auto-detect provider** when switching models via `/model` ([#1506](https://github.com/NousResearch/hermes-agent/pull/1506))
|
||||
- **Direct endpoint overrides** for auxiliary and delegation clients — point vision/subagent calls at specific endpoints ([#1375](https://github.com/NousResearch/hermes-agent/pull/1375))
|
||||
- **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))
|
||||
- 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))
|
||||
- Fix adaptive thinking without `budget_tokens` for Claude 4.6 models — by @ASRagab ([#1128](https://github.com/NousResearch/hermes-agent/pull/1128))
|
||||
- Fix Anthropic cache markers through adapter — by @brandtcormorant ([#1216](https://github.com/NousResearch/hermes-agent/pull/1216))
|
||||
- Retry Anthropic 429/529 errors and surface details to users — by @0xbyt4 ([#1585](https://github.com/NousResearch/hermes-agent/pull/1585))
|
||||
- Fix Anthropic adapter max_tokens, fallback crash, proxy base_url — by @0xbyt4 ([#1121](https://github.com/NousResearch/hermes-agent/pull/1121))
|
||||
- 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))
|
||||
- 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))
|
||||
- Skip reasoning params for unsupported OpenRouter models ([#1485](https://github.com/NousResearch/hermes-agent/pull/1485))
|
||||
- MiniMax Anthropic API compatibility fix ([#1623](https://github.com/NousResearch/hermes-agent/pull/1623))
|
||||
- Custom endpoint `/models` verification and `/v1` base URL suggestion ([#1480](https://github.com/NousResearch/hermes-agent/pull/1480))
|
||||
- Resolve delegation providers from `custom_providers` config ([#1328](https://github.com/NousResearch/hermes-agent/pull/1328))
|
||||
- Kimi model additions and User-Agent fix ([#1039](https://github.com/NousResearch/hermes-agent/pull/1039))
|
||||
- Strip `call_id`/`response_item_id` for Mistral compatibility ([#1058](https://github.com/NousResearch/hermes-agent/pull/1058))
|
||||
|
||||
### Agent Loop & Conversation
|
||||
- **Anthropic Context Editing API** support ([#1147](https://github.com/NousResearch/hermes-agent/pull/1147))
|
||||
- Improved context compaction handoff summaries — compressor now preserves more actionable state ([#1273](https://github.com/NousResearch/hermes-agent/pull/1273))
|
||||
- Sync session_id after mid-run context compression ([#1160](https://github.com/NousResearch/hermes-agent/pull/1160))
|
||||
- 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))
|
||||
- Include session ID in system prompt via `--pass-session-id` flag ([#1040](https://github.com/NousResearch/hermes-agent/pull/1040))
|
||||
- Prevent closed OpenAI client reuse across retries ([#1391](https://github.com/NousResearch/hermes-agent/pull/1391))
|
||||
- Sanitize chat payloads and provider precedence ([#1253](https://github.com/NousResearch/hermes-agent/pull/1253))
|
||||
- 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))
|
||||
|
||||
### Memory & Sessions
|
||||
- **Improve memory prioritization** — user preferences and corrections weighted above procedural knowledge ([#1548](https://github.com/NousResearch/hermes-agent/pull/1548))
|
||||
- Tighter memory and session recall guidance in system prompts ([#1329](https://github.com/NousResearch/hermes-agent/pull/1329))
|
||||
- Persist CLI token counts to session DB for `/insights` ([#1498](https://github.com/NousResearch/hermes-agent/pull/1498))
|
||||
- Keep Honcho recall out of the cached system prefix ([#1201](https://github.com/NousResearch/hermes-agent/pull/1201))
|
||||
- Correct `seed_ai_identity` to use `session.add_messages()` ([#1475](https://github.com/NousResearch/hermes-agent/pull/1475))
|
||||
- Isolate Honcho session routing for multi-user gateway ([#1500](https://github.com/NousResearch/hermes-agent/pull/1500))
|
||||
|
||||
---
|
||||
|
||||
## 📱 Messaging Platforms (Gateway)
|
||||
|
||||
### Gateway Core
|
||||
- **System gateway service mode** — run as a system-level systemd service, not just user-level ([#1371](https://github.com/NousResearch/hermes-agent/pull/1371))
|
||||
- **Gateway install scope prompts** — choose user vs system scope during setup ([#1374](https://github.com/NousResearch/hermes-agent/pull/1374))
|
||||
- **Reasoning hot reload** — change reasoning settings without restarting the gateway ([#1275](https://github.com/NousResearch/hermes-agent/pull/1275))
|
||||
- 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))
|
||||
- Harden gateway restart recovery ([#1310](https://github.com/NousResearch/hermes-agent/pull/1310))
|
||||
- Cancel active runs during shutdown ([#1427](https://github.com/NousResearch/hermes-agent/pull/1427))
|
||||
- SSL certificate auto-detection for NixOS and non-standard systems ([#1494](https://github.com/NousResearch/hermes-agent/pull/1494))
|
||||
- Auto-detect D-Bus session bus for `systemctl --user` on headless servers ([#1601](https://github.com/NousResearch/hermes-agent/pull/1601))
|
||||
- Auto-enable systemd linger during gateway install on headless servers ([#1334](https://github.com/NousResearch/hermes-agent/pull/1334))
|
||||
- Fall back to module entrypoint when `hermes` is not on PATH ([#1355](https://github.com/NousResearch/hermes-agent/pull/1355))
|
||||
- Fix dual gateways on macOS launchd after `hermes update` ([#1567](https://github.com/NousResearch/hermes-agent/pull/1567))
|
||||
- Remove recursive ExecStop from systemd units ([#1530](https://github.com/NousResearch/hermes-agent/pull/1530))
|
||||
- Prevent logging handler accumulation in gateway mode ([#1251](https://github.com/NousResearch/hermes-agent/pull/1251))
|
||||
- Restart on retryable startup failures — by @jplew ([#1517](https://github.com/NousResearch/hermes-agent/pull/1517))
|
||||
- Backfill model on gateway sessions after agent runs ([#1306](https://github.com/NousResearch/hermes-agent/pull/1306))
|
||||
- PID-based gateway kill and deferred config write ([#1499](https://github.com/NousResearch/hermes-agent/pull/1499))
|
||||
|
||||
### Telegram
|
||||
- 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))
|
||||
- Retry on transient TLS failures during connect and send ([#1535](https://github.com/NousResearch/hermes-agent/pull/1535))
|
||||
- Harden polling conflict handling ([#1339](https://github.com/NousResearch/hermes-agent/pull/1339))
|
||||
- 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))
|
||||
- Check updater/app state before disconnect ([#1389](https://github.com/NousResearch/hermes-agent/pull/1389))
|
||||
|
||||
### Discord
|
||||
- `/thread` command with `auto_thread` config and media metadata fixes ([#1178](https://github.com/NousResearch/hermes-agent/pull/1178))
|
||||
- Auto-thread on @mention, skip mention text in bot threads ([#1438](https://github.com/NousResearch/hermes-agent/pull/1438))
|
||||
- Retry without reply reference for system messages ([#1385](https://github.com/NousResearch/hermes-agent/pull/1385))
|
||||
- Preserve native document and video attachment support ([#1392](https://github.com/NousResearch/hermes-agent/pull/1392))
|
||||
- Defer discord adapter annotations to avoid optional import crashes ([#1314](https://github.com/NousResearch/hermes-agent/pull/1314))
|
||||
|
||||
### Slack
|
||||
- Thread handling overhaul — progress messages, responses, and session isolation all respect threads ([#1103](https://github.com/NousResearch/hermes-agent/pull/1103))
|
||||
- Formatting, reactions, user resolution, and command improvements ([#1106](https://github.com/NousResearch/hermes-agent/pull/1106))
|
||||
- Fix MAX_MESSAGE_LENGTH 3900 → 39000 ([#1117](https://github.com/NousResearch/hermes-agent/pull/1117))
|
||||
- File upload fallback preserves thread context — by @0xbyt4 ([#1122](https://github.com/NousResearch/hermes-agent/pull/1122))
|
||||
- Improve setup guidance ([#1387](https://github.com/NousResearch/hermes-agent/pull/1387))
|
||||
|
||||
### Email
|
||||
- Fix IMAP UID tracking and SMTP TLS verification ([#1305](https://github.com/NousResearch/hermes-agent/pull/1305))
|
||||
- Add `skip_attachments` option via config.yaml ([#1536](https://github.com/NousResearch/hermes-agent/pull/1536))
|
||||
|
||||
### Home Assistant
|
||||
- Event filtering closed by default ([#1169](https://github.com/NousResearch/hermes-agent/pull/1169))
|
||||
|
||||
---
|
||||
|
||||
## 🖥️ CLI & User Experience
|
||||
|
||||
### Interactive CLI
|
||||
- **Persistent CLI status bar** — always-visible model, provider, and token counts ([#1522](https://github.com/NousResearch/hermes-agent/pull/1522))
|
||||
- **File path autocomplete** in the input prompt ([#1545](https://github.com/NousResearch/hermes-agent/pull/1545))
|
||||
- **`/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))
|
||||
- **Major `/rollback` improvements** — richer checkpoint history, clearer UX ([#1505](https://github.com/NousResearch/hermes-agent/pull/1505))
|
||||
- **Preload CLI skills on launch** — skills are ready before the first prompt ([#1359](https://github.com/NousResearch/hermes-agent/pull/1359))
|
||||
- **Centralized slash command registry** — all commands defined once, consumed everywhere ([#1603](https://github.com/NousResearch/hermes-agent/pull/1603))
|
||||
- `/bg` alias for `/background` ([#1590](https://github.com/NousResearch/hermes-agent/pull/1590))
|
||||
- Prefix matching for slash commands — `/mod` resolves to `/model` ([#1320](https://github.com/NousResearch/hermes-agent/pull/1320))
|
||||
- `/new`, `/reset`, `/clear` now start genuinely fresh sessions ([#1237](https://github.com/NousResearch/hermes-agent/pull/1237))
|
||||
- Accept session ID prefixes for session actions ([#1425](https://github.com/NousResearch/hermes-agent/pull/1425))
|
||||
- TUI prompt and accent output now respect active skin ([#1282](https://github.com/NousResearch/hermes-agent/pull/1282))
|
||||
- Centralize tool emoji metadata in registry + skin integration ([#1484](https://github.com/NousResearch/hermes-agent/pull/1484))
|
||||
- "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))
|
||||
- Non-blocking startup update check and banner deduplication ([#1386](https://github.com/NousResearch/hermes-agent/pull/1386))
|
||||
- `/reasoning` command output ordering and inline think extraction fixes ([#1031](https://github.com/NousResearch/hermes-agent/pull/1031))
|
||||
- Verbose mode shows full untruncated output ([#1472](https://github.com/NousResearch/hermes-agent/pull/1472))
|
||||
- Fix `/status` to report live state and tokens ([#1476](https://github.com/NousResearch/hermes-agent/pull/1476))
|
||||
- Seed a default global SOUL.md ([#1311](https://github.com/NousResearch/hermes-agent/pull/1311))
|
||||
|
||||
### Setup & Configuration
|
||||
- **OpenClaw migration** during first-time setup — by @kshitijk4poor ([#981](https://github.com/NousResearch/hermes-agent/pull/981))
|
||||
- `hermes claw migrate` command + migration docs ([#1059](https://github.com/NousResearch/hermes-agent/pull/1059))
|
||||
- Smart vision setup that respects the user's chosen provider ([#1323](https://github.com/NousResearch/hermes-agent/pull/1323))
|
||||
- Handle headless setup flows end-to-end ([#1274](https://github.com/NousResearch/hermes-agent/pull/1274))
|
||||
- Prefer curses over `simple_term_menu` in setup.py ([#1487](https://github.com/NousResearch/hermes-agent/pull/1487))
|
||||
- Show effective model and provider in `/status` ([#1284](https://github.com/NousResearch/hermes-agent/pull/1284))
|
||||
- Config set examples use placeholder syntax ([#1322](https://github.com/NousResearch/hermes-agent/pull/1322))
|
||||
- Reload .env over stale shell overrides ([#1434](https://github.com/NousResearch/hermes-agent/pull/1434))
|
||||
- Fix is_coding_plan NameError crash — by @0xbyt4 ([#1123](https://github.com/NousResearch/hermes-agent/pull/1123))
|
||||
- Add missing packages to setuptools config — by @alt-glitch ([#912](https://github.com/NousResearch/hermes-agent/pull/912))
|
||||
- Installer: clarify why sudo is needed at every prompt ([#1602](https://github.com/NousResearch/hermes-agent/pull/1602))
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Tool System
|
||||
|
||||
### Terminal & Execution
|
||||
- **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))
|
||||
- **Tirith pre-exec command scanning** — security layer that analyzes commands before execution ([#1256](https://github.com/NousResearch/hermes-agent/pull/1256))
|
||||
- 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
|
||||
- SSH preflight check ([#1486](https://github.com/NousResearch/hermes-agent/pull/1486))
|
||||
- Docker backend: make cwd workspace mount explicit opt-in ([#1534](https://github.com/NousResearch/hermes-agent/pull/1534))
|
||||
- Add project root to PYTHONPATH in execute_code sandbox ([#1383](https://github.com/NousResearch/hermes-agent/pull/1383))
|
||||
- Eliminate execute_code progress spam on gateway platforms ([#1098](https://github.com/NousResearch/hermes-agent/pull/1098))
|
||||
- Clearer docker backend preflight errors ([#1276](https://github.com/NousResearch/hermes-agent/pull/1276))
|
||||
|
||||
### Browser
|
||||
- **`/browser connect`** — attach browser tools to a live Chrome instance via CDP ([#1549](https://github.com/NousResearch/hermes-agent/pull/1549))
|
||||
- Improve browser cleanup, local browser PATH setup, and screenshot recovery ([#1333](https://github.com/NousResearch/hermes-agent/pull/1333))
|
||||
|
||||
### MCP
|
||||
- **Selective tool loading** with utility policies — filter which MCP tools are available ([#1302](https://github.com/NousResearch/hermes-agent/pull/1302))
|
||||
- Auto-reload MCP tools when `mcp_servers` config changes without restart ([#1474](https://github.com/NousResearch/hermes-agent/pull/1474))
|
||||
- Resolve npx stdio connection failures ([#1291](https://github.com/NousResearch/hermes-agent/pull/1291))
|
||||
- Preserve MCP toolsets when saving platform tool config ([#1421](https://github.com/NousResearch/hermes-agent/pull/1421))
|
||||
|
||||
### Vision
|
||||
- Unify vision backend gating ([#1367](https://github.com/NousResearch/hermes-agent/pull/1367))
|
||||
- Surface actual error reason instead of generic message ([#1338](https://github.com/NousResearch/hermes-agent/pull/1338))
|
||||
- Make Claude image handling work end-to-end ([#1408](https://github.com/NousResearch/hermes-agent/pull/1408))
|
||||
|
||||
### Cron
|
||||
- **Compress cron management into one tool** — single `cronjob` tool replaces multiple commands ([#1343](https://github.com/NousResearch/hermes-agent/pull/1343))
|
||||
- Suppress duplicate cron sends to auto-delivery targets ([#1357](https://github.com/NousResearch/hermes-agent/pull/1357))
|
||||
- Persist cron sessions to SQLite ([#1255](https://github.com/NousResearch/hermes-agent/pull/1255))
|
||||
- Per-job runtime overrides (provider, model, base_url) ([#1398](https://github.com/NousResearch/hermes-agent/pull/1398))
|
||||
- Atomic write in `save_job_output` to prevent data loss on crash ([#1173](https://github.com/NousResearch/hermes-agent/pull/1173))
|
||||
- Preserve thread context for `deliver=origin` ([#1437](https://github.com/NousResearch/hermes-agent/pull/1437))
|
||||
|
||||
### Patch Tool
|
||||
- Avoid corrupting pipe chars in V4A patch apply ([#1286](https://github.com/NousResearch/hermes-agent/pull/1286))
|
||||
- Permissive `block_anchor` thresholds and unicode normalization ([#1539](https://github.com/NousResearch/hermes-agent/pull/1539))
|
||||
|
||||
### Delegation
|
||||
- Add observability metadata to subagent results (model, tokens, duration, tool trace) ([#1175](https://github.com/NousResearch/hermes-agent/pull/1175))
|
||||
|
||||
---
|
||||
|
||||
## 🧩 Skills Ecosystem
|
||||
|
||||
### Skills System
|
||||
- **Integrate skills.sh** as a hub source alongside ClawHub ([#1303](https://github.com/NousResearch/hermes-agent/pull/1303))
|
||||
- Secure skill env setup on load ([#1153](https://github.com/NousResearch/hermes-agent/pull/1153))
|
||||
- Honor policy table for dangerous verdicts ([#1330](https://github.com/NousResearch/hermes-agent/pull/1330))
|
||||
- Harden ClawHub skill search exact matches ([#1400](https://github.com/NousResearch/hermes-agent/pull/1400))
|
||||
- Fix ClawHub skill install — use `/download` ZIP endpoint ([#1060](https://github.com/NousResearch/hermes-agent/pull/1060))
|
||||
- Avoid mislabeling local skills as builtin — by @arceus77-7 ([#862](https://github.com/NousResearch/hermes-agent/pull/862))
|
||||
|
||||
### New Skills
|
||||
- **Linear** project management ([#1230](https://github.com/NousResearch/hermes-agent/pull/1230))
|
||||
- **X/Twitter** via x-cli ([#1285](https://github.com/NousResearch/hermes-agent/pull/1285))
|
||||
- **Telephony** — Twilio, SMS, and AI calls ([#1289](https://github.com/NousResearch/hermes-agent/pull/1289))
|
||||
- **1Password** — by @arceus77-7 ([#883](https://github.com/NousResearch/hermes-agent/pull/883), [#1179](https://github.com/NousResearch/hermes-agent/pull/1179))
|
||||
- **NeuroSkill BCI** integration ([#1135](https://github.com/NousResearch/hermes-agent/pull/1135))
|
||||
- **Blender MCP** for 3D modeling ([#1531](https://github.com/NousResearch/hermes-agent/pull/1531))
|
||||
- **OSS Security Forensics** ([#1482](https://github.com/NousResearch/hermes-agent/pull/1482))
|
||||
- **Parallel CLI** research skill ([#1301](https://github.com/NousResearch/hermes-agent/pull/1301))
|
||||
- **OpenCode** CLI skill ([#1174](https://github.com/NousResearch/hermes-agent/pull/1174))
|
||||
- **ASCII Video** skill refactored — by @SHL0MS ([#1213](https://github.com/NousResearch/hermes-agent/pull/1213), [#1598](https://github.com/NousResearch/hermes-agent/pull/1598))
|
||||
|
||||
---
|
||||
|
||||
## 🎙️ Voice Mode
|
||||
|
||||
- Voice mode foundation — push-to-talk CLI, Telegram/Discord voice notes ([#1299](https://github.com/NousResearch/hermes-agent/pull/1299))
|
||||
- Free local Whisper transcription via faster-whisper ([#1185](https://github.com/NousResearch/hermes-agent/pull/1185))
|
||||
- Discord voice channel reliability fixes ([#1429](https://github.com/NousResearch/hermes-agent/pull/1429))
|
||||
- Restore local STT fallback for gateway voice notes ([#1490](https://github.com/NousResearch/hermes-agent/pull/1490))
|
||||
- Honor `stt.enabled: false` across gateway transcription ([#1394](https://github.com/NousResearch/hermes-agent/pull/1394))
|
||||
- Fix bogus incapability message on Telegram voice notes (Issue [#1033](https://github.com/NousResearch/hermes-agent/issues/1033))
|
||||
|
||||
---
|
||||
|
||||
## 🔌 ACP (IDE Integration)
|
||||
|
||||
- Restore ACP server implementation ([#1254](https://github.com/NousResearch/hermes-agent/pull/1254))
|
||||
- Support slash commands in ACP adapter ([#1532](https://github.com/NousResearch/hermes-agent/pull/1532))
|
||||
|
||||
---
|
||||
|
||||
## 🧪 RL Training
|
||||
|
||||
- **Agentic On-Policy Distillation (OPD)** environment — new RL training environment for agent policy distillation ([#1149](https://github.com/NousResearch/hermes-agent/pull/1149))
|
||||
- Make tinker-atropos RL training fully optional ([#1062](https://github.com/NousResearch/hermes-agent/pull/1062))
|
||||
|
||||
---
|
||||
|
||||
## 🔒 Security & Reliability
|
||||
|
||||
### Security Hardening
|
||||
- **Tirith pre-exec command scanning** — static analysis of terminal commands before execution ([#1256](https://github.com/NousResearch/hermes-agent/pull/1256))
|
||||
- **PII redaction** when `privacy.redact_pii` is enabled ([#1542](https://github.com/NousResearch/hermes-agent/pull/1542))
|
||||
- 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))
|
||||
- Docker cwd workspace mount now explicit opt-in — never auto-mount host directories ([#1534](https://github.com/NousResearch/hermes-agent/pull/1534))
|
||||
- Escape parens and braces in fork bomb regex pattern ([#1397](https://github.com/NousResearch/hermes-agent/pull/1397))
|
||||
- Harden `.worktreeinclude` path containment ([#1388](https://github.com/NousResearch/hermes-agent/pull/1388))
|
||||
- Use description as `pattern_key` to prevent approval collisions ([#1395](https://github.com/NousResearch/hermes-agent/pull/1395))
|
||||
|
||||
### Reliability
|
||||
- Guard init-time stdio writes ([#1271](https://github.com/NousResearch/hermes-agent/pull/1271))
|
||||
- Session log writes reuse shared atomic JSON helper ([#1280](https://github.com/NousResearch/hermes-agent/pull/1280))
|
||||
- Atomic temp cleanup protected on interrupts ([#1401](https://github.com/NousResearch/hermes-agent/pull/1401))
|
||||
|
||||
---
|
||||
|
||||
## 🐛 Notable Bug Fixes
|
||||
|
||||
- **`/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))
|
||||
- **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))
|
||||
- **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))
|
||||
- **`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))
|
||||
- **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))
|
||||
- **Background process lost on gateway restart** — improved recovery (Issue [#1144](https://github.com/NousResearch/hermes-agent/issues/1144))
|
||||
- **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))
|
||||
- **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))
|
||||
- **Gateway systemd service failing to auto-restart** when browser processes orphaned (Issue [#1617](https://github.com/NousResearch/hermes-agent/issues/1617))
|
||||
- **`/background` completion report cut off in Telegram** (Issue [#1443](https://github.com/NousResearch/hermes-agent/issues/1443))
|
||||
- **Model switching not taking effect** (Issue [#1244](https://github.com/NousResearch/hermes-agent/issues/1244), [#1183](https://github.com/NousResearch/hermes-agent/pull/1183))
|
||||
- **`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))
|
||||
- **WhatsApp bridge messages not received** from mobile (Issue [#1142](https://github.com/NousResearch/hermes-agent/issues/1142))
|
||||
- **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))
|
||||
- **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))
|
||||
- **Gateway NULL model in DB** (Issue [#987](https://github.com/NousResearch/hermes-agent/issues/987), [#1306](https://github.com/NousResearch/hermes-agent/pull/1306))
|
||||
- **Strict endpoints rejecting replayed tool_calls** (Issue [#893](https://github.com/NousResearch/hermes-agent/issues/893))
|
||||
- **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))
|
||||
- **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))
|
||||
- **Skills Guard blocking official skills** (Issue [#1006](https://github.com/NousResearch/hermes-agent/issues/1006), [#1330](https://github.com/NousResearch/hermes-agent/pull/1330))
|
||||
- **Setup writing provider before model selection** (Issue [#1182](https://github.com/NousResearch/hermes-agent/issues/1182))
|
||||
- **`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))
|
||||
- **`/update` hard-failing with "command not found"** (Issue [#1049](https://github.com/NousResearch/hermes-agent/issues/1049))
|
||||
- **Image analysis failing silently** (Issue [#1034](https://github.com/NousResearch/hermes-agent/issues/1034), [#1338](https://github.com/NousResearch/hermes-agent/pull/1338))
|
||||
- **API `BadRequestError` from `'dict'` object has no attribute `'strip'`** (Issue [#1071](https://github.com/NousResearch/hermes-agent/issues/1071))
|
||||
- **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))
|
||||
- **Gateway stops responding when terminal is closed on headless** (Issue [#1005](https://github.com/NousResearch/hermes-agent/issues/1005))
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Testing
|
||||
|
||||
- Cover empty cached Anthropic tool-call turns ([#1222](https://github.com/NousResearch/hermes-agent/pull/1222))
|
||||
- Fix stale CI assumptions in parser and quick-command coverage ([#1236](https://github.com/NousResearch/hermes-agent/pull/1236))
|
||||
- Fix gateway async tests without implicit event loop ([#1278](https://github.com/NousResearch/hermes-agent/pull/1278))
|
||||
- Make gateway async tests xdist-safe ([#1281](https://github.com/NousResearch/hermes-agent/pull/1281))
|
||||
- Cross-timezone naive timestamp regression for cron ([#1319](https://github.com/NousResearch/hermes-agent/pull/1319))
|
||||
- Isolate codex provider tests from local env ([#1335](https://github.com/NousResearch/hermes-agent/pull/1335))
|
||||
- Lock retry replacement semantics ([#1379](https://github.com/NousResearch/hermes-agent/pull/1379))
|
||||
- Improve error logging in session search tool — by @aydnOktay ([#1533](https://github.com/NousResearch/hermes-agent/pull/1533))
|
||||
|
||||
---
|
||||
|
||||
## 📚 Documentation
|
||||
|
||||
- Comprehensive SOUL.md guide ([#1315](https://github.com/NousResearch/hermes-agent/pull/1315))
|
||||
- Voice mode documentation ([#1316](https://github.com/NousResearch/hermes-agent/pull/1316), [#1362](https://github.com/NousResearch/hermes-agent/pull/1362))
|
||||
- Provider contribution guide ([#1361](https://github.com/NousResearch/hermes-agent/pull/1361))
|
||||
- ACP and internal systems implementation guides ([#1259](https://github.com/NousResearch/hermes-agent/pull/1259))
|
||||
- Expand Docusaurus coverage across CLI, tools, skills, and skins ([#1232](https://github.com/NousResearch/hermes-agent/pull/1232))
|
||||
- Terminal backend and Windows troubleshooting ([#1297](https://github.com/NousResearch/hermes-agent/pull/1297))
|
||||
- Skills hub reference section ([#1317](https://github.com/NousResearch/hermes-agent/pull/1317))
|
||||
- Checkpoint, /rollback, and git worktrees guide ([#1493](https://github.com/NousResearch/hermes-agent/pull/1493), [#1524](https://github.com/NousResearch/hermes-agent/pull/1524))
|
||||
- CLI status bar and /usage reference ([#1523](https://github.com/NousResearch/hermes-agent/pull/1523))
|
||||
- Fallback providers + /background command docs ([#1430](https://github.com/NousResearch/hermes-agent/pull/1430))
|
||||
- Gateway service scopes docs ([#1378](https://github.com/NousResearch/hermes-agent/pull/1378))
|
||||
- Slack thread reply behavior docs ([#1407](https://github.com/NousResearch/hermes-agent/pull/1407))
|
||||
- Redesigned landing page with Nous blue palette — by @austinpickett ([#974](https://github.com/NousResearch/hermes-agent/pull/974))
|
||||
- Fix several documentation typos — by @JackTheGit ([#953](https://github.com/NousResearch/hermes-agent/pull/953))
|
||||
- Stabilize website diagrams ([#1405](https://github.com/NousResearch/hermes-agent/pull/1405))
|
||||
- CLI vs messaging quick reference in README ([#1491](https://github.com/NousResearch/hermes-agent/pull/1491))
|
||||
- Add search to Docusaurus ([#1053](https://github.com/NousResearch/hermes-agent/pull/1053))
|
||||
- Home Assistant integration docs ([#1170](https://github.com/NousResearch/hermes-agent/pull/1170))
|
||||
|
||||
---
|
||||
|
||||
## 👥 Contributors
|
||||
|
||||
### Core
|
||||
- **@teknium1** — 220+ PRs spanning every area of the codebase
|
||||
|
||||
### Top Community Contributors
|
||||
|
||||
- **@0xbyt4** (4 PRs) — Anthropic adapter fixes (max_tokens, fallback crash, 429/529 retry), Slack file upload thread context, setup NameError fix
|
||||
- **@erosika** (1 PR) — Honcho memory integration: async writes, memory modes, session title integration
|
||||
- **@SHL0MS** (2 PRs) — ASCII video skill design patterns and refactoring
|
||||
- **@alt-glitch** (2 PRs) — Persistent shell mode for local/SSH backends, setuptools packaging fix
|
||||
- **@arceus77-7** (2 PRs) — 1Password skill, fix skills list mislabeling
|
||||
- **@kshitijk4poor** (1 PR) — OpenClaw migration during setup wizard
|
||||
- **@ASRagab** (1 PR) — Fix adaptive thinking for Claude 4.6 models
|
||||
- **@eren-karakus0** (1 PR) — Strip Hermes provider env vars from subprocess environment
|
||||
- **@mr-emmett-one** (1 PR) — Fix DeepSeek V3 parser multi-tool call support
|
||||
- **@jplew** (1 PR) — Gateway restart on retryable startup failures
|
||||
- **@brandtcormorant** (1 PR) — Fix Anthropic cache control for empty text blocks
|
||||
- **@aydnOktay** (1 PR) — Improve error logging in session search tool
|
||||
- **@austinpickett** (1 PR) — Landing page redesign with Nous blue palette
|
||||
- **@JackTheGit** (1 PR) — Documentation typo fixes
|
||||
|
||||
### All Contributors
|
||||
|
||||
@0xbyt4, @alt-glitch, @arceus77-7, @ASRagab, @austinpickett, @aydnOktay, @brandtcormorant, @eren-karakus0, @erosika, @JackTheGit, @jplew, @kshitijk4poor, @mr-emmett-one, @SHL0MS, @teknium1
|
||||
|
||||
---
|
||||
|
||||
**Full Changelog**: [v2026.3.12...v2026.3.17](https://github.com/NousResearch/hermes-agent/compare/v2026.3.12...v2026.3.17)
|
||||
@@ -1,400 +0,0 @@
|
||||
# Hermes Agent v0.4.0 (v2026.3.23)
|
||||
|
||||
**Release Date:** March 23, 2026
|
||||
|
||||
> 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.
|
||||
|
||||
---
|
||||
|
||||
## ✨ Highlights
|
||||
|
||||
- **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))
|
||||
|
||||
- **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))
|
||||
|
||||
- **@ 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))
|
||||
|
||||
- **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))
|
||||
|
||||
- **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))
|
||||
|
||||
- **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))
|
||||
|
||||
- **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))
|
||||
|
||||
- **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))
|
||||
|
||||
---
|
||||
|
||||
## 🖥️ CLI & User Experience
|
||||
|
||||
### New Commands & Interactions
|
||||
- **@ 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))
|
||||
- **`/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))
|
||||
- **`/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))
|
||||
- **`/permission`** — Switch approval mode dynamically during a session ([#2207](https://github.com/NousResearch/hermes-agent/pull/2207))
|
||||
- **`/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))
|
||||
- **`/cost`** — Live pricing and usage tracking in gateway mode ([#2180](https://github.com/NousResearch/hermes-agent/pull/2180))
|
||||
- **`/approve` and `/deny`** — Replaced bare text approval in gateway with explicit commands ([#2002](https://github.com/NousResearch/hermes-agent/pull/2002))
|
||||
|
||||
### Streaming & Display
|
||||
- Streaming enabled by default in CLI ([#2340](https://github.com/NousResearch/hermes-agent/pull/2340))
|
||||
- Show spinners and tool progress during streaming mode ([#2161](https://github.com/NousResearch/hermes-agent/pull/2161))
|
||||
- Show reasoning/thinking blocks when `show_reasoning` enabled ([#2118](https://github.com/NousResearch/hermes-agent/pull/2118))
|
||||
- Context pressure warnings for CLI and gateway ([#2159](https://github.com/NousResearch/hermes-agent/pull/2159))
|
||||
- Fix: streaming chunks concatenated without whitespace ([#2258](https://github.com/NousResearch/hermes-agent/pull/2258))
|
||||
- Fix: iteration boundary linebreak prevents stream concatenation ([#2413](https://github.com/NousResearch/hermes-agent/pull/2413))
|
||||
- Fix: defer streaming linebreak to prevent blank line stacking ([#2473](https://github.com/NousResearch/hermes-agent/pull/2473))
|
||||
- Fix: suppress spinner animation in non-TTY environments ([#2216](https://github.com/NousResearch/hermes-agent/pull/2216))
|
||||
- Fix: display provider and endpoint in API error messages ([#2266](https://github.com/NousResearch/hermes-agent/pull/2266))
|
||||
- Fix: resolve garbled ANSI escape codes in status printouts ([#2448](https://github.com/NousResearch/hermes-agent/pull/2448))
|
||||
- Fix: update gold ANSI color to true-color format ([#2246](https://github.com/NousResearch/hermes-agent/pull/2246))
|
||||
- Fix: normalize toolset labels and use skin colors in banner ([#1912](https://github.com/NousResearch/hermes-agent/pull/1912))
|
||||
|
||||
### CLI Polish
|
||||
- Fix: prevent 'Press ENTER to continue...' on exit ([#2555](https://github.com/NousResearch/hermes-agent/pull/2555))
|
||||
- Fix: flush stdout during agent loop to prevent macOS display freeze ([#1654](https://github.com/NousResearch/hermes-agent/pull/1654))
|
||||
- Fix: show human-readable error when `hermes setup` hits permissions error ([#2196](https://github.com/NousResearch/hermes-agent/pull/2196))
|
||||
- Fix: `/stop` command crash + UnboundLocalError in streaming media delivery ([#2463](https://github.com/NousResearch/hermes-agent/pull/2463))
|
||||
- Fix: allow custom/local endpoints without API key ([#2556](https://github.com/NousResearch/hermes-agent/pull/2556))
|
||||
- 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))
|
||||
|
||||
### Configuration
|
||||
- **`${ENV_VAR}` substitution** in config.yaml ([#2684](https://github.com/NousResearch/hermes-agent/pull/2684))
|
||||
- **Real-time config reload** — config.yaml changes apply without restart ([#2210](https://github.com/NousResearch/hermes-agent/pull/2210))
|
||||
- **`custom_models.yaml`** for user-managed model additions ([#2214](https://github.com/NousResearch/hermes-agent/pull/2214))
|
||||
- **Priority-based context file selection** + CLAUDE.md support ([#2301](https://github.com/NousResearch/hermes-agent/pull/2301))
|
||||
- **Merge nested YAML sections** instead of replacing on config update ([#2213](https://github.com/NousResearch/hermes-agent/pull/2213))
|
||||
- Fix: config.yaml provider key overrides env var silently ([#2272](https://github.com/NousResearch/hermes-agent/pull/2272))
|
||||
- Fix: log warning instead of silently swallowing config.yaml errors ([#2683](https://github.com/NousResearch/hermes-agent/pull/2683))
|
||||
- Fix: disabled toolsets re-enable themselves after `hermes tools` ([#2268](https://github.com/NousResearch/hermes-agent/pull/2268))
|
||||
- Fix: platform default toolsets silently override tool deselection ([#2624](https://github.com/NousResearch/hermes-agent/pull/2624))
|
||||
- Fix: honor bare YAML `approvals.mode: off` ([#2620](https://github.com/NousResearch/hermes-agent/pull/2620))
|
||||
- Fix: `hermes update` use `.[all]` extras with fallback ([#1728](https://github.com/NousResearch/hermes-agent/pull/1728))
|
||||
- Fix: `hermes update` prompt before resetting working tree on stash conflicts ([#2390](https://github.com/NousResearch/hermes-agent/pull/2390))
|
||||
- Fix: use git pull --rebase in update/install to avoid divergent branch error ([#2274](https://github.com/NousResearch/hermes-agent/pull/2274))
|
||||
- Fix: add zprofile fallback and create zshrc on fresh macOS installs ([#2320](https://github.com/NousResearch/hermes-agent/pull/2320))
|
||||
- Fix: remove `ANTHROPIC_BASE_URL` env var to avoid collisions ([#1675](https://github.com/NousResearch/hermes-agent/pull/1675))
|
||||
- Fix: don't ask IMAP password if already in keyring or env ([#2212](https://github.com/NousResearch/hermes-agent/pull/2212))
|
||||
- Fix: OpenCode Zen/Go show OpenRouter models instead of their own ([#2277](https://github.com/NousResearch/hermes-agent/pull/2277))
|
||||
|
||||
---
|
||||
|
||||
## 🏗️ Core Agent & Architecture
|
||||
|
||||
### New Providers
|
||||
- **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))
|
||||
- **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))
|
||||
- **Kilo Code** — First-class inference provider ([#1666](https://github.com/NousResearch/hermes-agent/pull/1666))
|
||||
- **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)
|
||||
- **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))
|
||||
|
||||
### Provider Improvements
|
||||
- **Eager fallback** to backup model on rate-limit errors ([#1730](https://github.com/NousResearch/hermes-agent/pull/1730))
|
||||
- **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)
|
||||
- **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))
|
||||
- **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))
|
||||
- **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))
|
||||
- Inject model and provider into system prompt ([#1929](https://github.com/NousResearch/hermes-agent/pull/1929))
|
||||
- Tie `api_mode` to provider config instead of env var ([#1656](https://github.com/NousResearch/hermes-agent/pull/1656))
|
||||
- Fix: prevent Anthropic token leaking to third-party `anthropic_messages` providers ([#2389](https://github.com/NousResearch/hermes-agent/pull/2389))
|
||||
- Fix: prevent Anthropic fallback from inheriting non-Anthropic `base_url` ([#2388](https://github.com/NousResearch/hermes-agent/pull/2388))
|
||||
- Fix: `auxiliary_is_nous` flag never resets — leaked Nous tags to other providers ([#1713](https://github.com/NousResearch/hermes-agent/pull/1713))
|
||||
- Fix: Anthropic `tool_choice 'none'` still allowed tool calls ([#1714](https://github.com/NousResearch/hermes-agent/pull/1714))
|
||||
- Fix: Mistral parser nested JSON fallback extraction ([#2335](https://github.com/NousResearch/hermes-agent/pull/2335))
|
||||
- Fix: MiniMax 401 auth resolved by defaulting to `anthropic_messages` ([#2103](https://github.com/NousResearch/hermes-agent/pull/2103))
|
||||
- Fix: case-insensitive model family matching ([#2350](https://github.com/NousResearch/hermes-agent/pull/2350))
|
||||
- Fix: ignore placeholder provider keys in activation checks ([#2358](https://github.com/NousResearch/hermes-agent/pull/2358))
|
||||
- Fix: Preserve Ollama model:tag colons in context length detection ([#2149](https://github.com/NousResearch/hermes-agent/pull/2149))
|
||||
- Fix: recognize Claude Code OAuth credentials in startup gate ([#1663](https://github.com/NousResearch/hermes-agent/pull/1663))
|
||||
- Fix: detect Claude Code version dynamically for OAuth user-agent ([#1670](https://github.com/NousResearch/hermes-agent/pull/1670))
|
||||
- Fix: OAuth flag stale after refresh/fallback ([#1890](https://github.com/NousResearch/hermes-agent/pull/1890))
|
||||
- Fix: auxiliary client skips expired Codex JWT ([#2397](https://github.com/NousResearch/hermes-agent/pull/2397))
|
||||
|
||||
### Agent Loop
|
||||
- **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))
|
||||
- **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))
|
||||
- **Pre-call sanitization and post-call tool guardrails** ([#1732](https://github.com/NousResearch/hermes-agent/pull/1732))
|
||||
- **Auto-recover** from provider-rejected `tool_choice` by retrying without ([#2174](https://github.com/NousResearch/hermes-agent/pull/2174))
|
||||
- **Background memory/skill review** replaces inline nudges ([#2235](https://github.com/NousResearch/hermes-agent/pull/2235))
|
||||
- **SOUL.md as primary agent identity** instead of hardcoded default ([#1922](https://github.com/NousResearch/hermes-agent/pull/1922))
|
||||
- Fix: prevent silent tool result loss during context compression ([#1993](https://github.com/NousResearch/hermes-agent/pull/1993))
|
||||
- Fix: handle empty/null function arguments in tool call recovery ([#2163](https://github.com/NousResearch/hermes-agent/pull/2163))
|
||||
- Fix: handle API refusal responses gracefully instead of crashing ([#2156](https://github.com/NousResearch/hermes-agent/pull/2156))
|
||||
- Fix: prevent stuck agent loop on malformed tool calls ([#2114](https://github.com/NousResearch/hermes-agent/pull/2114))
|
||||
- Fix: return JSON parse error to model instead of dispatching with empty args ([#2342](https://github.com/NousResearch/hermes-agent/pull/2342))
|
||||
- Fix: consecutive assistant message merge drops content on mixed types ([#1703](https://github.com/NousResearch/hermes-agent/pull/1703))
|
||||
- Fix: message role alternation violations in JSON recovery and error handler ([#1722](https://github.com/NousResearch/hermes-agent/pull/1722))
|
||||
- Fix: `compression_attempts` resets each iteration — allowed unlimited compressions ([#1723](https://github.com/NousResearch/hermes-agent/pull/1723))
|
||||
- Fix: `length_continue_retries` never resets — later truncations got fewer retries ([#1717](https://github.com/NousResearch/hermes-agent/pull/1717))
|
||||
- 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))
|
||||
- Fix: remove hardcoded `gemini-3-flash-preview` as default summary model ([#2464](https://github.com/NousResearch/hermes-agent/pull/2464))
|
||||
- Fix: correctly handle empty tool results ([#2201](https://github.com/NousResearch/hermes-agent/pull/2201))
|
||||
- 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))
|
||||
- Fix: per-thread persistent event loops in worker threads ([#2214](https://github.com/NousResearch/hermes-agent/pull/2214) by @jquesnelle)
|
||||
- Fix: prevent 'event loop already running' when async tools run in parallel ([#2207](https://github.com/NousResearch/hermes-agent/pull/2207))
|
||||
- Fix: strip ANSI at the source — clean terminal output before it reaches the model ([#2115](https://github.com/NousResearch/hermes-agent/pull/2115))
|
||||
- Fix: skip top-level `cache_control` on role:tool for OpenRouter ([#2391](https://github.com/NousResearch/hermes-agent/pull/2391))
|
||||
- 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))
|
||||
- Fix: only strip last assistant message if empty string ([#2326](https://github.com/NousResearch/hermes-agent/pull/2326))
|
||||
|
||||
### Session & Memory
|
||||
- **Session search** and management slash commands ([#2198](https://github.com/NousResearch/hermes-agent/pull/2198))
|
||||
- **Auto session titles** and `.hermes.md` project config ([#1712](https://github.com/NousResearch/hermes-agent/pull/1712))
|
||||
- Fix: concurrent memory writes silently drop entries — added file locking ([#1726](https://github.com/NousResearch/hermes-agent/pull/1726))
|
||||
- Fix: search all sources by default in `session_search` ([#1892](https://github.com/NousResearch/hermes-agent/pull/1892))
|
||||
- Fix: handle hyphenated FTS5 queries and preserve quoted literals ([#1776](https://github.com/NousResearch/hermes-agent/pull/1776))
|
||||
- Fix: skip corrupt lines in `load_transcript` instead of crashing ([#1744](https://github.com/NousResearch/hermes-agent/pull/1744))
|
||||
- Fix: normalize session keys to prevent case-sensitive duplicates ([#2157](https://github.com/NousResearch/hermes-agent/pull/2157))
|
||||
- Fix: prevent `session_search` crash when no sessions exist ([#2194](https://github.com/NousResearch/hermes-agent/pull/2194))
|
||||
- Fix: reset token counters on new session for accurate usage display ([#2101](https://github.com/NousResearch/hermes-agent/pull/2101) by @InB4DevOps)
|
||||
- Fix: prevent stale memory overwrites by flush agent ([#2687](https://github.com/NousResearch/hermes-agent/pull/2687))
|
||||
- Fix: remove synthetic error message injection, fix session resume after repeated failures ([#2303](https://github.com/NousResearch/hermes-agent/pull/2303))
|
||||
- Fix: quiet mode with `--resume` now passes conversation_history ([#2357](https://github.com/NousResearch/hermes-agent/pull/2357))
|
||||
- Fix: unify resume logic in batch mode ([#2331](https://github.com/NousResearch/hermes-agent/pull/2331))
|
||||
|
||||
### Honcho Memory
|
||||
- Honcho config fixes and @ context reference integration ([#2343](https://github.com/NousResearch/hermes-agent/pull/2343))
|
||||
- Self-hosted / Docker configuration documentation ([#2475](https://github.com/NousResearch/hermes-agent/pull/2475))
|
||||
|
||||
---
|
||||
|
||||
## 📱 Messaging Platforms (Gateway)
|
||||
|
||||
### New Platform Adapters
|
||||
- **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))
|
||||
- **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))
|
||||
- **SMS (Twilio)** ([#1688](https://github.com/NousResearch/hermes-agent/pull/1688))
|
||||
- **Mattermost** — With @-mention-only channel filter ([#1683](https://github.com/NousResearch/hermes-agent/pull/1683), [#2443](https://github.com/NousResearch/hermes-agent/pull/2443))
|
||||
- **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))
|
||||
- **Webhook** — Platform adapter for external event triggers ([#2166](https://github.com/NousResearch/hermes-agent/pull/2166))
|
||||
- **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))
|
||||
|
||||
### Telegram Improvements
|
||||
- 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))
|
||||
- Auto-detect HTML tags and use `parse_mode=HTML` ([#1709](https://github.com/NousResearch/hermes-agent/pull/1709))
|
||||
- Telegram group vision support + thread-based sessions ([#2153](https://github.com/NousResearch/hermes-agent/pull/2153))
|
||||
- Auto-reconnect polling after network interruption ([#2517](https://github.com/NousResearch/hermes-agent/pull/2517))
|
||||
- Aggregate split text messages before dispatching ([#1674](https://github.com/NousResearch/hermes-agent/pull/1674))
|
||||
- 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))
|
||||
- Fix: edited_message event crashes ([#2074](https://github.com/NousResearch/hermes-agent/pull/2074))
|
||||
- Fix: retry 409 polling conflicts before giving up ([#2312](https://github.com/NousResearch/hermes-agent/pull/2312))
|
||||
- Fix: topic delivery via `platform:chat_id:thread_id` format ([#2455](https://github.com/NousResearch/hermes-agent/pull/2455))
|
||||
|
||||
### Discord Improvements
|
||||
- Document caching and text-file injection ([#2503](https://github.com/NousResearch/hermes-agent/pull/2503))
|
||||
- Persistent typing indicator for DMs ([#2468](https://github.com/NousResearch/hermes-agent/pull/2468))
|
||||
- Discord DM vision — inline images + attachment analysis ([#2186](https://github.com/NousResearch/hermes-agent/pull/2186))
|
||||
- Persist thread participation across gateway restarts ([#1661](https://github.com/NousResearch/hermes-agent/pull/1661))
|
||||
- Fix: gateway crash on non-ASCII guild names ([#2302](https://github.com/NousResearch/hermes-agent/pull/2302))
|
||||
- Fix: thread permission errors ([#2073](https://github.com/NousResearch/hermes-agent/pull/2073))
|
||||
- Fix: slash event routing in threads ([#2460](https://github.com/NousResearch/hermes-agent/pull/2460))
|
||||
- Fix: remove bugged followup messages + `/ask` command ([#1836](https://github.com/NousResearch/hermes-agent/pull/1836))
|
||||
- Fix: graceful WebSocket reconnection ([#2127](https://github.com/NousResearch/hermes-agent/pull/2127))
|
||||
- Fix: voice channel TTS when streaming enabled ([#2322](https://github.com/NousResearch/hermes-agent/pull/2322))
|
||||
|
||||
### WhatsApp & Other Adapters
|
||||
- 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))
|
||||
- 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))
|
||||
- Mattermost: MIME types for media attachments ([#2329](https://github.com/NousResearch/hermes-agent/pull/2329))
|
||||
|
||||
### Gateway Core
|
||||
- **Auto-reconnect** failed platforms with exponential backoff ([#2584](https://github.com/NousResearch/hermes-agent/pull/2584))
|
||||
- **Notify users when session auto-resets** ([#2519](https://github.com/NousResearch/hermes-agent/pull/2519))
|
||||
- **Reply-to message context** for out-of-session replies ([#1662](https://github.com/NousResearch/hermes-agent/pull/1662))
|
||||
- **Ignore unauthorized DMs** config option ([#1919](https://github.com/NousResearch/hermes-agent/pull/1919))
|
||||
- Fix: `/reset` in thread-mode resets global session instead of thread ([#2254](https://github.com/NousResearch/hermes-agent/pull/2254))
|
||||
- Fix: deliver MEDIA: files after streaming responses ([#2382](https://github.com/NousResearch/hermes-agent/pull/2382))
|
||||
- Fix: cap interrupt recursion depth to prevent resource exhaustion ([#1659](https://github.com/NousResearch/hermes-agent/pull/1659))
|
||||
- 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))
|
||||
- Fix: PID-based wait with force-kill for gateway restart ([#1902](https://github.com/NousResearch/hermes-agent/pull/1902))
|
||||
- Fix: prevent `--replace` mode from killing the caller process ([#2185](https://github.com/NousResearch/hermes-agent/pull/2185))
|
||||
- Fix: `/model` shows active fallback model instead of config default ([#1660](https://github.com/NousResearch/hermes-agent/pull/1660))
|
||||
- Fix: `/title` command fails when session doesn't exist in SQLite yet ([#2379](https://github.com/NousResearch/hermes-agent/pull/2379) by @ten-jampa)
|
||||
- Fix: process `/queue`'d messages after agent completion ([#2469](https://github.com/NousResearch/hermes-agent/pull/2469))
|
||||
- Fix: strip orphaned `tool_results` + let `/reset` bypass running agent ([#2180](https://github.com/NousResearch/hermes-agent/pull/2180))
|
||||
- Fix: prevent agents from starting gateway outside systemd management ([#2617](https://github.com/NousResearch/hermes-agent/pull/2617))
|
||||
- Fix: prevent systemd restart storm on gateway connection failure ([#2327](https://github.com/NousResearch/hermes-agent/pull/2327))
|
||||
- Fix: include resolved node path in systemd unit ([#1767](https://github.com/NousResearch/hermes-agent/pull/1767) by @sai-samarth)
|
||||
- Fix: send error details to user in gateway outer exception handler ([#1966](https://github.com/NousResearch/hermes-agent/pull/1966))
|
||||
- Fix: improve error handling for 429 usage limits and 500 context overflow ([#1839](https://github.com/NousResearch/hermes-agent/pull/1839))
|
||||
- Fix: add all missing platform allowlist env vars to startup warning check ([#2628](https://github.com/NousResearch/hermes-agent/pull/2628))
|
||||
- Fix: media delivery fails for file paths containing spaces ([#2621](https://github.com/NousResearch/hermes-agent/pull/2621))
|
||||
- Fix: duplicate session-key collision in multi-platform gateway ([#2171](https://github.com/NousResearch/hermes-agent/pull/2171))
|
||||
- Fix: Matrix and Mattermost never report as connected ([#1711](https://github.com/NousResearch/hermes-agent/pull/1711))
|
||||
- Fix: PII redaction config never read — missing yaml import ([#1701](https://github.com/NousResearch/hermes-agent/pull/1701))
|
||||
- Fix: NameError on skill slash commands ([#1697](https://github.com/NousResearch/hermes-agent/pull/1697))
|
||||
- Fix: persist watcher metadata in checkpoint for crash recovery ([#1706](https://github.com/NousResearch/hermes-agent/pull/1706))
|
||||
- Fix: pass `message_thread_id` in send_image_file, send_document, send_video ([#2339](https://github.com/NousResearch/hermes-agent/pull/2339))
|
||||
- Fix: media-group aggregation on rapid successive photo messages ([#2160](https://github.com/NousResearch/hermes-agent/pull/2160))
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Tool System
|
||||
|
||||
### MCP Enhancements
|
||||
- **MCP server management CLI** + OAuth 2.1 PKCE auth ([#2465](https://github.com/NousResearch/hermes-agent/pull/2465))
|
||||
- **Expose MCP servers as standalone toolsets** ([#1907](https://github.com/NousResearch/hermes-agent/pull/1907))
|
||||
- **Interactive MCP tool configuration** in `hermes tools` ([#1694](https://github.com/NousResearch/hermes-agent/pull/1694))
|
||||
- Fix: MCP-OAuth port mismatch, path traversal, and shared handler state ([#2552](https://github.com/NousResearch/hermes-agent/pull/2552))
|
||||
- Fix: preserve MCP tool registrations across session resets ([#2124](https://github.com/NousResearch/hermes-agent/pull/2124))
|
||||
- Fix: concurrent file access crash + duplicate MCP registration ([#2154](https://github.com/NousResearch/hermes-agent/pull/2154))
|
||||
- Fix: normalise MCP schemas + expand session list columns ([#2102](https://github.com/NousResearch/hermes-agent/pull/2102))
|
||||
- Fix: `tool_choice` `mcp_` prefix handling ([#1775](https://github.com/NousResearch/hermes-agent/pull/1775))
|
||||
|
||||
### Web Tool Backends
|
||||
- **Tavily** as web search/extract/crawl backend ([#1731](https://github.com/NousResearch/hermes-agent/pull/1731))
|
||||
- **Parallel** as alternative web search/extract backend ([#1696](https://github.com/NousResearch/hermes-agent/pull/1696))
|
||||
- **Configurable web backend** — Firecrawl/BeautifulSoup/Playwright selection ([#2256](https://github.com/NousResearch/hermes-agent/pull/2256))
|
||||
- Fix: whitespace-only env vars bypass web backend detection ([#2341](https://github.com/NousResearch/hermes-agent/pull/2341))
|
||||
|
||||
### New Tools
|
||||
- **IMAP email** reading and sending ([#2173](https://github.com/NousResearch/hermes-agent/pull/2173))
|
||||
- **STT (speech-to-text)** tool using Whisper API ([#2072](https://github.com/NousResearch/hermes-agent/pull/2072))
|
||||
- **Route-aware pricing estimates** ([#1695](https://github.com/NousResearch/hermes-agent/pull/1695))
|
||||
|
||||
### Tool Improvements
|
||||
- TTS: `base_url` support for OpenAI TTS provider ([#2064](https://github.com/NousResearch/hermes-agent/pull/2064) by @hanai)
|
||||
- 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))
|
||||
- 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))
|
||||
- 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))
|
||||
- Code execution: resource leak and double socket close fix ([#2381](https://github.com/NousResearch/hermes-agent/pull/2381))
|
||||
- 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))
|
||||
- Fix: make concurrent tool batching path-aware for file mutations ([#1914](https://github.com/NousResearch/hermes-agent/pull/1914))
|
||||
- Fix: chunk long messages in `send_message_tool` before platform dispatch ([#1646](https://github.com/NousResearch/hermes-agent/pull/1646))
|
||||
- Fix: add missing 'messaging' toolset ([#1718](https://github.com/NousResearch/hermes-agent/pull/1718))
|
||||
- Fix: prevent unavailable tool names from leaking into model schemas ([#2072](https://github.com/NousResearch/hermes-agent/pull/2072))
|
||||
- Fix: pass visited set by reference to prevent diamond dependency duplication ([#2311](https://github.com/NousResearch/hermes-agent/pull/2311))
|
||||
- Fix: Daytona sandbox lookup migrated from `find_one` to `get/list` ([#2063](https://github.com/NousResearch/hermes-agent/pull/2063) by @rovle)
|
||||
|
||||
---
|
||||
|
||||
## 🧩 Skills Ecosystem
|
||||
|
||||
### Skills System Improvements
|
||||
- **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))
|
||||
- **`--yes` flag** to bypass confirmation in `/skills install` and uninstall ([#1647](https://github.com/NousResearch/hermes-agent/pull/1647))
|
||||
- **Disabled skills respected** across banner, system prompt, and slash commands ([#1897](https://github.com/NousResearch/hermes-agent/pull/1897))
|
||||
- Fix: skills custom_tools import crash + sandbox file_tools integration ([#2239](https://github.com/NousResearch/hermes-agent/pull/2239))
|
||||
- Fix: agent-created skills with pip requirements crash on install ([#2145](https://github.com/NousResearch/hermes-agent/pull/2145))
|
||||
- Fix: race condition in `Skills.__init__` when `hub.yaml` missing ([#2242](https://github.com/NousResearch/hermes-agent/pull/2242))
|
||||
- Fix: validate skill metadata before install and block duplicates ([#2241](https://github.com/NousResearch/hermes-agent/pull/2241))
|
||||
- Fix: skills hub inspect/resolve — 4 bugs in inspect, redirects, discovery, tap list ([#2447](https://github.com/NousResearch/hermes-agent/pull/2447))
|
||||
- Fix: agent-created skills keep working after session reset ([#2121](https://github.com/NousResearch/hermes-agent/pull/2121))
|
||||
|
||||
### New Skills
|
||||
- **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))
|
||||
- **Huggingface-hub** bundled skill ([#1921](https://github.com/NousResearch/hermes-agent/pull/1921))
|
||||
- **Sherlock OSINT** username search ([#1671](https://github.com/NousResearch/hermes-agent/pull/1671))
|
||||
- **Meme-generation** — Image generator with Pillow ([#2344](https://github.com/NousResearch/hermes-agent/pull/2344))
|
||||
- **Bioinformatics** gateway skill — index to 400+ bio skills ([#2387](https://github.com/NousResearch/hermes-agent/pull/2387))
|
||||
- **Inference.sh** skill (terminal-based) ([#1686](https://github.com/NousResearch/hermes-agent/pull/1686))
|
||||
- **Base blockchain** optional skill ([#1643](https://github.com/NousResearch/hermes-agent/pull/1643))
|
||||
- **3D-model-viewer** optional skill ([#2226](https://github.com/NousResearch/hermes-agent/pull/2226))
|
||||
- **FastMCP** optional skill ([#2113](https://github.com/NousResearch/hermes-agent/pull/2113))
|
||||
- **Hermes-agent-setup** skill ([#1905](https://github.com/NousResearch/hermes-agent/pull/1905))
|
||||
|
||||
---
|
||||
|
||||
## 🔌 Plugin System Enhancements
|
||||
|
||||
- **TUI extension hooks** — Build custom CLIs on top of Hermes ([#2333](https://github.com/NousResearch/hermes-agent/pull/2333))
|
||||
- **`hermes plugins install/remove/list`** commands ([#2337](https://github.com/NousResearch/hermes-agent/pull/2337))
|
||||
- **Slash command registration** for plugins ([#2359](https://github.com/NousResearch/hermes-agent/pull/2359))
|
||||
- **`session:end` lifecycle event** hook ([#1725](https://github.com/NousResearch/hermes-agent/pull/1725))
|
||||
- Fix: require opt-in for project plugin discovery ([#2215](https://github.com/NousResearch/hermes-agent/pull/2215))
|
||||
|
||||
---
|
||||
|
||||
## 🔒 Security & Reliability
|
||||
|
||||
### Security
|
||||
- **SSRF protection** for vision_tools and web_tools ([#2679](https://github.com/NousResearch/hermes-agent/pull/2679))
|
||||
- **Shell injection prevention** in `_expand_path` via `~user` path suffix ([#2685](https://github.com/NousResearch/hermes-agent/pull/2685))
|
||||
- **Block untrusted browser-origin** API server access ([#2451](https://github.com/NousResearch/hermes-agent/pull/2451))
|
||||
- **Block sandbox backend creds** from subprocess env ([#1658](https://github.com/NousResearch/hermes-agent/pull/1658))
|
||||
- **Block @ references** from reading secrets outside workspace ([#2601](https://github.com/NousResearch/hermes-agent/pull/2601) by @Gutslabs)
|
||||
- **Malicious code pattern pre-exec scanner** for terminal_tool ([#2245](https://github.com/NousResearch/hermes-agent/pull/2245))
|
||||
- **Harden terminal safety** and sandbox file writes ([#1653](https://github.com/NousResearch/hermes-agent/pull/1653))
|
||||
- **PKCE verifier leak** fix + OAuth refresh Content-Type ([#1775](https://github.com/NousResearch/hermes-agent/pull/1775))
|
||||
- **Eliminate SQL string formatting** in `execute()` calls ([#2061](https://github.com/NousResearch/hermes-agent/pull/2061) by @dusterbloom)
|
||||
- **Harden jobs API** — input limits, field whitelist, startup check ([#2456](https://github.com/NousResearch/hermes-agent/pull/2456))
|
||||
|
||||
### Reliability
|
||||
- Thread locks on 4 SessionDB methods ([#1704](https://github.com/NousResearch/hermes-agent/pull/1704))
|
||||
- File locking for concurrent memory writes ([#1726](https://github.com/NousResearch/hermes-agent/pull/1726))
|
||||
- Handle OpenRouter errors gracefully ([#2112](https://github.com/NousResearch/hermes-agent/pull/2112))
|
||||
- Guard print() calls against OSError ([#1668](https://github.com/NousResearch/hermes-agent/pull/1668))
|
||||
- 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))
|
||||
- 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))
|
||||
- API server: persist ResponseStore to SQLite across restarts ([#2472](https://github.com/NousResearch/hermes-agent/pull/2472))
|
||||
- Fix: `fetch_nous_models` always TypeError from positional args ([#1699](https://github.com/NousResearch/hermes-agent/pull/1699))
|
||||
- Fix: resolve merge conflict markers in cli.py breaking startup ([#2347](https://github.com/NousResearch/hermes-agent/pull/2347))
|
||||
- Fix: `minisweagent_path.py` missing from wheel ([#2098](https://github.com/NousResearch/hermes-agent/pull/2098) by @JiwaniZakir)
|
||||
|
||||
### Cron System
|
||||
- **`[SILENT]` response** — cron agents can suppress delivery ([#1833](https://github.com/NousResearch/hermes-agent/pull/1833))
|
||||
- **Scale missed-job grace window** with schedule frequency ([#2449](https://github.com/NousResearch/hermes-agent/pull/2449))
|
||||
- **Recover recent one-shot jobs** ([#1918](https://github.com/NousResearch/hermes-agent/pull/1918))
|
||||
- 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)
|
||||
- Fix: Matrix added to scheduler delivery platform_map ([#2167](https://github.com/NousResearch/hermes-agent/pull/2167) by @buntingszn)
|
||||
- Fix: naive ISO timestamps without timezone — jobs fire at wrong time ([#1729](https://github.com/NousResearch/hermes-agent/pull/1729))
|
||||
- Fix: `get_due_jobs` reads `jobs.json` twice — race condition ([#1716](https://github.com/NousResearch/hermes-agent/pull/1716))
|
||||
- Fix: silent jobs return empty response for delivery skip ([#2442](https://github.com/NousResearch/hermes-agent/pull/2442))
|
||||
- Fix: stop injecting cron outputs into gateway session history ([#2313](https://github.com/NousResearch/hermes-agent/pull/2313))
|
||||
- Fix: close abandoned coroutine when `asyncio.run()` raises RuntimeError ([#2317](https://github.com/NousResearch/hermes-agent/pull/2317))
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Testing
|
||||
|
||||
- Resolve all consistently failing tests ([#2488](https://github.com/NousResearch/hermes-agent/pull/2488))
|
||||
- Replace `FakePath` with `monkeypatch` for Python 3.12 compat ([#2444](https://github.com/NousResearch/hermes-agent/pull/2444))
|
||||
- Align Hermes setup and full-suite expectations ([#1710](https://github.com/NousResearch/hermes-agent/pull/1710))
|
||||
|
||||
---
|
||||
|
||||
## 📚 Documentation
|
||||
|
||||
- Comprehensive docs update for recent features ([#1693](https://github.com/NousResearch/hermes-agent/pull/1693), [#2183](https://github.com/NousResearch/hermes-agent/pull/2183))
|
||||
- Alibaba Cloud and DingTalk setup guides ([#1687](https://github.com/NousResearch/hermes-agent/pull/1687), [#1692](https://github.com/NousResearch/hermes-agent/pull/1692))
|
||||
- Detailed skills documentation ([#2244](https://github.com/NousResearch/hermes-agent/pull/2244))
|
||||
- Honcho self-hosted / Docker configuration ([#2475](https://github.com/NousResearch/hermes-agent/pull/2475))
|
||||
- Context length detection FAQ and quickstart references ([#2179](https://github.com/NousResearch/hermes-agent/pull/2179))
|
||||
- Fix docs inconsistencies across reference and user guides ([#1995](https://github.com/NousResearch/hermes-agent/pull/1995))
|
||||
- Fix MCP install commands — use uv, not bare pip ([#1909](https://github.com/NousResearch/hermes-agent/pull/1909))
|
||||
- Replace ASCII diagrams with Mermaid/lists ([#2402](https://github.com/NousResearch/hermes-agent/pull/2402))
|
||||
- Gemini OAuth provider implementation plan ([#2467](https://github.com/NousResearch/hermes-agent/pull/2467))
|
||||
- Discord Server Members Intent marked as required ([#2330](https://github.com/NousResearch/hermes-agent/pull/2330))
|
||||
- Fix MDX build error in api-server.md ([#1787](https://github.com/NousResearch/hermes-agent/pull/1787))
|
||||
- Align venv path to match installer ([#2114](https://github.com/NousResearch/hermes-agent/pull/2114))
|
||||
- New skills added to hub index ([#2281](https://github.com/NousResearch/hermes-agent/pull/2281))
|
||||
|
||||
---
|
||||
|
||||
## 👥 Contributors
|
||||
|
||||
### Core
|
||||
- **@teknium1** (Teknium) — 280 PRs
|
||||
|
||||
### Community Contributors
|
||||
- **@mchzimm** (to_the_max) — GitHub Copilot provider integration ([#1879](https://github.com/NousResearch/hermes-agent/pull/1879))
|
||||
- **@jquesnelle** (Jeffrey Quesnelle) — Per-thread persistent event loops fix ([#2214](https://github.com/NousResearch/hermes-agent/pull/2214))
|
||||
- **@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))
|
||||
- **@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))
|
||||
- **@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))
|
||||
- **@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))
|
||||
- **@Gutslabs** (Guts) — Block @ references from reading secrets ([#2601](https://github.com/NousResearch/hermes-agent/pull/2601))
|
||||
- **@Mibayy** (Mibay) — Cron job repeat normalization ([#2612](https://github.com/NousResearch/hermes-agent/pull/2612))
|
||||
- **@ten-jampa** (Tenzin Jampa) — Gateway /title command fix ([#2379](https://github.com/NousResearch/hermes-agent/pull/2379))
|
||||
- **@cutepawss** (lila) — File tools search pagination fix ([#1824](https://github.com/NousResearch/hermes-agent/pull/1824))
|
||||
- **@hanai** (Hanai) — OpenAI TTS base_url support ([#2064](https://github.com/NousResearch/hermes-agent/pull/2064))
|
||||
- **@rovle** (Lovre Pešut) — Daytona sandbox API migration ([#2063](https://github.com/NousResearch/hermes-agent/pull/2063))
|
||||
- **@buntingszn** (bunting szn) — Matrix cron delivery support ([#2167](https://github.com/NousResearch/hermes-agent/pull/2167))
|
||||
- **@InB4DevOps** — Token counter reset on new session ([#2101](https://github.com/NousResearch/hermes-agent/pull/2101))
|
||||
- **@JiwaniZakir** (Zakir Jiwani) — Missing file in wheel fix ([#2098](https://github.com/NousResearch/hermes-agent/pull/2098))
|
||||
- **@ygd58** (buray) — Delegate tool parent tool names fix ([#2083](https://github.com/NousResearch/hermes-agent/pull/2083))
|
||||
|
||||
---
|
||||
|
||||
**Full Changelog**: [v2026.3.17...v2026.3.23](https://github.com/NousResearch/hermes-agent/compare/v2026.3.17...v2026.3.23)
|
||||
@@ -1,348 +0,0 @@
|
||||
# Hermes Agent v0.5.0 (v2026.3.28)
|
||||
|
||||
**Release Date:** March 28, 2026
|
||||
|
||||
> 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.
|
||||
|
||||
---
|
||||
|
||||
## ✨ Highlights
|
||||
|
||||
- **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
|
||||
|
||||
- **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))
|
||||
|
||||
- **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))
|
||||
|
||||
- **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))
|
||||
|
||||
- **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))
|
||||
|
||||
- **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))
|
||||
|
||||
- **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
|
||||
|
||||
- **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))
|
||||
|
||||
- **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))
|
||||
|
||||
---
|
||||
|
||||
## 🏗️ Core Agent & Architecture
|
||||
|
||||
### New Provider: Hugging Face
|
||||
- First-class Hugging Face Inference API integration with auth, setup wizard, and model picker ([#3419](https://github.com/NousResearch/hermes-agent/pull/3419))
|
||||
- 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))
|
||||
- Added glm-5-turbo to Z.AI provider model list ([#3095](https://github.com/NousResearch/hermes-agent/pull/3095))
|
||||
|
||||
### Provider & Model Improvements
|
||||
- `/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))
|
||||
- Removed `/model` slash command from CLI and gateway in favor of `hermes model` subcommand ([#3080](https://github.com/NousResearch/hermes-agent/pull/3080))
|
||||
- Preserve `custom` provider instead of silently remapping to `openrouter` ([#2792](https://github.com/NousResearch/hermes-agent/pull/2792))
|
||||
- Read root-level `provider` and `base_url` from config.yaml into model config ([#3112](https://github.com/NousResearch/hermes-agent/pull/3112))
|
||||
- Align Nous Portal model slugs with OpenRouter naming ([#3253](https://github.com/NousResearch/hermes-agent/pull/3253))
|
||||
- Fix Alibaba provider default endpoint and model list ([#3484](https://github.com/NousResearch/hermes-agent/pull/3484))
|
||||
- Allow MiniMax users to override `/v1` → `/anthropic` auto-correction ([#3553](https://github.com/NousResearch/hermes-agent/pull/3553))
|
||||
- Migrate OAuth token refresh to `platform.claude.com` with fallback ([#3246](https://github.com/NousResearch/hermes-agent/pull/3246))
|
||||
|
||||
### Agent Loop & Conversation
|
||||
- **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))
|
||||
- **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))
|
||||
- **Anthropic output limits** — Per-model native output limits instead of hardcoded 16K `max_tokens` ([#3426](https://github.com/NousResearch/hermes-agent/pull/3426))
|
||||
- **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))
|
||||
- Always prefer streaming for API calls to prevent hung subagents ([#3120](https://github.com/NousResearch/hermes-agent/pull/3120))
|
||||
- Restore safe non-streaming fallback after stream failures ([#3020](https://github.com/NousResearch/hermes-agent/pull/3020))
|
||||
- Give subagents independent iteration budgets ([#3004](https://github.com/NousResearch/hermes-agent/pull/3004))
|
||||
- Update `api_key` in `_try_activate_fallback` for subagent auth ([#3103](https://github.com/NousResearch/hermes-agent/pull/3103))
|
||||
- Graceful return on max retries instead of crashing thread ([untagged commit](https://github.com/NousResearch/hermes-agent))
|
||||
- Count compression restarts toward retry limit ([#3070](https://github.com/NousResearch/hermes-agent/pull/3070))
|
||||
- Include tool tokens in preflight estimate, guard context probe persistence ([#3164](https://github.com/NousResearch/hermes-agent/pull/3164))
|
||||
- Update context compressor limits after fallback activation ([#3305](https://github.com/NousResearch/hermes-agent/pull/3305))
|
||||
- Validate empty user messages to prevent Anthropic API 400 errors ([#3322](https://github.com/NousResearch/hermes-agent/pull/3322))
|
||||
- GLM reasoning-only and max-length handling ([#3010](https://github.com/NousResearch/hermes-agent/pull/3010))
|
||||
- Increase API timeout default from 900s to 1800s for slow-thinking models ([#3431](https://github.com/NousResearch/hermes-agent/pull/3431))
|
||||
- Send `max_tokens` for Claude/OpenRouter + retry SSE connection errors ([#3497](https://github.com/NousResearch/hermes-agent/pull/3497))
|
||||
- Prevent AsyncOpenAI/httpx cross-loop deadlock in gateway mode ([#2701](https://github.com/NousResearch/hermes-agent/pull/2701)) by @ctlst
|
||||
|
||||
### Streaming & Reasoning
|
||||
- **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))
|
||||
- Detect and kill stale SSE connections ([untagged commit](https://github.com/NousResearch/hermes-agent))
|
||||
- Fix stale stream detector race causing spurious `RemoteProtocolError` ([untagged commit](https://github.com/NousResearch/hermes-agent))
|
||||
- Skip duplicate callback for `<think>`-extracted reasoning during streaming ([#3116](https://github.com/NousResearch/hermes-agent/pull/3116))
|
||||
- Preserve reasoning fields in `rewrite_transcript` ([#3311](https://github.com/NousResearch/hermes-agent/pull/3311))
|
||||
- Preserve Gemini thought signatures in streamed tool calls ([#2997](https://github.com/NousResearch/hermes-agent/pull/2997))
|
||||
- Ensure first delta is fired during reasoning updates ([untagged commit](https://github.com/NousResearch/hermes-agent))
|
||||
|
||||
### Session & Memory
|
||||
- **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))
|
||||
- **Session config surfacing** on `/new`, `/reset`, and auto-reset ([#3321](https://github.com/NousResearch/hermes-agent/pull/3321))
|
||||
- **Third-party session isolation** — `--source` flag for isolating sessions by origin ([#3255](https://github.com/NousResearch/hermes-agent/pull/3255))
|
||||
- Add `/resume` CLI handler, session log truncation guard, `reopen_session` API ([#3315](https://github.com/NousResearch/hermes-agent/pull/3315))
|
||||
- Clear compressor summary and turn counter on `/clear` and `/new` ([#3102](https://github.com/NousResearch/hermes-agent/pull/3102))
|
||||
- Surface silent SessionDB failures that cause session data loss ([#2999](https://github.com/NousResearch/hermes-agent/pull/2999))
|
||||
- Session search fallback preview on summarization failure ([#3478](https://github.com/NousResearch/hermes-agent/pull/3478))
|
||||
- Prevent stale memory overwrites by flush agent ([#2687](https://github.com/NousResearch/hermes-agent/pull/2687))
|
||||
|
||||
### Context Compression
|
||||
- Replace dead `summary_target_tokens` with ratio-based scaling ([#2554](https://github.com/NousResearch/hermes-agent/pull/2554))
|
||||
- Expose `compression.target_ratio`, `protect_last_n`, and `threshold` in `DEFAULT_CONFIG` ([untagged commit](https://github.com/NousResearch/hermes-agent))
|
||||
- Restore sane defaults and cap summary at 12K tokens ([untagged commit](https://github.com/NousResearch/hermes-agent))
|
||||
- Preserve transcript on `/compress` and hygiene compression ([#3556](https://github.com/NousResearch/hermes-agent/pull/3556))
|
||||
- Update context pressure warnings and token estimates after compaction ([untagged commit](https://github.com/NousResearch/hermes-agent))
|
||||
|
||||
### Architecture & Dependencies
|
||||
- **Remove mini-swe-agent dependency** — Inline Docker and Modal backends directly ([#2804](https://github.com/NousResearch/hermes-agent/pull/2804))
|
||||
- **Replace swe-rex with native Modal SDK** for Modal backend ([#3538](https://github.com/NousResearch/hermes-agent/pull/3538))
|
||||
- **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))
|
||||
- Fix plugin toolsets invisible in `hermes tools` and standalone processes ([#3457](https://github.com/NousResearch/hermes-agent/pull/3457))
|
||||
- Consolidate `get_hermes_home()` and `parse_reasoning_effort()` ([#3062](https://github.com/NousResearch/hermes-agent/pull/3062))
|
||||
- Remove unused Hermes-native PKCE OAuth flow ([#3107](https://github.com/NousResearch/hermes-agent/pull/3107))
|
||||
- Remove ~100 unused imports across 55 files ([#3016](https://github.com/NousResearch/hermes-agent/pull/3016))
|
||||
- Fix 154 f-strings, simplify getattr/URL patterns, remove dead code ([#3119](https://github.com/NousResearch/hermes-agent/pull/3119))
|
||||
|
||||
---
|
||||
|
||||
## 📱 Messaging Platforms (Gateway)
|
||||
|
||||
### 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))
|
||||
- **Auto-discover fallback IPs via DNS-over-HTTPS** when `api.telegram.org` is unreachable ([#3376](https://github.com/NousResearch/hermes-agent/pull/3376))
|
||||
- **Configurable reply threading mode** ([#2907](https://github.com/NousResearch/hermes-agent/pull/2907))
|
||||
- Fall back to no `thread_id` on "Message thread not found" BadRequest ([#3390](https://github.com/NousResearch/hermes-agent/pull/3390))
|
||||
- Self-reschedule reconnect when `start_polling` fails after 502 ([#3268](https://github.com/NousResearch/hermes-agent/pull/3268))
|
||||
|
||||
### Discord
|
||||
- Stop phantom typing indicator after agent turn completes ([#3003](https://github.com/NousResearch/hermes-agent/pull/3003))
|
||||
|
||||
### Slack
|
||||
- Send tool call progress messages to correct Slack thread ([#3063](https://github.com/NousResearch/hermes-agent/pull/3063))
|
||||
- Scope progress thread fallback to Slack only ([#3488](https://github.com/NousResearch/hermes-agent/pull/3488))
|
||||
|
||||
### WhatsApp
|
||||
- Download documents, audio, and video media from messages ([#2978](https://github.com/NousResearch/hermes-agent/pull/2978))
|
||||
|
||||
### Matrix
|
||||
- Add missing Matrix entry in `PLATFORMS` dict ([#3473](https://github.com/NousResearch/hermes-agent/pull/3473))
|
||||
- Harden e2ee access-token handling ([#3562](https://github.com/NousResearch/hermes-agent/pull/3562))
|
||||
- Add backoff for `SyncError` in sync loop ([#3280](https://github.com/NousResearch/hermes-agent/pull/3280))
|
||||
|
||||
### Signal
|
||||
- Track SSE keepalive comments as connection activity ([#3316](https://github.com/NousResearch/hermes-agent/pull/3316))
|
||||
|
||||
### Email
|
||||
- Prevent unbounded growth of `_seen_uids` in EmailAdapter ([#3490](https://github.com/NousResearch/hermes-agent/pull/3490))
|
||||
|
||||
### Gateway Core
|
||||
- **Config-gated `/verbose` command** for messaging platforms — toggle tool output verbosity from chat ([#3262](https://github.com/NousResearch/hermes-agent/pull/3262))
|
||||
- **Background review notifications** delivered to user chat ([#3293](https://github.com/NousResearch/hermes-agent/pull/3293))
|
||||
- **Retry transient send failures** and notify user on exhaustion ([#3288](https://github.com/NousResearch/hermes-agent/pull/3288))
|
||||
- Recover from hung agents — `/stop` hard-kills session lock ([#3104](https://github.com/NousResearch/hermes-agent/pull/3104))
|
||||
- Thread-safe `SessionStore` — protect `_entries` with `threading.Lock` ([#3052](https://github.com/NousResearch/hermes-agent/pull/3052))
|
||||
- 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))
|
||||
- Fingerprint full auth token in agent cache signature ([#3247](https://github.com/NousResearch/hermes-agent/pull/3247))
|
||||
- Silence background agent terminal output ([#3297](https://github.com/NousResearch/hermes-agent/pull/3297))
|
||||
- Include per-platform `ALLOW_ALL` and `SIGNAL_GROUP` in startup allowlist check ([#3313](https://github.com/NousResearch/hermes-agent/pull/3313))
|
||||
- Include user-local bin paths in systemd unit PATH ([#3527](https://github.com/NousResearch/hermes-agent/pull/3527))
|
||||
- Track background task references in `GatewayRunner` ([#3254](https://github.com/NousResearch/hermes-agent/pull/3254))
|
||||
- Add request timeouts to HA, Email, Mattermost, SMS adapters ([#3258](https://github.com/NousResearch/hermes-agent/pull/3258))
|
||||
- Add media download retry to Mattermost, Slack, and base cache ([#3323](https://github.com/NousResearch/hermes-agent/pull/3323))
|
||||
- Detect virtualenv path instead of hardcoding `venv/` ([#2797](https://github.com/NousResearch/hermes-agent/pull/2797))
|
||||
- Use `TERMINAL_CWD` for context file discovery, not process cwd ([untagged commit](https://github.com/NousResearch/hermes-agent))
|
||||
- Stop loading hermes repo AGENTS.md into gateway sessions (~10k wasted tokens) ([#2891](https://github.com/NousResearch/hermes-agent/pull/2891))
|
||||
|
||||
---
|
||||
|
||||
## 🖥️ CLI & User Experience
|
||||
|
||||
### Interactive CLI
|
||||
- **Configurable busy input mode** + fix `/queue` always working ([#3298](https://github.com/NousResearch/hermes-agent/pull/3298))
|
||||
- **Preserve user input on multiline paste** ([#3065](https://github.com/NousResearch/hermes-agent/pull/3065))
|
||||
- **Tool generation callback** — streaming "preparing terminal…" updates during tool argument generation ([untagged commit](https://github.com/NousResearch/hermes-agent))
|
||||
- Show tool progress for substantive tools, not just "preparing" ([untagged commit](https://github.com/NousResearch/hermes-agent))
|
||||
- Buffer reasoning preview chunks and fix duplicate display ([#3013](https://github.com/NousResearch/hermes-agent/pull/3013))
|
||||
- Prevent reasoning box from rendering 3x during tool-calling loops ([#3405](https://github.com/NousResearch/hermes-agent/pull/3405))
|
||||
- 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))
|
||||
- Fix status bar shows 26K instead of 260K for token counts with trailing zeros ([#3024](https://github.com/NousResearch/hermes-agent/pull/3024))
|
||||
- Fix status bar duplicates and degrades during long sessions ([#3291](https://github.com/NousResearch/hermes-agent/pull/3291))
|
||||
- Refresh TUI before background task output to prevent status bar overlap ([#3048](https://github.com/NousResearch/hermes-agent/pull/3048))
|
||||
- Suppress KawaiiSpinner animation under `patch_stdout` ([#2994](https://github.com/NousResearch/hermes-agent/pull/2994))
|
||||
- Skip KawaiiSpinner when TUI handles tool progress ([#2973](https://github.com/NousResearch/hermes-agent/pull/2973))
|
||||
- Guard `isatty()` against closed streams via `_is_tty` property ([#3056](https://github.com/NousResearch/hermes-agent/pull/3056))
|
||||
- Ensure single closure of streaming boxes during tool generation ([untagged commit](https://github.com/NousResearch/hermes-agent))
|
||||
- Cap context pressure percentage at 100% in display ([#3480](https://github.com/NousResearch/hermes-agent/pull/3480))
|
||||
- Clean up HTML error messages in CLI display ([#3069](https://github.com/NousResearch/hermes-agent/pull/3069))
|
||||
- Show HTTP status code and 400 body in API error output ([#3096](https://github.com/NousResearch/hermes-agent/pull/3096))
|
||||
- Extract useful info from HTML error pages, dump debug on max retries ([untagged commit](https://github.com/NousResearch/hermes-agent))
|
||||
- Prevent TypeError on startup when `base_url` is None ([#3068](https://github.com/NousResearch/hermes-agent/pull/3068))
|
||||
- Prevent update crash in non-TTY environments ([#3094](https://github.com/NousResearch/hermes-agent/pull/3094))
|
||||
- Handle EOFError in sessions delete/prune confirmation prompts ([#3101](https://github.com/NousResearch/hermes-agent/pull/3101))
|
||||
- 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))
|
||||
- Guard `.strip()` against None values from YAML config ([#3552](https://github.com/NousResearch/hermes-agent/pull/3552))
|
||||
- Guard `config.get()` against YAML null values to prevent AttributeError ([#3377](https://github.com/NousResearch/hermes-agent/pull/3377))
|
||||
- Store asyncio task references to prevent GC mid-execution ([#3267](https://github.com/NousResearch/hermes-agent/pull/3267))
|
||||
|
||||
### Setup & Configuration
|
||||
- Use explicit key mapping for returning-user menu dispatch instead of positional index ([#3083](https://github.com/NousResearch/hermes-agent/pull/3083))
|
||||
- Use `sys.executable` for pip in update commands to fix PEP 668 ([#3099](https://github.com/NousResearch/hermes-agent/pull/3099))
|
||||
- Harden `hermes update` against diverged history, non-main branches, and gateway edge cases ([#3492](https://github.com/NousResearch/hermes-agent/pull/3492))
|
||||
- OpenClaw migration overwrites defaults and setup wizard skips imported sections — fixed ([#3282](https://github.com/NousResearch/hermes-agent/pull/3282))
|
||||
- Stop recursive AGENTS.md walk, load top-level only ([#3110](https://github.com/NousResearch/hermes-agent/pull/3110))
|
||||
- Add macOS Homebrew paths to browser and terminal PATH resolution ([#2713](https://github.com/NousResearch/hermes-agent/pull/2713))
|
||||
- YAML boolean handling for `tool_progress` config ([#3300](https://github.com/NousResearch/hermes-agent/pull/3300))
|
||||
- Reset default SOUL.md to baseline identity text ([#3159](https://github.com/NousResearch/hermes-agent/pull/3159))
|
||||
- Reject relative cwd paths for container terminal backends ([untagged commit](https://github.com/NousResearch/hermes-agent))
|
||||
- Add explicit `hermes-api-server` toolset for API server platform ([#3304](https://github.com/NousResearch/hermes-agent/pull/3304))
|
||||
- Reorder setup wizard providers — OpenRouter first ([untagged commit](https://github.com/NousResearch/hermes-agent))
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Tool System
|
||||
|
||||
### API Server
|
||||
- **Idempotency-Key support**, body size limit, and OpenAI error envelope ([#2903](https://github.com/NousResearch/hermes-agent/pull/2903))
|
||||
- Allow Idempotency-Key in CORS headers ([#3530](https://github.com/NousResearch/hermes-agent/pull/3530))
|
||||
- Cancel orphaned agent + true interrupt on SSE disconnect ([#3427](https://github.com/NousResearch/hermes-agent/pull/3427))
|
||||
- Fix streaming breaks when agent makes tool calls ([#2985](https://github.com/NousResearch/hermes-agent/pull/2985))
|
||||
|
||||
### Terminal & File Operations
|
||||
- Handle addition-only hunks in V4A patch parser ([#3325](https://github.com/NousResearch/hermes-agent/pull/3325))
|
||||
- Exponential backoff for persistent shell polling ([#2996](https://github.com/NousResearch/hermes-agent/pull/2996))
|
||||
- Add timeout to subprocess calls in `context_references` ([#3469](https://github.com/NousResearch/hermes-agent/pull/3469))
|
||||
|
||||
### Browser & Vision
|
||||
- Handle 402 insufficient credits error in vision tool ([#2802](https://github.com/NousResearch/hermes-agent/pull/2802))
|
||||
- Fix `browser_vision` ignores `auxiliary.vision.timeout` config ([#2901](https://github.com/NousResearch/hermes-agent/pull/2901))
|
||||
- Make browser command timeout configurable via config.yaml ([#2801](https://github.com/NousResearch/hermes-agent/pull/2801))
|
||||
|
||||
### MCP
|
||||
- MCP toolset resolution for runtime and config ([#3252](https://github.com/NousResearch/hermes-agent/pull/3252))
|
||||
- Add MCP tool name collision protection ([#3077](https://github.com/NousResearch/hermes-agent/pull/3077))
|
||||
|
||||
### Auxiliary LLM
|
||||
- Guard aux LLM calls against None content + reasoning fallback + retry ([#3449](https://github.com/NousResearch/hermes-agent/pull/3449))
|
||||
- Catch ImportError from `build_anthropic_client` in vision auto-detection ([#3312](https://github.com/NousResearch/hermes-agent/pull/3312))
|
||||
|
||||
### Other Tools
|
||||
- Add request timeouts to `send_message_tool` HTTP calls ([#3162](https://github.com/NousResearch/hermes-agent/pull/3162)) by @memosr
|
||||
- Auto-repair `jobs.json` with invalid control characters ([#3537](https://github.com/NousResearch/hermes-agent/pull/3537))
|
||||
- Enable fine-grained tool streaming for Claude/OpenRouter ([#3497](https://github.com/NousResearch/hermes-agent/pull/3497))
|
||||
|
||||
---
|
||||
|
||||
## 🧩 Skills Ecosystem
|
||||
|
||||
### Skills System
|
||||
- **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))
|
||||
- Cache skills prompt with shared `skill_utils` module for faster TTFT ([#3421](https://github.com/NousResearch/hermes-agent/pull/3421))
|
||||
- Avoid redundant file re-read for skill conditions ([#2992](https://github.com/NousResearch/hermes-agent/pull/2992))
|
||||
- Use Git Trees API to prevent silent subdirectory loss during install ([#2995](https://github.com/NousResearch/hermes-agent/pull/2995))
|
||||
- Fix skills-sh install for deeply nested repo structures ([#2980](https://github.com/NousResearch/hermes-agent/pull/2980))
|
||||
- Handle null metadata in skill frontmatter ([untagged commit](https://github.com/NousResearch/hermes-agent))
|
||||
- Preserve trust for skills-sh identifiers + reduce resolution churn ([#3251](https://github.com/NousResearch/hermes-agent/pull/3251))
|
||||
- Agent-created skills were incorrectly treated as untrusted community content — fixed ([untagged commit](https://github.com/NousResearch/hermes-agent))
|
||||
|
||||
### New Skills
|
||||
- **G0DM0D3 godmode jailbreaking skill** + docs ([#3157](https://github.com/NousResearch/hermes-agent/pull/3157))
|
||||
- **Docker management skill** added to optional-skills ([#3060](https://github.com/NousResearch/hermes-agent/pull/3060))
|
||||
- **OpenClaw migration v2** — 17 new modules, terminal recap for migrating from OpenClaw to Hermes ([#2906](https://github.com/NousResearch/hermes-agent/pull/2906))
|
||||
|
||||
---
|
||||
|
||||
## 🔒 Security & Reliability
|
||||
|
||||
### Security Hardening
|
||||
- **SSRF protection** added to `browser_navigate` ([#3058](https://github.com/NousResearch/hermes-agent/pull/3058))
|
||||
- **SSRF protection** added to `vision_tools` and `web_tools` (hardened) ([#2679](https://github.com/NousResearch/hermes-agent/pull/2679))
|
||||
- **Restrict subagent toolsets** to parent's enabled set ([#3269](https://github.com/NousResearch/hermes-agent/pull/3269))
|
||||
- **Prevent zip-slip path traversal** in self-update ([#3250](https://github.com/NousResearch/hermes-agent/pull/3250))
|
||||
- **Prevent shell injection** in `_expand_path` via `~user` path suffix ([#2685](https://github.com/NousResearch/hermes-agent/pull/2685))
|
||||
- **Normalize input** before dangerous command detection ([#3260](https://github.com/NousResearch/hermes-agent/pull/3260))
|
||||
- Make tirith block verdicts approvable instead of hard-blocking ([#3428](https://github.com/NousResearch/hermes-agent/pull/3428))
|
||||
- Remove compromised `litellm`/`typer`/`platformdirs` from deps ([#2796](https://github.com/NousResearch/hermes-agent/pull/2796))
|
||||
- Pin all dependency version ranges ([#2810](https://github.com/NousResearch/hermes-agent/pull/2810))
|
||||
- Regenerate `uv.lock` with hashes, use lockfile in setup ([#2812](https://github.com/NousResearch/hermes-agent/pull/2812))
|
||||
- Bump dependencies to fix CVEs + regenerate `uv.lock` ([#3073](https://github.com/NousResearch/hermes-agent/pull/3073))
|
||||
- Supply chain audit CI workflow for PR scanning ([#2816](https://github.com/NousResearch/hermes-agent/pull/2816))
|
||||
|
||||
### Reliability
|
||||
- **SQLite WAL write-lock contention** causing 15-20s TUI freeze — fixed ([#3385](https://github.com/NousResearch/hermes-agent/pull/3385))
|
||||
- **SQLite concurrency hardening** + session transcript integrity ([#3249](https://github.com/NousResearch/hermes-agent/pull/3249))
|
||||
- Prevent recurring cron job re-fire on gateway crash/restart loop ([#3396](https://github.com/NousResearch/hermes-agent/pull/3396))
|
||||
- Mark cron session as ended after job completes ([#2998](https://github.com/NousResearch/hermes-agent/pull/2998))
|
||||
|
||||
---
|
||||
|
||||
## ⚡ Performance
|
||||
|
||||
- **TTFT startup optimizations** — salvaged easy-win startup improvements ([#3395](https://github.com/NousResearch/hermes-agent/pull/3395))
|
||||
- Cache skills prompt with shared `skill_utils` module ([#3421](https://github.com/NousResearch/hermes-agent/pull/3421))
|
||||
- Avoid redundant file re-read for skill conditions in prompt builder ([#2992](https://github.com/NousResearch/hermes-agent/pull/2992))
|
||||
|
||||
---
|
||||
|
||||
## 🐛 Notable Bug Fixes
|
||||
|
||||
- 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))
|
||||
- Fix "Event loop is closed" / "Press ENTER to continue" during idle sessions ([#3398](https://github.com/NousResearch/hermes-agent/pull/3398))
|
||||
- Fix reasoning box rendering 3x during tool-calling loops ([#3405](https://github.com/NousResearch/hermes-agent/pull/3405))
|
||||
- Fix status bar shows 26K instead of 260K for token counts ([#3024](https://github.com/NousResearch/hermes-agent/pull/3024))
|
||||
- Fix `/queue` always working regardless of config ([#3298](https://github.com/NousResearch/hermes-agent/pull/3298))
|
||||
- Fix phantom Discord typing indicator after agent turn ([#3003](https://github.com/NousResearch/hermes-agent/pull/3003))
|
||||
- Fix Slack progress messages appearing in wrong thread ([#3063](https://github.com/NousResearch/hermes-agent/pull/3063))
|
||||
- Fix WhatsApp media downloads (documents, audio, video) ([#2978](https://github.com/NousResearch/hermes-agent/pull/2978))
|
||||
- Fix Telegram "Message thread not found" killing progress messages ([#3390](https://github.com/NousResearch/hermes-agent/pull/3390))
|
||||
- Fix OpenClaw migration overwriting defaults ([#3282](https://github.com/NousResearch/hermes-agent/pull/3282))
|
||||
- Fix returning-user setup menu dispatching wrong section ([#3083](https://github.com/NousResearch/hermes-agent/pull/3083))
|
||||
- Fix `hermes update` PEP 668 "externally-managed-environment" error ([#3099](https://github.com/NousResearch/hermes-agent/pull/3099))
|
||||
- Fix subagents hitting `max_iterations` prematurely via shared budget ([#3004](https://github.com/NousResearch/hermes-agent/pull/3004))
|
||||
- Fix YAML boolean handling for `tool_progress` config ([#3300](https://github.com/NousResearch/hermes-agent/pull/3300))
|
||||
- Fix `config.get()` crashes on YAML null values ([#3377](https://github.com/NousResearch/hermes-agent/pull/3377))
|
||||
- Fix `.strip()` crash on None values from YAML config ([#3552](https://github.com/NousResearch/hermes-agent/pull/3552))
|
||||
- Fix hung agents on gateway — `/stop` now hard-kills session lock ([#3104](https://github.com/NousResearch/hermes-agent/pull/3104))
|
||||
- Fix `_custom` provider silently remapped to `openrouter` ([#2792](https://github.com/NousResearch/hermes-agent/pull/2792))
|
||||
- Fix Matrix missing from `PLATFORMS` dict ([#3473](https://github.com/NousResearch/hermes-agent/pull/3473))
|
||||
- Fix Email adapter unbounded `_seen_uids` growth ([#3490](https://github.com/NousResearch/hermes-agent/pull/3490))
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Testing
|
||||
|
||||
- Pin `agent-client-protocol` < 0.9 to handle breaking upstream release ([#3320](https://github.com/NousResearch/hermes-agent/pull/3320))
|
||||
- Catch anthropic ImportError in vision auto-detection tests ([#3312](https://github.com/NousResearch/hermes-agent/pull/3312))
|
||||
- Update retry-exhaust test for new graceful return behavior ([#3320](https://github.com/NousResearch/hermes-agent/pull/3320))
|
||||
- Add regression tests for null metadata frontmatter ([untagged commit](https://github.com/NousResearch/hermes-agent))
|
||||
|
||||
---
|
||||
|
||||
## 📚 Documentation
|
||||
|
||||
- Update all docs for `/model` command overhaul and custom provider support ([#2800](https://github.com/NousResearch/hermes-agent/pull/2800))
|
||||
- Fix stale and incorrect documentation across 18 files ([#2805](https://github.com/NousResearch/hermes-agent/pull/2805))
|
||||
- Document 9 previously undocumented features ([#2814](https://github.com/NousResearch/hermes-agent/pull/2814))
|
||||
- Add missing skills, CLI commands, and messaging env vars to docs ([#2809](https://github.com/NousResearch/hermes-agent/pull/2809))
|
||||
- Fix api-server response storage documentation — SQLite, not in-memory ([#2819](https://github.com/NousResearch/hermes-agent/pull/2819))
|
||||
- Quote pip install extras to fix zsh glob errors ([#2815](https://github.com/NousResearch/hermes-agent/pull/2815))
|
||||
- Unify hooks documentation — add plugin hooks to hooks page, add `session:end` event ([untagged commit](https://github.com/NousResearch/hermes-agent))
|
||||
- Clarify two-mode behavior in `session_search` schema description ([untagged commit](https://github.com/NousResearch/hermes-agent))
|
||||
- Fix Discord Public Bot setting for Discord-provided invite link ([#3519](https://github.com/NousResearch/hermes-agent/pull/3519)) by @mehmoodosman
|
||||
- Revise v0.4.0 changelog — fix feature attribution, reorder sections ([untagged commit](https://github.com/NousResearch/hermes-agent))
|
||||
|
||||
---
|
||||
|
||||
## 👥 Contributors
|
||||
|
||||
### Core
|
||||
- **@teknium1** — 157 PRs covering the full scope of this release
|
||||
|
||||
### Community Contributors
|
||||
- **@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))
|
||||
- **@ctlst** — 1 PR: Prevent AsyncOpenAI/httpx cross-loop deadlock in gateway mode ([#2701](https://github.com/NousResearch/hermes-agent/pull/2701))
|
||||
- **@memosr** (memosr.eth) — 1 PR: Add request timeouts to `send_message_tool` HTTP calls ([#3162](https://github.com/NousResearch/hermes-agent/pull/3162))
|
||||
- **@mehmoodosman** (Osman Mehmood) — 1 PR: Fix Discord docs for Public Bot setting ([#3519](https://github.com/NousResearch/hermes-agent/pull/3519))
|
||||
|
||||
### All Contributors
|
||||
@alt-glitch, @ctlst, @mehmoodosman, @memosr, @teknium1
|
||||
|
||||
---
|
||||
|
||||
**Full Changelog**: [v2026.3.23...v2026.3.28](https://github.com/NousResearch/hermes-agent/compare/v2026.3.23...v2026.3.28)
|
||||
@@ -1,249 +0,0 @@
|
||||
# Hermes Agent v0.6.0 (v2026.3.30)
|
||||
|
||||
**Release Date:** March 30, 2026
|
||||
|
||||
> 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.
|
||||
|
||||
---
|
||||
|
||||
## ✨ Highlights
|
||||
|
||||
- **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))
|
||||
|
||||
- **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))
|
||||
|
||||
- **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))
|
||||
|
||||
- **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))
|
||||
|
||||
- **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))
|
||||
|
||||
- **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))
|
||||
|
||||
- **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))
|
||||
|
||||
- **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))
|
||||
|
||||
- **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))
|
||||
|
||||
- **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))
|
||||
|
||||
---
|
||||
|
||||
## 🏗️ Core Agent & Architecture
|
||||
|
||||
### Provider & Model Support
|
||||
- **Ordered fallback provider chain** — automatic failover across multiple configured providers ([#3813](https://github.com/NousResearch/hermes-agent/pull/3813))
|
||||
- **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))
|
||||
- **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))
|
||||
- **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))
|
||||
- **Gemini direct API context length** — full context length resolution for direct Google AI endpoints ([#3876](https://github.com/NousResearch/hermes-agent/pull/3876))
|
||||
- **gpt-5.4-mini** added to Codex fallback catalog ([#3855](https://github.com/NousResearch/hermes-agent/pull/3855))
|
||||
- **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))
|
||||
- **User-friendly 429 rate limit messages** with Retry-After countdown ([#3809](https://github.com/NousResearch/hermes-agent/pull/3809))
|
||||
- **Auxiliary client placeholder key** for local servers without auth requirements ([#3842](https://github.com/NousResearch/hermes-agent/pull/3842))
|
||||
- **INFO-level logging** for auxiliary provider resolution ([#3866](https://github.com/NousResearch/hermes-agent/pull/3866))
|
||||
|
||||
### Agent Loop & Conversation
|
||||
- **Subagent status reporting** — reports `completed` status when summary exists instead of generic failure ([#3829](https://github.com/NousResearch/hermes-agent/pull/3829))
|
||||
- **Session log file updated during compression** — prevents stale file references after context compression ([#3835](https://github.com/NousResearch/hermes-agent/pull/3835))
|
||||
- **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))
|
||||
|
||||
### Profiles & Multi-Instance
|
||||
- **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))
|
||||
- **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))
|
||||
- **Lazy display_hermes_home imports** — prevents `ImportError` during `hermes update` when modules cache stale bytecode ([#3776](https://github.com/NousResearch/hermes-agent/pull/3776))
|
||||
- **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))
|
||||
|
||||
---
|
||||
|
||||
## 📱 Messaging Platforms (Gateway)
|
||||
|
||||
### New Platforms
|
||||
- **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))
|
||||
- **WeCom (Enterprise WeChat)** — Text/image/voice messages, group chats, callback verification ([#3847](https://github.com/NousResearch/hermes-agent/pull/3847))
|
||||
|
||||
### Telegram
|
||||
- **Webhook mode** — run as webhook endpoint instead of polling for production deployments ([#3880](https://github.com/NousResearch/hermes-agent/pull/3880))
|
||||
- **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))
|
||||
- **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))
|
||||
|
||||
### Discord
|
||||
- **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))
|
||||
- **DISCORD_IGNORE_NO_MENTION** — skip messages that @mention other users/bots but not Hermes ([#3640](https://github.com/NousResearch/hermes-agent/pull/3640))
|
||||
- **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))
|
||||
|
||||
### Slack
|
||||
- **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))
|
||||
|
||||
### WhatsApp
|
||||
- **Persistent aiohttp session** — reuse HTTP sessions across requests instead of creating new ones per message ([#3818](https://github.com/NousResearch/hermes-agent/pull/3818))
|
||||
- **LID↔phone alias resolution** — correctly match Linked ID and phone number formats in allowlists ([#3830](https://github.com/NousResearch/hermes-agent/pull/3830))
|
||||
- **Skip reply prefix in bot mode** — cleaner message formatting when running as a WhatsApp bot ([#3931](https://github.com/NousResearch/hermes-agent/pull/3931))
|
||||
|
||||
### Matrix
|
||||
- **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))
|
||||
|
||||
### Mattermost
|
||||
- **Configurable mention behavior** — respond to messages without requiring @mention ([#3664](https://github.com/NousResearch/hermes-agent/pull/3664))
|
||||
|
||||
### Signal
|
||||
- **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
|
||||
|
||||
### Email
|
||||
- **Close SMTP/IMAP connections on failure** — prevents connection leaks during error scenarios ([#3804](https://github.com/NousResearch/hermes-agent/pull/3804))
|
||||
|
||||
### Gateway Core
|
||||
- **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))
|
||||
- **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))
|
||||
- **Replace print() with logger** — BasePlatformAdapter now uses proper logging instead of print statements ([#3669](https://github.com/NousResearch/hermes-agent/pull/3669))
|
||||
- **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))
|
||||
- **Cron [SILENT] tightening** — prevent agents from prefixing reports with [SILENT] to suppress delivery ([#3901](https://github.com/NousResearch/hermes-agent/pull/3901))
|
||||
- **Background task media delivery** and vision download timeout fixes ([#3919](https://github.com/NousResearch/hermes-agent/pull/3919))
|
||||
- **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))
|
||||
|
||||
---
|
||||
|
||||
## 🖥️ CLI & User Experience
|
||||
|
||||
### Interactive CLI
|
||||
- **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))
|
||||
- **Tool token context display** — `hermes tools` checklist now shows estimated token cost per toolset ([#3805](https://github.com/NousResearch/hermes-agent/pull/3805))
|
||||
- **/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))
|
||||
- **Prevent status bar wrapping** into duplicate rows ([#3883](https://github.com/NousResearch/hermes-agent/pull/3883)) — @kshitijk4poor
|
||||
- **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))
|
||||
- **Remove input() from /tools disable** — eliminates freeze in terminal when disabling tools ([#3918](https://github.com/NousResearch/hermes-agent/pull/3918))
|
||||
- **TTY guard for interactive CLI commands** — prevent CPU spin when launched without a terminal ([#3933](https://github.com/NousResearch/hermes-agent/pull/3933))
|
||||
- **Argparse entrypoint** — use argparse in the top-level launcher for cleaner error handling ([#3874](https://github.com/NousResearch/hermes-agent/pull/3874))
|
||||
- **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))
|
||||
- **Honcho tools shown in banner** when configured ([#3810](https://github.com/NousResearch/hermes-agent/pull/3810))
|
||||
|
||||
### Setup & Configuration
|
||||
- **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))
|
||||
- **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))
|
||||
- **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))
|
||||
- **Clear __pycache__ during update** — prevents stale bytecode ImportError after `hermes update` ([#3819](https://github.com/NousResearch/hermes-agent/pull/3819))
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Tool System
|
||||
|
||||
### MCP
|
||||
- **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))
|
||||
- **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))
|
||||
- **Non-deprecated HTTP transport** — switched from `sse_client` to `streamable_http_client` ([#3646](https://github.com/NousResearch/hermes-agent/pull/3646))
|
||||
|
||||
### Web Tools
|
||||
- **Exa search backend** — alternative to Firecrawl and DuckDuckGo for web search and extraction ([#3648](https://github.com/NousResearch/hermes-agent/pull/3648))
|
||||
|
||||
### Browser
|
||||
- **Guard against None LLM responses** in browser snapshot and vision tools ([#3642](https://github.com/NousResearch/hermes-agent/pull/3642))
|
||||
|
||||
### Terminal & Remote Backends
|
||||
- **Mount skill directories** into Modal and Docker containers ([#3890](https://github.com/NousResearch/hermes-agent/pull/3890))
|
||||
- **Mount credential files** into remote backends with mtime+size caching ([#3671](https://github.com/NousResearch/hermes-agent/pull/3671))
|
||||
- **Preserve partial output** when commands time out instead of losing everything ([#3868](https://github.com/NousResearch/hermes-agent/pull/3868))
|
||||
- **Stop marking persisted env vars as missing** on remote backends ([#3650](https://github.com/NousResearch/hermes-agent/pull/3650))
|
||||
|
||||
### Audio
|
||||
- **.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))
|
||||
- **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
|
||||
|
||||
### Vision
|
||||
- **Reject non-image files** and enforce website-only policy for vision analysis ([#3845](https://github.com/NousResearch/hermes-agent/pull/3845))
|
||||
|
||||
### Tool Schema
|
||||
- **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))
|
||||
|
||||
### ACP (Editor Integration)
|
||||
- **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))
|
||||
|
||||
---
|
||||
|
||||
## 🧩 Skills & Plugins
|
||||
|
||||
### Skills System
|
||||
- **External skill directories** — configure additional skill directories via `skills.external_dirs` in config.yaml ([#3678](https://github.com/NousResearch/hermes-agent/pull/3678))
|
||||
- **Category path traversal blocked** — prevents `../` attacks in skill category names ([#3844](https://github.com/NousResearch/hermes-agent/pull/3844))
|
||||
- **parallel-cli moved to optional-skills** — reduces default skill footprint ([#3673](https://github.com/NousResearch/hermes-agent/pull/3673)) — @kshitijk4poor
|
||||
|
||||
### New Skills
|
||||
- **memento-flashcards** — spaced repetition flashcard system ([#3827](https://github.com/NousResearch/hermes-agent/pull/3827))
|
||||
- **songwriting-and-ai-music** — songwriting craft and AI music generation prompts ([#3834](https://github.com/NousResearch/hermes-agent/pull/3834))
|
||||
- **SiYuan Note** — integration with SiYuan note-taking app ([#3742](https://github.com/NousResearch/hermes-agent/pull/3742))
|
||||
- **Scrapling** — web scraping skill using Scrapling library ([#3742](https://github.com/NousResearch/hermes-agent/pull/3742))
|
||||
- **one-three-one-rule** — communication framework skill ([#3797](https://github.com/NousResearch/hermes-agent/pull/3797))
|
||||
|
||||
### Plugin System
|
||||
- **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))
|
||||
- **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
|
||||
- **Honcho self-hosted support** — allow local Honcho instances without requiring an API key ([#3644](https://github.com/NousResearch/hermes-agent/pull/3644))
|
||||
|
||||
---
|
||||
|
||||
## 🔒 Security & Reliability
|
||||
|
||||
### Security Hardening
|
||||
- **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))
|
||||
- **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))
|
||||
- **Secret redaction expansion** — now covers ElevenLabs, Tavily, and Exa API keys ([#3920](https://github.com/NousResearch/hermes-agent/pull/3920))
|
||||
- **Vision file rejection** — reject non-image files passed to vision analysis to prevent information disclosure ([#3845](https://github.com/NousResearch/hermes-agent/pull/3845))
|
||||
- **Category path traversal blocking** — prevent directory traversal in skill category names ([#3844](https://github.com/NousResearch/hermes-agent/pull/3844))
|
||||
|
||||
### Reliability
|
||||
- **Atomic config.yaml writes** — prevent data loss during gateway crashes ([#3800](https://github.com/NousResearch/hermes-agent/pull/3800))
|
||||
- **Clear __pycache__ on update** — prevent stale bytecode from causing ImportError after updates ([#3819](https://github.com/NousResearch/hermes-agent/pull/3819))
|
||||
- **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))
|
||||
- **Restore terminalbench2 from patch corruption** — recovered file damaged by patch tool's secret redaction ([#3801](https://github.com/NousResearch/hermes-agent/pull/3801))
|
||||
- **Terminal timeout preserves partial output** — no more lost command output on timeout ([#3868](https://github.com/NousResearch/hermes-agent/pull/3868))
|
||||
|
||||
---
|
||||
|
||||
## 🐛 Notable Bug Fixes
|
||||
|
||||
- **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
|
||||
- **OpenClaw migration expanded** — covers full data footprint including sessions, cron, memory ([#3869](https://github.com/NousResearch/hermes-agent/pull/3869))
|
||||
- **Telegram deleted reply targets** — gracefully handle replies to deleted messages instead of crashing ([#3858](https://github.com/NousResearch/hermes-agent/pull/3858))
|
||||
- **Discord "thinking..." persistence** — properly cleans up deferred response indicators ([#3674](https://github.com/NousResearch/hermes-agent/pull/3674))
|
||||
- **WhatsApp LID↔phone aliases** — fixes allowlist matching failures with Linked ID format ([#3830](https://github.com/NousResearch/hermes-agent/pull/3830))
|
||||
- **Signal URL-encoded phone numbers** — fixes delivery failures with certain formats ([#3670](https://github.com/NousResearch/hermes-agent/pull/3670))
|
||||
- **Email connection leaks** — properly close SMTP/IMAP connections on error ([#3804](https://github.com/NousResearch/hermes-agent/pull/3804))
|
||||
- **_safe_print ValueError** — no more gateway thread crashes on closed stdout ([#3843](https://github.com/NousResearch/hermes-agent/pull/3843))
|
||||
- **Tool schema KeyError 'name'** — ensure name field always present in tool definitions ([#3811](https://github.com/NousResearch/hermes-agent/pull/3811))
|
||||
- **api_mode stale on provider switch** — correctly clear when switching providers via `hermes model` ([#3857](https://github.com/NousResearch/hermes-agent/pull/3857))
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Testing
|
||||
|
||||
- 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))
|
||||
|
||||
---
|
||||
|
||||
## 📚 Documentation
|
||||
|
||||
- **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))
|
||||
- **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))
|
||||
- **DuckDuckGo requirements clarified** — note runtime dependency on duckduckgo-search package ([#3680](https://github.com/NousResearch/hermes-agent/pull/3680))
|
||||
- **Skills catalog updated** — added red-teaming category and optional skills listing ([#3745](https://github.com/NousResearch/hermes-agent/pull/3745))
|
||||
- **Feishu docs MDX fix** — escape angle-bracket URLs that break Docusaurus build ([#3902](https://github.com/NousResearch/hermes-agent/pull/3902))
|
||||
|
||||
---
|
||||
|
||||
## 👥 Contributors
|
||||
|
||||
### Core
|
||||
- **@teknium1** — 90 PRs across all subsystems
|
||||
|
||||
### Community Contributors
|
||||
- **@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))
|
||||
- **@winglian** — 1 PR: Plugin message injection interface ([#3778](https://github.com/NousResearch/hermes-agent/pull/3778))
|
||||
- **@binhnt92** — 1 PR: Audio download retry logic ([#3401](https://github.com/NousResearch/hermes-agent/pull/3401))
|
||||
- **@0xbyt4** — 1 PR: OpenClaw migration model config fix ([#3924](https://github.com/NousResearch/hermes-agent/pull/3924))
|
||||
|
||||
### Issues Resolved from Community
|
||||
@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))
|
||||
|
||||
---
|
||||
|
||||
**Full Changelog**: [v2026.3.28...v2026.3.30](https://github.com/NousResearch/hermes-agent/compare/v2026.3.28...v2026.3.30)
|
||||
@@ -1,290 +0,0 @@
|
||||
# Hermes Agent v0.7.0 (v2026.4.3)
|
||||
|
||||
**Release Date:** April 3, 2026
|
||||
|
||||
> 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.
|
||||
|
||||
---
|
||||
|
||||
## ✨ Highlights
|
||||
|
||||
- **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))
|
||||
|
||||
- **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))
|
||||
|
||||
- **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))
|
||||
|
||||
- **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))
|
||||
|
||||
- **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))
|
||||
|
||||
- **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))
|
||||
|
||||
- **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))
|
||||
|
||||
- **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))
|
||||
|
||||
---
|
||||
|
||||
## 🏗️ Core Agent & Architecture
|
||||
|
||||
### Provider & Model Support
|
||||
- **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))
|
||||
- **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))
|
||||
- **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))
|
||||
- **`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))
|
||||
- **Google model operational guidance** — Gemini and Gemma models get provider-specific prompting guidance ([#4641](https://github.com/NousResearch/hermes-agent/pull/4641))
|
||||
- **Anthropic long-context tier 429 handling** — automatically reduces context to 200k when hitting tier limits ([#4747](https://github.com/NousResearch/hermes-agent/pull/4747))
|
||||
- **URL-based auth for third-party Anthropic endpoints** + CI test fixes ([#4148](https://github.com/NousResearch/hermes-agent/pull/4148))
|
||||
- **Bearer auth for MiniMax Anthropic endpoints** ([#4028](https://github.com/NousResearch/hermes-agent/pull/4028))
|
||||
- **Fireworks context length detection** ([#4158](https://github.com/NousResearch/hermes-agent/pull/4158))
|
||||
- **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))
|
||||
- **Custom providers context_length** honored in hygiene compression ([#4085](https://github.com/NousResearch/hermes-agent/pull/4085))
|
||||
- **Non-sk-ant keys** treated as regular API keys, not OAuth tokens ([#4093](https://github.com/NousResearch/hermes-agent/pull/4093))
|
||||
- **Claude-sonnet-4.6** added to OpenRouter and Nous model lists ([#4157](https://github.com/NousResearch/hermes-agent/pull/4157))
|
||||
- **Qwen 3.6 Plus Preview** added to model lists ([#4376](https://github.com/NousResearch/hermes-agent/pull/4376))
|
||||
- **MiniMax M2.7** added to hermes model picker and OpenCode ([#4208](https://github.com/NousResearch/hermes-agent/pull/4208))
|
||||
- **Auto-detect models from server probe** in custom endpoint setup ([#4218](https://github.com/NousResearch/hermes-agent/pull/4218))
|
||||
- **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))
|
||||
- **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))
|
||||
- **Unified setup wizard provider selection** with `hermes model` — single code path for both flows ([#4200](https://github.com/NousResearch/hermes-agent/pull/4200))
|
||||
- **Root-level provider config** no longer overrides `model.provider` ([#4329](https://github.com/NousResearch/hermes-agent/pull/4329))
|
||||
- **Rate-limit pairing rejection messages** to prevent spam ([#4081](https://github.com/NousResearch/hermes-agent/pull/4081))
|
||||
|
||||
### Agent Loop & Conversation
|
||||
- **Preserve Anthropic thinking block signatures** across tool-use turns ([#4626](https://github.com/NousResearch/hermes-agent/pull/4626))
|
||||
- **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))
|
||||
- **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))
|
||||
- **Persist compressed context** to gateway session after mid-run compression ([#4095](https://github.com/NousResearch/hermes-agent/pull/4095))
|
||||
- **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))
|
||||
- **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))
|
||||
- **Harden Codex responses preflight** and stream error handling ([#4313](https://github.com/NousResearch/hermes-agent/pull/4313))
|
||||
- **Deterministic call_id fallbacks** instead of random UUIDs for prompt cache consistency ([#3991](https://github.com/NousResearch/hermes-agent/pull/3991))
|
||||
- **Context pressure warning spam** prevented after compression ([#4012](https://github.com/NousResearch/hermes-agent/pull/4012))
|
||||
- **AsyncOpenAI created lazily** in trajectory compressor to avoid closed event loop errors ([#4013](https://github.com/NousResearch/hermes-agent/pull/4013))
|
||||
|
||||
### Memory & Sessions
|
||||
- **Pluggable memory provider interface** — ABC-based plugin system for custom memory backends with profile isolation ([#4623](https://github.com/NousResearch/hermes-agent/pull/4623))
|
||||
- **Honcho full integration parity** restored as reference memory provider plugin ([#4355](https://github.com/NousResearch/hermes-agent/pull/4355)) — @erosika
|
||||
- **Honcho profile-scoped** host and peer resolution ([#4616](https://github.com/NousResearch/hermes-agent/pull/4616))
|
||||
- **Memory flush state persisted** to prevent redundant re-flushes on gateway restart ([#4481](https://github.com/NousResearch/hermes-agent/pull/4481))
|
||||
- **Memory provider tools** routed through sequential execution path ([#4803](https://github.com/NousResearch/hermes-agent/pull/4803))
|
||||
- **Honcho config** written to instance-local path for profile isolation ([#4037](https://github.com/NousResearch/hermes-agent/pull/4037))
|
||||
- **API server sessions** persist to shared SessionDB ([#4802](https://github.com/NousResearch/hermes-agent/pull/4802))
|
||||
- **Token usage persisted** for non-CLI sessions ([#4627](https://github.com/NousResearch/hermes-agent/pull/4627))
|
||||
- **Quote dotted terms in FTS5 queries** — fixes session search for terms containing dots ([#4549](https://github.com/NousResearch/hermes-agent/pull/4549))
|
||||
|
||||
---
|
||||
|
||||
## 📱 Messaging Platforms (Gateway)
|
||||
|
||||
### Gateway Core
|
||||
- **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))
|
||||
- **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))
|
||||
- **Resume agent after /approve** — tool result is no longer lost when executing blocked commands ([#4418](https://github.com/NousResearch/hermes-agent/pull/4418))
|
||||
- **DM thread sessions seeded** with parent transcript to preserve context ([#4559](https://github.com/NousResearch/hermes-agent/pull/4559))
|
||||
- **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))
|
||||
- **Per-platform disabled skills** respected in Telegram menu and gateway dispatch ([#4799](https://github.com/NousResearch/hermes-agent/pull/4799))
|
||||
- **Remove user-facing compression warnings** — cleaner message flow ([#4139](https://github.com/NousResearch/hermes-agent/pull/4139))
|
||||
- **`-v/-q` flags wired to stderr logging** for gateway service ([#4474](https://github.com/NousResearch/hermes-agent/pull/4474))
|
||||
- **HERMES_HOME remapped** to target user in system service unit ([#4456](https://github.com/NousResearch/hermes-agent/pull/4456))
|
||||
- **Honor default for invalid bool-like config values** ([#4029](https://github.com/NousResearch/hermes-agent/pull/4029))
|
||||
- **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))
|
||||
- **'Initializing agent...'** shown on first message for better UX ([#4086](https://github.com/NousResearch/hermes-agent/pull/4086))
|
||||
- **Allow running gateway service as root** for LXC/container environments ([#4732](https://github.com/NousResearch/hermes-agent/pull/4732))
|
||||
|
||||
### Telegram
|
||||
- **32-char limit on command names** with collision avoidance ([#4211](https://github.com/NousResearch/hermes-agent/pull/4211))
|
||||
- **Priority order enforced** in menu — core > plugins > skills ([#4023](https://github.com/NousResearch/hermes-agent/pull/4023))
|
||||
- **Capped at 50 commands** — API rejects above ~60 ([#4006](https://github.com/NousResearch/hermes-agent/pull/4006))
|
||||
- **Skip empty/whitespace text** to prevent 400 errors ([#4388](https://github.com/NousResearch/hermes-agent/pull/4388))
|
||||
- **E2E gateway tests** added ([#4497](https://github.com/NousResearch/hermes-agent/pull/4497)) — @pefontana
|
||||
|
||||
### Discord
|
||||
- **Button-based approval UI** — register `/approve` and `/deny` slash commands with interactive button prompts ([#4800](https://github.com/NousResearch/hermes-agent/pull/4800))
|
||||
- **Configurable reactions** — `discord.reactions` config option to disable message processing reactions ([#4199](https://github.com/NousResearch/hermes-agent/pull/4199))
|
||||
- **Skip reactions and auto-threading** for unauthorized users ([#4387](https://github.com/NousResearch/hermes-agent/pull/4387))
|
||||
|
||||
### Slack
|
||||
- **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))
|
||||
|
||||
### WhatsApp
|
||||
- **Enforce require_mention in group chats** ([#4730](https://github.com/NousResearch/hermes-agent/pull/4730))
|
||||
|
||||
### Webhook
|
||||
- **Platform support fixes** — skip home channel prompt, disable tool progress for webhook adapters ([#4660](https://github.com/NousResearch/hermes-agent/pull/4660))
|
||||
|
||||
### Matrix
|
||||
- **E2EE decryption hardening** — request missing keys, auto-trust devices, retry buffered events ([#4083](https://github.com/NousResearch/hermes-agent/pull/4083))
|
||||
|
||||
---
|
||||
|
||||
## 🖥️ CLI & User Experience
|
||||
|
||||
### New Slash Commands
|
||||
- **`/yolo`** — toggle dangerous command approvals on/off for the session ([#3990](https://github.com/NousResearch/hermes-agent/pull/3990))
|
||||
- **`/btw`** — ephemeral side questions that don't affect the main conversation context ([#4161](https://github.com/NousResearch/hermes-agent/pull/4161))
|
||||
- **`/profile`** — show active profile info without leaving the chat session ([#4027](https://github.com/NousResearch/hermes-agent/pull/4027))
|
||||
|
||||
### Interactive CLI
|
||||
- **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))
|
||||
- **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))
|
||||
- **`/history` and `/resume`** now surface recent sessions directly instead of requiring search ([#4728](https://github.com/NousResearch/hermes-agent/pull/4728))
|
||||
- **Cache tokens shown** in `/insights` overview so total adds up ([#4428](https://github.com/NousResearch/hermes-agent/pull/4428))
|
||||
- **`--max-turns` CLI flag** for `hermes chat` to limit agent iterations ([#4314](https://github.com/NousResearch/hermes-agent/pull/4314))
|
||||
- **Detect dragged file paths** instead of treating them as slash commands ([#4533](https://github.com/NousResearch/hermes-agent/pull/4533)) — @rolme
|
||||
- **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))
|
||||
- **Voice mode in WSL** when PulseAudio bridge is configured ([#4317](https://github.com/NousResearch/hermes-agent/pull/4317))
|
||||
- **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
|
||||
- **Correct shell reload instruction** for macOS/zsh users ([#4025](https://github.com/NousResearch/hermes-agent/pull/4025))
|
||||
- **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
|
||||
- **on_session_end hook fires** on interrupted exits ([#4159](https://github.com/NousResearch/hermes-agent/pull/4159))
|
||||
- **Profile list display** reads `model.default` key correctly ([#4160](https://github.com/NousResearch/hermes-agent/pull/4160))
|
||||
- **Browser and TTS** shown in reconfigure menu ([#4041](https://github.com/NousResearch/hermes-agent/pull/4041))
|
||||
- **Web backend priority** detection simplified ([#4036](https://github.com/NousResearch/hermes-agent/pull/4036))
|
||||
|
||||
### Setup & Configuration
|
||||
- **Allowed_users preserved** during setup and quiet unconfigured provider warnings ([#4551](https://github.com/NousResearch/hermes-agent/pull/4551)) — @kshitijk4poor
|
||||
- **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))
|
||||
- **Claude Code credentials gated** behind explicit Hermes config in wizard trigger ([#4210](https://github.com/NousResearch/hermes-agent/pull/4210))
|
||||
- **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))
|
||||
- **Scopes field written** to Claude Code credentials on token refresh ([#4126](https://github.com/NousResearch/hermes-agent/pull/4126))
|
||||
|
||||
### Update System
|
||||
- **Fork detection and upstream sync** in `hermes update` ([#4744](https://github.com/NousResearch/hermes-agent/pull/4744))
|
||||
- **Preserve working optional extras** when one extra fails during update ([#4550](https://github.com/NousResearch/hermes-agent/pull/4550))
|
||||
- **Handle conflicted git index** during hermes update ([#4735](https://github.com/NousResearch/hermes-agent/pull/4735))
|
||||
- **Avoid launchd restart race** on macOS ([#4736](https://github.com/NousResearch/hermes-agent/pull/4736))
|
||||
- **Missing subprocess.run() timeouts** added to doctor and status commands ([#4009](https://github.com/NousResearch/hermes-agent/pull/4009))
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Tool System
|
||||
|
||||
### Browser
|
||||
- **Camofox anti-detection browser backend** — local stealth browsing with auto-install via `hermes tools` ([#4008](https://github.com/NousResearch/hermes-agent/pull/4008))
|
||||
- **Persistent Camofox sessions** with VNC URL discovery for visual debugging ([#4419](https://github.com/NousResearch/hermes-agent/pull/4419))
|
||||
- **Skip SSRF check for local backends** (Camofox, headless Chromium) ([#4292](https://github.com/NousResearch/hermes-agent/pull/4292))
|
||||
- **Configurable SSRF check** via `browser.allow_private_urls` ([#4198](https://github.com/NousResearch/hermes-agent/pull/4198)) — @nils010485
|
||||
- **CAMOFOX_PORT=9377** added to Docker commands ([#4340](https://github.com/NousResearch/hermes-agent/pull/4340))
|
||||
|
||||
### File Operations
|
||||
- **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))
|
||||
- **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))
|
||||
- **Staleness timestamp refreshed** after writes ([#4390](https://github.com/NousResearch/hermes-agent/pull/4390))
|
||||
- **Size guard, dedup, and device blocking** on read_file ([#4315](https://github.com/NousResearch/hermes-agent/pull/4315))
|
||||
|
||||
### MCP
|
||||
- **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))
|
||||
|
||||
### ACP (Editor Integration)
|
||||
- **Client-provided MCP servers** registered as agent tools — editors pass their MCP servers to Hermes ([#4705](https://github.com/NousResearch/hermes-agent/pull/4705))
|
||||
|
||||
### Skills System
|
||||
- **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))
|
||||
- **Validate hub bundle paths** before install — blocks path traversal in skill bundles ([#3986](https://github.com/NousResearch/hermes-agent/pull/3986))
|
||||
- **Unified hermes-agent and hermes-agent-setup** into single skill ([#4332](https://github.com/NousResearch/hermes-agent/pull/4332))
|
||||
- **Skill metadata type check** in extract_skill_conditions ([#4479](https://github.com/NousResearch/hermes-agent/pull/4479))
|
||||
|
||||
### New/Updated Skills
|
||||
- **research-paper-writing** — full end-to-end research pipeline (replaced ml-paper-writing) ([#4654](https://github.com/NousResearch/hermes-agent/pull/4654)) — @SHL0MS
|
||||
- **ascii-video** — text readability techniques and external layout oracle ([#4054](https://github.com/NousResearch/hermes-agent/pull/4054)) — @SHL0MS
|
||||
- **youtube-transcript** updated for youtube-transcript-api v1.x ([#4455](https://github.com/NousResearch/hermes-agent/pull/4455)) — @el-analista
|
||||
- **Skills browse and search page** added to documentation site ([#4500](https://github.com/NousResearch/hermes-agent/pull/4500)) — @IAvecilla
|
||||
|
||||
---
|
||||
|
||||
## 🔒 Security & Reliability
|
||||
|
||||
### Security Hardening
|
||||
- **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))
|
||||
- **Redact secrets from execute_code sandbox output** ([#4360](https://github.com/NousResearch/hermes-agent/pull/4360))
|
||||
- **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
|
||||
- **GitHub OAuth token patterns** added to redaction + snapshot redact flag ([#4295](https://github.com/NousResearch/hermes-agent/pull/4295))
|
||||
- **Reject private and loopback IPs** in Telegram DoH fallback ([#4129](https://github.com/NousResearch/hermes-agent/pull/4129))
|
||||
- **Reject path traversal** in credential file registration ([#4316](https://github.com/NousResearch/hermes-agent/pull/4316))
|
||||
- **Validate tar archive member paths** on profile import — blocks zip-slip attacks ([#4318](https://github.com/NousResearch/hermes-agent/pull/4318))
|
||||
- **Exclude auth.json and .env** from profile exports ([#4475](https://github.com/NousResearch/hermes-agent/pull/4475))
|
||||
|
||||
### Reliability
|
||||
- **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))
|
||||
- **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))
|
||||
- **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))
|
||||
- **OpenCode model routing** repaired ([#4508](https://github.com/NousResearch/hermes-agent/pull/4508))
|
||||
- **Docker container image** optimized ([#4034](https://github.com/NousResearch/hermes-agent/pull/4034)) — @bcross
|
||||
|
||||
### Windows & Cross-Platform
|
||||
- **Voice mode in WSL** with PulseAudio bridge ([#4317](https://github.com/NousResearch/hermes-agent/pull/4317))
|
||||
- **Homebrew packaging** preparation ([#4099](https://github.com/NousResearch/hermes-agent/pull/4099))
|
||||
- **CI fork conditionals** to prevent workflow failures on forks ([#4107](https://github.com/NousResearch/hermes-agent/pull/4107))
|
||||
|
||||
---
|
||||
|
||||
## 🐛 Notable Bug Fixes
|
||||
|
||||
- **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))
|
||||
- **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))
|
||||
- **Anthropic thinking blocks lost** across tool-use turns ([#4626](https://github.com/NousResearch/hermes-agent/pull/4626))
|
||||
- **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))
|
||||
- **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))
|
||||
- **Dragged file paths** treated as slash commands instead of file references ([#4533](https://github.com/NousResearch/hermes-agent/pull/4533)) — @rolme
|
||||
- **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))
|
||||
- **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))
|
||||
- **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))
|
||||
- **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))
|
||||
- **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))
|
||||
- **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))
|
||||
- **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))
|
||||
- **Mobile sidebar** shows only close button due to backdrop-filter issue in docs site ([#4207](https://github.com/NousResearch/hermes-agent/pull/4207)) — @xsmyile
|
||||
- **Config restore reverted** by stale-branch squash merge — `_config_version` fixed ([#4440](https://github.com/NousResearch/hermes-agent/pull/4440))
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Testing
|
||||
|
||||
- **Telegram gateway E2E tests** — full integration test suite for the Telegram adapter ([#4497](https://github.com/NousResearch/hermes-agent/pull/4497)) — @pefontana
|
||||
- **11 real test failures fixed** plus sys.modules cascade poisoner resolved ([#4570](https://github.com/NousResearch/hermes-agent/pull/4570))
|
||||
- **7 CI failures resolved** across hooks, plugins, and skill tests ([#3936](https://github.com/NousResearch/hermes-agent/pull/3936))
|
||||
- **Codex 401 refresh tests** updated for CI compatibility ([#4166](https://github.com/NousResearch/hermes-agent/pull/4166))
|
||||
- **Stale OPENAI_BASE_URL test** fixed ([#4217](https://github.com/NousResearch/hermes-agent/pull/4217))
|
||||
|
||||
---
|
||||
|
||||
## 📚 Documentation
|
||||
|
||||
- **Comprehensive documentation audit** — 9 HIGH and 20+ MEDIUM gaps fixed across 21 files ([#4087](https://github.com/NousResearch/hermes-agent/pull/4087))
|
||||
- **Site navigation restructured** — features and platforms promoted to top-level ([#4116](https://github.com/NousResearch/hermes-agent/pull/4116))
|
||||
- **Tool progress streaming** documented for API server and Open WebUI ([#4138](https://github.com/NousResearch/hermes-agent/pull/4138))
|
||||
- **Telegram webhook mode** documentation ([#4089](https://github.com/NousResearch/hermes-agent/pull/4089))
|
||||
- **Local LLM provider guides** — comprehensive setup guides with context length warnings ([#4294](https://github.com/NousResearch/hermes-agent/pull/4294))
|
||||
- **WhatsApp allowlist behavior** clarified with `WHATSAPP_ALLOW_ALL_USERS` documentation ([#4293](https://github.com/NousResearch/hermes-agent/pull/4293))
|
||||
- **Slack configuration options** — new config section in Slack docs ([#4644](https://github.com/NousResearch/hermes-agent/pull/4644))
|
||||
- **Terminal backends section** expanded + docs build fixes ([#4016](https://github.com/NousResearch/hermes-agent/pull/4016))
|
||||
- **Adding-providers guide** updated for unified setup flow ([#4201](https://github.com/NousResearch/hermes-agent/pull/4201))
|
||||
- **ACP Zed config** fixed ([#4743](https://github.com/NousResearch/hermes-agent/pull/4743))
|
||||
- **Community FAQ** entries for common workflows and troubleshooting ([#4797](https://github.com/NousResearch/hermes-agent/pull/4797))
|
||||
- **Skills browse and search page** on docs site ([#4500](https://github.com/NousResearch/hermes-agent/pull/4500)) — @IAvecilla
|
||||
|
||||
---
|
||||
|
||||
## 👥 Contributors
|
||||
|
||||
### Core
|
||||
- **@teknium1** — 135 commits across all subsystems
|
||||
|
||||
### Top Community Contributors
|
||||
- **@kshitijk4poor** — 13 commits: preserve allowed_users during setup ([#4551](https://github.com/NousResearch/hermes-agent/pull/4551)), and various fixes
|
||||
- **@erosika** — 12 commits: Honcho full integration parity restored as memory provider plugin ([#4355](https://github.com/NousResearch/hermes-agent/pull/4355))
|
||||
- **@pefontana** — 9 commits: Telegram gateway E2E test suite ([#4497](https://github.com/NousResearch/hermes-agent/pull/4497))
|
||||
- **@bcross** — 5 commits: Docker container image optimization ([#4034](https://github.com/NousResearch/hermes-agent/pull/4034))
|
||||
- **@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))
|
||||
|
||||
### All Contributors
|
||||
@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
|
||||
|
||||
### Issues Resolved from Community
|
||||
@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))
|
||||
|
||||
---
|
||||
|
||||
**Full Changelog**: [v2026.3.30...v2026.4.3](https://github.com/NousResearch/hermes-agent/compare/v2026.3.30...v2026.4.3)
|
||||
@@ -34,7 +34,7 @@ echo ""
|
||||
echo -e "${GREEN}[DEPLOY]${NC} Copying config to Ezra..."
|
||||
|
||||
# Backup existing
|
||||
ssh root@$EZRA_HOST "cp $EZRA_HERMES_HOME/config.yaml $EZRA_HERMES_HOME/config.yaml.backup.anthropic-$(date +%s) 2>/dev/null || true"
|
||||
ssh root@$EZRA_HOST "cp $EZRA_HERMES_HOME/config.yaml $EZRA_HERMES_HOME/config.yaml.backup.pre-kimi-$(date +%s) 2>/dev/null || true"
|
||||
|
||||
# Copy new config
|
||||
scp "$CONFIG_SOURCE" root@$EZRA_HOST:$EZRA_HERMES_HOME/config.yaml
|
||||
@@ -52,7 +52,7 @@ ssh root@$EZRA_HOST "cd $EZRA_HERMES_HOME && nohup python -m gateway.run > logs/
|
||||
echo ""
|
||||
echo -e "${GREEN}[SUCCESS]${NC} Ezra is now running Kimi primary!"
|
||||
echo ""
|
||||
echo "Anthropic: FIRED ✓"
|
||||
echo "Anthropic: BANNED ✓ (removed from all configs)"
|
||||
echo "Kimi: PRIMARY ✓"
|
||||
echo ""
|
||||
echo "To verify: ssh root@$EZRA_HOST 'tail -f $EZRA_HERMES_HOME/logs/gateway.log'"
|
||||
|
||||
@@ -2,22 +2,29 @@ model:
|
||||
default: kimi-k2.5
|
||||
provider: kimi-coding
|
||||
toolsets:
|
||||
- all
|
||||
- all
|
||||
fallback_providers:
|
||||
- provider: kimi-coding
|
||||
model: kimi-k2.5
|
||||
timeout: 120
|
||||
reason: Kimi coding fallback (front of chain)
|
||||
- provider: anthropic
|
||||
model: claude-sonnet-4-20250514
|
||||
timeout: 120
|
||||
reason: Direct Anthropic fallback
|
||||
- provider: openrouter
|
||||
model: anthropic/claude-sonnet-4-20250514
|
||||
base_url: https://openrouter.ai/api/v1
|
||||
api_key_env: OPENROUTER_API_KEY
|
||||
timeout: 120
|
||||
reason: OpenRouter fallback
|
||||
- provider: kimi-coding
|
||||
model: kimi-k2.5
|
||||
timeout: 120
|
||||
reason: Kimi coding fallback (front of chain)
|
||||
- provider: openrouter
|
||||
model: google/gemini-2.5-pro
|
||||
base_url: https://openrouter.ai/api/v1
|
||||
api_key_env: OPENROUTER_API_KEY
|
||||
timeout: 120
|
||||
reason: Gemini 2.5 Pro fallback (replaces banned Anthropic)
|
||||
- provider: openrouter
|
||||
model: google/gemini-2.5-pro
|
||||
base_url: https://openrouter.ai/api/v1
|
||||
api_key_env: OPENROUTER_API_KEY
|
||||
timeout: 120
|
||||
reason: Gemini 2.5 Pro via OpenRouter (replaces banned Anthropic)
|
||||
- provider: ollama
|
||||
model: gemma4:latest
|
||||
base_url: http://localhost:11434
|
||||
timeout: 300
|
||||
reason: "Terminal fallback \u2014 local Ollama"
|
||||
agent:
|
||||
max_turns: 90
|
||||
reasoning_effort: high
|
||||
@@ -27,8 +34,6 @@ providers:
|
||||
base_url: https://api.kimi.com/coding/v1
|
||||
timeout: 60
|
||||
max_retries: 3
|
||||
anthropic:
|
||||
timeout: 120
|
||||
openrouter:
|
||||
base_url: https://openrouter.ai/api/v1
|
||||
timeout: 120
|
||||
|
||||
@@ -1,53 +1,43 @@
|
||||
# Hermes Agent Fallback Configuration
|
||||
# Deploy this to Timmy and Ezra for automatic kimi-coding fallback
|
||||
# Primary: kimi-k2.5 | Fallback: gemini-2.5-pro | Terminal: local Ollama
|
||||
# Anthropic BANNED per BANNED_PROVIDERS.yml (2026-04-09)
|
||||
|
||||
model: anthropic/claude-opus-4.6
|
||||
|
||||
# Fallback chain: Anthropic -> Kimi -> Ollama (local)
|
||||
model: kimi-k2.5
|
||||
fallback_providers:
|
||||
- provider: kimi-coding
|
||||
model: kimi-k2.5
|
||||
timeout: 60
|
||||
reason: "Primary fallback when Anthropic quota limited"
|
||||
|
||||
- provider: ollama
|
||||
model: qwen2.5:7b
|
||||
base_url: http://localhost:11434
|
||||
timeout: 120
|
||||
reason: "Local fallback for offline operation"
|
||||
|
||||
# Provider settings
|
||||
- provider: kimi-coding
|
||||
model: kimi-k2.5
|
||||
timeout: 60
|
||||
reason: Gemini 2.5 Pro via OpenRouter (replaces banned Anthropic)
|
||||
- provider: openrouter
|
||||
model: google/gemini-2.5-pro
|
||||
base_url: https://openrouter.ai/api/v1
|
||||
api_key_env: OPENROUTER_API_KEY
|
||||
timeout: 120
|
||||
reason: Gemini 2.5 Pro via OpenRouter (replaces banned Anthropic)
|
||||
- provider: ollama
|
||||
model: qwen2.5:7b
|
||||
base_url: http://localhost:11434
|
||||
timeout: 120
|
||||
reason: Local fallback for offline operation
|
||||
providers:
|
||||
anthropic:
|
||||
timeout: 30
|
||||
retry_on_quota: true
|
||||
max_retries: 2
|
||||
|
||||
kimi-coding:
|
||||
timeout: 60
|
||||
max_retries: 3
|
||||
|
||||
ollama:
|
||||
timeout: 120
|
||||
keep_alive: true
|
||||
|
||||
# Toolsets
|
||||
toolsets:
|
||||
- hermes-cli
|
||||
- github
|
||||
- web
|
||||
|
||||
# Agent settings
|
||||
- hermes-cli
|
||||
- github
|
||||
- web
|
||||
agent:
|
||||
max_turns: 90
|
||||
tool_use_enforcement: auto
|
||||
fallback_on_errors:
|
||||
- rate_limit_exceeded
|
||||
- quota_exceeded
|
||||
- timeout
|
||||
- service_unavailable
|
||||
|
||||
# Display settings
|
||||
- rate_limit_exceeded
|
||||
- quota_exceeded
|
||||
- timeout
|
||||
- service_unavailable
|
||||
display:
|
||||
show_fallback_notifications: true
|
||||
show_provider_switches: true
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/bin/bash
|
||||
# Deploy fallback config to Timmy
|
||||
# Deploy kimi-primary config to Timmy (Anthropic BANNED)
|
||||
# Run this from Timmy's VPS or via SSH
|
||||
|
||||
set -e
|
||||
@@ -50,10 +50,10 @@ sleep 2
|
||||
ssh root@$TIMMY_HOST "cd $TIMMY_HERMES_HOME && nohup python -m gateway.run > logs/gateway.log 2>&1 &"
|
||||
|
||||
echo ""
|
||||
echo -e "${GREEN}[SUCCESS]${NC} Timmy is now running with Anthropic + Kimi fallback!"
|
||||
echo -e "${GREEN}[SUCCESS]${NC} Timmy is now running with Kimi primary + Gemini fallback!"
|
||||
echo ""
|
||||
echo "Anthropic: PRIMARY (with quota retry)"
|
||||
echo "Kimi: FALLBACK ✓"
|
||||
echo "Kimi: PRIMARY ✓"
|
||||
echo "Gemini: FALLBACK (via OpenRouter) ✓"
|
||||
echo "Ollama: LOCAL FALLBACK ✓"
|
||||
echo ""
|
||||
echo "To verify: ssh root@$TIMMY_HOST 'tail -f $TIMMY_HERMES_HOME/logs/gateway.log'"
|
||||
|
||||
@@ -609,6 +609,7 @@ def run_job(job: dict) -> tuple[bool, str, str, Optional[str]]:
|
||||
providers_order=pr.get("order"),
|
||||
provider_sort=pr.get("sort"),
|
||||
disabled_toolsets=["cronjob", "messaging", "clarify"],
|
||||
tool_choice="required",
|
||||
quiet_mode=True,
|
||||
skip_memory=True, # Cron system prompts would corrupt user representations
|
||||
platform="cron",
|
||||
|
||||
335
docs/browser-integration-analysis.md
Normal file
335
docs/browser-integration-analysis.md
Normal file
@@ -0,0 +1,335 @@
|
||||
# Browser Integration Analysis: Browser Use + Graphify + Multica
|
||||
|
||||
**Issue:** #262 — Investigation: Browser Use + Graphify + Multica — Hermes Integration Analysis
|
||||
**Date:** 2026-04-10
|
||||
**Author:** Hermes Agent (burn branch)
|
||||
|
||||
## Executive Summary
|
||||
|
||||
This document evaluates three browser-related projects for integration with
|
||||
hermes-agent. Each tool is assessed on capability, integration complexity,
|
||||
security posture, and strategic fit with Hermes's existing browser stack.
|
||||
|
||||
| Tool | Recommendation | Integration Path |
|
||||
|-------------------|-------------------------|-------------------------|
|
||||
| Browser Use | **Integrate** (PoC) | Tool + MCP server |
|
||||
| Graphify | Investigate further | MCP server or tool |
|
||||
| Multica | Skip (for now) | N/A — premature |
|
||||
|
||||
---
|
||||
|
||||
## 1. Browser Use (`browser-use`)
|
||||
|
||||
### What It Does
|
||||
|
||||
Browser Use is a Python library that wraps Playwright to provide LLM-driven
|
||||
browser automation. An agent describes a task in natural language, and
|
||||
browser-use autonomously navigates, clicks, types, and extracts data by
|
||||
feeding the page's accessibility tree to an LLM and executing the resulting
|
||||
actions in a loop.
|
||||
|
||||
Key capabilities:
|
||||
- Autonomous multi-step browser workflows from a single text instruction
|
||||
- Accessibility tree extraction (DOM + ARIA snapshot)
|
||||
- Screenshot and visual context for multimodal models
|
||||
- Form filling, navigation, data extraction, file downloads
|
||||
- Custom actions (register callable Python functions the LLM can invoke)
|
||||
- Parallel agent execution (multiple browser agents simultaneously)
|
||||
- Cloud execution via browser-use.com API (no local browser needed)
|
||||
|
||||
### Integration with Hermes
|
||||
|
||||
**Primary path: Custom Hermes tool** wrapping `browser-use` as a high-level
|
||||
"automated browsing" capability alongside the existing `browser_tool.py`
|
||||
(low-level, agent-controlled) tools.
|
||||
|
||||
**Why a separate tool rather than replacing browser_tool.py:**
|
||||
- Hermes's existing browser tools (navigate, snapshot, click, type) give the
|
||||
LLM fine-grained step-by-step control — this is valuable for interactive
|
||||
tasks and debugging.
|
||||
- browser-use gives coarse-grained "do this task for me" autonomy — better
|
||||
for multi-step extraction workflows where the LLM would otherwise need
|
||||
10+ tool calls.
|
||||
- Both modes have legitimate use cases. Offer both.
|
||||
|
||||
**Integration architecture:**
|
||||
|
||||
```
|
||||
hermes-agent
|
||||
tools/
|
||||
browser_tool.py # Existing — low-level agent-controlled browsing
|
||||
browser_use_tool.py # NEW — high-level autonomous browsing (PoC)
|
||||
|
|
||||
+-- browser_use.run() # Wraps browser-use Agent class
|
||||
+-- browser_use.extract() # Wraps browser-use for data extraction
|
||||
```
|
||||
|
||||
The tool registers with `tools/registry.py` as toolset `browser_use` with
|
||||
a `check_fn` that verifies `browser-use` is installed.
|
||||
|
||||
**Alternative: MCP server** — browser-use could also be exposed as an MCP
|
||||
server for multi-agent setups where subagents need independent browser
|
||||
access. This is a follow-up, not the initial integration.
|
||||
|
||||
### Dependencies and Requirements
|
||||
|
||||
```
|
||||
pip install browser-use # Core library
|
||||
playwright install chromium # Playwright browser binary
|
||||
```
|
||||
|
||||
Or use cloud mode with `BROWSER_USE_API_KEY` — no local browser needed.
|
||||
|
||||
Python 3.11+, Playwright. No exotic system dependencies beyond what
|
||||
Hermes already requires for its existing browser tool.
|
||||
|
||||
### Security Considerations
|
||||
|
||||
| Concern | Mitigation |
|
||||
|----------------------------|---------------------------------------------------------|
|
||||
| Arbitrary URL access | Reuse Hermes's `website_policy` and `url_safety` modules |
|
||||
| Data exfiltration | Browser-use agents run in isolated Playwright contexts; no access to Hermes filesystem |
|
||||
| Prompt injection via page | browser-use feeds page content to LLM — same risk as existing browser_snapshot; already handled by Hermes prompt hardening |
|
||||
| Credential leakage | Do not pass API keys to untrusted pages; cloud mode keeps credentials server-side |
|
||||
| Resource exhaustion | Set max_steps on browser-use Agent to prevent infinite loops |
|
||||
| Downloaded files | Playwright download path is sandboxed; tool should restrict to temp directory |
|
||||
|
||||
**Key security property:** browser-use executes within Playwright's sandboxed
|
||||
browser context. The LLM controlling browser-use is Hermes itself (or a
|
||||
configured auxiliary model), not the page content. This is equivalent to the
|
||||
existing browser tool's security model.
|
||||
|
||||
### Performance Characteristics
|
||||
|
||||
- **Startup:** ~2-3s for Playwright Chromium launch (same as existing local mode)
|
||||
- **Per-step:** ~1-3s per LLM call + browser action (comparable to manual
|
||||
browser_navigate + browser_snapshot loop)
|
||||
- **Full task (5-10 steps):** ~15-45s depending on page complexity
|
||||
- **Token usage:** Each step sends the accessibility tree to the LLM.
|
||||
Browser-use supports vision mode (screenshots) which is more token-heavy.
|
||||
- **Parallelism:** Supports multiple concurrent browser agents
|
||||
|
||||
**Comparison to existing tools:**
|
||||
For a 10-step browser task, the existing approach requires 10+ Hermes API
|
||||
calls (navigate, snapshot, click, type, snapshot, click, ...). Browser-use
|
||||
consolidates this into a single Hermes tool call that internally runs its
|
||||
own LLM loop. This reduces Hermes API round-trips but shifts the LLM cost
|
||||
to browser-use's internal model calls.
|
||||
|
||||
### Recommendation: INTEGRATE
|
||||
|
||||
Browser Use fills a clear gap — autonomous multi-step browser tasks — that
|
||||
complements Hermes's existing fine-grained browser tools. The integration
|
||||
is straightforward (Python library, same security model). A PoC tool is
|
||||
provided in `tools/browser_use_tool.py`.
|
||||
|
||||
---
|
||||
|
||||
## 2. Graphify
|
||||
|
||||
### What It Does
|
||||
|
||||
Graphify is a knowledge graph extraction tool that processes unstructured
|
||||
text (including web content) and extracts entities, relationships, and
|
||||
structured knowledge into a graph format. It can:
|
||||
|
||||
- Extract entities and relationships from text using NLP/LLM techniques
|
||||
- Build knowledge graphs from web-scraped content
|
||||
- Support incremental graph updates as new content is processed
|
||||
- Export graphs in standard formats (JSON-LD, RDF, etc.)
|
||||
|
||||
(Note: "Graphify" as a project name is used by several tools. The most
|
||||
relevant for browser integration is the concept of extracting structured
|
||||
knowledge graphs from web content during or after browsing.)
|
||||
|
||||
### Integration with Hermes
|
||||
|
||||
**Primary path: MCP server or Hermes tool** that takes web content (from
|
||||
browser_tool or web_extract) and produces structured knowledge graphs.
|
||||
|
||||
**Integration architecture:**
|
||||
|
||||
```
|
||||
hermes-agent
|
||||
tools/
|
||||
graphify_tool.py # NEW — knowledge graph extraction from text
|
||||
|
|
||||
+-- graphify.extract() # Extract entities/relations from text
|
||||
+-- graphify.merge() # Merge into existing graph
|
||||
+-- graphify.query() # Query the accumulated graph
|
||||
```
|
||||
|
||||
Or via MCP:
|
||||
```
|
||||
hermes-agent --mcp-server graphify-mcp
|
||||
-> tools: graphify_extract, graphify_query, graphify_export
|
||||
```
|
||||
|
||||
**Synergy with browser tools:**
|
||||
1. `browser_navigate` + `browser_snapshot` to get page content
|
||||
2. `graphify_extract` to pull entities and relationships
|
||||
3. Repeat across multiple pages to build a domain knowledge graph
|
||||
4. `graphify_query` to answer questions about accumulated knowledge
|
||||
|
||||
### Dependencies and Requirements
|
||||
|
||||
Varies significantly depending on the specific Graphify implementation.
|
||||
Typical requirements:
|
||||
- Python 3.11+
|
||||
- spaCy or similar NLP library for entity extraction
|
||||
- Optional: Neo4j or NetworkX for graph storage
|
||||
- LLM access (can reuse Hermes's existing model configuration)
|
||||
|
||||
### Security Considerations
|
||||
|
||||
| Concern | Mitigation |
|
||||
|----------------------------|---------------------------------------------------------|
|
||||
| Processing untrusted text | NLP extraction is read-only; no code execution |
|
||||
| Graph data persistence | Store in Hermes's data directory with appropriate permissions |
|
||||
| Information aggregation | Knowledge graphs could accumulate sensitive data; provide clear/delete commands |
|
||||
| External graph DB access | If using Neo4j, require authentication and restrict to localhost |
|
||||
|
||||
### Performance Characteristics
|
||||
|
||||
- **Extraction:** ~0.5-2s per page depending on content length and NLP model
|
||||
- **Graph operations:** Sub-second for graphs under 100K nodes
|
||||
- **Storage:** Lightweight (JSON/SQLite) for small graphs, Neo4j for large-scale
|
||||
- **Token usage:** If using LLM-based extraction, ~500-2000 tokens per page
|
||||
|
||||
### Recommendation: INVESTIGATE FURTHER
|
||||
|
||||
The concept is sound — knowledge graph extraction from web content is a
|
||||
natural complement to browser tools. However:
|
||||
|
||||
1. **Multiple competing tools** exist under this name; need to identify the
|
||||
best-maintained option
|
||||
2. **Value proposition unclear** vs. Hermes's existing memory system and
|
||||
file-based knowledge storage
|
||||
3. **NLP dependency** adds complexity (spaCy models are ~500MB)
|
||||
|
||||
**Suggested next steps:**
|
||||
- Evaluate specific Graphify implementations (graphify.ai, custom NLP pipelines)
|
||||
- Prototype with a lightweight approach: LLM-based entity extraction + NetworkX
|
||||
- Assess whether Hermes's existing memory/graph_store.py can serve this role
|
||||
|
||||
---
|
||||
|
||||
## 3. Multica
|
||||
|
||||
### What It Does
|
||||
|
||||
Multica is a multi-agent browser coordination framework. It enables multiple
|
||||
AI agents to collaboratively browse the web, with features for:
|
||||
|
||||
- Task decomposition: splitting complex web tasks across multiple agents
|
||||
- Shared browser state: agents see a common view of browsing progress
|
||||
- Coordination protocols: agents can communicate about what they've found
|
||||
- Parallel web research: multiple agents researching different aspects simultaneously
|
||||
|
||||
### Integration with Hermes
|
||||
|
||||
**Theoretical path:** Multica would integrate as a higher-level orchestration
|
||||
layer on top of Hermes's existing browser tools, coordinating multiple
|
||||
Hermes subagents (via `delegate_tool`) each with browser access.
|
||||
|
||||
**Integration architecture:**
|
||||
|
||||
```
|
||||
hermes-agent (orchestrator)
|
||||
delegate_tool -> subagent_1 (browser_navigate, browser_snapshot, ...)
|
||||
delegate_tool -> subagent_2 (browser_navigate, browser_snapshot, ...)
|
||||
delegate_tool -> subagent_3 (browser_navigate, browser_snapshot, ...)
|
||||
|
|
||||
+-- Multica coordination layer (shared state, task splitting)
|
||||
```
|
||||
|
||||
### Dependencies and Requirements
|
||||
|
||||
- Complex multi-agent orchestration infrastructure
|
||||
- Shared state management between agents
|
||||
- Potentially a custom runtime for agent coordination
|
||||
- Likely requires significant architectural changes to Hermes's delegation model
|
||||
|
||||
### Security Considerations
|
||||
|
||||
| Concern | Mitigation |
|
||||
|----------------------------|---------------------------------------------------------|
|
||||
| Multiple agents on same browser | Session isolation per agent (Hermes already does this) |
|
||||
| Coordinated exfiltration | Same per-agent restrictions apply |
|
||||
| Amplified prompt injection | Each agent processes its own pages independently |
|
||||
| Resource multiplication | N agents = N browser instances = Nx resource usage |
|
||||
|
||||
### Performance Characteristics
|
||||
|
||||
- **Scaling:** Near-linear improvement for embarrassingly parallel tasks
|
||||
(e.g., "research 10 companies simultaneously")
|
||||
- **Overhead:** Significant coordination overhead for tightly coupled tasks
|
||||
- **Resource cost:** Each agent needs its own LLM calls + browser instance
|
||||
- **Complexity:** Debugging multi-agent browser workflows is extremely difficult
|
||||
|
||||
### Recommendation: SKIP (for now)
|
||||
|
||||
Multica addresses a real need (parallel web research) but is premature for
|
||||
Hermes for several reasons:
|
||||
|
||||
1. **Hermes already has subagent delegation** (`delegate_tool`) — agents can
|
||||
already do parallel browser work without Multica
|
||||
2. **No mature implementation** — Multica is more of a concept than a
|
||||
production-ready tool
|
||||
3. **Complexity vs. benefit** — the coordination overhead and debugging
|
||||
difficulty outweigh the benefits for most use cases
|
||||
4. **Better alternatives exist** — for parallel research, simply delegating
|
||||
multiple subagents with browser tools is simpler and already works
|
||||
|
||||
**Revisit when:** Hermes's delegation model supports shared state between
|
||||
subagents, or a mature Multica implementation emerges.
|
||||
|
||||
---
|
||||
|
||||
## Integration Roadmap
|
||||
|
||||
### Phase 1: Browser Use PoC (this PR)
|
||||
- [x] Create `tools/browser_use_tool.py` wrapping browser-use as Hermes tool
|
||||
- [x] Create `docs/browser-integration-analysis.md` (this document)
|
||||
- [ ] Test with real browser tasks
|
||||
- [ ] Add to toolset configuration
|
||||
|
||||
### Phase 2: Browser Use Production (follow-up)
|
||||
- [ ] Add `browser_use` to `toolsets.py` toolset definitions
|
||||
- [ ] Add configuration options in `config.yaml`
|
||||
- [ ] Add tests in `tests/test_browser_use_tool.py`
|
||||
- [ ] Consider MCP server variant for subagent use
|
||||
|
||||
### Phase 3: Graphify Investigation (follow-up)
|
||||
- [ ] Evaluate specific Graphify implementations
|
||||
- [ ] Prototype lightweight LLM-based entity extraction tool
|
||||
- [ ] Assess integration with existing `graph_store.py`
|
||||
- [ ] Create PoC if investigation is positive
|
||||
|
||||
### Phase 4: Multi-Agent Browser (future)
|
||||
- [ ] Monitor Multica ecosystem maturity
|
||||
- [ ] Evaluate when delegation model supports shared state
|
||||
- [ ] Consider simpler parallel delegation patterns first
|
||||
|
||||
---
|
||||
|
||||
## Appendix: Existing Browser Stack
|
||||
|
||||
Hermes already has a comprehensive browser tool stack:
|
||||
|
||||
| Component | Description |
|
||||
|-----------------------|--------------------------------------------------|
|
||||
| `browser_tool.py` | Low-level agent-controlled browser (navigate, click, type, snapshot) |
|
||||
| `browser_camofox.py` | Anti-detection browser via Camofox REST API |
|
||||
| `browser_providers/` | Cloud providers (Browserbase, Browser Use API, Firecrawl) |
|
||||
| `web_tools.py` | Web search (Parallel) and extraction (Firecrawl) |
|
||||
| `mcp_tool.py` | MCP client for connecting external tool servers |
|
||||
|
||||
The existing stack covers:
|
||||
- **Local browsing:** Headless Chromium via agent-browser CLI
|
||||
- **Cloud browsing:** Browserbase, Browser Use cloud, Firecrawl
|
||||
- **Anti-detection:** Camofox (local) or Browserbase advanced stealth
|
||||
- **Content extraction:** Firecrawl for clean markdown extraction
|
||||
- **Search:** Parallel AI web search
|
||||
|
||||
New browser integrations should complement rather than replace these tools.
|
||||
477
docs/hermes-agent-census.md
Normal file
477
docs/hermes-agent-census.md
Normal file
@@ -0,0 +1,477 @@
|
||||
# Hermes Agent — Feature Census
|
||||
|
||||
**Epic:** [#290 — Know Thy Agent: Hermes Feature Census](https://forge.alexanderwhitestone.com/Timmy_Foundation/hermes-agent/issues/290)
|
||||
**Date:** 2026-04-11
|
||||
**Source:** Timmy_Foundation/hermes-agent (fork of NousResearch/hermes-agent)
|
||||
**Upstream:** NousResearch/hermes-agent (last sync: 2026-04-07, 499 commits merged in PR #201)
|
||||
**Codebase:** ~200K lines Python (335 source files), 470 test files
|
||||
|
||||
---
|
||||
|
||||
## 1. Feature Matrix
|
||||
|
||||
### 1.1 Memory System
|
||||
|
||||
| Feature | Status | File:Line | Notes |
|
||||
|---------|--------|-----------|-------|
|
||||
| **`add` action** | ✅ Exists | `tools/memory_tool.py:457` | Append entry to MEMORY.md or USER.md |
|
||||
| **`replace` action** | ✅ Exists | `tools/memory_tool.py:466` | Find by substring, replace content |
|
||||
| **`remove` action** | ✅ Exists | `tools/memory_tool.py:475` | Find by substring, delete entry |
|
||||
| **Dual stores (memory + user)** | ✅ Exists | `tools/memory_tool.py:43-45` | MEMORY.md (2200 char limit) + USER.md (1375 char limit) |
|
||||
| **Entry deduplication** | ✅ Exists | `tools/memory_tool.py:128-129` | Exact-match dedup on load |
|
||||
| **Injection/exfiltration scanning** | ✅ Exists | `tools/memory_tool.py:85` | Blocks prompt injection, role hijacking, secret exfil |
|
||||
| **Frozen snapshot pattern** | ✅ Exists | `tools/memory_tool.py:119-135` | Preserves LLM prefix cache across session |
|
||||
| **Atomic writes** | ✅ Exists | `tools/memory_tool.py:417-436` | tempfile.mkstemp + os.replace |
|
||||
| **File locking (fcntl)** | ✅ Exists | `tools/memory_tool.py:137-153` | Exclusive lock for concurrent safety |
|
||||
| **External provider plugin** | ✅ Exists | `agent/memory_manager.py` | Supports 1 external provider (Honcho, Mem0, Hindsight, etc.) |
|
||||
| **Provider lifecycle hooks** | ✅ Exists | `agent/memory_provider.py:55-66` | on_memory_write, prefetch, sync_turn, on_session_end, on_pre_compress, on_delegation |
|
||||
| **Session search (past conversations)** | ✅ Exists | `tools/session_search_tool.py:492` | FTS5 search across SQLite message store |
|
||||
| **Holographic memory** | 🔌 Plugin slot | Config `memory.provider` | Accepted as external provider name, not built-in |
|
||||
| **Engram integration** | ❌ Not present | — | Not in codebase; Engram is a Timmy Foundation project |
|
||||
| **Trust system** | ❌ Not present | — | No trust scoring on memory entries |
|
||||
|
||||
### 1.2 Tool System
|
||||
|
||||
| Feature | Status | File:Line | Notes |
|
||||
|---------|--------|-----------|-------|
|
||||
| **Central registry** | ✅ Exists | `tools/registry.py:290` | Module-level singleton, all tools self-register |
|
||||
| **47 static tools** | ✅ Exists | See full list below | Organized in 21+ toolsets |
|
||||
| **Dynamic MCP tools** | ✅ Exists | `tools/mcp_tool.py` | Runtime registration from MCP servers (17 in live instance) |
|
||||
| **Tool approval system** | ✅ Exists | `tools/approval.py` | Manual/smart/off modes, dangerous command detection |
|
||||
| **Toolset composition** | ✅ Exists | `toolsets.py:404` | Composite toolsets (e.g., `debugging = terminal + web + file`) |
|
||||
| **Per-platform toolsets** | ✅ Exists | `toolsets.py` | `hermes-cli`, `hermes-telegram`, `hermes-discord`, etc. |
|
||||
| **Skill management** | ✅ Exists | `tools/skill_manager_tool.py:747` | Create, patch, delete skill documents |
|
||||
| **Mixture of Agents** | ✅ Exists | `tools/mixture_of_agents_tool.py:553` | Route through 4+ frontier LLMs |
|
||||
| **Subagent delegation** | ✅ Exists | `tools/delegate_tool.py:963` | Isolated contexts, up to 3 parallel |
|
||||
| **Code execution sandbox** | ✅ Exists | `tools/code_execution_tool.py:1360` | Python scripts with tool access |
|
||||
| **Image generation** | ✅ Exists | `tools/image_generation_tool.py:694` | FLUX 2 Pro |
|
||||
| **Vision analysis** | ✅ Exists | `tools/vision_tools.py:606` | Multi-provider vision |
|
||||
| **Text-to-speech** | ✅ Exists | `tools/tts_tool.py:974` | Edge TTS, ElevenLabs, OpenAI, NeuTTS |
|
||||
| **Speech-to-text** | ✅ Exists | Config `stt.*` | Local Whisper, Groq, OpenAI, Mistral Voxtral |
|
||||
| **Home Assistant** | ✅ Exists | `tools/homeassistant_tool.py:456-483` | 4 HA tools (list, state, services, call) |
|
||||
| **RL training** | ✅ Exists | `tools/rl_training_tool.py:1376-1394` | 10 Tinker-Atropos tools |
|
||||
| **Browser automation** | ✅ Exists | `tools/browser_tool.py:2137-2211` | 10 tools (navigate, click, type, scroll, screenshot, etc.) |
|
||||
| **Gitea client** | ✅ Exists | `tools/gitea_client.py` | Gitea API integration |
|
||||
| **Cron job management** | ✅ Exists | `tools/cronjob_tools.py:508` | Scheduled task CRUD |
|
||||
| **Send message** | ✅ Exists | `tools/send_message_tool.py:1036` | Cross-platform messaging |
|
||||
|
||||
#### Complete Tool List (47 static)
|
||||
|
||||
| # | Tool | Toolset | File:Line |
|
||||
|---|------|---------|-----------|
|
||||
| 1 | `read_file` | file | `tools/file_tools.py:832` |
|
||||
| 2 | `write_file` | file | `tools/file_tools.py:833` |
|
||||
| 3 | `patch` | file | `tools/file_tools.py:834` |
|
||||
| 4 | `search_files` | file | `tools/file_tools.py:835` |
|
||||
| 5 | `terminal` | terminal | `tools/terminal_tool.py:1783` |
|
||||
| 6 | `process` | terminal | `tools/process_registry.py:1039` |
|
||||
| 7 | `web_search` | web | `tools/web_tools.py:2082` |
|
||||
| 8 | `web_extract` | web | `tools/web_tools.py:2092` |
|
||||
| 9 | `vision_analyze` | vision | `tools/vision_tools.py:606` |
|
||||
| 10 | `image_generate` | image_gen | `tools/image_generation_tool.py:694` |
|
||||
| 11 | `text_to_speech` | tts | `tools/tts_tool.py:974` |
|
||||
| 12 | `skills_list` | skills | `tools/skills_tool.py:1357` |
|
||||
| 13 | `skill_view` | skills | `tools/skills_tool.py:1367` |
|
||||
| 14 | `skill_manage` | skills | `tools/skill_manager_tool.py:747` |
|
||||
| 15 | `browser_navigate` | browser | `tools/browser_tool.py:2137` |
|
||||
| 16 | `browser_snapshot` | browser | `tools/browser_tool.py:2145` |
|
||||
| 17 | `browser_click` | browser | `tools/browser_tool.py:2154` |
|
||||
| 18 | `browser_type` | browser | `tools/browser_tool.py:2162` |
|
||||
| 19 | `browser_scroll` | browser | `tools/browser_tool.py:2170` |
|
||||
| 20 | `browser_back` | browser | `tools/browser_tool.py:2178` |
|
||||
| 21 | `browser_press` | browser | `tools/browser_tool.py:2186` |
|
||||
| 22 | `browser_get_images` | browser | `tools/browser_tool.py:2195` |
|
||||
| 23 | `browser_vision` | browser | `tools/browser_tool.py:2203` |
|
||||
| 24 | `browser_console` | browser | `tools/browser_tool.py:2211` |
|
||||
| 25 | `todo` | todo | `tools/todo_tool.py:260` |
|
||||
| 26 | `memory` | memory | `tools/memory_tool.py:544` |
|
||||
| 27 | `session_search` | session_search | `tools/session_search_tool.py:492` |
|
||||
| 28 | `clarify` | clarify | `tools/clarify_tool.py:131` |
|
||||
| 29 | `execute_code` | code_execution | `tools/code_execution_tool.py:1360` |
|
||||
| 30 | `delegate_task` | delegation | `tools/delegate_tool.py:963` |
|
||||
| 31 | `cronjob` | cronjob | `tools/cronjob_tools.py:508` |
|
||||
| 32 | `send_message` | messaging | `tools/send_message_tool.py:1036` |
|
||||
| 33 | `mixture_of_agents` | moa | `tools/mixture_of_agents_tool.py:553` |
|
||||
| 34 | `ha_list_entities` | homeassistant | `tools/homeassistant_tool.py:456` |
|
||||
| 35 | `ha_get_state` | homeassistant | `tools/homeassistant_tool.py:465` |
|
||||
| 36 | `ha_list_services` | homeassistant | `tools/homeassistant_tool.py:474` |
|
||||
| 37 | `ha_call_service` | homeassistant | `tools/homeassistant_tool.py:483` |
|
||||
| 38-47 | `rl_*` (10 tools) | rl | `tools/rl_training_tool.py:1376-1394` |
|
||||
|
||||
### 1.3 Session System
|
||||
|
||||
| Feature | Status | File:Line | Notes |
|
||||
|---------|--------|-----------|-------|
|
||||
| **Session creation** | ✅ Exists | `gateway/session.py:676` | get_or_create_session with auto-reset |
|
||||
| **Session keying** | ✅ Exists | `gateway/session.py:429` | platform:chat_type:chat_id[:thread_id][:user_id] |
|
||||
| **Reset policies** | ✅ Exists | `gateway/session.py:610` | none / idle / daily / both |
|
||||
| **Session switching (/resume)** | ✅ Exists | `gateway/session.py:825` | Point key at a previous session ID |
|
||||
| **Session branching (/branch)** | ✅ Exists | CLI commands.py | Fork conversation history |
|
||||
| **SQLite persistence** | ✅ Exists | `hermes_state.py:41-94` | sessions + messages + FTS5 search |
|
||||
| **JSONL dual-write** | ✅ Exists | `gateway/session.py:891` | Backward compatibility with legacy format |
|
||||
| **WAL mode concurrency** | ✅ Exists | `hermes_state.py:157` | Concurrent read/write with retry |
|
||||
| **Context compression** | ✅ Exists | Config `compression.*` | Auto-compress when context exceeds ratio |
|
||||
| **Memory flush on reset** | ✅ Exists | `gateway/run.py:632` | Reviews old transcript before auto-reset |
|
||||
| **Token/cost tracking** | ✅ Exists | `hermes_state.py:41` | input, output, cache_read, cache_write, reasoning tokens |
|
||||
| **PII redaction** | ✅ Exists | Config `privacy.redact_pii` | Hash user IDs, strip phone numbers |
|
||||
|
||||
### 1.4 Plugin System
|
||||
|
||||
| Feature | Status | File:Line | Notes |
|
||||
|---------|--------|-----------|-------|
|
||||
| **Plugin discovery** | ✅ Exists | `hermes_cli/plugins.py:5-11` | User (~/.hermes/plugins/), project, pip entry-points |
|
||||
| **Plugin manifest (plugin.yaml)** | ✅ Exists | `hermes_cli/plugins.py` | name, version, requires_env, provides_tools, provides_hooks |
|
||||
| **Lifecycle hooks** | ✅ Exists | `hermes_cli/plugins.py:55-66` | 9 hooks (pre/post tool_call, llm_call, api_request; on_session_start/end/finalize/reset) |
|
||||
| **PluginContext API** | ✅ Exists | `hermes_cli/plugins.py:124-233` | register_tool, inject_message, register_cli_command, register_hook |
|
||||
| **Plugin management CLI** | ✅ Exists | `hermes_cli/plugins_cmd.py:1-690` | install, update, remove, enable, disable |
|
||||
| **Project plugins (opt-in)** | ✅ Exists | `hermes_cli/plugins.py` | Requires HERMES_ENABLE_PROJECT_PLUGINS env var |
|
||||
| **Pip plugins** | ✅ Exists | `hermes_cli/plugins.py` | Entry-point group: hermes_agent.plugins |
|
||||
|
||||
### 1.5 Config System
|
||||
|
||||
| Feature | Status | File:Line | Notes |
|
||||
|---------|--------|-----------|-------|
|
||||
| **YAML config** | ✅ Exists | `hermes_cli/config.py:259-619` | ~120 config keys across 25 sections |
|
||||
| **Schema versioning** | ✅ Exists | `hermes_cli/config.py` | `_config_version: 14` with migration support |
|
||||
| **Provider config** | ✅ Exists | Config `providers.*`, `fallback_providers` | Per-provider overrides, fallback chains |
|
||||
| **Credential pooling** | ✅ Exists | Config `credential_pool_strategies` | Key rotation strategies |
|
||||
| **Auxiliary model config** | ✅ Exists | Config `auxiliary.*` | 8 separate side-task models (vision, compression, etc.) |
|
||||
| **Smart model routing** | ✅ Exists | Config `smart_model_routing.*` | Route simple prompts to cheap model |
|
||||
| **Env var management** | ✅ Exists | `hermes_cli/config.py:643-1318` | ~80 env vars across provider/tool/messaging/setting categories |
|
||||
| **Interactive setup wizard** | ✅ Exists | `hermes_cli/setup.py` | Guided first-run configuration |
|
||||
| **Config migration** | ✅ Exists | `hermes_cli/config.py` | Auto-migrates old config versions |
|
||||
|
||||
### 1.6 Gateway
|
||||
|
||||
| Feature | Status | File:Line | Notes |
|
||||
|---------|--------|-----------|-------|
|
||||
| **18 platform adapters** | ✅ Exists | `gateway/platforms/` | Telegram, Discord, Slack, WhatsApp, Signal, Mattermost, Matrix, HomeAssistant, Email, SMS, DingTalk, API Server, Webhook, Feishu, Wecom, Weixin, BlueBubbles |
|
||||
| **Message queuing** | ✅ Exists | `gateway/run.py:507` | Queue during agent processing, media placeholder support |
|
||||
| **Agent caching** | ✅ Exists | `gateway/run.py:515` | Preserve AIAgent instances per session for prompt caching |
|
||||
| **Background reconnection** | ✅ Exists | `gateway/run.py:527` | Exponential backoff for failed platforms |
|
||||
| **Authorization** | ✅ Exists | `gateway/run.py:1826` | Per-user allowlists, DM pairing codes |
|
||||
| **Slash command interception** | ✅ Exists | `gateway/run.py` | Commands handled before agent (not billed) |
|
||||
| **ACP server** | ✅ Exists | `acp_adapter/server.py:726` | VS Code / Zed / JetBrains integration |
|
||||
| **Cron scheduler** | ✅ Exists | `cron/scheduler.py:850` | Full job scheduler with cron expressions |
|
||||
| **Batch runner** | ✅ Exists | `batch_runner.py:1285` | Parallel batch processing |
|
||||
| **API server** | ✅ Exists | `gateway/platforms/api_server.py` | OpenAI-compatible HTTP API |
|
||||
|
||||
### 1.7 Providers (20 supported)
|
||||
|
||||
| Provider | ID | Key Env Var |
|
||||
|----------|----|-------------|
|
||||
| Nous Portal | `nous` | `NOUS_BASE_URL` |
|
||||
| OpenRouter | `openrouter` | `OPENROUTER_API_KEY` |
|
||||
| Anthropic | `anthropic` | (standard) |
|
||||
| Google AI Studio | `gemini` | `GOOGLE_API_KEY`, `GEMINI_API_KEY` |
|
||||
| OpenAI Codex | `openai-codex` | (standard) |
|
||||
| GitHub Copilot | `copilot` / `copilot-acp` | (OAuth) |
|
||||
| DeepSeek | `deepseek` | `DEEPSEEK_API_KEY` |
|
||||
| Kimi / Moonshot | `kimi-coding` | `KIMI_API_KEY` |
|
||||
| Z.AI / GLM | `zai` | `GLM_API_KEY`, `ZAI_API_KEY` |
|
||||
| MiniMax | `minimax` | `MINIMAX_API_KEY` |
|
||||
| MiniMax (China) | `minimax-cn` | `MINIMAX_CN_API_KEY` |
|
||||
| Alibaba / DashScope | `alibaba` | `DASHSCOPE_API_KEY` |
|
||||
| Hugging Face | `huggingface` | `HF_TOKEN` |
|
||||
| OpenCode Zen | `opencode-zen` | `OPENCODE_ZEN_API_KEY` |
|
||||
| OpenCode Go | `opencode-go` | `OPENCODE_GO_API_KEY` |
|
||||
| Qwen OAuth | `qwen-oauth` | (Portal) |
|
||||
| AI Gateway | `ai-gateway` | (Nous) |
|
||||
| Kilo Code | `kilocode` | (standard) |
|
||||
| Ollama (local) | — | First-class via auxiliary wiring |
|
||||
| Custom endpoint | `custom` | user-provided URL |
|
||||
|
||||
### 1.8 UI / UX
|
||||
|
||||
| Feature | Status | File:Line | Notes |
|
||||
|---------|--------|-----------|-------|
|
||||
| **Skin/theme engine** | ✅ Exists | `hermes_cli/skin_engine.py` | 7 built-in skins, user YAML skins |
|
||||
| **Kawaii spinner** | ✅ Exists | `agent/display.py` | Animated faces, configurable verbs/wings |
|
||||
| **Rich banner** | ✅ Exists | `banner.py` | Logo, hero art, system info |
|
||||
| **Prompt_toolkit input** | ✅ Exists | `cli.py` | Autocomplete, history, syntax |
|
||||
| **Streaming output** | ✅ Exists | Config `display.streaming` | Optional streaming |
|
||||
| **Reasoning display** | ✅ Exists | Config `display.show_reasoning` | Show/hide chain-of-thought |
|
||||
| **Cost display** | ✅ Exists | Config `display.show_cost` | Show $ in status bar |
|
||||
| **Voice mode** | ✅ Exists | Config `voice.*` | Ctrl+B record, auto-TTS, silence detection |
|
||||
| **Human delay simulation** | ✅ Exists | Config `human_delay.*` | Simulated typing delay |
|
||||
|
||||
### 1.9 Security
|
||||
|
||||
| Feature | Status | File:Line | Notes |
|
||||
|---------|--------|-----------|-------|
|
||||
| **Tirith security scanning** | ✅ Exists | `tools/tirith_security.py` | Pre-exec code scanning |
|
||||
| **Secret redaction** | ✅ Exists | Config `security.redact_secrets` | Auto-strip secrets from output |
|
||||
| **Memory injection scanning** | ✅ Exists | `tools/memory_tool.py:85` | Blocks prompt injection in memory |
|
||||
| **URL safety** | ✅ Exists | `tools/url_safety.py` | URL reputation checking |
|
||||
| **Command approval** | ✅ Exists | `tools/approval.py` | Manual/smart/off modes |
|
||||
| **OSV vulnerability check** | ✅ Exists | `tools/osv_check.py` | Open Source Vulnerabilities DB |
|
||||
| **Conscience validator** | ✅ Exists | `tools/conscience_validator.py` | SOUL.md alignment checking |
|
||||
| **Shield detector** | ✅ Exists | `tools/shield/detector.py` | Jailbreak/crisis detection |
|
||||
|
||||
---
|
||||
|
||||
## 2. Architecture Overview
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ Entry Points │
|
||||
├──────────┬──────────┬──────────┬──────────┬─────────────┤
|
||||
│ CLI │ Gateway │ ACP │ Cron │ Batch Runner│
|
||||
│ cli.py │gateway/ │acp_apt/ │ cron/ │batch_runner │
|
||||
│ 8620 ln │ run.py │server.py │sched.py │ 1285 ln │
|
||||
│ │ 7905 ln │ 726 ln │ 850 ln │ │
|
||||
└────┬─────┴────┬─────┴──────────┴──────┬───┴─────────────┘
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ AIAgent (run_agent.py, 9423 ln) │
|
||||
│ ┌──────────────────────────────────────────────────┐ │
|
||||
│ │ Core Conversation Loop │ │
|
||||
│ │ while iterations < max: │ │
|
||||
│ │ response = client.chat(tools, messages) │ │
|
||||
│ │ if tool_calls: handle_function_call() │ │
|
||||
│ │ else: return response │ │
|
||||
│ └──────────────────────┬───────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ┌──────────────────────▼───────────────────────────┐ │
|
||||
│ │ model_tools.py (577 ln) │ │
|
||||
│ │ _discover_tools() → handle_function_call() │ │
|
||||
│ └──────────────────────┬───────────────────────────┘ │
|
||||
└─────────────────────────┼───────────────────────────────┘
|
||||
│
|
||||
┌────────────────────▼────────────────────┐
|
||||
│ tools/registry.py (singleton) │
|
||||
│ ToolRegistry.register() → dispatch() │
|
||||
└────────────────────┬────────────────────┘
|
||||
│
|
||||
┌─────────┬───────────┼───────────┬────────────────┐
|
||||
▼ ▼ ▼ ▼ ▼
|
||||
┌────────┐┌────────┐┌──────────┐┌──────────┐ ┌──────────┐
|
||||
│ file ││terminal││ web ││ browser │ │ memory │
|
||||
│ tools ││ tool ││ tools ││ tool │ │ tool │
|
||||
│ 4 tools││2 tools ││ 2 tools ││ 10 tools │ │ 3 actions│
|
||||
└────────┘└────────┘└──────────┘└──────────┘ └────┬─────┘
|
||||
│
|
||||
┌──────────▼──────────┐
|
||||
│ agent/memory_manager │
|
||||
│ ┌──────────────────┐│
|
||||
│ │BuiltinProvider ││
|
||||
│ │(MEMORY.md+USER.md)│
|
||||
│ ├──────────────────┤│
|
||||
│ │External Provider ││
|
||||
│ │(optional, 1 max) ││
|
||||
│ └──────────────────┘│
|
||||
└─────────────────────┘
|
||||
|
||||
┌─────────────────────────────────────────────────┐
|
||||
│ Session Layer │
|
||||
│ SessionStore (gateway/session.py, 1030 ln) │
|
||||
│ SessionDB (hermes_state.py, 1238 ln) │
|
||||
│ ┌───────────┐ ┌─────────────────────────────┐ │
|
||||
│ │sessions.js│ │ state.db (SQLite + FTS5) │ │
|
||||
│ │ JSONL │ │ sessions │ messages │ fts │ │
|
||||
│ └───────────┘ └─────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────┘
|
||||
|
||||
┌─────────────────────────────────────────────────┐
|
||||
│ Gateway Platform Adapters │
|
||||
│ telegram │ discord │ slack │ whatsapp │ signal │
|
||||
│ matrix │ email │ sms │ mattermost│ api │
|
||||
│ homeassistant │ dingtalk │ feishu │ wecom │ ... │
|
||||
└─────────────────────────────────────────────────┘
|
||||
|
||||
┌─────────────────────────────────────────────────┐
|
||||
│ Plugin System │
|
||||
│ User ~/.hermes/plugins/ │ Project .hermes/ │
|
||||
│ Pip entry-points (hermes_agent.plugins) │
|
||||
│ 9 lifecycle hooks │ PluginContext API │
|
||||
└─────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Key dependency chain:**
|
||||
```
|
||||
tools/registry.py (no deps — imported by all tool files)
|
||||
↑
|
||||
tools/*.py (each calls registry.register() at import time)
|
||||
↑
|
||||
model_tools.py (imports tools/registry + triggers tool discovery)
|
||||
↑
|
||||
run_agent.py, cli.py, batch_runner.py, environments/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Recent Development Activity (Last 30 Days)
|
||||
|
||||
### Activity Summary
|
||||
|
||||
| Metric | Value |
|
||||
|--------|-------|
|
||||
| Total commits (since 2026-03-12) | ~1,750 |
|
||||
| Top contributor | Teknium (1,169 commits) |
|
||||
| Timmy Foundation commits | ~55 (Alexander Whitestone: 21, Timmy Time: 22, Bezalel: 12) |
|
||||
| Key upstream sync | PR #201 — 499 commits from NousResearch/hermes-agent (2026-04-07) |
|
||||
|
||||
### Top Contributors (Last 30 Days)
|
||||
|
||||
| Contributor | Commits | Focus Area |
|
||||
|-------------|---------|------------|
|
||||
| Teknium | 1,169 | Core features, bug fixes, streaming, browser, Telegram/Discord |
|
||||
| teknium1 | 238 | Supplementary work |
|
||||
| 0xbyt4 | 117 | Various |
|
||||
| Test | 61 | Testing |
|
||||
| Allegro | 49 | Fleet ops, CI |
|
||||
| kshitijk4poor | 30 | Features |
|
||||
| SHL0MS | 25 | Features |
|
||||
| Google AI Agent | 23 | MemPalace plugin |
|
||||
| Timmy Time | 22 | CI, fleet config, merge coordination |
|
||||
| Alexander Whitestone | 21 | Memory fixes, browser PoC, docs, CI, provider config |
|
||||
| Bezalel | 12 | CI pipeline, devkit, health checks |
|
||||
|
||||
### Key Upstream Changes (Merged in Last 30 Days)
|
||||
|
||||
| Change | PR | Impact |
|
||||
|--------|----|--------|
|
||||
| Browser provider switch (Browserbase → Browser Use) | upstream #5750 | Breaking change in browser tooling |
|
||||
| notify_on_complete for background processes | upstream #5779 | New feature for async workflows |
|
||||
| Interactive model picker (Telegram + Discord) | upstream #5742 | UX improvement |
|
||||
| Streaming fix after tool boundaries | upstream #5739 | Bug fix |
|
||||
| Delegate: share credential pools with subagents | upstream | Security improvement |
|
||||
| Permanent command allowlist on startup | upstream #5076 | Bug fix |
|
||||
| Paginated model picker for Telegram | upstream | UX improvement |
|
||||
| Slack thread replies without @mentions | upstream | Gateway improvement |
|
||||
| Supermemory memory provider (added then removed) | upstream | Experimental, rolled back |
|
||||
| Background process management overhaul | upstream | Major feature |
|
||||
|
||||
### Timmy Foundation Contributions (Our Fork)
|
||||
|
||||
| Change | PR | Author |
|
||||
|--------|----|--------|
|
||||
| Memory remove action bridge fix | #277 | Alexander Whitestone |
|
||||
| Browser integration PoC + analysis | #262 | Alexander Whitestone |
|
||||
| Memory budget enforcement tool | #256 | Alexander Whitestone |
|
||||
| Memory sovereignty verification | #257 | Alexander Whitestone |
|
||||
| Memory Architecture Guide | #263, #258 | Alexander Whitestone |
|
||||
| MemPalace plugin creation | #259, #265 | Google AI Agent |
|
||||
| CI: duplicate model detection | #235 | Alexander Whitestone |
|
||||
| Kimi model config fix | #225 | Bezalel |
|
||||
| Ollama provider wiring fix | #223 | Alexander Whitestone |
|
||||
| Deep Self-Awareness Epic | #215 | Bezalel |
|
||||
| BOOT.md for repo | #202 | Bezalel |
|
||||
| Upstream sync (499 commits) | #201 | Alexander Whitestone |
|
||||
| Forge CI pipeline | #154, #175, #187 | Bezalel |
|
||||
| Gitea PR & Issue automation skill | #181 | Bezalel |
|
||||
| Development tools for wizard fleet | #166 | Bezalel |
|
||||
| KNOWN_VIOLATIONS justification | #267 | Manus AI |
|
||||
|
||||
---
|
||||
|
||||
## 4. Overlap Analysis
|
||||
|
||||
### What We're Building That Already Exists
|
||||
|
||||
| Timmy Foundation Planned Work | Hermes-Agent Already Has | Verdict |
|
||||
|------------------------------|--------------------------|---------|
|
||||
| **Memory system (add/remove/replace)** | `tools/memory_tool.py` with all 3 actions | **USE IT** — already exists, we just needed the `remove` fix (PR #277) |
|
||||
| **Session persistence** | SQLite + JSONL dual-write system | **USE IT** — battle-tested, FTS5 search included |
|
||||
| **Gateway platform adapters** | 18 adapters including Telegram, Discord, Matrix | **USE IT** — don't rebuild, contribute fixes |
|
||||
| **Config management** | Full YAML config with migration, env vars | **USE IT** — extend rather than replace |
|
||||
| **Plugin system** | Complete with lifecycle hooks, PluginContext API | **USE IT** — write plugins, not custom frameworks |
|
||||
| **Tool registry** | Centralized registry with self-registration | **USE IT** — register new tools via existing pattern |
|
||||
| **Cron scheduling** | `cron/scheduler.py` + `cronjob` tool | **USE IT** — integrate rather than duplicate |
|
||||
| **Subagent delegation** | `delegate_task` with isolated contexts | **USE IT** — extend for fleet coordination |
|
||||
|
||||
### What We Need That Doesn't Exist
|
||||
|
||||
| Timmy Foundation Need | Hermes-Agent Status | Action |
|
||||
|----------------------|---------------------|--------|
|
||||
| **Engram integration** | Not present | Build as external memory provider plugin |
|
||||
| **Holographic fact store** | Accepted as provider name, not implemented | Build as external memory provider |
|
||||
| **Fleet orchestration** | Not present (single-agent focus) | Build on top, contribute patterns upstream |
|
||||
| **Trust scoring on memory** | Not present | Build as extension to memory tool |
|
||||
| **Multi-agent coordination** | delegate_tool supports parallel (max 3) | Extend for fleet-wide dispatch |
|
||||
| **VPS wizard deployment** | Not present | Timmy Foundation domain — build independently |
|
||||
| **Gitea CI/CD integration** | Minimal (gitea_client.py exists) | Extend existing client |
|
||||
|
||||
### Duplication Risk Assessment
|
||||
|
||||
| Risk | Level | Details |
|
||||
|------|-------|---------|
|
||||
| Memory system duplication | 🟢 LOW | We were almost duplicating memory removal (PR #278 vs #277). Now resolved. |
|
||||
| Config system duplication | 🟢 LOW | Using hermes config directly via fork |
|
||||
| Gateway duplication | 🟡 MEDIUM | Our fleet-ops patterns may partially overlap with gateway capabilities |
|
||||
| Session management duplication | 🟢 LOW | Using hermes sessions directly |
|
||||
| Plugin system duplication | 🟢 LOW | We write plugins, not a parallel system |
|
||||
|
||||
---
|
||||
|
||||
## 5. Contribution Roadmap
|
||||
|
||||
### What to Build (Timmy Foundation Own)
|
||||
|
||||
| Item | Rationale | Priority |
|
||||
|------|-----------|----------|
|
||||
| **Engram memory provider** | Sovereign local memory (Go binary, SQLite+FTS). Must be ours. | 🔴 HIGH |
|
||||
| **Holographic fact store** | Our architecture for knowledge graph memory. Unique to Timmy. | 🔴 HIGH |
|
||||
| **Fleet orchestration layer** | Multi-wizard coordination (Allegro, Bezalel, Ezra, Claude). Not upstream's problem. | 🔴 HIGH |
|
||||
| **VPS deployment automation** | Sovereign wizard provisioning. Timmy-specific. | 🟡 MEDIUM |
|
||||
| **Trust scoring system** | Evaluate memory entry reliability. Research needed. | 🟡 MEDIUM |
|
||||
| **Gitea CI/CD integration** | Deep integration with our forge. Extend gitea_client.py. | 🟡 MEDIUM |
|
||||
| **SOUL.md compliance tooling** | Conscience validator exists (`tools/conscience_validator.py`). Extend it. | 🟢 LOW |
|
||||
|
||||
### What to Contribute Upstream
|
||||
|
||||
| Item | Rationale | Difficulty |
|
||||
|------|-----------|------------|
|
||||
| **Memory remove action fix** | Already done (PR #277). ✅ | Done |
|
||||
| **Browser integration analysis** | Useful for all users (PR #262). ✅ | Done |
|
||||
| **CI stability improvements** | Reduce deps, increase timeout (our commit). ✅ | Done |
|
||||
| **Duplicate model detection** | CI check useful for all forks (PR #235). ✅ | Done |
|
||||
| **Memory sovereignty patterns** | Verification scripts, budget enforcement. Useful broadly. | Medium |
|
||||
| **Engram provider adapter** | If Engram proves useful, offer as memory provider option. | Medium |
|
||||
| **Fleet delegation patterns** | If multi-agent coordination patterns generalize. | Hard |
|
||||
| **Wizard health monitoring** | If monitoring patterns generalize to any agent fleet. | Medium |
|
||||
|
||||
### Quick Wins (Next Sprint)
|
||||
|
||||
1. **Verify memory remove action** — Confirm PR #277 works end-to-end in our fork
|
||||
2. **Test browser tool after upstream switch** — Browserbase → Browser Use (upstream #5750) may break our PoC
|
||||
3. **Update provider config** — Kimi model references updated (PR #225), verify no remaining stale refs
|
||||
4. **Engram provider prototype** — Start implementing as external memory provider plugin
|
||||
5. **Fleet health integration** — Use gateway's background reconnection patterns for wizard fleet
|
||||
|
||||
---
|
||||
|
||||
## Appendix A: File Counts by Directory
|
||||
|
||||
| Directory | Files | Lines |
|
||||
|-----------|-------|-------|
|
||||
| `tools/` | 70+ .py files | ~50K |
|
||||
| `gateway/` | 20+ .py files | ~25K |
|
||||
| `agent/` | 10 .py files | ~10K |
|
||||
| `hermes_cli/` | 15 .py files | ~20K |
|
||||
| `acp_adapter/` | 9 .py files | ~8K |
|
||||
| `cron/` | 3 .py files | ~2K |
|
||||
| `tests/` | 470 .py files | ~80K |
|
||||
| **Total** | **335 source + 470 test** | **~200K + ~80K** |
|
||||
|
||||
## Appendix B: Key File Index
|
||||
|
||||
| File | Lines | Purpose |
|
||||
|------|-------|---------|
|
||||
| `run_agent.py` | 9,423 | AIAgent class, core conversation loop |
|
||||
| `cli.py` | 8,620 | CLI orchestrator, slash command dispatch |
|
||||
| `gateway/run.py` | 7,905 | Gateway main loop, platform management |
|
||||
| `tools/terminal_tool.py` | 1,783 | Terminal orchestration |
|
||||
| `tools/web_tools.py` | 2,082 | Web search + extraction |
|
||||
| `tools/browser_tool.py` | 2,211 | Browser automation (10 tools) |
|
||||
| `tools/code_execution_tool.py` | 1,360 | Python sandbox |
|
||||
| `tools/delegate_tool.py` | 963 | Subagent delegation |
|
||||
| `tools/mcp_tool.py` | ~1,050 | MCP client |
|
||||
| `tools/memory_tool.py` | 560 | Memory CRUD |
|
||||
| `hermes_state.py` | 1,238 | SQLite session store |
|
||||
| `gateway/session.py` | 1,030 | Session lifecycle |
|
||||
| `cron/scheduler.py` | 850 | Job scheduler |
|
||||
| `hermes_cli/config.py` | 1,318 | Config system |
|
||||
| `hermes_cli/plugins.py` | 611 | Plugin system |
|
||||
| `hermes_cli/skin_engine.py` | 500+ | Theme engine |
|
||||
271
docs/matrix-setup.md
Normal file
271
docs/matrix-setup.md
Normal file
@@ -0,0 +1,271 @@
|
||||
# Matrix Integration Setup Guide
|
||||
|
||||
Connect Hermes Agent to any Matrix homeserver for sovereign, encrypted messaging.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Python 3.10+
|
||||
- matrix-nio SDK: `pip install "matrix-nio[e2e]"`
|
||||
- For E2EE: libolm C library (see below)
|
||||
|
||||
## Option A: matrix.org Public Homeserver (Testing)
|
||||
|
||||
Best for quick evaluation. No server to run.
|
||||
|
||||
### 1. Create a Matrix Account
|
||||
|
||||
Go to https://app.element.io and create an account on matrix.org.
|
||||
Choose a username like `@hermes-bot:matrix.org`.
|
||||
|
||||
### 2. Get an Access Token
|
||||
|
||||
The recommended auth method. Token avoids storing passwords and survives
|
||||
password changes.
|
||||
|
||||
```bash
|
||||
# Using curl (replace user/password):
|
||||
curl -X POST 'https://matrix-client.matrix.org/_matrix/client/v3/login' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{
|
||||
"type": "m.login.password",
|
||||
"user": "your-bot-username",
|
||||
"password": "your-password"
|
||||
}'
|
||||
```
|
||||
|
||||
Look for `access_token` and `device_id` in the response.
|
||||
|
||||
Alternatively, in Element: Settings -> Help & About -> Advanced -> Access Token.
|
||||
|
||||
### 3. Set Environment Variables
|
||||
|
||||
Add to `~/.hermes/.env`:
|
||||
|
||||
```bash
|
||||
MATRIX_HOMESERVER=https://matrix-client.matrix.org
|
||||
MATRIX_ACCESS_TOKEN=syt_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
MATRIX_USER_ID=@hermes-bot:matrix.org
|
||||
MATRIX_DEVICE_ID=HERMES_BOT
|
||||
```
|
||||
|
||||
### 4. Install Dependencies
|
||||
|
||||
```bash
|
||||
pip install "matrix-nio[e2e]"
|
||||
```
|
||||
|
||||
### 5. Start Hermes Gateway
|
||||
|
||||
```bash
|
||||
hermes gateway
|
||||
```
|
||||
|
||||
## Option B: Self-Hosted Homeserver (Sovereignty)
|
||||
|
||||
For full control over your data and encryption keys.
|
||||
|
||||
### Popular Homeservers
|
||||
|
||||
- **Synapse** (reference impl): https://github.com/element-hq/synapse
|
||||
- **Conduit** (lightweight, Rust): https://conduit.rs
|
||||
- **Dendrite** (Go): https://github.com/matrix-org/dendrite
|
||||
|
||||
### 1. Deploy Your Homeserver
|
||||
|
||||
Follow your chosen server's documentation. Common setup with Docker:
|
||||
|
||||
```bash
|
||||
# Synapse example:
|
||||
docker run -d --name synapse \
|
||||
-v /opt/synapse/data:/data \
|
||||
-e SYNAPSE_SERVER_NAME=your.domain.com \
|
||||
-e SYNAPSE_REPORT_STATS=no \
|
||||
matrixdotorg/synapse:latest
|
||||
```
|
||||
|
||||
### 2. Create Bot Account
|
||||
|
||||
Register on your homeserver:
|
||||
|
||||
```bash
|
||||
# Synapse: register new user (run inside container)
|
||||
docker exec -it synapse register_new_matrix_user http://localhost:8008 \
|
||||
-c /data/homeserver.yaml -u hermes-bot -p 'secure-password' --admin
|
||||
```
|
||||
|
||||
### 3. Configure Hermes
|
||||
|
||||
Set in `~/.hermes/.env`:
|
||||
|
||||
```bash
|
||||
MATRIX_HOMESERVER=https://matrix.your.domain.com
|
||||
MATRIX_ACCESS_TOKEN=<obtain via login API>
|
||||
MATRIX_USER_ID=@hermes-bot:your.domain.com
|
||||
MATRIX_DEVICE_ID=HERMES_BOT
|
||||
```
|
||||
|
||||
## Environment Variables Reference
|
||||
|
||||
| Variable | Required | Description |
|
||||
|----------|----------|-------------|
|
||||
| `MATRIX_HOMESERVER` | Yes | Homeserver URL (e.g. `https://matrix.org`) |
|
||||
| `MATRIX_ACCESS_TOKEN` | Yes* | Access token (preferred over password) |
|
||||
| `MATRIX_USER_ID` | With password | Full user ID (`@user:server`) |
|
||||
| `MATRIX_PASSWORD` | Alt* | Password (alternative to token) |
|
||||
| `MATRIX_DEVICE_ID` | Recommended | Stable device ID for E2EE persistence |
|
||||
| `MATRIX_ENCRYPTION` | No | Set `true` to enable E2EE |
|
||||
| `MATRIX_ALLOWED_USERS` | No | Comma-separated allowed user IDs |
|
||||
| `MATRIX_HOME_ROOM` | No | Room ID for cron/notifications |
|
||||
| `MATRIX_REACTIONS` | No | Enable processing reactions (default: true) |
|
||||
| `MATRIX_REQUIRE_MENTION` | No | Require @mention in rooms (default: true) |
|
||||
| `MATRIX_FREE_RESPONSE_ROOMS` | No | Room IDs exempt from mention requirement |
|
||||
| `MATRIX_AUTO_THREAD` | No | Auto-create threads (default: true) |
|
||||
|
||||
\* Either `MATRIX_ACCESS_TOKEN` or `MATRIX_USER_ID` + `MATRIX_PASSWORD` is required.
|
||||
|
||||
## Config YAML Entries
|
||||
|
||||
Add to `~/.hermes/config.yaml` under a `matrix:` key for declarative settings:
|
||||
|
||||
```yaml
|
||||
matrix:
|
||||
require_mention: true
|
||||
free_response_rooms:
|
||||
- "!roomid1:matrix.org"
|
||||
- "!roomid2:matrix.org"
|
||||
auto_thread: true
|
||||
```
|
||||
|
||||
These override to env vars only if the env var is not already set.
|
||||
|
||||
## End-to-End Encryption (E2EE)
|
||||
|
||||
E2EE protects messages so only participants can read them. Hermes uses
|
||||
matrix-nio's Olm/Megolm implementation.
|
||||
|
||||
### 1. Install E2EE Dependencies
|
||||
|
||||
```bash
|
||||
# macOS
|
||||
brew install libolm
|
||||
|
||||
# Ubuntu/Debian
|
||||
sudo apt install libolm-dev
|
||||
|
||||
# Then install matrix-nio with E2EE support:
|
||||
pip install "matrix-nio[e2e]"
|
||||
```
|
||||
|
||||
### 2. Enable Encryption
|
||||
|
||||
Set in `~/.hermes/.env`:
|
||||
|
||||
```bash
|
||||
MATRIX_ENCRYPTION=true
|
||||
MATRIX_DEVICE_ID=HERMES_BOT
|
||||
```
|
||||
|
||||
### 3. How It Works
|
||||
|
||||
- On first connect, Hermes creates a device and uploads encryption keys.
|
||||
- Keys are stored in `~/.hermes/platforms/matrix/store/`.
|
||||
- On shutdown, Megolm session keys are exported to `exported_keys.txt`.
|
||||
- On next startup, keys are imported so the bot can decrypt old messages.
|
||||
- The `MATRIX_DEVICE_ID` ensures the bot reuses the same device identity
|
||||
across restarts. Without it, each restart creates a new "device" in
|
||||
Matrix and old keys become unusable.
|
||||
|
||||
### 4. Verifying E2EE
|
||||
|
||||
1. Create an encrypted room in Element.
|
||||
2. Invite your bot user.
|
||||
3. Send a message — the bot should respond.
|
||||
4. Check logs: `grep -i "e2ee\|crypto\|encrypt" ~/.hermes/logs/gateway.log`
|
||||
|
||||
## Room Configuration
|
||||
|
||||
### Inviting the Bot
|
||||
|
||||
1. Create a room in Element or any Matrix client.
|
||||
2. Invite the bot: `/invite @hermes-bot:your.domain.com`
|
||||
3. The bot auto-accepts invites (controlled by `MATRIX_ALLOWED_USERS`).
|
||||
|
||||
### Home Room
|
||||
|
||||
Set `MATRIX_HOME_ROOM` to a room ID for cron jobs and notifications:
|
||||
|
||||
```bash
|
||||
MATRIX_HOME_ROOM=!abcde12345:matrix.org
|
||||
```
|
||||
|
||||
### Free-Response Rooms
|
||||
|
||||
Rooms where the bot responds to all messages without @mention:
|
||||
|
||||
```bash
|
||||
MATRIX_FREE_RESPONSE_ROOMS=!room1:matrix.org,!room2:matrix.org
|
||||
```
|
||||
|
||||
Or in config.yaml:
|
||||
|
||||
```yaml
|
||||
matrix:
|
||||
free_response_rooms:
|
||||
- "!room1:matrix.org"
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "Matrix: need MATRIX_ACCESS_TOKEN or MATRIX_USER_ID + MATRIX_PASSWORD"
|
||||
|
||||
Neither auth method is configured. Set `MATRIX_ACCESS_TOKEN` in `~/.hermes/.env`
|
||||
or provide `MATRIX_USER_ID` + `MATRIX_PASSWORD`.
|
||||
|
||||
### "Matrix: whoami failed"
|
||||
|
||||
The access token is invalid or expired. Generate a new one via the login API.
|
||||
|
||||
### "Matrix: E2EE dependencies are missing"
|
||||
|
||||
Install libolm and matrix-nio with E2EE support:
|
||||
|
||||
```bash
|
||||
brew install libolm # macOS
|
||||
pip install "matrix-nio[e2e]"
|
||||
```
|
||||
|
||||
### "Matrix: login failed"
|
||||
|
||||
- Check username and password.
|
||||
- Ensure the account exists on the target homeserver.
|
||||
- Some homeservers require admin approval for new registrations.
|
||||
|
||||
### Bot Not Responding in Rooms
|
||||
|
||||
1. Check `MATRIX_REQUIRE_MENTION` — if `true` (default), messages must
|
||||
@mention the bot.
|
||||
2. Check `MATRIX_ALLOWED_USERS` — if set, only listed users can interact.
|
||||
3. Check logs: `tail -f ~/.hermes/logs/gateway.log`
|
||||
|
||||
### E2EE Rooms Show "Unable to Decrypt"
|
||||
|
||||
1. Ensure `MATRIX_DEVICE_ID` is set to a stable value.
|
||||
2. Check that `~/.hermes/platforms/matrix/store/` has read/write permissions.
|
||||
3. Verify libolm is installed: `python -c "from nio.crypto import ENCRYPTION_ENABLED; print(ENCRYPTION_ENABLED)"`
|
||||
|
||||
### Slow Message Delivery
|
||||
|
||||
Matrix federation can add latency. For faster responses:
|
||||
- Use the same homeserver for the bot and users.
|
||||
- Set `MATRIX_HOME_ROOM` to a local room.
|
||||
- Check network connectivity between Hermes and the homeserver.
|
||||
|
||||
## Quick Start (Automated)
|
||||
|
||||
Run the interactive setup script:
|
||||
|
||||
```bash
|
||||
python scripts/setup_matrix.py
|
||||
```
|
||||
|
||||
This guides you through homeserver selection, authentication, and verification.
|
||||
335
docs/memory-architecture.md
Normal file
335
docs/memory-architecture.md
Normal file
@@ -0,0 +1,335 @@
|
||||
# Memory Architecture Guide
|
||||
|
||||
How Hermes Agent remembers things across sessions — the stores, the tools, the data flow, and how to configure it all.
|
||||
|
||||
## Overview
|
||||
|
||||
Hermes has a multi-layered memory system. It is not one thing — it is several independent systems that complement each other:
|
||||
|
||||
1. **Persistent Memory** (MEMORY.md / USER.md) — bounded, curated notes injected into every system prompt
|
||||
2. **Session Search** — full-text search across all past conversation transcripts
|
||||
3. **Skills** — procedural memory: reusable workflows stored as SKILL.md files
|
||||
4. **External Memory Providers** — optional plugins (Honcho, Holographic, Mem0, etc.) for deeper recall
|
||||
|
||||
All built-in memory lives on disk under `~/.hermes/` (or `$HERMES_HOME`). No memory data leaves the machine unless you explicitly configure an external cloud provider.
|
||||
|
||||
## Memory Types in Detail
|
||||
|
||||
### 1. Persistent Memory (MEMORY.md and USER.md)
|
||||
|
||||
The core memory system. Two files in `~/.hermes/memories/`:
|
||||
|
||||
| File | Purpose | Default Char Limit |
|
||||
|------|---------|--------------------|
|
||||
| `MEMORY.md` | Agent's personal notes — environment facts, project conventions, tool quirks, lessons learned | 2,200 chars (~800 tokens) |
|
||||
| `USER.md` | User profile — name, preferences, communication style, pet peeves | 1,375 chars (~500 tokens) |
|
||||
|
||||
**How it works:**
|
||||
|
||||
- Loaded from disk at session start and injected into the system prompt as a frozen snapshot
|
||||
- The agent uses the `memory` tool to add, replace, or remove entries during a session
|
||||
- Mid-session writes go to disk immediately (durable) but do NOT update the system prompt — this preserves the LLM's prefix cache for performance
|
||||
- The snapshot refreshes on the next session start
|
||||
- Entries are delimited by `§` (section sign) and can be multiline
|
||||
|
||||
**System prompt appearance:**
|
||||
|
||||
```
|
||||
══════════════════════════════════════════════
|
||||
MEMORY (your personal notes) [67% — 1,474/2,200 chars]
|
||||
══════════════════════════════════════════════
|
||||
User's project is a Rust web service at ~/code/myapi using Axum + SQLx
|
||||
§
|
||||
This machine runs Ubuntu 22.04, has Docker and Podman installed
|
||||
§
|
||||
User prefers concise responses, dislikes verbose explanations
|
||||
```
|
||||
|
||||
**Memory tool actions:**
|
||||
|
||||
- `add` — append a new entry (rejected if it would exceed the char limit)
|
||||
- `replace` — find an entry by substring match and replace it
|
||||
- `remove` — find an entry by substring match and delete it
|
||||
|
||||
Substring matching means you only need a unique fragment of the entry, not the full text. If the fragment matches multiple entries, the tool returns an error asking for a more specific match.
|
||||
|
||||
### 2. Session Search
|
||||
|
||||
Cross-session conversation recall via SQLite FTS5 full-text search.
|
||||
|
||||
- All CLI and messaging sessions are stored in `~/.hermes/state.db`
|
||||
- The `session_search` tool finds relevant past conversations by keyword
|
||||
- Top matching sessions are summarized by Gemini Flash (cheap, fast) before being returned to the main model
|
||||
- Returns focused summaries, not raw transcripts
|
||||
|
||||
**When to use session_search vs. memory:**
|
||||
|
||||
| Feature | Persistent Memory | Session Search |
|
||||
|---------|------------------|----------------|
|
||||
| Capacity | ~3,575 chars total | Unlimited (all sessions) |
|
||||
| Speed | Instant (in system prompt) | Requires search + LLM summarization |
|
||||
| Use case | Key facts always in context | "What did we discuss about X last week?" |
|
||||
| Management | Manually curated by the agent | Automatic — all sessions stored |
|
||||
| Token cost | Fixed per session (~1,300 tokens) | On-demand (searched when needed) |
|
||||
|
||||
**Rule of thumb:** Memory is for facts that should *always* be available. Session search is for recalling specific past conversations on demand. Don't save task progress or session outcomes to memory — use session_search to find those.
|
||||
|
||||
### 3. Skills (Procedural Memory)
|
||||
|
||||
Skills are reusable workflows stored as `SKILL.md` files in `~/.hermes/skills/` (and optionally external skill directories).
|
||||
|
||||
- Organized by category: `skills/github/github-pr-workflow/SKILL.md`
|
||||
- YAML frontmatter with name, description, version, platform restrictions
|
||||
- Progressive disclosure: metadata shown in skill list, full content loaded on demand via `skill_view`
|
||||
- The agent creates skills proactively after complex tasks (5+ tool calls) using the `skill_manage` tool
|
||||
- Skills can be patched when found outdated — stale skills are a liability
|
||||
|
||||
Skills are *not* injected into the system prompt by default. The agent sees a compact index of available skills and loads them on demand. This keeps the prompt lean while giving access to deep procedural knowledge.
|
||||
|
||||
**Skills vs. Memory:**
|
||||
|
||||
- **Memory:** compact facts ("User's project uses Go 1.22 with chi router")
|
||||
- **Skills:** detailed procedures ("How to deploy the staging server: step 1, step 2, ...")
|
||||
|
||||
### 4. External Memory Providers
|
||||
|
||||
Optional plugins that add deeper, structured memory alongside the built-in system. Only one external provider can be active at a time.
|
||||
|
||||
| Provider | Storage | Key Feature |
|
||||
|----------|---------|-------------|
|
||||
| Honcho | Cloud | Dialectic user modeling with semantic search |
|
||||
| OpenViking | Self-hosted | Filesystem-style knowledge hierarchy |
|
||||
| Mem0 | Cloud | Server-side LLM fact extraction |
|
||||
| Hindsight | Cloud/Local | Knowledge graph with entity resolution |
|
||||
| Holographic | Local SQLite | HRR algebraic reasoning + trust scoring |
|
||||
| RetainDB | Cloud | Hybrid search with delta compression |
|
||||
| ByteRover | Local/Cloud | Hierarchical knowledge tree with CLI |
|
||||
| Supermemory | Cloud | Context fencing + session graph ingest |
|
||||
|
||||
External providers run **alongside** built-in memory (never replacing it). They receive hooks for:
|
||||
- System prompt injection (provider context)
|
||||
- Pre-turn memory prefetch
|
||||
- Post-turn conversation sync
|
||||
- Session-end extraction
|
||||
- Built-in memory write mirroring
|
||||
|
||||
Setup: `hermes memory setup` or set `memory.provider` in `~/.hermes/config.yaml`.
|
||||
|
||||
See `website/docs/user-guide/features/memory-providers.md` for full provider details.
|
||||
|
||||
## How the Systems Interact
|
||||
|
||||
```
|
||||
Session Start
|
||||
|
|
||||
+--> Load MEMORY.md + USER.md from disk --> frozen snapshot into system prompt
|
||||
+--> Provider: system_prompt_block() --> injected into system prompt
|
||||
+--> Skills index --> injected into system prompt (compact metadata only)
|
||||
|
|
||||
v
|
||||
Each Turn
|
||||
|
|
||||
+--> Provider: prefetch(query) --> relevant recalled context
|
||||
+--> Agent sees: system prompt (memory + provider context + skills index)
|
||||
+--> Agent can call: memory tool, session_search tool, skill tools, provider tools
|
||||
|
|
||||
v
|
||||
After Each Response
|
||||
|
|
||||
+--> Provider: sync_turn(user, assistant) --> persist conversation
|
||||
|
|
||||
v
|
||||
Periodic (every N turns, default 10)
|
||||
|
|
||||
+--> Memory nudge: agent prompted to review and update memory
|
||||
|
|
||||
v
|
||||
Session End / Compression
|
||||
|
|
||||
+--> Memory flush: agent saves important facts before context is discarded
|
||||
+--> Provider: on_session_end(messages) --> final extraction
|
||||
+--> Provider: on_pre_compress(messages) --> save insights before compression
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### What to Save
|
||||
|
||||
Save proactively — don't wait for the user to ask:
|
||||
|
||||
- **User preferences:** "I prefer TypeScript over JavaScript" → `user` target
|
||||
- **Corrections:** "Don't use sudo for Docker, I'm in the docker group" → `memory` target
|
||||
- **Environment facts:** "This server runs Debian 12 with PostgreSQL 16" → `memory` target
|
||||
- **Conventions:** "Project uses tabs, 120-char lines, Google docstrings" → `memory` target
|
||||
- **Explicit requests:** "Remember that my API key rotation is monthly" → `memory` target
|
||||
|
||||
### What NOT to Save
|
||||
|
||||
- **Task progress or session outcomes** — use session_search to recall these
|
||||
- **Trivially re-discoverable facts** — "Python 3.12 supports f-strings" (web search this)
|
||||
- **Raw data dumps** — large code blocks, log files, data tables
|
||||
- **Session-specific ephemera** — temporary file paths, one-off debugging context
|
||||
- **Content already in SOUL.md or AGENTS.md** — those are already in context
|
||||
|
||||
### Writing Good Entries
|
||||
|
||||
Compact, information-dense entries work best:
|
||||
|
||||
```
|
||||
# Good — packs multiple related facts
|
||||
User runs macOS 14 Sonoma, uses Homebrew, has Docker Desktop and Podman. Shell: zsh. Editor: VS Code with Vim bindings.
|
||||
|
||||
# Good — specific, actionable convention
|
||||
Project ~/code/api uses Go 1.22, sqlc for DB, chi router. Tests: make test. CI: GitHub Actions.
|
||||
|
||||
# Bad — too vague
|
||||
User has a project.
|
||||
|
||||
# Bad — too verbose
|
||||
On January 5th, 2026, the user asked me to look at their project which is
|
||||
located at ~/code/api. I discovered it uses Go version 1.22 and...
|
||||
```
|
||||
|
||||
### Capacity Management
|
||||
|
||||
When memory is above 80% capacity (visible in the system prompt header), consolidate before adding. Merge related entries into shorter, denser versions. The tool will reject additions that would exceed the limit — use `replace` to consolidate first.
|
||||
|
||||
Priority order for what stays in memory:
|
||||
1. User preferences and corrections (highest — prevents repeated steering)
|
||||
2. Environment facts and project conventions
|
||||
3. Tool quirks and workarounds
|
||||
4. Lessons learned (lowest — can often be rediscovered)
|
||||
|
||||
### Memory Nudge
|
||||
|
||||
Every N turns (default: 10), the agent receives a nudge prompting it to review and update its memory. This is a lightweight prompt injected into the conversation — not a separate API call. The agent can choose to update memory or skip if nothing has changed.
|
||||
|
||||
## Privacy and Data Locality
|
||||
|
||||
**Built-in memory is fully local.** MEMORY.md and USER.md are plain text files in `~/.hermes/memories/`. No network calls are made in the memory read/write path. The memory tool scans entries for prompt injection and exfiltration patterns before accepting them.
|
||||
|
||||
**Session search is local.** The SQLite database (`~/.hermes/state.db`) stays on disk. FTS5 search is a local operation. However, the summarization step uses Gemini Flash (via the auxiliary LLM client) — conversation snippets are sent to Google's API for summarization. If this is a concern, session_search can be disabled.
|
||||
|
||||
**External providers may send data off-machine.** Cloud providers (Honcho, Mem0, RetainDB, Supermemory) send data to their respective APIs. Self-hosted providers (OpenViking, Hindsight local mode, Holographic, ByteRover local mode) keep everything on your machine. Check the provider's documentation for specifics.
|
||||
|
||||
**Security scanning.** All content written to memory (via the `memory` tool) is scanned for:
|
||||
- Prompt injection patterns ("ignore previous instructions", role hijacking, etc.)
|
||||
- Credential exfiltration attempts (curl/wget with secrets, reading .env files)
|
||||
- SSH backdoor patterns
|
||||
- Invisible unicode characters (used for steganographic injection)
|
||||
|
||||
Blocked content is rejected with a descriptive error message.
|
||||
|
||||
## Configuration
|
||||
|
||||
In `~/.hermes/config.yaml`:
|
||||
|
||||
```yaml
|
||||
memory:
|
||||
# Enable/disable the two built-in memory stores
|
||||
memory_enabled: true # MEMORY.md
|
||||
user_profile_enabled: true # USER.md
|
||||
|
||||
# Character limits (not tokens — model-independent)
|
||||
memory_char_limit: 2200 # ~800 tokens at 2.75 chars/token
|
||||
user_char_limit: 1375 # ~500 tokens at 2.75 chars/token
|
||||
|
||||
# External memory provider (empty string = built-in only)
|
||||
# Options: "honcho", "openviking", "mem0", "hindsight",
|
||||
# "holographic", "retaindb", "byterover", "supermemory"
|
||||
provider: ""
|
||||
```
|
||||
|
||||
Additional settings are read from `run_agent.py` defaults:
|
||||
|
||||
| Setting | Default | Description |
|
||||
|---------|---------|-------------|
|
||||
| `nudge_interval` | 10 | Turns between memory review nudges (0 = disabled) |
|
||||
| `flush_min_turns` | 6 | Minimum user turns before memory flush on session end/compression (0 = never flush) |
|
||||
|
||||
These are set under the `memory` key in config.yaml:
|
||||
|
||||
```yaml
|
||||
memory:
|
||||
nudge_interval: 10
|
||||
flush_min_turns: 6
|
||||
```
|
||||
|
||||
### Disabling Memory
|
||||
|
||||
To disable memory entirely, set both to false:
|
||||
|
||||
```yaml
|
||||
memory:
|
||||
memory_enabled: false
|
||||
user_profile_enabled: false
|
||||
```
|
||||
|
||||
The `memory` tool will not appear in the tool list, and no memory blocks are injected into the system prompt.
|
||||
|
||||
You can also disable memory per-invocation with `skip_memory=True` in the AIAgent constructor (used by cron jobs and flush agents).
|
||||
|
||||
## File Locations
|
||||
|
||||
```
|
||||
~/.hermes/
|
||||
├── memories/
|
||||
│ ├── MEMORY.md # Agent's persistent notes
|
||||
│ ├── USER.md # User profile
|
||||
│ ├── MEMORY.md.lock # File lock (auto-created)
|
||||
│ └── USER.md.lock # File lock (auto-created)
|
||||
├── state.db # SQLite session store (FTS5)
|
||||
├── config.yaml # Memory config + provider selection
|
||||
└── .env # API keys for external providers
|
||||
```
|
||||
|
||||
All paths respect `$HERMES_HOME` — if you use Hermes profiles, each profile has its own isolated memory directory.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "Memory full" errors
|
||||
|
||||
The tool returns an error when adding would exceed the character limit. The response includes current entries so the agent can consolidate. Fix by:
|
||||
1. Replacing multiple related entries with one denser entry
|
||||
2. Removing entries that are no longer relevant
|
||||
3. Increasing `memory_char_limit` in config (at the cost of larger system prompts)
|
||||
|
||||
### Stale memory entries
|
||||
|
||||
If the agent seems to have outdated information:
|
||||
- Check `~/.hermes/memories/MEMORY.md` directly — you can edit it by hand
|
||||
- The frozen snapshot pattern means changes only take effect on the next session start
|
||||
- If the agent wrote something wrong mid-session, it persists on disk but won't affect the current session's system prompt
|
||||
|
||||
### Memory not appearing in system prompt
|
||||
|
||||
- Verify `memory_enabled: true` in config.yaml
|
||||
- Check that `~/.hermes/memories/MEMORY.md` exists and has content
|
||||
- The file might be empty if all entries were removed — add entries with the `memory` tool
|
||||
|
||||
### Session search returns no results
|
||||
|
||||
- Session search requires sessions to be stored in `state.db` — new installations have no history
|
||||
- FTS5 indexes are built automatically but may lag behind on very large databases
|
||||
- The summarization step requires the auxiliary LLM client to be configured (API key for Gemini Flash)
|
||||
|
||||
### Skill drift
|
||||
|
||||
Skills that haven't been updated can become wrong or incomplete. The agent is prompted to patch skills when it finds them outdated during use (`skill_manage(action='patch')`). If you notice stale skills:
|
||||
- Use `/skills` to browse and review installed skills
|
||||
- Delete or update skills in `~/.hermes/skills/` directly
|
||||
- The agent creates skills after complex tasks — review and prune periodically
|
||||
|
||||
### Provider not activating
|
||||
|
||||
- Run `hermes memory status` to check provider state
|
||||
- Verify the provider plugin is installed in `~/.hermes/plugins/memory/`
|
||||
- Check that required API keys are set in `~/.hermes/.env`
|
||||
- Start a new session after changing provider config — existing sessions use the old provider
|
||||
|
||||
### Concurrent write conflicts
|
||||
|
||||
The memory tool uses file locking (`fcntl.flock`) and atomic file replacement (`os.replace`) to handle concurrent writes from multiple sessions. If you see corrupted memory files:
|
||||
- Check for stale `.lock` files in `~/.hermes/memories/`
|
||||
- Restart any hung Hermes processes
|
||||
- The atomic write pattern means readers always see either the old or new file — never a partial write
|
||||
@@ -103,3 +103,8 @@ AI_GATEWAY_CHAT_URL = f"{AI_GATEWAY_BASE_URL}/chat/completions"
|
||||
|
||||
NOUS_API_BASE_URL = "https://inference-api.nousresearch.com/v1"
|
||||
NOUS_API_CHAT_URL = f"{NOUS_API_BASE_URL}/chat/completions"
|
||||
|
||||
|
||||
# Guardrails
|
||||
HERMES_MAX_TOKENS_PER_SESSION = int(os.getenv("HERMES_MAX_TOKENS", "50000"))
|
||||
HERMES_MAX_TOOL_CALLS_PER_TURN = int(os.getenv("HERMES_MAX_TOOL_CALLS", "10"))
|
||||
|
||||
@@ -241,13 +241,29 @@ class HolographicMemoryProvider(MemoryProvider):
|
||||
self._auto_extract_facts(messages)
|
||||
|
||||
def on_memory_write(self, action: str, target: str, content: str) -> None:
|
||||
"""Mirror built-in memory writes as facts."""
|
||||
if action == "add" and self._store and content:
|
||||
try:
|
||||
"""Mirror built-in memory writes as facts.
|
||||
|
||||
- add: mirror new fact to holographic store
|
||||
- replace: search for old content, update or re-add
|
||||
- remove: lower trust on matching facts so they fade naturally
|
||||
"""
|
||||
if not self._store:
|
||||
return
|
||||
try:
|
||||
if action == "add" and content:
|
||||
category = "user_pref" if target == "user" else "general"
|
||||
self._store.add_fact(content, category=category)
|
||||
except Exception as e:
|
||||
logger.debug("Holographic memory_write mirror failed: %s", e)
|
||||
elif action == "replace" and content:
|
||||
category = "user_pref" if target == "user" else "general"
|
||||
self._store.add_fact(content, category=category)
|
||||
elif action == "remove" and content:
|
||||
# Lower trust on matching facts so they decay naturally
|
||||
results = self._store.search_facts(content, limit=5)
|
||||
for fact in results:
|
||||
if content.strip().lower() in fact.get("content", "").lower():
|
||||
self._store.update_fact(fact["fact_id"], trust=max(0.0, fact.get("trust", 0.5) - 0.4))
|
||||
except Exception as e:
|
||||
logger.debug("Holographic memory_write mirror failed: %s", e)
|
||||
|
||||
def shutdown(self) -> None:
|
||||
self._store = None
|
||||
|
||||
248
plugins/memory/mempalace/__init__.py
Normal file
248
plugins/memory/mempalace/__init__.py
Normal file
@@ -0,0 +1,248 @@
|
||||
"""
|
||||
MemPalace Portal — Hybrid Memory Provider.
|
||||
|
||||
Bridges the local Holographic fact store with the fleet-wide MemPalace vector database.
|
||||
Implements smart context compression for token efficiency.
|
||||
"""
|
||||
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import requests
|
||||
from typing import Any, Dict, List, Optional
|
||||
from agent.memory_provider import MemoryProvider
|
||||
|
||||
# Import Holographic components if available
|
||||
try:
|
||||
from plugins.memory.holographic.store import MemoryStore
|
||||
from plugins.memory.holographic.retrieval import FactRetriever
|
||||
HAS_HOLOGRAPHIC = True
|
||||
except ImportError:
|
||||
HAS_HOLOGRAPHIC = False
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Tool Schemas
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
MEMPALACE_SCHEMA = {
|
||||
"name": "mempalace",
|
||||
"description": (
|
||||
"Search or record memories in the shared fleet vector database. "
|
||||
"Use this for long-term, high-volume memory across the entire fleet."
|
||||
),
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"action": {"type": "string", "enum": ["search", "record", "wings"]},
|
||||
"query": {"type": "string", "description": "Search query."},
|
||||
"text": {"type": "string", "description": "Memory text to record."},
|
||||
"room": {"type": "string", "description": "Target room (e.g., forge, hermes, nexus)."},
|
||||
"n_results": {"type": "integer", "default": 5},
|
||||
},
|
||||
"required": ["action"],
|
||||
},
|
||||
}
|
||||
|
||||
FACT_STORE_SCHEMA = {
|
||||
"name": "fact_store",
|
||||
"description": (
|
||||
"Structured local fact storage. Use for durable facts about people, projects, and decisions."
|
||||
),
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"action": {"type": "string", "enum": ["add", "search", "probe", "reason", "update", "remove"]},
|
||||
"content": {"type": "string"},
|
||||
"query": {"type": "string"},
|
||||
"entity": {"type": "string"},
|
||||
"fact_id": {"type": "integer"},
|
||||
},
|
||||
"required": ["action"],
|
||||
},
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Provider Implementation
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class MemPalacePortalProvider(MemoryProvider):
|
||||
"""Hybrid Fleet Vector + Local Structured memory provider."""
|
||||
|
||||
def __init__(self, config: dict | None = None):
|
||||
self._config = config or {}
|
||||
self._api_url = os.environ.get("MEMPALACE_API_URL", "http://127.0.0.1:7771")
|
||||
self._hologram_store = None
|
||||
self._hologram_retriever = None
|
||||
self._session_id = None
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return "mempalace"
|
||||
|
||||
def is_available(self) -> bool:
|
||||
# Always available if we can reach the API or have Holographic
|
||||
return True
|
||||
|
||||
def initialize(self, session_id: str, **kwargs) -> None:
|
||||
self._session_id = session_id
|
||||
hermes_home = kwargs.get("hermes_home")
|
||||
|
||||
if HAS_HOLOGRAPHIC and hermes_home:
|
||||
db_path = os.path.join(hermes_home, "memory_store.db")
|
||||
try:
|
||||
self._hologram_store = MemoryStore(db_path=db_path)
|
||||
self._hologram_retriever = FactRetriever(store=self._hologram_store)
|
||||
logger.info("Holographic store initialized as local portal layer.")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to init Holographic layer: {e}")
|
||||
|
||||
def system_prompt_block(self) -> str:
|
||||
status = "Active (Fleet Portal)"
|
||||
if self._hologram_store:
|
||||
status += " + Local Hologram"
|
||||
return (
|
||||
f"# MemPalace Portal\n"
|
||||
f"Status: {status}.\n"
|
||||
"You have access to the shared fleet vector database (mempalace) and local structured facts (fact_store).\n"
|
||||
"Use mempalace for semantic fleet-wide recall. Use fact_store for precise local knowledge."
|
||||
)
|
||||
|
||||
def prefetch(self, query: str, *, session_id: str = "") -> str:
|
||||
if not query:
|
||||
return ""
|
||||
|
||||
context_blocks = []
|
||||
|
||||
# 1. Fleet Search (MemPalace)
|
||||
try:
|
||||
res = requests.get(f"{self._api_url}/search", params={"q": query, "n": 3}, timeout=2)
|
||||
if res.ok:
|
||||
data = res.json()
|
||||
memories = data.get("memories", [])
|
||||
if memories:
|
||||
block = "## Fleet Memories (MemPalace)\n"
|
||||
for m in memories:
|
||||
block += f"- {m['text']}\n"
|
||||
context_blocks.append(block)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# 2. Local Probe (Holographic)
|
||||
if self._hologram_retriever:
|
||||
try:
|
||||
# Extract entities from query to probe
|
||||
entities = re.findall(r'\b([A-Z][a-z]+(?:\s+[A-Z][a-z]+)*)\b', query)
|
||||
facts = []
|
||||
for ent in entities:
|
||||
results = self._hologram_retriever.probe(ent, limit=3)
|
||||
facts.extend(results)
|
||||
|
||||
if facts:
|
||||
block = "## Local Facts (Hologram)\n"
|
||||
seen = set()
|
||||
for f in facts:
|
||||
if f['content'] not in seen:
|
||||
block += f"- {f['content']}\n"
|
||||
seen.add(f['content'])
|
||||
context_blocks.append(block)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return "\n\n".join(context_blocks)
|
||||
|
||||
def sync_turn(self, user_content: str, assistant_content: str, *, session_id: str = "") -> None:
|
||||
# Record to Fleet Palace
|
||||
try:
|
||||
payload = {
|
||||
"text": f"User: {user_content}\nAssistant: {assistant_content}",
|
||||
"room": "hermes_sync",
|
||||
"metadata": {"session_id": session_id}
|
||||
}
|
||||
requests.post(f"{self._api_url}/record", json=payload, timeout=2)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def on_pre_compress(self, messages: List[Dict[str, Any]]) -> str:
|
||||
"""Token Efficiency: Summarize and archive before context is lost."""
|
||||
if not messages:
|
||||
return ""
|
||||
|
||||
# Extract key facts for Hologram
|
||||
if self._hologram_store:
|
||||
# Simple heuristic: look for \"I prefer\", \"The project uses\", etc.
|
||||
for msg in messages:
|
||||
if msg.get(\"role\") == \"user\":
|
||||
content = msg.get(\"content\", \"\")
|
||||
if \"prefer\" in content.lower() or \"use\" in content.lower():
|
||||
try:
|
||||
self._hologram_store.add_fact(content[:200], category=\"user_pref\")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Archive session summary to MemPalace
|
||||
summary_text = f"Session {self._session_id} summary: " + " | ".join([m['content'][:50] for m in messages if m.get('role') == 'user'])
|
||||
try:
|
||||
payload = {
|
||||
"text": summary_text,
|
||||
"room": "summaries",
|
||||
"metadata": {"type": "session_summary", "session_id": self._session_id}
|
||||
}
|
||||
requests.post(f"{self._api_url}/record", json=payload, timeout=2)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return "Insights archived to MemPalace and Hologram."
|
||||
|
||||
def get_tool_schemas(self) -> List[Dict[str, Any]]:
|
||||
return [MEMPALACE_SCHEMA, FACT_STORE_SCHEMA]
|
||||
|
||||
def handle_tool_call(self, tool_name: str, args: Dict[str, Any], **kwargs) -> str:
|
||||
if tool_name == "mempalace":
|
||||
return self._handle_mempalace(args)
|
||||
elif tool_name == "fact_store":
|
||||
return self._handle_fact_store(args)
|
||||
return json.dumps({"error": f"Unknown tool: {tool_name}"})
|
||||
|
||||
def _handle_mempalace(self, args: dict) -> str:
|
||||
action = args.get("action")
|
||||
try:
|
||||
if action == "search":
|
||||
res = requests.get(f"{self._api_url}/search", params={"q": args["query"], "n": args.get("n_results", 5)}, timeout=10)
|
||||
return res.text
|
||||
elif action == "record":
|
||||
res = requests.post(f"{self._api_url}/record", json={"text": args["text"], "room": args.get("room", "general")}, timeout=10)
|
||||
return res.text
|
||||
elif action == "wings":
|
||||
res = requests.get(f"{self._api_url}/wings", timeout=10)
|
||||
return res.text
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": str(e)})
|
||||
return json.dumps({"error": "Invalid action"})
|
||||
|
||||
def _handle_fact_store(self, args: dict) -> str:
|
||||
if not self._hologram_store:
|
||||
return json.dumps({"error": "Holographic store not initialized locally."})
|
||||
# Logic similar to holographic plugin
|
||||
action = args["action"]
|
||||
try:
|
||||
if action == "add":
|
||||
fid = self._hologram_store.add_fact(args["content"])
|
||||
return json.dumps({"fact_id": fid, "status": "added"})
|
||||
elif action == "probe":
|
||||
res = self._hologram_retriever.probe(args["entity"])
|
||||
return json.dumps({"results": res})
|
||||
# ... other actions ...
|
||||
return json.dumps({"status": "ok", "message": f"Action {action} processed (partial impl)"})
|
||||
except Exception as e:
|
||||
return json.dumps({"error": str(e)})
|
||||
|
||||
def shutdown(self) -> None:
|
||||
if self._hologram_store:
|
||||
self._hologram_store.close()
|
||||
|
||||
def register(ctx) -> None:
|
||||
provider = MemPalacePortalProvider()
|
||||
ctx.register_memory_provider(provider)
|
||||
7
plugins/memory/mempalace/plugin.yaml
Normal file
7
plugins/memory/mempalace/plugin.yaml
Normal file
@@ -0,0 +1,7 @@
|
||||
|
||||
name: mempalace
|
||||
version: 1.0.0
|
||||
description: "The Portal: Hybrid Fleet Vector (MemPalace) + Local Structured (Holographic) memory."
|
||||
dependencies:
|
||||
- requests
|
||||
- numpy
|
||||
43
run_agent.py
43
run_agent.py
@@ -45,7 +45,7 @@ import fire
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
from hermes_constants import get_hermes_home
|
||||
from hermes_constants import get_hermes_home, HERMES_MAX_TOKENS_PER_SESSION, HERMES_MAX_TOOL_CALLS_PER_TURN
|
||||
|
||||
# Load .env from ~/.hermes/.env first, then project root as dev fallback.
|
||||
# User-managed env files should override stale shell exports on restart.
|
||||
@@ -104,6 +104,7 @@ from agent.trajectory import (
|
||||
save_trajectory as _save_trajectory_to_file,
|
||||
)
|
||||
from utils import atomic_json_write, env_var_enabled
|
||||
from json_repair import repair_json
|
||||
|
||||
|
||||
|
||||
@@ -274,7 +275,7 @@ def _should_parallelize_tool_batch(tool_calls) -> bool:
|
||||
for tool_call in tool_calls:
|
||||
tool_name = tool_call.function.name
|
||||
try:
|
||||
function_args = json.loads(tool_call.function.arguments)
|
||||
function_args = json.loads(repair_json(tool_call.function.arguments))
|
||||
except Exception:
|
||||
logging.debug(
|
||||
"Could not parse args for %s — defaulting to sequential; raw=%s",
|
||||
@@ -526,6 +527,7 @@ class AIAgent:
|
||||
status_callback: callable = None,
|
||||
max_tokens: int = None,
|
||||
reasoning_config: Dict[str, Any] = None,
|
||||
tool_choice: str = "auto",
|
||||
prefill_messages: List[Dict[str, Any]] = None,
|
||||
platform: str = None,
|
||||
skip_context_files: bool = False,
|
||||
@@ -570,6 +572,7 @@ class AIAgent:
|
||||
max_tokens (int): Maximum tokens for model responses (optional, uses model default if not set)
|
||||
reasoning_config (Dict): OpenRouter reasoning configuration override (e.g. {"effort": "none"} to disable thinking).
|
||||
If None, defaults to {"enabled": True, "effort": "medium"} for OpenRouter. Set to disable/customize reasoning.
|
||||
tool_choice (str): Tool calling mode hint (e.g. "auto", "required", "none", or specific tool name).
|
||||
prefill_messages (List[Dict]): Messages to prepend to conversation history as prefilled context.
|
||||
Useful for injecting a few-shot example or priming the model's response style.
|
||||
Example: [{"role": "user", "content": "Hi!"}, {"role": "assistant", "content": "Hello!"}]
|
||||
@@ -685,6 +688,7 @@ class AIAgent:
|
||||
# Model response configuration
|
||||
self.max_tokens = max_tokens # None = use model default
|
||||
self.reasoning_config = reasoning_config # None = use default (medium for OpenRouter)
|
||||
self.tool_choice = tool_choice
|
||||
self.prefill_messages = prefill_messages or [] # Prefilled conversation turns
|
||||
|
||||
# Anthropic prompt caching: auto-enabled for Claude models via OpenRouter.
|
||||
@@ -2121,7 +2125,7 @@ class AIAgent:
|
||||
# Parse arguments - should always succeed since we validate during conversation
|
||||
# but keep try-except as safety net
|
||||
try:
|
||||
arguments = json.loads(tool_call["function"]["arguments"]) if isinstance(tool_call["function"]["arguments"], str) else tool_call["function"]["arguments"]
|
||||
arguments = json.loads(repair_json(tool_call["function"]["arguments"])) if isinstance(tool_call["function"]["arguments"], str) else tool_call["function"]["arguments"]
|
||||
except json.JSONDecodeError:
|
||||
# This shouldn't happen since we validate and retry during conversation,
|
||||
# but if it does, log warning and use empty dict
|
||||
@@ -5350,6 +5354,7 @@ class AIAgent:
|
||||
tools=self.tools,
|
||||
max_tokens=self.max_tokens,
|
||||
reasoning_config=self.reasoning_config,
|
||||
tool_choice=self.tool_choice,
|
||||
is_oauth=self._is_anthropic_oauth,
|
||||
preserve_dots=self._anthropic_preserve_dots(),
|
||||
context_length=ctx_len,
|
||||
@@ -5383,7 +5388,7 @@ class AIAgent:
|
||||
"instructions": instructions,
|
||||
"input": self._chat_messages_to_responses_input(payload_messages),
|
||||
"tools": self._responses_tools(),
|
||||
"tool_choice": "auto",
|
||||
"tool_choice": self.tool_choice,
|
||||
"parallel_tool_calls": True,
|
||||
"store": False,
|
||||
}
|
||||
@@ -5480,6 +5485,8 @@ class AIAgent:
|
||||
}
|
||||
if self.tools:
|
||||
api_kwargs["tools"] = self.tools
|
||||
if self.tool_choice is not None:
|
||||
api_kwargs["tool_choice"] = self.tool_choice
|
||||
|
||||
if self.max_tokens is not None:
|
||||
api_kwargs.update(self._max_tokens_param(self.max_tokens))
|
||||
@@ -6086,7 +6093,7 @@ class AIAgent:
|
||||
store=self._memory_store,
|
||||
)
|
||||
# Bridge: notify external memory provider of built-in memory writes
|
||||
if self._memory_manager and function_args.get("action") in ("add", "replace"):
|
||||
if self._memory_manager and function_args.get("action") in ("add", "replace", "remove"):
|
||||
try:
|
||||
self._memory_manager.on_memory_write(
|
||||
function_args.get("action", ""),
|
||||
@@ -6148,6 +6155,17 @@ class AIAgent:
|
||||
for tool_call in tool_calls:
|
||||
function_name = tool_call.function.name
|
||||
|
||||
# Poka-yoke #294: Block tool hallucination before execution
|
||||
if hasattr(self, 'valid_tool_names') and self.valid_tool_names and function_name not in self.valid_tool_names:
|
||||
logging.warning(f"Tool hallucination blocked: '{function_name}' not in valid toolset")
|
||||
available = ', '.join(sorted(self.valid_tool_names)[:20])
|
||||
messages.append({
|
||||
"role": "tool",
|
||||
"tool_call_id": tool_call.id,
|
||||
"content": f"Error: Tool '{function_name}' does not exist. Available: {available}"
|
||||
})
|
||||
continue
|
||||
|
||||
# Reset nudge counters
|
||||
if function_name == "memory":
|
||||
self._turns_since_memory = 0
|
||||
@@ -6155,7 +6173,7 @@ class AIAgent:
|
||||
self._iters_since_skill = 0
|
||||
|
||||
try:
|
||||
function_args = json.loads(tool_call.function.arguments)
|
||||
function_args = json.loads(repair_json(tool_call.function.arguments))
|
||||
except json.JSONDecodeError:
|
||||
function_args = {}
|
||||
if not isinstance(function_args, dict):
|
||||
@@ -6361,6 +6379,17 @@ class AIAgent:
|
||||
|
||||
function_name = tool_call.function.name
|
||||
|
||||
# Poka-yoke #294: Block tool hallucination before execution
|
||||
if hasattr(self, 'valid_tool_names') and self.valid_tool_names and function_name not in self.valid_tool_names:
|
||||
logging.warning(f"Tool hallucination blocked: '{function_name}' not in valid toolset")
|
||||
available = ', '.join(sorted(self.valid_tool_names)[:20])
|
||||
messages.append({
|
||||
"role": "tool",
|
||||
"tool_call_id": tool_call.id,
|
||||
"content": f"Error: Tool '{function_name}' does not exist. Available: {available}"
|
||||
})
|
||||
continue
|
||||
|
||||
# Reset nudge counters when the relevant tool is actually used
|
||||
if function_name == "memory":
|
||||
self._turns_since_memory = 0
|
||||
@@ -6368,7 +6397,7 @@ class AIAgent:
|
||||
self._iters_since_skill = 0
|
||||
|
||||
try:
|
||||
function_args = json.loads(tool_call.function.arguments)
|
||||
function_args = json.loads(repair_json(tool_call.function.arguments))
|
||||
except json.JSONDecodeError as e:
|
||||
logging.warning(f"Unexpected JSON error after validation: {e}")
|
||||
function_args = {}
|
||||
|
||||
374
scripts/memory_budget.py
Normal file
374
scripts/memory_budget.py
Normal file
@@ -0,0 +1,374 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Memory Budget Enforcement Tool for hermes-agent.
|
||||
|
||||
Checks and enforces character/token budgets on MEMORY.md and USER.md files.
|
||||
Designed for CI integration, pre-commit hooks, and manual health checks.
|
||||
|
||||
Usage:
|
||||
python scripts/memory_budget.py # Check budget (exit 0/1)
|
||||
python scripts/memory_budget.py --report # Detailed breakdown
|
||||
python scripts/memory_budget.py --enforce # Trim entries to fit budget
|
||||
python scripts/memory_budget.py --hermes-home ~/.hermes # Custom HERMES_HOME
|
||||
|
||||
Exit codes:
|
||||
0 Within budget
|
||||
1 Over budget (no trimming performed)
|
||||
2 Entries were trimmed (--enforce was used)
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Constants (must stay in sync with tools/memory_tool.py)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
ENTRY_DELIMITER = "\n§\n"
|
||||
DEFAULT_MEMORY_CHAR_LIMIT = 2200
|
||||
DEFAULT_USER_CHAR_LIMIT = 1375
|
||||
WARN_THRESHOLD = 0.80 # alert when >80% of budget used
|
||||
CHARS_PER_TOKEN = 4 # rough estimate matching agent/model_metadata.py
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Data structures
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@dataclass
|
||||
class FileReport:
|
||||
"""Budget analysis for a single memory file."""
|
||||
label: str # "MEMORY.md" or "USER.md"
|
||||
path: Path
|
||||
exists: bool
|
||||
char_limit: int
|
||||
raw_chars: int # raw file size in chars
|
||||
entry_chars: int # chars after splitting/rejoining entries
|
||||
entry_count: int
|
||||
entries: List[str] # individual entry texts
|
||||
|
||||
@property
|
||||
def usage_pct(self) -> float:
|
||||
if self.char_limit <= 0:
|
||||
return 0.0
|
||||
return min(100.0, (self.entry_chars / self.char_limit) * 100)
|
||||
|
||||
@property
|
||||
def estimated_tokens(self) -> int:
|
||||
return self.entry_chars // CHARS_PER_TOKEN
|
||||
|
||||
@property
|
||||
def over_budget(self) -> bool:
|
||||
return self.entry_chars > self.char_limit
|
||||
|
||||
@property
|
||||
def warning(self) -> bool:
|
||||
return self.usage_pct >= (WARN_THRESHOLD * 100)
|
||||
|
||||
@property
|
||||
def remaining_chars(self) -> int:
|
||||
return max(0, self.char_limit - self.entry_chars)
|
||||
|
||||
|
||||
def _read_entries(path: Path) -> List[str]:
|
||||
"""Read a memory file and split into entries (matching MemoryStore logic)."""
|
||||
if not path.exists():
|
||||
return []
|
||||
try:
|
||||
raw = path.read_text(encoding="utf-8")
|
||||
except (OSError, IOError):
|
||||
return []
|
||||
if not raw.strip():
|
||||
return []
|
||||
entries = [e.strip() for e in raw.split(ENTRY_DELIMITER)]
|
||||
return [e for e in entries if e]
|
||||
|
||||
|
||||
def _write_entries(path: Path, entries: List[str]) -> None:
|
||||
"""Write entries back to a memory file."""
|
||||
content = ENTRY_DELIMITER.join(entries) if entries else ""
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(content, encoding="utf-8")
|
||||
|
||||
|
||||
def analyze_file(path: Path, label: str, char_limit: int) -> FileReport:
|
||||
"""Analyze a single memory file against its budget."""
|
||||
exists = path.exists()
|
||||
entries = _read_entries(path) if exists else []
|
||||
raw_chars = path.stat().st_size if exists else 0
|
||||
joined = ENTRY_DELIMITER.join(entries)
|
||||
return FileReport(
|
||||
label=label,
|
||||
path=path,
|
||||
exists=exists,
|
||||
char_limit=char_limit,
|
||||
raw_chars=raw_chars,
|
||||
entry_chars=len(joined),
|
||||
entry_count=len(entries),
|
||||
entries=entries,
|
||||
)
|
||||
|
||||
|
||||
def trim_entries(report: FileReport) -> List[str]:
|
||||
"""Trim oldest entries until the file fits within its budget.
|
||||
|
||||
Entries are removed from the front (oldest first) because memory files
|
||||
append new entries at the end.
|
||||
"""
|
||||
entries = list(report.entries)
|
||||
joined = ENTRY_DELIMITER.join(entries)
|
||||
|
||||
while len(joined) > report.char_limit and entries:
|
||||
entries.pop(0)
|
||||
joined = ENTRY_DELIMITER.join(entries)
|
||||
|
||||
return entries
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Reporting
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _bar(pct: float, width: int = 30) -> str:
|
||||
"""Render a text progress bar."""
|
||||
filled = int(pct / 100 * width)
|
||||
bar = "#" * filled + "-" * (width - filled)
|
||||
return f"[{bar}]"
|
||||
|
||||
|
||||
def print_report(memory: FileReport, user: FileReport, *, verbose: bool = False) -> None:
|
||||
"""Print a human-readable budget report."""
|
||||
total_chars = memory.entry_chars + user.entry_chars
|
||||
total_limit = memory.char_limit + user.char_limit
|
||||
total_tokens = total_chars // CHARS_PER_TOKEN
|
||||
total_pct = (total_chars / total_limit * 100) if total_limit > 0 else 0
|
||||
|
||||
print("=" * 60)
|
||||
print(" MEMORY BUDGET REPORT")
|
||||
print("=" * 60)
|
||||
print()
|
||||
|
||||
for rpt in (memory, user):
|
||||
status = "OVER " if rpt.over_budget else ("WARN" if rpt.warning else " OK ")
|
||||
print(f" {rpt.label:12s} {status} {_bar(rpt.usage_pct)} {rpt.usage_pct:5.1f}%")
|
||||
print(f" {'':12s} {rpt.entry_chars:,}/{rpt.char_limit:,} chars "
|
||||
f"| {rpt.entry_count} entries "
|
||||
f"| ~{rpt.estimated_tokens:,} tokens")
|
||||
if rpt.exists and verbose and rpt.entries:
|
||||
for i, entry in enumerate(rpt.entries):
|
||||
preview = entry[:72].replace("\n", " ")
|
||||
if len(entry) > 72:
|
||||
preview += "..."
|
||||
print(f" #{i+1}: ({len(entry)} chars) {preview}")
|
||||
print()
|
||||
|
||||
print(f" TOTAL {_bar(total_pct)} {total_pct:5.1f}%")
|
||||
print(f" {total_chars:,}/{total_limit:,} chars | ~{total_tokens:,} tokens")
|
||||
print()
|
||||
|
||||
# Alerts
|
||||
alerts = []
|
||||
for rpt in (memory, user):
|
||||
if rpt.over_budget:
|
||||
overshoot = rpt.entry_chars - rpt.char_limit
|
||||
alerts.append(
|
||||
f" CRITICAL {rpt.label} is {overshoot:,} chars over budget "
|
||||
f"({rpt.entry_chars:,}/{rpt.char_limit:,}). "
|
||||
f"Run with --enforce to auto-trim."
|
||||
)
|
||||
elif rpt.warning:
|
||||
alerts.append(
|
||||
f" WARNING {rpt.label} is at {rpt.usage_pct:.0f}% capacity. "
|
||||
f"Consider compressing or cleaning up entries."
|
||||
)
|
||||
|
||||
if alerts:
|
||||
print(" ALERTS")
|
||||
print(" ------")
|
||||
for a in alerts:
|
||||
print(a)
|
||||
print()
|
||||
|
||||
|
||||
def print_json(memory: FileReport, user: FileReport) -> None:
|
||||
"""Print a JSON report for machine consumption."""
|
||||
import json
|
||||
|
||||
def _rpt_dict(r: FileReport) -> dict:
|
||||
return {
|
||||
"label": r.label,
|
||||
"path": str(r.path),
|
||||
"exists": r.exists,
|
||||
"char_limit": r.char_limit,
|
||||
"entry_chars": r.entry_chars,
|
||||
"entry_count": r.entry_count,
|
||||
"estimated_tokens": r.estimated_tokens,
|
||||
"usage_pct": round(r.usage_pct, 1),
|
||||
"over_budget": r.over_budget,
|
||||
"warning": r.warning,
|
||||
"remaining_chars": r.remaining_chars,
|
||||
}
|
||||
|
||||
total_chars = memory.entry_chars + user.entry_chars
|
||||
total_limit = memory.char_limit + user.char_limit
|
||||
|
||||
data = {
|
||||
"memory": _rpt_dict(memory),
|
||||
"user": _rpt_dict(user),
|
||||
"total": {
|
||||
"chars": total_chars,
|
||||
"limit": total_limit,
|
||||
"estimated_tokens": total_chars // CHARS_PER_TOKEN,
|
||||
"usage_pct": round((total_chars / total_limit * 100) if total_limit else 0, 1),
|
||||
"over_budget": memory.over_budget or user.over_budget,
|
||||
"warning": memory.warning or user.warning,
|
||||
},
|
||||
}
|
||||
print(json.dumps(data, indent=2))
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Main
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _resolve_hermes_home(custom: str | None) -> Path:
|
||||
"""Resolve HERMES_HOME directory."""
|
||||
if custom:
|
||||
return Path(custom).expanduser()
|
||||
import os
|
||||
return Path(os.getenv("HERMES_HOME", Path.home() / ".hermes"))
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Check and enforce memory budgets for hermes-agent.",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog=__doc__,
|
||||
)
|
||||
parser.add_argument(
|
||||
"--hermes-home", metavar="DIR",
|
||||
help="Custom HERMES_HOME directory (default: $HERMES_HOME or ~/.hermes)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--memory-limit", type=int, default=DEFAULT_MEMORY_CHAR_LIMIT,
|
||||
help=f"Character limit for MEMORY.md (default: {DEFAULT_MEMORY_CHAR_LIMIT})",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--user-limit", type=int, default=DEFAULT_USER_CHAR_LIMIT,
|
||||
help=f"Character limit for USER.md (default: {DEFAULT_USER_CHAR_LIMIT})",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--report", action="store_true",
|
||||
help="Print detailed per-file budget report",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--verbose", "-v", action="store_true",
|
||||
help="Show individual entry details in report",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--enforce", action="store_true",
|
||||
help="Trim oldest entries to fit within budget (writes to disk)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--json", action="store_true", dest="json_output",
|
||||
help="Output report as JSON (for CI/scripting)",
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
hermes_home = _resolve_hermes_home(args.hermes_home)
|
||||
memories_dir = hermes_home / "memories"
|
||||
|
||||
# Analyze both files
|
||||
memory = analyze_file(
|
||||
memories_dir / "MEMORY.md", "MEMORY.md", args.memory_limit,
|
||||
)
|
||||
user = analyze_file(
|
||||
memories_dir / "USER.md", "USER.md", args.user_limit,
|
||||
)
|
||||
|
||||
over_budget = memory.over_budget or user.over_budget
|
||||
trimmed = False
|
||||
|
||||
# Enforce budget by trimming entries
|
||||
if args.enforce and over_budget:
|
||||
for rpt in (memory, user):
|
||||
if rpt.over_budget and rpt.exists:
|
||||
trimmed_entries = trim_entries(rpt)
|
||||
removed = rpt.entry_count - len(trimmed_entries)
|
||||
if removed > 0:
|
||||
_write_entries(rpt.path, trimmed_entries)
|
||||
rpt.entries = trimmed_entries
|
||||
rpt.entry_count = len(trimmed_entries)
|
||||
rpt.entry_chars = len(ENTRY_DELIMITER.join(trimmed_entries))
|
||||
rpt.raw_chars = rpt.path.stat().st_size
|
||||
print(f" Trimmed {removed} oldest entries from {rpt.label} "
|
||||
f"({rpt.entry_chars:,}/{rpt.char_limit:,} chars now)")
|
||||
trimmed = True
|
||||
# Re-check after trimming
|
||||
over_budget = memory.over_budget or user.over_budget
|
||||
|
||||
# Output
|
||||
if args.json_output:
|
||||
print_json(memory, user)
|
||||
elif args.report or args.verbose:
|
||||
print_report(memory, user, verbose=args.verbose)
|
||||
else:
|
||||
# Compact summary
|
||||
if over_budget:
|
||||
print("Memory budget: OVER")
|
||||
for rpt in (memory, user):
|
||||
if rpt.over_budget:
|
||||
print(f" {rpt.label}: {rpt.entry_chars:,}/{rpt.char_limit:,} chars "
|
||||
f"({rpt.usage_pct:.0f}%)")
|
||||
elif memory.warning or user.warning:
|
||||
print("Memory budget: WARNING")
|
||||
for rpt in (memory, user):
|
||||
if rpt.warning:
|
||||
print(f" {rpt.label}: {rpt.entry_chars:,}/{rpt.char_limit:,} chars "
|
||||
f"({rpt.usage_pct:.0f}%)")
|
||||
else:
|
||||
print("Memory budget: OK")
|
||||
for rpt in (memory, user):
|
||||
if rpt.exists:
|
||||
print(f" {rpt.label}: {rpt.entry_chars:,}/{rpt.char_limit:,} chars "
|
||||
f"({rpt.usage_pct:.0f}%)")
|
||||
|
||||
# Suggest actions when over budget but not enforced
|
||||
if over_budget and not args.enforce:
|
||||
suggestions = []
|
||||
for rpt in (memory, user):
|
||||
if rpt.over_budget:
|
||||
suggestions.append(
|
||||
f" - {rpt.label}: remove stale entries or run with --enforce to auto-trim"
|
||||
)
|
||||
# Identify largest entries
|
||||
if rpt.entries:
|
||||
indexed = sorted(enumerate(rpt.entries), key=lambda x: len(x[1]), reverse=True)
|
||||
top3 = indexed[:3]
|
||||
for idx, entry in top3:
|
||||
preview = entry[:60].replace("\n", " ")
|
||||
if len(entry) > 60:
|
||||
preview += "..."
|
||||
suggestions.append(
|
||||
f" largest entry #{idx+1}: ({len(entry)} chars) {preview}"
|
||||
)
|
||||
if suggestions:
|
||||
print()
|
||||
print("Suggestions:")
|
||||
for s in suggestions:
|
||||
print(s)
|
||||
|
||||
# Exit code
|
||||
if trimmed:
|
||||
return 2
|
||||
if over_budget:
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
430
scripts/setup_matrix.py
Executable file
430
scripts/setup_matrix.py
Executable file
@@ -0,0 +1,430 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Interactive Matrix setup wizard for Hermes Agent.
|
||||
|
||||
Guides you through configuring Matrix integration:
|
||||
- Homeserver URL
|
||||
- Token auth or password auth
|
||||
- Device ID generation
|
||||
- Config/env file writing
|
||||
- Optional: test room creation and message send
|
||||
- E2EE verification
|
||||
|
||||
Usage:
|
||||
python scripts/setup_matrix.py
|
||||
"""
|
||||
|
||||
import getpass
|
||||
import json
|
||||
import os
|
||||
import secrets
|
||||
import sys
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _hermes_home() -> Path:
|
||||
"""Resolve ~/.hermes (or HERMES_HOME override)."""
|
||||
return Path(os.environ.get("HERMES_HOME", Path.home() / ".hermes"))
|
||||
|
||||
|
||||
def _prompt(msg: str, default: str = "") -> str:
|
||||
"""Prompt with optional default. Returns stripped input or default."""
|
||||
suffix = f" [{default}]" if default else ""
|
||||
val = input(f"{msg}{suffix}: ").strip()
|
||||
return val or default
|
||||
|
||||
|
||||
def _prompt_bool(msg: str, default: bool = True) -> bool:
|
||||
"""Yes/no prompt."""
|
||||
d = "Y/n" if default else "y/N"
|
||||
val = input(f"{msg} [{d}]: ").strip().lower()
|
||||
if not val:
|
||||
return default
|
||||
return val in ("y", "yes")
|
||||
|
||||
|
||||
def _http_post_json(url: str, data: dict, timeout: int = 15) -> dict:
|
||||
"""POST JSON and return parsed response. Raises on HTTP errors."""
|
||||
body = json.dumps(data).encode()
|
||||
req = urllib.request.Request(
|
||||
url,
|
||||
data=body,
|
||||
headers={"Content-Type": "application/json"},
|
||||
method="POST",
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
||||
return json.loads(resp.read())
|
||||
except urllib.error.HTTPError as exc:
|
||||
detail = exc.read().decode(errors="replace")
|
||||
raise RuntimeError(f"HTTP {exc.code}: {detail}") from exc
|
||||
except urllib.error.URLError as exc:
|
||||
raise RuntimeError(f"Connection error: {exc.reason}") from exc
|
||||
|
||||
|
||||
def _http_get_json(url: str, token: str = "", timeout: int = 15) -> dict:
|
||||
"""GET JSON, optionally with Bearer auth."""
|
||||
req = urllib.request.Request(url, method="GET")
|
||||
if token:
|
||||
req.add_header("Authorization", f"Bearer {token}")
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
||||
return json.loads(resp.read())
|
||||
except urllib.error.HTTPError as exc:
|
||||
detail = exc.read().decode(errors="replace")
|
||||
raise RuntimeError(f"HTTP {exc.code}: {detail}") from exc
|
||||
except urllib.error.URLError as exc:
|
||||
raise RuntimeError(f"Connection error: {exc.reason}") from exc
|
||||
|
||||
|
||||
def _write_env_file(env_path: Path, vars: dict) -> None:
|
||||
"""Write/update ~/.hermes/.env with given variables."""
|
||||
existing: dict[str, str] = {}
|
||||
if env_path.exists():
|
||||
for line in env_path.read_text().splitlines():
|
||||
line = line.strip()
|
||||
if line and not line.startswith("#") and "=" in line:
|
||||
k, v = line.split("=", 1)
|
||||
existing[k.strip()] = v.strip().strip("'\"")
|
||||
|
||||
existing.update(vars)
|
||||
|
||||
lines = ["# Hermes Agent environment variables"]
|
||||
for k, v in sorted(existing.items()):
|
||||
# Quote values with spaces or special chars
|
||||
if any(c in v for c in " \t#\"'$"):
|
||||
lines.append(f'{k}="{v}"')
|
||||
else:
|
||||
lines.append(f"{k}={v}")
|
||||
|
||||
env_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
env_path.write_text("\n".join(lines) + "\n")
|
||||
try:
|
||||
os.chmod(str(env_path), 0o600)
|
||||
except (OSError, NotImplementedError):
|
||||
pass
|
||||
print(f" -> Wrote {len(vars)} vars to {env_path}")
|
||||
|
||||
|
||||
def _write_config_yaml(config_path: Path, matrix_section: dict) -> None:
|
||||
"""Add/update matrix: section in config.yaml (creates file if needed)."""
|
||||
try:
|
||||
import yaml
|
||||
except ImportError:
|
||||
print(" [!] PyYAML not installed — skipping config.yaml update.")
|
||||
print(" Add manually under 'matrix:' key.")
|
||||
return
|
||||
|
||||
config: dict = {}
|
||||
if config_path.exists():
|
||||
try:
|
||||
config = yaml.safe_load(config_path.read_text()) or {}
|
||||
except Exception:
|
||||
config = {}
|
||||
|
||||
config["matrix"] = matrix_section
|
||||
|
||||
config_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
config_path.write_text(yaml.dump(config, default_flow_style=False, sort_keys=False))
|
||||
try:
|
||||
os.chmod(str(config_path), 0o600)
|
||||
except (OSError, NotImplementedError):
|
||||
pass
|
||||
print(f" -> Updated matrix section in {config_path}")
|
||||
|
||||
|
||||
def _generate_device_id() -> str:
|
||||
"""Generate a stable, human-readable device ID."""
|
||||
return f"HERMES_{secrets.token_hex(4).upper()}"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Login flows
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def login_with_token(homeserver: str) -> dict:
|
||||
"""Validate an existing access token via whoami."""
|
||||
token = getpass.getpass("Access token (hidden): ").strip()
|
||||
if not token:
|
||||
print(" [!] Token cannot be empty.")
|
||||
sys.exit(1)
|
||||
|
||||
whoami_url = f"{homeserver}/_matrix/client/v3/account/whoami"
|
||||
print(" Validating token...")
|
||||
resp = _http_get_json(whoami_url, token=token)
|
||||
|
||||
user_id = resp.get("user_id", "")
|
||||
device_id = resp.get("device_id", "")
|
||||
print(f" Authenticated as: {user_id}")
|
||||
if device_id:
|
||||
print(f" Server device ID: {device_id}")
|
||||
|
||||
return {
|
||||
"MATRIX_ACCESS_TOKEN": token,
|
||||
"MATRIX_USER_ID": user_id,
|
||||
}
|
||||
|
||||
|
||||
def login_with_password(homeserver: str) -> dict:
|
||||
"""Login with username + password, get access token."""
|
||||
user_id = _prompt("Full user ID (e.g. @bot:matrix.org)")
|
||||
if not user_id:
|
||||
print(" [!] User ID cannot be empty.")
|
||||
sys.exit(1)
|
||||
|
||||
password = getpass.getpass("Password (hidden): ").strip()
|
||||
if not password:
|
||||
print(" [!] Password cannot be empty.")
|
||||
sys.exit(1)
|
||||
|
||||
login_url = f"{homeserver}/_matrix/client/v3/login"
|
||||
print(" Logging in...")
|
||||
resp = _http_post_json(login_url, {
|
||||
"type": "m.login.password",
|
||||
"identifier": {
|
||||
"type": "m.id.user",
|
||||
"user": user_id,
|
||||
},
|
||||
"password": password,
|
||||
"device_name": "Hermes Agent",
|
||||
})
|
||||
|
||||
access_token = resp.get("access_token", "")
|
||||
device_id = resp.get("device_id", "")
|
||||
resolved_user = resp.get("user_id", user_id)
|
||||
|
||||
if not access_token:
|
||||
print(" [!] Login succeeded but no access_token in response.")
|
||||
sys.exit(1)
|
||||
|
||||
print(f" Authenticated as: {resolved_user}")
|
||||
if device_id:
|
||||
print(f" Device ID: {device_id}")
|
||||
|
||||
return {
|
||||
"MATRIX_ACCESS_TOKEN": access_token,
|
||||
"MATRIX_USER_ID": resolved_user,
|
||||
"_server_device_id": device_id,
|
||||
}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Test room + message
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def create_test_room(homeserver: str, token: str) -> str | None:
|
||||
"""Create a private test room and return the room ID."""
|
||||
create_url = f"{homeserver}/_matrix/client/v3/createRoom"
|
||||
try:
|
||||
resp = _http_post_json(create_url, {
|
||||
"name": "Hermes Test Room",
|
||||
"topic": "Auto-created by hermes setup_matrix.py — safe to delete",
|
||||
"preset": "private_chat",
|
||||
"visibility": "private",
|
||||
}, timeout=30)
|
||||
# Set auth header manually (createRoom needs proper auth)
|
||||
room_id = resp.get("room_id", "")
|
||||
if room_id:
|
||||
print(f" Created test room: {room_id}")
|
||||
return room_id
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Fallback: use curl-style with auth
|
||||
req = urllib.request.Request(
|
||||
create_url,
|
||||
data=json.dumps({
|
||||
"name": "Hermes Test Room",
|
||||
"topic": "Auto-created by hermes setup_matrix.py — safe to delete",
|
||||
"preset": "private_chat",
|
||||
"visibility": "private",
|
||||
}).encode(),
|
||||
headers={
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": f"Bearer {token}",
|
||||
},
|
||||
method="POST",
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=30) as resp:
|
||||
data = json.loads(resp.read())
|
||||
room_id = data.get("room_id", "")
|
||||
if room_id:
|
||||
print(f" Created test room: {room_id}")
|
||||
return room_id
|
||||
except Exception as exc:
|
||||
print(f" [!] Room creation failed: {exc}")
|
||||
return None
|
||||
|
||||
|
||||
def send_test_message(homeserver: str, token: str, room_id: str) -> bool:
|
||||
"""Send a test message to a room. Returns True on success."""
|
||||
txn_id = secrets.token_hex(8)
|
||||
url = (
|
||||
f"{homeserver}/_matrix/client/v3/rooms/"
|
||||
f"{urllib.request.quote(room_id, safe='')}/send/m.room.message/{txn_id}"
|
||||
)
|
||||
req = urllib.request.Request(
|
||||
url,
|
||||
data=json.dumps({
|
||||
"msgtype": "m.text",
|
||||
"body": "Hermes Agent setup verified successfully!",
|
||||
}).encode(),
|
||||
headers={
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": f"Bearer {token}",
|
||||
},
|
||||
method="PUT",
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=15) as resp:
|
||||
data = json.loads(resp.read())
|
||||
event_id = data.get("event_id", "")
|
||||
if event_id:
|
||||
print(f" Test message sent: {event_id}")
|
||||
return True
|
||||
except Exception as exc:
|
||||
print(f" [!] Test message failed: {exc}")
|
||||
return False
|
||||
|
||||
|
||||
def check_e2ee_support() -> bool:
|
||||
"""Check if E2EE dependencies are available."""
|
||||
try:
|
||||
import nio
|
||||
from nio.crypto import ENCRYPTION_ENABLED
|
||||
return bool(ENCRYPTION_ENABLED)
|
||||
except (ImportError, AttributeError):
|
||||
return False
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Main
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def main():
|
||||
print("=" * 60)
|
||||
print(" Hermes Agent — Matrix Setup Wizard")
|
||||
print("=" * 60)
|
||||
print()
|
||||
|
||||
# -- Homeserver --
|
||||
print("Step 1: Homeserver")
|
||||
print(" A) matrix.org (public, for testing)")
|
||||
print(" B) Custom homeserver (self-hosted)")
|
||||
choice = _prompt("Choose [A/B]", "A").upper()
|
||||
|
||||
if choice == "B":
|
||||
homeserver = _prompt("Homeserver URL (e.g. https://matrix.example.com)")
|
||||
if not homeserver:
|
||||
print(" [!] Homeserver URL is required.")
|
||||
sys.exit(1)
|
||||
else:
|
||||
homeserver = "https://matrix-client.matrix.org"
|
||||
|
||||
homeserver = homeserver.rstrip("/")
|
||||
print(f" Using: {homeserver}")
|
||||
print()
|
||||
|
||||
# -- Authentication --
|
||||
print("Step 2: Authentication")
|
||||
print(" A) Access token (recommended)")
|
||||
print(" B) Username + password")
|
||||
auth_choice = _prompt("Choose [A/B]", "A").upper()
|
||||
|
||||
if auth_choice == "B":
|
||||
auth_vars = login_with_password(homeserver)
|
||||
else:
|
||||
auth_vars = login_with_token(homeserver)
|
||||
print()
|
||||
|
||||
# -- Device ID --
|
||||
print("Step 3: Device ID (for E2EE persistence)")
|
||||
server_device = auth_vars.pop("_server_device_id", "")
|
||||
default_device = server_device or _generate_device_id()
|
||||
device_id = _prompt("Device ID", default_device)
|
||||
auth_vars["MATRIX_DEVICE_ID"] = device_id
|
||||
print()
|
||||
|
||||
# -- E2EE --
|
||||
print("Step 4: End-to-End Encryption")
|
||||
e2ee_available = check_e2ee_support()
|
||||
if e2ee_available:
|
||||
enable_e2ee = _prompt_bool("Enable E2EE?", default=False)
|
||||
if enable_e2ee:
|
||||
auth_vars["MATRIX_ENCRYPTION"] = "true"
|
||||
print(" E2EE enabled. Keys will be stored in:")
|
||||
print(" ~/.hermes/platforms/matrix/store/")
|
||||
else:
|
||||
print(" E2EE dependencies not found. Skipping.")
|
||||
print(" To enable later: pip install 'matrix-nio[e2e]'")
|
||||
print()
|
||||
|
||||
# -- Optional settings --
|
||||
print("Step 5: Optional Settings")
|
||||
allowed = _prompt("Allowed user IDs (comma-separated, or empty for all)")
|
||||
if allowed:
|
||||
auth_vars["MATRIX_ALLOWED_USERS"] = allowed
|
||||
|
||||
home_room = _prompt("Home room ID for notifications (or empty)")
|
||||
if home_room:
|
||||
auth_vars["MATRIX_HOME_ROOM"] = home_room
|
||||
|
||||
require_mention = _prompt_bool("Require @mention in rooms?", default=True)
|
||||
auto_thread = _prompt_bool("Auto-create threads?", default=True)
|
||||
print()
|
||||
|
||||
# -- Write files --
|
||||
print("Step 6: Writing Configuration")
|
||||
hermes_home = _hermes_home()
|
||||
|
||||
env_path = hermes_home / ".env"
|
||||
_write_env_file(env_path, auth_vars)
|
||||
|
||||
config_path = hermes_home / "config.yaml"
|
||||
matrix_cfg = {
|
||||
"require_mention": require_mention,
|
||||
"auto_thread": auto_thread,
|
||||
}
|
||||
_write_config_yaml(config_path, matrix_cfg)
|
||||
print()
|
||||
|
||||
# -- Verify connection --
|
||||
print("Step 7: Verification")
|
||||
token = auth_vars.get("MATRIX_ACCESS_TOKEN", "")
|
||||
|
||||
do_test = _prompt_bool("Create test room and send message?", default=True)
|
||||
if do_test and token:
|
||||
room_id = create_test_room(homeserver, token)
|
||||
if room_id:
|
||||
send_test_message(homeserver, token, room_id)
|
||||
print()
|
||||
|
||||
# -- Summary --
|
||||
print("=" * 60)
|
||||
print(" Setup Complete!")
|
||||
print("=" * 60)
|
||||
print()
|
||||
print(" Config written to:")
|
||||
print(f" {env_path}")
|
||||
print(f" {config_path}")
|
||||
print()
|
||||
print(" To start the Matrix gateway:")
|
||||
print(" hermes gateway --platform matrix")
|
||||
print()
|
||||
if not e2ee_available:
|
||||
print(" To enable E2EE later:")
|
||||
print(" pip install 'matrix-nio[e2e]'")
|
||||
print(" Then set MATRIX_ENCRYPTION=true in .env")
|
||||
print()
|
||||
print(" Docs: docs/matrix-setup.md")
|
||||
print()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
325
scripts/verify_memory_sovereignty.py
Executable file
325
scripts/verify_memory_sovereignty.py
Executable file
@@ -0,0 +1,325 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Memory Sovereignty Verification
|
||||
|
||||
Verifies that the memory path in hermes-agent has no network dependencies.
|
||||
Memory data must stay on the local filesystem only — no HTTP calls, no external
|
||||
API calls, no cloud sync during memory read/write/flush/load operations.
|
||||
|
||||
Scans:
|
||||
- tools/memory_tool.py (MEMORY.md / USER.md store)
|
||||
- hermes_state.py (SQLite session store)
|
||||
- tools/session_search_tool.py (FTS5 session search + summarization)
|
||||
- tools/graph_store.py (knowledge graph persistence)
|
||||
- tools/temporal_kg_tool.py (temporal knowledge graph)
|
||||
- agent/temporal_knowledge_graph.py (temporal triple store)
|
||||
- tools/skills_tool.py (skill listing/viewing)
|
||||
- tools/skills_sync.py (bundled skill syncing)
|
||||
|
||||
Exit codes:
|
||||
0 = sovereign (no violations)
|
||||
1 = violations found
|
||||
"""
|
||||
|
||||
import ast
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Configuration
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# Files in the memory path to scan (relative to repo root).
|
||||
MEMORY_FILES = [
|
||||
"tools/memory_tool.py",
|
||||
"hermes_state.py",
|
||||
"tools/session_search_tool.py",
|
||||
"tools/graph_store.py",
|
||||
"tools/temporal_kg_tool.py",
|
||||
"agent/temporal_knowledge_graph.py",
|
||||
"tools/skills_tool.py",
|
||||
"tools/skills_sync.py",
|
||||
]
|
||||
|
||||
# Patterns that indicate network/external API usage.
|
||||
NETWORK_PATTERNS = [
|
||||
# HTTP libraries
|
||||
(r'\brequests\.(get|post|put|delete|patch|head|session)', "requests HTTP call"),
|
||||
(r'\burllib\.request\.(urlopen|Request)', "urllib HTTP call"),
|
||||
(r'\bhttpx\.(get|post|put|delete|Client|AsyncClient)', "httpx HTTP call"),
|
||||
(r'\bhttp\.client\.(HTTPConnection|HTTPSConnection)', "http.client connection"),
|
||||
(r'\baiohttp\.(ClientSession|get|post)', "aiohttp HTTP call"),
|
||||
(r'\bwebsockets\.\w+', "websocket connection"),
|
||||
|
||||
# API client patterns
|
||||
(r'\bopenai\b.*\b(api_key|chat|completions|Client)\b', "OpenAI API usage"),
|
||||
(r'\banthropic\b.*\b(api_key|messages|Client)\b', "Anthropic API usage"),
|
||||
(r'\bAsyncOpenAI\b', "AsyncOpenAI client"),
|
||||
(r'\bAsyncAnthropic\b', "AsyncAnthropic client"),
|
||||
|
||||
# Generic network indicators
|
||||
(r'\bsocket\.(socket|connect|create_connection)', "raw socket connection"),
|
||||
(r'\bftplib\b', "FTP connection"),
|
||||
(r'\bsmtplib\b', "SMTP connection"),
|
||||
(r'\bparamiko\b', "SSH connection via paramiko"),
|
||||
|
||||
# URL patterns (hardcoded endpoints)
|
||||
(r'https?://(?!example\.com)[a-zA-Z0-9._-]+\.(com|org|net|io|dev|ai)', "hardcoded URL"),
|
||||
]
|
||||
|
||||
# Import aliases that indicate network-capable modules.
|
||||
NETWORK_IMPORTS = {
|
||||
"requests",
|
||||
"httpx",
|
||||
"aiohttp",
|
||||
"urllib.request",
|
||||
"http.client",
|
||||
"websockets",
|
||||
"openai",
|
||||
"anthropic",
|
||||
"openrouter_client",
|
||||
}
|
||||
|
||||
# Functions whose names suggest network I/O.
|
||||
NETWORK_FUNC_NAMES = {
|
||||
"async_call_llm",
|
||||
"extract_content_or_reasoning",
|
||||
}
|
||||
|
||||
# Files that are ALLOWED to have network calls (known violations with justification).
|
||||
# Each entry maps to a reason string.
|
||||
KNOWN_VIOLATIONS = {
|
||||
"tools/graph_store.py": (
|
||||
"GraphStore persists to Gitea via API. This is a known architectural trade-off "
|
||||
"for knowledge graph persistence, which is not part of the core memory path "
|
||||
"(MEMORY.md/USER.md/SQLite). Future work will explore local-first alternatives "
|
||||
"to align more closely with SOUL.md principles."
|
||||
),
|
||||
"tools/session_search_tool.py": (
|
||||
"Session search uses LLM summarization via an auxiliary client. While the FTS5 "
|
||||
"search is local, the LLM call for summarization is an external dependency. "
|
||||
"This is a temporary architectural trade-off for enhanced presentation. "
|
||||
"Research is ongoing to implement local LLM options for full sovereignty, "
|
||||
"in line with SOUL.md."
|
||||
),
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Scanner
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class Violation:
|
||||
"""A sovereignty violation with location and description."""
|
||||
def __init__(self, file: str, line: int, description: str, code: str):
|
||||
self.file = file
|
||||
self.line = line
|
||||
self.description = description
|
||||
self.code = code.strip()
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.file}:{self.line}: {self.description}\n {self.code}"
|
||||
|
||||
|
||||
def scan_file(filepath: Path, repo_root: Path) -> list[Violation]:
|
||||
"""Scan a single file for network dependency patterns."""
|
||||
violations = []
|
||||
rel_path = str(filepath.relative_to(repo_root))
|
||||
|
||||
# Skip known violations
|
||||
if rel_path in KNOWN_VIOLATIONS:
|
||||
return violations
|
||||
|
||||
try:
|
||||
content = filepath.read_text(encoding="utf-8")
|
||||
except (OSError, IOError) as e:
|
||||
print(f"WARNING: Cannot read {rel_path}: {e}", file=sys.stderr)
|
||||
return violations
|
||||
|
||||
lines = content.splitlines()
|
||||
|
||||
# --- Check imports ---
|
||||
try:
|
||||
tree = ast.parse(content, filename=str(filepath))
|
||||
except SyntaxError as e:
|
||||
print(f"WARNING: Cannot parse {rel_path}: {e}", file=sys.stderr)
|
||||
return violations
|
||||
|
||||
for node in ast.walk(tree):
|
||||
if isinstance(node, ast.Import):
|
||||
for alias in node.names:
|
||||
mod = alias.name
|
||||
if mod in NETWORK_IMPORTS or any(
|
||||
mod.startswith(ni + ".") for ni in NETWORK_IMPORTS
|
||||
):
|
||||
violations.append(Violation(
|
||||
rel_path, node.lineno,
|
||||
f"Network-capable import: {mod}",
|
||||
lines[node.lineno - 1] if node.lineno <= len(lines) else "",
|
||||
))
|
||||
|
||||
elif isinstance(node, ast.ImportFrom):
|
||||
if node.module and (
|
||||
node.module in NETWORK_IMPORTS
|
||||
or any(node.module.startswith(ni + ".") for ni in NETWORK_IMPORTS)
|
||||
):
|
||||
violations.append(Violation(
|
||||
rel_path, node.lineno,
|
||||
f"Network-capable import from: {node.module}",
|
||||
lines[node.lineno - 1] if node.lineno <= len(lines) else "",
|
||||
))
|
||||
|
||||
# --- Check for LLM call function usage ---
|
||||
for i, line in enumerate(lines, 1):
|
||||
stripped = line.strip()
|
||||
if stripped.startswith("#"):
|
||||
continue
|
||||
|
||||
for func_name in NETWORK_FUNC_NAMES:
|
||||
if func_name in line and not stripped.startswith("def ") and not stripped.startswith("class "):
|
||||
# Check it's actually a call, not a definition or import
|
||||
if re.search(r'\b' + func_name + r'\s*\(', line):
|
||||
violations.append(Violation(
|
||||
rel_path, i,
|
||||
f"External LLM call function: {func_name}()",
|
||||
line,
|
||||
))
|
||||
|
||||
# --- Regex-based pattern matching ---
|
||||
for i, line in enumerate(lines, 1):
|
||||
stripped = line.strip()
|
||||
if stripped.startswith("#"):
|
||||
continue
|
||||
|
||||
for pattern, description in NETWORK_PATTERNS:
|
||||
if re.search(pattern, line, re.IGNORECASE):
|
||||
violations.append(Violation(
|
||||
rel_path, i,
|
||||
f"Suspicious pattern ({description})",
|
||||
line,
|
||||
))
|
||||
|
||||
return violations
|
||||
|
||||
|
||||
def verify_sovereignty(repo_root: Path) -> tuple[list[Violation], list[str]]:
|
||||
"""Run sovereignty verification across all memory files.
|
||||
|
||||
Returns (violations, info_messages).
|
||||
"""
|
||||
all_violations = []
|
||||
info = []
|
||||
|
||||
for rel_path in MEMORY_FILES:
|
||||
filepath = repo_root / rel_path
|
||||
if not filepath.exists():
|
||||
info.append(f"SKIP: {rel_path} (file not found)")
|
||||
continue
|
||||
|
||||
if rel_path in KNOWN_VIOLATIONS:
|
||||
info.append(
|
||||
f"WARN: {rel_path} — known violation (excluded from gate): "
|
||||
f"{KNOWN_VIOLATIONS[rel_path]}"
|
||||
)
|
||||
continue
|
||||
|
||||
violations = scan_file(filepath, repo_root)
|
||||
all_violations.extend(violations)
|
||||
|
||||
if not violations:
|
||||
info.append(f"PASS: {rel_path} — sovereign (local-only)")
|
||||
|
||||
return all_violations, info
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Deep analysis helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def check_graph_store_network(repo_root: Path) -> str:
|
||||
"""Analyze graph_store.py for its network dependencies."""
|
||||
filepath = repo_root / "tools" / "graph_store.py"
|
||||
if not filepath.exists():
|
||||
return ""
|
||||
content = filepath.read_text(encoding="utf-8")
|
||||
if "GiteaClient" in content:
|
||||
return (
|
||||
"tools/graph_store.py uses GiteaClient for persistence — "
|
||||
"this is an external API call. However, graph_store is NOT part of "
|
||||
"the core memory path (MEMORY.md/USER.md/SQLite). It is a separate "
|
||||
"knowledge graph system."
|
||||
)
|
||||
return ""
|
||||
|
||||
|
||||
def check_session_search_llm(repo_root: Path) -> str:
|
||||
"""Analyze session_search_tool.py for LLM usage."""
|
||||
filepath = repo_root / "tools" / "session_search_tool.py"
|
||||
if not filepath.exists():
|
||||
return ""
|
||||
content = filepath.read_text(encoding="utf-8")
|
||||
warnings = []
|
||||
if "async_call_llm" in content:
|
||||
warnings.append("uses async_call_llm for summarization")
|
||||
if "auxiliary_client" in content:
|
||||
warnings.append("imports auxiliary_client (LLM calls)")
|
||||
if warnings:
|
||||
return (
|
||||
f"tools/session_search_tool.py: {'; '.join(warnings)}. "
|
||||
f"The FTS5 search is local SQLite, but session summarization "
|
||||
f"involves LLM API calls."
|
||||
)
|
||||
return ""
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Main
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def main():
|
||||
repo_root = Path(__file__).resolve().parent.parent
|
||||
print(f"Memory Sovereignty Verification")
|
||||
print(f"Repository: {repo_root}")
|
||||
print(f"Scanning {len(MEMORY_FILES)} memory-path files...")
|
||||
print()
|
||||
|
||||
violations, info = verify_sovereignty(repo_root)
|
||||
|
||||
# Print info messages
|
||||
for msg in info:
|
||||
print(f" {msg}")
|
||||
|
||||
# Print deep analysis
|
||||
print()
|
||||
print("Deep analysis:")
|
||||
for checker in [check_graph_store_network, check_session_search_llm]:
|
||||
note = checker(repo_root)
|
||||
if note:
|
||||
print(f" NOTE: {note}")
|
||||
|
||||
print()
|
||||
|
||||
if violations:
|
||||
print(f"SOVEREIGNTY VIOLATIONS FOUND: {len(violations)}")
|
||||
print("=" * 60)
|
||||
for v in violations:
|
||||
print(v)
|
||||
print()
|
||||
print("=" * 60)
|
||||
print(
|
||||
f"FAIL: {len(violations)} potential network dependencies detected "
|
||||
f"in the memory path."
|
||||
)
|
||||
print("Memory must be local-only (filesystem + SQLite).")
|
||||
print()
|
||||
print("If a violation is intentional and documented, add it to")
|
||||
print("KNOWN_VIOLATIONS in this script with a justification.")
|
||||
return 1
|
||||
else:
|
||||
print("PASS: Memory path is sovereign — no network dependencies detected.")
|
||||
print("All memory operations use local filesystem and/or SQLite only.")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -121,6 +121,27 @@ workspace/
|
||||
human_eval/ # Human evaluation materials (if needed)
|
||||
```
|
||||
|
||||
### Quick Start with Scaffolder
|
||||
|
||||
Use the scaffolder to create a ready-to-write workspace in one command:
|
||||
|
||||
```bash
|
||||
python3 SKILL_DIR/scripts/scaffold_paper.py init my-paper --venue neurips2025 --title "My Paper Title"
|
||||
cd my-paper
|
||||
# Workspace is ready — fill in scope.md, then proceed to Phase 1
|
||||
```
|
||||
|
||||
This creates the full directory structure (paper/, experiments/, results/, etc.), copies the venue LaTeX template, generates starter files (scope.md, experiment_log.md, TODO.md, cost_tracker.py), initializes git, and makes the first commit.
|
||||
|
||||
```bash
|
||||
# List available venues and their requirements
|
||||
python3 SKILL_DIR/scripts/scaffold_paper.py venues
|
||||
|
||||
# Check project progress at any time
|
||||
python3 SKILL_DIR/scripts/scaffold_paper.py status my-paper
|
||||
```
|
||||
|
||||
|
||||
### Step 0.3: Set Up Version Control
|
||||
|
||||
```bash
|
||||
|
||||
675
skills/research/research-paper-writing/scripts/scaffold_paper.py
Normal file
675
skills/research/research-paper-writing/scripts/scaffold_paper.py
Normal file
@@ -0,0 +1,675 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Research Paper Project Scaffolder.
|
||||
|
||||
Creates a ready-to-write research paper workspace with directory structure,
|
||||
LaTeX templates, experiment tracking files, and git initialization.
|
||||
|
||||
Companion tool for the /research-paper-writing skill.
|
||||
|
||||
Usage:
|
||||
python3 SKILL_DIR/scripts/scaffold_paper.py init my-paper --venue neurips2025 --title "My Paper"
|
||||
python3 SKILL_DIR/scripts/scaffold_paper.py venues
|
||||
python3 SKILL_DIR/scripts/scaffold_paper.py status my-paper
|
||||
|
||||
Pure Python stdlib — no external dependencies.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
|
||||
# ── Venue definitions ─────────────────────────────────────────────
|
||||
|
||||
VENUES = {
|
||||
"neurips2025": {
|
||||
"name": "NeurIPS 2025",
|
||||
"pages": 9,
|
||||
"format": "Double-blind",
|
||||
"checklist": "Paper Checklist required (15 items)",
|
||||
"notes": "No Broader Impact section required since 2022. Supplementary unlimited.",
|
||||
"template_dir": "neurips2025",
|
||||
},
|
||||
"icml2026": {
|
||||
"name": "ICML 2026",
|
||||
"pages": 8,
|
||||
"format": "Double-blind",
|
||||
"checklist": "Broader Impact statement required",
|
||||
"notes": "Strict 8-page limit for main text. Appendix allowed.",
|
||||
"template_dir": "icml2026",
|
||||
},
|
||||
"iclr2026": {
|
||||
"name": "ICLR 2026",
|
||||
"pages": 9,
|
||||
"format": "Double-blind",
|
||||
"checklist": "LLM disclosure required, reciprocal reviewing agreement",
|
||||
"notes": "arXiv posting allowed before submission.",
|
||||
"template_dir": "iclr2026",
|
||||
},
|
||||
"acl": {
|
||||
"name": "ACL (long paper)",
|
||||
"pages": 8,
|
||||
"format": "Double-blind",
|
||||
"checklist": "Mandatory Limitations section, Responsible NLP checklist",
|
||||
"notes": "Short papers: 4 pages. Findings track available.",
|
||||
"template_dir": "acl",
|
||||
},
|
||||
"aaai2026": {
|
||||
"name": "AAAI 2026",
|
||||
"pages": 7,
|
||||
"format": "Double-blind",
|
||||
"checklist": "Strict style file - no modifications whatsoever",
|
||||
"notes": "7-page limit is tight. Plan content carefully.",
|
||||
"template_dir": "aaai2026",
|
||||
},
|
||||
"colm2025": {
|
||||
"name": "COLM 2025",
|
||||
"pages": 9,
|
||||
"format": "Double-blind",
|
||||
"checklist": "Frame contribution for language model community",
|
||||
"notes": "Newer venue focused on language models.",
|
||||
"template_dir": "colm2025",
|
||||
},
|
||||
}
|
||||
|
||||
# ── Directory structure ───────────────────────────────────────────
|
||||
|
||||
DIRS = [
|
||||
"paper",
|
||||
"experiments",
|
||||
"code",
|
||||
"results",
|
||||
"tasks",
|
||||
"human_eval",
|
||||
"context",
|
||||
"figures",
|
||||
"logs",
|
||||
]
|
||||
|
||||
# ── Template content ──────────────────────────────────────────────
|
||||
|
||||
|
||||
def scope_md(title: str, venue_key: str) -> str:
|
||||
v = VENUES.get(venue_key, {})
|
||||
venue_name = v.get("name", venue_key)
|
||||
pages = v.get("pages", "?")
|
||||
return f"""# Research Paper Scope
|
||||
|
||||
## The What (one-sentence contribution)
|
||||
[What is the single thing this paper contributes?]
|
||||
|
||||
## The Why (evidence)
|
||||
[What evidence supports it?]
|
||||
|
||||
## The So What (significance)
|
||||
[Why should readers care?]
|
||||
|
||||
## Title
|
||||
{title}
|
||||
|
||||
## Target Venue
|
||||
{venue_name} ({pages} pages, deadline: TBD)
|
||||
|
||||
## Paper Type
|
||||
- [ ] Empirical (IMRaD)
|
||||
- [ ] Survey / Literature Review
|
||||
- [ ] Position Paper
|
||||
- [ ] Technical Report
|
||||
- [ ] Theory
|
||||
- [ ] Benchmark
|
||||
|
||||
## Authors & Roles
|
||||
| Author | Sections Owned | Role |
|
||||
|--------|---------------|------|
|
||||
| | | Lead |
|
||||
|
||||
## Notation Conventions
|
||||
- Vectors: lowercase bold (\\mathbf{{x}})
|
||||
- Matrices: uppercase bold (\\mathbf{{A}})
|
||||
- Citation style: \\citet{{}} for narrative, \\citep{{}} for parenthetical
|
||||
- British / American spelling: [choose one]
|
||||
"""
|
||||
|
||||
|
||||
EXPERIMENT_LOG_MD = """# Experiment Log
|
||||
|
||||
## Contribution (one sentence)
|
||||
[The paper's main claim]
|
||||
|
||||
## Experiments Run
|
||||
|
||||
### Experiment 1: [Name]
|
||||
- **Claim tested**: [Which paper claim this supports]
|
||||
- **Setup**: [Model, dataset, config, number of runs]
|
||||
- **Key result**: [One sentence with the number]
|
||||
- **Result files**: results/exp1/
|
||||
- **Figures generated**: figures/exp1_*.pdf
|
||||
- **Surprising findings**: [Anything unexpected]
|
||||
|
||||
## Figures
|
||||
| Filename | Description | Section |
|
||||
|----------|-------------|---------|
|
||||
| | | |
|
||||
|
||||
## Failed Experiments (document for honesty)
|
||||
- [What was tried, why it failed, what it tells us]
|
||||
|
||||
## Open Questions
|
||||
- [Anything the results raised that the paper should address]
|
||||
"""
|
||||
|
||||
|
||||
TODO_MD = """# Research Paper TODO
|
||||
|
||||
## Phase 0: Setup
|
||||
- [x] Scaffold project workspace
|
||||
- [ ] Define one-sentence contribution (scope.md)
|
||||
- [ ] Agree on notation conventions
|
||||
|
||||
## Phase 1: Literature Review
|
||||
- [ ] Identify seed papers from existing references
|
||||
- [ ] Search for related work (3 rounds: breadth, depth, targeted)
|
||||
- [ ] Verify every citation programmatically
|
||||
- [ ] Organize related work by methodology
|
||||
|
||||
## Phase 2: Experiment Design
|
||||
- [ ] Map claims to experiments (claim-experiment table)
|
||||
- [ ] Design baselines (naive, strong, ablation, compute-matched)
|
||||
- [ ] Define evaluation protocol (metrics, aggregation, tests, sample sizes)
|
||||
- [ ] Estimate compute budget
|
||||
|
||||
## Phase 3: Execution
|
||||
- [ ] Write experiment scripts
|
||||
- [ ] Run experiments
|
||||
- [ ] Monitor and handle failures
|
||||
- [ ] Commit results with descriptive messages
|
||||
|
||||
## Phase 4: Analysis
|
||||
- [ ] Aggregate results
|
||||
- [ ] Compute statistical significance
|
||||
- [ ] Identify the story (main finding, surprises, failures)
|
||||
- [ ] Create figures and tables
|
||||
- [ ] Write experiment_log.md (bridge to writeup)
|
||||
|
||||
## Phase 5: Drafting
|
||||
- [ ] Write Abstract
|
||||
- [ ] Write Introduction
|
||||
- [ ] Write Related Work
|
||||
- [ ] Write Methods
|
||||
- [ ] Write Experiments / Results
|
||||
- [ ] Write Discussion
|
||||
- [ ] Write Conclusion
|
||||
|
||||
## Phase 6: Self-Review
|
||||
- [ ] Simulate 3 reviewers (skeptical, supportive, methods-focused)
|
||||
- [ ] Address major concerns
|
||||
- [ ] Revision pass
|
||||
|
||||
## Phase 7: Submission
|
||||
- [ ] Conference-specific formatting
|
||||
- [ ] Venue checklist complete
|
||||
- [ ] Final PDF compiles cleanly
|
||||
- [ ] Supplementary materials ready
|
||||
- [ ] Submit
|
||||
"""
|
||||
|
||||
|
||||
GITIGNORE = """# LaTeX auxiliary files
|
||||
*.aux
|
||||
*.bbl
|
||||
*.blg
|
||||
*.fdb_latexmk
|
||||
*.fls
|
||||
*.log
|
||||
*.out
|
||||
*.synctex.gz
|
||||
*.toc
|
||||
*.nav
|
||||
*.snm
|
||||
*.vrb
|
||||
*.lot
|
||||
*.lof
|
||||
*.idx
|
||||
*.ilg
|
||||
*.ind
|
||||
*.bak
|
||||
*.sav
|
||||
|
||||
# Compiled output
|
||||
*.pdf
|
||||
!paper/*.pdf
|
||||
|
||||
# Python
|
||||
__pycache__/
|
||||
*.pyc
|
||||
.venv/
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Editor
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
.idea/
|
||||
.vscode/
|
||||
|
||||
# Large data (download, don't commit)
|
||||
data/*.csv
|
||||
data/*.json
|
||||
data/*.parquet
|
||||
checkpoints/
|
||||
*.ckpt
|
||||
*.pt
|
||||
*.pth
|
||||
"""
|
||||
|
||||
|
||||
# ── Commands ──────────────────────────────────────────────────────
|
||||
|
||||
|
||||
def cmd_init(args):
|
||||
"""Initialize a new research paper project."""
|
||||
project = Path(args.project_dir)
|
||||
|
||||
if project.exists() and any(project.iterdir()):
|
||||
print(f"Error: {project} already exists and is not empty.", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
venue_key = args.venue
|
||||
if venue_key and venue_key not in VENUES:
|
||||
print(f"Error: Unknown venue '{venue_key}'. Run 'venues' to see options.", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
title = args.title or "Untitled Paper"
|
||||
venue_key = venue_key or "neurips2025"
|
||||
|
||||
print(f"Scaffolding project: {project}")
|
||||
print(f" Venue: {VENUES[venue_key]['name']}")
|
||||
print(f" Title: {title}")
|
||||
print()
|
||||
|
||||
# Create directories
|
||||
for d in DIRS:
|
||||
(project / d).mkdir(parents=True, exist_ok=True)
|
||||
print(f" mkdir {d}/")
|
||||
|
||||
# Copy venue template if available
|
||||
skill_dir = Path(__file__).resolve().parent.parent
|
||||
template_src = skill_dir / "templates" / VENUES[venue_key]["template_dir"]
|
||||
if template_src.exists():
|
||||
template_dst = project / "paper"
|
||||
for item in template_src.iterdir():
|
||||
dst = template_dst / item.name
|
||||
if item.is_file():
|
||||
shutil.copy2(item, dst)
|
||||
elif item.is_dir():
|
||||
shutil.copytree(item, dst / item.name, dirs_exist_ok=True)
|
||||
print(f" Copied {venue_key} template to paper/")
|
||||
else:
|
||||
print(f" (No template found at {template_src} — create paper/ files manually)")
|
||||
|
||||
# Write starter files
|
||||
_write(project / "scope.md", scope_md(title, venue_key))
|
||||
_write(project / "experiment_log.md", EXPERIMENT_LOG_MD)
|
||||
_write(project / "TODO.md", TODO_MD)
|
||||
_write(project / "experiment_journal.jsonl", "")
|
||||
_write(project / "results" / "cost_log.jsonl", "")
|
||||
_write(project / ".gitignore", GITIGNORE)
|
||||
|
||||
# Write a minimal README
|
||||
readme = f"# {title}\n\nResearch paper project targeting {VENUES[venue_key]['name']}.\n\n"
|
||||
readme += "Scaffolded with `/research-paper-writing` skill.\n\n"
|
||||
readme += "## Quick Start\n\n"
|
||||
readme += "1. Fill in `scope.md` (contribution, evidence, significance)\n"
|
||||
readme += "2. Literature review (Phase 1)\n"
|
||||
readme += "3. Design experiments (Phase 2)\n"
|
||||
readme += "4. Run experiments, write results to `experiment_log.md` (Phases 3-4)\n"
|
||||
readme += "5. Draft paper in `paper/` (Phase 5)\n"
|
||||
readme += "6. Self-review and submit (Phases 6-7)\n\n"
|
||||
readme += "See `TODO.md` for detailed checklist.\n"
|
||||
_write(project / "README.md", readme)
|
||||
|
||||
# Write cost tracker helper
|
||||
cost_tracker = '''"""
|
||||
Cost tracker for research experiments.
|
||||
|
||||
Usage:
|
||||
from cost_tracker import log_cost, summarize_costs
|
||||
log_cost("exp1", "claude-sonnet-4-20250514", 1000, 500, 0.015)
|
||||
summarize_costs()
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
||||
COST_LOG = os.path.join(os.path.dirname(__file__), "results", "cost_log.jsonl")
|
||||
|
||||
|
||||
def log_cost(experiment: str, model: str, input_tokens: int,
|
||||
output_tokens: int, cost_usd: float):
|
||||
"""Append a cost entry to the cost log."""
|
||||
entry = {
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
"experiment": experiment,
|
||||
"model": model,
|
||||
"input_tokens": input_tokens,
|
||||
"output_tokens": output_tokens,
|
||||
"cost_usd": cost_usd,
|
||||
}
|
||||
with open(COST_LOG, "a") as f:
|
||||
f.write(json.dumps(entry) + "\\n")
|
||||
|
||||
|
||||
def summarize_costs() -> dict:
|
||||
"""Summarize total costs from the cost log."""
|
||||
if not os.path.exists(COST_LOG):
|
||||
return {"total_usd": 0, "entries": 0, "by_experiment": {}}
|
||||
|
||||
total = 0.0
|
||||
by_exp = {}
|
||||
count = 0
|
||||
with open(COST_LOG) as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
entry = json.loads(line)
|
||||
cost = entry.get("cost_usd", 0)
|
||||
total += cost
|
||||
exp = entry.get("experiment", "unknown")
|
||||
by_exp[exp] = by_exp.get(exp, 0) + cost
|
||||
count += 1
|
||||
|
||||
return {"total_usd": round(total, 4), "entries": count, "by_experiment": by_exp}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
summary = summarize_costs()
|
||||
print(json.dumps(summary, indent=2))
|
||||
'''
|
||||
_write(project / "cost_tracker.py", cost_tracker)
|
||||
|
||||
# Initialize git
|
||||
print()
|
||||
try:
|
||||
subprocess.run(["git", "init"], cwd=project, capture_output=True, check=True)
|
||||
subprocess.run(["git", "add", "-A"], cwd=project, capture_output=True, check=True)
|
||||
subprocess.run(
|
||||
["git", "commit", "-m", f"Scaffold research paper project: {title}"],
|
||||
cwd=project,
|
||||
capture_output=True,
|
||||
check=True,
|
||||
env={**os.environ, "GIT_AUTHOR_NAME": "Hermes", "GIT_COMMITTER_NAME": "Hermes",
|
||||
"GIT_AUTHOR_EMAIL": "hermes@local", "GIT_COMMITTER_EMAIL": "hermes@local"},
|
||||
)
|
||||
print(" git init + initial commit")
|
||||
except FileNotFoundError:
|
||||
print(" (git not found — skipping git init)")
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f" (git init failed: {e})")
|
||||
|
||||
print()
|
||||
print(f"Project ready at: {project.resolve()}")
|
||||
print(f"Next: fill in scope.md, then start Phase 1 (Literature Review)")
|
||||
|
||||
|
||||
def cmd_venues(args):
|
||||
"""List available conference templates."""
|
||||
if args.json:
|
||||
print(json.dumps(VENUES, indent=2))
|
||||
return
|
||||
|
||||
print("Available conference templates:")
|
||||
print()
|
||||
print(f" {'Venue':<20} {'Pages':<7} {'Format':<15} Notes")
|
||||
print(f" {'─'*20} {'─'*7} {'─'*15} {'─'*40}")
|
||||
for key, v in VENUES.items():
|
||||
print(f" {key:<20} {v['pages']:<7} {v['format']:<15} {v['checklist']}")
|
||||
print()
|
||||
print("Use: scaffold_paper.py init <project> --venue <venue>")
|
||||
|
||||
|
||||
def cmd_status(args):
|
||||
"""Check project health and phase progress."""
|
||||
project = Path(args.project_dir)
|
||||
|
||||
if not project.exists():
|
||||
print(f"Error: {project} does not exist.", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
if args.json:
|
||||
print(json.dumps(_status_dict(project), indent=2))
|
||||
return
|
||||
|
||||
status = _status_dict(project)
|
||||
print(f"Project: {project.resolve()}")
|
||||
print()
|
||||
|
||||
# Phase artifacts
|
||||
print("Phase Progress:")
|
||||
for phase, info in status["phases"].items():
|
||||
icon = "\u2705" if info["complete"] else "\u2b1c"
|
||||
print(f" {icon} {phase}: {info['detail']}")
|
||||
|
||||
# TODO summary
|
||||
print()
|
||||
todo = status["todo"]
|
||||
print(f"TODO: {todo['done']}/{todo['total']} tasks complete ({todo['pct']}%)")
|
||||
|
||||
# Cost summary
|
||||
if status["costs"]["entries"] > 0:
|
||||
c = status["costs"]
|
||||
print(f"Costs: ${c['total_usd']:.2f} across {c['entries']} entries")
|
||||
for exp, cost in c["by_experiment"].items():
|
||||
print(f" {exp}: ${cost:.4f}")
|
||||
|
||||
# Git status
|
||||
print()
|
||||
git = status["git"]
|
||||
if git["initialized"]:
|
||||
print(f"Git: {git['commits']} commits, {'CLEAN' if git['clean'] else 'DIRTY (' + str(git['uncommitted']) + ' uncommitted)'}")
|
||||
else:
|
||||
print("Git: not initialized")
|
||||
|
||||
# Results
|
||||
results = status["results"]
|
||||
if results["count"] > 0:
|
||||
print(f"Results: {results['count']} result files found")
|
||||
|
||||
print()
|
||||
|
||||
|
||||
def _status_dict(project: Path) -> dict:
|
||||
"""Build a status dictionary for the project."""
|
||||
phases = {}
|
||||
|
||||
# Phase 0: Setup
|
||||
scope_exists = (project / "scope.md").exists()
|
||||
scope_filled = False
|
||||
if scope_exists:
|
||||
content = (project / "scope.md").read_text()
|
||||
scope_filled = "[What is the single thing" not in content
|
||||
phases["Phase 0: Setup"] = {
|
||||
"complete": scope_filled,
|
||||
"detail": "scope.md filled" if scope_filled else ("scope.md exists (template)" if scope_exists else "scope.md missing"),
|
||||
}
|
||||
|
||||
# Phase 1: Literature
|
||||
bib_files = list(project.rglob("*.bib"))
|
||||
phases["Phase 1: Literature"] = {
|
||||
"complete": len(bib_files) > 0,
|
||||
"detail": f"{len(bib_files)} .bib file(s)" if bib_files else "No .bib files yet",
|
||||
}
|
||||
|
||||
# Phase 2-3: Experiments
|
||||
exp_scripts = list((project / "experiments").glob("*.py")) if (project / "experiments").exists() else []
|
||||
phases["Phase 2-3: Experiments"] = {
|
||||
"complete": len(exp_scripts) > 0,
|
||||
"detail": f"{len(exp_scripts)} experiment script(s)" if exp_scripts else "No experiment scripts",
|
||||
}
|
||||
|
||||
# Phase 4: Analysis
|
||||
exp_log = project / "experiment_log.md"
|
||||
log_filled = False
|
||||
if exp_log.exists():
|
||||
content = exp_log.read_text()
|
||||
log_filled = "Experiment 1: [Name]" not in content and len(content.strip()) > 200
|
||||
phases["Phase 4: Analysis"] = {
|
||||
"complete": log_filled,
|
||||
"detail": "experiment_log.md populated" if log_filled else "experiment_log.md is template",
|
||||
}
|
||||
|
||||
# Phase 5: Drafting
|
||||
tex_files = list((project / "paper").rglob("*.tex")) if (project / "paper").exists() else []
|
||||
has_content = False
|
||||
for tf in tex_files:
|
||||
content = tf.read_text()
|
||||
if "\\begin{abstract}" in content and len(content) > 2000:
|
||||
has_content = True
|
||||
break
|
||||
phases["Phase 5: Drafting"] = {
|
||||
"complete": has_content,
|
||||
"detail": f"{len(tex_files)} .tex file(s), {'content detected' if has_content else 'template only'}",
|
||||
}
|
||||
|
||||
# Phase 6-7: Review & Submit
|
||||
pdf_files = list((project / "paper").glob("*.pdf")) if (project / "paper").exists() else []
|
||||
phases["Phase 6-7: Review & Submit"] = {
|
||||
"complete": len(pdf_files) > 0,
|
||||
"detail": f"{len(pdf_files)} PDF(s) in paper/" if pdf_files else "No compiled PDFs",
|
||||
}
|
||||
|
||||
# TODO parsing
|
||||
todo_info = {"done": 0, "total": 0, "pct": 0}
|
||||
todo_file = project / "TODO.md"
|
||||
if todo_file.exists():
|
||||
content = todo_file.read_text()
|
||||
done = content.count("- [x]")
|
||||
undone = content.count("- [ ]")
|
||||
total = done + undone
|
||||
todo_info = {"done": done, "total": total, "pct": round(done / total * 100) if total > 0 else 0}
|
||||
|
||||
# Cost summary
|
||||
cost_info = {"total_usd": 0, "entries": 0, "by_experiment": {}}
|
||||
cost_file = project / "results" / "cost_log.jsonl"
|
||||
if cost_file.exists():
|
||||
total = 0.0
|
||||
by_exp = {}
|
||||
count = 0
|
||||
for line in cost_file.read_text().splitlines():
|
||||
if not line.strip():
|
||||
continue
|
||||
try:
|
||||
entry = json.loads(line)
|
||||
cost = entry.get("cost_usd", 0)
|
||||
total += cost
|
||||
exp = entry.get("experiment", "unknown")
|
||||
by_exp[exp] = by_exp.get(exp, 0) + cost
|
||||
count += 1
|
||||
except json.JSONDecodeError:
|
||||
continue
|
||||
cost_info = {"total_usd": round(total, 4), "entries": count, "by_experiment": by_exp}
|
||||
|
||||
# Git
|
||||
git_info = {"initialized": False, "clean": True, "commits": 0, "uncommitted": 0}
|
||||
if (project / ".git").exists():
|
||||
git_info["initialized"] = True
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["git", "rev-list", "--count", "HEAD"],
|
||||
cwd=project, capture_output=True, text=True
|
||||
)
|
||||
if result.returncode == 0:
|
||||
git_info["commits"] = int(result.stdout.strip())
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["git", "status", "--porcelain"],
|
||||
cwd=project, capture_output=True, text=True
|
||||
)
|
||||
if result.returncode == 0:
|
||||
changes = [l for l in result.stdout.splitlines() if l.strip()]
|
||||
git_info["uncommitted"] = len(changes)
|
||||
git_info["clean"] = len(changes) == 0
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Results
|
||||
result_files = list((project / "results").rglob("*.json")) if (project / "results").exists() else []
|
||||
# Exclude cost_log.jsonl
|
||||
result_files = [f for f in result_files if f.name != "cost_log.jsonl"]
|
||||
results_info = {"count": len(result_files)}
|
||||
|
||||
return {
|
||||
"project": str(project.resolve()),
|
||||
"phases": phases,
|
||||
"todo": todo_info,
|
||||
"costs": cost_info,
|
||||
"git": git_info,
|
||||
"results": results_info,
|
||||
}
|
||||
|
||||
|
||||
# ── Helpers ───────────────────────────────────────────────────────
|
||||
|
||||
|
||||
def _write(path: Path, content: str):
|
||||
"""Write content to a file, creating parent dirs."""
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(content)
|
||||
print(f" write {path.relative_to(path.parent.parent) if len(path.parts) > 2 else path.name}")
|
||||
|
||||
|
||||
# ── CLI ───────────────────────────────────────────────────────────
|
||||
|
||||
COMMAND_MAP = {
|
||||
"init": cmd_init,
|
||||
"venues": cmd_venues,
|
||||
"status": cmd_status,
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
prog="scaffold_paper",
|
||||
description="Research paper project scaffolder. Companion to /research-paper-writing skill.",
|
||||
)
|
||||
subparsers = parser.add_subparsers(dest="command", help="Command to run")
|
||||
|
||||
# init
|
||||
p_init = subparsers.add_parser("init", help="Initialize a new paper project")
|
||||
p_init.add_argument("project_dir", help="Directory to create")
|
||||
p_init.add_argument("--venue", default="neurips2025",
|
||||
choices=list(VENUES.keys()),
|
||||
help="Target conference (default: neurips2025)")
|
||||
p_init.add_argument("--title", default=None, help="Paper title")
|
||||
|
||||
# venues
|
||||
p_venues = subparsers.add_parser("venues", help="List available conference templates")
|
||||
p_venues.add_argument("--json", action="store_true", help="Output as JSON")
|
||||
|
||||
# status
|
||||
p_status = subparsers.add_parser("status", help="Check project status")
|
||||
p_status.add_argument("project_dir", help="Project directory to check")
|
||||
p_status.add_argument("--json", action="store_true", help="Output as JSON")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if not args.command:
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
COMMAND_MAP[args.command](args)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -363,6 +363,7 @@ class TestRunJobSessionPersistence:
|
||||
kwargs = mock_agent_cls.call_args.kwargs
|
||||
assert kwargs["session_db"] is fake_db
|
||||
assert kwargs["platform"] == "cron"
|
||||
assert kwargs["tool_choice"] == "required"
|
||||
assert kwargs["session_id"].startswith("cron_test-job_")
|
||||
fake_db.end_session.assert_called_once()
|
||||
call_args = fake_db.end_session.call_args
|
||||
|
||||
@@ -799,6 +799,12 @@ class TestBuildApiKwargs:
|
||||
assert kwargs["messages"] is messages
|
||||
assert kwargs["timeout"] == 1800.0
|
||||
|
||||
def test_custom_tool_choice_injected_for_chat_completions(self, agent):
|
||||
agent.tool_choice = "required"
|
||||
messages = [{"role": "user", "content": "hi"}]
|
||||
kwargs = agent._build_api_kwargs(messages)
|
||||
assert kwargs["tool_choice"] == "required"
|
||||
|
||||
def test_provider_preferences_injected(self, agent):
|
||||
agent.base_url = "https://openrouter.ai/api/v1"
|
||||
agent.providers_allowed = ["Anthropic"]
|
||||
@@ -2461,6 +2467,16 @@ class TestSaveSessionLogAtomicWrite:
|
||||
class TestBuildApiKwargsAnthropicMaxTokens:
|
||||
"""Bug fix: max_tokens was always None for Anthropic mode, ignoring user config."""
|
||||
|
||||
def test_tool_choice_passed_to_anthropic(self, agent):
|
||||
agent.api_mode = "anthropic_messages"
|
||||
agent.tool_choice = "required"
|
||||
agent.reasoning_config = None
|
||||
|
||||
with patch("agent.anthropic_adapter.build_anthropic_kwargs") as mock_build:
|
||||
mock_build.return_value = {"model": "claude-sonnet-4-20250514", "messages": [], "max_tokens": 4096}
|
||||
agent._build_api_kwargs([{"role": "user", "content": "test"}])
|
||||
assert mock_build.call_args.kwargs["tool_choice"] == "required"
|
||||
|
||||
def test_max_tokens_passed_to_anthropic(self, agent):
|
||||
agent.api_mode = "anthropic_messages"
|
||||
agent.max_tokens = 4096
|
||||
|
||||
@@ -265,6 +265,30 @@ def test_build_api_kwargs_codex(monkeypatch):
|
||||
assert "extra_body" not in kwargs
|
||||
|
||||
|
||||
def test_build_api_kwargs_codex_respects_custom_tool_choice(monkeypatch):
|
||||
_patch_agent_bootstrap(monkeypatch)
|
||||
|
||||
agent = run_agent.AIAgent(
|
||||
model="gpt-5-codex",
|
||||
base_url="https://chatgpt.com/backend-api/codex",
|
||||
api_key="codex-token",
|
||||
tool_choice="required",
|
||||
quiet_mode=True,
|
||||
max_iterations=4,
|
||||
skip_context_files=True,
|
||||
skip_memory=True,
|
||||
)
|
||||
|
||||
kwargs = agent._build_api_kwargs(
|
||||
[
|
||||
{"role": "system", "content": "You are Hermes."},
|
||||
{"role": "user", "content": "Ping"},
|
||||
]
|
||||
)
|
||||
|
||||
assert kwargs["tool_choice"] == "required"
|
||||
|
||||
|
||||
def test_build_api_kwargs_copilot_responses_omits_openai_only_fields(monkeypatch):
|
||||
agent = _build_copilot_agent(monkeypatch)
|
||||
kwargs = agent._build_api_kwargs([{"role": "user", "content": "hi"}])
|
||||
|
||||
572
tools/browser_use_tool.py
Normal file
572
tools/browser_use_tool.py
Normal file
@@ -0,0 +1,572 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Browser Use Tool Module
|
||||
|
||||
Proof-of-concept wrapper around the browser-use Python library for
|
||||
LLM-driven autonomous browser automation. This complements Hermes's
|
||||
existing low-level browser_tool.py (navigate/snapshot/click/type) by
|
||||
providing a high-level "do this task for me" capability.
|
||||
|
||||
Where browser_tool.py gives the LLM fine-grained control (each click is
|
||||
a separate tool call), browser_use_tool.py lets the LLM describe a task
|
||||
in natural language and have browser-use autonomously execute the steps.
|
||||
|
||||
Usage:
|
||||
from tools.browser_use_tool import browser_use_run, browser_use_extract
|
||||
|
||||
# Run an autonomous browser task
|
||||
result = browser_use_run(
|
||||
task="Find the top 3 stories on Hacker News and return their titles",
|
||||
max_steps=15,
|
||||
)
|
||||
|
||||
# Extract structured data from a URL
|
||||
data = browser_use_extract(
|
||||
url="https://example.com/pricing",
|
||||
instruction="Extract all pricing tiers with their names, prices, and features",
|
||||
)
|
||||
|
||||
Integration notes:
|
||||
- Requires: pip install browser-use
|
||||
- Optional: BROWSER_USE_API_KEY for cloud mode (no local Playwright needed)
|
||||
- Falls back to local Playwright Chromium when no API key is set
|
||||
- Uses the same url_safety and website_policy checks as browser_tool.py
|
||||
"""
|
||||
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import tempfile
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Security: URL validation (reuse existing modules)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
try:
|
||||
from tools.url_safety import is_safe_url as _is_safe_url
|
||||
except Exception:
|
||||
_is_safe_url = lambda url: False # noqa: E731 — fail-closed
|
||||
|
||||
try:
|
||||
from tools.website_policy import check_website_access
|
||||
except Exception:
|
||||
check_website_access = lambda url: None # noqa: E731 — fail-open
|
||||
|
||||
|
||||
def _validate_url(url: str) -> Optional[str]:
|
||||
"""Validate a URL for safety and policy compliance.
|
||||
|
||||
Returns None if OK, or an error message string if blocked.
|
||||
"""
|
||||
if not url or not url.strip():
|
||||
return "URL cannot be empty"
|
||||
url = url.strip()
|
||||
if not _is_safe_url(url):
|
||||
return f"URL blocked by safety policy: {url}"
|
||||
try:
|
||||
check_website_access(url)
|
||||
except Exception as e:
|
||||
return f"URL blocked by website policy: {e}"
|
||||
return None
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Availability check
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
_browser_use_available: Optional[bool] = None
|
||||
|
||||
|
||||
def _check_browser_use_available() -> bool:
|
||||
"""Check if browser-use library is installed and usable."""
|
||||
global _browser_use_available
|
||||
if _browser_use_available is not None:
|
||||
return _browser_use_available
|
||||
try:
|
||||
import browser_use # noqa: F401
|
||||
_browser_use_available = True
|
||||
except ImportError:
|
||||
_browser_use_available = False
|
||||
return _browser_use_available
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Core functions
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def browser_use_run(
|
||||
task: str,
|
||||
max_steps: int = 25,
|
||||
model: str = None,
|
||||
url: str = None,
|
||||
use_vision: bool = False,
|
||||
) -> str:
|
||||
"""Run an autonomous browser task using browser-use.
|
||||
|
||||
Args:
|
||||
task: Natural language description of what to do in the browser.
|
||||
max_steps: Maximum number of autonomous steps before stopping.
|
||||
model: LLM model for browser-use's internal agent (default: from env).
|
||||
url: Optional starting URL. If provided, navigates there first.
|
||||
use_vision: Whether to use screenshots for visual context.
|
||||
|
||||
Returns:
|
||||
JSON string with task result, final page content, and metadata.
|
||||
"""
|
||||
if not _check_browser_use_available():
|
||||
return json.dumps({
|
||||
"error": "browser-use library not installed. "
|
||||
"Install with: pip install browser-use && playwright install chromium"
|
||||
})
|
||||
|
||||
# Validate URL if provided
|
||||
if url:
|
||||
err = _validate_url(url)
|
||||
if err:
|
||||
return json.dumps({"error": err})
|
||||
|
||||
# Resolve model
|
||||
if not model:
|
||||
model = os.getenv("BROWSER_USE_MODEL", "").strip() or None
|
||||
|
||||
try:
|
||||
import asyncio
|
||||
from browser_use import Agent, Browser, BrowserConfig
|
||||
from langchain_openai import ChatOpenAI
|
||||
from langchain_anthropic import ChatAnthropic
|
||||
|
||||
return asyncio.run(
|
||||
_run_browser_use_agent(
|
||||
task=task,
|
||||
max_steps=max_steps,
|
||||
model=model,
|
||||
url=url,
|
||||
use_vision=use_vision,
|
||||
)
|
||||
)
|
||||
except ImportError as e:
|
||||
return json.dumps({
|
||||
"error": f"Missing dependency: {e}. "
|
||||
"Install with: pip install browser-use langchain-openai langchain-anthropic"
|
||||
})
|
||||
except Exception as e:
|
||||
logger.exception("browser_use_run failed")
|
||||
return json.dumps({"error": f"Browser use failed: {type(e).__name__}: {e}"})
|
||||
|
||||
|
||||
async def _run_browser_use_agent(
|
||||
task: str,
|
||||
max_steps: int,
|
||||
model: Optional[str],
|
||||
url: Optional[str],
|
||||
use_vision: bool,
|
||||
) -> str:
|
||||
"""Async implementation of browser_use_run."""
|
||||
from browser_use import Agent, Browser, BrowserConfig
|
||||
|
||||
# Build LLM
|
||||
llm = _resolve_langchain_llm(model)
|
||||
if isinstance(llm, str):
|
||||
# Error message returned
|
||||
return llm
|
||||
|
||||
# Configure browser
|
||||
browser_config = BrowserConfig(
|
||||
headless=True,
|
||||
)
|
||||
|
||||
# Build the task string with optional starting URL
|
||||
full_task = task
|
||||
if url:
|
||||
full_task = f"Start by navigating to {url}. Then: {task}"
|
||||
|
||||
# Create agent
|
||||
agent = Agent(
|
||||
task=full_task,
|
||||
llm=llm,
|
||||
browser=Browser(config=browser_config),
|
||||
use_vision=use_vision,
|
||||
max_actions_per_step=5,
|
||||
)
|
||||
|
||||
# Run with step limit
|
||||
result = await agent.run(max_steps=max_steps)
|
||||
|
||||
# Extract results
|
||||
final_url = ""
|
||||
final_content = ""
|
||||
steps_taken = 0
|
||||
|
||||
if hasattr(result, "all_results") and result.all_results:
|
||||
steps_taken = len(result.all_results)
|
||||
last = result.all_results[-1]
|
||||
if hasattr(last, "extracted_content"):
|
||||
final_content = last.extracted_content or ""
|
||||
if hasattr(last, "url"):
|
||||
final_url = last.url or ""
|
||||
|
||||
# Get the final content from the agent's history
|
||||
if hasattr(result, "final_result"):
|
||||
final_content = result.final_result or final_content
|
||||
|
||||
return json.dumps({
|
||||
"success": True,
|
||||
"task": task,
|
||||
"result": final_content,
|
||||
"final_url": final_url,
|
||||
"steps_taken": steps_taken,
|
||||
"max_steps": max_steps,
|
||||
}, indent=2)
|
||||
|
||||
|
||||
def browser_use_extract(
|
||||
url: str,
|
||||
instruction: str = "Extract all meaningful content from this page",
|
||||
max_steps: int = 15,
|
||||
model: str = None,
|
||||
) -> str:
|
||||
"""Navigate to a URL and extract structured data using browser-use.
|
||||
|
||||
This is a convenience wrapper that combines navigation + extraction
|
||||
into a single tool call.
|
||||
|
||||
Args:
|
||||
url: The URL to extract data from.
|
||||
instruction: What to extract (e.g., "Extract all pricing tiers").
|
||||
max_steps: Maximum browser steps.
|
||||
model: LLM model for browser-use agent.
|
||||
|
||||
Returns:
|
||||
JSON string with extracted data.
|
||||
"""
|
||||
err = _validate_url(url)
|
||||
if err:
|
||||
return json.dumps({"error": err})
|
||||
|
||||
task = (
|
||||
f"Navigate to {url}. {instruction}. "
|
||||
f"Return the extracted data in a structured format. "
|
||||
f"When done, use the 'done' action to finish."
|
||||
)
|
||||
|
||||
return browser_use_run(
|
||||
task=task,
|
||||
max_steps=max_steps,
|
||||
model=model,
|
||||
url=url,
|
||||
)
|
||||
|
||||
|
||||
def browser_use_compare(
|
||||
urls: list,
|
||||
instruction: str = "Compare the content on these pages",
|
||||
max_steps: int = 25,
|
||||
model: str = None,
|
||||
) -> str:
|
||||
"""Visit multiple URLs and compare their content.
|
||||
|
||||
Args:
|
||||
urls: List of URLs to visit and compare.
|
||||
instruction: What to compare (e.g., "Compare pricing plans").
|
||||
max_steps: Maximum browser steps.
|
||||
model: LLM model for browser-use agent.
|
||||
|
||||
Returns:
|
||||
JSON string with comparison results.
|
||||
"""
|
||||
if not urls or not isinstance(urls, list):
|
||||
return json.dumps({"error": "urls must be a non-empty list"})
|
||||
|
||||
# Validate all URLs
|
||||
for u in urls:
|
||||
err = _validate_url(u)
|
||||
if err:
|
||||
return json.dumps({"error": f"URL validation failed for {u}: {err}"})
|
||||
|
||||
url_list = "\n".join(f" {i+1}. {u}" for i, u in enumerate(urls))
|
||||
task = (
|
||||
f"Visit each of these URLs and compare them:\n{url_list}\n\n"
|
||||
f"Comparison task: {instruction}\n\n"
|
||||
f"Visit each URL one by one, extract relevant information, "
|
||||
f"then provide a structured comparison. Use the 'done' action when finished."
|
||||
)
|
||||
|
||||
return browser_use_run(
|
||||
task=task,
|
||||
max_steps=max_steps,
|
||||
model=model,
|
||||
url=urls[0],
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# LLM resolution helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _resolve_langchain_llm(model: Optional[str]):
|
||||
"""Build a LangChain LLM from a model string or environment.
|
||||
|
||||
Supports OpenAI and Anthropic models. Returns the LLM instance or
|
||||
an error message string on failure.
|
||||
"""
|
||||
if not model:
|
||||
# Auto-detect from available API keys
|
||||
if os.getenv("ANTHROPIC_API_KEY"):
|
||||
model = "claude-sonnet-4-20250514"
|
||||
elif os.getenv("OPENAI_API_KEY"):
|
||||
model = "gpt-4o"
|
||||
else:
|
||||
return json.dumps({
|
||||
"error": "No LLM model configured for browser-use. "
|
||||
"Set BROWSER_USE_MODEL, ANTHROPIC_API_KEY, or OPENAI_API_KEY."
|
||||
})
|
||||
|
||||
model_lower = model.lower()
|
||||
|
||||
if "claude" in model_lower or "anthropic" in model_lower:
|
||||
try:
|
||||
from langchain_anthropic import ChatAnthropic
|
||||
api_key = os.getenv("ANTHROPIC_API_KEY", "")
|
||||
if not api_key:
|
||||
return json.dumps({"error": "ANTHROPIC_API_KEY not set"})
|
||||
return ChatAnthropic(
|
||||
model=model,
|
||||
api_key=api_key,
|
||||
timeout=60,
|
||||
stop=None,
|
||||
)
|
||||
except ImportError:
|
||||
return json.dumps({
|
||||
"error": "langchain-anthropic not installed. "
|
||||
"Install: pip install langchain-anthropic"
|
||||
})
|
||||
|
||||
# Default to OpenAI-compatible
|
||||
try:
|
||||
from langchain_openai import ChatOpenAI
|
||||
api_key = os.getenv("OPENAI_API_KEY", "")
|
||||
base_url = os.getenv("OPENAI_BASE_URL", None)
|
||||
if not api_key:
|
||||
return json.dumps({"error": "OPENAI_API_KEY not set"})
|
||||
kwargs = {
|
||||
"model": model,
|
||||
"api_key": api_key,
|
||||
"timeout": 60,
|
||||
}
|
||||
if base_url:
|
||||
kwargs["base_url"] = base_url
|
||||
return ChatOpenAI(**kwargs)
|
||||
except ImportError:
|
||||
return json.dumps({
|
||||
"error": "langchain-openai not installed. "
|
||||
"Install: pip install langchain-openai"
|
||||
})
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Schema definitions
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
BROWSER_USE_RUN_SCHEMA = {
|
||||
"name": "browser_use_run",
|
||||
"description": (
|
||||
"Run an autonomous browser task using AI-driven browser automation. "
|
||||
"Describe what you want to accomplish in natural language, and browser-use "
|
||||
"will autonomously navigate, click, type, and extract data to complete it. "
|
||||
"Best for multi-step tasks like 'find X on website Y' or 'fill out this form'. "
|
||||
"For simple single-page extraction, prefer web_extract (faster). "
|
||||
"For fine-grained step-by-step control, use browser_navigate/snapshot/click/type."
|
||||
),
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"task": {
|
||||
"type": "string",
|
||||
"description": "Natural language description of the browser task to perform"
|
||||
},
|
||||
"max_steps": {
|
||||
"type": "integer",
|
||||
"description": "Maximum number of autonomous steps (default: 25)",
|
||||
"default": 25,
|
||||
},
|
||||
"model": {
|
||||
"type": "string",
|
||||
"description": "LLM model for the browser-use agent (default: auto-detect from available API keys)",
|
||||
},
|
||||
"url": {
|
||||
"type": "string",
|
||||
"description": "Optional starting URL to navigate to before beginning the task",
|
||||
},
|
||||
"use_vision": {
|
||||
"type": "boolean",
|
||||
"description": "Use screenshots for visual context (more token-heavy, default: false)",
|
||||
"default": False,
|
||||
},
|
||||
},
|
||||
"required": ["task"],
|
||||
},
|
||||
}
|
||||
|
||||
BROWSER_USE_EXTRACT_SCHEMA = {
|
||||
"name": "browser_use_extract",
|
||||
"description": (
|
||||
"Navigate to a URL and extract structured data using autonomous browser automation. "
|
||||
"Specify what to extract in natural language. This is a convenience wrapper that "
|
||||
"combines navigation + extraction into a single call."
|
||||
),
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"url": {
|
||||
"type": "string",
|
||||
"description": "The URL to navigate to and extract data from"
|
||||
},
|
||||
"instruction": {
|
||||
"type": "string",
|
||||
"description": "What to extract (e.g., 'Extract all pricing tiers with prices and features')",
|
||||
"default": "Extract all meaningful content from this page",
|
||||
},
|
||||
"max_steps": {
|
||||
"type": "integer",
|
||||
"description": "Maximum number of browser steps (default: 15)",
|
||||
"default": 15,
|
||||
},
|
||||
"model": {
|
||||
"type": "string",
|
||||
"description": "LLM model for the browser-use agent",
|
||||
},
|
||||
},
|
||||
"required": ["url"],
|
||||
},
|
||||
}
|
||||
|
||||
BROWSER_USE_COMPARE_SCHEMA = {
|
||||
"name": "browser_use_compare",
|
||||
"description": (
|
||||
"Visit multiple URLs and compare their content using autonomous browser automation. "
|
||||
"Specify what to compare in natural language. The agent will visit each URL, "
|
||||
"extract relevant data, and produce a structured comparison."
|
||||
),
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"urls": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
"description": "List of URLs to visit and compare"
|
||||
},
|
||||
"instruction": {
|
||||
"type": "string",
|
||||
"description": "What to compare (e.g., 'Compare pricing plans and features')",
|
||||
"default": "Compare the content on these pages",
|
||||
},
|
||||
"max_steps": {
|
||||
"type": "integer",
|
||||
"description": "Maximum number of browser steps (default: 25)",
|
||||
"default": 25,
|
||||
},
|
||||
"model": {
|
||||
"type": "string",
|
||||
"description": "LLM model for the browser-use agent",
|
||||
},
|
||||
},
|
||||
"required": ["urls"],
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Handlers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _handle_browser_use_run(args: dict, **kw) -> str:
|
||||
return browser_use_run(
|
||||
task=args.get("task", ""),
|
||||
max_steps=args.get("max_steps", 25),
|
||||
model=args.get("model"),
|
||||
url=args.get("url"),
|
||||
use_vision=args.get("use_vision", False),
|
||||
)
|
||||
|
||||
|
||||
def _handle_browser_use_extract(args: dict, **kw) -> str:
|
||||
return browser_use_extract(
|
||||
url=args.get("url", ""),
|
||||
instruction=args.get("instruction", "Extract all meaningful content from this page"),
|
||||
max_steps=args.get("max_steps", 15),
|
||||
model=args.get("model"),
|
||||
)
|
||||
|
||||
|
||||
def _handle_browser_use_compare(args: dict, **kw) -> str:
|
||||
return browser_use_compare(
|
||||
urls=args.get("urls", []),
|
||||
instruction=args.get("instruction", "Compare the content on these pages"),
|
||||
max_steps=args.get("max_steps", 25),
|
||||
model=args.get("model"),
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Module test
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("Browser Use Tool Module")
|
||||
print("=" * 40)
|
||||
|
||||
if _check_browser_use_available():
|
||||
print("browser-use library: installed")
|
||||
else:
|
||||
print("browser-use library: NOT installed")
|
||||
print(" Install: pip install browser-use && playwright install chromium")
|
||||
|
||||
# Check API keys
|
||||
if os.getenv("ANTHROPIC_API_KEY"):
|
||||
print("ANTHROPIC_API_KEY: set")
|
||||
elif os.getenv("OPENAI_API_KEY"):
|
||||
print("OPENAI_API_KEY: set")
|
||||
else:
|
||||
print("No LLM API keys found (need ANTHROPIC_API_KEY or OPENAI_API_KEY)")
|
||||
|
||||
if os.getenv("BROWSER_USE_API_KEY"):
|
||||
print("BROWSER_USE_API_KEY: set (cloud mode available)")
|
||||
else:
|
||||
print("BROWSER_USE_API_KEY: not set (local Playwright mode)")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Registry
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
from tools.registry import registry
|
||||
|
||||
registry.register(
|
||||
name="browser_use_run",
|
||||
toolset="browser_use",
|
||||
schema=BROWSER_USE_RUN_SCHEMA,
|
||||
handler=_handle_browser_use_run,
|
||||
check_fn=_check_browser_use_available,
|
||||
emoji="🤖",
|
||||
)
|
||||
|
||||
registry.register(
|
||||
name="browser_use_extract",
|
||||
toolset="browser_use",
|
||||
schema=BROWSER_USE_EXTRACT_SCHEMA,
|
||||
handler=_handle_browser_use_extract,
|
||||
check_fn=_check_browser_use_available,
|
||||
emoji="🔍",
|
||||
)
|
||||
|
||||
registry.register(
|
||||
name="browser_use_compare",
|
||||
toolset="browser_use",
|
||||
schema=BROWSER_USE_COMPARE_SCHEMA,
|
||||
handler=_handle_browser_use_compare,
|
||||
check_fn=_check_browser_use_available,
|
||||
emoji="⚖️",
|
||||
)
|
||||
203
tools/provider_allowlist.py
Normal file
203
tools/provider_allowlist.py
Normal file
@@ -0,0 +1,203 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Provider Allowlist Guard — runtime enforcement of the banned provider policy.
|
||||
|
||||
Intercepts model/provider configuration at startup and rejects anything
|
||||
not on the explicit allowlist. Prevents accidental Anthropic/OpenAI
|
||||
fallback from ever reaching inference.
|
||||
|
||||
Usage:
|
||||
Import and call validate_provider() at agent startup:
|
||||
|
||||
from provider_allowlist import validate_provider, ProviderBanned
|
||||
|
||||
try:
|
||||
validate_provider(config["provider"], config["model"])
|
||||
except ProviderBanned as e:
|
||||
logger.error(f"Banned provider: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
The allowlist is intentionally conservative. Add new providers here
|
||||
only after human review and a merged PR.
|
||||
"""
|
||||
import re
|
||||
import os
|
||||
|
||||
class ProviderBanned(Exception):
|
||||
"""Raised when a banned provider is detected."""
|
||||
pass
|
||||
|
||||
# ═══ ALLOWLIST ═══
|
||||
# Only these provider/model combinations are permitted.
|
||||
# Everything else is rejected by default.
|
||||
ALLOWED_PROVIDERS = {
|
||||
"ollama": {
|
||||
"description": "Local inference via Ollama",
|
||||
"model_patterns": [
|
||||
r"^hermes.*", # All Hermes variants
|
||||
r"^gemma.*", # Gemma family
|
||||
r"^qwen.*", # Qwen family
|
||||
r"^llama.*", # Llama family
|
||||
r"^mistral.*", # Mistral family
|
||||
r"^codestral.*", # Codestral
|
||||
r"^deepseek.*", # DeepSeek family
|
||||
r"^phi.*", # Phi family
|
||||
r"^nomic-embed.*", # Embedding models
|
||||
]
|
||||
},
|
||||
"openrouter": {
|
||||
"description": "OpenRouter relay (temporary falsework)",
|
||||
"model_patterns": [
|
||||
r"^google/.*", # Google models via relay
|
||||
r"^meta-llama/.*", # Meta models via relay
|
||||
r"^mistralai/.*", # Mistral via relay
|
||||
r"^qwen/.*", # Qwen via relay
|
||||
r"^deepseek/.*", # DeepSeek via relay
|
||||
r"^nousresearch/.*", # NousResearch (Hermes) via relay
|
||||
]
|
||||
},
|
||||
"kimi": {
|
||||
"description": "Kimi/Moonshot (wizard-specific)",
|
||||
"model_patterns": [
|
||||
r"^kimi.*",
|
||||
r"^moonshot.*",
|
||||
]
|
||||
},
|
||||
"gemini": {
|
||||
"description": "Google Gemini direct (temporary falsework)",
|
||||
"model_patterns": [
|
||||
r"^gemini.*",
|
||||
]
|
||||
},
|
||||
}
|
||||
|
||||
# ═══ BANLIST ═══
|
||||
# These are permanently banned. No exceptions.
|
||||
BANNED_PROVIDERS = {
|
||||
"anthropic": "Permanently banned — hard policy since 2026-04-09",
|
||||
"claude": "Alias for Anthropic — permanently banned",
|
||||
"openai": "Not sovereign — use local models or OpenRouter relay",
|
||||
}
|
||||
|
||||
BANNED_MODEL_PATTERNS = [
|
||||
r"^claude.*", # Any Claude model
|
||||
r"^gpt-4.*", # GPT-4 variants
|
||||
r"^gpt-3.*", # GPT-3 variants
|
||||
r"^o1.*", # O1 variants
|
||||
r"^o3.*", # O3 variants
|
||||
]
|
||||
|
||||
|
||||
def validate_provider(provider: str, model: str = "") -> bool:
|
||||
"""
|
||||
Validate that a provider/model combination is allowed.
|
||||
|
||||
Args:
|
||||
provider: The provider name (e.g., "ollama", "anthropic")
|
||||
model: The model name (e.g., "hermes3:latest", "claude-3-opus")
|
||||
|
||||
Returns:
|
||||
True if allowed
|
||||
|
||||
Raises:
|
||||
ProviderBanned: If the provider or model is banned
|
||||
"""
|
||||
provider_lower = provider.lower().strip()
|
||||
model_lower = model.lower().strip()
|
||||
|
||||
# Check banlist first
|
||||
if provider_lower in BANNED_PROVIDERS:
|
||||
raise ProviderBanned(
|
||||
f"Provider '{provider}' is permanently banned: {BANNED_PROVIDERS[provider_lower]}"
|
||||
)
|
||||
|
||||
# Check model against banned patterns
|
||||
for pattern in BANNED_MODEL_PATTERNS:
|
||||
if re.match(pattern, model_lower):
|
||||
raise ProviderBanned(
|
||||
f"Model '{model}' matches banned pattern '{pattern}'"
|
||||
)
|
||||
|
||||
# Check allowlist
|
||||
if provider_lower not in ALLOWED_PROVIDERS:
|
||||
raise ProviderBanned(
|
||||
f"Provider '{provider}' is not on the allowlist. "
|
||||
f"Allowed: {', '.join(sorted(ALLOWED_PROVIDERS.keys()))}"
|
||||
)
|
||||
|
||||
# If model specified, validate against provider's allowed patterns
|
||||
if model_lower:
|
||||
allowed = ALLOWED_PROVIDERS[provider_lower]
|
||||
for pattern in allowed["model_patterns"]:
|
||||
if re.match(pattern, model_lower):
|
||||
return True
|
||||
|
||||
raise ProviderBanned(
|
||||
f"Model '{model}' is not allowed for provider '{provider}'. "
|
||||
f"Allowed patterns: {allowed['model_patterns']}"
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def scan_config(config: dict) -> list:
|
||||
"""
|
||||
Scan an entire config dict for banned provider references.
|
||||
Returns a list of violations.
|
||||
"""
|
||||
violations = []
|
||||
|
||||
def _scan(obj, path=""):
|
||||
if isinstance(obj, dict):
|
||||
for k, v in obj.items():
|
||||
_scan(v, f"{path}.{k}")
|
||||
elif isinstance(obj, list):
|
||||
for i, v in enumerate(obj):
|
||||
_scan(v, f"{path}[{i}]")
|
||||
elif isinstance(obj, str):
|
||||
val = obj.lower()
|
||||
for banned in BANNED_PROVIDERS:
|
||||
if banned in val:
|
||||
violations.append(f"{path}: contains banned provider '{banned}' in value '{obj}'")
|
||||
for pattern in BANNED_MODEL_PATTERNS:
|
||||
if re.match(pattern, val):
|
||||
violations.append(f"{path}: matches banned model pattern '{pattern}' with value '{obj}'")
|
||||
|
||||
_scan(config)
|
||||
return violations
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Self-test
|
||||
import sys
|
||||
|
||||
tests = [
|
||||
("ollama", "hermes3:latest", True),
|
||||
("ollama", "gemma4:latest", True),
|
||||
("anthropic", "claude-3-opus", False),
|
||||
("claude", "", False),
|
||||
("openai", "gpt-4", False),
|
||||
("openrouter", "google/gemini-2.5-pro", True),
|
||||
("openrouter", "anthropic/claude-3", False),
|
||||
("kimi", "kimi-k2.5", True),
|
||||
("unknown_provider", "", False),
|
||||
]
|
||||
|
||||
passed = 0
|
||||
for provider, model, should_pass in tests:
|
||||
try:
|
||||
validate_provider(provider, model)
|
||||
if should_pass:
|
||||
passed += 1
|
||||
print(f" ✓ {provider}/{model} — allowed (expected)")
|
||||
else:
|
||||
print(f" ✗ {provider}/{model} — allowed (SHOULD HAVE BEEN BLOCKED)")
|
||||
except ProviderBanned as e:
|
||||
if not should_pass:
|
||||
passed += 1
|
||||
print(f" ✓ {provider}/{model} — blocked: {e}")
|
||||
else:
|
||||
print(f" ✗ {provider}/{model} — blocked (SHOULD HAVE BEEN ALLOWED): {e}")
|
||||
|
||||
print(f"\n{passed}/{len(tests)} tests passed")
|
||||
sys.exit(0 if passed == len(tests) else 1)
|
||||
Reference in New Issue
Block a user