Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion GRUNTCODE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/<id>/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 <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.

Expand Down
12 changes: 7 additions & 5 deletions packages/opencode/src/effect/runtime-flags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,14 +55,16 @@ export class Service extends ConfigService.Service<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")),
}) {}

Expand Down
11 changes: 6 additions & 5 deletions packages/opencode/src/session/hivemind-loop-hook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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" })
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
9 changes: 5 additions & 4 deletions packages/opencode/src/session/processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Loading