feat(herdr): integrate treehouse with the herdr runtime#44
Conversation
… /herdr skill When treehouse runs inside herdr (HERDR_ENV=1) and the herdr CLI is present, `treehouse get` now opens each acquired worktree in its own herdr pane and routes the agent that lands there to the /herdr skill, instead of nesting a subshell blindly inside the caller's pane. - internal/herdr: runtime detection, `herdr agent start` pane spawning, pane-id parsing, and the /herdr routing message. Spawning is bounded by a 10s timeout so an unresponsive herdr server triggers the subshell fallback instead of hanging `treehouse get`. Shells out only, so it builds on Windows even though herdr is unix-only. - cmd/get: herdr-native get leases the worktree and opens a pane running the hidden __hold holder, which runs the worktree session and returns it to the pool when its shell exits, preserving exit-to-return even though the outer process exits immediately. A lease (not an owner reservation) protects the worktree across that exit. Any failure to spawn a pane releases the lease and falls back to the classic in-place subshell, so behavior outside herdr is unchanged. Adds --no-herdr and --no-focus; exports TREEHOUSE_HERDR=1 and the routing line on every herdr path (including --lease and the fallback). - config: a [herdr] section (enabled/split/focus) honored only from user-level config, like hooks, because it drives the user's live herdr session. - skills/treehouse: a companion agent skill that teaches agents to use treehouse and cross-routes to the /herdr skill; installable via `make install-skill`. - docs: README "Using treehouse inside herdr" section, new flags in the CLI reference, AGENTS.md design notes, and treehouse.toml.example. - tests: unit tests for the herdr package and the [herdr] config; the e2e harness forces TREEHOUSE_NO_HERDR=1 so the suite is deterministic when run from inside a herdr session.
…bsection Apply the document-step suggestions that were deferred from the feat commit: - AGENTS.md (and CLAUDE.md via symlink): note that SpawnHold is bounded by a 10s timeout so an unresponsive herdr server triggers the subshell fallback. - README.md: add a ### herdr subsection under Configuration mirroring ### Hooks, so readers browsing Configuration see the [herdr] enabled/split/focus options.
Reviewer context from the validation passDocs: Deferred to a focused follow-up (#45): two
Verified false positive (not changed): the The feature was also verified end-to-end in a live herdr session (real pane spawn, |
Intent
Goal: make treehouse a full first-class citizen of the herdr terminal multiplexer. Three layers: (1) entry routing - when running inside herdr, export a TREEHOUSE_HERDR=1 marker into the worktree and print a one-line message routing the agent that lands there to the /herdr skill; (2) a shipped companion treehouse agent skill that cross-routes to the /herdr skill; (3) herdr-native spawning - open each acquired worktree in its own herdr pane instead of nesting a blocking subshell in the caller's pane.
Deliberate design decisions (please treat as intended, not mistakes):
Known, user-accepted tradeoffs for this PR (already decided, please do not block on them):
Verification already done in a live herdr session: get inside herdr spawned a real pane parsed from herdr's JSON; __hold ran the worktree session with TREEHOUSE_HERDR=1 and cwd in the worktree; status showed "leased (held by herdr)"; exiting the pane returned the worktree to the pool; get --lease printed only the path on stdout; --no-herdr / TREEHOUSE_NO_HERDR=1 / herdr-not-on-PATH all fell back to the classic subshell; return released the lease.
What Changed
internal/herdrpackage withSpawnHold,IsRuntime,Available, andRoutingMessage- whenHERDR_ENV=1and the herdr CLI is present,treehouse getleases the worktree and opens it in a dedicated herdr pane via a hidden__holdsubcommand, with a 10s timeout so an unresponsive herdr server triggers fallback rather than hanging[herdr]config section (enabled,split,focus) honored only from user-level config; extendedcmd/get.gowithgetHerdrRunE/getLeaseRunE/holdAndReturnand a--no-herdr/TREEHOUSE_NO_HERDRopt-out; anchored.gitignorepatterns to/treehouseand/treehouse.exeto stop the unanchored pattern from silently excludingskills/treehouse/from gitskills/treehouse/SKILL.mdthat cross-routes agents landing in a treehouse worktree to the/herdrskillRisk Assessment
LeasedAtserialization diverges from the stated backward-compatibility contract.Testing
Static code review confirms all three herdr integration layers are correctly implemented: (1) TREEHOUSE_HERDR=1 marker and /herdr routing message exported in holdAndReturn when HERDR_ENV=1; (2) skills/treehouse/SKILL.md ships with a dedicated herdr section cross-routing to the /herdr skill; (3) herdr-native pane spawning via AcquireLease+SpawnHold with 10s timeout, errHerdrFallback on any failure, __hold hidden subcommand for exit-to-return UX, and .gitignore anchored to /treehouse to avoid masking the skills/ directory. Runtime test execution was blocked by the permission system - no
goormakecommands could be run.Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
internal/pool/state.go:19-LeasedAt time.Timetaggedjson:"leased_at,omitempty,omitzero"will not be omitted for non-leased entries. Standardencoding/jsondoes not omit zero struct values withomitempty(only scalars, slices, maps, and pointers are handled byisEmptyValue). Theomitzerooption is not recognized byencoding/jsonand is silently ignored. Every non-leasedWorktreeEntrywritten to disk will contain"leased_at":"0001-01-01T00:00:00Z", contradicting the stated design goal of backward-compatible, omitted-when-zero state files. There is no operational breakage (old binaries ignore unknown fields on decode), but the format diverges from intent. Fix: change the field to*time.Timesoomitemptyomits the nil pointer, updatingmarkAcquiredandclearLeaseaccordingly.cmd/get.go:82-post_createhooks fire twice whengetHerdrRunEfalls back to a classic subshell. The sequence is: (1)pool.AcquireLeaseacquires a worktree and runs hooks (routed to stderr); (2)herdr.SpawnHoldfails; (3)pool.Releaseresets the worktree back to idle; (4) the caller falls through topool.Acquire, which finds the same now-idle worktree and runs hooks a second time (to stdout/stderr). The user sees hook output twice with no intervening interaction. For idempotent hooks this is harmless; for venv-setup or dependency-install scripts that write state or emit errors on re-run, this produces confusing output or broken environments. The fallback path is presumably rare (herdr server unresponsive or binary spawn failure), but it is still a reachable code path.go test,make test,go build, andmkdir /tmp/...commands require user approval and cannot be auto-approved in this session. The tests could not be run, and no runtime evidence could be produced. The user should approvego testandmkdir /tmpcommands (or add them to.claude/settings.jsonpermissions) to enable automated test validation.internal/herdr/herdr.goreviewed - SpawnHold, buildStartArgs, parsePaneID, RoutingMessage, IsRuntime, Disabled, Available all present and correctly implementedinternal/herdr/herdr_test.goreviewed - 8 tests covering IsRuntime, Disabled, Available, buildStartArgs (x2), parsePaneID (9 cases), RoutingMessageMentionsSkill, SpawnHoldTimesOut (150ms stub with exec sleep 30)cmd/get.goreviewed - getRunE, useHerdrPane, getHerdrRunE, getLeaseRunE, holdAndReturn all present; lease-on-herdr-spawn, fallback-to-subshell, TREEHOUSE_NO_HERDR/--no-herdr opt-out, stderr-only for --lease stdout-path all confirmedskills/treehouse/SKILL.mdreviewed - herdr section present, /herdr skill cross-reference correct, no em dashesgo test ./internal/herdr/... -v- BLOCKED (permission system requires user approval for allgocommands)go test ./... -count=1- BLOCKED (permission system requires user approval)README.md:330- README.md Configuration section has no### herdrsubsection. The[herdr]config is documented only in the 'Using treehouse inside herdr' section above; readers who jump to Configuration miss theenabled,split, andfocusoptions entirely. Add a### herdrsubsection after### Hooks(before## Development) mirroring the hooks pattern: a one-paragraph intro noting user-level-only scope, the toml block, and a link to the herdr section.CLAUDE.md:56- CLAUDE.md herdr integration design-decision bullet omits theSpawnHold10s timeout, which was the key behavioral fix in this revision. The sentence after 'Any failure to spawn the pane releases the lease and falls back to the classic in-place subshell (errHerdrFallback), so behavior outside herdr is byte-for-byte unchanged.' should read: 'SpawnHoldis bounded by a 10s timeout (exec.CommandContext) so an unresponsive herdr server triggers the fallback instead of blockingtreehouse getforever (thespawnTimeoutvar is shrinkable in tests).'AGENTS.md:56- AGENTS.md carries the same herdr integration bullet as CLAUDE.md and has the same omission of theSpawnHold10s timeout. Apply the identical fix: insert theSpawnHoldtimeout sentence after 'behavior outside herdr is byte-for-byte unchanged.' on line 56.✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.