Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ Forge provides these tool groups:

- **Plan tools** — `plan-write`, `plan-edit`, `plan-read`, `section-read`, `plan-adjust`
- **Review tools** — `review-write`, `review-read`, `review-delete`
- **Loop tools** — `execute-plan`, `execute-goal`, `loop-cancel`, `loop-status`
- **Loop tools** — `execute-plan`, `execute-goal`, `loop-cancel`, `loop-status`, `loop-migrate`
- **Sandbox routing** — native `bash`, `glob`, and `grep` tools route into msb for sandboxed sessions

Loops always run in an isolated git worktree; msb is used when enabled, configured, and available.
Expand All @@ -170,6 +170,7 @@ Loops always run in an isolated git worktree; msb is used when enabled, configur
| `execute-goal` | Execute a free-text goal in rotating dedicated code and auditor sessions inside an isolated git worktree. Args: `goal` required; `title`, `loopName`, `maxIterations` optional. |
| `loop-cancel` | Cancel an active loop by worktree name |
| `loop-status` | List active/recent loops or get detailed status by worktree name, including cumulative token usage when available. Supports `restart=true` to restart any non-completed loop (`running`, `cancelled`, `errored`, `stalled`). Completed loops are history-only and cannot be restarted. |
| `loop-migrate` | Move a loop to a configured remote opencode server, preserving phase, section progress, section summaries, and review findings. |

`execute-plan` reads the current session's captured plan when `plan` is omitted. `maxIterations`, execution model, auditor model, and sandbox behavior come from configuration or the TUI execution dialog, not direct `execute-plan` tool arguments.

Expand All @@ -183,6 +184,7 @@ Loops always run in an isolated git worktree; msb is used when enabled, configur
| `/execute-goal` | Execute a free-text goal in dedicated worktree sessions until an audit leaves no findings | code |
| `/loop-status` | Check status of all active loops | code |
| `/loop-cancel` | Cancel the active loop | code |
| `/loop-migrate` | Migrate a loop to a remote opencode server | code |
| `/launch-group` | Decompose a PRD or feature list into features and launch them as parallel planning + development loops | code |

## Configuration
Expand Down Expand Up @@ -473,8 +475,8 @@ On model errors during execution, automatic fallback to the default model kicks

### Management

- **Slash commands**: `/execute-plan` to start, `/loop-cancel` to cancel
- **Tools**: `execute-plan` to start with parameters, `loop-status` for checking progress (with restart capability), `loop-cancel` to cancel
- **Slash commands**: `/execute-plan` to start, `/loop-cancel` to cancel, `/loop-migrate` to move a loop to a remote server
- **Tools**: `execute-plan` to start with parameters, `loop-status` for checking progress (with restart capability), `loop-cancel` to cancel, `loop-migrate` to move a loop to a configured remote

### Loop termination

Expand Down
2 changes: 2 additions & 0 deletions docs/agents-and-commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ Excluded tools:
- `execute-goal`
- `loop-cancel`
- `loop-status`
- `loop-migrate`

Source: [`AUDITOR_TOOL_EXCLUDES`](../src/agents/auditor.ts).

Expand All @@ -52,5 +53,6 @@ Source: [`AUDITOR_TOOL_EXCLUDES`](../src/agents/auditor.ts).
| `/execute-goal` | Execute a goal in rotating dedicated code and auditor sessions inside an isolated worktree. | `code` | no |
| `/loop-status` | Check status of all active loops. | `code` | no |
| `/loop-cancel` | Cancel the active loop. | `code` | no |
| `/loop-migrate` | Migrate a loop to a configured remote opencode server. | `code` | no |

Source: [`buildPluginCommands()`](../src/config.ts).
8 changes: 5 additions & 3 deletions docs/api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ Forge provides these tool groups:

