This file explains how the plugin is assembled and where to change each layer. It reflects the actual on-disk code layout — every path below is real.
The plugin has five main layers:
- config and defaults
- agent registration and prompt assembly
- skill tooling
- MCP and LSP wiring
- execution gates and hooks
User input
│
▼
┌─────────┐
│ Bob │ ← orchestrator / router (visible, primary)
└────┬────┘
│ routes by complexity
▼
┌──────────────────────────────────────────────────────┐
│ │
│ Simple / small (<5 todos, no parallelism) │
│ └─► build (hidden, deep/bounded implementation) │
│ └─► general (visible, quick/fallback executor) │
│ │
│ Planning / architecture │
│ └─► plan (visible, deep planning, read-only) │
│ │
│ Specialist tiers (always delegated, not routed): │
│ explore ◄── discovery (grep, firecrawl, context7) │
│ writer ◄── copy / SEO / messaging │
│ designer ◄── visual direction │
│ critic ◄── review gate (APPROVED/REJECTED) │
│ manager ◄── delegation orchestrator, memory │
│ vision ◄── browser operator, multimodal │
│ │
└──────────────────────────────────────────────────────┘
│
▼
┌─────────┐
│ Bob │ ← collects results, verifies, reports
└────┬────┘
│
▼
User response
Key wiring rules:
- OpenCode plugins are NOT MCP servers.
hiai-opencodeonly provides the OpenCode-side launch wiring for MCP servers through itsmcpconfig and helper launchers. - Model credentials go through OpenCode Connect, not
bob.json. - Service keys (e.g.
FIRECRAWL_API_KEY) are configured inbob.env(env file) or as shell variables. Use{env:VAR_NAME}placeholders inbob.json— never put raw API keys there.
- src/config.ts: config loading, defaults, env-file loading, validation
- src/index.ts: plugin entry — agent registration via
hooks.config, tool registration, hook wiring - src/agents/: agent factories, prompts (
bob.ts,build.ts,plan.ts,manager.ts,critic.ts,designer.ts,writer.ts,vision.ts,explore.ts,general.ts,index.ts) - src/prompt-library/: shared prompt fragments (browser, caveman, native-memory, postgres-rules, workspace, worktree)
- src/shared/: closure protocol, env-var resolution, event helpers
- src/permissions.ts: per-agent permission/tool-disable resolution
- src/hooks/: all runtime hooks (circuit breaker, quality gate, closure injector, legal gate, worktree lifecycle, etc.)
- src/features/: background-manager, completion-controller, dream-distill, mcp, shell-env, telemetry, workspace-adapter, worktree
- src/tools/: tool registrations (skill, lsp, session-manager, background-task, worktree, agent-browser, firecrawl, memory-tool)
- assets/cli/hiai-opencode.mjs: the
hiai-opencodeCLI (doctor, mcp-status, export-mcp, diagnose) - assets/runtime/: npm bootstrap helper for MCP/LSP tools
- skills/: packaged project skills
- config/: packaged sample OpenCode config and schema
bob(primary) — Orchestrator, router, entry pointplan(mode: all) — Principal Architect, deep planninggeneral(mode: all) — Cheap bounded executor, fallback
Hidden subagents
build— Senior Staff Engineer, implementation (deep/bounded)explore— discovery (firecrawl + grep_app + context7)critic— review gate (binary APPROVED/REJECTED)designer— UI/visual directionwriter— copy/positioning/SEOvision— browser operator, multimodalmanager— delegation orchestrator, memory stewarddream-consolidator— memory consolidation (auto-triggered)distill-packager— workflow packaging (auto-triggered)
Counts:
createAllAgents()returns 8 agent definitions; 4 more (explore,plan,build,general) are native-upgraded inline insrc/index.ts. Total registered: 12. The 10 user-facing model slots arebob, build, plan, manager, critic, designer, explore, writer, vision, general(validated byREQUIRED_AGENT_KEYSinsrc/config.ts).
There is no separate migration module. Legacy agent keys are mapped only in two places:
DEPRECATED_MODEL_KEYSin assets/cli/hiai-opencode.mjs (doctor diagnostics):coder→build,strategist→plan,researcher→explore,sub→general,guard→manager,brainstormer→writer.- The prompts reference agents by canonical runtime key (
explore,build,plan,general).
- src/agents/index.ts —
createAllAgents()registers 8 agents with visibility, mode, description, and prompts;resolveAgentModel/applyPromptOverridehelpers - src/index.ts —
hooks.configcallback native-upgradesexplore/plan/build/generaland merges all agents into OpenCode'scfg.agentdict; assembles MCP config - src/types.ts —
AgentConfig,BobConfig,CompletionConfig,WorktreeConfig,ClosureBlocktypes
User-facing model IDs live in one place:
- bob.json — the single config file. The plugin ships bundled defaults; a
bob.jsonin the project root or.opencode/overrides them.
The runtime loader is:
- src/config.ts —
loadConfig()readsbob.jsonfrom plugin root, project dir,.opencode/, and global config dir (first found wins);mergeConfig()applies defaults;REQUIRED_AGENT_KEYSvalidates the 10 slots.
Users configure the 10 primary agent model slots under models: bob, build, plan, manager, critic, designer, explore, writer, vision, general. Use fully qualified provider/model-id strings. Do not invent prefixes — run opencode models and copy exact IDs.
Prompting is layered. src/agents/ is the main authoring layer, but runtime prompts are assembled from several sources.
Each agent's prompt is a template literal assembled from imported fragments:
- Bob: src/agents/bob.ts
- Build (Coder): src/agents/build.ts
- Plan (Strategist): src/agents/plan.ts
- Manager: src/agents/manager.ts
- Critic: src/agents/critic.ts
- Designer: src/agents/designer.ts
- Writer: src/agents/writer.ts
- Vision: src/agents/vision.ts
- Explore: src/agents/explore.ts
- General: src/agents/general.ts
Reusable policy/behavior blocks imported by the agent prompts:
- src/prompt-library/browser.ts —
BROWSER_VIA_VISION(delegate browser to Vision) - src/prompt-library/caveman.ts — caveman protocol fragments (injected at runtime by
caveman-system-injector) - src/prompt-library/native-memory.ts — native memory/tasks tool reminders
- src/prompt-library/postgres-rules.ts — DB query rules
- src/prompt-library/workspace.ts —
getWorkspaceContext()(workspace/project type detection) - src/prompt-library/worktree.ts —
WORKTREE_AWARENESS
Prompt content appended/transformed after the source prompt is built:
- src/shared/closure.ts —
CLOSURE_SCHEMA_PROMPT+validateClosure() - src/hooks/closure-injector.ts — appends closure schema when missing
- src/hooks/caveman-system-injector.ts — resolves agent identity and injects caveman protocol fragments
- src/agents/index.ts —
applyPromptOverride()appliesprompt_appendfromagent_overrides
Final agent registration into OpenCode's cfg.agent dict happens in the hooks.config callback in src/index.ts. This is where model resolution, visibility, mode, temperature, thinking budgets, and per-agent permissions are applied.
- change bob.json when a model slot should change
- change src/config.ts (
DEFAULT_CONFIG) when defaults (permissions, mcp, lsp, completion, etc.) should change - change
src/agents/*.tswhen prompt content or behavior should change - change
src/prompt-library/*.tswhen a shared prompt fragment should change - change src/index.ts
hooks.configwhen agent registration, visibility, or model resolution should change
Skills are loaded by a single file:
- src/tools/skill.ts —
createSkillTool()walks the packagedskills/directory, indexes everySKILL.mdby both namespaced path and leaf name, and serves content via theskilltool.
Packaged skills live in skills/, organized by agent group (build/, designer/, plan/, explore/, bob/, critic/, general/, vision/, writer/).
Note: The
skill_discovery/skills.sources/skills.disableconfig documented in older versions is not implemented in the current codebase. Skills are discovered from the packagedskills/directory only. The skills dir is hardcoded relative todist/.
CLI skills (not MCP, invoked via skill("...") or shell):
firecrawl— web scraping (requiresFIRECRAWL_API_KEY)context7— on-demand library docs viaskill("explore/context7")agent-browser— browser automation via Chrome CDP (uses/agent-browserskill)
- src/features/mcp/registry.ts —
MCP_REGISTRYconstant (2 servers) +getMcpConfig() - src/features/mcp/auto-export.ts —
autoExportStaticMcp()writes.opencode/.mcp.jsonat startup
sequential-thinking— local, npx-backed reasoning servergrep_app— remote code-search endpoint (no key required)
Removed in v0.3.0: context7 (now on-demand CLI skill), stitch, mempalace.
- assets/runtime/npm-package-runner.mjs — shared
npx -y <pkg>bootstrap with isolated npm cache
The plugin auto-exports .opencode/.mcp.json at startup so hosts whose opencode mcp list only reads static config can see hiai-managed servers. Controlled by:
HIAI_OPENCODE_AUTO_EXPORT_MCP:if-missing(default) |always|offHIAI_OPENCODE_MCP_EXPORT_PATH: override output pathHIAI_OPENCODE_EXPORT_MCP_MODE:safe(default) |force(overwrite policy inalwaysmode)
Manual refresh: hiai-opencode export-mcp .opencode/.mcp.json
LSP defaults live in:
- src/config.ts —
DEFAULT_CONFIG.lsp
Current defaults: typescript, svelte, eslint, pyright.
LSP tool registrations: src/tools/lsp/ (lsp_diagnostics, lsp_goto_definition, lsp_find_references, lsp_symbols, lsp_prepare_rename, lsp_rename).
Server definitions: src/tools/lsp/server-definitions.ts.
The plugin enforces a layered safety model. From strongest to weakest:
- Legal gate — src/hooks/legal-gate.ts: three-tier deny list (browser automation always blocked, military/malicious always blocked, contextual dual-use with offensive-intent regex). Throws
BlockingHookError. - Per-agent permission maps — src/permissions.ts:
applyAgentPermissions()convertsagent_restrictionsintopermission.deny/tools.<key>=false. Applied insrc/index.tshooks.config. - Completion controller — src/features/completion-controller/: state machine that gates task completion.
decide()(decide.ts) requires: no blocker, no incomplete todos, quality gate passed, LSP diagnostics run (if edits made), and Critic approval (ifrequire_criticand changed files exist). - Circuit breaker — src/hooks/circuit-breaker.ts + src/features/background-manager/index.ts: feeds every
tool.execute.aftercall intoBackgroundManager.recordSessionToolCall(); trips on N consecutive identical calls (default 20) or total tool calls (default 4000), aborting the session viaclient.session.abort. - Agent-browser guard — src/tools/agent-browser/index.ts:
browserGateGuard()throws for non-vision/generalagents. - LSP sandbox — src/tools/lsp/index.ts: throws on paths resolving outside
ctx.directory.
- Quality gate — src/hooks/quality-gate.ts: scans bash output from quality commands (test/lint/typecheck); on failure sets
qualityGateFailedflag (blocks completion) and appends a directive. - Closure injector — src/hooks/closure-injector.ts: appends
CLOSURE_SCHEMA_PROMPTwhen missing; logs on invalid CLOSURE. - Non-interactive env — src/hooks/non-interactive-env.ts: rewrites interactive commands (
vim/less/ssh) to echo stubs. - Runtime fallback — src/hooks/runtime-fallback.ts: clamps
maxOutputTokens.
- src/shared/closure.ts — schema prompt +
validateClosure()(enforcesreadinessenum:done|accept|reject) - src/types.ts —
ClosureBlocktype
All agents must emit a <CLOSURE> block. The completion controller parses Critic verdicts from it.
Git worktree-based task isolation for parallel work.
- WorktreeManager — src/features/worktree/index.ts: create/list/remove/cleanup operations
- Tools — src/tools/worktree.ts:
hiai_worktree_create,hiai_worktree_remove,hiai_worktree_list,hiai_worktree_status - Lifecycle hooks — src/hooks/worktree-lifecycle.ts: auto-create on plan-start signals, auto-remove on
<CLOSURE> - Skill — skills/general/using-git-worktrees/
- Prompt integration — src/prompt-library/worktree.ts:
WORKTREE_AWARENESS
Gated on config.worktreeConfig?.enabled === true (default: enabled).
- OpenCode's native
Tasklifecycle owns background delegation and task output. - BackgroundManager — src/features/background-manager/index.ts: circuit-breaker state only (consecutive-identical and total-call limits).
- Hook wiring — src/hooks/circuit-breaker.ts: feeds
tool.execute.afterinto the circuit breaker.
The deprecated background_output / background_cancel tools and task-status CLI command were removed because they could not manage native OpenCode task IDs.
The hiai-opencode CLI is installed via the bin field in package.json:
Commands: doctor, mcp-status, export-mcp, diagnose, task-status. doctor/mcp-status exit non-zero on hard failures (usable as CI gate).
The root documentation set should stay small:
README.mdAGENTS.mdARCHITECTURE.mdLICENSE.md
Avoid adding more root docs unless they serve a genuinely new role.
When changing the plugin, keep these invariants:
- bob.json is the source of truth for user-facing runtime defaults and model IDs
- src/config.ts
DEFAULT_CONFIGis the loader for internal defaults — not a second model map - root docs should use canonical runtime names (
bob,build,plan,explore,general), not stale aliases - the CLI's
MCP_REGISTRYmust mirror src/features/mcp/registry.ts — keep them in sync - user-facing docs should describe visible agents first and hidden/system agents second
- third-party MCPs should follow upstream install/launch conventions whenever possible