OpenCode plugin • Server + TUI

Background goals that loop.

opencode-loopd is a Codex-inspired goal engine for OpenCode. Create a goal with /goal, watch an autonomous child session loop in the background, schedule it to requeue on an interval without manual re-prompt, and monitor everything from a modal TUI dashboard — without blocking the parent chat.

⬢ AGPL-3.0 ⚡ bun ≥1.1.0 ◈ opencode ≥1.4 ☆ like Claude Code loop

Install in 30s

Add to both configs, restart OpenCode:

// ~/.config/opencode/opencode.jsonc
{ "plugin": ["@bojackduy/opencode-loopd"] }

// ~/.config/opencode/tui.json
{ "plugin": ["@bojackduy/opencode-loopd"] }

Or one-line installer (adds /goal + skill):

npx -y @bojackduy/opencode-loopd@latest

Dashboard: /loop or <leader>o • Shift+B bug report

opencode — Loop Dashboard • NORMAL • 0 goals • ○ 0 RUNNING • 7 done
Loop Dashboard modal TUI inside OpenCode terminal — showing NORMAL mode, goal list, and vivid status colors

Modal dashboard (/loop / <leader>o): vivid per-status coloring, running spinner, NORMAL vs INSERT — keys trapped, no chat leak.

⚡ Engine-driven

Idle + lease + 30s polling re-prompts the child with accumulated steering (progress + transcript tail + inbox). Not a parent re-prompt machine.

⏱️ Scheduled

scheduleEveryMs auto-requeues the same goal on an interval (1.7) — 5s poll, skip-if-running, workspaceWrite gate, inbox Scheduled tick N/M.

👁️ Parent ↔ child

Owner tools inspect/steer; inbox injects into next turn. Wake-up on complete/block without polling.

🛡️ Safe

Per-goal artifact isolation .opencode/loopd/goals/<id>/, maxTurns force-finish → semantic summary → parent.

🖥️ Command sessions

Installs, builds, dev servers & REPLs as background processes with a fullscreen terminal — owner exit notifications, filter/until watches, timeouts. Never block the turn in bash.

🧠 Switch models mid-run

Provider hits quota? switch_goal_model moves the goal to another model on the same worker — or configure ordered fallbackModels for automatic 429 failover.

✋ Manual mode

interactive: true stops the engine from starting turns — every turn comes from your :send. For workers that wait for input.

Architecture — how the loop runs

                        ┌─────────────────────────┐
                        │  Main session (parent)  │
                        │  owner tools            │
                        │  inspect / steer / pause│
                        └────────────┬────────────┘
                                     │  inbox → child
                                     │  wake-up ← engine
             ┌───────────────────────┼───────────────────────┐
             ▼                       ▼                       ▼
        ┌─────────┐           ┌──────────┐            ┌─────────┐
        │  Goal A │           │  Goal B  │            │  Goal C │
        │ active  │           │ blocked  │            │  paused │
        └────┬────┘           └────┬─────┘            └────┬────┘
             │                     │                       │
             └─────────────────────┼───────────────────────┘
                                   │
                     ┌─────────────┴─────────────┐
                     ▼                           ▼
              Loopd engine  ──────────►   Child worker (subagent)
              (plugin server)  steering   get_goal / report / complete
                 │  lease / retry / polling       │  block / question
                 │  force-finish / notify         ▼
                 │                         .opencode/loopd/goals/<id>/
                 │                           progress.md / artifacts
                 └─────────────────────────────────┘

Each goal is a GoalID — the loop identity. The dashboard is a modal dialog that pushes loopd.dashboard onto the keymap so the chat prompt never sees your keys. Artifacts are namespaced per-goal; if your objective says save to ./reports/, that wins. 1.7 adds a 5s schedule worker that requeues complete scheduled goals on interval with skip-if-running.

Quick start — background goals in OpenCode

/goal fetch the latest AI news and save 10 items to ai-news.md
# agent clarifies → loopd_create_goal → child loops every ~30s
/loop                          # or <leader>o
j/k  move   o open child   :send hello   :force done   ? help   q close
Shift+B  → prefilled GitHub bug report (like lazyjira / lazyconfluence)

# Scheduled — one goal, auto-requeues on interval (1.7)
loopd_create_goal({
  name: "hourly-report",
  objective: "Append date -u to tick.txt each run",
  scheduleEveryMs: 3600000, // 1h
  scheduleMaxRuns: 24
})

The dashboard keybinding is <leader>o (also registered in the command palette as "Loop Dashboard" / /loop). Scheduled goals show scheduleRunCount/nextRunAt in inspect.

Why loopd vs. plain loop?

Plain /loop injects prompts into the main session — it pollutes the transcript. Codex goals keep history per-thread and steer via internal context. Loopd does the same for OpenCode: one child session per goal, continuation steering with progress history + transcript tail, and an explicit artifact namespace. The parent stays clean.

opencode-loopd

Child session per goal, no transcript pollution, artifact isolation, scheduled requeue, parent wake-up injection.

Plain /loop

Single session, prompt injected into main chat, manual polling to see progress, no interval.

Codex / Claude Code

Inspiration: Codex goals keep per-thread history and steer via internal context — loopd adds scheduled intervals for repetitive dialogues.

FAQ — SEO for OpenCode loop & background agents

How to run background agents in OpenCode?

Install @bojackduy/opencode-loopd as a plugin (both opencode.jsonc and tui.json), restart, then /goal in any session. The engine spawns a child worker that loops autonomously; you monitor with /loop.

Is this like Claude Code's loop or Codex goals?

Yes — same mental model: engine-driven idle → continuation steering with accumulated context → child calls complete_goal/block_goal → parent wake-up. Like Codex, but native to OpenCode's session model.

Where do loop outputs go?

Per-goal: .opencode/loopd/goals/<id>/ (e.g. progress.md). If your objective names ./reports/, that wins.

How to schedule repetitive tasks?

Use scheduleEveryMs (1.7) — e.g. scheduleEveryMs: 60000 for 1m interval, scheduleMaxRuns: 10 for 10 total. The 5s schedule worker resurrects complete → active with Scheduled tick N/M inbox; `workspaceWrite:true` defers if another writer is active.

How to report a bug?

In the dashboard press Shift+B or :bug — opens github.com/bojackduy/opencode-loopd/issues/new prefilled with environment (version, OS, goal context).

What if my provider hits quota mid-run?

Run loopd_list_models to see what's connected, then switch_goal_model to move the goal to another model — same worker, transcript and progress kept. Or set fallbackModels at creation for automatic ordered failover on 429s. Dashboard: :model / :models.

Can I run dev servers and long installs without blocking?

Yes — use command sessions (loopd_command_start) instead of the blocking shell: installs, builds, dev servers and REPLs run in the background with a fullscreen terminal, owner exit notifications, and until pattern watches that wake you when output appears.