- **Plan tools** — `plan-write`, `plan-edit`, `plan-read`, `section-read`, `plan-adjust`
- **Review tools** — `review-write`, `review-read`, `review-delete`
- **Loop tools** — `execute-plan`, `execute-goal`, `loop-cancel`, `loop-status`
- **Loop tools** — `execute-plan`, `execute-goal`, `loop-cancel`, `loop-status`, `loop-migrate`
- **Sandbox routing** — native `bash`, `glob`, and `grep` tools route into msb for sandboxed sessions

Loops always run in an isolated git worktree; msb is used when enabled, configured, and available.
Expand All @@ -172,6 +172,7 @@ Loops always run in an isolated git worktree; msb is used when enabled, configur
| `execute-goal` | Execute a free-text goal in rotating dedicated code and auditor sessions inside an isolated git worktree. Args: `goal` required; `title`, `loopName`, `maxIterations` optional. |
| `loop-cancel` | Cancel an active loop by worktree name |
| `loop-status` | List active/recent loops or get detailed status by worktree name, including cumulative token usage when available. Supports `restart=true` to restart any non-completed loop (`running`, `cancelled`, `errored`, `stalled`). Completed loops are history-only and cannot be restarted. |
| `loop-migrate` | Move a loop to a configured remote opencode server, preserving phase, section progress, section summaries, and review findings. |

`execute-plan` reads the current session's captured plan when `plan` is omitted. `maxIterations`, execution model, auditor model, and sandbox behavior come from configuration or the TUI execution dialog, not direct `execute-plan` tool arguments.

Expand All @@ -185,6 +186,7 @@ Loops always run in an isolated git worktree; msb is used when enabled, configur
| `/execute-goal` | Execute a free-text goal in dedicated worktree sessions until an audit leaves no findings | code |
| `/loop-status` | Check status of all active loops | code |
| `/loop-cancel` | Cancel the active loop | code |
| `/loop-migrate` | Migrate a loop to a remote opencode server | code |
| `/launch-group` | Decompose a PRD or feature list into features and launch them as parallel planning + development loops | code |

## Configuration
Expand Down Expand Up @@ -475,8 +477,8 @@ On model errors during execution, automatic fallback to the default model kicks

### Management

- **Slash commands**: `/execute-plan` to start, `/loop-cancel` to cancel
- **Tools**: `execute-plan` to start with parameters, `loop-status` for checking progress (with restart capability), `loop-cancel` to cancel
- **Slash commands**: `/execute-plan` to start, `/loop-cancel` to cancel, `/loop-migrate` to move a loop to a remote server
- **Tools**: `execute-plan` to start with parameters, `loop-status` for checking progress (with restart capability), `loop-cancel` to cancel, `loop-migrate` to move a loop to a configured remote

### Loop termination

Expand Down
2 changes: 2 additions & 0 deletions docs/api/_media/agents-and-commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ Excluded tools:
- `execute-goal`
- `loop-cancel`
- `loop-status`
- `loop-migrate`

Source: [`AUDITOR_TOOL_EXCLUDES`](../src/agents/auditor.ts).

Expand All @@ -52,5 +53,6 @@ Source: [`AUDITOR_TOOL_EXCLUDES`](../src/agents/auditor.ts).
| `/execute-goal` | Execute a goal in rotating dedicated code and auditor sessions inside an isolated worktree. | `code` | no |
| `/loop-status` | Check status of all active loops. | `code` | no |
| `/loop-cancel` | Cancel the active loop. | `code` | no |
| `/loop-migrate` | Migrate a loop to a configured remote opencode server. | `code` | no |

Source: [`buildPluginCommands()`](../src/config.ts).
2 changes: 1 addition & 1 deletion docs/api/_media/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ OpenCode Forge integrates with OpenCode through several hook points. The plugin
Loops are autonomous and cannot answer permission prompts, but OpenCode's default subagent ruleset falls back to `ask` for most tools. To prevent deadlocks, `createLoopPermissionRejectHook` listens for `session.created` events. When the new session resolves to an active loop, the hook calls `v2.session.update()` to overwrite the child session's `permission` ruleset:

