winter-super-cli 2026.6.26 → 2026.6.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +28 -5
- package/README.md +66 -0
- package/package.json +5 -1
- package/resources/local/gsap-skills/.claude-plugin/marketplace.json +20 -0
- package/resources/local/gsap-skills/.claude-plugin/plugin.json +6 -0
- package/resources/local/gsap-skills/.cursor-plugin/marketplace.json +13 -0
- package/resources/local/gsap-skills/.cursor-plugin/plugin.json +22 -0
- package/resources/local/gsap-skills/.github/copilot-instructions.md +17 -0
- package/resources/local/gsap-skills/.github/instructions/react.instructions.md +15 -0
- package/resources/local/gsap-skills/.github/instructions/scrolltrigger.instructions.md +18 -0
- package/resources/local/gsap-skills/AGENTS.md +27 -0
- package/resources/local/gsap-skills/CLAUDE.md +1 -0
- package/resources/local/gsap-skills/GEMINI.md +1 -0
- package/resources/local/gsap-skills/LICENSE +21 -0
- package/resources/local/gsap-skills/README.md +163 -0
- package/resources/local/gsap-skills/assets/gsap-green.svg +7 -0
- package/resources/local/gsap-skills/assets/gsap-icon-inverted.svg +15 -0
- package/resources/local/gsap-skills/assets/gsap-icon-square.svg +1 -0
- package/resources/local/gsap-skills/assets/gsap-white.svg +7 -0
- package/resources/local/gsap-skills/examples/README.md +29 -0
- package/resources/local/gsap-skills/examples/nuxt/app/app.vue +3 -0
- package/resources/local/gsap-skills/examples/nuxt/app/composables/useGSAP.ts +91 -0
- package/resources/local/gsap-skills/examples/nuxt/app/pages/index.vue +55 -0
- package/resources/local/gsap-skills/examples/nuxt/nuxt.config.ts +4 -0
- package/resources/local/gsap-skills/examples/nuxt/package.json +18 -0
- package/resources/local/gsap-skills/examples/react/App.jsx +46 -0
- package/resources/local/gsap-skills/examples/react/index.html +12 -0
- package/resources/local/gsap-skills/examples/react/main.jsx +9 -0
- package/resources/local/gsap-skills/examples/react/package.json +21 -0
- package/resources/local/gsap-skills/examples/react/vite.config.js +7 -0
- package/resources/local/gsap-skills/examples/vanilla/index.html +33 -0
- package/resources/local/gsap-skills/examples/vanilla/main.js +36 -0
- package/resources/local/gsap-skills/examples/vue/app.vue +47 -0
- package/resources/local/gsap-skills/examples/vue/index.html +15 -0
- package/resources/local/gsap-skills/examples/vue/main.js +9 -0
- package/resources/local/gsap-skills/examples/vue/package.json +19 -0
- package/resources/local/gsap-skills/examples/vue/vite.config.js +7 -0
- package/resources/local/gsap-skills/skills/gsap-core/SKILL.md +254 -0
- package/resources/local/gsap-skills/skills/gsap-frameworks/SKILL.md +266 -0
- package/resources/local/gsap-skills/skills/gsap-performance/SKILL.md +79 -0
- package/resources/local/gsap-skills/skills/gsap-plugins/SKILL.md +433 -0
- package/resources/local/gsap-skills/skills/gsap-react/SKILL.md +136 -0
- package/resources/local/gsap-skills/skills/gsap-scrolltrigger/SKILL.md +296 -0
- package/resources/local/gsap-skills/skills/gsap-timeline/SKILL.md +107 -0
- package/resources/local/gsap-skills/skills/gsap-utils/SKILL.md +284 -0
- package/resources/local/gsap-skills/skills/llms.txt +39 -0
- package/resources/local/hermes-agent-core/AGENTS.md +1132 -0
- package/resources/local/hermes-agent-core/LICENSE +21 -0
- package/resources/local/hermes-agent-core/README.md +215 -0
- package/resources/local/hermes-agent-core/docs/2026-05-07-s6-overlay-dynamic-subagent-gateways.md +434 -0
- package/resources/local/hermes-agent-core/hermes-already-has-routines.md +160 -0
- package/resources/local/hermes-agent-core/skills/autonomous-ai-agents/DESCRIPTION.md +3 -0
- package/resources/local/hermes-agent-core/skills/autonomous-ai-agents/claude-code/SKILL.md +745 -0
- package/resources/local/hermes-agent-core/skills/autonomous-ai-agents/codex/SKILL.md +130 -0
- package/resources/local/hermes-agent-core/skills/autonomous-ai-agents/hermes-agent/SKILL.md +1021 -0
- package/resources/local/hermes-agent-core/skills/autonomous-ai-agents/kanban-codex-lane/SKILL.md +277 -0
- package/resources/local/hermes-agent-core/skills/autonomous-ai-agents/kanban-codex-lane/templates/pmb-codex-lane-prompt.md +57 -0
- package/resources/local/hermes-agent-core/skills/autonomous-ai-agents/opencode/SKILL.md +219 -0
- package/resources/local/hermes-agent-core/skills/github/DESCRIPTION.md +3 -0
- package/resources/local/hermes-agent-core/skills/github/codebase-inspection/SKILL.md +116 -0
- package/resources/local/hermes-agent-core/skills/github/github-auth/SKILL.md +247 -0
- package/resources/local/hermes-agent-core/skills/github/github-auth/scripts/gh-env.sh +66 -0
- package/resources/local/hermes-agent-core/skills/github/github-code-review/SKILL.md +481 -0
- package/resources/local/hermes-agent-core/skills/github/github-code-review/references/review-output-template.md +74 -0
- package/resources/local/hermes-agent-core/skills/github/github-issues/SKILL.md +370 -0
- package/resources/local/hermes-agent-core/skills/github/github-issues/templates/bug-report.md +35 -0
- package/resources/local/hermes-agent-core/skills/github/github-issues/templates/feature-request.md +31 -0
- package/resources/local/hermes-agent-core/skills/github/github-pr-workflow/SKILL.md +367 -0
- package/resources/local/hermes-agent-core/skills/github/github-pr-workflow/references/ci-troubleshooting.md +183 -0
- package/resources/local/hermes-agent-core/skills/github/github-pr-workflow/references/conventional-commits.md +71 -0
- package/resources/local/hermes-agent-core/skills/github/github-pr-workflow/templates/pr-body-bugfix.md +35 -0
- package/resources/local/hermes-agent-core/skills/github/github-pr-workflow/templates/pr-body-feature.md +33 -0
- package/resources/local/hermes-agent-core/skills/github/github-repo-management/SKILL.md +516 -0
- package/resources/local/hermes-agent-core/skills/github/github-repo-management/references/github-api-cheatsheet.md +161 -0
- package/resources/local/hermes-agent-core/skills/mcp/DESCRIPTION.md +3 -0
- package/resources/local/hermes-agent-core/skills/mcp/native-mcp/SKILL.md +357 -0
- package/resources/local/hermes-agent-core/skills/software-development/debugging-hermes-tui-commands/SKILL.md +152 -0
- package/resources/local/hermes-agent-core/skills/software-development/hermes-agent-skill-authoring/SKILL.md +165 -0
- package/resources/local/hermes-agent-core/skills/software-development/hermes-s6-container-supervision/SKILL.md +176 -0
- package/resources/local/hermes-agent-core/skills/software-development/node-inspect-debugger/SKILL.md +319 -0
- package/resources/local/hermes-agent-core/skills/software-development/plan/SKILL.md +58 -0
- package/resources/local/hermes-agent-core/skills/software-development/python-debugpy/SKILL.md +375 -0
- package/resources/local/hermes-agent-core/skills/software-development/requesting-code-review/SKILL.md +280 -0
- package/resources/local/hermes-agent-core/skills/software-development/spike/SKILL.md +197 -0
- package/resources/local/hermes-agent-core/skills/software-development/subagent-driven-development/SKILL.md +352 -0
- package/resources/local/hermes-agent-core/skills/software-development/subagent-driven-development/references/context-budget-discipline.md +53 -0
- package/resources/local/hermes-agent-core/skills/software-development/subagent-driven-development/references/gates-taxonomy.md +93 -0
- package/resources/local/hermes-agent-core/skills/software-development/systematic-debugging/SKILL.md +367 -0
- package/resources/local/hermes-agent-core/skills/software-development/test-driven-development/SKILL.md +343 -0
- package/resources/local/hermes-agent-core/skills/software-development/writing-plans/SKILL.md +297 -0
- package/resources/local/manifest.json +12 -0
- package/rule.md +2 -0
- package/scripts/audit-pack.js +5 -0
- package/scripts/smoke-browser.js +53 -0
- package/scripts/smoke-package.js +38 -4
- package/skill.md +36 -4
- package/skills/gsap.md +26 -0
- package/skills/hermes-agent.md +17 -0
- package/src/agent/agent-definitions.js +4 -4
- package/src/agent/runtime.js +179 -5
- package/src/agent/subagent-child.js +44 -0
- package/src/ai/capability-scorecard.js +193 -14
- package/src/ai/hermes-core.js +77 -0
- package/src/ai/model-capabilities.js +42 -2
- package/src/ai/prompts/system-prompt.js +16 -2
- package/src/ai/small-model-amplifier.js +35 -7
- package/src/ai/workflow-selector.js +22 -1
- package/src/cli/commands.js +21 -1
- package/src/cli/config.js +42 -4
- package/src/cli/context-loader.js +253 -9
- package/src/cli/conversation-format.js +5 -0
- package/src/cli/input-controller.js +79 -10
- package/src/cli/prompt-builder.js +45 -8
- package/src/cli/repl-commands.js +115 -0
- package/src/cli/repl.js +147 -86
- package/src/cli/slash-commands.js +3 -1
- package/src/cli/tui.js +133 -37
- package/src/mcp/client.js +46 -5
- package/src/tools/agent.js +316 -25
- package/src/tools/executor.js +310 -9
- package/src/tools/permission.js +20 -17
- package/winter.d.ts +112 -10
package/resources/local/hermes-agent-core/docs/2026-05-07-s6-overlay-dynamic-subagent-gateways.md
ADDED
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
# s6-overlay Supervision for Per-Profile Gateways in Docker — Implementation Plan
|
|
2
|
+
|
|
3
|
+
> **Status: shipped.** Phases 0–5 landed via PR
|
|
4
|
+
> [NousResearch/hermes-agent#30136](https://github.com/NousResearch/hermes-agent/pull/30136)
|
|
5
|
+
> in May 2026. This document is preserved as a post-implementation reference
|
|
6
|
+
> for the architecture and the resolved design questions. The phase-by-phase
|
|
7
|
+
> TDD walkthrough (≈2,800 lines) and the v2/v3 re-validation preambles have
|
|
8
|
+
> been removed — the canonical implementation history is the PR commit log
|
|
9
|
+
> (`git log --oneline a957ef083..a6f7171a5 -- 'docker/*' 'hermes_cli/service_manager.py' …`).
|
|
10
|
+
> Open Questions are collapsed into a single Decision Log table; full
|
|
11
|
+
> deliberations live in PR review comments.
|
|
12
|
+
|
|
13
|
+
**Goal:** Replace `tini` with s6-overlay as PID 1 in the Hermes Docker image so
|
|
14
|
+
that the main hermes process, the dashboard, and dynamically-created
|
|
15
|
+
per-profile gateways all run as supervised services (auto-restart on crash,
|
|
16
|
+
clean shutdown, signal forwarding, zombie reaping). Preserve every existing
|
|
17
|
+
`docker run …` invocation pattern — including interactive TUI.
|
|
18
|
+
|
|
19
|
+
**Architecture:** s6-overlay's `/init` is the container ENTRYPOINT, running
|
|
20
|
+
s6-svscan as PID 1. Main hermes and the dashboard are declared as static
|
|
21
|
+
s6-rc services at image build time. Per-profile gateways — which users create
|
|
22
|
+
*after* the image is built (`hermes profile create coder` →
|
|
23
|
+
`coder gateway start`) — are registered dynamically by writing service
|
|
24
|
+
directories under a scandir watched by s6-svscan. A `ServiceManager` protocol
|
|
25
|
+
abstracts the install/start/stop/restart surface across the init systems we
|
|
26
|
+
care about (systemd on Linux host, launchd on macOS host, Scheduled Tasks on
|
|
27
|
+
native Windows host, s6 inside container) and adds a second tier for runtime
|
|
28
|
+
service registration that only s6 implements.
|
|
29
|
+
|
|
30
|
+
**Tech Stack:**
|
|
31
|
+
|
|
32
|
+
- [s6-overlay](https://github.com/just-containers/s6-overlay) v3.2.3.0
|
|
33
|
+
(noarch + per-arch tarballs ~15 MB). SHA256-pinned via build ARGs;
|
|
34
|
+
multi-arch via `TARGETARCH` (amd64 → `x86_64`, arm64 → `aarch64`).
|
|
35
|
+
- Debian 13.4 base image (unchanged).
|
|
36
|
+
- [hadolint](https://github.com/hadolint/hadolint) for the Dockerfile +
|
|
37
|
+
[shellcheck](https://github.com/koalaman/shellcheck) for entrypoint scripts.
|
|
38
|
+
- Python subprocess wrappers for `s6-svc`, `s6-svstat`, `s6-svscanctl`.
|
|
39
|
+
- Existing systemd/launchd/windows surface in `hermes_cli/gateway.py` and
|
|
40
|
+
`hermes_cli/gateway_windows.py`.
|
|
41
|
+
|
|
42
|
+
**Scope:**
|
|
43
|
+
|
|
44
|
+
- Container-only (host-side systemd/launchd/windows behavior is preserved,
|
|
45
|
+
not modified).
|
|
46
|
+
- s6-overlay only (no pure-Python fallback).
|
|
47
|
+
- Architecture A (s6 owns PID 1; tini is removed).
|
|
48
|
+
- Interactive TUI must keep working:
|
|
49
|
+
`docker run -it --rm nousresearch/hermes-agent:latest --tui`.
|
|
50
|
+
- Dynamic registration is limited to per-profile gateways — one service per
|
|
51
|
+
profile, created when a profile is created, torn down when deleted. A
|
|
52
|
+
`gateway-default` slot is always registered for the root HERMES_HOME
|
|
53
|
+
profile so `hermes gateway start` (no `-p`) has somewhere to land.
|
|
54
|
+
|
|
55
|
+
**Out of scope:**
|
|
56
|
+
|
|
57
|
+
- Host-side dynamic supervision (systemd-run / launchd transient plists) —
|
|
58
|
+
not needed.
|
|
59
|
+
- Pure-Python supervisor fallback — not needed.
|
|
60
|
+
- Arbitrary user-defined supervised processes inside the container — only
|
|
61
|
+
profile gateways.
|
|
62
|
+
- Migration of existing per-profile systemd unit generation to s6 on the
|
|
63
|
+
host side.
|
|
64
|
+
- Non-Docker container runtimes (Podman rootless validated reactively).
|
|
65
|
+
- UX polish around in-container profile lifecycle (e.g. a nice status view
|
|
66
|
+
of all supervised profile gateways) — deferred to follow-up.
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Background From The Codebase
|
|
71
|
+
|
|
72
|
+
> **Note on line numbers:** This section refers to functions and structures
|
|
73
|
+
> by name only. Use `grep -n 'def <name>' <file>` to locate anything below
|
|
74
|
+
> if you need the current line.
|
|
75
|
+
|
|
76
|
+
### Pre-s6 container init (what we replaced)
|
|
77
|
+
|
|
78
|
+
The original `Dockerfile` declared
|
|
79
|
+
`ENTRYPOINT [ "/usr/bin/tini", "-g", "--", "/opt/hermes/docker/entrypoint.sh" ]`.
|
|
80
|
+
tini was PID 1, reaped zombies, forwarded SIGTERM to the process group. The
|
|
81
|
+
old `docker/entrypoint.sh`:
|
|
82
|
+
|
|
83
|
+
1. `gosu` privilege drop from root → `hermes` UID.
|
|
84
|
+
2. Copied `.env.example`, `cli-config.yaml.example`, `SOUL.md` into
|
|
85
|
+
`$HERMES_HOME` if missing.
|
|
86
|
+
3. Synced bundled skills via `tools/skills_sync.py`.
|
|
87
|
+
4. Optionally backgrounded `hermes dashboard` in a subshell when
|
|
88
|
+
`HERMES_DASHBOARD=1` — **not supervised**, no restart.
|
|
89
|
+
5. `exec hermes "$@"` — tini's sole direct child.
|
|
90
|
+
|
|
91
|
+
Known limitations: dashboard crash → stays dead; dashboard fails at startup →
|
|
92
|
+
silent; gateway crash → dashboard dies too. The May 4, 2026 decision was
|
|
93
|
+
"leave as is" because nothing in the container needed supervision then.
|
|
94
|
+
Adding per-profile gateway supervision changed that.
|
|
95
|
+
|
|
96
|
+
### ServiceManager surface (what we wrapped, not refactored)
|
|
97
|
+
|
|
98
|
+
All init-system logic lives in **`hermes_cli/gateway.py`** (~5,400 LOC at
|
|
99
|
+
re-validation). The systemd/launchd code is ~1,500 lines of that, plus a
|
|
100
|
+
separate **`hermes_cli/gateway_windows.py`** (~690 LOC) for Windows
|
|
101
|
+
Scheduled Tasks.
|
|
102
|
+
|
|
103
|
+
| Layer | Systemd functions | Launchd functions | Windows functions |
|
|
104
|
+
|---|---|---|---|
|
|
105
|
+
| **Detection** | `supports_systemd_services()`, `_systemd_operational()`, `_wsl_systemd_operational()`, `_container_systemd_operational()` | `is_macos()` | `is_windows()`, `gateway_windows.is_installed()` |
|
|
106
|
+
| **Paths** | `get_systemd_unit_path(system)`, `get_service_name()` | `get_launchd_plist_path()`, `get_launchd_label()` | `gateway_windows.get_task_name()`, `get_task_script_path()`, `get_startup_entry_path()` |
|
|
107
|
+
| **Install/lifecycle** | `systemd_install(force, system, run_as_user)`, `systemd_uninstall(system)`, `systemd_start/stop/restart(system)` | `launchd_install(force)`, `launchd_uninstall/start/stop/restart` | `gateway_windows.install/uninstall/start/stop/restart` |
|
|
108
|
+
| **Probes** | `_probe_systemd_service_running(system)`, `_read_systemd_unit_properties(system)`, `_wait_for_systemd_service_restart`, `_recover_pending_systemd_restart` | `_probe_launchd_service_running()` | `gateway_windows.is_task_registered()`, `_pid_exists` helper |
|
|
109
|
+
| **D-Bus plumbing** | `_ensure_user_systemd_env`, `_user_systemd_socket_ready`, `_user_systemd_private_socket_path`, `get_systemd_linger_status` | — | — |
|
|
110
|
+
| **Unit/plist generation** | `generate_systemd_unit(system, run_as_user)`, `systemd_unit_is_current`, `refresh_systemd_unit_if_needed` | plist templating in `launchd_install` | `_build_gateway_cmd_script`, `_build_startup_launcher`, `_write_task_script` |
|
|
111
|
+
|
|
112
|
+
Container-relevant callers outside `gateway.py`:
|
|
113
|
+
|
|
114
|
+
- `hermes_cli/status.py` — gained an `s6` branch for in-container runs.
|
|
115
|
+
- `hermes_cli/profiles.py` — `create_profile` / `delete_profile` register and
|
|
116
|
+
unregister with s6 inside the container (no-op on host).
|
|
117
|
+
- `hermes_cli/doctor.py` — `_check_gateway_service_linger` skips on s6, and a
|
|
118
|
+
new "Service Supervisor" section reports main-hermes / dashboard /
|
|
119
|
+
profile-gateway counts via the ServiceManager.
|
|
120
|
+
- `hermes_cli/gateway.py::gateway_command` — the
|
|
121
|
+
`elif is_container():` rejection arms that refused gateway lifecycle
|
|
122
|
+
operations were removed; the `_dispatch_via_service_manager_if_s6` helper
|
|
123
|
+
intercepts start/stop/restart and routes them through s6.
|
|
124
|
+
|
|
125
|
+
### Per-profile gateway spawning
|
|
126
|
+
|
|
127
|
+
`hermes gateway start`, `coder gateway start` (profile alias), and
|
|
128
|
+
`hermes -p <profile> gateway start` all spawn a gateway process scoped to a
|
|
129
|
+
given profile. See
|
|
130
|
+
[Profiles: Running Gateways](https://hermes-agent.nousresearch.com/docs/user-guide/profiles#running-gateways).
|
|
131
|
+
On host, lifecycle is managed via per-profile systemd units
|
|
132
|
+
(`hermes-gateway-<profile>.service`); inside the container, an s6 service at
|
|
133
|
+
`/run/service/gateway-<name>/` is registered when the profile is created and
|
|
134
|
+
torn down when it's deleted.
|
|
135
|
+
|
|
136
|
+
**Persistence across container restart:** `/run/service/` is tmpfs —
|
|
137
|
+
service registrations are wiped when the container restarts. Profile
|
|
138
|
+
directories at `/opt/data/profiles/<name>/` live on the persistent VOLUME,
|
|
139
|
+
and each one records its gateway's last state in `gateway_state.json`.
|
|
140
|
+
`/etc/cont-init.d/02-reconcile-profiles` walks the persistent profiles on
|
|
141
|
+
every container boot, recreates the s6 service slots via
|
|
142
|
+
`hermes_cli/container_boot.py`, and auto-starts those whose last recorded
|
|
143
|
+
state was `running`. Profiles whose last state was `stopped`,
|
|
144
|
+
`startup_failed`, `starting`, or absent get their slot recreated in the
|
|
145
|
+
`down` state and wait for explicit user action. `docker restart` is therefore
|
|
146
|
+
invisible to a user with running profile gateways: they come back up;
|
|
147
|
+
stopped ones stay stopped.
|
|
148
|
+
|
|
149
|
+
### s6-overlay constraints
|
|
150
|
+
|
|
151
|
+
- **Root/non-root model:** `/init` runs as root to set up the supervision
|
|
152
|
+
tree, install signal handlers, and run the stage2 hook that does
|
|
153
|
+
`usermod`/`chown`. Each supervised service drops to UID 10000 via
|
|
154
|
+
`s6-setuidgid hermes` in its `run` script. The per-service `s6-supervise`
|
|
155
|
+
monitor stays root so it can signal its child regardless of UID. Net
|
|
156
|
+
effect: hermes and all its subprocesses run as UID 10000 exactly as
|
|
157
|
+
before; only the supervision tree itself runs as root.
|
|
158
|
+
- v3.2.3.0 has limited non-root support for running `/init` itself as
|
|
159
|
+
non-root — some tools (`fix-attrs`, `logutil-service`) assume root. We
|
|
160
|
+
don't hit this because `/init` runs as root.
|
|
161
|
+
- Scandir hard cap: `services_max` default 1000, configurable to 160,000.
|
|
162
|
+
- `/command/with-contenv` sources `/run/s6/container_environment/*` into
|
|
163
|
+
service env — convenient for passing `HERMES_HOME` etc.
|
|
164
|
+
- s6 signal semantics: service crash triggers `s6-supervise` restart after
|
|
165
|
+
1s; override with a `finish` script.
|
|
166
|
+
- Zombie reaping: PID 1 (s6-svscan) reaps all zombies non-blockingly on
|
|
167
|
+
SIGCHLD. Any subagent subprocess spawned by the main hermes process is
|
|
168
|
+
reaped automatically.
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## Key Design Decisions
|
|
173
|
+
|
|
174
|
+
### D1. s6-overlay replaces tini entirely
|
|
175
|
+
|
|
176
|
+
Container ENTRYPOINT is `/init`, PID 1 is s6-svscan. The main hermes
|
|
177
|
+
process, the dashboard, and every per-profile gateway run as supervised
|
|
178
|
+
services. This is a single breaking change to the container contract.
|
|
179
|
+
|
|
180
|
+
### D2. Main hermes is an s6 service with container-exit semantics
|
|
181
|
+
|
|
182
|
+
The contract "container exits when `hermes` exits" is preserved via a
|
|
183
|
+
service `finish` script that writes to
|
|
184
|
+
`/run/s6-linux-init-container-results/exitcode` and calls
|
|
185
|
+
`/run/s6/basedir/bin/halt`. All five supported invocations work:
|
|
186
|
+
|
|
187
|
+
| `docker run <image> …` | Behavior |
|
|
188
|
+
|---|---|
|
|
189
|
+
| (no args) | `hermes` with no args, container exits when hermes exits |
|
|
190
|
+
| `chat -q "..."` | `hermes chat -q "..."`, container exits with hermes exit code |
|
|
191
|
+
| `sleep infinity` | `sleep infinity` directly (long-lived sandbox mode) |
|
|
192
|
+
| `bash` | interactive `bash` directly |
|
|
193
|
+
| `docker run -it … --tui` | interactive Ink TUI with real TTY — see D9 |
|
|
194
|
+
|
|
195
|
+
`docker/main-wrapper.sh` detects whether `$1` is an executable on PATH and
|
|
196
|
+
routes either to "run this as a one-shot main service" or "wrap with
|
|
197
|
+
hermes".
|
|
198
|
+
|
|
199
|
+
### D3. Static services at build time; dynamic (per-profile) services at runtime
|
|
200
|
+
|
|
201
|
+
s6 offers two mechanisms:
|
|
202
|
+
|
|
203
|
+
- **s6-rc** (declarative, compile-then-swap): used for main hermes and the
|
|
204
|
+
dashboard — they're known at image build time.
|
|
205
|
+
- **scandir** (drop a directory + `s6-svscanctl -a`): used for per-profile
|
|
206
|
+
gateways — profiles are user-created after the image is built.
|
|
207
|
+
|
|
208
|
+
Per-profile gateway service dirs live at `/run/service/gateway-<profile>/`
|
|
209
|
+
(tmpfs, hermes-writable). s6-svscan picks them up on rescan.
|
|
210
|
+
|
|
211
|
+
### D4. ServiceManager protocol with two methods for runtime registration
|
|
212
|
+
|
|
213
|
+
Host paths (systemd, launchd, Windows Scheduled Tasks) need only
|
|
214
|
+
install/start/stop/restart of pre-declared services. Inside the container,
|
|
215
|
+
we additionally need to register services at runtime when a profile is
|
|
216
|
+
created. The protocol exposes this directly:
|
|
217
|
+
|
|
218
|
+
```python
|
|
219
|
+
class ServiceManager(Protocol):
|
|
220
|
+
kind: ServiceManagerKind # "systemd" | "launchd" | "windows" | "s6" | "none"
|
|
221
|
+
|
|
222
|
+
# Lifecycle of an already-declared service
|
|
223
|
+
def start(self, name: str) -> None: ...
|
|
224
|
+
def stop(self, name: str) -> None: ...
|
|
225
|
+
def restart(self, name: str) -> None: ...
|
|
226
|
+
def is_running(self, name: str) -> bool: ...
|
|
227
|
+
|
|
228
|
+
# Runtime registration (container-only; hosts raise NotImplementedError)
|
|
229
|
+
def supports_runtime_registration(self) -> bool: ...
|
|
230
|
+
def register_profile_gateway(
|
|
231
|
+
self, profile: str, *,
|
|
232
|
+
extra_env: dict[str, str] | None = None,
|
|
233
|
+
) -> None: ...
|
|
234
|
+
def unregister_profile_gateway(self, profile: str) -> None: ...
|
|
235
|
+
def list_profile_gateways(self) -> list[str]: ...
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Systemd, launchd, and Windows backends raise `NotImplementedError` on the
|
|
239
|
+
registration methods. Only the s6 backend implements them. Callers check
|
|
240
|
+
`supports_runtime_registration()` before calling.
|
|
241
|
+
|
|
242
|
+
The scope is intentionally narrow: it's specifically "register/unregister a
|
|
243
|
+
profile gateway," not a general-purpose process-management API.
|
|
244
|
+
|
|
245
|
+
### D5. Per-profile gateway service spec is fixed, not user-provided
|
|
246
|
+
|
|
247
|
+
Every profile gateway has the same command shape
|
|
248
|
+
(`hermes -p <profile> gateway run`, or `hermes gateway run` for the default
|
|
249
|
+
profile). The s6 backend generates the `run` script from a fixed template
|
|
250
|
+
given the profile name — no arbitrary command list. This keeps the API
|
|
251
|
+
surface tight and prevents callers from accidentally registering
|
|
252
|
+
non-gateway services.
|
|
253
|
+
|
|
254
|
+
Port selection is governed by the profile's `config.yaml`
|
|
255
|
+
(`[gateway] port = …`) — the single source of truth. (The original plan
|
|
256
|
+
proposed a Python-side SHA-256 port allocator with a 600-port range; it was
|
|
257
|
+
retired during PR review because it was dead code through the entire stack.)
|
|
258
|
+
|
|
259
|
+
### D6. Add detect_service_manager() alongside supports_systemd_services()
|
|
260
|
+
|
|
261
|
+
`supports_systemd_services()` stays as-is (host code paths unchanged). A new
|
|
262
|
+
`detect_service_manager() -> Literal["systemd", "launchd", "windows", "s6", "none"]`
|
|
263
|
+
composes existing detection functions (`is_macos()`, `is_windows()`,
|
|
264
|
+
`supports_systemd_services()`, `is_container()` + `_s6_running()`) and adds
|
|
265
|
+
an s6 branch for container detection. Host call sites continue to use the
|
|
266
|
+
existing functions; container-only code (the profile hooks) uses the new one.
|
|
267
|
+
|
|
268
|
+
`_s6_running()` probes `/proc/1/comm` (world-readable) and
|
|
269
|
+
`/run/s6/basedir`. The earlier `/proc/1/exe` probe was root-only readable
|
|
270
|
+
and silently failed for the unprivileged hermes user (UID 10000), making
|
|
271
|
+
the entire runtime-registration path inert in production — caught in PR
|
|
272
|
+
review.
|
|
273
|
+
|
|
274
|
+
### D7. Wrap existing systemd/launchd/windows functions, don't rewrite them
|
|
275
|
+
|
|
276
|
+
`SystemdServiceManager` / `LaunchdServiceManager` / `WindowsServiceManager`
|
|
277
|
+
are thin adapters over the existing `systemd_*` / `launchd_*` module-level
|
|
278
|
+
functions in `hermes_cli/gateway.py` and the
|
|
279
|
+
`gateway_windows.install/uninstall/start/stop/restart/is_installed`
|
|
280
|
+
functions in `hermes_cli/gateway_windows.py`. We get the abstraction
|
|
281
|
+
without rewriting ~2,200 LOC of working code.
|
|
282
|
+
|
|
283
|
+
### D8. Profile create/delete hooks register/unregister the s6 service
|
|
284
|
+
|
|
285
|
+
When `hermes profile create <name>` runs inside the container, the
|
|
286
|
+
profile-creation code path calls
|
|
287
|
+
`ServiceManager.register_profile_gateway(<name>)` if
|
|
288
|
+
`supports_runtime_registration()` is True. When `hermes profile delete
|
|
289
|
+
<name>` runs, it calls `unregister_profile_gateway(<name>)`. On host, both
|
|
290
|
+
calls are no-ops (registration not supported; existing systemd unit
|
|
291
|
+
generation continues to handle install/uninstall).
|
|
292
|
+
|
|
293
|
+
Existing per-profile `hermes -p <profile> gateway start/stop/restart` CLI
|
|
294
|
+
commands continue to work — in the container they dispatch to
|
|
295
|
+
`ServiceManager.start/stop/restart("gateway-<profile>")`, which translates
|
|
296
|
+
to `s6-svc -u`/`-d`/`-t` on the service dir.
|
|
297
|
+
|
|
298
|
+
`hermes gateway start` (no `-p`) targets a special `gateway-default` slot
|
|
299
|
+
that's always registered by the cont-init reconciler. Its run script omits
|
|
300
|
+
the `-p` flag and runs against the root `$HERMES_HOME` profile.
|
|
301
|
+
|
|
302
|
+
`--all` lifecycle (`hermes gateway stop --all`, `... restart --all`)
|
|
303
|
+
iterates `mgr.list_profile_gateways()` through s6 so s6's `want up`/`want
|
|
304
|
+
down` flips correctly. Without this, `--all` fell through to `pkill`
|
|
305
|
+
followed by s6-supervise auto-restart — net effect: kick instead of stop.
|
|
306
|
+
|
|
307
|
+
### D9. Interactive TUI bypasses s6 service-mode and runs as CMD for TTY passthrough
|
|
308
|
+
|
|
309
|
+
`docker run -it --rm <image> --tui` needs a real TTY connected to container
|
|
310
|
+
stdin/stdout for Ink raw-mode keyboard input, cursor control, and SIGWINCH.
|
|
311
|
+
Running the TUI as a normal s6 service fails because s6-supervise
|
|
312
|
+
disconnects service stdio from the container TTY (documented:
|
|
313
|
+
[s6-overlay#230](https://github.com/just-containers/s6-overlay/issues/230)).
|
|
314
|
+
|
|
315
|
+
**The pattern:** s6-overlay's `/init` execs a CMD as the container's "main
|
|
316
|
+
program" after the supervision tree is up. The CMD inherits
|
|
317
|
+
stdin/stdout/stderr from `/init` — which in `-it` mode is the container
|
|
318
|
+
TTY. The stage2 hook detects the TUI case and short-circuits the
|
|
319
|
+
main-hermes service so the hermes CMD becomes that main program.
|
|
320
|
+
|
|
321
|
+
```sh
|
|
322
|
+
# In docker/stage2-hook.sh
|
|
323
|
+
_is_tui_invocation() {
|
|
324
|
+
for arg in "$@"; do
|
|
325
|
+
case "$arg" in --tui|-T) return 0 ;; esac
|
|
326
|
+
done
|
|
327
|
+
case "${HERMES_TUI:-}" in 1|true|TRUE|yes) return 0 ;; esac
|
|
328
|
+
if [ -t 0 ] && [ $# -eq 0 ]; then return 0; fi
|
|
329
|
+
return 1
|
|
330
|
+
}
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
And in `docker/s6-rc.d/main-hermes/run`:
|
|
334
|
+
|
|
335
|
+
```sh
|
|
336
|
+
if [ -f /var/run/s6/container_environment/HERMES_TUI_MODE ]; then
|
|
337
|
+
exec sleep infinity # s6-overlay will exec CMD as the TTY-connected main
|
|
338
|
+
fi
|
|
339
|
+
exec s6-setuidgid hermes hermes ${HERMES_ARGS:-}
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
In TUI mode main hermes is effectively unsupervised (same as the pre-s6
|
|
343
|
+
behavior with tini — acceptable because the user is interactively
|
|
344
|
+
present). Dashboard and profile gateways still get full s6 supervision via
|
|
345
|
+
their separate services.
|
|
346
|
+
|
|
347
|
+
The integration test `test_tty_passthrough_to_container` uses `tput cols`
|
|
348
|
+
and `COLUMNS=123` as the probe.
|
|
349
|
+
|
|
350
|
+
---
|
|
351
|
+
|
|
352
|
+
## Risk Register
|
|
353
|
+
|
|
354
|
+
| Risk | Likelihood | Impact | Mitigation |
|
|
355
|
+
|---|---|---|---|
|
|
356
|
+
| Phase 2 breaks a downstream user's Dockerfile that `FROM`s ours | Medium | Medium | Release notes call out ENTRYPOINT change; the test harness (`tests/docker/`) gives high confidence in behavior parity |
|
|
357
|
+
| TUI TTY passthrough fails on some Docker versions | Low | High | Harness includes `test_tty_passthrough_to_container` as a hard gate; fallback plan = s6-fdholder ([s6-overlay#230](https://github.com/just-containers/s6-overlay/issues/230) Solution 2) |
|
|
358
|
+
| s6-overlay non-root quirks (logutil-service, fix-attrs) bite us | Low | Low | Supervisor runs as root, services drop — sidesteps these issues |
|
|
359
|
+
| Podman rootless UID mapping confuses s6 | Medium | Low | Documented as supported, fix reactively; a Podman + Docker environment is stood up for validation |
|
|
360
|
+
| Test harness is flaky (docker daemon issues, timing) | Medium | Low | Generous timeouts; skip when docker unavailable; polling helpers replace fixed sleeps in `test_container_restart.py` |
|
|
361
|
+
| Profile gateway crash loop masks a real config error | Low | Medium | s6 `finish` script `max_restarts` cap (planned follow-up); operators see crash-looping logs in `$HERMES_HOME/logs/gateways/<profile>/` |
|
|
362
|
+
| Dockerfile+entrypoint drift from linter (hadolint/shellcheck) reveals latent bugs | Low | Low | CI lint jobs catch them; fix or document ignore with rationale |
|
|
363
|
+
| Stale `gateway.pid` from a dead container collides with an unrelated live PID in the restarted container | Low | Medium | Cont-init reconciliation removes `gateway.pid` and `processes.json` from every profile dir on boot, before any new gateway starts |
|
|
364
|
+
| `docker restart` silently loses per-profile gateway registrations (tmpfs scandir wiped) | High (without mitigation) | High | Cont-init reconciliation re-registers from persistent `$HERMES_HOME/profiles/` and auto-starts those last seen `running`; outcome recorded to `$HERMES_HOME/logs/container-boot.log` (size-bounded, rotates to `.1` at 256 KiB) |
|
|
365
|
+
| A `running` gateway that's actually broken auto-restarts into a crash loop after every container restart | Low | Medium | s6 `finish` script `max_restarts` cap (planned); follow-up: `hermes doctor` alerts when N consecutive container restarts ended in `startup_failed` |
|
|
366
|
+
| `_s6_running()` detection works as root but silently fails for unprivileged hermes user, making runtime-registration path inert | High (without mitigation) | High | **Caught in PR review.** Detection now probes `/proc/1/comm` (world-readable) + `/run/s6/basedir`. Docker integration tests refactored to `docker exec -u hermes` so the realistic runtime user is exercised |
|
|
367
|
+
| `s6-svscanctl` from hermes hits EACCES on the root-owned control FIFO | Medium | Medium | `02-reconcile-profiles` chowns `/run/service/.s6-svscan/{control,lock}` to hermes after stage1 creates them |
|
|
368
|
+
| Per-service `supervise/control` FIFO is root-owned by s6-supervise, blocking `s6-svc` from hermes | Known | Medium | Surfaced cleanly as `S6CommandError` (with rc + stderr) instead of raw `CalledProcessError`. Permission fix tracked as a follow-up (small SUID helper, polling chown loop in cont-init.d, or replace `s6-svc` with `down`-marker manipulation) |
|
|
369
|
+
|
|
370
|
+
---
|
|
371
|
+
|
|
372
|
+
## Decision Log
|
|
373
|
+
|
|
374
|
+
| # | Question | Decision |
|
|
375
|
+
|---|---|---|
|
|
376
|
+
| OQ1 | Gate Phase 2 behind env var? | Ship directly (Hermes is pre-1.0; users can pin the previous image) |
|
|
377
|
+
| OQ2 | s6 root model | Root `/init`, drop per-service via `s6-setuidgid hermes` |
|
|
378
|
+
| OQ3 | Dashboard opt-in mechanism | Always declared as an s6 service; `03-dashboard-toggle` cont-init script writes a `down` marker when `HERMES_DASHBOARD` is unset so `s6-svstat` reports the slot's real state |
|
|
379
|
+
| OQ4 | Podman rootless | Supported, fix reactively |
|
|
380
|
+
| OQ5 | Service naming | `gateway-<profile>` (matches pre-existing `hermes-gateway-<profile>.service` systemd convention) |
|
|
381
|
+
| OQ6 | — (retired; no subagent gateways in scope) | — |
|
|
382
|
+
| OQ7 | Resource limits per profile gateway | Defer (no per-cgroup limits; rely on the container's overall limit) |
|
|
383
|
+
| OQ8 | Log persistence | `$HERMES_HOME/logs/gateways/<profile>/`. The log path is sourced from runtime `$HERMES_HOME` via `with-contenv`, NOT Python-substituted at registration time |
|
|
384
|
+
| OQ9 | TUI passthrough | Trust the documented [s6-overlay#230](https://github.com/just-containers/s6-overlay/issues/230) Solution 1; harness includes a TTY passthrough hard-gate test |
|
|
385
|
+
|
|
386
|
+
**Post-merge additions from PR #30136 review:**
|
|
387
|
+
|
|
388
|
+
- **Multi-arch tarballs:** `TARGETARCH` mapped to `x86_64` / `aarch64`;
|
|
389
|
+
per-arch tarball fetched via `curl` because `ADD` doesn't honor BuildKit
|
|
390
|
+
args.
|
|
391
|
+
- **SHA256 verification:** all three tarballs (noarch, symlinks, per-arch)
|
|
392
|
+
pinned via build ARGs and verified with `sha256sum -c` against a single
|
|
393
|
+
checksum file (avoids hadolint DL4006 piped-shell warning).
|
|
394
|
+
- **`gateway-default` slot:** always registered by the reconciler so
|
|
395
|
+
`hermes gateway start` (no `-p`) has somewhere to land.
|
|
396
|
+
- **Friendly lifecycle errors:** `GatewayNotRegisteredError` and
|
|
397
|
+
`S6CommandError` translate `CalledProcessError` into actionable CLI
|
|
398
|
+
messages.
|
|
399
|
+
- **Atomic publication in the reconciler:** mirrors
|
|
400
|
+
`register_profile_gateway`'s tmp+rename pattern.
|
|
401
|
+
- **`container-boot.log` rotation:** 256 KiB soft cap, rotated to `.1`.
|
|
402
|
+
- **`port` parameter retired:** allocator + kwarg were dead code through
|
|
403
|
+
the entire stack; `config.yaml` is the single source of truth.
|
|
404
|
+
|
|
405
|
+
---
|
|
406
|
+
|
|
407
|
+
## Verification Checklist
|
|
408
|
+
|
|
409
|
+
- [x] Test harness (`tests/docker/`) passes against the s6 image
|
|
410
|
+
- [x] hadolint + shellcheck run green in CI
|
|
411
|
+
- [x] `docker run -it --rm hermes-agent --tui` starts the Ink TUI with
|
|
412
|
+
working keyboard input, cursor control, and resize (SIGWINCH)
|
|
413
|
+
- [x] Dashboard crashes are recovered by s6 within ~2s
|
|
414
|
+
- [x] `hermes profile create test` inside a container creates
|
|
415
|
+
`/run/service/gateway-test/`
|
|
416
|
+
- [x] `hermes -p test gateway start` inside a container dispatches through s6
|
|
417
|
+
- [x] `hermes -p test gateway stop` inside a container cleanly stops via s6
|
|
418
|
+
- [x] `hermes profile delete test` inside a container removes
|
|
419
|
+
`/run/service/gateway-test/`
|
|
420
|
+
- [x] Profile gateway logs persist at
|
|
421
|
+
`$HERMES_HOME/logs/gateways/test/current`
|
|
422
|
+
- [x] `hermes status` inside the container shows `Manager: s6`
|
|
423
|
+
- [x] `hermes gateway start` (no `-p`) inside a container targets
|
|
424
|
+
`gateway-default` and runs against the root profile
|
|
425
|
+
- [x] `hermes gateway stop --all` / `... restart --all` iterate every
|
|
426
|
+
profile gateway under s6 instead of pkill-then-supervise-restart
|
|
427
|
+
- [x] `docker restart` survives per-profile gateway registrations via the
|
|
428
|
+
cont-init reconciler; running gateways come back up, stopped ones
|
|
429
|
+
stay down
|
|
430
|
+
- [x] Multi-arch image builds for both `linux/amd64` and `linux/arm64`
|
|
431
|
+
- [x] s6-overlay tarballs are SHA256-verified at build time
|
|
432
|
+
- [x] No systemd/launchd host-side functions were modified (only wrapped)
|
|
433
|
+
- [x] `hermes gateway install/start/stop` on Linux host and macOS host
|
|
434
|
+
behave identically to pre-change
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# Hermes Agent Has Had "Routines" Since March
|
|
2
|
+
|
|
3
|
+
Anthropic just announced [Claude Code Routines](https://claude.com/blog/introducing-routines-in-claude-code) — scheduled tasks, GitHub event triggers, and API-triggered agent runs. Bundled prompt + repo + connectors, running on their infrastructure.
|
|
4
|
+
|
|
5
|
+
It's a good feature. We shipped it two months ago.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## The Three Trigger Types — Side by Side
|
|
10
|
+
|
|
11
|
+
Claude Code Routines offers three ways to trigger an automation:
|
|
12
|
+
|
|
13
|
+
**1. Scheduled (cron)**
|
|
14
|
+
> "Every night at 2am: pull the top bug from Linear, attempt a fix, and open a draft PR."
|
|
15
|
+
|
|
16
|
+
Hermes equivalent — works today:
|
|
17
|
+
```bash
|
|
18
|
+
hermes cron create "0 2 * * *" \
|
|
19
|
+
"Pull the top bug from the issue tracker, attempt a fix, and open a draft PR." \
|
|
20
|
+
--name "Nightly bug fix" \
|
|
21
|
+
--deliver telegram
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
**2. GitHub Events (webhook)**
|
|
25
|
+
> "Flag PRs that touch the /auth-provider module and post to #auth-changes."
|
|
26
|
+
|
|
27
|
+
Hermes equivalent — works today:
|
|
28
|
+
```bash
|
|
29
|
+
hermes webhook subscribe auth-watch \
|
|
30
|
+
--events "pull_request" \
|
|
31
|
+
--prompt "PR #{pull_request.number}: {pull_request.title} by {pull_request.user.login}. Check if it touches the auth-provider module. If yes, summarize the changes." \
|
|
32
|
+
--deliver slack
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
**3. API Triggers**
|
|
36
|
+
> "Read the alert payload, find the owning service, post a triage summary to #oncall."
|
|
37
|
+
|
|
38
|
+
Hermes equivalent — works today:
|
|
39
|
+
```bash
|
|
40
|
+
hermes webhook subscribe alert-triage \
|
|
41
|
+
--prompt "Alert: {alert.name} — Severity: {alert.severity}. Find the owning service, investigate, and post a triage summary with proposed first steps." \
|
|
42
|
+
--deliver slack
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Every use case in their blog post — backlog triage, docs drift, deploy verification, alert correlation, library porting, bespoke PR review — has a working Hermes implementation. No new features needed. It's been shipping since March 2026.
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## What's Different
|
|
50
|
+
|
|
51
|
+
| | Claude Code Routines | Hermes Agent |
|
|
52
|
+
|---|---|---|
|
|
53
|
+
| **Scheduled tasks** | ✅ Schedule-based | ✅ Any cron expression + human-readable intervals |
|
|
54
|
+
| **GitHub triggers** | ✅ PR, issue, push events | ✅ Any GitHub event via webhook subscriptions |
|
|
55
|
+
| **API triggers** | ✅ POST to unique endpoint | ✅ POST to webhook routes with HMAC auth |
|
|
56
|
+
| **MCP connectors** | ✅ Native connectors | ✅ Full MCP client support |
|
|
57
|
+
| **Script pre-processing** | ❌ | ✅ Python scripts run before agent, inject context |
|
|
58
|
+
| **Skill chaining** | ❌ | ✅ Load multiple skills per automation |
|
|
59
|
+
| **Daily limit** | 5-25 runs/day | **Unlimited** |
|
|
60
|
+
| **Model choice** | Claude only | **Any model** — Claude, GPT, Gemini, DeepSeek, Qwen, local |
|
|
61
|
+
| **Delivery targets** | GitHub comments | Telegram, Discord, Slack, SMS, email, GitHub comments, webhooks, local files |
|
|
62
|
+
| **Infrastructure** | Anthropic's servers | **Your infrastructure** — VPS, home server, laptop |
|
|
63
|
+
| **Data residency** | Anthropic's cloud | **Your machines** |
|
|
64
|
+
| **Cost** | Pro/Max/Team/Enterprise subscription | Your API key, your rates |
|
|
65
|
+
| **Open source** | No | **Yes** — MIT license |
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Things Hermes Does That Routines Can't
|
|
70
|
+
|
|
71
|
+
### Script Injection
|
|
72
|
+
|
|
73
|
+
Run a Python script *before* the agent. The script's stdout becomes context. The script handles mechanical work (fetching, diffing, computing); the agent handles reasoning.
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
hermes cron create "every 1h" \
|
|
77
|
+
"If CHANGE DETECTED, summarize what changed. If NO_CHANGE, respond with [SILENT]." \
|
|
78
|
+
--script ~/.hermes/scripts/watch-site.py \
|
|
79
|
+
--name "Pricing monitor" \
|
|
80
|
+
--deliver telegram
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
The `[SILENT]` pattern means you only get notified when something actually happens. No spam.
|
|
84
|
+
|
|
85
|
+
### Multi-Skill Workflows
|
|
86
|
+
|
|
87
|
+
Chain specialized skills together. Each skill teaches the agent a specific capability, and the prompt ties them together.
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
hermes cron create "0 8 * * *" \
|
|
91
|
+
"Search arXiv for papers on language model reasoning. Save the top 3 as Obsidian notes." \
|
|
92
|
+
--skills "arxiv,obsidian" \
|
|
93
|
+
--name "Paper digest"
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Deliver Anywhere
|
|
97
|
+
|
|
98
|
+
One automation, any destination:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
--deliver telegram # Telegram home channel
|
|
102
|
+
--deliver discord # Discord home channel
|
|
103
|
+
--deliver slack # Slack channel
|
|
104
|
+
--deliver sms:+15551234567 # Text message
|
|
105
|
+
--deliver telegram:-1001234567890:42 # Specific Telegram forum topic
|
|
106
|
+
--deliver local # Save to file, no notification
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Model-Agnostic
|
|
110
|
+
|
|
111
|
+
Your nightly triage can run on Claude. Your deploy verification can run on GPT. Your cost-sensitive monitors can run on DeepSeek or a local model. Same automation system, any backend.
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## The Limits Tell the Story
|
|
116
|
+
|
|
117
|
+
Claude Code Routines: **5 routines per day** on Pro. **25 on Enterprise.** That's their ceiling.
|
|
118
|
+
|
|
119
|
+
Hermes has no daily limit. Run 500 automations a day if you want. The only constraint is your API budget, and you choose which models to use for which tasks.
|
|
120
|
+
|
|
121
|
+
A nightly backlog triage on Sonnet costs roughly $0.02-0.05. A monitoring check on DeepSeek costs fractions of a cent. You control the economics.
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## Get Started
|
|
126
|
+
|
|
127
|
+
Hermes Agent is open source and free. The automation infrastructure — cron scheduler, webhook platform, skill system, multi-platform delivery — is built in.
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
pip install hermes-agent
|
|
131
|
+
hermes setup
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Set up a scheduled task in 30 seconds:
|
|
135
|
+
```bash
|
|
136
|
+
hermes cron create "0 9 * * 1" \
|
|
137
|
+
"Generate a weekly AI news digest. Search the web for major announcements, trending repos, and notable papers. Keep it under 500 words with links." \
|
|
138
|
+
--name "Weekly digest" \
|
|
139
|
+
--deliver telegram
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Set up a GitHub webhook in 60 seconds:
|
|
143
|
+
```bash
|
|
144
|
+
hermes gateway setup # enable webhooks
|
|
145
|
+
hermes webhook subscribe pr-review \
|
|
146
|
+
--events "pull_request" \
|
|
147
|
+
--prompt "Review PR #{pull_request.number}: {pull_request.title}" \
|
|
148
|
+
--skills "github-code-review" \
|
|
149
|
+
--deliver github_comment
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Full automation templates gallery: [hermes-agent.nousresearch.com/docs/guides/automation-templates](https://hermes-agent.nousresearch.com/docs/guides/automation-templates)
|
|
153
|
+
|
|
154
|
+
Documentation: [hermes-agent.nousresearch.com](https://hermes-agent.nousresearch.com)
|
|
155
|
+
|
|
156
|
+
GitHub: [github.com/NousResearch/hermes-agent](https://github.com/NousResearch/hermes-agent)
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
*Hermes Agent is built by [Nous Research](https://nousresearch.com). Open source, model-agnostic, runs on your infrastructure.*
|