This repository was archived by the owner on Aug 17, 2026. It is now read-only.
Repository navigation
feat(grunt/tui): bare gruntcode auto-spawns serve + attaches (one-command wakeable TUI) - #5
Merged
Merged
Conversation
…ommand wakeable TUI) ## Goal Bare `gruntcode` (no subcommand) should Just Work — the launched TUI is automatically wakeable. No `serve` + `attach` two-command dance, no shell wrapper. Nik framing 2026-05-26: "like i run gruntcode and things can get woken up via hivemind". ## Behavior of bare `gruntcode` now 1. Probes http://127.0.0.1:4096/session — if a daemon is already serving there, attach to it. 2. If no daemon: spawns one as a detached child (`gruntcode serve --port 4096 --hostname 127.0.0.1 --print-logs`), waits up to 10s for readiness. 3. Re-execs into `gruntcode attach <url>` with all the relevant args passed through (--peer-id, --dir, --continue, --session, --fork). The user sees an unchanged TUI; implementation detail (the daemon) is hidden behind the bare command. 4. Daemon survives the TUI exit — second bare `gruntcode` invocation reuses it. ## Escape hatches - `OPENCODE_DISABLE_AUTO_SERVE=1` skips the auto-serve and uses the legacy in-process worker (the path used today by upstream opencode). - `OPENCODE_AUTO_SERVE_PORT` / `OPENCODE_AUTO_SERVE_HOST` override the defaults (4096 / 127.0.0.1). ## Implementation - New `packages/opencode/src/cli/cmd/tui/auto-serve.ts` exports `ensureServeDaemon()`. Returns `{ok: true, url, bin}` or `{ok: false, reason}`. Logic: - Resolves gruntcode binary path (argv0 if compiled binary; `which gruntcode` if running via `bun run` in dev mode; fallback to `which opencode`). - Probes /session with 1.5s timeout. - If 200 → returns the URL. - If connection refused → spawns daemon as detached child, polls /session every 200ms until ready or 10s timeout. - `packages/opencode/src/cli/cmd/tui/thread.ts` handler calls `ensureServeDaemon()` early. On success: builds attach argv from current args and re-execs via child_process.spawn with stdio inherited. On failure: logs the reason and falls through to the existing in-process worker path. ## Verification End-to-end manual test: 1. Killed existing daemon. Confirmed `curl :4096/session` returns connection refused. 2. Launched bare TUI from patched source: `bun run src/index.ts --peer-id nik-test --project /tmp`. 3. Process tree showed: bun run → child `gruntcode serve --port 4096 ...` AND child `gruntcode attach http://127.0.0.1:4096 --dir /tmp --peer-id nik-test`. 4. `curl :4096/session` returned 200 with the active session. 5. `curl -X POST :4096/session/<id>/prompt_async` accepted (204) and the daemon log showed the LLM running. The wake response 'BARE-CMD-WORKS' was written to opencode.db (verified via `/session/<id>/message`). ## Known follow-up The wake response sometimes doesn't render visually in the TUI even though it's persisted server-side. This is a separate refinement of patch 2c (SSE subscription) that needs investigation — the wake is audit-complete and the system works for autonomous coordination, but human-watcher UX needs polish. Tracked in hivemind anomalyco#221. Refs hivemind anomalyco#224.
|
This PR doesn't fully meet our contributing guidelines and PR template. What needs to be fixed:
Please edit this PR description to address the above within 2 hours, or it will be automatically closed. If you believe this was flagged incorrectly, please let a maintainer know. |
|
Hey! Your PR title Please update it to start with one of:
Where See CONTRIBUTING.md for details. |
terrxo
added a commit
that referenced
this pull request
May 27, 2026
…alyco#266 Phase 1) (#21) Bridges gruntcode's per-turn lifecycle to the hivemind-mcp loop primitive shipped in hivemind-mcp v0.8.2 (PR #18) + extended in v0.8.3 (PR #20 await-review). Every step-finish fires hivemind_record_turn_end into the MCP; every successful tool-result fires hivemind_loop_progress. The MCP decides whether to auto-wake the same peer (continue), wake the parent (escalate / await-review), or no-op. Workers self-drive toward goals WITHOUT Nik typing. This is Phase 1 of the loop primitive program (anomalyco#266). Phase 0 (schema + tools + decision logic) lives in hivemind-mcp; this PR wires the gruntcode side that emits the events. Implementation: - packages/opencode/src/session/hivemind-loop-hook.ts (new): - HivemindLoopHook.recordTurnEnd(...) — called from step-finish handler - HivemindLoopHook.loopProgress(...) — called from tool-result handler - Both take MCP.Service as Option (Effect.serviceOption) so the processor degrades cleanly when MCP isn't in context (tests don't have to provide MCP just to satisfy a single hook env requirement) - Both fire-and-forget via Effect.forkIn(scope) + Effect.ignore — the hook MUST NEVER block or throw into the TUI (loop primitive correctness rule; the loop is supposed to PREVENT failures, not cause them) - findHivemindClient: fast-path matches client named 'hivemind'; slow- path scans tools() map for the tool-name suffix (handles users who renamed their MCP under a non-conventional key) - buildTurnSummary deferred until AFTER client resolution to skip the Database.use read when we're no-opping anyway - packages/opencode/src/effect/runtime-flags.ts: - New hivemindLoopEnabled flag (OPENCODE_HIVEMIND_LOOP_ENABLED env var, default false). Opt-in per-tab in Phase 1; planned default-on in Phase 2 after validating in the wild. - packages/opencode/src/session/processor.ts: - Yield MCP.Service via Effect.serviceOption (doesn't leak through Handle.process's never-environment signature; processor still has MCP at runtime via prompt.ts's layer wiring) - case 'step-finish' (after value.reason captured, after summary fork): fires HivemindLoopHook.recordTurnEnd - case 'tool-result' (after completeToolCall): fires HivemindLoopHook.loopProgress - packages/opencode/test/session/hivemind-loop-hook.test.ts (new): - 5 unit tests covering the gating paths: * flag off + MCP=None → silent no-op * flag on + MCP=None → silent no-op (no error) * flag off + (loopProgress) → silent no-op * flag on + MCP=None + (loopProgress) → silent no-op * flag on + MCP=Some (stub w/ empty clients()) → silent no-op (the findHivemindClient miss path) - GRUNTCODE.md: - Documented as patch #5 with the feature flag + behavior. Testing: - bun typecheck (from packages/opencode): green. - bun test test/session/ : 357 pass / 5 skip / 1 todo / 0 fail (was 352 pre-hook; +5 new hook tests). No regressions across the session module (processor-effect.test.ts + compaction.test.ts + all peers in /session still green). - The wake fire-path is exercised end-to-end by the production loop primitive once Phase 1 is enabled per-tab; that's better validated by live use than by mocked MCP calls in unit tests. Refs: - hivemind anomalyco#266 (parent — Phase 1) - hivemind-mcp PR #18 (Phase 0 — schema + 6 tools) - hivemind-mcp PR #20 (await-review decision branch — receiver-side already live on hivemind-mcp main as of 65a1020) - AGENTS.md + hivemind-peers.md three-layer goal-loop contract (the behavioral rules this PR mechanically enforces)
terrxo
added a commit
that referenced
this pull request
May 28, 2026
…ommand wakeable TUI) (#5) ## Goal Bare `gruntcode` (no subcommand) should Just Work — the launched TUI is automatically wakeable. No `serve` + `attach` two-command dance, no shell wrapper. Nik framing 2026-05-26: "like i run gruntcode and things can get woken up via hivemind". ## Behavior of bare `gruntcode` now 1. Probes http://127.0.0.1:4096/session — if a daemon is already serving there, attach to it. 2. If no daemon: spawns one as a detached child (`gruntcode serve --port 4096 --hostname 127.0.0.1 --print-logs`), waits up to 10s for readiness. 3. Re-execs into `gruntcode attach <url>` with all the relevant args passed through (--peer-id, --dir, --continue, --session, --fork). The user sees an unchanged TUI; implementation detail (the daemon) is hidden behind the bare command. 4. Daemon survives the TUI exit — second bare `gruntcode` invocation reuses it. ## Escape hatches - `OPENCODE_DISABLE_AUTO_SERVE=1` skips the auto-serve and uses the legacy in-process worker (the path used today by upstream opencode). - `OPENCODE_AUTO_SERVE_PORT` / `OPENCODE_AUTO_SERVE_HOST` override the defaults (4096 / 127.0.0.1). ## Implementation - New `packages/opencode/src/cli/cmd/tui/auto-serve.ts` exports `ensureServeDaemon()`. Returns `{ok: true, url, bin}` or `{ok: false, reason}`. Logic: - Resolves gruntcode binary path (argv0 if compiled binary; `which gruntcode` if running via `bun run` in dev mode; fallback to `which opencode`). - Probes /session with 1.5s timeout. - If 200 → returns the URL. - If connection refused → spawns daemon as detached child, polls /session every 200ms until ready or 10s timeout. - `packages/opencode/src/cli/cmd/tui/thread.ts` handler calls `ensureServeDaemon()` early. On success: builds attach argv from current args and re-execs via child_process.spawn with stdio inherited. On failure: logs the reason and falls through to the existing in-process worker path. ## Verification End-to-end manual test: 1. Killed existing daemon. Confirmed `curl :4096/session` returns connection refused. 2. Launched bare TUI from patched source: `bun run src/index.ts --peer-id nik-test --project /tmp`. 3. Process tree showed: bun run → child `gruntcode serve --port 4096 ...` AND child `gruntcode attach http://127.0.0.1:4096 --dir /tmp --peer-id nik-test`. 4. `curl :4096/session` returned 200 with the active session. 5. `curl -X POST :4096/session/<id>/prompt_async` accepted (204) and the daemon log showed the LLM running. The wake response 'BARE-CMD-WORKS' was written to opencode.db (verified via `/session/<id>/message`). ## Known follow-up The wake response sometimes doesn't render visually in the TUI even though it's persisted server-side. This is a separate refinement of patch 2c (SSE subscription) that needs investigation — the wake is audit-complete and the system works for autonomous coordination, but human-watcher UX needs polish. Tracked in hivemind anomalyco#221. Refs hivemind anomalyco#224.
terrxo
added a commit
that referenced
this pull request
May 28, 2026
…alyco#266 Phase 1) (#21) Bridges gruntcode's per-turn lifecycle to the hivemind-mcp loop primitive shipped in hivemind-mcp v0.8.2 (PR #18) + extended in v0.8.3 (PR #20 await-review). Every step-finish fires hivemind_record_turn_end into the MCP; every successful tool-result fires hivemind_loop_progress. The MCP decides whether to auto-wake the same peer (continue), wake the parent (escalate / await-review), or no-op. Workers self-drive toward goals WITHOUT Nik typing. This is Phase 1 of the loop primitive program (anomalyco#266). Phase 0 (schema + tools + decision logic) lives in hivemind-mcp; this PR wires the gruntcode side that emits the events. Implementation: - packages/opencode/src/session/hivemind-loop-hook.ts (new): - HivemindLoopHook.recordTurnEnd(...) — called from step-finish handler - HivemindLoopHook.loopProgress(...) — called from tool-result handler - Both take MCP.Service as Option (Effect.serviceOption) so the processor degrades cleanly when MCP isn't in context (tests don't have to provide MCP just to satisfy a single hook env requirement) - Both fire-and-forget via Effect.forkIn(scope) + Effect.ignore — the hook MUST NEVER block or throw into the TUI (loop primitive correctness rule; the loop is supposed to PREVENT failures, not cause them) - findHivemindClient: fast-path matches client named 'hivemind'; slow- path scans tools() map for the tool-name suffix (handles users who renamed their MCP under a non-conventional key) - buildTurnSummary deferred until AFTER client resolution to skip the Database.use read when we're no-opping anyway - packages/opencode/src/effect/runtime-flags.ts: - New hivemindLoopEnabled flag (OPENCODE_HIVEMIND_LOOP_ENABLED env var, default false). Opt-in per-tab in Phase 1; planned default-on in Phase 2 after validating in the wild. - packages/opencode/src/session/processor.ts: - Yield MCP.Service via Effect.serviceOption (doesn't leak through Handle.process's never-environment signature; processor still has MCP at runtime via prompt.ts's layer wiring) - case 'step-finish' (after value.reason captured, after summary fork): fires HivemindLoopHook.recordTurnEnd - case 'tool-result' (after completeToolCall): fires HivemindLoopHook.loopProgress - packages/opencode/test/session/hivemind-loop-hook.test.ts (new): - 5 unit tests covering the gating paths: * flag off + MCP=None → silent no-op * flag on + MCP=None → silent no-op (no error) * flag off + (loopProgress) → silent no-op * flag on + MCP=None + (loopProgress) → silent no-op * flag on + MCP=Some (stub w/ empty clients()) → silent no-op (the findHivemindClient miss path) - GRUNTCODE.md: - Documented as patch #5 with the feature flag + behavior. Testing: - bun typecheck (from packages/opencode): green. - bun test test/session/ : 357 pass / 5 skip / 1 todo / 0 fail (was 352 pre-hook; +5 new hook tests). No regressions across the session module (processor-effect.test.ts + compaction.test.ts + all peers in /session still green). - The wake fire-path is exercised end-to-end by the production loop primitive once Phase 1 is enabled per-tab; that's better validated by live use than by mocked MCP calls in unit tests. Refs: - hivemind anomalyco#266 (parent — Phase 1) - hivemind-mcp PR #18 (Phase 0 — schema + 6 tools) - hivemind-mcp PR #20 (await-review decision branch — receiver-side already live on hivemind-mcp main as of 65a1020) - AGENTS.md + hivemind-peers.md three-layer goal-loop contract (the behavioral rules this PR mechanically enforces)
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 subscribe to this conversation on GitHub.
Already have an account?
Sign in.
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.
Closes hivemind anomalyco#224.
What
Bare
gruntcodenow just works as a wakeable TUI. Noserve+attachdance.Behind the scenes: probes :4096 for an existing daemon, spawns one if missing, re-execs into
gruntcode attach.Implementation
packages/opencode/src/cli/cmd/tui/auto-serve.ts—ensureServeDaemon()returns{ok: true, url, bin}or{ok: false, reason}thread.tshandler runsensureServeDaemon()early; on success re-execs into attach with same args; on failure falls through to in-process worker (preserves upstream behavior)Escape hatches
OPENCODE_DISABLE_AUTO_SERVE=1keeps the legacy in-process worker pathOPENCODE_AUTO_SERVE_PORT/OPENCODE_AUTO_SERVE_HOSToverride defaultsVerified end-to-end
bun run→ childgruntcode serveAND childgruntcode attachcurl -X POST :4096/session/<id>/prompt_asyncaccepted, daemon LLM ran, response "BARE-CMD-WORKS" written to opencode.dbKnown follow-up
The wake response doesn't always render visually in the TUI yet (the message lands in the daemon's DB but the SSE subscription path established by patch 2c needs more work for newly-created sessions). Tracking in hivemind anomalyco#221 — the wake itself works for autonomous coordination, just the human-watcher UX needs polish.
bun typecheckclean.