Repository navigation
Description
Claim under challenge
parity.yaml:
- id: subagent-dispatch
summary: The pipeline's ability to hand a phase to a subagent for isolated execution.
harnesses:
opencode:
state: absent
scope_note: no isolation-argument dispatch primitive on OpenCode —
fr-goal §5's documented fallback runs phases inline;
correct behaviour, not a gap to filland plugins/super-fr/skills/fr-goal/SKILL.md:91:
OpenCode has no dispatch primitive of its own — phases run inline, which is
correct
Both look wrong, in different ways.
1. OpenCode does have a dispatch primitive, and we have measured it
OpenCode ships built-in subagents (general, explore, scout), invocable
two ways: by @mention, or programmatically through the Task tool
(gated by permission.task).
This is not a docs-reading claim. Our own recorded experiment measured it. From
docs/presentation/version-1/experiment/run-metrics.csv, arm A, harness
opencode --auto (see that experiment's runbook — all arms were OpenCode):
| arm | sessions | subagents | cost | root minutes |
|---|---|---|---|---|
| A — fr skills | 14 | 13 | $7.59 | 56.2 |
| B — vanilla | 1 | 0 | $1.07 | 77.6 |
| C — vanilla + plan | 1 | 0 | $0.97 | 105.2 |
Thirteen child sessions, each with parent_id set and agent = general, titled
(@general subagent). So fr-goal on OpenCode did dispatch subagents, and
the rows are still in the repo.
So SKILL.md:91's "no dispatch primitive of its own" is false as written.
2. The parity scope_note's reason is self-refuting
The note blames the absence of an isolation-argument dispatch primitive. That
cannot be the blocker for this surface, for two independent reasons.
Hermes has no isolation argument either, and is declared enforced.
plugins/super-fr/rules/fr-isolation-required.md:149 says so in as many words:
Harnesses without an
isolationargument on their dispatch tool — Hermes'
delegate_task, OpenCode — cannot express the poisoned shape and need no hook.
Both named together as lacking it. One is enforced, the other absent. The
stated discriminator does not discriminate.
The carve-out forbids an isolation argument here anyway. The same rule's
fr-phase-executor section is emphatic that phase executors must not be
dispatched with isolation: "worktree" — fr-goal §6 runs them serially inside
the workspace that already exists, and fr-phase-executor-guard.sh refuses the
combination outright. So this surface requires the absence of an isolation
argument. Citing that absence as the reason it cannot work is backwards.
3. There is a real limitation — just not the stated one
Worth stating precisely, because it is the part that may justify a partial:
- Custom named subagents appear not to be reliably invocable on OpenCode
(upstream:anomalyco/opencode#29616—@namerouting does not fire and the
Task tool'ssubagent_typeexposes only built-ins). So mirroring
fr-phase-executorby name, with its tool restrictions, may genuinely not
work today. - And we ship nothing to find out:
plugins/super-fr/agents/contains only
fr-phase-executor.mdfor Claude Code, and this repo'sopencode.jsonhas no
agentkey at all.
Dispatch to the built-in general subagent demonstrably works, which is
what arm A did 13 times. That is context isolation per phase — the thing the
surface is for — minus the tool restrictions.
4. If the real reason is cost, the declaration should say cost
Arm A's 13 subagents cost $7.59 against roughly $1 inline — about 7×. That
is a defensible reason to run phases inline by default, and it may well be the
actual motivation for the 4.5.x change.
But note arm A was also faster in wall clock (56.2 min, vs 77.6 and 105.2 for
the single-session arms), so it is a cost-versus-speed-and-context-isolation
tradeoff, not a plain loss.
Either way: "no primitive exists" and "the primitive is too expensive to use by
default" are very different statements, and only one of them is true. A parity
row is a claim about capability; encoding a cost decision as an absence is how
a capability stops being reconsidered.
What I think closing this looks like
- Correct SKILL.md:91 — OpenCode has a dispatch primitive (Task tool /
@mention, built-in subagents). - Re-state the
subagent-dispatch/ opencode row.partialwith an
accurate note looks right: dispatch to a built-in subagent works; a
named custom agent with tool restrictions does not. - Decide explicitly whether inline-by-default is a cost policy. If it
is, say so in the note and ideally make it selectable, rather than
describing a capability that exists as one that does not. - Reconcile with Hermes, which is
enforcedwhile sharing the stated
blocker. Whichever way this lands, the two should not disagree for a
reason they have in common.
Why this surfaced
Planning a recorded demo on opencode --auto, the absent row meant the
Extensibility beat could not appear on camera at all. The operator challenged
it, and the challenge holds up: the measured evidence for OpenCode dispatching
subagents is sitting in this repo.
Activity
docs: correct the OpenCode subagent claim — I got it backwards (gh-493)
YiannisDermitzakis commentedon Sep 19, 2026
Tested live: custom subagents work. My "narrower real limitation" above is wrong too.
I wrote that custom named subagents "may not be reliably invocable on OpenCode"
per upstream
anomalyco/opencode#29616. That issue is stale — filed2026-05-27, and the behaviour it describes is fixed in the version on this
machine (opencode 1.18.31).
Definitions are read — all three documented forms
Created one agent in each form in a scratch project:
And they dispatch — with a real child session
Output alone could be the primary agent role-playing, so — the session table:
ses_…b802<root>buildses_…a51dses_…b802fr-phase-exec-pluralA genuine child session, parented, with its own cost and token accounting, and
the custom agent name recorded in the
agentcolumn. Structurally identicalto the 13
@generalrows in arm A, but with a named custom agent.So the row is simply wrong
subagent-dispatch / opencode: absentdoes not describe this harness. Bothhalves of the surface work today: definition and dispatch. And OpenCode agents
accept a
model:field, so the per-phase model tiering thatfr modelsexists for is reachable here too — not just context isolation.
What implementing it looks like
Small, and it rides machinery that already exists:
Mirror
plugins/super-fr/agents/fr-phase-executor.mdinto an OpenCode agentfile, the way
scripts/sync-opencode.pyalready mirrors skills and rules.The frontmatters are close but not identical and need a translation:
name:description:description:tools: Read, Edit, Write, Bash, Grep, Globpermission: {edit: allow, bash: allow, …}mode: subagent(required)fr models)model:Add it to
REPO_LOCAL_ONLY_*/mirror lists plus a sync tripwire, matching howskills and rules are already guarded against drift.
Correct
fr-goalSKILL.md:91, which currently tells the model OpenCode hasno dispatch primitive — so even with an agent defined, the skill would still
instruct it to run phases inline.
Decide the default explicitly. Dispatching costs more (arm A: $7.59 for 13
subagents vs ~$1 inline) and buys context isolation plus per-phase model
tiering, and arm A was also the fastest arm. That is a policy choice worth
stating as one.
Point 3 is the one that would otherwise bite: the definition alone changes
nothing while the prose still says the capability does not exist.
Verification note
Every claim above is from the installed binary, not documentation — the same
posture #486 established. Anyone re-checking should run
opencode agent listand inspect
session.parent_idrather than trusting this comment.docs: journal that OpenCode custom subagents work (gh-493)
docs: await gh-494 before recording; subagents restore the Extensibil…
feat(opencode): the prose and the row now say dispatch (phase 4/6)
Ship fr-phase-executor as an OpenCode subagent: the agent, the prose …
YiannisDermitzakis commentedon Sep 21, 2026
Closing — both claims this issue challenged have since been corrected, and the correction predates the issue's own triage.
Verified on
mainat 089fb51:1. The parity row no longer says
absent.The self-refuting
scope_note— which blamed the lack of an isolation-argument dispatch primitive, a discriminator that did not discriminate because Hermes lacks one too and was declaredenforced— is gone entirely. The summary now points at observable state in the run cursor rather than at skill prose being followed:2. The SKILL.md line is gone.
plugins/super-fr/skills/fr-goal/SKILL.mdno longer contains "no dispatch primitive of its own" or a claim that OpenCode phases run inline.Changed by a5cd114 (#495, gh-494), "Ship fr-phase-executor as an OpenCode subagent: the agent, the prose that forbade it, and the row that agreed" — which is this issue's argument, implemented.
What this issue got right, recorded so it is not lost: the reasoning stands as a worked example of the failure mode
fr harness parityexists to catch — a declaration whose stated reason cannot be the real blocker, kept plausible because nothing checked it against the wiring. The measured counter-evidence it cited (13 recorded OpenCode subagent dispatches in the repo's own experiment CSV) was the decisive part.What the issue did not cover, and is still open: #530 — on OpenCode the holder of a dispatched unit is unnameable while it holds it, because the task tool blocks and returns the child session id only with the result. That is declared honestly as
dispatch-holder-identity: opencode: partialrather than folded into this row, which is the distinction this issue argued for.Found during a backlog triage sweep, not by new work.