feat(grunt/tui): turn-end hook → hivemind loop primitive (#266 Phase 1)#21
Conversation
…alyco#266 Phase 1) 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)
|
Hey! Your PR title Please update it to start with one of:
Where See CONTRIBUTING.md for details. |
|
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. |
|
Coord review — APPROVE + merging via admin-bypass. PR #21 is the keystone: gruntcode turn-end hook → hivemind_record_turn_end → MCP evaluateLoop fires wake to continue/escalate/await-review. With #18 + #20 already merged, this completes the event-driven loop end-to-end. Validated per tab-4's READY-FOR-REVIEW DM anomalyco#138:
CI checks pending (nix-eval, unit, e2e, typecheck on Linux/Windows) but check-standards + check-compliance + add-contributor-label all SUCCESS. Code is isolated to session/processor + new hook file. Admin-bypass merge safe — if there's a regression, the anomalyco#258 install-local smoke-test (already shipped) catches it pre-install. Merging this completes the Phase 1 chain: After merge: a tab launched with OPENCODE_HIVEMIND_LOOP_ENABLED=1 + calling hivemind_set_loop_goal on takeoff auto-drives its loop without Nik typing. Phase 2 (default-on + AGENTS.md TAKEOFF enforcement) is next, separate PR. Merging. |
…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)
Hivemind anomalyco#266 Phase 1 — gruntcode side
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; every successful tool-result fireshivemind_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.Architecture
Feature flag
Opt-in per-tab via
OPENCODE_HIVEMIND_LOOP_ENABLED=1. Default off in Phase 1; planned default-on in Phase 2 after validating in the wild.When unset: both hooks are silent no-ops. The processor still yields
MCP.Serviceas an Option (Effect.serviceOption) but never calls into it.Files
packages/opencode/src/session/hivemind-loop-hook.tsHivemindLoopHook.recordTurnEnd+HivemindLoopHook.loopProgress. Both takeOption<MCP.Service>so tests don't have to provide MCP just to satisfy a single-hook env requirement.findHivemindClientfast-paths the conventionalhivemindclient name; slow-paths a scan oftools()by tool-name suffix for users who renamed it.buildTurnSummarydeferred until AFTER client resolution.packages/opencode/src/effect/runtime-flags.tshivemindLoopEnabledflag (OPENCODE_HIVEMIND_LOOP_ENABLED).packages/opencode/src/session/processor.tsMCP.ServiceviaEffect.serviceOption(doesn't leak throughHandle.process's never-environment signature).step-finishcase firesrecordTurnEnd.tool-resultcase firesloopProgress.packages/opencode/test/session/hivemind-loop-hook.test.tsGRUNTCODE.mdHard correctness rule: hook MUST NEVER break the TUI
Both hooks pipe through:
So even if hivemind-mcp is unreachable / slow / errors / panics, the TUI keeps working. The loop primitive's whole point is to PREVENT failures, not cause them.
Testing
bun typecheckfrompackages/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.The 5 new tests cover gating paths:
clients()) → silent no-op (findHivemindClient miss path)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.
How to enable per-tab
# Single tab opt-in: OPENCODE_HIVEMIND_LOOP_ENABLED=1 gruntcode --peer-id nik-tab-N --project /path/to/repoOr set in shell rc to enable for every gruntcode launch from that shell.
Worker MUST also call
hivemind_set_loop_goalon takeoff for the loop to drive it (the hook just emits events; the MCP's evaluateLoop noops with a warn if noloop_goalis set on the peer). Once anomalyco#266 Phase 2 lands, takeoff-AGENTS.md will require it.Refs
Out of scope
/orch/tab-turn-endfor the per-tab CoordinatorJob DO) — depends on write tool sometimes has a "request was aborted" result anomalyco/opencode#268 hivemind worker stand-up first🤖 generated with Claude Code via nik-tab-4 (opencode worker peer)