feat: bump hermes-agent 0.2.0 → 0.3.0 + auto-update CI

- Update package.nix to v0.3.0 (NousResearch/hermes-agent v2026.3.17)
- Add scripts/update-pins.sh — checks latest stable release, prefetches, validates build
- Add .github/workflows/update-pins.yml — runs every 6h + manual dispatch
- Pattern follows nix-openclaw's yolo-update approach
This commit is contained in:
Ciphercat 2026-03-17 19:09:36 +00:00
commit 010a1b6644
4 changed files with 526 additions and 3 deletions

30
.github/workflows/update-pins.yml vendored Normal file
View file

@ -0,0 +1,30 @@
name: Update Hermes Agent Pins
on:
schedule:
# Check every 6 hours — hermes-agent doesn't release as often as openclaw
- cron: "15 */6 * * *"
workflow_dispatch: {}
permissions:
contents: write
jobs:
update:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install Nix
uses: DeterminateSystems/nix-installer-action@v13
- name: Run updater
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
git config user.name "nix-hermes-agent-bot"
git config user.email "bot@nix-hermes-agent.local"
scripts/update-pins.sh

335
docs/CONFIG-MAP.md Normal file
View file

@ -0,0 +1,335 @@
# Hermes Agent Configuration Map
**Source:** `hermes_cli/config.py` + `gateway/config.py` (v0.2.0)
Everything below can be set declaratively via the NixOS module's `services.hermes-agent.config` option (rendered as `cli-config.yaml`), `environmentFiles` (secrets), or `environment` (non-secret env vars).
---
## 1. Model & Provider
| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `model` | string | `"anthropic/claude-opus-4.6"` | Default model (`provider/model` format) |
| `toolsets` | list | `["hermes-cli"]` | Enabled toolsets (e.g. `["all"]`, `["hermes-cli", "browser"]`) |
**Auth providers** (resolved automatically based on env vars / OAuth):
- Nous Portal (OAuth) → `hermes login --provider nous`
- OpenAI Codex (OAuth) → `hermes login --provider openai-codex`
- OpenRouter → `OPENROUTER_API_KEY`
- Z.AI / GLM → `GLM_API_KEY` / `ZAI_API_KEY`
- Kimi → `KIMI_API_KEY`
- MiniMax → `MINIMAX_API_KEY`
- Anthropic → `ANTHROPIC_API_KEY` or Claude Code OAuth
---
## 2. Agent Behavior
| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `agent.max_turns` | int | `90` | Max tool-calling iterations per conversation |
---
## 3. Terminal / Sandbox
| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `terminal.backend` | string | `"local"` | `local` / `docker` / `singularity` / `modal` / `daytona` / `ssh` |
| `terminal.cwd` | string | `"."` | Working directory for commands |
| `terminal.timeout` | int | `180` | Command timeout (seconds) |
| `terminal.docker_image` | string | `"nikolaik/python-nodejs:..."` | Docker image |
| `terminal.singularity_image` | string | ... | Singularity image |
| `terminal.modal_image` | string | ... | Modal image |
| `terminal.daytona_image` | string | ... | Daytona image |
| `terminal.container_cpu` | int | `1` | Container CPU limit |
| `terminal.container_memory` | int | `5120` | Container memory (MB) |
| `terminal.container_disk` | int | `51200` | Container disk (MB) |
| `terminal.container_persistent` | bool | `true` | Persist filesystem across sessions |
| `terminal.docker_volumes` | list | `[]` | Docker volume mounts (`host:container`) |
**SSH backend env vars:**
- `TERMINAL_SSH_HOST`, `TERMINAL_SSH_USER`, `TERMINAL_SSH_KEY`
---
## 4. Context Compression
| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `compression.enabled` | bool | `true` | Auto-compress long conversations |
| `compression.threshold` | float | `0.50` | Context usage % to trigger compression |
| `compression.summary_model` | string | `"google/gemini-3-flash-preview"` | Model for summaries |
| `compression.summary_provider` | string | `"auto"` | Provider for compression model |
---
## 5. Auxiliary Models
Side-task models (vision, web extraction, etc). Each has the same schema:
| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `auxiliary.<task>.provider` | string | `"auto"` | Provider override |
| `auxiliary.<task>.model` | string | `""` | Model override |
| `auxiliary.<task>.base_url` | string | `""` | Direct endpoint URL |
| `auxiliary.<task>.api_key` | string | `""` | API key for endpoint |
**Tasks:** `vision`, `web_extract`, `compression`, `session_search`, `skills_hub`, `mcp`, `flush_memories`
---
## 6. Delegation (Subagents)
| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `delegation.model` | string | `""` | Model for subagents (empty = inherit parent) |
| `delegation.provider` | string | `""` | Provider for subagents |
| `delegation.base_url` | string | `""` | Direct endpoint for subagents |
| `delegation.api_key` | string | `""` | API key for delegation endpoint |
---
## 7. Display
| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `display.compact` | bool | `false` | Compact output mode |
| `display.personality` | string | `"kawaii"` | UI personality |
| `display.resume_display` | string | `"full"` | Session resume display |
| `display.bell_on_complete` | bool | `false` | Bell sound on completion |
| `display.show_reasoning` | bool | `false` | Show model reasoning |
| `display.skin` | string | `"default"` | UI skin |
---
## 8. Memory
| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `memory.memory_enabled` | bool | `true` | Persistent curated memory |
| `memory.user_profile_enabled` | bool | `true` | User profile memory |
| `memory.memory_char_limit` | int | `2200` | Max chars for memory context |
| `memory.user_char_limit` | int | `1375` | Max chars for user profile |
---
## 9. Browser
| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `browser.inactivity_timeout` | int | `120` | Browser auto-close (seconds) |
| `browser.record_sessions` | bool | `false` | Record browser sessions as WebM |
---
## 10. Checkpoints (Filesystem Snapshots)
| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `checkpoints.enabled` | bool | `false` | Auto-snapshot before destructive ops |
| `checkpoints.max_snapshots` | int | `50` | Max checkpoints per directory |
---
## 11. TTS (Text-to-Speech)
| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `tts.provider` | string | `"edge"` | `edge` (free) / `elevenlabs` (premium) / `openai` |
| `tts.edge.voice` | string | `"en-US-AriaNeural"` | Edge TTS voice |
| `tts.elevenlabs.voice_id` | string | `"pNInz6obpgDQGcFmaJgB"` | ElevenLabs voice ID |
| `tts.elevenlabs.model_id` | string | `"eleven_multilingual_v2"` | ElevenLabs model |
| `tts.openai.model` | string | `"gpt-4o-mini-tts"` | OpenAI TTS model |
| `tts.openai.voice` | string | `"alloy"` | OpenAI TTS voice |
---
## 12. STT (Speech-to-Text)
| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `stt.enabled` | bool | `true` | Enable voice transcription |
| `stt.provider` | string | `"local"` | `local` (faster-whisper) / `groq` / `openai` |
| `stt.local.model` | string | `"base"` | Local model size: tiny/base/small/medium/large-v3 |
| `stt.openai.model` | string | `"whisper-1"` | OpenAI STT model |
---
## 13. Voice (Interactive)
| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `voice.record_key` | string | `"ctrl+b"` | Push-to-talk key |
| `voice.max_recording_seconds` | int | `120` | Max recording length |
| `voice.auto_tts` | bool | `false` | Auto-speak responses |
| `voice.silence_threshold` | int | `200` | RMS silence threshold |
| `voice.silence_duration` | float | `3.0` | Seconds of silence → auto-stop |
---
## 14. Human Delay (Anti-Detection)
| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `human_delay.mode` | string | `"off"` | `off` / `on` / `adaptive` |
| `human_delay.min_ms` | int | `800` | Min delay (ms) |
| `human_delay.max_ms` | int | `2500` | Max delay (ms) |
---
## 15. Security
| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `security.redact_secrets` | bool | `true` | Redact API keys from tool output |
| `security.tirith_enabled` | bool | `true` | Pre-exec scanning via tirith |
| `security.tirith_path` | string | `"tirith"` | Path to tirith binary |
| `security.tirith_timeout` | int | `5` | Tirith scan timeout (seconds) |
| `security.tirith_fail_open` | bool | `true` | Allow exec if tirith fails |
---
## 16. Discord
| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `discord.require_mention` | bool | `true` | Require @mention to respond in channels |
| `discord.free_response_channels` | string | `""` | Comma-separated channel IDs for free response |
| `discord.auto_thread` | bool | `true` | Auto-create threads on @mention |
---
## 17. Session Reset Policy
| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `session_reset.mode` | string | `"both"` | `daily` / `idle` / `both` / `none` |
| `session_reset.at_hour` | int | `4` | Daily reset hour (0-23, local time) |
| `session_reset.idle_minutes` | int | `1440` | Idle timeout before reset (minutes) |
---
## 18. Miscellaneous
| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `timezone` | string | `""` | IANA timezone (empty = server-local) |
| `command_allowlist` | list | `[]` | Permanently allowed dangerous commands |
| `quick_commands` | dict | `{}` | User-defined slash commands (exec type) |
| `personalities` | dict | `{}` | Custom personality prompts |
| `prefill_messages_file` | string | `""` | Path to JSON prefill messages |
| `honcho` | dict | `{}` | Honcho AI-native memory overrides |
---
## 19. MCP Servers
Defined via `services.hermes-agent.mcpServers` in the NixOS module:
```nix
mcpServers = {
my-server = {
command = "npx";
args = [ "-y" "my-mcp-server" ];
env = { API_KEY = "..."; };
timeout = 30;
};
};
```
Merged into `config.mcp_servers` in the rendered YAML.
---
## 20. Fallback Model (Failover)
Not in defaults — must be explicitly configured:
```yaml
fallback_model:
provider: openrouter # openrouter | nous | openai-codex | zai | kimi-coding | minimax
model: anthropic/claude-sonnet-4
# Optional for custom endpoints:
# base_url: "http://..."
# api_key_env: "MY_CUSTOM_KEY"
```
Triggers on: 429 (rate limit), 529 (overload), 503 (service error), connection failures.
---
## 21. Environment Variables (Secrets via `environmentFiles`)
### Provider Keys
| Variable | Provider |
|----------|----------|
| `OPENROUTER_API_KEY` | OpenRouter (default fallback) |
| `GLM_API_KEY` / `ZAI_API_KEY` | Z.AI / GLM |
| `KIMI_API_KEY` | Kimi / Moonshot |
| `MINIMAX_API_KEY` | MiniMax (international) |
| `MINIMAX_CN_API_KEY` | MiniMax (China) |
| `ANTHROPIC_API_KEY` | Anthropic direct |
| `ANTHROPIC_TOKEN` | Anthropic OAuth/setup token |
### Tool Keys
| Variable | Tool |
|----------|------|
| `FIRECRAWL_API_KEY` | Web search/scraping |
| `BROWSERBASE_API_KEY` | Cloud browser |
| `FAL_KEY` | Image generation |
| `ELEVENLABS_API_KEY` | Premium TTS |
| `VOICE_TOOLS_OPENAI_KEY` | Whisper STT + OpenAI TTS |
| `GITHUB_TOKEN` | Skills Hub |
| `HONCHO_API_KEY` | AI-native memory |
| `TINKER_API_KEY` | RL training |
| `WANDB_API_KEY` | Experiment tracking |
### Messaging Tokens
| Variable | Platform |
|----------|----------|
| `TELEGRAM_BOT_TOKEN` | Telegram |
| `TELEGRAM_ALLOWED_USERS` | Telegram user whitelist |
| `DISCORD_BOT_TOKEN` | Discord |
| `DISCORD_ALLOWED_USERS` | Discord user whitelist |
| `SLACK_BOT_TOKEN` | Slack |
| `SLACK_APP_TOKEN` | Slack (Socket Mode) |
| `SIGNAL_HTTP_URL` | Signal |
| `SIGNAL_ACCOUNT` | Signal |
| `HASS_TOKEN` | Home Assistant |
| `EMAIL_ADDRESS` / `EMAIL_PASSWORD` / `EMAIL_IMAP_HOST` / `EMAIL_SMTP_HOST` | Email |
| `GATEWAY_ALLOW_ALL_USERS` | Allow all users (global) |
### Agent Settings
| Variable | Description |
|----------|-------------|
| `MESSAGING_CWD` | Working directory for messaging |
| `SUDO_PASSWORD` | Sudo password for terminal |
| `HERMES_MAX_ITERATIONS` | Max iterations override |
---
## 22. Custom Providers (config.yaml)
```yaml
custom_providers:
- name: "local-vllm"
base_url: "http://localhost:8000/v1"
api_key: "" # optional
```
Use with: `hermes chat --provider custom:local-vllm`
---
## Summary: What Goes Where
| What | Where | NixOS Module Option |
|------|-------|---------------------|
| Model, toolsets, display, terminal, etc. | `cli-config.yaml` | `config = { ... }` |
| API keys, tokens, passwords | `.env` / env vars | `environmentFiles = [ "/run/secrets/hermes.env" ]` |
| OAuth tokens (Nous, Codex) | `auth.json` | Runtime via `hermes login` (persistent) |
| MCP servers | `cli-config.yaml` | `mcpServers = { ... }` |
| SOUL.md, AGENTS.md, USER.md | Workspace files | `documents = { ... }` |
| Gateway platform config | env vars or `gateway.json` | `environmentFiles` |

