winter-super-cli 2026.6.24 → 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.
Files changed (123) hide show
  1. package/CHANGELOG.md +28 -5
  2. package/README.md +85 -0
  3. package/package.json +5 -1
  4. package/resources/local/gsap-skills/.claude-plugin/marketplace.json +20 -0
  5. package/resources/local/gsap-skills/.claude-plugin/plugin.json +6 -0
  6. package/resources/local/gsap-skills/.cursor-plugin/marketplace.json +13 -0
  7. package/resources/local/gsap-skills/.cursor-plugin/plugin.json +22 -0
  8. package/resources/local/gsap-skills/.github/copilot-instructions.md +17 -0
  9. package/resources/local/gsap-skills/.github/instructions/react.instructions.md +15 -0
  10. package/resources/local/gsap-skills/.github/instructions/scrolltrigger.instructions.md +18 -0
  11. package/resources/local/gsap-skills/AGENTS.md +27 -0
  12. package/resources/local/gsap-skills/CLAUDE.md +1 -0
  13. package/resources/local/gsap-skills/GEMINI.md +1 -0
  14. package/resources/local/gsap-skills/LICENSE +21 -0
  15. package/resources/local/gsap-skills/README.md +163 -0
  16. package/resources/local/gsap-skills/assets/gsap-green.svg +7 -0
  17. package/resources/local/gsap-skills/assets/gsap-icon-inverted.svg +15 -0
  18. package/resources/local/gsap-skills/assets/gsap-icon-square.svg +1 -0
  19. package/resources/local/gsap-skills/assets/gsap-white.svg +7 -0
  20. package/resources/local/gsap-skills/examples/README.md +29 -0
  21. package/resources/local/gsap-skills/examples/nuxt/app/app.vue +3 -0
  22. package/resources/local/gsap-skills/examples/nuxt/app/composables/useGSAP.ts +91 -0
  23. package/resources/local/gsap-skills/examples/nuxt/app/pages/index.vue +55 -0
  24. package/resources/local/gsap-skills/examples/nuxt/nuxt.config.ts +4 -0
  25. package/resources/local/gsap-skills/examples/nuxt/package.json +18 -0
  26. package/resources/local/gsap-skills/examples/react/App.jsx +46 -0
  27. package/resources/local/gsap-skills/examples/react/index.html +12 -0
  28. package/resources/local/gsap-skills/examples/react/main.jsx +9 -0
  29. package/resources/local/gsap-skills/examples/react/package.json +21 -0
  30. package/resources/local/gsap-skills/examples/react/vite.config.js +7 -0
  31. package/resources/local/gsap-skills/examples/vanilla/index.html +33 -0
  32. package/resources/local/gsap-skills/examples/vanilla/main.js +36 -0
  33. package/resources/local/gsap-skills/examples/vue/app.vue +47 -0
  34. package/resources/local/gsap-skills/examples/vue/index.html +15 -0
  35. package/resources/local/gsap-skills/examples/vue/main.js +9 -0
  36. package/resources/local/gsap-skills/examples/vue/package.json +19 -0
  37. package/resources/local/gsap-skills/examples/vue/vite.config.js +7 -0
  38. package/resources/local/gsap-skills/skills/gsap-core/SKILL.md +254 -0
  39. package/resources/local/gsap-skills/skills/gsap-frameworks/SKILL.md +266 -0
  40. package/resources/local/gsap-skills/skills/gsap-performance/SKILL.md +79 -0
  41. package/resources/local/gsap-skills/skills/gsap-plugins/SKILL.md +433 -0
  42. package/resources/local/gsap-skills/skills/gsap-react/SKILL.md +136 -0
  43. package/resources/local/gsap-skills/skills/gsap-scrolltrigger/SKILL.md +296 -0
  44. package/resources/local/gsap-skills/skills/gsap-timeline/SKILL.md +107 -0
  45. package/resources/local/gsap-skills/skills/gsap-utils/SKILL.md +284 -0
  46. package/resources/local/gsap-skills/skills/llms.txt +39 -0
  47. package/resources/local/hermes-agent-core/AGENTS.md +1132 -0
  48. package/resources/local/hermes-agent-core/LICENSE +21 -0
  49. package/resources/local/hermes-agent-core/README.md +215 -0
  50. package/resources/local/hermes-agent-core/docs/2026-05-07-s6-overlay-dynamic-subagent-gateways.md +434 -0
  51. package/resources/local/hermes-agent-core/hermes-already-has-routines.md +160 -0
  52. package/resources/local/hermes-agent-core/skills/autonomous-ai-agents/DESCRIPTION.md +3 -0
  53. package/resources/local/hermes-agent-core/skills/autonomous-ai-agents/claude-code/SKILL.md +745 -0
  54. package/resources/local/hermes-agent-core/skills/autonomous-ai-agents/codex/SKILL.md +130 -0
  55. package/resources/local/hermes-agent-core/skills/autonomous-ai-agents/hermes-agent/SKILL.md +1021 -0
  56. package/resources/local/hermes-agent-core/skills/autonomous-ai-agents/kanban-codex-lane/SKILL.md +277 -0
  57. package/resources/local/hermes-agent-core/skills/autonomous-ai-agents/kanban-codex-lane/templates/pmb-codex-lane-prompt.md +57 -0
  58. package/resources/local/hermes-agent-core/skills/autonomous-ai-agents/opencode/SKILL.md +219 -0
  59. package/resources/local/hermes-agent-core/skills/github/DESCRIPTION.md +3 -0
  60. package/resources/local/hermes-agent-core/skills/github/codebase-inspection/SKILL.md +116 -0
  61. package/resources/local/hermes-agent-core/skills/github/github-auth/SKILL.md +247 -0
  62. package/resources/local/hermes-agent-core/skills/github/github-auth/scripts/gh-env.sh +66 -0
  63. package/resources/local/hermes-agent-core/skills/github/github-code-review/SKILL.md +481 -0
  64. package/resources/local/hermes-agent-core/skills/github/github-code-review/references/review-output-template.md +74 -0
  65. package/resources/local/hermes-agent-core/skills/github/github-issues/SKILL.md +370 -0
  66. package/resources/local/hermes-agent-core/skills/github/github-issues/templates/bug-report.md +35 -0
  67. package/resources/local/hermes-agent-core/skills/github/github-issues/templates/feature-request.md +31 -0
  68. package/resources/local/hermes-agent-core/skills/github/github-pr-workflow/SKILL.md +367 -0
  69. package/resources/local/hermes-agent-core/skills/github/github-pr-workflow/references/ci-troubleshooting.md +183 -0
  70. package/resources/local/hermes-agent-core/skills/github/github-pr-workflow/references/conventional-commits.md +71 -0
  71. package/resources/local/hermes-agent-core/skills/github/github-pr-workflow/templates/pr-body-bugfix.md +35 -0
  72. package/resources/local/hermes-agent-core/skills/github/github-pr-workflow/templates/pr-body-feature.md +33 -0
  73. package/resources/local/hermes-agent-core/skills/github/github-repo-management/SKILL.md +516 -0
  74. package/resources/local/hermes-agent-core/skills/github/github-repo-management/references/github-api-cheatsheet.md +161 -0
  75. package/resources/local/hermes-agent-core/skills/mcp/DESCRIPTION.md +3 -0
  76. package/resources/local/hermes-agent-core/skills/mcp/native-mcp/SKILL.md +357 -0
  77. package/resources/local/hermes-agent-core/skills/software-development/debugging-hermes-tui-commands/SKILL.md +152 -0
  78. package/resources/local/hermes-agent-core/skills/software-development/hermes-agent-skill-authoring/SKILL.md +165 -0
  79. package/resources/local/hermes-agent-core/skills/software-development/hermes-s6-container-supervision/SKILL.md +176 -0
  80. package/resources/local/hermes-agent-core/skills/software-development/node-inspect-debugger/SKILL.md +319 -0
  81. package/resources/local/hermes-agent-core/skills/software-development/plan/SKILL.md +58 -0
  82. package/resources/local/hermes-agent-core/skills/software-development/python-debugpy/SKILL.md +375 -0
  83. package/resources/local/hermes-agent-core/skills/software-development/requesting-code-review/SKILL.md +280 -0
  84. package/resources/local/hermes-agent-core/skills/software-development/spike/SKILL.md +197 -0
  85. package/resources/local/hermes-agent-core/skills/software-development/subagent-driven-development/SKILL.md +352 -0
  86. package/resources/local/hermes-agent-core/skills/software-development/subagent-driven-development/references/context-budget-discipline.md +53 -0
  87. package/resources/local/hermes-agent-core/skills/software-development/subagent-driven-development/references/gates-taxonomy.md +93 -0
  88. package/resources/local/hermes-agent-core/skills/software-development/systematic-debugging/SKILL.md +367 -0
  89. package/resources/local/hermes-agent-core/skills/software-development/test-driven-development/SKILL.md +343 -0
  90. package/resources/local/hermes-agent-core/skills/software-development/writing-plans/SKILL.md +297 -0
  91. package/resources/local/manifest.json +12 -0
  92. package/rule.md +2 -0
  93. package/scripts/audit-pack.js +5 -0
  94. package/scripts/smoke-browser.js +53 -0
  95. package/scripts/smoke-package.js +38 -4
  96. package/skill.md +36 -4
  97. package/skills/gsap.md +26 -0
  98. package/skills/hermes-agent.md +17 -0
  99. package/src/agent/agent-definitions.js +4 -4
  100. package/src/agent/runtime.js +179 -5
  101. package/src/agent/subagent-child.js +44 -0
  102. package/src/ai/capability-scorecard.js +193 -14
  103. package/src/ai/hermes-core.js +77 -0
  104. package/src/ai/model-capabilities.js +42 -2
  105. package/src/ai/prompts/system-prompt.js +18 -2
  106. package/src/ai/small-model-amplifier.js +35 -7
  107. package/src/ai/workflow-selector.js +22 -1
  108. package/src/cli/commands.js +46 -2
  109. package/src/cli/config.js +45 -6
  110. package/src/cli/context-loader.js +253 -9
  111. package/src/cli/conversation-format.js +5 -0
  112. package/src/cli/input-controller.js +79 -10
  113. package/src/cli/prompt-builder.js +47 -8
  114. package/src/cli/repl-commands.js +115 -0
  115. package/src/cli/repl.js +343 -85
  116. package/src/cli/slash-commands.js +4 -2
  117. package/src/cli/tui.js +133 -37
  118. package/src/mcp/client.js +54 -11
  119. package/src/mcp/presets.js +114 -0
  120. package/src/tools/agent.js +316 -25
  121. package/src/tools/executor.js +412 -12
  122. package/src/tools/permission.js +20 -17
  123. package/winter.d.ts +112 -10
@@ -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.*
@@ -0,0 +1,3 @@
1
+ ---
2
+ description: Skills for spawning and orchestrating autonomous AI coding agents and multi-agent workflows — running independent agent processes, delegating tasks, and coordinating parallel workstreams.
3
+ ---