plans/subagent.md
Updated 2026-07-13 after reviewing OpenAI Codex commit
c39520f3d1522f2587694b52eba7d3eb39460137and resolving the initial product decisions.
Dyad will add a visible, root-controlled, depth-one sub-agent system with three fixed personas:
gpt-5.6-luna with high reasoning, may be invoked automatically after a Pro user enables its separate default-off setting, directly edits app files, receives only controlled file-edit tools, and is limited to one exclusive writer at a time.The design adopts independent child threads, asynchronous lifecycle controls, bounded context transfer, durable transcripts and messaging, fixed concurrency, and visible status. The root remains accountable for synthesis, verification, commits, deploys, and the final user response.
The existing root-level explore_code tool will be replaced immediately by spawn_agent; there is no compatibility period or duplicate exploration path.
These are binding decisions:
sourceCommitHash to commitHash), with the working tree as a fallback when that turn has no commit range.enableImplementerSubagent setting that defaults to false.write_file and search_replace.HEAD. Ignored files and binary contents are excluded; an unborn repository compares against Git's empty tree; non-Git projects cannot be reviewed initially.entitlement_revoked.autoFixReviewIssues. The countdown includes Skip fix, does not change the persistent setting, and keeps the user message queued.Each persona has its own model and reasoning defaults rather than inheriting the root model:
| Persona | Default model | Default reasoning effort |
|---|---|---|
| Explorer | gpt-5.6-luna | High |
| Reviewer | gpt-5.6-sol | Medium |
| Implementer | gpt-5.6-luna | High |
Both selected models exist in the reviewed Codex model catalog but are not currently registered in Dyad. Adding them to Dyad's model catalog/constants is therefore an implementation prerequisite. Resolve the exact persona model before scheduling; if it is unavailable for the Pro user's configured account/provider, block the run with setup guidance.
Dyad's Local Agent currently performs investigation, implementation, and self-review in one linear loop. Reconnaissance consumes root context, independent work cannot proceed concurrently, and users lack an explicit independent review checkpoint.
Dyad already has a specialized explore_code nested loop, but it is blocking, single-purpose, hard-coded, and lacks a durable lifecycle. This plan replaces that surface with a general child runtime while preserving Dyad's consent model, provider abstraction, live change visibility, and root-owned completion flow.
Dyad can assemble a small, visible team of specialized agents while the root remains accountable for the overall task.
Availability: Pro entitlement plus enableExplorerSubagent, default true. When both permit it, Explorer appears in the root model's spawn_agent schema and prompt guidance. Otherwise it is absent and runtime-rejected.
Default runtime: gpt-5.6-luna, high reasoning effort.
Purpose: Locate code, trace behavior, gather evidence, identify tests, and explain relevant architecture.
Authority: Read-only. Its effective tools are the intersection of the parent policy and the Explorer allowlist: targeted file/code inspection, grep, listing, reading, and the compiler-aware exploration primitives moved out of the old explore_code wrapper. Any tool whose effective modifiesState is true is excluded and runtime-rejected.
Input: current request, bounded assignment, intended outcome, allowed roots, a root summary, and relevant manifests.
Report: concise conclusion, file/line evidence, relevant flow, confidence, gaps, and recommended next action.
The root-level explore_code definition and prompt references are removed as soon as Explorer moves to the general runtime. spawn_agent({ persona: "explorer" }) is the sole root-facing exploration mechanism.
Availability: Pro users may always manually invoke Reviewer. enableAutoReview, default false, controls only application-triggered automatic review.
Invocation boundary: Reviewer is never part of the model-visible spawn_agent persona enum or autonomous prompt guidance. It may start only from:
enableAutoReview is true.Manual actions and the application auto-review trigger call a dedicated internal review-start path. The root may then use durable messaging to answer a Reviewer question or request a follow-up, but it cannot originate a Reviewer thread autonomously. Auto-review must be initiated by application state, never by a hidden model tool call.
Default runtime: gpt-5.6-sol, medium reasoning effort.
Purpose: Independently evaluate the latest assistant turn's changes against the user request and repository expectations without editing away its own findings.
Authority: Read-only, enforced identically to Explorer.
Explicit review target: Every review first targets the latest assistant message's sourceCommitHash to commitHash range. This matches Local Agent's auto-commit architecture and avoids an empty post-turn working tree. If no valid turn range exists, review the full working-tree diff since the last commit, including staged, unstaged, and non-ignored untracked text files. Ignored files and binary contents are excluded. Capture at start:
The Review button and auto-review use the same target construction: commit SHA, file list, exclusions, and diff hash. An unborn repository uses Git's empty tree as its base. A non-Git project shows review as unavailable. There is no slash-command or free-form review-scope parser.
Reviewer waits until no Implementer holds the mutation lease. At completion, recompute the target hash. If changed, report outdated; do not silently broaden or substitute the scope.
Report: findings, no_findings, partial, or outdated; prioritized severity; file/line evidence; impact; remediation; and validation gaps. no_findings is not a safety certification. A current report with findings exposes Fix findings and the persistent auto-fix control.
Availability: only when the user is Pro, enableImplementerSubagent is true, the root is in writable Agent mode, no Implementer is active for the app, and the mutation lease is available. Once available, the root model may invoke it automatically.
Default runtime: gpt-5.6-luna, high reasoning effort.
Purpose: Complete one narrowly scoped app-file implementation task delegated by the root.
Authority: controlled app-file tools such as write_file and search_replace. No terminal, MCP, SQL, sandbox writes, git, packages, integrations, deployment, database mutation, settings mutation, or orchestration.
The setting permits delegation; it does not approve tool calls. Existing app-blueprint gates, consent, containment, protected paths, edit tracking, and runtime preconditions still apply.
Input: concrete task, acceptance criteria, explicit path scope, relevant findings, and effective policy.
Report: changed files, description, checks performed, denied actions, unresolved issues, and required root follow-up.
The root performs final tests, verification, commit, deploy, synthesis, and completion reporting after the lease is released.
| Capability | Explorer | Reviewer | Implementer |
|---|---|---|---|
| Root-model automatic start | Yes | Never | Yes, when enabled |
| Application automatic start | No | When auto-review is enabled | No |
| Explicit user start | Natural request | Review button | Natural request |
| Read scoped app files | Yes | Yes | Yes |
| Modify app files | No | No | Scoped controlled edits only |
| Terminal / sandbox / MCP / SQL | No | No | No |
| Git / commit / deploy | No | No | No |
| Spawn agents | No | No | No |
| Receive durable root messages | Yes | Yes | Yes |
| Receive follow-up turns | Yes | Yes | Yes |
| Existing write consent | N/A | N/A | Required |
For Pro users, add four independent controls under a searchable Sub-agents section. Do not expose functional sub-agent controls to non-Pro users; an upgrade surface may explain Pro availability, but entitlement remains enforced server-side/main-process-side as applicable.
enableExplorerSubagent?: boolean
true.enableAutoReview?: boolean
false initially.enableImplementerSubagent?: boolean
false.autoFixReviewIssues?: boolean
false.There is no master enableSubagents dependency. All four controls additionally require Pro entitlement. Enabling Implementer shows a one-time warning explaining automatic delegation, direct edits, the one-writer lease, visible attribution, and unchanged consent requirements. Do not show a second spawn-level confirmation.
Follow rules/adding-settings.md: schema, defaults, search IDs/index, switches, Settings placement, and snapshots.
spawn_agent with an allowed persona, stable task name, bounded assignment, and scope.enableAutoReview and does not change that setting.enableAutoReview is true, the application starts Reviewer after any completed writable assistant turn, including an Implementer handoff, once the writer lease is free. It requires a non-empty diff and skips a hash already reviewed.No changes to review explanation. A non-Git project disables it with a Git-history requirement. An unborn repository is reviewable against Git's empty tree.When enableAutoReview is true and the user submits a message while the current assistant turn is still completing:
Fixing findings in 10… countdown beside the findings with a Skip fix action. This forced countdown applies even when autoFixReviewIssues is false and does not change that setting.If the user selects Skip fix, release the queued message immediately and leave findings visible. A failed, cancelled, blocked, outdated, or unavailable-model review/fix also releases the queued message with the status shown; the barrier must never strand the user's message. Additional user messages preserve FIFO order behind the first.
The inline card shows a stable row per child: persona, task, scope, status, elapsed time, latest meaningful activity, Implementer file attribution, expandable report/transcript, message/follow-up activity, stop action, and usage under progressive disclosure.
States include queued, running, idle, waiting_for_writer, waiting_for_auto_review, auto_fix_countdown, fixing_findings, verification_review, needs_approval, finishing_report, completed, partial, review_outdated, stopping, cancelled, entitlement_revoked, interrupted_by_restart, and failed.
An idle/completed child thread remains addressable for followup_task. A follow-up moves it back to queued/running while preserving thread history.
Reuse the existing consent banner with persona/task attribution. The card links to the canonical banner; it does not add another approval surface.
entitlement_revoked.Add a root-chat-scoped SubagentManager in the Electron main process. It owns identity, scheduling, concurrency, cancellation, immutable policy, writer lease, durable mailboxes, threads, messages, persistence, and renderer events.
Each child runs an independent Vercel AI SDK streamText loop through a child-only runSubagentTurn, generalized from explore_code_subagent.ts. Do not recursively invoke or initially refactor handleLocalAgentStream; it contains mature root-only persistence, compaction, retry, commit, and deployment behavior.
Root Local Agent
|
+-- SubagentManager (chat scoped, max 3 running children)
|-- Explorer threads (read only, model-callable)
|-- Reviewer threads (read only, button/auto-review initiated)
+-- Implementer thread (optional, one writer lease)
At root-turn start, compute one policy containing chat mode; Explorer, auto-review, Implementer, and auto-fix settings; allowed roots; consents/tools; app-blueprint state; persona model resolution; concurrency; and the app writer-lease state.
The policy also contains an immutable Pro-entitlement snapshot. A current entitlement check is repeated at spawn/start and execution boundaries. Entitlement loss rejects new operations immediately and aborts active children at the next safe boundary after any atomic file write; persist partial state as entitlement_revoked.
Schema exposure, prompt hints, tool construction, IPC review starts, and runtime checks derive from this policy. Runtime must reject stale, replayed, forged, disabled, or over-authorized calls.
For Pro users, the model-visible spawn_agent enum includes Explorer when enabled and Implementer only when its setting and writable mode permit it. It never includes Reviewer. For non-Pro users, no sub-agent orchestration tools or persona prompt hints are exposed.
SubagentContextEnvelope contains only the current request, assignment, persona, acceptance criteria, scope, allowed roots, relevant manifests, root summary, review metadata, and effective policy summary. It does not fork the complete chat.
Every child has a persistent ordered mailbox and resumable thread history:
send_message durably queues a root message without starting a new model turn.followup_task durably appends an assignment/message and schedules a new turn on the existing child thread when idle.wait_agents.On restart, active execution is not resumed automatically. Threads and queued/delivered messages remain durable and visible; the root or user may explicitly follow up after restart.
spawn_agent({
persona: "explorer" | "implementer", // dynamically restricted; never reviewer
task_name: string,
message: string,
context_summary?: string,
path_scope?: string[]
}) => { agent_id: string; status: AgentStatus }
send_message({ agent_id: string, message: string })
=> { message_id: string; delivery: "queued" | "delivered" }
followup_task({ agent_id: string, message: string })
=> { message_id: string; status: AgentStatus }
list_agents({}) => AgentSummary[]
wait_agents({ agent_ids?: string[], timeout_ms?: number }) => WaitResult
cancel_agent({ agent_id: string }) => { previous_status: AgentStatus }
Reviewer starts through a separate typed IPC/internal command authorized by a Review button click or the deterministic enableAutoReview application trigger. Do not route either through a hidden model tool call.
Build review targets from Git, not assistant-turn attribution:
sourceCommitHash; the fallback is HEAD, or Git's empty tree for an unborn repository.Fix findings and automatic fixing both start a new root Agent turn with the immutable review ID, target hash, and bounded findings projection. They do not grant Reviewer mutation authority. Auto-fix is default off and uses the same persistent setting in the findings panel and Settings page. Existing mode, consent, path, blueprint, and lease checks apply. Record the remediation origin so an auto-fix can receive one verification review without recursively launching another auto-fix.
The root chat scheduler also owns a queued-message review barrier. When auto-review is enabled, completion of a writable turn with at least one queued user message schedules review ahead of dequeuing that message. A current findings result creates a 10-second countdown deadline and a cancel/skip token visible to the renderer. Expiry schedules remediation ahead of the user-message queue regardless of autoFixReviewIssues; this override is per-event and never persists a setting change. Remediation schedules exactly one verification review, then releases the queue. Every non-success terminal path must release the barrier so user messages cannot be stranded.
Personas are configuration over the shared runner: prompt, allowlisted tools, report schema, model/default reasoning, and whether model/user invocation is permitted.
Add gpt-5.6-luna and gpt-5.6-sol to Dyad's model constants/catalog with accurate capabilities. Resolve the exact persona model/provider before durable scheduling and record the provider, model, and reasoning on the thread. If the required model is unavailable, do not create a runnable child or substitute another model; return a handled blocked result with setup guidance.
Use an app-level exclusive lease. Every state-changing invocation verifies that the Implementer owns the lease. Root writes receive a handled writer_busy; reads remain available. Acquisition/release must be exception- and cancellation-safe. Writes must fall inside both app root and explicit assignment scope. Scope expansion requires a new visible root assignment.
The lease supplements rather than replaces consent, containment, protected-path rules, blueprint approval, and edit tracking.
Add generated Drizzle migrations for:
agent_threads: ID, chat ID, persona, task name, bounded assignment/context/result, status, resolved provider/model/reasoning, measured usage, review target/hash, invocation/remediation origin, error, and timestamps. A nullable parent field may be reserved, but depth-one is enforced.
agent_messages: ID, thread ID, monotonic sequence, message ID/idempotency key, direction, role/type, bounded content/envelope, delivery/consumption state, originating root turn, and timestamps.
Persist at assignment, message, step/tool, and terminal boundaries—not token deltas. Threads/messages cascade-delete with the parent chat and otherwise are not pruned. The root chat stores only concise orchestration records and bounded result projections, not full child transcripts.
Use contract-driven IPC to query threads/messages, fetch reports/transcripts, start button/auto-review runs, skip a queued-message auto-fix countdown, start fix/auto-fix remediation, cancel children, send messages/follow-ups, and subscribe to batched events. Project renderer fields explicitly. Use TanStack Query for durable state and IPC events for live updates; clean global state after navigation/unmount.
Record persona, resolved provider/model, counts, durations, token totals, state, invocation source (model, review_button, auto_review), remediation source (fix_button, auto_fix, queued_message_override), countdown skipped/expired, barrier duration/outcome, entitlement denial, unavailable-model blocks, and error category. Never record prompts, code, reports, or tool contents.
src/pro/main/ipc/handlers/local_agent/tools/explore_code_subagent.ts — generalize into runSubagentTurn while preserving compiler-aware internals.src/pro/main/ipc/handlers/local_agent/tools/explore_code.ts — remove immediately after spawn_agent replaces it; no facade.src/pro/main/ipc/handlers/local_agent/subagents/ — manager, policies, runner, personas, models, context, mailbox, persistence, and tools.explore_code success, cancellation, progress, evidence, and report bounding.runSubagentTurn, persona definitions, schemas, and bounded context from the child-only Explorer loop.gpt-5.6-luna and gpt-5.6-sol to Dyad model metadata and implement exact-model blocking with setup guidance.gpt-5.6-luna with high reasoning.spawn_agent and remove the root explore_code tool, prompt references, snapshots, and duplicate code path in the same change.agent_threads and agent_messages schema/migration.send_message and followup_task with IDs, ordering, safe-boundary delivery, acknowledgement, and restart persistence.list_agents, wait_agents, and cancel_agent.enableExplorerSubagent with Settings UI/search/snapshots.enableAutoReview; keep manual Pro review independent of the setting.gpt-5.6-sol with medium reasoning and stable explicit targets.autoFixReviewIssues, synchronized between each findings panel and Settings.gpt-5.6-luna with high reasoning and automatic model invocation when enabled.explore_code after migration.autoFixReviewIssues.After npm run build:
gpt-5.6-luna with high reasoning.gpt-5.6-sol with medium reasoning and is never exposed to or autonomously started by the root model.enableAutoReview controls only deterministic application-triggered review.autoFixReviewIssues; the override never changes the stored setting.gpt-5.6-luna with high reasoning and may be automatically model-invoked only after its Pro-only setting is enabled.explore_code is removed when spawn_agent ships.| Risk | Impact | Mitigation |
|---|---|---|
| Read-only wrapper exposes a write path | High | Immutable policy, allowlist intersection, execution revalidation, escalation tests |
| Model starts Reviewer despite the invocation contract | High | Never include Reviewer in model schema/prompt; dedicated manual/application start path; audit invocation source |
| Review covers the wrong or moving change | High | One documented since-commit target, base/hash/files, writer serialization, and outdated state |
| Root and Implementer conflict | High | One Implementer, exclusive app lease, handled root write failure, root verification |
| Persona default model is unavailable for a provider/account | High | Resolve before scheduling, block without fallback, and provide visible setup guidance |
| Non-Pro user reaches sub-agent execution through stale state or replay | High | Entitlement in schema/prompt policy plus repeated IPC, manager, messaging, follow-up, and execution checks |
| Durable messaging duplicates or loses instructions | High | Durable IDs, ordered mailbox, acknowledgement, safe-boundary injection, idempotency tests |
| Parallel children multiply cost | Medium | Three-child concurrency cap, bounded context/reports, visible measured usage, and telemetry; no token/step/time budgets initially |
| Auto-review creates stale review backlog | Medium | Diff-hash identity, manual priority, same-hash reuse, latest-wins auto-review coalescing |
| Auto-fix and auto-review loop indefinitely | High | Tag remediation origin; permit one verification review; never recursively auto-fix its findings |
| Review barrier strands a queued user message | High | One scheduler-owned release path exercised by every terminal outcome; FIFO integration tests and visible barrier state |
| Forced queued-message auto-fix surprises users | Medium | Visible 10-second countdown, clear reason, Skip fix, unchanged persistent setting, and canonical consent |
| No transcript pruning grows the database | Medium | Bounded message/content fields, no token-delta/event persistence, cascade delete with chat, measure growth without silently pruning |
| Crash leaves stale status | Medium | Bounded shutdown and startup reconciliation to interrupted; retain partial state |
| Implementer setting seems like blanket approval | High | Default off, warning, clear two-gate copy, canonical existing consent |
Future write isolation—path-level leases versus worktrees—remains intentionally undecided and does not block the initial implementation. The first release uses one exclusive app-level writer lease. Revisit only if measured writer contention justifies multiple concurrent Implementers.