@sorenllm/opencode-forge 0.3.4 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +185 -166
  2. package/dist/index.js +706 -1717
  3. package/package.json +64 -61
package/README.md CHANGED
@@ -33,8 +33,10 @@ OpenSpec spec workflows remain a third, separate lane.
33
33
  - Plan files: `.opencode/plan/<date>-<slug>.md` in your project, frontmatter
34
34
  state machine `draft → approved → done` (exit: `abandoned`).
35
35
  - While a plan is in draft, `write` / `edit` / `bash` / `task` are **denied
36
- at the permission layer** — including your own `allow` config. The only
37
- exits are approval and discard. This is deliberate; see Design stance.
36
+ at the permission layer** — including your own `allow` config, and for
37
+ every agent in that session (the draft protects shared session state; see
38
+ Agent partition below). The only exits are approval and discard. This is
39
+ deliberate; see Design stance.
38
40
  - `plan_approve` / `plan_close` are pinned to a confirmation dialog: the
39
41
  model can never flip the state itself.
40
42
  - Goal files: `.opencode/goal/<date>-<slug>.md`, state machine
@@ -42,9 +44,40 @@ OpenSpec spec workflows remain a third, separate lane.
42
44
  session plus a workspace queue. Completion is **verified by the plugin**,
43
45
  not attested by the model: `goal_complete` re-executes every check itself
44
46
  and refuses (fail-closed) on any failure.
45
- - The native `build` / `plan` agents are hidden while the plugin is loaded
46
- (runtime injection, nothing written to your config). Uninstall restores
47
- them automatically; plan and goal files are never deleted.
47
+ - The native `build` / `plan` agents **stay installed and independent**:
48
+ forge coexists with them in the Tab cycle. The plugin hard-partitions the
49
+ two worlds (see Agent partition below) instead of hiding anything; plan
50
+ and goal files are never deleted on uninstall.
51
+
52
+ ## Agent partition (who sees what)
53
+
54
+ The plugin draws a hard line between two agent families, enforced at the
55
+ tool layer (verified against the host's `tools` filter):
56
+
57
+ | | **forge family** — `forge` + your forge.json `forge-*` workers | **everyone else** — native `build` / `plan` / `general` / `explore` and your own agents |
58
+ | --- | --- | --- |
59
+ | exec surface | `forge_shell` / `forge_jobs` only; builtin `shell`/`bash` hidden (unconditional, probe-independent; covers user-defined `agent.forge` entries too) | builtin `shell`/`bash`, fully native |
60
+ | forge tools (`plan_*`, `goal_*`, `crew_*`, `forge_shell`, `forge_jobs`) | `forge` sees all; `forge-*` workers only `forge_shell` + `forge_jobs` | none visible |
61
+ | dispatching `forge-*` via `task` | allowed | refused at the tool layer |
62
+ | writing `.opencode/plan/` / `.opencode/goal/` | allowed (state machine governs) | refused at the tool layer (reads stay open) |
63
+ | forge system prompts (job guidance, plan/goal notices) | injected | never injected — zero forge text |
64
+ | watchdog (stuck builtin shell) | governed | untouched — no markers, no kills |
65
+
66
+ Escape hatches: `jobs.keepBuiltinShell: true` gives the forge family the
67
+ builtin shell back; `jobs.mode: "native"` retires `forge_shell`/`forge_jobs`
68
+ entirely (the hide is withdrawn in the same stroke). Mixed sessions (Tab
69
+ between agents in one session): harness state (plans/goals) stays
70
+ session-bound, tool surfaces follow the current speaker, and the goal loop
71
+ parks instead of driving a non-forge turn. One deliberate exception: a
72
+ draft-phase write ban binds the **session** (protecting shared state), so a
73
+ non-forge agent is write-blocked until the draft is approved or discarded in
74
+ forge. Running `/plan` `/goal` `/crew` under a non-forge agent redirects to
75
+ forge instead of executing.
76
+
77
+ **Default subject.** The host otherwise defaults new sessions to `build`
78
+ once a build agent exists — the plugin pins `default_agent: "forge"`
79
+ automatically (a `default_agent` you set yourself always wins), so bare
80
+ runs and fresh sessions still land on forge under coexistence.
48
81
 
49
82
  ## Install
50
83
 
@@ -78,18 +111,19 @@ the plugin never writes them):
78
111
  "forge": {
79
112
  "model": "provider/model", // pick any model for forge
80
113
  "disable": true // one-knob return to native: no forge,
81
- // build/plan restored, no tools/commands
114
+ // no injections at all, no tools/commands
82
115
  }
