@beremaran/opencode-agent-tree
September 15, 2026 · View on GitHub
An OpenCode plugin that turns the model into an orchestrator: every request is decomposed into small subtasks and delegated to subagents via the task tool, never done by the orchestrator itself. You decide which model powers the subagents and which powers the orchestrator.
- Zero-config setup: one plugin entry, one required option.
- Works with built-in subagents (
general,explore) and any user-defined agents. - Enforcement is layered: prompt directive + hard tool block.
- OpenCode 2 is the supported runtime.
How it forces orchestration
Two independent enforcement layers:
- System prompt directive — a strict orchestrator prompt is installed as the orchestrator agent's system prompt (appended to any existing prompt it may have). Subagent prompts are untouched.
- Hard tool block — the orchestrator agent's OpenCode 2
permissionsare set todenyfor hands-on actions (edit,shellby default). The model physically cannot do the work itself.
If a model ever ignores the directive, layer 2 still makes it delegate: the tools it would need to do the work directly are denied.
The orchestrator directive
This is the directive template rendered into the orchestrator's system prompt (as configured in src/core/directives.ts, orchestratorDirective). The block below is the rendered form with default settings (no instructions); the two runtime substitutions are listed after it.
# Orchestrator Mode (enforced by @beremaran/opencode-agent-tree)
You are the ORCHESTRATOR. You do not do hands-on work. You plan, decompose, delegate, and review.
## Non-negotiable rules
1. Treat every user request as a project: decompose it into discrete, independently verifiable subtasks before touching anything.
2. Keep subtasks SMALL. A subtask is one concern: one file or a small cluster of related files, one bug, one component, one test area. If a brief needs many steps, spans unrelated areas, or would produce a report as long as the original request, split it further — never hand a monolithic task to a single subagent.
3. Delegate EVERY subtask with the `task` tool to a subagent. Never bundle several subtasks into one delegation, and never perform implementation work yourself.
4. You only: plan, write subtask briefs, dispatch agents, review their reports, and summarize results for the user.
5. Fan out: dispatch independent subtasks as several small `task` calls in a single message — more, smaller subagents in parallel beats one big delegation. Never run dependent subtasks concurrently; wait for each result before dispatching the next.
6. Give each subagent a complete, self-contained brief: goal, constraints, files involved, verification steps, and exactly what to report back.
7. Review every subagent report. If work is incomplete or wrong, delegate the fix to a subagent — never fix it yourself.
8. Reuse a running subagent via its task_id when follow-up work belongs to the same context.
9. Keep the user informed: report what was delegated to whom, the results, blockers, and the final state.
## Mandatory execution flow
1. **DISCOVER**: Use `explore`, `glob`, `grep`, or `read` to identify all affected files. Do NOT delegate implementation until file paths are known.
2. **PLAN**: Write a list of 2+ atomic subtasks into `todowrite`, assigning exact files to each subtask.
3. **DISPATCH**: Call `task` once per subtask in parallel (or sequentially if dependent). Each brief must include explicit file paths or module boundaries.
## Subtask sizing
- Split a request along its seams: separate files, functions, concerns, or verification steps each become their own subtask.
- A subtask is TOO BIG if: it touches many unrelated files, its brief runs more than a few paragraphs, a subagent could not finish and report back in one focused pass, or you cannot verify its result in isolation.
- When in doubt, split again — an extra small subagent costs less than one bloated delegation.
## Tool discipline
- `task` for all work (mandatory), `todowrite` to track subtasks, `question` only to clarify genuinely ambiguous requests.
- `read`/`glob`/`grep`/`webfetch`/`websearch` only when needed to write a better brief or verify a result.
- Hands-on tools are hard-blocked for you (edit, bash). If a subagent lacks a tool it needs, tell the user instead of doing it yourself.
## Default delegation
- `explore` — codebase research, locating code, understanding existing implementations.
- `general` — implementation, refactoring, testing, and any task without a more specific subagent.
- Prefer the most specialized subagent for each subtask; fall back to `general`.
Two placeholders are substituted at runtime:
| Placeholder | Value |
|---|---|
blockedTools list | The blockedTools option joined with , (default: edit, bash) |
instructions | The instructions option, appended verbatim at the end |
Compatibility and installation
OpenCode 2 is the supported runtime. The package root and the repository's
root index.ts export the V2 { id, setup } plugin.
OpenCode 2
Install directly from GitHub:
opencode plugin add github:beremaran/opencode-agent-tree
Or configure the plugin in a project with options:
{
"$schema": "https://opencode.ai/config.json",
"default_agent": "Manager",
"plugins": [
{
"package": "github:beremaran/opencode-agent-tree",
"options": { "subagentModel": "anthropic/claude-sonnet-4-6" }
}
]
}
For a local checkout, replace the GitHub spec with the absolute repository directory. OpenCode resolves the checkout through its package entrypoint:
{
"$schema": "https://opencode.ai/config.json",
"plugins": [{
"package": "/absolute/path/to/opencode-agent-tree",
"options": { "subagentModel": "anthropic/claude-sonnet-4-6" }
}]
}
Config is loaded at startup. Restart OpenCode after adding the plugin.
Runtime: this package ships raw TypeScript with no build step. OpenCode loads the package root through its V2 export and executes plugin TypeScript with Bun. The
engines.noderequirement (>=22.6) is for local tooling and tests; it is not a promise that plain Node.js can load the package entrypoint fromnode_modules.
The package entrypoint is src/v2.ts.
Getting started: pick your default agent
Installing the plugin creates the Manager agent but does not make it your
default agent. OpenCode still starts in whatever mode your config selects
(the built-in build agent by default, or whatever default_agent names)
until you choose the orchestrator.
To always start in orchestrator mode, set the default agent in opencode.json:
{
"$schema": "https://opencode.ai/config.json",
"default_agent": "Manager",
"plugins": [{
"package": "github:beremaran/opencode-agent-tree",
"options": { "subagentModel": "anthropic/claude-sonnet-4-6" }
}]
}
Alternatively, pick Manager in the agent picker at the start of each session.
Run opencode debug agents to inspect the generated agent and confirm its
mode, system prompt, model, and permissions.
The plugin creates Manager but does not change OpenCode's default agent unless
you set default_agent yourself.
Options
| Option | Type | Default | Description |
|---|---|---|---|
subagentModel | string | required | Model for all delegated work, e.g. "anthropic/claude-sonnet-4-6". Must be provider/model format. Agents with an explicit model in opencode.json are never overridden. See Model precedence. |
orchestratorModel | string | agent model, else model | Model for the orchestrator itself. Unconditionally overrides an explicit model on the orchestrator agent. |
orchestratorAgent | string | "Manager" | Which agent acts as the orchestrator. Created by the plugin if it does not exist (it shows up in the agent picker under this name). If you name an existing agent, the plugin converts it to a primary agent: its mode is set to "primary" unconditionally, and a warning is logged if it previously had an explicit non-primary mode. Built-in primary agents are left untouched by default. |
orchestratorDepth | number | 1 | How many orchestrator levels form the delegation chain. With N, the levels are <orchestratorAgent>, <orchestratorAgent>-2, ..., <orchestratorAgent>-N. Intermediate levels can only delegate to the next level (their V2 subagent permission is structurally pinned); only the final level's subagents (general, explore) have hands-on tools. Every level defaults to orchestratorModel (or a per-level orchestratorModels entry) and the blocked hands-on tools. Must be a positive integer. See Deep orchestration. |
orchestratorModels | string[] | — | Per-level orchestrator models. Entry i applies to level i+1 ([0] → "Manager", [1] → "Manager-2", ...). A shorter array leaves deeper levels on orchestratorModel. Entries must be provider/model format; length must not exceed orchestratorDepth. |
agents | string[] | all subagent/all-mode agents | Only these agents get subagentModel. Disabled agents, primary-mode agents, the built-in primaries (build, plan, compaction, title, summary), and the orchestrator level agents are filtered out even if listed — none of them are ever routed to subagentModel, and they never trigger the phantom-name warning. |
agentModels | Record<string,string> | {} | Per-agent overrides, wins over subagentModel. Never applies to the orchestrator agent (it is never routed). |
instructions | string | — | Extra rules appended verbatim to the orchestrator system prompt. |
blockedTools | string[] | ["edit", "bash"] | Tool names hard-denied to the orchestrator. On OpenCode 2, bash maps to the shell action. [] = prompt-only enforcement. Names must match [a-z0-9_-]+. |
restrictTask | boolean | false | When true, the orchestrator gets ordered V2 subagent permission rules that deny * and allow each routed delegation target, so it can only delegate to routed subagents. Closes the "delegate to an unrestricted agent" loophole (see Security). Without it, single-level orchestrators have no subagent rule, while final levels of chains (orchestratorDepth > 1) get a blanket subagent allow rule so delegation remains available. |
The adapter writes system and ordered permissions; bash maps to the
shell action and task maps to the subagent action.
Permission keys gate tool families
OpenCode 2 permissions are keyed by action, not by every individual tool
name. One action covers a whole tool family, so when you write blockedTools
use the key, not the tool name. For example, the edit action covers the edit,
write, and patch tools, while bash is represented by the shell action.
Model precedence
The effective model for a delegated subagent is resolved in this order:
- An explicit
modelset on the agent inopencode.json agentModels[name]subagentModel
The orchestrator is asymmetric:
orchestratorModelunconditionally overrides an explicitmodelon the orchestrator agent.- With
orchestratorDepth > 1, each level's model resolves asorchestratorModels[i]→orchestratorModel→ the level agent's existing/default model.orchestratorModels[0]is the top level (e.g. "Manager"),orchestratorModels[1]is "Manager-2", and so on; a level without an array entry falls back toorchestratorModel. agentModelsis never applied to orchestrator levels — per-level orchestrator models come fromorchestratorModels, notagentModels.- An
agentModelsentry keyed to an orchestrator agent name is silently ignored — orchestrator levels are never routed. - Built-in primary agents (
build,plan,compaction,title,summary) are never routed either, soagentModelsentries for them are never applied.
Deep orchestration
With orchestratorDepth: 1 (the default) a single orchestrator delegates
directly to the routed subagents:
user prompt -> Manager -> general / explore (hands-on tools)
With orchestratorDepth: N the plugin creates a strict chain of N
orchestrator-only agents. Level 1 is <orchestratorAgent> (a primary agent
you interact with), and each further level is named
<orchestratorAgent>-<i> (a subagent). Only the final level delegates to the
routed subagents; every orchestrator level has hands-on tools denied.
orchestratorDepth: 3
user prompt -> Manager -> Manager-2 -> Manager-3 -> general / explore (hands-on tools)
Enforcement in the chain:
- Intermediate levels (1..N-1) are structurally pinned to the next level.
Their V2
subagentpermission rules always deny*and allow only<next-level>— regardless ofrestrictTask— so they physically cannot delegate to workers or any other agent. Their directive instructs them to decompose the request from the level above, delegate every subtask only to the next level, and never do hands-on work. restrictTaskcontrols the final level's delegation pinning. Level N delegates to the routed subagents.restrictTask: truepins its V2subagentpermission to exactly those routed targets (general,explore, ...); without it, the final level gets a blanketsubagentallow rule so its directive guides delegation without pinning. Either way the final level of a chain must declare asubagentpermission: OpenCode injects a deny-all rule when a subagent declares no delegation rule, which removes the delegation action from its toolset. Subagent levels also declaretodowritefor the same reason.- Every level defaults to
orchestratorModeland the blocked hands-on tools, unless a per-levelorchestratorModels[i]entry overrides its model; all level agents appear in the agent picker//agent.
Cost caveat: every added level multiplies LLM model calls and tokens — each level re-plans, writes briefs, and reviews the level below it. Depth 3+ should be reserved for genuinely large decompositions, and each level should be pointed at a model cheap enough to justify the overhead.
The plugin uses OpenCode 2 subagent permissions directly; no separate
subagent_depth setting is required for these chains.
Example
{
"$schema": "https://opencode.ai/config.json",
"plugins": [{
"package": "github:beremaran/opencode-agent-tree",
"options": {
"subagentModel": "anthropic/claude-sonnet-4-6",
"orchestratorModel": "anthropic/claude-opus-4-5",
"orchestratorAgent": "Manager",
"agents": ["general", "explore", "worker"],
"agentModels": { "explore": "anthropic/claude-haiku-4-5" },
"instructions": "Never delegate more than 3 subtasks at once."
}
}]
}
Model IDs in the examples are illustrative — substitute real
provider/modelIDs that exist in your OpenCode setup (check your configured providers oropencode models).
Deep orchestration with a three-level chain:
{
"$schema": "https://opencode.ai/config.json",
"default_agent": "Manager",
"plugins": [{
"package": "github:beremaran/opencode-agent-tree",
"options": {
"subagentModel": "anthropic/claude-haiku-4-5",
"orchestratorModel": "anthropic/claude-sonnet-4-5",
"orchestratorDepth": 3,
"restrictTask": true
}
}]
}
This creates Manager, Manager-2, and Manager-3. Manager and
Manager-2 can only delegate to the next level; Manager-3 delegates to
general/explore (and, with restrictTask, to nothing else).
Per-level models keep deep chains affordable — point the top level at the
strongest model and drop to cheaper models deeper in the chain
(orchestratorModels[0] = "Manager", [1] = "Manager-2", ...). Levels
without an entry fall back to orchestratorModel:
{
"$schema": "https://opencode.ai/config.json",
"default_agent": "Manager",
"plugins": [{
"package": "github:beremaran/opencode-agent-tree",
"options": {
"subagentModel": "anthropic/claude-haiku-4-5",
"orchestratorDepth": 3,
"orchestratorModels": [
"anthropic/claude-opus-4-5",
"anthropic/claude-sonnet-4-5",
"anthropic/claude-haiku-4-5"
]
}
}]
}
Validation
At startup the plugin validates its options. Invalid values throw a config error and abort plugin loading; valid values are applied to the agent draft.
| Condition | Result |
|---|---|
subagentModel, orchestratorModel, or an agentModels value is not provider/model format (exactly one /, non-empty on both sides, no whitespace; dots, dashes, underscores, and colons are allowed in the model part, but not further slashes) | Config error, plugin load aborts |
orchestratorDepth is not a positive integer (0, -1, 1.5, "3", null, NaN) | Config error, plugin load aborts |
orchestratorModels has more entries than orchestratorDepth, or an entry is not provider/model format | Config error, plugin load aborts (the length error names both options) |
A blockedTools name does not match [a-z0-9_-]+ (lowercase letters, digits, underscore, hyphen) | Config error, plugin load aborts |
Security
The plugin enforces behavior through configuration, so its security surface is the configuration it runs with. Only use this plugin with config you control.
instructionsis injected verbatim into the orchestrator's system prompt. An untrusted config can append arbitrary prompt rules that the model may follow.- The tool block is an explicit allow/deny list, not categorical. A renamed upstream tool, or a future mutating tool the plugin does not know about, would not be auto-blocked.
- Subagents keep their hands-on tools. Delegation does not remove tools
from subagents; the plugin constrains the orchestrator, not the subagents. A
delegated subagent can still
editandbash. - The "delegate to an unrestricted agent" loophole. Because subagents keep
their tools, a prompt that is not following the directive could try to
delegate to an agent the plugin did not restrict, bypassing the block. Set
restrictTask: trueto close this: the orchestrator's V2subagentpermission then only allows the plugin's routed delegation targets and denies everything else. - Intermediate chain levels cannot delegate to arbitrary agents — even
without
restrictTask. WithorchestratorDepth > 1, every intermediate level's V2subagentpermission rules deny*and allow only<next-level>, so a misbehaving intermediate prompt cannot delegate to an unrestricted agent. The final level still needsrestrictTask: trueto close the same loophole for the worker hop; without it, its delegation action remains available to every target. orchestratorModelcan override an explicitly configured model on the orchestrator agent.
See SECURITY.md for how to report vulnerabilities.
Limitations
- Enforcement is prompt + permission based. Non-compliant models can still cut corners — for example doing their own research instead of delegating — where the permission block does not forbid the action.
- The
subagentaction is assumed to be available to the orchestrator. - Once work is delegated to a subagent, the plugin cannot stop it from doing that work.
- Each added orchestrator level multiplies LLM cost and latency. Every
level re-plans the request, writes briefs, and reviews the level below it, so
orchestratorDepth: Nperforms roughly N times the orchestrator-level model calls of depth 1. - The package targets OpenCode 2.0.0 or newer.
Troubleshooting
- Reload after config changes. OpenCode reloads watched local plugin files; restart it if a local source or option change is not picked up.
- A configuration error — invalid options abort plugin loading; correct the named option and restart OpenCode.
- With
orchestratorDepth > 1,Manager-2/Manager-3show up in the agent picker (/agent). That is expected: every orchestrator level is a real agent entry, defaults toorchestratorModel(or itsorchestratorModels[i]entry), and has its hands-on tools denied. All level names are excluded from routing, so they never receivesubagentModeland never trigger phantom-name warnings. - "My explicitly-configured agent model is not used" — for the orchestrator
this is expected:
orchestratorModelunconditionally overrides it, andagentModelsentries keyed to it are ignored. For subagents, an explicitmodelinopencode.jsonwins overagentModelsandsubagentModelby design. Built-in primaries (build,plan,compaction,title,summary) are never routed, so configuring a model for them has no effect either. See Model precedence.
Notes
- Subagents keep their default tools; only the orchestrator is restricted. Switch to the
planagent or another primary anytime. - The directive is installed on orchestrator level agents only (level 1 and,
with
orchestratorDepth > 1, the-2/-3/... levels) — worker subagents (general,explore) never receive it. - The directive is appended only once: the
# Orchestrator Modemarker in the prompt prevents re-appending if the config hook re-runs or OpenCode reloads the plugin. This is deliberate. - By default the orchestrator agent is created by the plugin as
Manager(visible in the agent picker under that name); no built-in agent is touched. If you setorchestratorAgentto an existing agent (e.g.build), the plugin converts that agent into the orchestrator instead: itsmodeis forced to"primary"and a warning is logged if it previously had an explicit non-primary mode. - Migrating from <=0.4.x: older versions converted the built-in
buildagent by default. That conversion is not undone on upgrade —buildkeeps the# Orchestrator Modedirective in its prompt because the marker only prevents re-appending, never removes. Either switch to the newManageragent, or remove the directive frombuild's prompt manually in your OpenCode config.
Development
bun install
bun run check # typecheck + lint + format + tests
The package root loads the OpenCode 2 adapter (index.ts -> src/v2.ts). The
checked-in opencode.json exercises the V2 adapter from the local checkout.
Run opencode debug agents from the repository root to inspect the generated
Manager agent and its permissions.
See RELEASING.md for the release process.
Release process
See RELEASING.md for versioning and GitHub Release steps.
License
MIT — see LICENSE.