feat(run): add hermit run — Superintendent loop owned by HERMIT's own process - #189
Merged
Conversation
Owner
Author
Superintendentによるレビュー(PR #189, Issue #181)
差分の内容確認
特記事項: REQUIREMENTS.md REQ-014の書き換え本PRは既存のREQ-014(「Go バイナリがClaude Codeをサブプロセスとして起動・管理する『外側から包む』アーキテクチャを行わない」)の非ゴールに、 .claude/settings.json の変更新設した 懸念点
推奨設計判断・実装ともにIssueの要求と過去の失敗(#147, #171)を踏まえた妥当なものです。人間による承認後のマージを推奨します。マージ順序およびREQ-015番号衝突の解消(5件分)をご検討ください。 |
ytnobody
force-pushed
the
hermit/ytnobody/issue-181
branch
2 times, most recently
from
July 28, 2026 02:59
8d4bbca to
f328533
Compare
… process (Closes #181) Adds `hermit run`, a long-lived process (same shape as `hermit serve`) that owns an internal ticker and launches `claude -p` non-interactively once per tick, waiting `[agent].loop_interval` after each pass before starting the next. This removes the requirement that a human keep a Claude Code session open indefinitely to run /hermit — the third design attempt at this problem (#147 background-subagent design failed per but session-bound). No subagent spawning is introduced here at all. - internal/runloop: the tick loop. Sequential by construction (no overlapping passes), graceful SIGINT/SIGTERM handling (an in-flight pass always finishes; shutdown is only checked between passes), .hermit-paused/.hermit-quit detection, and consecutive-failure webhook notification via internal/notification. - internal/state: owns .hermit/superintendent-state.json (atomic read-modify-write). Both `hermit run` (last_success_tick, consecutive_failures) and the new get_loop_state/update_loop_state MCP tools (pr_comments_since, issue_comments_since, requirements_sweep_since) go through this package instead of the Superintendent hand-writing the JSON file. - cmd/hermit: `hermit run` subcommand, [run].failure_notify_threshold config, and the claude invocation (--dangerously-skip-permissions, optional --model, -p <CLAUDE.md contents>) matching the pattern already documented in docs/github-actions.md. - README: "Alternative to Step 2" and a new "Running HERMIT Continuously" section documenting systemd/launchd/Windows service/tmux/Docker daemonization — no OS-specific code is added, per the Issue's explicit scope boundary. - REQUIREMENTS.md: adds REQ-015 and carves out an explicit, narrow exception in REQ-014's non-goals for this one case. Verified GOOS=windows GOARCH=amd64 build and go test ./... pass. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
ytnobody
force-pushed
the
hermit/ytnobody/issue-181
branch
from
July 28, 2026 03:04
f328533 to
cc4d750
Compare
Owner
Author
|
|
Merged
3 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #181. Adds
hermit run, a long-lived process (the same shapehermit servealready is for the MCP server) that owns an internal ticker and drives the Superintendent cycle by launchingclaude -pnon-interactively once per tick, waiting[agent].loop_intervalafter each pass finishes before starting the next one. This is the third design attempt at "the loop shouldn't require a human to keep a Claude Code session open forever":No subagent spawning is introduced anywhere in this design —
hermit runnever touches the Agent tool at all, so #171's failure mode structurally cannot recur.What changed
internal/runloop(new): the tick loop itself.Invokebefore doing anything else, so two passes can never overlap even if a pass runs long.Invokecall — a running pass always finishes; the loop stops only after that..hermit-paused(skips the tick, loop keeps ticking) and.hermit-quit(stops the loop) itself, without relying on the invokedclaude -psession's own logic.internal/notification) once a configurable threshold is reached.internal/state(new): owns.hermit/superintendent-state.json(atomic read-modify-write via temp file + rename). Bothhermit runitself (last_success_tick,consecutive_failures) and the two new MCP tools below write through this package — nothing hand-writes the JSON file directly anymore.internal/mcp: two new tools,get_loop_stateandupdate_loop_state, so the Superintendent cycle reads/writes the three cadence timestamps (pr_comments_since,issue_comments_since,requirements_sweep_since) through an MCP tool instead of writing the JSON file itself.cmd/hermit: thehermit runsubcommand, a new[run].failure_notify_thresholdconfig field (default 3), and the actualclaudeinvocation —--dangerously-skip-permissions, optional--model,-p <CLAUDE.md contents>— matching the non-interactive pattern already documented indocs/github-actions.md.hermit run, plus a new "Running HERMIT Continuously" section with systemd/launchd/Windows-service/tmux/Docker guidance. No OS-specific code was added anywhere — this is documentation only, per the Issue's explicit scope boundary.hermit run, and carves out a narrow, explicit exception in REQ-014's non-goals list (which otherwise forbids a Go binary launching Claude Code as a subprocess) — see the REQ-014 update for the reasoning.Assumptions made (no chat clarification was requested, per HERMIT's Human Input Policy)
[run].failure_notify_threshold <= 0falls back to the default (3), matching this codebase's existing convention for[agent].loop_interval/[agent].max_engineers(there's no way to explicitly disable notification via this field, same limitation those two already have).hermit runreadsCLAUDE.mdfresh from the project root on every tick (not cached at startup), so edits toCLAUDE.mdtake effect on the very next tick without restartinghermit run.hermit runrefuses to start (fails fast, before entering the loop) ifCLAUDE.mdis missing from the project root, rather than looping forever invoking aclaude -pthat would immediately error every tick./hermitandhermit runare documented as coexisting alternatives, not a replacement — per the Issue's explicit "スコープ外" on retiring the slash command.Test plan
go test ./...passesGOOS=windows GOARCH=amd64 go build ./...succeedsgofmt -l ./go vet ./...cleaninternal/runloop/runloop_test.go,internal/state/state_test.go,internal/mcp/req_test.go(TestREQ019_*),cmd/hermit/run_test.go— cover no-overlap, graceful shutdown not interrupting an in-flight pass,.hermit-quit/.hermit-pauseddetection, state persistence, failure-threshold notification, non-interactiveclaudeargument construction, and CLAUDE.md-missing fail-fast..claude/settings.json'spermissions.allowupdated with the two new MCP tools (TestAllRegisteredToolsAreAllowlistedguards this).🤖 Generated with Claude Code