From 648ddd695fa417e8260cef4db44ce0a738936149 Mon Sep 17 00:00:00 2001 From: Nik Divjak Date: Wed, 27 May 2026 15:03:06 +0200 Subject: [PATCH] feat(opencode/session): default-on hivemind loop primitive (#266 Phase 2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Flip OPENCODE_HIVEMIND_LOOP_ENABLED default false → true. Phase 1 validation proved the hook is reliably non-fatal (forkIn(scope) + Effect.ignore swallow every failure) and the auto-drive payoff is significant. Users who want to opt out can still set OPENCODE_HIVEMIND_LOOP_ENABLED=0 in the env. Pairs with hivemind-mcp PR adding a 3-turn grace window before 'missing-loop-goal' escalation, so peers that haven't called hivemind_set_loop_goal yet aren't immediately flagged. Refs: grunt-it/hivemind-mcp#266 --- GRUNTCODE.md | 2 +- packages/opencode/src/effect/runtime-flags.ts | 12 +++++++----- packages/opencode/src/session/hivemind-loop-hook.ts | 11 ++++++----- packages/opencode/src/session/processor.ts | 9 +++++---- 4 files changed, 19 insertions(+), 15 deletions(-) diff --git a/GRUNTCODE.md b/GRUNTCODE.md index 0e1d22ad5182..165b42483369 100644 --- a/GRUNTCODE.md +++ b/GRUNTCODE.md @@ -12,7 +12,7 @@ Current patches (each tracked in [hivemind #222](https://github.com/grunt-it/hiv 2. **Attach subscribes to all session message updates.** When a wake fires via `/session//prompt_async`, the attach-client TUI renders the response. (Without this patch, externally-triggered messages land in the server's db but never reach the user's screen.) 3. **`OPENCODE_SERVER_URL` propagation.** The attach binary reads and propagates this env var to all subprocesses (MCP children, etc.) so agents can find their serve daemon without a launch-script shim. 4. **`--peer-id ` flag.** Lets launch scripts set the tab's hivemind peer-id at startup instead of via TAKEOFF gymnastics. Powers patch #1. -5. **Turn-end hook into the hivemind loop primitive ([#266](https://github.com/grunt-it/hivemind-mcp/issues/266) Phase 1).** Behind feature flag `OPENCODE_HIVEMIND_LOOP_ENABLED=1`. Every step-finish fires `hivemind_record_turn_end` into the hivemind MCP, which evaluates the three-layer goal-loop contract + decides to auto-wake the same peer (`continue`), wake the parent peer (`escalate` / `await-review`), or no-op. Per-tool-call `hivemind_loop_progress` bumps the progress timestamp. Fire-and-forget via `Effect.forkIn(scope)` — hook failures NEVER break the TUI. Opt-in per-tab via env var in Phase 1; planned default-on in Phase 2 after validating in the wild. +5. **Turn-end hook into the hivemind loop primitive ([#266](https://github.com/grunt-it/hivemind-mcp/issues/266) Phase 2 — default-on).** Every step-finish fires `hivemind_record_turn_end` into the hivemind MCP, which evaluates the three-layer goal-loop contract + decides to auto-wake the same peer (`continue`), wake the parent peer (`escalate` / `await-review`), or no-op. Per-tool-call `hivemind_loop_progress` bumps the progress timestamp. Fire-and-forget via `Effect.forkIn(scope)` — hook failures NEVER break the TUI. ON by default as of Phase 2; opt out for a specific tab/session via `OPENCODE_HIVEMIND_LOOP_ENABLED=0`. Pairs with a 3-turn grace window in `hivemind-mcp` (#266 Phase 2) — peers have 3 turns to call `hivemind_set_loop_goal` before the MCP escalates with `violationKind=missing-loop-goal`. Everything else is opencode upstream. We rebase against `anomalyco/opencode:dev` weekly. diff --git a/packages/opencode/src/effect/runtime-flags.ts b/packages/opencode/src/effect/runtime-flags.ts index 8aff4ad67d8c..b76cf8ec8c3b 100644 --- a/packages/opencode/src/effect/runtime-flags.ts +++ b/packages/opencode/src/effect/runtime-flags.ts @@ -55,14 +55,16 @@ export class Service extends ConfigService.Service()("@opencode/Runtime bashDefaultTimeoutMs: positiveInteger("OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS"), experimentalNativeLlm: bool("OPENCODE_EXPERIMENTAL_NATIVE_LLM"), /** - * Loop primitive (#266 Phase 1) — when true, the session processor fires + * Loop primitive (#266 Phase 2 — default-on) — when true, the session processor fires * hivemind_record_turn_end after every step-finish + hivemind_loop_progress after every * tool-result. The MCP then decides whether to auto-wake the same peer (continue), wake - * the parent (escalate), or no-op. Default off in Phase 1; opt-in per-tab via env var; - * planned default-on in Phase 2 after validating in the wild. Failure of the hook is - * always non-fatal — the TUI is never blocked or thrown into on a hivemind error. + * the parent (escalate / await-review), or no-op. Default ON as of Phase 2 — Phase 1 + * proved the hook is reliably non-fatal (forkIn(scope) + interrupted-error swallow) and + * the auto-drive payoff is significant. To opt OUT for a specific tab/session, set + * `OPENCODE_HIVEMIND_LOOP_ENABLED=0` in the env. Failure of the hook is always non-fatal — + * the TUI is never blocked or thrown into on a hivemind error. */ - hivemindLoopEnabled: bool("OPENCODE_HIVEMIND_LOOP_ENABLED"), + hivemindLoopEnabled: Config.boolean("OPENCODE_HIVEMIND_LOOP_ENABLED").pipe(Config.withDefault(true)), client: Config.string("OPENCODE_CLIENT").pipe(Config.withDefault("cli")), }) {} diff --git a/packages/opencode/src/session/hivemind-loop-hook.ts b/packages/opencode/src/session/hivemind-loop-hook.ts index 68e63ffadcef..4184043e8103 100644 --- a/packages/opencode/src/session/hivemind-loop-hook.ts +++ b/packages/opencode/src/session/hivemind-loop-hook.ts @@ -19,9 +19,10 @@ type MCPShape = MCP.Interface * pipes through Effect.ignore + Effect.forkIn(scope) so the TUI never blocks on the MCP * call and never sees an exception from a slow / failing hivemind connection. * - * Feature-flagged: opt-in via OPENCODE_HIVEMIND_LOOP_ENABLED=1 (RuntimeFlags.hivemindLoopEnabled). - * Off by default in Phase 1 so users can adopt per-tab + we can flip the global default in - * Phase 2 after validating in the wild. + * Feature-flagged via OPENCODE_HIVEMIND_LOOP_ENABLED (RuntimeFlags.hivemindLoopEnabled). + * DEFAULT-ON as of Phase 2 — Phase 1 validation proved the hook is reliably non-fatal + + * the auto-drive payoff is significant. To opt out for a specific tab/session, set + * OPENCODE_HIVEMIND_LOOP_ENABLED=0 in the env. */ const log = Log.create({ service: "session.hivemind-loop-hook" }) @@ -90,7 +91,7 @@ function buildTurnSummary(messageId: MessageV2.Assistant["id"]) { * The MCP wraps evaluateLoop + fires the wake/escalation internally. This side just sends * the turn-end event and forgets. * - * Off when OPENCODE_HIVEMIND_LOOP_ENABLED is unset/false — silent no-op. + * Silent no-op when OPENCODE_HIVEMIND_LOOP_ENABLED=0 (Phase 2 default is ON). */ export const recordTurnEnd = Effect.fn("HivemindLoopHook.recordTurnEnd")(function* (input: { enabled: boolean @@ -136,7 +137,7 @@ export const recordTurnEnd = Effect.fn("HivemindLoopHook.recordTurnEnd")(functio * Fire hivemind_loop_progress after a tool-result. Cheap call — just bumps * last_loop_progress_at so evaluate_loop sees fresh activity. Same fire-and-forget guarantee. * - * Off when OPENCODE_HIVEMIND_LOOP_ENABLED is unset/false — silent no-op. + * Silent no-op when OPENCODE_HIVEMIND_LOOP_ENABLED=0 (Phase 2 default is ON). */ export const loopProgress = Effect.fn("HivemindLoopHook.loopProgress")(function* (input: { enabled: boolean diff --git a/packages/opencode/src/session/processor.ts b/packages/opencode/src/session/processor.ts index e30d1a52c22e..2c44b1fe7ee5 100644 --- a/packages/opencode/src/session/processor.ts +++ b/packages/opencode/src/session/processor.ts @@ -623,10 +623,11 @@ export const layer = Layer.effect( messageID: ctx.assistantMessage.parentID, }) .pipe(Effect.ignore, Effect.forkIn(scope)) - // Hivemind loop primitive (#266 Phase 1): fire the turn-end event into hivemind-mcp. - // The MCP runs evaluateLoop + decides auto-wake / auto-escalate / no-op. Off by - // default; opt-in via OPENCODE_HIVEMIND_LOOP_ENABLED. Always fire-and-forget — - // hivemind failure must never break the TUI. + // Hivemind loop primitive (#266 Phase 2 — default-on): fire the turn-end event into + // hivemind-mcp. The MCP runs evaluateLoop + decides auto-wake / auto-escalate / + // await-review / no-op. ON by default; opt out per tab via + // OPENCODE_HIVEMIND_LOOP_ENABLED=0. Always fire-and-forget — hivemind failure must + // never break the TUI. yield* HivemindLoopHook.recordTurnEnd({ enabled: flags.hivemindLoopEnabled, mcp: mcpOption,