View file

@ -72,14 +72,14 @@ let
pythonImportsCheck = [ "acp" ];
};
version = "0.2.0";
rev = "64d333204bb2e32cc90a58b5ec5a4db127396dfc";
version = "0.3.0";
rev = "6ebb816e5611aaf1f3f7187ba8b10e985e899c75";
src = fetchFromGitHub {
owner = "NousResearch";
repo = "hermes-agent";
inherit rev;
hash = "sha256-Li8jPEFDthj/AKmlwJhLWxItc34qcTrmJUDQ4kaSxVg=";
hash = "sha256-JGjusff/jGjvCCdUtl9IErBTGmpIq6BVA5Gj8mwqVYg=";
fetchSubmodules = true;
};

158
scripts/update-pins.sh Executable file
View file

@ -0,0 +1,158 @@
#!/usr/bin/env bash
set -euo pipefail
# Auto-update nix-hermes-agent to track latest stable release from NousResearch/hermes-agent.
# Designed to run in GitHub Actions (see .github/workflows/update-pins.yml).
# Similar to nix-openclaw's update-pins.sh but tracks releases instead of HEAD.
repo_root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
package_file="$repo_root/package.nix"
log() {
printf '>> %s\n' "$*"
}
if ! command -v jq >/dev/null 2>&1; then
echo "jq is required but not installed." >&2
exit 1
fi
# --- Resolve latest stable release ---
log "Fetching latest release from NousResearch/hermes-agent"
release_json=$(gh api /repos/NousResearch/hermes-agent/releases/latest 2>/dev/null || true)
if [[ -z "$release_json" ]]; then
echo "Failed to fetch latest release" >&2
exit 1
fi
release_tag=$(printf '%s' "$release_json" | jq -r '.tag_name // empty')
release_name=$(printf '%s' "$release_json" | jq -r '.name // empty')
if [[ -z "$release_tag" ]]; then
echo "No release tag found" >&2
exit 1
fi
log "Latest release: $release_tag ($release_name)"
# Extract version from release name or tag (e.g. "Hermes Agent v0.3.0 (v2026.3.17)" → "0.3.0")
upstream_version=$(printf '%s' "$release_name" | grep -oP 'v\K[0-9]+\.[0-9]+\.[0-9]+' | head -1 || true)
if [[ -z "$upstream_version" ]]; then
# Fallback: try tag itself
upstream_version=$(printf '%s' "$release_tag" | grep -oP 'v?\K[0-9]+\.[0-9]+\.[0-9]+' | head -1 || true)
fi
if [[ -z "$upstream_version" ]]; then
echo "Could not parse version from release tag=$release_tag name=$release_name" >&2
exit 1
fi
log "Parsed version: $upstream_version"
# --- Compare with current ---
current_version=$(awk -F'"' '/^ version = /{print $2}' "$package_file" | head -1)
log "Current pinned version: $current_version"
if [[ "$current_version" == "$upstream_version" ]]; then
log "Already up to date ($current_version). Nothing to do."
exit 0
fi
log "Update available: $current_version$upstream_version"
# --- Resolve the commit SHA for the release tag ---
tag_sha=$(gh api "/repos/NousResearch/hermes-agent/git/ref/tags/${release_tag}" --jq '.object.sha' 2>/dev/null || true)
if [[ -z "$tag_sha" ]]; then
# Maybe it's an annotated tag — dereference
tag_sha=$(gh api "/repos/NousResearch/hermes-agent/git/ref/tags/${release_tag}" --jq '.object.sha' 2>/dev/null || true)
if [[ -n "$tag_sha" ]]; then
# Check if it's an annotated tag (type=tag) and dereference
tag_type=$(gh api "/repos/NousResearch/hermes-agent/git/tags/${tag_sha}" --jq '.object.type // empty' 2>/dev/null || true)
if [[ "$tag_type" == "commit" ]]; then
tag_sha=$(gh api "/repos/NousResearch/hermes-agent/git/tags/${tag_sha}" --jq '.object.sha' 2>/dev/null || true)
fi
fi
fi
if [[ -z "$tag_sha" ]]; then
# Last resort: ls-remote
tag_sha=$(git ls-remote https://github.com/NousResearch/hermes-agent.git "refs/tags/${release_tag}" | awk '{print $1}' || true)
fi
if [[ -z "$tag_sha" ]]; then
echo "Failed to resolve commit SHA for tag $release_tag" >&2
exit 1
fi
log "Release commit SHA: $tag_sha"
# --- Prefetch source ---
source_url="https://github.com/NousResearch/hermes-agent/archive/${tag_sha}.tar.gz"
log "Prefetching source tarball (with submodules via fetchFromGitHub)..."
# Use nix-prefetch-url for the base archive, but we need fetchFromGitHub hash (includes submodules).
# Best approach: temporarily update package.nix with empty hash and let nix build tell us the right one.
# Or use nix store prefetch-file for the tarball (no submodules).
# Since the package uses fetchSubmodules = true, we need the fetchFromGitHub hash.
# Strategy: use nix to evaluate the hash by building with a fake hash
log "Computing fetchFromGitHub hash (with submodules)..."
# Save original
cp "$package_file" "$package_file.bak"
# Update version, rev, and set hash to empty
perl -0pi -e "s|version = \"[^\"]+\";|version = \"${upstream_version}\";|" "$package_file"
perl -0pi -e "s|rev = \"[^\"]+\";|rev = \"${tag_sha}\";|" "$package_file"
perl -0pi -e 's|hash = "sha256-[^"]+";|hash = "";|' "$package_file"
# Build and capture the correct hash from the error
build_log=$(mktemp)
log "Running nix build to get correct hash..."
if nix build .#hermes-agent --accept-flake-config >"$build_log" 2>&1; then
log "Build succeeded with empty hash?! Unexpected, but OK."
source_hash=""
else
source_hash=$(grep -oP 'got: *\Ksha256-[A-Za-z0-9+/=]+' "$build_log" | head -1 || true)
if [[ -z "$source_hash" ]]; then
log "Build failed but couldn't extract hash. Build log:"
tail -50 "$build_log" >&2
cp "$package_file.bak" "$package_file"
rm -f "$build_log" "$package_file.bak"
exit 1
fi
fi
rm -f "$build_log"
log "Source hash: $source_hash"
# Update with the correct hash
if [[ -n "$source_hash" ]]; then
perl -0pi -e "s|hash = \"[^\"]*\";|hash = \"${source_hash}\";|" "$package_file"
fi
# --- Validate build ---
build_log=$(mktemp)
log "Validating full build..."
if ! nix build .#hermes-agent --accept-flake-config >"$build_log" 2>&1; then
log "Build validation FAILED. This likely means dependencies changed upstream."
log "Build log (last 100 lines):"
tail -100 "$build_log" >&2
cp "$package_file.bak" "$package_file"
rm -f "$build_log" "$package_file.bak"
exit 1
fi
rm -f "$build_log" "$package_file.bak"
log "Build validation PASSED ✅"
# --- Commit and push ---
if git diff --quiet "$package_file"; then
log "No changes to commit (shouldn't happen)"
exit 0
fi
log "Committing update"
git add "$package_file"
git commit -m "🤖 bump hermes-agent ${current_version}${upstream_version} (${release_tag})" \
-m "Upstream: https://github.com/NousResearch/hermes-agent/releases/tag/${release_tag}" \
-m "Tests: nix build .#hermes-agent (passed)"
log "Pushing to main"
git fetch origin main
git rebase origin/main
git push origin HEAD:main
log "Done! Updated hermes-agent to ${upstream_version} (${release_tag})"