- If the parent session has an allow-all ruleset (e.g. an auditor subagent), the parent's ruleset is inherited so the child stays under the same constraints.
- Otherwise the default loop ruleset from `buildLoopPermissionRuleset()` (`src/constants/loop.ts`) is applied — blanket allow-all inside the worktree, with explicit structural denies for `external_directory`, `review-write`, `review-delete`, `plan`, `plan_enter`, `plan_exit`, `plan-write`, `plan-edit`, `execute-plan`, `execute-goal`, `question`, `loop-cancel`, `loop-status`, `launch-group`, `group-status`, `group-cancel`. User-configured `loop.permissions` rules are layered in after the external-directory allows and before these structural denies (via `resolveLoopPermissionOptions`), so they can tailor user tools without overriding a structural deny.
- Otherwise the default loop ruleset from `buildLoopPermissionRuleset()` (`src/constants/loop.ts`) is applied — blanket allow-all inside the worktree, with explicit structural denies for `external_directory`, `review-write`, `review-delete`, `plan`, `plan_enter`, `plan_exit`, `plan-write`, `plan-edit`, `execute-plan`, `execute-goal`, `question`, `loop-cancel`, `loop-status`, `loop-migrate`, `launch-group`, `group-status`, `group-cancel`. User-configured `loop.permissions` rules are layered in after the external-directory allows and before these structural denies (via `resolveLoopPermissionOptions`), so they can tailor user tools without overriding a structural deny.

A `PATCHED_SESSIONS` set deduplicates retries. Audit-only subagents use the stricter `buildAuditSessionPermissionRuleset()` (blanket allow-all with structural denies for the direct mutation tools `edit`/`write`/`multiedit`/`apply_patch`, plus the shared plan/loop structural denies).

Expand Down
13 changes: 11 additions & 2 deletions docs/api/_media/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ Configured rules are layered into the ruleset in this order:

Because configured rules sit between the external-directory allows and Forge's structural denies, they can deny user tools but can never override a structural deny.

**Forge-managed permissions are rejected** (ignored with a warning at plugin load, surfaced in the log and a one-time TUI toast). These include `*`, `external_directory`, and the structural denies: `plan`, `plan_enter`, `plan_exit`, `plan-write`, `plan-edit`, `execute-plan`, `execute-goal`, `question`, `loop-cancel`, `loop-status`, `launch-group`, `group-status`, `group-cancel`, `review-write`, `review-delete`, `edit`, `write`, `multiedit`, and `apply_patch`. An unattended loop that can call `question` would hang forever; allowing `plan-write`/`review-write` would corrupt the plan-of-record and audit channels; and `execute-plan`/`loop-*`/`group-*` would let a loop recurse into itself or manage other loops.
**Forge-managed permissions are rejected** (ignored with a warning at plugin load, surfaced in the log and a one-time TUI toast). These include `*`, `external_directory`, and the structural denies: `plan`, `plan_enter`, `plan_exit`, `plan-write`, `plan-edit`, `execute-plan`, `execute-goal`, `question`, `loop-cancel`, `loop-status`, `loop-migrate`, `launch-group`, `group-status`, `group-cancel`, `review-write`, `review-delete`, `edit`, `write`, `multiedit`, and `apply_patch`. An unattended loop that can call `question` would hang forever; allowing `plan-write`/`review-write` would corrupt the plan-of-record and audit channels; and `execute-plan`/`loop-*`/`group-*` would let a loop recurse into itself or manage other loops.

**Blanket denies of Forge-required permissions are rejected too**: `review-read`, `plan-read`, `section-read`, `plan-adjust`, `bash`, and `read` may not be denied outright. A loop that cannot read its findings, section plan, or plan-of-record — or cannot run `bash` or `read` at all — cannot do its job and would silently burn iterations to `maxIterations` with nothing pointing at the config. Only the blanket form (a bare tool name, or pattern `*`) is rejected; a scoped deny such as `{ "permission": "bash", "pattern": "git push *" }` is honoured.

