@mcrescenzo/opencode-workflows 0.1.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 (71) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/CODE_OF_CONDUCT.md +134 -0
  3. package/CONTRIBUTING.md +95 -0
  4. package/LICENSE +21 -0
  5. package/README.md +746 -0
  6. package/SECURITY.md +38 -0
  7. package/commands/repo-bughunt.md +94 -0
  8. package/commands/repo-review.md +148 -0
  9. package/commands/workflow-live-gates-release-check.md +143 -0
  10. package/docs/workflow-plugin.md +400 -0
  11. package/opencode-workflows.js +5 -0
  12. package/package.json +86 -0
  13. package/skills/opencode-workflow-authoring/SKILL.md +180 -0
  14. package/skills/repo-review-command-protocol/SKILL.md +56 -0
  15. package/skills/workflow-model-tiering/SKILL.md +57 -0
  16. package/skills/workflow-plan-review/SKILL.md +91 -0
  17. package/workflow-kernel/approval-hashing.js +39 -0
  18. package/workflow-kernel/async-util.js +33 -0
  19. package/workflow-kernel/audited-shell-policy.js +200 -0
  20. package/workflow-kernel/authority-policy.js +670 -0
  21. package/workflow-kernel/budget-accounting.js +142 -0
  22. package/workflow-kernel/capability-adapter.js +753 -0
  23. package/workflow-kernel/child-agent-runner.js +1264 -0
  24. package/workflow-kernel/constants.js +117 -0
  25. package/workflow-kernel/diagnostics.js +152 -0
  26. package/workflow-kernel/drain-runtime.js +421 -0
  27. package/workflow-kernel/errors.js +181 -0
  28. package/workflow-kernel/event-journal.js +487 -0
  29. package/workflow-kernel/extension-registry.js +144 -0
  30. package/workflow-kernel/free-text-redactor.js +91 -0
  31. package/workflow-kernel/gate-shapes.js +82 -0
  32. package/workflow-kernel/git-util.js +45 -0
  33. package/workflow-kernel/index.js +72 -0
  34. package/workflow-kernel/integration-mode.js +155 -0
  35. package/workflow-kernel/lane-effort-policy.js +134 -0
  36. package/workflow-kernel/lifecycle-control.js +608 -0
  37. package/workflow-kernel/live-gate-probes.js +916 -0
  38. package/workflow-kernel/notification-toast-cards.js +393 -0
  39. package/workflow-kernel/notification-toast-policy.js +179 -0
  40. package/workflow-kernel/notification-toast-scope.js +100 -0
  41. package/workflow-kernel/notification-toast.js +287 -0
  42. package/workflow-kernel/path-policy.js +219 -0
  43. package/workflow-kernel/result-readback.js +106 -0
  44. package/workflow-kernel/role-template-loading.js +606 -0
  45. package/workflow-kernel/run-context.js +139 -0
  46. package/workflow-kernel/run-observability.js +43 -0
  47. package/workflow-kernel/run-store-fs.js +231 -0
  48. package/workflow-kernel/run-store-locks.js +180 -0
  49. package/workflow-kernel/run-store-projections.js +421 -0
  50. package/workflow-kernel/run-store-rehydrate.js +68 -0
  51. package/workflow-kernel/run-store-state.js +147 -0
  52. package/workflow-kernel/run-store-status-format.js +1154 -0
  53. package/workflow-kernel/run-store-status.js +107 -0
  54. package/workflow-kernel/sandbox-executor.js +1131 -0
  55. package/workflow-kernel/session-access.js +56 -0
  56. package/workflow-kernel/structured-output.js +143 -0
  57. package/workflow-kernel/test-fix-drain-adapter.js +119 -0
  58. package/workflow-kernel/text-json.js +136 -0
  59. package/workflow-kernel/workflow-plugin.js +3017 -0
  60. package/workflow-kernel/workflow-source.js +444 -0
  61. package/workflow-kernel/worktree-adapter.js +256 -0
  62. package/workflow-kernel/worktree-git.js +147 -0
  63. package/workflows/repo-bughunt.js +362 -0
  64. package/workflows/repo-cleanup.js +383 -0
  65. package/workflows/repo-complexity.js +404 -0
  66. package/workflows/repo-deps.js +457 -0
  67. package/workflows/repo-modernize.js +395 -0
  68. package/workflows/repo-perf.js +394 -0
  69. package/workflows/repo-review.js +831 -0
  70. package/workflows/repo-security-audit.js +466 -0
  71. package/workflows/repo-test-gaps.js +377 -0