83
116
  },
84
117
  // per-plugin options ride the plugin entry as a [spec, options] tuple
85
118
  "plugin": [
86
119
  ["@sorenllm/opencode-forge", {
87
120
  "jobs": {
88
- "mode": "auto", // "auto" (default) | "forge" | "native" — see the stage matrix below
89
- "keepBuiltinShell": false // stage 0: keep the builtin shell visible alongside forge_shell
121
+ "mode": "auto", // "auto" (default) | "forge" — equivalent, both keep the partition
122
+ // | "native" — retire the supervisor, builtin shell returns
123
+ "keepBuiltinShell": false // true: give the forge family the builtin shell back (the one exec escape hatch)
90
124
  },
91
125
  "watchdog": {
92
- "mode": "kill", // "kill" (default) | "dry-run" | "off"
126
+ "mode": "kill", // "kill" (default) | "dry-run" | "off" — forge-family sessions only
93
127
  "stallMs": 600000 // stall threshold, min 60000
94
128
  }
95
129
  }]
@@ -110,12 +144,14 @@ and the plugin's command of that name is not registered.
110
144
  `~/.cache/opencode/packages/github_ChengZiiii/opencode-forge/`).
111
145
  3. Delete the `agent["forge"]` block from your config if you added one
112
146
  (otherwise the name lingers in the agent list).
113
- 4. Done — the hidden native `build`/`plan` agents come back automatically
147
+ 4. Done — all runtime injections (agents, tool hiding, commands) are gone
148
+ with the plugin; nothing was ever written to your config
114
149
  (the hide was runtime-only). Your `.opencode/plan/` and `.opencode/goal/`
115
150
  files are yours; delete them yourself if you want.
116
- 5. Optional runtime debris: delete `<tmp>/opencode-forge/` (job logs, the
117
- job registry ledger, the watchdog ledger, and the dispatch ledger — the
118
- file ledger table above lists everything).
151
+ 5. Optional runtime debris: delete `<tmp>/opencode-forge/` (job logs, the
152
+ job registry ledger, the watchdog ledger, and any leftover dispatch
153
+ ledger from older versions — the file ledger table above lists
154
+ everything).
119
155
 
120
156
  ## File ledger
121
157
 
@@ -125,13 +161,13 @@ What this plugin touches, exhaustively:
125
161
  | --- | --- | --- |
126
162
  | `<project>/.opencode/plan/*.md` | plan files | user data — kept forever, uninstall never deletes |
127
163
  | `<project>/.opencode/goal/*.md` | goal files (contract, Check Log, Turn Ledger) | user data — kept forever, uninstall never deletes |
128
- | merged config object (RAM only) | forge agent, native build/plan `disable`, `command.plan`, `command.goal`, goal permission keys, `permission.forge_shell`, stage-0 builtin shell hide | vanishes when the plugin is removed; nothing is written to disk |
164
+ | merged config object (RAM only) | forge agent, static `forge-<id>` subagents, tool-partition injections (forge family shell hide, non-forge forge-tool hide, native build/plan/general/explore minimal entries), `command.plan`, `command.goal`, `command.crew`, goal permission keys, `permission.forge_shell`, `permission.crew_close` | vanishes when the plugin is removed; nothing is written to disk |
129
165
  | `<tmp>/opencode-forge/jobs/<jobId>.log` | job output tee (full output; oldest rotated out above 50 files) | runtime debris — delete freely, also after uninstall |
130
166
  | `<tmp>/opencode-forge/jobs/ledger.jsonl` | job registry ledger (bounded: 1 MB reset, 200 entries) | runtime debris — delete freely, also after uninstall |
131
167
  | `<tmp>/opencode-forge/jobs/registry.json` | persistent survivor registry (bounded: 100 entries) | runtime debris — after uninstall, kill any still-running `survive` jobs yourself first |
132
168
  | `<tmp>/opencode-forge/watchdog/log.jsonl` | watchdog interventions ledger (bounded: 200 entries, oldest rotated) | runtime debris — delete freely, also after uninstall |
