Skip to content

Commit 0c684aa

Browse files
feat(architect): settle the implementation route with grouped question rounds before planning (#113)
* feat(goal): sequential stacked plan-loop goals replace goal loops and feature groups - /goal (architect) resolves tasks read-only, saves a draft with goal-write, plans chunk 1 - first loop launched from the session attaches as chunk 1 and snapshots models, variants, sandbox settings and max iterations - on loop settled (after teardown commit) architect-auto plans the next chunk in a detached checkout of the previous chunk tip; the loop starts from that exact commit - goal-status (restart) / goal-cancel; failures pause, no boot mutation - loops record base_commit and accept baseRef; audit prompts scope to the base commit - remove execute-goal goal loops and launch-group feature groups (migrations 150-153) - dashboard Goals section; docs * fix(goal): apply review fixes for the goal orchestrator - orchestration uses a process-shared DB handle; planner session errors pause the goal - unique loop names skip goal-chunk names and existing forge branches; handle markChunkRunning throws - goal-status restart recovers a chunk whose loop row is gone and shows stalled chunk loop status - dashboard ships goalCount unscoped and goal detail only when scoped; batched chunk query; goal retention sweep - goal-complete marker via classifyArchitectOutput; derived architect-auto excludes; final-audit addendum defers to runtime scope - TUI reports goalId when chunk 1 starts; dead code and duplicate badge styles removed; docs and upgrade note * fix(loop): honor model variants and planner interrupt reasons * feat(architect): settle the implementation route with grouped question rounds before planning * fix(install): refresh pristine bundled prompts automatically * fix(goal): deny planner permission prompts and keep failed planner sessions Goal chunk planner sessions run unattended, so a permission ask (such as OpenCode's default external_directory and .env read asks) waited until OpenCode interrupted the planner after 60 minutes without activity. The permission.evaluate hook now denies asks from goal planner sessions before auto-approve applies. A planner session error now pauses the goal while keeping the planning chunk, its session, stored plan, and checkout for inspection, and names the session and checkout in the pause message. goal-status restart (no force) or goal cancel discards them. * fix(goal): deny planner permission asks only while the goal is running A planner session kept for inspection after its goal paused is attended, so its permission prompts are shown again. The deny now applies only to the planner session of a chunk still planning while its goal is running.
1 parent 668a40b commit 0c684aa

37 files changed

Lines changed: 393 additions & 94 deletions

‎docs/agents-and-commands.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ See also: [Tools](tools.md), [Configuration](configuration.md), [Loop System](lo
99
| Agent | Mode | Description |
1010
|---|---|---|
1111
| `code` | `all` | Primary implementation agent. |
12-
| `architect` | `primary` | Read-only planning agent. Authors and validates the stored plan with `plan-write`/`plan-edit` before approval. |
12+
| `architect` | `primary` | Read-only planning agent. Researches the goal, settles the implementation route with grouped `question` rounds, then authors and validates the stored plan with `plan-write`/`plan-edit` before approval. |
1313
| `auditor` | `subagent` | Read-only code review agent for convention-aware reviews. |
1414
| `auditor-loop` | `primary`, hidden | Internal auditor used by loop audit sessions. |
15-
| `architect-auto` | `primary`, hidden | Non-interactive planning agent that authors a stored plan from a single prompt, without `question` or execution tools. Used by the goal orchestrator to plan each subsequent goal chunk. |
15+
| `architect-auto` | `primary`, hidden | Non-interactive planning agent that authors a stored plan from a single prompt, without `question` or execution tools, following route decisions recorded in its brief. Used by the goal orchestrator to plan each subsequent goal chunk. |
1616

1717
Source: [`src/agents/index.ts`](../src/agents/index.ts), [`src/agents/architect.ts`](../src/agents/architect.ts), [`src/agents/auditor.ts`](../src/agents/auditor.ts).
1818

‎docs/api/_media/agents-and-commands.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ See also: [Tools](tools.md), [Configuration](configuration.md), [Loop System](lo
99
| Agent | Mode | Description |
1010
|---|---|---|
1111
| `code` | `all` | Primary implementation agent. |
12-
| `architect` | `primary` | Read-only planning agent. Authors and validates the stored plan with `plan-write`/`plan-edit` before approval. |
12+
| `architect` | `primary` | Read-only planning agent. Researches the goal, settles the implementation route with grouped `question` rounds, then authors and validates the stored plan with `plan-write`/`plan-edit` before approval. |
1313
| `auditor` | `subagent` | Read-only code review agent for convention-aware reviews. |
1414
| `auditor-loop` | `primary`, hidden | Internal auditor used by loop audit sessions. |
15-
| `architect-auto` | `primary`, hidden | Non-interactive planning agent that authors a stored plan from a single prompt, without `question` or execution tools. Used by the goal orchestrator to plan each subsequent goal chunk. |
15+
| `architect-auto` | `primary`, hidden | Non-interactive planning agent that authors a stored plan from a single prompt, without `question` or execution tools, following route decisions recorded in its brief. Used by the goal orchestrator to plan each subsequent goal chunk. |
1616

1717
Source: [`src/agents/index.ts`](../src/agents/index.ts), [`src/agents/architect.ts`](../src/agents/architect.ts), [`src/agents/auditor.ts`](../src/agents/auditor.ts).
1818

‎docs/api/_media/architecture.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ A `PATCHED_SESSIONS` set deduplicates retries. Audit-only subagents use the stri
195195
- `location.shutdown` - Run cleanup when the location shuts down
196196
- Plan approval events via `createPlanApprovalEventHook`
197197
- Plan capture from streaming message parts via `createPlanCaptureEventHook`
198-
- Goal planner idle and error events via `createGoalPlannerEventHook` (`src/hooks/goal-orchestrator.ts`) — routes a chunk planner session's idle event to the goal orchestrator; ownership is proven by the persisted planner-session row, so an unrelated session's idle is ignored. A normalized `session.error` for the same session (a provider failure or an interrupted execution) is routed to `onPlannerError`, which abandons the chunk and pauses the goal; a premature idle that yields no output keeps the chunk planning.
198+
- Goal planner idle and error events via `createGoalPlannerEventHook` (`src/hooks/goal-orchestrator.ts`) — routes a chunk planner session's idle event to the goal orchestrator; ownership is proven by the persisted planner-session row, so an unrelated session's idle is ignored. A normalized `session.error` for the same session (a provider failure or an interrupted execution) is routed to `onPlannerError`, which pauses the goal and keeps the planning chunk, its planner session, stored plan, and detached checkout for inspection; a later `goal-status restart` discards them and re-plans the chunk, and cancelling the goal cleans them up. A premature idle that yields no output keeps the chunk planning.
199199

200200
### Additional Hooks
201201

‎docs/api/_media/loop-system.md‎

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -292,9 +292,10 @@ A **goal** is an objective executed as a sequential series of ordinary plan loop
292292
The `/goal <objective>` command runs as the `architect` agent. It:
293293

294294
1. Resolves the work strictly read-only with the tools already in the session (`gh issue list`/`gh issue view`, a tracker's MCP tools, or a plain task breakdown), never commenting on, assigning, labelling, transitioning, or closing a ticket.
295-
2. Orders the tasks dependencies-first, **one task per chunk** (two tickets merge into one task only when they cannot be implemented separately).
296-
3. Saves the draft with `goal-write` — title, objective, the ordered task list, and an optional `maxChunks`.
297-
4. Plans **chunk 1 only** with `plan-write`/`plan-edit`, naming the goal and the task ref in the Objective.
295+
2. Settles the route after researching the goal: the objective is the fixed outcome, so the rounds settle how to reach it. It calls `question` with one grouped set of questions that settles the route — one per decision group (placement and ownership, reuse versus extending versus new code, data and interface shape, edge-case and error policy, sequencing, verification strategy, plus any group research surfaced), each with 2–4 research-backed options and the recommended one first — and repeats a fresh grouped round after each answer set, re-researching newly opened paths, until every route decision is settled. These rounds also cover chunk 1.
296+
3. Orders the tasks dependencies-first, **one task per chunk** (two tickets merge into one task only when they cannot be implemented separately).
297+
4. Saves the draft with `goal-write` — title, objective (the objective verbatim followed by a `Route decisions:` list of every settled route decision), the ordered task list, and an optional `maxChunks`. Later `architect-auto` chunks treat those recorded route decisions as binding.
298+
5. Plans **chunk 1 only** with `plan-write`/`plan-edit`, naming the goal and the task ref in the Objective.
298299

299300
Later chunks are never planned up front: each is planned against the code the previous chunks produced.
300301

@@ -314,14 +315,14 @@ On `settled` for a **completed** chunk loop:
314315

315316
1. The chunk is marked `completed` with a capped summary — the loop's `completionSummary`, else its `postActionReport`, truncated to `GOAL_CHUNK_SUMMARY_MAX_CHARS`.
316317
2. A detached, read-only planning checkout of the previous chunk branch tip is created at `<dataDir>/worktrees/.goal-plan-<goalId>-<chunkIndex>`.
317-
3. `architect-auto` plans the next task there with the goal's **auditor** model and variant; the planner prompt carries the objective, the ordered task list, and the completed-chunk summaries. The planner either:
318+
3. `architect-auto` plans the next task there with the goal's **auditor** model and variant; the planner prompt carries the objective, the ordered task list, and the completed-chunk summaries. While the goal is running the planner runs unattended, so any request OpenCode would `ask` about is denied before per-session or sandbox auto-approve applies, with `Blocked in goal planning: the chunk planner runs unattended, so requests that need approval are denied. Continue without it and report what was skipped.` OpenCode `allow` and `deny` decisions are untouched. A planner session kept for inspection after the goal pauses is no longer unattended and prompts normally. The planner either:
318319
- **writes a plan** — the chunk loop launches with `baseRef` set to that exact commit and the snapshotted settings, so each chunk gets its own branch stacked on the previous chunk's commit;
319320
- **emits `<!-- forge-goal:complete -->`** (`GOAL_COMPLETE_MARKER`) — the goal is marked `completed`;
320321
- **fails** — the goal is **paused** with the reason.
321322

322323
Why a plain detached checkout instead of a Forge workspace: a Forge workspace with no matching loop row is reclaimed by the stale-workspace sweep after a 5-minute grace window, so a planning workspace would be removed out from under the planner.
323324

324-
Planner failures pause the goal. A normalized `session.error` for a chunk planner session — a provider failure or an interrupted/aborted execution — abandons the chunk and pauses the goal with `Planner failed for chunk <n>: <reason>`. A premature idle that yields neither assistant text nor a stored plan leaves the chunk planning rather than failing it.
325+
Planner failures pause the goal. A normalized `session.error` for a chunk planner session — a provider failure or an interrupted/aborted execution — or an execution OpenCode interrupts for inactivity pauses the goal with `Planner failed for chunk <n>: <reason> (kept planner session <id> and checkout <dir> for inspection; goal-status restart discards them and re-plans chunk <n>)`. The chunk stays in stage `planning`, and its planner session, stored plan, and detached planning checkout are kept for inspection; `goal-status restart` (no `force` needed) discards them and re-plans the chunk, and cancelling the goal also cleans them up. A later idle for that session is a no-op because the goal is paused. Other planning failures — no stored plan, an insufficient brief, or a loop start failure — still discard the planning resources. A premature idle that yields neither assistant text nor a stored plan leaves the chunk planning rather than failing it.
325326

326327
### Pause, resume, restart, and cancel
327328

‎docs/api/_media/sandbox.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ Commands the user runs directly — `!` commands and terminals — do not pass t
6868

6969
When the host sandbox is turned on for a session (the `Host sandbox` menu in the TUI, which writes a desired state through the Forge server RPC and waits for the server's applied acknowledgement), Forge resolves that session's permission prompts automatically, and those of its Task subagents. It does this through OpenCode's `permission.evaluate` hook: a decision that would ask is resolved to allow or deny rather than shown. It is denied when it matches an `autoApprove.deny` rule; otherwise it is allowed, including requests that match an explicit OpenCode `ask` rule. OpenCode `deny` rules still deny. This applies only while the session's shell calls actually route into a running sandbox. A request that arrives during initial sandbox startup waits for that resolution: if the sandbox becomes active it is auto-approved under the same policy; otherwise the prompt is shown as usual. When the sandbox is off or failed to start, prompts are shown as usual unless a per-session auto-approve is separately enabled. The attached server's `sandbox.enabled` decides whether the toggle is available at all.
7070

71-
OpenCode `deny` rules still apply: they settle before the hook runs. Loop sessions are unaffected because their permission ruleset already allows everything it doesn't deny.
71+
OpenCode `deny` rules still apply: they settle before the hook runs. Loop sessions are unaffected because their permission ruleset already allows everything it doesn't deny. Goal chunk planner sessions are the exception while their goal is running: a request OpenCode would `ask` about is denied before this policy applies, because the planner runs unattended (see [Loop System → Goals](loop-system.md#goals)).
7272

7373
The same allow/deny policy also applies to the per-session `Toggle auto-approve`, which works whether or not a sandbox is on — see [TUI → Sidebar](tui.md#sidebar).
7474

‎docs/api/_media/troubleshooting.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,12 @@ Worktree loops require a git repository with at least one commit. If OpenCode st
2424

2525
## A goal is paused or stuck planning
2626

27-
A goal pauses instead of retrying when a chunk loop ends not-completed, when the chunk planner fails to produce a plan or its session errors, or when the chunk cap is reached. Plugin boot never mutates goals, so a plugin restart while a chunk was planning leaves the goal `running` with a stale `planning` chunk (or a chunk whose loop ended without a `settled` notification), not paused. `goal-status <goalId>` shows the goal and each chunk's stage, and `goal-status restart` recovers it (`force` discards planning still marked in flight).
27+
A goal pauses instead of retrying when a chunk loop ends not-completed, when the chunk planner fails to produce a plan or its session errors, or when the chunk cap is reached. When the chunk planner's session errors (a provider failure or an interrupted execution), the goal pauses and keeps the planner session, stored plan, and planning checkout, named in the pause message for inspection. While the goal is running a planner session runs unattended, so any request OpenCode would prompt for is denied rather than shown; a planner session kept for inspection after the goal pauses prompts normally. Plugin boot never mutates goals, so a plugin restart while a chunk was planning leaves the goal `running` with a stale `planning` chunk (or a chunk whose loop ended without a `settled` notification), not paused. `goal-status <goalId>` shows the goal and each chunk's stage, and `goal-status restart` recovers it (`force` discards planning still marked in flight).
2828

2929
Recover with `goal-status restart`:
3030

3131
- `goal-status goalId=<goalId> restart=true` re-plans an interrupted planning chunk, or catches a completed chunk loop whose `settled` notification never advanced the goal.
32+
- After a planner-session error, `restart=true` (no `force`) discards the kept planner session, stored plan, and planning checkout and re-plans that chunk; `goal-cancel` also cleans them up.
3233
- Add `force=true` when the chunk is still marked as planning in flight and you want to discard it (for example after the planner session was lost).
3334
- Add `maxChunks=<n>` when the goal paused at its chunk cap.
3435
- A paused goal whose chunk loop ended cancelled, errored, or stalled resumes automatically once you restart that loop (`loop-status name=<loop> restart=true`) and it completes.

‎docs/api/_media/tui.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ The sidebar shows the Forge title (with version when `tui.showVersion` is on) an
2929

3030
When sandboxing is configured, the sidebar displays the current session's msb state as `· MSB enabled/disabled/loading/failed` next to the Forge title. The `Toggle sandbox` palette command, and optional `tui.keybinds.toggleHostSandbox` binding, open the [host sandbox menu](#host-sandbox-menu), which requests a desired sandbox state for the current session and its Task subagents through the Forge server RPC; the server reconciles the request, acknowledges the applied revision, and routes the session's shell, `glob`, and `grep` calls into the sandbox. The server's `sandbox.enabled` decides availability, so the indicator is hidden and the request is refused when the attached server has sandboxing disabled. While it is on, permission prompts in those sessions are resolved to allow or deny without prompting unless `sandbox.autoApprovePermissions` is `false` (see [Sandbox](sandbox.md#permission-auto-approval)). The TUI also follows replacement code and auditor sessions when a loop rotates, but does not follow unrelated subagent sessions.
3131

32-
When auto-approve is on for the current session, including when it is inherited from a parent session, the sidebar displays `· AUTO` next to the Forge title. The `Toggle auto-approve` palette command, and optional `tui.keybinds.toggleAutoApprove` binding, turn it on or off for the current session and its Task subagents; in a subagent that inherits it, the toggle is refused and names the parent session to toggle instead. The TUI reads and changes the state through the Forge server RPC rather than the local Forge database, so it also works when the TUI is attached to a remote OpenCode server running Forge. It is applied server-side through OpenCode's `permission.evaluate` hook, so it follows the session and its subagents regardless of which TUI is showing it, and it expires after 15 days without session activity (prompts and auto-approved requests). While on, nothing prompts: a request OpenCode would `ask` is resolved to deny or allow. It is denied when it matches an `autoApprove.deny` rule (see [Configuration → Auto-Approve](configuration.md#auto-approve)); otherwise it is allowed, including requests that match an explicit OpenCode `ask` rule. OpenCode `deny` rules still deny. It is refused for loop sessions, whose ruleset already allows everything not denied, and enabling it while the host sandbox is off warns that approved commands run directly on the machine. Unlike OpenCode's built-in Auto mode (`session.permissions: "autoaccept"`), which approves requests from every session on the server, this is scoped to the one session and its subagents; keep the built-in setting at `"prompt"`.
32+
When auto-approve is on for the current session, including when it is inherited from a parent session, the sidebar displays `· AUTO` next to the Forge title. The `Toggle auto-approve` palette command, and optional `tui.keybinds.toggleAutoApprove` binding, turn it on or off for the current session and its Task subagents; in a subagent that inherits it, the toggle is refused and names the parent session to toggle instead. The TUI reads and changes the state through the Forge server RPC rather than the local Forge database, so it also works when the TUI is attached to a remote OpenCode server running Forge. It is applied server-side through OpenCode's `permission.evaluate` hook, so it follows the session and its subagents regardless of which TUI is showing it, and it expires after 15 days without session activity (prompts and auto-approved requests). While on, nothing prompts: a request OpenCode would `ask` is resolved to deny or allow. It is denied when it matches an `autoApprove.deny` rule (see [Configuration → Auto-Approve](configuration.md#auto-approve)); otherwise it is allowed, including requests that match an explicit OpenCode `ask` rule. OpenCode `deny` rules still deny. Goal chunk planner sessions are exempt while their goal is running: a request OpenCode would `ask` about is denied before auto-approve applies, since the planner runs unattended. It is refused for loop sessions, whose ruleset already allows everything not denied, and enabling it while the host sandbox is off warns that approved commands run directly on the machine. Unlike OpenCode's built-in Auto mode (`session.permissions: "autoaccept"`), which approves requests from every session on the server, this is scoped to the one session and its subagents; keep the built-in setting at `"prompt"`.
3333

3434
## Additional Commands
3535

0 commit comments

Comments
 (0)