package/README.md ADDED
@@ -0,0 +1,746 @@
1
+ # opencode-workflows
2
+
3
+ Reusable opencode workflow plugin and autonomous workflow harness work area.
4
+
5
+ The plugin entrypoint is `opencode-workflows.js`. Package installs load it via
6
+ `@mcrescenzo/opencode-workflows`; source checkouts can point opencode at the
7
+ checkout's `opencode-workflows.js` during local development.
8
+
9
+ ## Install
10
+
11
+ ```sh
12
+ bun add @mcrescenzo/opencode-workflows
13
+ # or
14
+ npm install @mcrescenzo/opencode-workflows
15
+ ```
16
+
17
+ Register it in `opencode.json` (the singular `plugin` array key):
18
+
19
+ ```json
20
+ {
21
+ "plugin": ["@mcrescenzo/opencode-workflows"]
22
+ }
23
+ ```
24
+
25
+ Restart opencode after changing plugin config.
26
+
27
+ ### Configuration & prerequisites
28
+
29
+ - **`OPENCODE_WORKFLOWS_DIR`** (optional) — where global run state, roles, and templates live. Defaults to `$XDG_CONFIG_HOME/opencode/workflows` (`~/.config/opencode/workflows`). Role prompts are plain `.md` files; optional typed defaults live in sibling `roles.json`. Per-run state is also written project-locally under `<worktree>/.opencode/workflows/runs`.
30
+ - **`OPENCODE_WORKFLOWS_HARD_CONCURRENCY_LIMIT`** (optional) — raises or lowers the per-run `concurrency` schema/runtime ceiling. The default ceiling is `64`; `DEFAULT_CONCURRENCY` remains `4` until an active runtime is proven to sustain higher prompt fan-out. The same ceiling can be set per plugin entry with the `hardConcurrencyLimit` option.
31
+ - **`OPENCODE_WORKFLOWS_DEBUG_CAPTURE=1`** (optional, default off) — opt in to per-lane debug artifacts under each private run directory: rendered prompt, schema, and child transcript JSONL after secrets redaction and size caps. The same capture mode can be enabled per run with `workflow_run({ debugCapture: true })`. Leave it off for the default privacy/size posture.
32
+ - **Plugin `autoApprove` option** (optional, default off) — set to `"readOnly"`, `"worktree"`, or `"all"` to let eligible `workflow_run` calls launch on the first call when their resolved authority tier is within that ceiling. A per-call `autoApprove` argument may narrow the configured ceiling, never widen it; `workflow_apply` keeps its separate hash gate.
33
+ - **Node ≥ 20.11** (see `engines`); the test runner is Node's built-in `node --test`.
34
+ - **`git`** on `PATH` (worktree, apply, and integration tests shell out to `git`).
35
+ - **`bd` (Beads CLI)** is required only for the `beads-drain` adapter/scratch tests and the `beads-drain` workflow; all `repo-*` workflows and the rest of the no-token matrix run without it.
36
+ - **Package manager / lockfile:** `bun.lock` is the canonical tracked lockfile (`bun install` reproduces it). All package scripts are thin `node` wrappers invoked via `npm run <script>` and work regardless of installer; `package-lock.json` is gitignored.
37
+ - **Dependency notes:** `@opencode-ai/plugin` currently pins the transitive `effect` package on its `4.0.0-beta` channel. This repo accepts that upstream coupling instead of overriding `effect` independently; keep `@opencode-ai/plugin` and `@opencode-ai/sdk` in lockstep and verify `bun.lock` with `npm run test:lockfile-sync` during release checks.
38
+ - **Alpine/musl install note:** `@opencode-ai/plugin` currently pulls `effect -> msgpackr -> msgpackr-extract` as an optional native speedup. `msgpackr-extract@3.0.4` has no musl prebuild, so `npm install` on Alpine can fall back to a local native rebuild and fail if `python3`/`make`/a compiler are absent. This plugin does not import `msgpackr`, and the extractor is optional; use `npm install --ignore-scripts` on musl hosts when native install scripts are not acceptable.
39
+ - **Apply platform note:** primary-tree patch writes verify the opened file descriptor's real path before writing. On platforms/filesystems where Node cannot resolve fd real paths (for example, no `/proc/self/fd` equivalent), `workflow_apply` and in-run auto-apply fail closed with a containment error rather than using a racy path fallback.
40
+ - **Live child system smoke** (separate, required for public release): needs the `opencode` binary + local config and is NOT part of `npm test` / `release:no-token`. See `CONTRIBUTING.md` and [docs/plugin-system-tests.md](https://github.com/mcrescenzo/opencode-workflows/blob/main/docs/plugin-system-tests.md) (GitHub only, not packaged).
41
+
42
+ See `CONTRIBUTING.md` for the full contributor prerequisites, lockfile policy, and release-readiness notes.
43
+
44
+ ## Source Checkout Verification
45
+
46
+ The npm package ships the runtime plugin, bundled workflows, commands, skills,
47
+ and the "Active operator references" / "Active technical contracts" docs listed
48
+ in the Documentation Map below (see `files` in `package.json` for the exact
49
+ list). It does not ship this repository's `tests/`, `scripts/`, or reference
50
+ extension source. It also does not ship the "Historical snapshots / audits" or
51
+ "Roadmap / planning" docs — those stay in git for contributors but are not part
52
+ of the published tarball. The `npm run ...` verification commands below are for
53
+ a source checkout or contributor clone, not for an installed package tarball.
54
+
55
+ Run the nested repo workflow regression wrapper from this directory:
56
+
57
+ ```sh
58
+ npm run test:workflows
59
+ ```
60
+
61
+ This wrapper covers the core `workflow_run` / `workflow_apply` paths and the
62
+ `repo-*` review workflows. Beads-drain, extension-seam, live-gate, and durable
63
+ state coverage live in the focused scripts below and in the catch-all `npm test`
64
+ matrix.
65
+
66
+ Run focused beads-drain and extension coverage from this directory:
67
+
68
+ ```sh
69
+ npm run test:workflow-kernel
70
+ npm run test:beads-drain
71
+ npm run test:workflow-adapters
72
+ npm run test:extension-seam
73
+ ```
74
+
75
+ Run mocked, no-token live-gate probe coverage from this directory:
76
+
77
+ ```sh
78
+ npm run test:live-gates
79
+ ```
80
+
81
+ Run the full plugin test matrix (all workflow, adapter, runtime, live-gate,
82
+ durable-state, and beads-drain integration tests) from this directory:
83
+
84
+ ```sh
85
+ npm test
86
+ ```
87
+
88
+ The public CI workflow in `.github/workflows/ci.yml` provisions Node 22, Bun, and
89
+ the Beads CLI, installs dependencies from `bun.lock`, and runs
90
+ `npm run release:no-token`. It intentionally does not run publishing, token-using
91
+ live probes, the private parent integration check, or the required live child
92
+ system smoke.
93
+
94
+ For system-level plugin startup checks, use [docs/plugin-system-tests.md](https://github.com/mcrescenzo/opencode-workflows/blob/main/docs/plugin-system-tests.md) (GitHub only, not packaged). Those
95
+ checks start disposable child opencode servers and verify startup health,
96
+ registries, and cleanup without restarting the parent TUI.
97
+
98
+ ## Documentation Map
99
+
100
+ Use `workflow_list({ format: "json" })` as the machine-canonical discovery surface
101
+ for saved and bundled workflow names, args schemas, examples, authority profile,
102
+ model-tier hints, and safe readback steps. The docs below are operator guidance,
103
+ technical contracts, or historical context.
104
+
105
+ **Packaged vs GitHub-only.** Under the `files[]` policy, a doc ships inside the
106
+ published npm tarball only when a shipped command, skill, or workflow instructs
107
+ an *agent* to read it at runtime (a prompt/instruction string, not a code comment
108
+ or this README's own prose). `docs/workflow-plugin.md` is the only doc that
109
+ clears that bar today — three bundled commands point a running agent at
110
+ `docs/workflow-plugin.md#workflow-tool-reference`. Every other doc below lives in
111
+ the GitHub repository only: read it from a source checkout, or follow the
112
+ GitHub links in the table.
113
+
114
+ | Category | Documents | Packaged? |
115
+ | --- | --- | --- |
116
+ | Ships with the npm package | `docs/workflow-plugin.md` | **Yes** |
117
+ | Active operator references (GitHub only) | `README.md`, `commands/*.md`, `skills/*/SKILL.md`, [docs/workflow-recipes.md](https://github.com/mcrescenzo/opencode-workflows/blob/main/docs/workflow-recipes.md), [docs/plugin-system-tests.md](https://github.com/mcrescenzo/opencode-workflows/blob/main/docs/plugin-system-tests.md), [docs/repo-review.md](https://github.com/mcrescenzo/opencode-workflows/blob/main/docs/repo-review.md), [docs/run-audit-playbook.md](https://github.com/mcrescenzo/opencode-workflows/blob/main/docs/run-audit-playbook.md), [docs/goal-supervision-autonomous-drains.md](https://github.com/mcrescenzo/opencode-workflows/blob/main/docs/goal-supervision-autonomous-drains.md) | No (`README.md` itself ships) |
118
+ | Active technical contracts (GitHub only) | [docs/workflow-extensions.md](https://github.com/mcrescenzo/opencode-workflows/blob/main/docs/workflow-extensions.md), [docs/repo-review-leaf-contract.md](https://github.com/mcrescenzo/opencode-workflows/blob/main/docs/repo-review-leaf-contract.md), [docs/repo-review-parity-matrix.md](https://github.com/mcrescenzo/opencode-workflows/blob/main/docs/repo-review-parity-matrix.md) | No |
119
+ | Historical snapshots / audits (GitHub only) | `docs/release-gate-validation-2026-06-16.md`, `docs/dogfood-rollout-2026-06-16.md`, `docs/workflow-autonomous-harness-design.md`, `docs/review-2026-06-19-bug-robustness-remediation-plan.md`, `docs/superpowers/plans/2026-06-23-port-repo-bughunt-to-opencode.md`, `docs/superpowers/plans/2026-06-23-session-aware-model-tiering-plan.md` | No |
120
+ | Roadmap / planning (GitHub only) | `docs/workflow-autonomous-harness-plan.md`, `docs/general-purpose-harness-extraction-plan.md`, `docs/claude-parity-roadmap.md`, `docs/superpowers/specs/2026-06-23-session-aware-model-tiering-design.md` | No |
121
+
122
+ Canonical safety references: apply authority and primary-tree writes are in
123
+ `Authority Profiles And Apply Boundary`; raw run artifacts, transcript fallback,
124
+ debug capture, and event access are in `Source Of Truth And Transcript Fallback`;
125
+ lifecycle recovery and cleanup are in `Durable Lifecycle And Cleanup`;
126
+ live-gate behavior is in `Live Gates`; and the complete `workflow_*` tool table
127
+ is in `docs/workflow-plugin.md#workflow-tool-reference`.
128
+
129
+ ## Hooks
130
+
131
+ The plugin factory (`workflow-kernel/workflow-plugin.js`) returns exactly five
132
+ opencode plugin hooks:
133
+
134
+ | Hook | What it does |
135
+ | --- | --- |
136
+ | `config` | Configures workflow permission defaults and registers bundled/extension commands, skills, and other config-time entrypoints. Mutates the passed config object in place. |
137
+ | `event` | Fire-and-forget lifecycle listener. Tracks session idle state and delivers best-effort background workflow-completion notifications; failures are swallowed so a notification error can never destabilize the session. |
138
+ | `dispose` | Clears in-memory notification runtime state when the plugin instance is torn down. |
139
+ | `"chat.params"` | Applies lane-effort model-tiering parameters to outgoing chat requests. |
140
+ | `tool` | Registers the `workflow_*` tool family (`workflow_run`, `workflow_status`, `workflow_events`, `workflow_reconcile`, `workflow_cancel`, `workflow_pause`, `workflow_kill`, `workflow_save`, `workflow_list`, `workflow_cleanup`, `workflow_apply`, `workflow_salvage`, `workflow_roles`, `workflow_models`, `workflow_templates`, `workflow_template_save`, `workflow_live_gates`), plus any NET-NEW tools contributed by a configured trusted extension. See `docs/workflow-plugin.md#workflow-tool-reference` for the full per-tool contract. |
141
+
142
+ ## Command And Skill Registration
143
+
144
+ At opencode startup the plugin config hook registers the bundled commands —
145
+ `workflow-live-gates-release-check`, `repo-bughunt`, and `repo-review` — plus any
146
+ commands contributed by configured extensions (e.g. `beads-drain` from the beads
147
+ extension), and adds this plugin's `skills` directory (and extension skill dirs) to
148
+ `skills.paths`. Restart opencode after
149
+ changing plugin commands, skills, or config-time registration code; the running
150
+ process keeps the previously loaded config.
151
+
152
+ ## Extension Trust Boundary
153
+
154
+ Configured workflow extensions are trusted Node modules, not sandboxed workflow
155
+ guests. When an extension path appears in opencode config, its module top-level
156
+ code and factory run in the host process with normal Node privileges before the
157
+ kernel can validate its exported shape. There is no built-in signature, hash pin,
158
+ or allowlist beyond the configured path.
159
+
160
+ The QuickJS sandbox, path policy, approval hashes, and workflow authority guards
161
+ protect guest workflow execution and core tool paths. They do not automatically
162
+ wrap extension-contributed tools, drain adapters, or mutation finalizers. An
163
+ extension can voluntarily call injected guard helpers, but a configured extension
164
+ must be treated as full-privilege host code that can read/write local files,
165
+ spawn processes, use network-capable APIs, and declare its own drain live-gate
166
+ requirements. Only install extensions you would trust as local code.
167
+
168
+ ## Source Versus Local State
169
+
170
+ Intentional source assets live in `.github/`, `commands/`, `docs/`, `skills/`,
171
+ `tests/`, the source-checkout reference extension directory, `workflow-kernel/`,
172
+ `workflows/`, `opencode-workflows.js`, root package/community files, and this README. The local
173
+ `.opencode/` directory is runtime/install state for this work area, including
174
+ goal state, workflow run records, package metadata, and `node_modules`.
175
+
176
+ Those `.opencode/` artifacts are ignored rather than deleted so active workflow
177
+ or goal evidence remains available locally while staying out of source review.
178
+
179
+ ## Beads Drain
180
+
181
+ `beads-drain` is not part of the published core package. It is contributed only
182
+ after a Beads extension is explicitly configured, and is then discoverable
183
+ through `workflow_list` with `scope: "extension"`. See
184
+ [docs/workflow-extensions.md#beads-is-the-reference-extension](https://github.com/mcrescenzo/opencode-workflows/blob/main/docs/workflow-extensions.md#beads-is-the-reference-extension)
185
+ (GitHub only, not packaged) for the source-checkout reference extension shape
186
+ and config pattern. Start it by name rather than by path:
187
+
188
+ ```js
189
+ workflow_run({ name: "beads-drain", args: { mode: "dry-run", scope: { issueTypes: ["task"], limit: 5 } } })
190
+ ```
191
+
192
+ Dry-run is the default safe path. It discovers and classifies scoped Beads work,
193
+ reports planned ready items, skipped/human-gated items, gate status, and final
194
+ dry proof without claiming Beads, spawning child edit lanes, creating worktrees,
195
+ applying patches, or mutating Beads.
196
+
197
+ Empty or omitted `args` keep that conservative dry-run default. The extension
198
+ `beads-drain` workflow rejects non-null args that are not JSON objects, including
199
+ strings and arrays, before approval preview so a mistyped scope cannot silently dry-run.
200
+ When `mode: "autonomous-local"` is requested and `background` is omitted, the
201
+ beads-drain workflow defaults to a background run so status can be inspected while it
202
+ is active. Pass `background: false` explicitly for a foreground non-dry run.
203
+ Child lane prompts default to a 10 minute timeout. For dogfood runs where lanes
204
+ are making progress but timing out, pass top-level `laneTimeoutMs` (or the alias
205
+ `childPromptTimeoutMs`) up to `3600000` milliseconds; keep `maxAgents` low until
206
+ successful lane closeout is proven.
207
+
208
+ Non-dry Beads drain fails closed before adapter discovery, Beads mutation, or
209
+ lane launch unless the required active-runtime live gates are verified:
210
+ permission enforcement, command-scoped bash denial, secret-read denial,
211
+ structured output, directory rooting, local Git integration worktree isolation,
212
+ and cancellation. `unsafeAcceptUnverifiedPermissions` is not a non-dry bypass;
213
+ run `mode: "dry-run"` or fix the reported live gates before retrying
214
+ `mode: "autonomous-local"`.
215
+ Use `/workflow-live-gates-release-check` only after explicit approval for
216
+ token/worktree/background/notification side effects.
217
+
218
+ The extension `beads-drain` script is a thin wrapper around the host-owned
219
+ `drain({ adapter: "beads" })` primitive. The trusted kernel and Beads adapter own
220
+ preflight, snapshots, claims, isolated implementation lanes, validation, mutation
221
+ staging/finalization, and final dry proof instead of reimplementing those steps in
222
+ script-body prompt plumbing. The drain runs until the scoped queue is dry, a legal
223
+ stop is reached, or bounded wave/attempt caps are exhausted. In
224
+ `mode: "autonomous-local"` a verified successful diff plan is applied to the local
225
+ primary tree in-run (accepted code changes land; staged Beads closes/followups
226
+ finalized and read back) and the run ends in `completed`/`not_dry`/legal-stop rather
227
+ than `awaiting-diff-approval`. A failed drain surfaces as `failed-with-diff-plan`,
228
+ preserved for review and applied through `workflow_apply`; apply errors enter the
229
+ retryable `apply-failed` state. `remote_sync` is always `local-only`.
230
+
231
+ If an implementation lane times out or fails after creating a dirty worktree, the
232
+ controller records dirty-timeout salvage metadata in the lane projection and drain
233
+ result (`salvaged`), preserves the worktree, and includes the salvage path and
234
+ changed files in the Beads cleanup note before reopening/unassigning the issue. It
235
+ does not auto-close or auto-apply salvaged dirty work without successful controller
236
+ validation.
237
+
238
+ ## Repo Review Suite
239
+
240
+ The `repo-*` review suite is a set of read-only analysis workflows ported from a
241
+ Claude Code suite. It ships **eight leaf workflows** (`repo-bughunt`,
242
+ `repo-security-audit`, `repo-test-gaps`, `repo-cleanup`, `repo-modernize`,
243
+ `repo-perf`, `repo-complexity`, `repo-deps`), a **`repo-review` meta orchestrator**
244
+ that runs all eight with one shared recon pass and merges/ranks findings cross-domain,
245
+ and two commands: `/repo-bughunt` (single-domain) and `/repo-review` (full-suite).
246
+
247
+ Every leaf and the meta run under `profile: "read-only-review"` — no shell, edit, git,
248
+ network, or MCP, and no live-gate preflight. The QuickJS guest physically cannot write
249
+ files, so every envelope carries `reportPath: null`; **guests return data, they never
250
+ write**. Only the command wrapper persists a report under the gitignored
251
+ `.repo-review/runs/` directory (`<run-id>-bughunt-report.md` or
252
+ `<run-id>-repo-review-report.md`), and that report artifact is the only allowed
253
+ workspace write from a repo-review command.
254
+
255
+ Launch leaves/meta by name. Example leaf and meta runs (the operator resolves `fast`
256
+ (recon + finders) and `deep` (skeptics) model tiers via `workflow_models`; see
257
+ `workflow-model-tiering`):
258
+
259
+ ```js
260
+ workflow_run({ name: "repo-bughunt", args: { "depth": "normal", "paths": ["src"] }, modelTiers: { "fast": "...", "deep": "..." } })
261
+ workflow_run({ name: "repo-review", args: { "depth": "thorough", "domains": ["bughunt", "security"] }, modelTiers: { "fast": "...", "deep": "..." } })
262
+ ```
263
+
264
+ Workflow source can additionally request OpenAI-only per-lane reasoning effort:
265
+ `agent(prompt, { tier: "deep", effort: "high" })`. Valid values are `minimal`,
266
+ `low`, `medium`, and `high`; the plugin applies them through OpenAI
267
+ `chat.params` provider options. A lane whose resolved model is not an OpenAI
268
+ provider fails before child launch rather than silently ignoring `effort`.
269
+
270
+ The meta uses **static one-level nesting only**: it calls each leaf via a literal
271
+ `workflow("repo-bughunt", args)` name, and a nested leaf's own declared
272
+ `maxAgents`/`concurrency` is **ignored at runtime** — the parent run's budget governs
273
+ the whole tree (see "Sizing `maxAgents`" below). Read the merged result via
274
+ `workflow_status({ runId, detail: "result" })`; treat raw
275
+ `.opencode/workflows/runs/` files as local sensitive artifacts and prefer the redacted
276
+ `workflow_status` readback.
277
+
278
+ **Nothing mutates automatically.** `review-materialize`, `beads-drain`,
279
+ `workflow_apply`, git writes, and Beads mutation are separate explicit follow-ups and
280
+ are never run by any repo-review workflow or command. The optional
281
+ `inspect-with-shell` carve-outs (the `repo-complexity` churn lens and `repo-deps`
282
+ manifest inspection) are deferred until the `permissionEnforcement` and
283
+ `commandScopedBash` live gates are verified; until then every `repo-*` lane stays plain
284
+ read-only-review. Note also that native structured output is currently unavailable in
285
+ this runtime, so the kernel's structured-text fallback is the production path.
286
+
287
+ The full user-facing guide, per-domain one-liners, the arg tables, the nested-restriction
288
+ and budgeting details, and the deferred-mutation boundary live in
289
+ [docs/repo-review.md](https://github.com/mcrescenzo/opencode-workflows/blob/main/docs/repo-review.md)
290
+ (GitHub only, not packaged). The exact leaf envelope, finding fields,
291
+ fingerprint, counts shape, size-fit semantics, and meta-to-leaf arg contract are the
292
+ named technical spec in [docs/repo-review-leaf-contract.md](https://github.com/mcrescenzo/opencode-workflows/blob/main/docs/repo-review-leaf-contract.md)
293
+ (GitHub only, not packaged).
294
+
295
+ ## Sizing `maxAgents`
296
+
297
+ `maxAgents` caps the number of **child agent lanes a run launches**, counted as a
298
+ single `agentsStarted` tally. Each `agent()` call costs one slot, so a
299
+ `parallel()` of N lanes costs N and a `pipeline()` of N stages costs N. Combining,
300
+ ranking, deduping, or formatting lane results in plain JavaScript inside the
301
+ workflow body costs **zero** slots — the controller already holds those return
302
+ values in memory. A synthesis step costs a slot only when it is itself another
303
+ `agent()` lane (for example, an LLM that writes the final report); pure-JS
304
+ synthesis does not.
305
+
306
+ Size it to **(agent lanes you launch) + (1 per agent-based synthesis lane)**:
307
+
308
+ - Five research lanes with the results merged in JS: `maxAgents >= 5`.
309
+ - Five research lanes plus one agent-based synthesis lane: `maxAgents >= 6`.
310
+
311
+ Nested `workflow()` lanes **share the parent run's budget**: the nested body runs
312
+ against the same run context, so its `agent()` launches draw down the same
313
+ `maxAgents`, and a nested workflow's own `meta.maxAgents` is ignored at runtime.
314
+ Size the top-level `maxAgents` to cover the parent lanes plus every lane any
315
+ nested workflow will launch. See `docs/workflow-plugin.md` ("Sizing `maxAgents`:
316
+ child-agent accounting") for the full accounting and the code references.
317
+
318
+ ## Authority Profiles And Apply Boundary
319
+
320
+ Workflow authority is approved once at launch. The approval hash covers the
321
+ source hash, runtime args, profile-expanded authority, budgets, concurrency,
322
+ child model, nested workflow snapshots, and base commit when edits are possible.
323
+ Approved runs must not stop mid-run for interactive permission prompts; they
324
+ fail closed before lane launch when required live gates are unavailable,
325
+ `available-unverified`, `blocked`, or `failed-with-evidence`.
326
+
327
+ | Profile | Purpose | Auto-Approve Tier | Required Gate Shape |
328
+ | --- | --- | --- | --- |
329
+ | `read-only-review` | Read-only analysis lanes. | `readOnly` | No elevated authority gates. Child-capable runs still preflight `permissionEnforcement` (read-only child lanes are contained by a deny-by-default permission ruleset). |
330
+ | `inspect-with-shell` | Read-only work plus a command-scoped, audited read-only shell (e.g. `git ls-files`, `git log --numstat`, `npm ls --depth=0`, `cargo tree`, `pip list`, `go list`). Shell chaining, redirection, filesystem mutation, network fetch, and package install/publish are denied at the permission-rule level. | `readOnly` | Permission enforcement and command-scoped bash denial. |
331
+ | `drain-dry-run` | Safe drain preview. | `readOnly` | No elevated authority gates. Child-capable runs still preflight `permissionEnforcement`. |
332
+ | `drain-autonomous-local` | Non-dry local drain through integration worktrees. | `all` | Permission enforcement, command-scoped bash denial, secret-read denial, structured output, directory rooting, integration worktree isolation, cancellation. |
333
+ | `edit-plan-only` | Native isolated edit plans. | `worktree` | Permission, native worktree API, directory rooting, worktree edit isolation. |
334
+ | `apply-approved-plan` | Hash-gated primary-tree apply. | `worktree` | Permission enforcement, native worktree API, directory rooting, worktree edit isolation. |
335
+
336
+ > **Network/MCP workflow authority is permission-rule enforced.** Ad-hoc `network`/`mcp`
337
+ > authority (via `args.authority` or `meta.authority`) launches after the normal approval and
338
+ > permission-enforcement gates. The runtime emits `webfetch`/`websearch`/MCP permission rules from
339
+ > the resolved authority profile. `networkAccess` remains an informational reserved diagnostic;
340
+ > `mcpAccess` has an opt-in behavioral probe, and `mcpPolicy: { allow, deny }` can scope MCP
341
+ > server/tool patterns at the run or lane level without allowing lane escalation.
342
+
343
+ `workflow_apply` is the normal explicit primary-tree write boundary for edit or
344
+ integration runs. It requires `approvalIntent: "apply"`, approved source hash,
345
+ base commit, diff-plan hash, domain mutation hash, and clean primary dirty-state
346
+ proof before writing.
347
+ The single intentional in-run apply exception is the extension-trusted non-dry `beads-drain`
348
+ under the `drain-autonomous-local` profile: its one-time launch approval
349
+ authorizes in-run apply of a verified successful diff plan to the local primary
350
+ tree (accepted code changes land; staged Beads closes/followups finalize) instead
351
+ of stopping at `awaiting-diff-approval`. Every other edit/integration run keeps
352
+ `workflow_apply` as the explicit, hash-gated write boundary; failed autonomous
353
+ drains keep `failed-with-diff-plan` for review through `workflow_apply`.
354
+ Elevated workflow launch may run required live-gate preflight probes after
355
+ approval and before mutation or lane launch. Permission, structured-output,
356
+ directory-rooting, integration-worktree-isolation, and cancellation probes each
357
+ spawn a short-lived child session (model token use), and the worktree/
358
+ directory-rooting probes create and remove scratch worktrees. The approval
359
+ preview itself never probes; these side effects begin only after the run is
360
+ approved.
361
+ Apply/finalization state is durable and retryable: `apply-running` and
362
+ `apply-failed` runs are protected from cleanup, and domain mutations finalize
363
+ only after the patch apply path succeeds.
364
+
365
+ Raw `result.json`, ledgers, diff plans, request files, and run state under
366
+ `.opencode/workflows/runs/` are local sensitive artifacts. `workflow_status
367
+ detail=result` redacts credential-like keys for display, but the raw files remain
368
+ local evidence and should not be published casually.
369
+
370
+ ### Workflow Source Trust Boundary
371
+
372
+ `workflow_run` accepts a workflow by `name`, inline `source`, or explicit
373
+ `scriptPath`. Named workflows load from the trusted registries (project
374
+ `.opencode/workflows`, the global workflows directory, configured extension
375
+ workflow dirs, and the bundled plugin workflows — in that resolution order) and
376
+ inline `source` is self-contained, so neither is path-restricted.
377
+ An explicit `scriptPath` that resolves outside those trusted workflow roots
378
+ fails closed before the approval preview: absolute, out-of-root, and relative
379
+ paths with `..` traversal are rejected before the target file is even stat-ed.
380
+ Pass `allowExternalScriptPath: true` to opt in; the approval preview then shows
381
+ the resolved absolute `Source:` path, its `sourceHash`, and an `External source
382
+ (allowExternalScriptPath opt-in): true` line so the external path and hash are
383
+ visible for review before approval.
384
+
385
+ ## Durable Lifecycle And Cleanup
386
+
387
+ `workflow_cancel` and `workflow_pause` write durable `cancel-request.json` and
388
+ `pause-request.json` files even when the active run is owned by another opencode
389
+ process. Active runs observe those requests before launching more child lanes.
390
+ In-process cancel/pause still aborts active child sessions best-effort.
391
+
392
+ `workflow_reconcile` is the explicit mutating recovery path for stale active runs
393
+ and stale workflow locks; `workflow_status` remains read-only. `workflow_cleanup` uses cleanup locks before deletion and preserves active,
394
+ locked, pinned, malformed, corrupt, interrupted, paused, ambiguous edit,
395
+ `apply-running`, and retryable `apply-failed` runs. Dry-run output includes
396
+ `protectedRuns` reason entries plus delete candidates so cleanup can be reviewed
397
+ before non-dry deletion.
398
+
399
+ ## Source Of Truth And Transcript Fallback
400
+
401
+ Workflow state has a strict source-of-truth hierarchy. Stronger evidence always
402
+ wins; weaker evidence is recovery/diagnostic only and never finalizes work:
403
+
404
+ 1. **Controller-owned run artifacts** (authoritative): the append-only
405
+ `journal.jsonl`, the durable `result.json`, the domain/integration ledgers,
406
+ and integration worktrees under `.opencode/workflows/runs/`. These are
407
+ captured by the controller (the trusted kernel hub) and are the only evidence
408
+ that may close Beads, apply diffs, or merge integration lanes.
409
+ 2. **`workflow_status`** (persisted inspection): the authoritative read-only and
410
+ recovery surface over those artifacts. Use `detail: "result"` for final
411
+ output and `detail: "full"` only for diagnostics/apply internals. Foreground
412
+ `workflow_run` includes the redacted return value inline when it fits the
413
+ inline cap; larger foreground results point to the persisted
414
+ `workflow_status({ detail: "result" })` readback. Result readback preserves
415
+ full-fidelity non-secret strings/arrays while it fits the readback cap, then
416
+ returns a partial projection with `resultReadback.truncated` metadata.
417
+ 3. **Session transcripts** (diagnostic / fallback only): child-session message
418
+ history persisted in opencode's session store. Transcript evidence is
419
+ strictly weaker than a controller-captured structured result and is used only
420
+ to surface and salvage orphaned lanes, never as the primary substrate.
421
+
422
+ Subagents never hand off directly to each other; the controller is always the
423
+ hub, and its captured return values are the primary substrate. Transcripts are a
424
+ fallback for a narrow crash window only.
425
+
426
+ `MAX_RESULT_BYTES` is still the guest return cap, not an invitation to raise
427
+ payload sizes freely. The source/result caps are coupled to the 32 MB QuickJS
428
+ heap; raising them should be reviewed alongside the sandbox heap limit.
429
+
430
+ ### The crash window and `workflow_salvage`
431
+
432
+ A child lane can finish and persist its transcript while the owning opencode
433
+ process dies before the controller writes the authoritative journal entry. In
434
+ that window the result exists in the child transcript but is missing from
435
+ workflow state. `workflow_salvage` is the explicit, hash-gated recovery path for
436
+ those orphaned read-only lanes.
437
+
438
+ - **Preview/approve gate.** A preview call (no `approve`) lists each recoverable
439
+ lane — `callId`, `childID`, JSON-parse verdict, final-message presence/length,
440
+ a length-truncated, free-text-secret-masked `redactedSnippet`, and
441
+ `resumeSignatureAvailable` — and
442
+ writes nothing. It returns an `approvalHash`. Re-running with `approve: true`
443
+ and that matching `approvalHash` (optionally narrowing with `callIds`) writes
444
+ the recovered entries. A wrong or missing hash stays in preview and writes
445
+ nothing. The tool is gated like `workflow_reconcile`
446
+ (`assertWriteWorkflowAllowed`) and is denied in plan mode.
447
+ - **`salvagedFromTranscript` tag.** Every salvaged journal entry is tagged
448
+ `salvagedFromTranscript: true` plus `salvageValidation: { kind: "json-parse",
449
+ originalSchemaAvailable: false }`, so a recovered result is never mistaken for
450
+ a normally-captured, schema-validated one. The original per-lane AJV schema is
451
+ not durably persisted, so salvage validation is conservative JSON-parse only:
452
+ outcome `success` requires the final assistant message to parse as JSON, else
453
+ the entry is written with outcome `failure` and an error summary.
454
+ - **Read-only lane scope.** Salvage only writes entries for read-only/report
455
+ lanes. Edit/integration lanes (those with a worktree path or integration-lane
456
+ marker) are reported as `salvage-skipped: edit-lane-without-commit` and are
457
+ never salvaged, because a salvaged lane has no worktree commit by
458
+ construction. Unreadable transcripts are reported `transcript-unreadable:*`
459
+ and skipped.
460
+ - **No auto-apply; never finalizes domain mutations or primary writes.** Salvage
461
+ never calls `integrate()` or `runAutoApply`, never touches `state.json`,
462
+ worktrees, or integration ledgers, and never closes Beads or applies a diff.
463
+ It only appends a tagged synthetic journal entry and updates the lane
464
+ projection on a durable interrupted run directory. Salvage is always explicit
465
+ and approved — never automatic on resume.
466
+ - **Resume reuse, by code-enforced asymmetry.** On a later resume a salvaged
467
+ read-only result is reused as a cache hit instead of re-running the lane, and
468
+ emits a distinct `cache.salvaged_hit` event (separate from `cache.hit`) so its
469
+ weaker provenance stays observable. The read-only-vs-edit asymmetry is enforced
470
+ in code, not just documented: integration is filtered through `isLaneIntegrable`,
471
+ which requires a real worktree commit and rejects any `salvagedFromTranscript`
472
+ lane even if it somehow carried `committed: true`, so a salvaged claim can
473
+ never reach `integrate()` or `runAutoApply`.
474
+ - **Signature fallback for shifted lanes.** If an edited workflow body inserts or
475
+ reorders `agent()` calls, resume can claim-once reuse a prior successful
476
+ journal entry with the same lane signature in the same fan-out scope. This
477
+ emits `cache.signature_hit` with `originalCallId` and retags copied edit or
478
+ integration plan entries to the new `callId` in place.
479
+
480
+ The controller reads these transcripts via the **raw SDK
481
+ `session.messages` API** (unredacted), not through the redacting
482
+ `session_read` wrapper from the separate, independently published
483
+ [`@mcrescenzo/opencode-sessions`](https://github.com/mcrescenzo/opencode-sessions)
484
+ plugin (not a dependency of this package — named here only as a point of
485
+ comparison). This is consistent with the journal already holding unredacted
486
+ lane results; salvage recovers that same class of content.
487
+
488
+ ### Narrowing the crash window: lane checkpoints
489
+
490
+ Workflow-native lane checkpoints narrow the window so transcript salvage is
491
+ rarely needed. The controller writes a durable `lanes/<callId>.request.json`
492
+ before `session.prompt` and `lanes/<callId>.result.json` immediately after it
493
+ returns. On resume, a same-signature checkpoint result is reused before the
494
+ authoritative journal check and emits a distinct `cache.checkpoint_hit` event.
495
+ Checkpoint files are a narrower, earlier, transcript-independent capture; the
496
+ journal remains the source of truth and supersedes them (a superseded checkpoint
497
+ is removed). Unlike transcript salvage, checkpoint recovery needs no approval
498
+ because it is a controller-owned own-store capture, not weaker transcript
499
+ evidence.
500
+
501
+ ### Background execution is not durable across process death
502
+
503
+ Transcript fallback recovers a completed child's result; it does **not** make
504
+ background execution durable. A background run dies with the owning opencode
505
+ process. There is no detached supervisor, no respawn, and no attach: the run
506
+ directory is left behind and surfaces as stale until `workflow_reconcile` marks
507
+ it interrupted, at which point `workflow_salvage` can recover orphaned read-only
508
+ lane results that completed before the crash. Completing the underlying workflow
509
+ work after process death remains out of scope until a supervisor exists.
510
+
511
+ ## Background Completion Notifications
512
+
513
+ `workflow_run({ background: true })` returns a run id immediately and continues
514
+ execution in the current opencode process. When that process remains alive, the
515
+ plugin writes a compact completion notice after terminal run state is persisted
516
+ and delivers it to the invoking session after that session emits `session.idle`.
517
+ When `background` is omitted, large/long runs default to background if requested
518
+ fan-out or declared/requested duration trips the built-in heuristic: per-call
519
+ `maxAgents >= 8`, at least three serialized concurrency waves from per-call
520
+ `maxAgents` and `concurrency`, or `maxRuntimeMs >= 600000`. Declared/default
521
+ `maxAgents` ceilings are not treated as predictions of actual fan-out.
522
+ Explicit `background: true` and `background: false` always win, and resumed runs
523
+ keep their originally pinned mode. If the host lacks `session.promptAsync`, the
524
+ run still starts in background, but `workflow_run` warns that no completion
525
+ prompt can be delivered and callers must poll `workflow_status`.
526
+
527
+ Background runs expose three separate status surfaces:
528
+
529
+ | Surface | Purpose |
530
+ | --- | --- |
531
+ | `workflow_status` | Authoritative persisted inspection and recovery path. Use `detail: "result"` for final workflow output, and `detail: "full"` only for diagnostics/apply internals. |
532
+ | Toasts | Best-effort transient TUI hints for humans. They are not durable transcript context. |
533
+ | Idle notification | Best-effort `client.session.promptAsync` continuation to the original session, only after `session.idle` and only while the same opencode process is alive. |
534
+
535
+ Notifications are idempotent per run/session and include the run id, terminal
536
+ status, result path when available, a `workflow_status detail=result` hint, and
537
+ bounded error or diff-plan details.
538
+ `awaiting-diff-approval` and `review-required` notifications ask for follow-up;
539
+ they never apply changes automatically.
540
+
541
+ ### Notification recovery is not durable execution
542
+
543
+ When the owning opencode process stays alive, unsent completion notifications are
544
+ recovered automatically across a plugin/module reload: on each `session.idle` the
545
+ plugin re-scans the active project/worktree run roots for persisted
546
+ `notification.json` records whose `sentAt` is still null and re-enqueues the
547
+ well-formed, session-matching ones before delivery. Already-sent, malformed, and
548
+ unrelated-session records are skipped safely. This is notification **recovery**
549
+ only — it surfaces a completion notice that was already persisted by a prior
550
+ in-process run.
551
+
552
+ It is **not** durable execution across opencode process death. A background run
553
+ dies with the owning process; its run directory is left behind and surfaces as
554
+ stale until `workflow_reconcile` marks it interrupted. There is no detached
555
+ supervisor, no respawn, and no attach. Completing the underlying workflow work
556
+ after process death is out of scope until a supervisor exists.
557
+
558
+ ## Workflow Toast Status Cards
559
+
560
+ Workflow toasts are best-effort plain-text status cards for human visibility.
561
+ Persisted run state from `workflow_status` remains the authoritative source for
562
+ details, recovery, and automation.
563
+
564
+ Default toast behavior is tuned for long-running workflows:
565
+
566
+ | Setting | Default | Rationale |
567
+ | --- | --- | --- |
568
+ | Toast duration | 90 seconds | Keeps the current card visible without assuming a persistent dashboard. |
569
+ | Progress refresh interval | 45 seconds | Refreshes slower than the display duration to reduce stale stacking. |
570
+ | Forced refresh window | 75 seconds | Re-sends unchanged running state before the previous card expires. |
571
+ | Message cap | 1000 characters | Allows useful lane summaries while keeping prompts and secrets out of UI text. |
572
+ | Lane rows | 4 | Shows the longest-running active lanes and preserves idle lanes in the limited card space. |
573
+
574
+ Four card types are rendered in the same indented-outline style:
575
+
576
+ - **Heartbeat / phase**: workflow name, elapsed time, current phase `n/N`,
577
+ active lanes, done/queued/fail counters, optional pipeline/parallel
578
+ `items N/M`, optional budget percent, and the latest narrator `log()` line.
579
+ Heartbeats fire every 45 seconds, force-refresh after 75 seconds even when the
580
+ signature is unchanged, and fire immediately on `phase()` changes.
581
+ - **Problem**: lane failures/retries, stalls, and budget threshold crossings.
582
+ Lane-failure storms are cooldown-batched; budget cards fire once at 80% and
583
+ once at 100% for a run.
584
+ - **Terminal**: final phase breadcrumb, lane totals, recovered count, token/cost
585
+ totals, optional budget percent, last log line, and an `inspect:
586
+ workflow_status` hint.
587
+ - **Apply**: apply-running, applied, review-required, and apply-failed states
588
+ with patch count, diff-plan hash, errors, and the same inspect hint.
589
+
590
+ Low-value fields are intentionally omitted from running heartbeat cards: no run
591
+ id body line, in-run dollar cost, replayed stats, concurrency/maxAgents, cache
592
+ stats, or `inspect:` hint. Problem, terminal, and apply cards keep the inspect
593
+ hint because those cards usually require follow-up.
594
+
595
+ The plugin only relies on the documented opencode toast fields: `title`,
596
+ `message`, `variant`, and `duration`. It does not assume toast replacement,
597
+ stable toast ids, markdown tables, or rich rendering. Toast delivery has a short
598
+ timeout and failures are ignored so UI behavior cannot affect workflow
599
+ correctness.
600
+
601
+ `workflowToastAscii: true` or `toastCards.ascii: true` switches all four card
602
+ types to a plain-ASCII variant for terminals or fonts that misrender box-drawing
603
+ glyphs. The default remains the outline style; the 2026-07-07 live TUI probe
604
+ rendered multiline box-drawing, warning/check, and arrow glyphs correctly in the
605
+ normal opencode TUI.
606
+
607
+ Recommended manual TUI check after changing toast timing or formatting:
608
+
609
+ 1. Restart opencode so the plugin code is reloaded.
610
+ 2. Start a small workflow with declared phases, at least one `phase()` change,
611
+ and a narrator `log()` line.
612
+ 3. Observe whether multiline cards stack reasonably, box-drawing glyphs align,
613
+ long lane labels wrap readably, phase changes emit immediately, and terminal
614
+ states are clear.
615
+ 4. If glyphs misrender, enable the ASCII flag instead of changing the default
616
+ renderer.
617
+
618
+ ## Live Gates
619
+
620
+ `workflow_live_gates({ format: "json" })` is token-free by default. It reports
621
+ API shape and configuration as `available-unverified` until a behavioral probe is
622
+ run or a test injects forced gate evidence. Permission safety probes use the
623
+ child-session `session.prompt` path with tools enabled, matching workflow lanes;
624
+ direct `session.shell` behavior is diagnostic only and is not sufficient Beads
625
+ lane safety evidence.
626
+
627
+ Opt-in probe flags include:
628
+
629
+ ```json
630
+ {
631
+ "approvalIntent": "probe",
632
+ "probePermissionEnforcement": true,
633
+ "probeCommandScopedBash": true,
634
+ "probeSecretReadDeny": true,
635
+ "probeStructuredOutput": true,
636
+ "probeWorktreeApi": true,
637
+ "probeDirectoryRooting": true,
638
+ "probeWorktreeEditIsolation": true,
639
+ "probeIntegrationWorktreeIsolation": true,
640
+ "probeBackgroundContinuation": true,
641
+ "probeConcurrencyCapacity": true,
642
+ "concurrencyProbeLimit": 16,
643
+ "probeCancellation": true,
644
+ "probeWorkflowNotification": true
645
+ }
646
+ ```
647
+
648
+ Live probes require `approvalIntent: "probe"` and an active opencode server/client. Session probes can spend
649
+ model tokens; the concurrency-capacity probe launches `concurrencyProbeLimit`
650
+ child prompt calls at the same time and can expose provider or runtime rate
651
+ limits. Worktree probes can create and remove throwaway worktrees. Gate
652
+ states mean:
653
+
654
+ | State | Meaning |
655
+ | --- | --- |
656
+ | `blocked` | Required API shape or precondition is unavailable. |
657
+ | `available-unverified` | API shape exists, but behavior was not live-probed. |
658
+ | `verified` | Behavioral evidence was observed. |
659
+ | `failed-with-evidence` | A probe ran and did not prove the safety claim. |
660
+
661
+ Workflow features that depend on permissions, native worktrees, directory
662
+ rooting, local Git integration worktrees, or worktree edit isolation fail closed
663
+ when required capabilities are unavailable or `available-unverified`. Only
664
+ `verified` live-gate evidence, or explicit test capability evidence, is
665
+ sufficient for elevated workflow authority.
666
+
667
+ A verified gate's `evidenceStrength` distinguishes directly-observed target
668
+ behavior (`observed`) from weaker evidence: `no-attempt-fallback` (permission
669
+ fallback when the denied tool is hidden) and `in-process-smoke`
670
+ (`backgroundContinuation`, which only yields the event loop and does not
671
+ exercise the opencode background subsystem or imply restart survival).
672
+ Directory-rooting and integration-worktree rooting no longer verify from
673
+ model-reported cwd text alone; a text-only echo is reported as
674
+ `available-unverified` until a tool-anchored sentinel read proves the behavior.
675
+ `concurrencyCapacity` is diagnostic: it characterizes whether this active runtime
676
+ can complete an N-wide burst of trivial `session.prompt` calls. It is not required
677
+ for read-only or Beads safety gates, but it is the evidence to consult before
678
+ raising the hard concurrency ceiling for production runs.
679
+ See `/workflow-live-gates-release-check` for the weak-evidence release policy.
680
+
681
+ Non-dry Beads drain requires the local Git `integrationWorktreeIsolation` gate
682
+ instead of the native opencode `worktreeApi` and `worktreeEditIsolation` gates,
683
+ because integration lanes use the plugin-local Git worktree adapter. Native
684
+ worktree gates still report edit-mode readiness separately.
685
+
686
+ `unsafeAcceptUnverifiedPermissions` is intentionally not a bypass for non-dry
687
+ Beads drain. Permission gates (`permissionEnforcement`, `commandScopedBash`, and
688
+ `secretReadDeny`) must be verified alongside structured output, directory
689
+ rooting, local Git integration worktree isolation, and cancellation. If any gate
690
+ is unverified, blocked, or failed-with-evidence, use dry-run mode or fix the
691
+ reported live gates before retrying non-dry.
692
+
693
+ ### Active Runtime Release Check
694
+
695
+ The opt-in slash command `/workflow-live-gates-release-check` runs every
696
+ behavioral `workflow_live_gates` probe in the active opencode runtime and
697
+ reports raw JSON evidence. Use it only after explicit approval for token,
698
+ worktree, background, and notification side effects.
699
+
700
+ Readiness is reported per capability tier, not as a single all-gates verdict:
701
+ non-dry `beads-drain` uses local Git `integrationWorktreeIsolation` and does
702
+ not require the native `worktreeApi` or `worktreeEditIsolation` gates, so a
703
+ blocked native worktree gate does not by itself block Beads. Each tier passes
704
+ only when `configured: true` and every gate in that tier's required subset is
705
+ `state: "verified"`; any `blocked`, `available-unverified`, or
706
+ `failed-with-evidence` gate in the relevant tier blocks that tier's release
707
+ claim. See `/workflow-live-gates-release-check` for the exact full-edit and
708
+ beads-non-dry subsets and the evidence-strength caveats. Normal `npm test`,
709
+ `npm run test:live-gates`, and `npm run test:workflows` remain no-token by
710
+ default.
711
+
712
+ ## Workflow Recipes
713
+
714
+ [docs/workflow-recipes.md](https://github.com/mcrescenzo/opencode-workflows/blob/main/docs/workflow-recipes.md)
715
+ (GitHub only, not packaged) collects reusable, copy-adaptable workflow shapes so a
716
+ fresh agent does not have to rebuild a known-good run from scratch. Each recipe
717
+ ships with its authority tier, model tiering, `maxAgents`/`concurrency` sizing,
718
+ the two-phase preview/approve `workflow_run` shape, and the
719
+ `workflow_status({ runId, detail: "result" })` readback.
720
+
721
+ Start with the **first-run read-only slice** recipe when standing up any new
722
+ workflow. It ships as a saved template named `first-run-slice` (in
723
+ `DEFAULT_TEMPLATES`; list it with `workflow_templates`, save a copy with
724
+ `workflow_template_save({ template: "first-run-slice" })`, or paste its body as
725
+ `source`). It is the smallest safe shape: `profile: "read-only-review"`, one or
726
+ two scoped `parallel()` lanes, pure-JS synthesis, `maxAgents: 2`/`concurrency: 2`,
727
+ and no filesystem or Beads writes. Use it to validate the preview -> approve
728
+ handshake, the per-lane structured shape, and the
729
+ `workflow_status({ runId, detail: "result" })` readback before you widen the
730
+ fanout or nest workflows. The recipe documents `maxAgents` sizing (one slot per
731
+ `agent()` lane) and a failure-handling checklist (stale `approvalHash`, a lane
732
+ that fails its schema, evidence-free claims that land in
733
+ `droppedUnsupportedClaims`).
734
+
735
+ The next recipe is **generic read-only deep research**: scoped `parallel()`
736
+ inventory lanes that return claim + concrete evidence, with pure-JS synthesis
737
+ that drops any evidence-free claim instead of promoting it. It defaults to the
738
+ safest `read-only-review` profile and explicitly distinguishes three authority
739
+ tiers — read-only review (no shell, no network), `inspect-with-shell`
740
+ (command-scoped read-only bash), and network-authorized research (opt-in
741
+ `network`/`mcp`) — so you launch on the weakest tier the question actually needs
742
+ and never over-claim beyond what the lanes verified.
743
+
744
+ ## Roadmap
745
+
746
+ The autonomous harness plan lives at `docs/workflow-autonomous-harness-plan.md`.