133
- | `<tmp>/opencode-forge/dispatch/ledger.jsonl` | dispatch ledger: resolved identity/depth, outcomes, tokens/cost, timeouts, transport interruptions, lost-on-exit, crew summaries (bounded, capped rotation) | runtime debris — delete freely, also after uninstall |
134
- | `<project>/.opencode/forge.json`, `~/.config/opencode/forge.json` | your dispatch agent definitions (JSONC). **User data — the plugin only reads it, never writes or migrates it** | yours — version the project one, keep the global one out of sync tools if it holds machine-specific models |
169
+ | `<tmp>/opencode-forge/dispatch/ledger.jsonl` | dispatch ledger — **no longer written** (the dispatch engine was removed); only present as inert debris from older versions | runtime debris — delete freely |
170
+ | `<project>/.opencode/forge.json`, `~/.config/opencode/forge.json` | your static subagent definitions (JSONC). **User data — the plugin only reads it, never writes or migrates it** | yours — version the project one, keep the global one out of sync tools if it holds machine-specific models |
135
171
  | `~/.cache/opencode/packages/...` | installed package copy | written by the `opencode plugin` installer, not the plugin |
136
172
  | `~/.config/opencode/opencode.json` | `plugin` array entry | written by the installer |
137
173
 
@@ -292,19 +328,20 @@ as usual. Config `jobs.survive: "always"` flips the per-call default; config
292
328
  whose pid died are detected at next start, ledgered as orphans, and dropped
293
329
  from the registry. Stop survivors explicitly — nothing else will.
294
330
 
295
- ### Stage matrix (future compatibility, by design)
331
+ ### Mode matrix (supervisor lifecycle)
296
332
 
