@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.
- package/CHANGELOG.md +14 -0
- package/CODE_OF_CONDUCT.md +134 -0
- package/CONTRIBUTING.md +95 -0
- package/LICENSE +21 -0
- package/README.md +746 -0
- package/SECURITY.md +38 -0
- package/commands/repo-bughunt.md +94 -0
- package/commands/repo-review.md +148 -0
- package/commands/workflow-live-gates-release-check.md +143 -0
- package/docs/workflow-plugin.md +400 -0
- package/opencode-workflows.js +5 -0
- package/package.json +86 -0
- package/skills/opencode-workflow-authoring/SKILL.md +180 -0
- package/skills/repo-review-command-protocol/SKILL.md +56 -0
- package/skills/workflow-model-tiering/SKILL.md +57 -0
- package/skills/workflow-plan-review/SKILL.md +91 -0
- package/workflow-kernel/approval-hashing.js +39 -0
- package/workflow-kernel/async-util.js +33 -0
- package/workflow-kernel/audited-shell-policy.js +200 -0
- package/workflow-kernel/authority-policy.js +670 -0
- package/workflow-kernel/budget-accounting.js +142 -0
- package/workflow-kernel/capability-adapter.js +753 -0
- package/workflow-kernel/child-agent-runner.js +1264 -0
- package/workflow-kernel/constants.js +117 -0
- package/workflow-kernel/diagnostics.js +152 -0
- package/workflow-kernel/drain-runtime.js +421 -0
- package/workflow-kernel/errors.js +181 -0
- package/workflow-kernel/event-journal.js +487 -0
- package/workflow-kernel/extension-registry.js +144 -0
- package/workflow-kernel/free-text-redactor.js +91 -0
- package/workflow-kernel/gate-shapes.js +82 -0
- package/workflow-kernel/git-util.js +45 -0
- package/workflow-kernel/index.js +72 -0
- package/workflow-kernel/integration-mode.js +155 -0
- package/workflow-kernel/lane-effort-policy.js +134 -0
- package/workflow-kernel/lifecycle-control.js +608 -0
- package/workflow-kernel/live-gate-probes.js +916 -0
- package/workflow-kernel/notification-toast-cards.js +393 -0
- package/workflow-kernel/notification-toast-policy.js +179 -0
- package/workflow-kernel/notification-toast-scope.js +100 -0
- package/workflow-kernel/notification-toast.js +287 -0
- package/workflow-kernel/path-policy.js +219 -0
- package/workflow-kernel/result-readback.js +106 -0
- package/workflow-kernel/role-template-loading.js +606 -0
- package/workflow-kernel/run-context.js +139 -0
- package/workflow-kernel/run-observability.js +43 -0
- package/workflow-kernel/run-store-fs.js +231 -0
- package/workflow-kernel/run-store-locks.js +180 -0
- package/workflow-kernel/run-store-projections.js +421 -0
- package/workflow-kernel/run-store-rehydrate.js +68 -0
- package/workflow-kernel/run-store-state.js +147 -0
- package/workflow-kernel/run-store-status-format.js +1154 -0
- package/workflow-kernel/run-store-status.js +107 -0
- package/workflow-kernel/sandbox-executor.js +1131 -0
- package/workflow-kernel/session-access.js +56 -0
- package/workflow-kernel/structured-output.js +143 -0
- package/workflow-kernel/test-fix-drain-adapter.js +119 -0
- package/workflow-kernel/text-json.js +136 -0
- package/workflow-kernel/workflow-plugin.js +3017 -0
- package/workflow-kernel/workflow-source.js +444 -0
- package/workflow-kernel/worktree-adapter.js +256 -0
- package/workflow-kernel/worktree-git.js +147 -0
- package/workflows/repo-bughunt.js +362 -0
- package/workflows/repo-cleanup.js +383 -0
- package/workflows/repo-complexity.js +404 -0
- package/workflows/repo-deps.js +457 -0
- package/workflows/repo-modernize.js +395 -0
- package/workflows/repo-perf.js +394 -0
- package/workflows/repo-review.js +831 -0
- package/workflows/repo-security-audit.js +466 -0
- 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`.
|