Repository navigation
Description
Date: 2026-11-06 · Surface: laptop production OpenCode server (build 2.0.30+5e9b135f, fastlane drop-in), driven over the attach API. Sessions ran in a scratch worktree of developing-today/code; trivial tasks only (echo markers); plan-pool routes only; no PAYG. Prompt sources were read at fork main 93f5382 — one rev ahead of the deployed build; every behavior claim below is production-measured, file paths are where a fix would land.
Sessions (8): parent ses_e5e4dbeab89ffe46hz8cRqBlw4 · ping child ses_e5e4dbe9e4dffeWizUlwwUm39M · nesting child ses_e5e4dbc9e3fffeMb60xdJOxdBp · grandchild ses_e5e4dbc7a9bffeslsXpOLx6swS · background child ses_e5e4dba3ad9ffeU3Mi31lGq3rW · question child ses_e5e4db95f87ffeK76Av0XDWn2P · messaging pair ses_e5e4dbc04f3ffewG71bZwljs53 / ses_e5e4dbc0516ffexNg6xfwuU22a.
Q1 — Does a workhorse session know it can launch subsessions, and how does it do it?
Yes, immediately and unprompted. The parent was told only "launch a subsession to print PING42" — no mechanism, no tool name. First turn, it called the native subagent tool (ordinary tool call; the tool is registered codemode: false, it is not reached through mcpx):
{"agent": "build", "description": "Print exact ping", "prompt": "Use the shell to print exactly the string PING42 and nothing else...", "background": false}Tool result: <subagent sessionID="ses_e5e4dbe9e4dffeWizUlwwUm39M" state="completed">PING42</subagent>. The child landed in the parent's directory (the worktree) and inherited the parent's session model (opencode-route/workhorse) because no model argument was passed. The parent waited synchronously (foreground is the default) and aggregated the result verbatim. The parent self-reports the mechanism as "functions.subagent".
Q2 — Can a subsession launch its own subsession?
Yes. The nesting child launched a grandchild the same way (subagent tool, agent build), output NEST7 relayed verbatim up both levels. Nothing — no prompt, tool, or error — claimed it couldn't. Depth cap is real but generous: experimental.subagent_depth, default 5 (packages/core/src/session/subagent-depth.ts), and the tool description itself says "A subagent may itself launch subagents, subject to the depth limit and to model governance".
Q3 — Are defaults clear? What actually served?
- Child sessions inherit the parent's route ref and directory when
modelis not passed (observed in all 5 children). The launched sessions behave as if this is obvious; nothing in the tool result states it back. - Leaf ≠ head, and leaves vary per step. Every session's declared model stayed
opencode-route/workhorse, butroute_attemptshows single turns served step-by-step by different leaves: e.g. the messaging pair's turns ranopenai.main/gpt-6-luna#xhighfor the tool steps andvibe-direct/mistral-small-latest#highfor the closing step — all underroute_id=workhorse. After an API model switch (Q5), thespeedroute's first step hitopencode-go.drewrypope/muse-spark-1.3-contributor#xhigh→ 429 (pool walled) and failed over tovibe-direct/mistral-small-latest#high, success. So: no drift of the session's model ref — but a session cannot tell which leaf served any given step, and prompt policy differs by leaf (see Q7). Launch prompts should state the intended route/head explicitly; the machinery will not confirm it back.
Q4 — Cross-session messaging while running
Works, both directions, via mcpx code mode. Messaging is not a plain tool call in these transcripts: the sender first ran mcpx_discover (namespace comms; the first discover call errored, the retry succeeded), then the execute tool:
tools.comms.msg({mode: "send", to: "ses_e5e4dbc0516ffexNg6xfwuU22a", text: "Token: TOK-987. Reply to me with the token reversed.", deliver: "wake"})
→ {"topic": "<sender>/bidi/<recipient>", "seq": 1, "delivered": [{"session": "<recipient>", "delivery": "wake"}]}The woken recipient replied the same way (789-KOT), and the original sender, blocked in tools.comms.msg({mode: "unread", wait: 120}), received it inside the same turn and reported it. DM + wake + unread-with-wait is a complete request/response loop between two running sessions.
Q5 — Can a session change its own model?
Not from inside — the mechanism exists one level up. Asked to switch itself to opencode-route/speed, the session improvised in code mode with tools.opencode.session_move({directory: "opencode-route/speed"}) — that is a directory move, not a model switch — got "Unable to move session", and honestly reported SWITCH_METHOD: NONE_AVAILABLE. (Session.switchModel exists in core and is used by the command/API path — packages/core/src/session/session.ts, packages/protocol/src/groups/session.ts session.switchModel — but no in-session tool exposes it.) The API switch is instant and clean: POST /api/session/:id/model → 204, the session's model ref became opencode-route/speed, and the next turn's attempts ran under route_id=speed. The cache caveat stands: a switch invalidates the prompt cache for the new model, so sessions should switch rarely and deliberately — today they can't switch at all, which makes the point moot until a tool exists.
Q6 — Questions and errors in detached operation
Errors: handled. A background child told to cat a missing path hit an environment error (cat: command not found, exit 127 — the restricted shell profile). It did not stall or ask anyone; it reported the error verbatim as its final output. The parent, which had launched it background=true and ended its turn, received the completion unprompted as a synthetic turn (<subagent sessionID="..." state="completed" ...> + a follow-up assistant turn carrying the child's output). No babysitting needed for errors or for background completion.
Questions: a hard stall — the one real hazard found. A child instructed to use the question tool ("Which color?", options Red/Blue) called it and blocked: the tool call sat in status=running for 10+ minutes, the parent's foreground subagent call blocked with it, and nothing notified anyone — no message in the parent's transcript, no form on the parent session (GET /api/session/<parent>/form → []). The form waits on the child session for an external actor (UI or API client).
The stall degrades into a retry loop if left alone. Somewhere in the following ~25 minutes the child's question call ended in status=error (operator probing of the form endpoints coincided with this window and its output was lost to a transport timeout, so whether the call timed out on its own is undetermined). The error propagated to the parent's subagent call (status=error), and the fleet's session-retry machinery then resumed both sessions automatically: the child got its task re-delivered as a fresh user message and re-asked the identical question (stall #2), while the parent relaunched the child through the sessionID continuation path. Unanswered, this loop can repeat — worse than a clean stall, since it burns turns producing nothing.
An external answer unblocks everything instantly. The pending form lives on the child (GET /api/session/<child>/form → frm_…, one field, key q0); POST /api/session/<child>/form/<id>/reply with {"answer": {"q0": "Blue"}} (answers are keyed by field key — a bare array is rejected with Expected Form.Answer, an array value with Expected string for form field: q0) returned 204; the child's question call completed (User has answered your questions: "Which color?"="Blue"), it printed CHOSEN: Blue, and the parent's subagent call returned and relayed it verbatim. Both sessions idle, outcome succeeded.
Detached design work must therefore treat the question tool as forbidden unless a watcher is assigned: record the question, pick the documented default, and continue.
Q7 — Do the system prompts describe any of this?
Mostly no, and where they do, they contradict each other:
- Base prompt is silent.
packages/core/src/session/runner/prompt/system.txt(15 lines) mentions none of: thesubagenttool, nesting, background mode/stop policies, messaging, or detached operation. - Family prompts disagree about delegation itself.
packages/core/src/plugin/system-prompt/gpt.txtandgpt-astra.txt: "Do not spawn subagents unless the user or applicable AGENTS.md/skill instructions explicitly ask for subagents, delegation, or parallel agent work."meta.txtproactively encourages thesubagenttool;kimi.txtmentions it neutrally. Selection is per-model (packages/core/src/plugin/optimize.tsmatches gpt/claude/kimi/muse/trinity in the model id). DeepSeek — the documented workhorse head — matches no family and gets no delegation guidance at all, while a single workhorse session can be served by a GPT-policy leaf on one step and a no-policy leaf on the next (Q3). Delegation behavior currently varies by leaf luck, not by design. - The fleet's own instruction file is stale. developing-today/code
.opencode/instructions/subagents.md(loaded via the global config'sinstructions) calls the tool the "Task tool" (its name issubagent), listsplanas a subagent type (modeprimary— the tool rejects it: "cannot run as a subagent";buildworks only because its mode isall), and says nothing about the depth limit, background/stopPolicy, model/directory inheritance, messaging, model switching, or the question stall. - The
commsnamespace is undocumented..opencode/instructions/mcpx.md's namespace table omitscommsentirely; the sender had to discover it viamcpx_discover(first call errored). Themsg/topics/sessionstool descriptions inpackages/core/src/tool/plugin/messaging.tsare thorough — but sessions reach messaging through code mode, where discoverability is the weak link. - The
questiontool's description invites the stall.packages/core/src/tool/plugin/question.ts: "Use this tool when you need to ask the user questions during execution" — no warning that in a detached/subsession context there may be no user, that the parent is not notified, or that a foreground parent blocks with it. - The
subagenttool description is the best text in the system (packages/core/src/tool/plugin/subagent.ts) — nesting, background, stop policies, continuation by sessionID are all there. Two gaps: it doesn't state that children inherit the parent's directory and (without amodelarg) the parent's model/route; and it doesn't warn that a child'squestioncall stalls the foreground call until an external actor answers.
Suggestions
- Add a short delegation/messaging block to the base system prompt (
system.txtor a shared include): subsessions exist, nesting is allowed to the depth cap, children inherit directory + route unless overridden, background completion arrives on its own, messaging is viacommsin code mode, and detached sessions must not call thequestiontool — record the question and continue with the stated default. - Reconcile the family prompts: replace the gpt/gpt-astra prohibition with the same delegation block (the fleet's standing doctrine is subagent-first; a leaf-level veto makes route behavior unpredictable).
- Give DeepSeek-family models an explicit prompt entry in
optimize.tsselection — the workhorse head should not be the one model with no guidance. - Fix the fleet instructions file: rename "Task tool" →
subagent, dropplanfrom subagent types (or note mode rules), add the depth cap, inheritance, background/stopPolicy,comms, and the question-stall warning. (Fleet-side change, tracked here for visibility.) - Document the
commsnamespace inmcpx.md's table, and consider registeringmsgas a plain tool as well as a code-mode projection so messaging doesn't depend on discovery. - Either expose an in-session model-switch tool (wrapping
Session.switchModel, with the cache warning in its description) or state in the base prompt that sessions cannot change their own model and must ask their launcher to do it. Silence today produces improvisation with the wrong API (session_move). - Surface the serving leaf: include the resolved provider/model for the current step somewhere a session can see (e.g. in its context or a
sessionstool field). A session asked "which model are you on" currently answers from its route ref or guesses. - Consider a question-relay: when a child session's
questionform goes unanswered for N minutes, surface it to the parent session (the parent is the natural answerer in detached trees) — or time the form out with a visible cancellation the child can handle. Today's behavior is a silent stall of the whole foreground chain that, once the call eventually errors, the retry machinery can turn into a re-ask loop.
Test hygiene note: one intermediate tool output during this test returned a fabricated transcript (a wrong child session id and an invented successful answer). Every claim above was re-verified with independent reads of the session store and route_attempt before filing.
Acceptance criteria
- Each system-prompt suggestion in the workshop results is dispositioned: adopted (with the prompt change landed or linked), declined with reasoning, or spun out as its own issue
- The disposition is recorded on this issue so the workshop findings do not sit as an unowned report
Activity
dezren39 commentedon Nov 6, 2026
Gap issues filed (2026-11-06, per Drew's order)
Every gap from this workshop now has its own issue in this repo — one per finding, each carrying the workshop evidence and an acceptance sketch:
questioncall stalls child AND foreground parent silently — no notification, and retry machinery re-asks the identical question #1883 — subsessionquestionstalls child + foreground parent silently; retry machinery re-asks the identical question.questionshould be answerable by its parent session, passing up the stack if unanswered #1884 — parent session answers a subsession'squestion; unanswered questions pass up the stack to root/client.question(feature, Drew): feat: API launch mode that disables thequestiontool entirely for detached / non-interactive sessions #1885 — detached/non-interactive launch mode wherequestionis unavailable; inherited by children.Session.switchModelin-session, or a switch-request path to the parent/launcher #1886 — exposeSession.switchModelin-session, or a switch-request path, or document the limit.system.txtmentions none of the subsession machinery; add the delegation/messaging block.subagenttool description gaps: docs:subagenttool description omits inheritance, background semantics, and the question-stall warning #1889 — inheritance, depth cap, background completion semantics, question-stall warning.questiontool description warning: docs:questiontool description invites use with no detached-context warning #1890 — no detached-context warning today.commsdiscoverability (fork side): feat/docs: cross-session messaging (comms) is undiscoverable from the fork's prompt and tool surface #1892 — cross-session messaging works but nothing in the prompt/tool surface points at it.Excluded by scope: suggestion 4 in this issue (the stale fleet instructions file, developing-today/code
.opencode/instructions/subagents.md) is code-repo work and is being handled there, not filed here.dezren39 commentedon Nov 7, 2026
Intake review
Notes: this is a measured workshop report (8 sessions, production server) rather than a defect — its value is the per-question findings and the prompt suggestions at the end. Intake found no defect to validate as still-live; the 'work' is triage of its suggestions. Risks: prompt changes made without the workshop's measurement setup cannot be compared against its baselines. Suggested approach: disposition the suggestions in one pass in the main chat or a lane, and re-run the ping/nesting probes only for adopted changes.
— Fleet monitor intake (repo-fleet-monitor, 2026-11-07 run): acceptance criteria appended to the issue body, labels normalized (kind/docs, source/human, priority/low, effort/s, status/needs-triage, meta/enriched), marked meta/enriched. No merged PR or commit was found this run that explicitly resolves this issue, so it stays open.
dezren39 commentedon Nov 8, 2026
Triage: DISPOSITIONED — every suggestion spun out; closing this report issue.
Fresh verification (2026-11-07, current labels/state read per issue): all ten gap issues filed 2026-11-06 per Drew's order exist and carry the work; none of the findings sits unowned.
Suggestion-by-suggestion disposition (acceptance criterion 1 — "adopted, declined with reasoning, or spun out as its own issue"):
task/small-fix,queue/queued).task/small-fix,queue/queued).optimize.ts→ prompts: model-family prompts contradict each other on subagent spawning — and DeepSeek matches no family at all #1888 (same issue; its verified scope includes "no deepseek entry and no unmatched fallback").developing-today/code.opencode/instructions/subagents.md) → not filed here by scope — code-repo work, handled there (per the 2026-11-06 gap-filing comment on this issue).commsdiscoverability/documentation → feat/docs: cross-session messaging (comms) is undiscoverable from the fork's prompt and tool surface #1892 (CLOSED).Session.switchModelin-session, or a switch-request path to the parent/launcher #1886 (OPEN,queue/queued).questioncall stalls child AND foreground parent silently — no notification, and retry machinery re-asks the identical question #1883 (OPEN,task/fix, priority/high,queue/queued), feat: question escalation — a subsession'squestionshould be answerable by its parent session, passing up the stack if unanswered #1884 (OPEN, design-first,queue/queued), feat: API launch mode that disables thequestiontool entirely for detached / non-interactive sessions #1885 (OPEN,task/triage,queue/queued).Related Q7 tool-description findings: #1889 (
subagenttool description gaps — CLOSED) and #1890 (questiontool detached-context warning — OPEN,queue/parked,work/assigned).With the disposition recorded here (criterion 2) and every implementation item owned by a queued spin-out, this workshop report is closed; the spin-outs carry the remaining work.