297
- OpenCode upstream is converging on native backgrounding (PRs #47231 /
298
- #50276, umbrella #34366). The supervisor degrades ahead of it:
333
+ The old three-stage capability probe is retired: the forge-side partition
334
+ holds regardless of what the host's builtin shell can do, so `auto` and
335
+ `forge` are equivalent. Only the manual `native` mode retires the
336
+ supervisor:
299
337
 
300
- | stage | how you get there | builtin shell | forge_shell / forge_jobs |
338
+ | mode | how you get there | builtin shell (forge family) | forge_shell / forge_jobs |
301
339
  | --- | --- | --- | --- |
302
- | 0 — full forge path | default (`jobs.mode: "auto"`, no native support detected) | hidden on the plugin-created forge agent (runtime injection only) | registered; the exec surface |
303
- | 1 — native backgrounding detected | automatic: `config.experimental` background flag, or the builtin shell's schema grows `run_in_background` | visible again | still registered as the additive layer (idle/success/wake supervision); its description now points plain backgrounding at the native parameter |
304
- | 2 — native confirmed complete | manual only: `jobs.mode: "native"` (never auto-detected — completeness is a semantic judgement) | visible | retired; calls throw with a pointer to the native parameter |
340
+ | auto (default) / forge | nothing to do | hidden — the exec surface is forge_shell, always | registered |
341
+ | native | manual only: `jobs.mode: "native"` | restored | retired; calls throw with a pointer to the native parameter |
305
342
 
306
- Pin `jobs.mode: "forge"` to stay on stage 0 forever; `jobs.keepBuiltinShell:
307
- true` keeps the builtin shell visible at any stage.
343
+ `jobs.keepBuiltinShell: true` gives the forge family the builtin shell back
344
+ in any mode (the watchdog keeps governing those calls).
308
345
 
309
346
  ### Artifacts and uninstall additions
310
347
 
@@ -319,163 +356,144 @@ These are runtime debris, not data — with ONE caveat: **if you uninstall with
319
356
  /F /T`, or just reboot); deleting the directory afterwards is safe and
320
357
  complete.
321
358
 
322
- ## Worker dispatch (scoped subagents on a chosen model + depth)
359
+ ## Forge subagents (static worker agents in forge.json)
360
+
361
+ Forge subagents are **static, user-authored worker agents**: each forge.json
362
+ entry pins a model and an optional reasoning depth, materializes as a native
363
+ `forge-<id>` subagent, and is dispatched through the host's **native `task`
364
+ tool** — which means every run is visible in the TUI, expandable and
365
+ monitorable, exactly like any native subagent. There is no dispatch tool, no
366
+ child-session engine, and no configuration invitation anywhere: the session
367
+ AI never writes your config.
323
368
 
324
- `forge_dispatch {prompt, agent, depth}` spawns a child session that runs one
325
- scoped task on the agent's pinned model at the requested reasoning depth. The
326
- result is an honest report: the actual model/depth used, real token counts, a
327
- self-computed cost (`null` when the model is unpriced), the
328
- `depthTranslation` disclosure (`canonical high → native XHigh`, or
329
- `verbatim`), and the worker's concluding report.
369
+ No forge.json? Nothing happens — the plugin registers nothing and the native
370
+ subagent behavior is untouched. Onboarding is this README, for you the human.
330
371
 
331
- ### forge.json — the dispatch configuration
372
+ ### forge.json — the configuration
332
373
 
333
- Agents live in a dedicated `forge.json` (JSONC — comments allowed), resolved
334
- by a three-level cascade with a single winning source (never merged):
374
+ A dedicated `forge.json` (JSONC — comments allowed), resolved by a two-level
375
+ cascade with a single winning source (never merged):
335
376
 
336
377
  1. `<project>/.opencode/forge.json` — project-level, versionable, team-shared
337
378
  2. `~/.config/opencode/forge.json` — global
338
- 3. the built-in seed — `research {depths: [low, medium]}` and
339
- `review {depths: [medium, high, max]}`, both pinned to the placeholder
340
- `Local/GPT Luna` (an identity that never resolves: an unconfigured install
341
- fails every dispatch with the configuration recipe below)
342
379
 
343
380
  ```jsonc
344
381
  {
345
- // forge dispatch agents — save the file; changes apply on the next dispatch, no restart
382
+ // forge subagents — definitions; see the restart table below for apply timing
346
383
  "agents": {
347
384
  "research": {
348
- "model": "zai-coding-plan/glm-5.3", // exact "provider/model" string
349
- "depths": ["low", "medium"], // first entry is the default
350
- // "prompt": "optional role prompt riding inside the discipline wrapper",
351
- // "shape": "write", // default readonly denies mutating tools
352
- // "permission": { "bash": "deny" } // optional override; "task" is always denied
385
+ "model": "zai-coding-plan/glm-5.3", // exact "provider/model" string — REQUIRED
386
+ "thoughtLevel": "low", // optional: none/low/medium/high/max or a native level name
387
+ // "prompt": "optional role prompt; built-in research/review defaults exist",
388
+ // "shape": "write", // default readonly denies write/edit/bash
389
+ // "permission": { "bash": "deny" } // optional override; "task" is ALWAYS denied
353
390
  }
354
391
  }
355
392
  }
356
393
  ```
357
394
 
358
395
  - **The plugin only ever reads this file.** It never creates, writes, or
359
- migrates it. Unconfigured? The first `forge_dispatch` error carries a
360
- machine-actionable recipe (detected identities, both file paths, the
361
- template, a verify dispatch) — or just ask your session AI to configure
362
- dispatch; `forge_dispatch_config` shows the exact live state.
363
- - **Hot-apply**: the file is re-read (mtime-cached) before every dispatch and
364
- on every `forge_dispatch_config` call — edits take effect on the next
365
- dispatch, no host restart. One boundary: newly added agents materialize
366
- their hidden `forge-<agent>` entry only on the next config reload; the
367
- dispatch itself still works (the body names the agent), but a brand-new
368
- agent's first dispatch may need a host reload if the host validates agent
369
- names on session create.
370
- - Each agent materializes as a hidden `mode: subagent` agent `forge-<agent>`
371
- carrying the role prompt (explicit `prompt` > built-in research/review
372
- defaults > generic worker) and a deny-style permission: `shape: "readonly"`
373
- (the default) denies `write`/`edit`/`bash`, `shape: "write"` allows them,
374
- an explicit `permission` map overrides the shape default — and `task` is
375
- ALWAYS denied (recursive dispatch stays physically impossible). Agents
376
- never carry a `model` field; the brain is bound per dispatch.
377
-
378
- ### The depth metalanguage
379
-
380
- `depth` comes from the canonical five-word set `none | low | medium | high |
381
- max`, or the model's **native level names verbatim** (escape hatch — e.g.
382
- Qwen's `XHigh`). Translation to the provider's native parameter follows three
383
- hard rules:
384
-
385
- - **verbatim-first**: a meta word the model natively offers passes as-is
386
- - **no interpolation**: a meta word with no counterpart errors listing both
387
- vocabularies — never a nearest guess
388
- - **full disclosure**: the report always shows `canonical → native`
389
-
390
- Per family: effort-style providers (OpenAI-compatible) receive
391
- `reasoningEffort = <word>`; budget-style (Anthropic-style) receive a
392
- published thinking tier — `low → 8192`, `medium → 16384`, `high → 24576`,
393
- `max → 32768` budget tokens, `none → thinking off`; toggle-style (zai/GLM)
394
- receive thinking on/off (that shape has no level slot — disclosed honestly).
395
- A provider whose option shape the plugin does not know gets nothing injected,
396
- disclosed as `not injected (unknown provider shape)`. The provider is the
397
- final judge of every depth word: its raw errors flow back unedited.
398
-
399
- ### Legacy inline roster (advanced)
400
-
401
- The pre-forge.json inline form still works when configured (the plugin
402
- options `dispatch.roster` / `dispatch.tiers`): tier ids it defines keep their
403
- exact-match resolution, one-retry-excluding-a-failed-identity semantics, and
404
- startup dead-key warnings. Legacy tier ids win over forge.json agents of the
405
- same name while inline options exist. Zero-config auto-generation of roster
406
- entries was removed (configured ≠ usable) — dispatch without configuration is
407
- the seed + recipe path above.
408
-
409
- ### Background dispatch and wake briefs
410
-
411
- `forge_dispatch` takes `background: true`: it returns
412
- `{dispatchId, agent, requested, resolved, depth, queuedAt}` immediately and
413
- the full result arrives later as a coalesced `[forge:dispatch-complete]`
414
- brief — debounced, never interrupting an active turn (delivery waits for an
415
- idle), each terminal delivered exactly once, and merged into a single
416
- combined re-prompt when a goal continuation fires on the same idle.
417
- `forge_dispatch_list` recovers in-flight and recent terminal state (with
418
- dispatchIds) after context compaction; `forge_dispatch_kill {dispatchId}`
419
- stops polling, suppresses the brief, and ledgers `killed`. On host exit,
420
- in-flight dispatches are ledgered `lost-on-exit` — sessions are host memory
421
- objects, there are no orphan processes and no survive semantics.
422
-
423
- **Transport-interruption recovery**: the child's prompt is delivered by a
424
- turn-synchronous HTTP POST that stays open for the whole first turn. If that
425
- POST dies at the transport layer (fetch abort / network failure — anything
426
- that is not an HTTP error response), the dispatch does **not** fail: the
427
- engine ledgers `transport-interrupted` (with the child sessionID) and falls
428
- back to completion polling under the same deadline — the child session is a
429
- host-side object and usually keeps running, so its result is still collected
430
- and delivered normally. The message is never re-POSTed (the prompt may
431
- already be delivering); a message that never arrived polls as waiting and
432
- ends in the honest deadline `timeout`. Terminal reports and terminal ledger
433
- rows carry the child sessionID, dispatchId, parentSessionID, and durationMs.
434
-
435
- ### Crew workflow (`/crew`)
436
-
437
- `/crew <objective>` registers a crew: decompose the objective into
438
- evidence-checked subtasks, dispatch them as background waves paced on
439
- completion briefs (**never past the concurrency cap — launch the next batch
440
- only as briefs free capacity**), at most ONE retry per failed subtask, then
441
- `crew_close` with the full report. The close is a hard gate: every subtask
442
- needs a PASS/FAIL verdict with evidence, a FAIL needs both failure reports,
443
- and the report is cross-checked against the dispatch ledger — a dispatched
444
- subtask missing from the report refuses the close (refusals never bother you
445
- with a dialog). A valid close asks once, appends a crew summary to the
446
- ledger, and ends the crew. Crew state is in-memory by design: a host restart
447
- ends it honestly and the ledger keeps the history.
448
-
449
- ### Known pitfalls
450
-
451
- - **Keyless endpoints × readonly agents**: free/keyless providers can return
452
- a deterministic EMPTY response for any toolset-reduced (readonly) agent —
453
- 0 tokens, no parts. The plugin detects this and fails the dispatch honestly
454
- (`empty-response`, never counted as success); use a `shape: "write"` agent
455
- or a keyed provider for readonly work on such endpoints.
456
- - **`opencode run` and background**: a finished run-mode session cannot be
457
- re-prompted, so completion briefs have nowhere to land — completions are
458
- ledger-only (read them via `forge_dispatch_list`). Background mode targets
459
- live TUI sessions; prefer sync under `opencode run`.
460
- - **Host tool ceiling (~262s observed on 1.18.32)**: the host may kill a
461
- sync tool call that exceeds roughly 262 seconds even though the plugin's
462
- own deadline is higher. A same-family transport abort (~281s observed,
463
- 2026-09-27 incident) can also kill the background dispatch's turn POST.
464
- Transport-interruption recovery (above) absorbs both: the dispatch falls
465
- back to polling and still delivers the child's result; keep `timeoutMs`
466
- modest anyway for sync-heavy workflows.
467
- - **Child permission asks**: a worker that hits a permission ask surfaces the
468
- ask on the CHILD's session in the TUI. An unanswered ask stalls the child
469
- until the dispatch deadline — scope worker prompts to avoid permission
470
- boundaries (readonly tiers and workspace-relative paths help).
396
+ migrates it. The old AI-authored onboarding path (seed placeholder +
397
+ error recipe) is gone: an unconfigured host is inert and silent, and the
398
+ session AI is never invited to touch config. The ONE exception is the
399
+ `/crew` initialization gate (below), where AI-assisted configuration is
400
+ offered to YOU and happens only on your explicit go-ahead.
401
+ - **Parsing is field-level fail-soft** (ZCode-style): a semantically bad
402
+ agent entry is skipped with an error finding while its siblings apply; a
403
+ mistyped optional field is ignored with a warning; a syntactically broken
404
+ file empties the agent set with one parse-location error. There is no seed
405
+ in any failure path. Findings surface in the plugin diagnostics log.
406
+ - **`depths` is deprecated**: the array is ignored with a warning finding.
407
+ Pin ONE `thoughtLevel` per agent instead.
408
+ - Each agent materializes as `forge-<id>` — a subagent-mode agent visible in
409
+ the task tool's vocabulary (never hidden), carrying the pinned `model`,
410
+ the worker discipline (workspace-relative paths only, verbatim reporting
411
+ of tool refusals, evidence-bearing conclusions) around its role prompt,
412
+ and a deny-style permission: `shape: "readonly"` (the default) denies
413
+ `write`/`edit`/`bash`, `shape: "write"` allows them, an explicit
414
+ `permission` map overrides the shape default — and `task` is ALWAYS
415
+ denied (recursive spawning stays physically impossible).
416
+
417
+ **What applies when** — edits take hold per field:
418
+
419
+ | change | needs a host restart? |
420
+ | --- | --- |
421
+ | `thoughtLevel` on an existing agent | no (applies to NEW sessions of that agent; running sessions keep their frozen depth) |
422
+ | agent added / removed, or `model` / `prompt` / `permission` changed | yes (materialization runs on the config hook) |
423
+
424
+ ### Dispatching
425
+
426
+ Say "dispatch a subagent..." in a forge session and the model picks from the
427
+ task tool vocabulary: a configured `forge-*` agent whose description matches
428
+ runs on its pinned brain; if none matches, forge is instructed to say so
429
+ plainly and fall back to the native task channel. During a plan draft the
430
+ native `task` tool is already denied by the plan harness — no plugin-side
431
+ rule needed.
432
+
433
+ **Known pitfall — keyless endpoints × readonly agents**: free/keyless
434
+ providers can return a deterministic EMPTY response for any toolset-reduced
435
+ (readonly) agent. On the native task channel this now surfaces as a visible,
436
+ attributable task failure; the mitigation is unchanged — use
437
+ `shape: "write"` for such endpoints, or a keyed provider.
438
+
439
+ ### The thoughtLevel word
440
+
441
+ One word per agent: the canonical `none | low | medium | high | max`, or the
442
+ model's native level name verbatim (e.g. Qwen's `XHigh`). Injection rides the
443
+ `chat.params` hook keyed by agent name, translated per provider family:
444
+ effort-style (OpenAI-compatible) receives `reasoningEffort = <word>`;
445
+ budget-style (Anthropic-style) receives a published thinking tier
446
+ (`low → 8192`, `medium → 16384`, `high → 24576`, `max → 32768`,
447
+ `none → off`); toggle-style (zai/GLM) receives thinking on/off. Rules:
448
+ verbatim-first, no interpolation, unknown family injects nothing. A word the
449
+ model does not natively offer injects NOTHING and records one finding naming
450
+ both vocabularies — the session is never broken by a config word. A
451
+ session's depth is frozen at its first request (editing forge.json
452
+ mid-session never rewrites a running session).
453
+
454
+ ## Crew workflow (`/crew`)
455
+
456
+ `/crew <objective>` is the third state-activation command next to `/plan` and
457
+ `/goal`: it enters crew orchestration discipline for the session. The flow:
458
+
459
+ 1. **Register** the declared plan: `crew_begin {objective, subtasks}` — one
460
+ titled subtask each, optionally naming the intended `forge-*` agent.
461
+ 2. **Execute in waves** of parallel native `task` calls (results return
462
+ in-turn; the next wave launches only after the previous wave's results).
463
+ 3. **Missing role?** Run the subtask through a native task call anyway and
464
+ tell the user — suggest configuring the missing role.
465
+ 4. **Verify** each subtask against its acceptance-evidence statement;
466
+ retry a failure at most once.
467
+ 5. **Close** with `crew_close` — a hard, ask-gated gate cross-checking the
468
+ report against the DECLARED plan: every declared subtask needs a
469
+ PASS/FAIL verdict with evidence; an undeclared subtask in the report
470
+ refuses the close (fold discoveries into an existing verdict, or discard
471
+ and re-crew); a FAIL needs both failure reports. The close output is the
472
+ crew's record — crew state is in-memory and a host restart ends it
473
+ honestly.
474
+
475
+ **Not initialized?** `/crew` on a host with zero usable forge agents refuses
476
+ with one-time setup guidance: the two file paths, a copy-paste template, and
477
+ the choice of configuring it yourself OR having the session AI do it — the
478
+ latter only on your explicit go-ahead in that conversation, through the
479
+ normal visible write path. This is the plugin's only configuration
480
+ invitation. After saving the file, restart opencode to materialize the
481
+ agents.
482
+
483
+ ### Debris note
484
+
485
+ The old dispatch suite's ledger at `<tmp>/opencode-forge/dispatch/` is no
486
+ longer written. If it exists on your machine it is inert runtime debris —
487
+ delete the directory freely, also after uninstalling.
471
488
 
472
489
  ## Hang watchdog (a stuck builtin shell unblocks itself)
473
490
 
474
491
  `forge_shell` is structurally immune to the stdio-EOF hang, but the builtin
475
- `shell`/`bash` tool can still hang outside it — user-defined agents keep
476
- the builtin shell, and the stage matrix above restores it at stage 1/2.
477
- The watchdog is the independent backstop for those paths, in **every**
478
- session (primary and delegated subagents alike):
492
+ `shell`/`bash` tool can still hang outside it — under `jobs.mode: "native"`
493
+ or `jobs.keepBuiltinShell: true`, forge-family sessions carry it again. The
494
+ watchdog is the independent backstop for those paths, in **forge-family
495
+ sessions only** (primary and delegated forge-* workers alike) — non-forge
496
+ agents' builtin shells are entirely native: no markers, no timing, no kills:
479
497
 
480
498
  1. The host's shell environment hook stamps each builtin shell call's
481
499
  process with a plugin-namespaced marker (`FORGE_WATCHDOG_MARK`), and the
@@ -538,11 +556,12 @@ read/grep/glob; if you genuinely need a shell command to decide the plan,
538
556
  approve the plan first (revising after approval is allowed via a new `/plan`).
539
557
  The escape hatches are `plan_approve` and `/plan discard`, by design.
540
558
 
541
- Separately, at stage 0 the builtin shell is hidden on the plugin-created
542
- forge agent on purpose — `forge_shell` is the exec surface there (see the
543
- job supervisor chapter above). A **user-defined** `agent.forge` entry,
544
- `jobs.keepBuiltinShell: true`, or any stage ≥ 1 keeps the builtin shell
545
- visible.
559
+ Separately, the builtin shell is hidden on the forge agent on purpose —
560
+ `forge_shell` is the exec surface there (see Agent partition and the job
561
+ supervisor chapter above). The hide is unconditional: it covers a
562
+ **user-defined** `agent.forge` entry too. The escape hatches are
563
+ `jobs.keepBuiltinShell: true` (builtin shell back on the forge family) and
564
+ `jobs.mode: "native"` (supervisor retired entirely).
546
565
 
547
566
  A process restart forgets the session binding: the write-ban soft-disables
548
567
  (safety over strictness) and the next session's system notice + `/plan