Expand Down Expand Up @@ -232,9 +232,18 @@ Example:
3. The remote server creates the loop worktree pinned to that exact SHA, fetching the sync ref when the commit is not yet in its clone.
4. On final loop teardown, the remote deletes the sync ref from the shared git remote (restart-preserving teardowns keep it). If a loop is deleted outside normal teardown, remove leftovers manually with `git push <gitRemote> --delete refs/forge/<loopName>`.

### Migrating a running loop

The `loop-migrate` tool (and `/loop-migrate`) moves an existing local loop to a remote server. It reuses the same remote discovery and sync-ref mechanics as the TUI launch above, with two differences:

- The pushed sync ref `refs/forge/<remoteLoopName>` points at the **loop branch tip** (`refs/heads/<loopBranch>`), not local `HEAD` — the work done inside the loop travels with the migration even though it was never merged.
- The remote worktree is pinned to that loop-branch SHA via `startRef`, and the loop resumes from a snapshot of its phase, section progress, and review findings.

The local loop is terminated as `migrated: <remote>` and further management (including `loop-status` and `loop-cancel`) happens on the remote server; see [Tools → `loop-migrate`](tools.md#loop-migrate) for eligibility, rollback, and local force-restart. The observability caveat below applies — the migrated loop disappears from the local sidebar, `loop-status`, and dashboard.

### Caveats

- **Version skew**: the remote server must run a forge version with SHA-pin support (`startRef`/`syncRef` handling — the same release that introduced `remotes`, or newer). An older remote silently ignores the pin and runs the loop from its clone's current `HEAD` with no error on either side.
- **Version skew**: the remote server must run a forge version with SHA-pin support (`startRef`/`syncRef` handling — the same release that introduced `remotes`, or newer). An older remote silently ignores the pin and runs the loop from its clone's current `HEAD` with no error on either side. `loop-migrate` additionally requires the remote to understand the `forgeLoop.resume` snapshot (the release that introduced `loop-migrate`, or newer); an older remote ignores it and starts the loop from section 0 while the first prompt already targets the migrated phase.
- **Sandbox mirroring**: `remotes[].sandbox` is a local assertion about the remote's capability. The launch bakes the session's shell permission ruleset from it; if it does not match the remote's real sandbox state, loop shell commands can be denied.
- **Observability**: remote loops run entirely on the remote server. They do not appear in the local sidebar, `loop-status`, or dashboard. Results land on the `forge/<loopName>` branch in the remote machine's clone; fetch or push that branch from the remote to retrieve them.

Expand Down
3 changes: 2 additions & 1 deletion docs/api/_media/loop-system.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,14 @@ The loop system provides autonomous iterative development with automatic code au
- **Any non-completed loop is restartable** via explicit restart when the worktree is available.
- Restartable statuses: `running`, `cancelled`, `errored`, `stalled`.
- **Completed loops are history-only** and cannot be restarted.
- **`migrated` loops require force** — a loop migrated to a remote server with `loop-migrate` is terminated with the terminal reason `migrated: <remote>`; it restarts locally only with `force=true`, since a copy may be running on the remote. See [Tools → `loop-migrate`](tools.md#loop-migrate).
- **Missing worktree blocks restart** — the worktree directory must exist for restart to proceed.

### Restart Semantics

- Restart preserves loop identity, plan, worktree path, section progress, and review findings.
- Restart resets iteration count and error budget.
- Restart creates a fresh session and resumes from the persisted phase and section index.
- Restart creates a fresh session and resumes from the persisted phase and section index. The phase/prompt selection for the resumed session is shared with the remote migration path: a loop migrated with `loop-migrate` starts on the remote from the same phase-appropriate prompt plan a local restart would build.

### Stale Workspace Sweep

Expand Down
23 changes: 23 additions & 0 deletions docs/api/_media/tools.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ See also: [Agents and Slash Commands](agents-and-commands.md), [Configuration](c
| `execute-goal` | Start a managed goal loop in a dedicated code session inside an isolated Forge worktree. | [`src/tools/loop.ts`](../src/tools/loop.ts) |
| `loop-cancel` | Cancel an active loop. | [`src/tools/loop.ts`](../src/tools/loop.ts) |
| `loop-status` | List loops, inspect one loop, or restart a restartable loop. | [`src/tools/loop.ts`](../src/tools/loop.ts) |
| `loop-migrate` | Move a loop to a configured remote opencode server, preserving its progress. | [`src/tools/loop.ts`](../src/tools/loop.ts) |
| `launch-group` | Launch a group of features (from a PRD or a pre-split list), each planned and run as its own loop, scheduled with a concurrency cap. | [`src/tools/group.ts`](../src/tools/group.ts) |
| `group-status` | List groups, inspect one group's per-feature stages, or restart a non-completed group. | [`src/tools/group.ts`](../src/tools/group.ts) |
| `group-cancel` | Cancel a group, optionally cancelling its running loops. | [`src/tools/group.ts`](../src/tools/group.ts) |
Expand Down Expand Up @@ -187,6 +188,28 @@ Arguments:

Completed loops are history-only and cannot be restarted. See [Loop System](loop-system.md#restartability).

### `loop-migrate`

Moves a loop to a configured remote opencode server (see [Configuration → Remotes](configuration.md#remotes)). The local loop is terminated with the terminal reason `migrated: <remote>` and the loop continues on the remote from the pushed loop branch tip. The local worktree and branch stay in place; a migrated loop can still be restarted locally, but only with `loop-status restart=true force=true`, because a copy may be running on the remote.

Eligibility: only worktree-backed loops that are not part of a feature group can be migrated. Project-directory loops (`execute-plan mode: new-session`) and feature-group loops are refused before any side effect, as are completed loops and loops already marked `migrated`.

What is carried over:

- The loop's phase, section pointers (`currentSectionIndex`/`totalSections`/`finalAuditDone`), section plan rows, section summaries, and review findings travel as a resume snapshot in the remote workspace's `forgeLoop` extra. The snapshot is validated on the remote; an invalid or incompatible snapshot removes the remote workspace instead of starting a fresh loop, and the local loop can then be force-restarted.
- The loop's original plan text is forwarded, so restartability display and legacy non-sectioned resume keep working on the remote.
- The execution/auditor models and variants are forwarded unchanged.
- The remote loop name is reserved (the local name is kept when available). The remote session's portable permission rules are resolved from the local loop's workspace — configured `loop.permissions` merged with any portable rules the loop itself arrived with — without host-specific external directories.

Failure semantics: every pre-freeze failure (unknown remote, no matching project, unreachable server, ineligible loop) leaves the local loop untouched. The freeze re-checks the loop under its state lock and refuses if the loop changed state meanwhile. After the freeze, the loop's worktree must be clean (the teardown commit must have landed); a dirty worktree, a failed snapshot, branch-tip resolve, push, or remote launch rolls back: the sync-ref push is deleted (best effort) and the local loop is restored to its previous terminal status (`cancelled`, `errored`, or `stalled`) or relabelled `cancelled` when it was running, so it stays restartable with `loop-status restart=true`. Only the success path leaves the loop marked `migrated`.

Arguments:

| Argument | Description |
|---|---|
| `name` | Required loop name (or branch) to migrate. |
| `remote` | Required configured `remotes[].name` to migrate to. |

> Group, loop, and plan tools are denied inside loop and audit sessions so an in-flight loop cannot recursively spawn more work.

## Group Tools
Expand Down
2 changes: 1 addition & 1 deletion docs/api/functions/createForgePlugin.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

> **createForgePlugin**(`config`): `Plugin`

Defined in: [index.ts:290](https://github.com/chriswritescode-dev/opencode-forge/blob/dc0dfab121ce8e8b3901dbd0f2cb4632f5f3e52c/src/index.ts#L290)
Defined in: [index.ts:290](https://github.com/chriswritescode-dev/opencode-forge/blob/de5f48e9cdfb630dd2d5275361e34f2dbbea5aa1/src/index.ts#L290)

Creates an OpenCode plugin instance with loop management and sandboxing.

Expand Down
Loading