opencode-workflow-guard 1.15.5 → 1.17.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/README.md CHANGED
@@ -4,9 +4,9 @@
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
5
  [![Tests](https://img.shields.io/badge/tests-1100%2B%20passing-brightgreen.svg)](test/test.mts)
6
6
 
7
- Deterministic policy and enforcement layer for OpenCode. Workflow Guard enforces safe engineering discipline through **hard plugin hooks**, not prompt instructions that LLMs can ignore, and pairs enforcement with a proactive workflow-tool surface for planning, review, worktree isolation, technical debt, and policy simulation.
7
+ Deterministic policy and enforcement layer for OpenCode. Workflow Guard enforces engineering discipline through **hard plugin hooks**, not prompt instructions that an LLM can ignore. The plugin also exposes a proactive workflow-tool surface for planning, review, worktree isolation, technical debt, and policy simulation.
8
8
 
9
- Workflow Guard is deliberately **not an agent harness**: it can constrain actions, record and explain decisions, and supply bounded context, but it does not plan, prioritize, delegate, or autonomously sequence the agent's work. Bounded continuation may resume an existing session with unfinished owned todos; it never chooses the next task or creates new work.
9
+ Workflow Guard is deliberately **not an agent harness**. It can constrain actions, record and explain decisions, and supply bounded context. It does not plan, prioritize, delegate, or autonomously sequence the agent's work. Bounded continuation may resume an existing session with unfinished owned todos. It never chooses the next task or creates new work.
10
10
 
11
11
  ---
12
12
 
@@ -22,11 +22,11 @@ opencode plugin add "opencode-workflow-guard@$VERSION" # OpenCode 2.x
22
22
  opencode plugin "opencode-workflow-guard@$VERSION" --global --force # OpenCode 1.x
23
23
  ```
24
24
 
25
- *Requires OpenCode >= 1.18.* OpenCode detects the package's server and TUI targets and updates both global configs. Use the same command after a release to upgrade; the explicit version gives OpenCode a fresh package-cache key. Restart OpenCode after installation. See [docs/installation.md](docs/installation.md) for details and links to the OpenCode plugin documentation.
25
+ *Requires OpenCode >= 1.18.* OpenCode detects the package's server and TUI targets and updates both global configs. Run the same command after a release to upgrade. The explicit version gives OpenCode a fresh package-cache key. Restart OpenCode after installation. See [docs/installation.md](docs/installation.md) for details and links to the OpenCode plugin documentation.
26
26
 
27
- The plugin supports both generations from one package: **OpenCode 1.x** loads the `server()` entrypoint, and **OpenCode 2.x** loads the `setup()` entrypoint (`@opencode/plugin`). Policies, tools, and configuration behave the same on both; V2 load tests run against a real OpenCode 2 binary in `npm run test:install`.
27
+ The plugin supports both generations from one package. **OpenCode 1.x** loads the `server()` entrypoint, and **OpenCode 2.x** loads the `setup()` entrypoint (`@opencode/plugin`). Policies, tools, and configuration behave the same on both. V2 load tests run against a real OpenCode 2 binary in `npm run test:install`.
28
28
 
29
- For the optional TUI badge, configure `"opencode-workflow-guard"` in `tui.json`. OpenCode resolves the package's exported `./tui` entrypoint automatically for TUI plugins. The companion keeps a static `Workflow Guard` shield in the prompt bar; warning toasts provide block details without changing agent behavior. Do not place the TUI module under the server `plugins/` directory.
29
+ For the optional TUI badge, configure `"opencode-workflow-guard"` in `tui.json`. OpenCode resolves the package's exported `./tui` entrypoint automatically for TUI plugins. The companion keeps a static `Workflow Guard` shield in the prompt bar. Warning toasts provide block details without changing agent behavior. Do not place the TUI module under the server `plugins/` directory.
30
30
 
31
31
  ![Workflow Guard status badge in the OpenCode TUI](docs/assets/workflow-guard-tui-badge.png)
32
32
 
@@ -43,12 +43,12 @@ WORKFLOW_GUARD_LIVE_E2E=1 npm run test:install # Also run policy probes with Ope
43
43
 
44
44
  ## Core Guardrails Overview
45
45
 
46
- The plugin enforces **24 deterministic policy rules** across four main pillars:
46
+ The plugin enforces **26 deterministic policy rules** across four main pillars:
47
47
 
48
- * **Branch & History Protection:** Enforces feature-branch workflows (blocks edits/commits on `main`/`master`), hard-blocks direct or forced pushes to protected branches, enforces mergeability pre-flight checks, enforces secondary review approval (bound to the reviewed worktree content) before PR creation by default, and requires PR changelogs / changesets.
49
- * **Task & Verification Discipline:** Gates file mutations on active `todowrite` tasks with subagent inheritance, blocks concurrent direct edits to the same canonical file across sessions, requires same-session fresh reads before `edit`/`write` can replace existing files, blocks silent task deletion, enforces fresh test verification evidence before task completion, and journals completion claims vs. verification mismatches.
50
- * **Secrets & Workspace Confinement:** Confines edit tools and recognized shell/git mutations within the workspace root (including symlink and `../` escape checks), scrubs sensitive environment variables in agent subshells, and redacts `.env` reads with safe schema masks. Arbitrary executables are not OS-sandboxed; use containers or filesystem isolation when hard confinement is required.
51
- * **Destructive CLI & Environment Safety:** Intercepts destructive cloud/database/infrastructure commands, stops script laundering and interpreter evasion, blocks TTY hangs (`vim`, `nano`, `sudo`), and guards against dangerous package manager flags.
48
+ * **Branch & History Protection:** Enforces feature-branch workflows and blocks edits and commits on `main`/`master`. Direct and forced pushes to protected branches are hard-blocked. The guard checks mergeability before PR creation, requires secondary review approval bound to the reviewed worktree content, and requires a PR changelog or changeset.
49
+ * **Task & Verification Discipline:** Gates file mutations on active `todowrite` tasks, which subagents inherit. A task can be marked completed only after its own recorded work. Review and investigation tasks use an audited `[read-only]` marker instead. The guard also blocks concurrent direct edits to the same canonical file across sessions. It requires a same-session fresh read before `edit`/`write` can replace an existing file. Silent task deletion is blocked, and task completion requires fresh test verification evidence. Completion claims and sweeping repository claims are journaled against their evidence. Repeated failures force a live-documentation lookup instead of a guess from training data.
50
+ * **Secrets & Workspace Confinement:** Confines edit tools and recognized shell/git mutations to the workspace root, including symlink and `../` escape checks. Agent subshells have sensitive environment variables scrubbed, and `.env` reads return a safe schema mask. Arbitrary executables are not OS-sandboxed. Use containers or filesystem isolation when hard confinement is required.
51
+ * **Destructive CLI & Environment Safety:** Intercepts destructive cloud, database, and infrastructure commands. Stops script laundering and interpreter evasion, blocks TTY hangs such as `vim`, `nano`, and `sudo`, and guards against dangerous package manager flags.
52
52
 
53
53
  For complete policy specifications and override rules, see [docs/policies.md](docs/policies.md).
54
54
 
@@ -56,17 +56,17 @@ For complete policy specifications and override rules, see [docs/policies.md](do
56
56
 
57
57
  ## Proactive Workflow Tools
58
58
 
59
- Enforcement is only half the surface. The plugin also registers first-class agent tools (same tool names on OpenCode 1.x and 2.x) so disciplined workflow is the path of least resistance:
59
+ Enforcement is only half the surface. The plugin also registers first-class agent tools under the same names on OpenCode 1.x and 2.x, so disciplined workflow is the path of least resistance:
60
60
 
61
- * **Task Planning:** `guard_next_tasks` surfaces durable repository planning context (`TODO.md`, `ROADMAP.md`, `PLAN.md`, `TASKS.md`, `BACKLOG.md`, their `docs/` counterparts, and plans under `docs/plans/`) at session start or during planning; `guard_status` reports active guardrails, branch protection, mutation counts, outstanding verification/review gates, a read-only git-hygiene snapshot, and the loaded `pluginVersion`.
62
- * **Review & Verification:** before PR creation, a secondary reviewer subagent evaluates the diff against `guard_review_rubric` (five core axes: test integrity, task completeness, cleanliness, security, platform) and records its verdict with `record_review`; approvals are bound to the reviewed worktree content.
63
- * **Worktree Isolation:** `guard_worktree_create` and `guard_worktree_cleanup` give parallel subagents isolated linked worktrees so concurrent mutations never collide.
64
- * **Technical Debt:** `guard_review_followups` lists open P2/P3 durable review follow-ups; `guard_review_followup_resolve` closes them after the underlying issue is fixed and verified.
65
- * **Policy Simulation:** `guard_why` returns the structured policy decision for a tool call or command without performing it; `guard_audit` shows recent audited policy entries.
66
- * **Project Memory** (on by default): `project_memory_search`, `project_memory_record`, `project_memory_export`, and `project_memory_import` maintain durable facts, decisions, constraints, and lessons; secret content is rejected, and export promotes records to the repo-local `.opencode/memory/project-memory.jsonl`.
61
+ * **Task Planning:** `guard_next_tasks` surfaces durable repository planning context at session start or during planning. It reads `TODO.md`, `ROADMAP.md`, `PLAN.md`, `TASKS.md`, `BACKLOG.md`, their `docs/` counterparts, and plans under `docs/plans/`. `guard_status` reports active guardrails, branch protection, mutation counts, outstanding verification and review gates, a read-only git-hygiene snapshot, and the loaded `pluginVersion`.
62
+ * **Review & Verification:** before PR creation, a secondary reviewer subagent evaluates the diff against `guard_review_rubric`. The rubric covers five axes: test integrity, task completeness, cleanliness, security, and platform. The reviewer records its verdict with `record_review`, and approvals bind to the reviewed worktree content.
63
+ * **Worktree Isolation:** `guard_worktree_create` and `guard_worktree_cleanup` give parallel subagents isolated linked worktrees, so concurrent mutations never collide.
64
+ * **Technical Debt:** `guard_review_followups` lists open P2/P3 durable review follow-ups. `guard_review_followup_resolve` closes one after the underlying issue is fixed and verified.
65
+ * **Policy Simulation:** `guard_why` returns the structured policy decision for a tool call or command without performing it. `guard_audit` shows recent audited policy entries.
66
+ * **Project Memory** (on by default): `project_memory_search`, `project_memory_record`, `project_memory_export`, and `project_memory_import` maintain durable facts, decisions, constraints, and lessons. Secret content is rejected. Export promotes records to the repo-local `.opencode/memory/project-memory.jsonl`.
67
67
  * **Socratic Learning** (off by default): `learning_checkpoint`, `learning_profile`, and `learning_record` maintain a global evidence-based learner profile with a per-session intervention budget.
68
68
 
69
- The context modules — project memory, Socratic learning, and recovery checkpoints (`guard_recovery_restore`) — are independently gated and can be disabled without weakening policy enforcement; see [docs/installation.md](docs/installation.md). Full tool specifications live in [docs/policies.md](docs/policies.md#custom-tools).
69
+ The context modules are project memory, Socratic learning, and recovery checkpoints (`guard_recovery_restore`). Each is independently gated, so any of them can be disabled without weakening policy enforcement. See [docs/installation.md](docs/installation.md). Full tool specifications live in [docs/policies.md](docs/policies.md#custom-tools).
70
70
 
71
71
  ---
72
72
 
@@ -74,10 +74,11 @@ The context modules — project memory, Socratic learning, and recovery checkpoi
74
74
 
75
75
  | Guide | Description |
76
76
  |---|---|
77
- | [**Policy Reference**](docs/policies.md) | Comprehensive specification of all 24 enforced policies, invariants, and the proactive tool surface (`guard_*`, `project_memory_*`, `learning_*`) |
78
- | [**Installation & Configuration**](docs/installation.md) | Setup options, global vs. local install, worktree isolation, and project configuration |
77
+ | [**Policy Reference**](docs/policies.md) | Specification of all 26 enforced policies, invariants, and the proactive tool surface (`guard_*`, `project_memory_*`, `learning_*`) |
78
+ | [**Installation & Configuration**](docs/installation.md) | Setup options, global and local install, worktree isolation, and project configuration |
79
+ | [**Static Policy Posture**](docs/policy-posture.md) | How the guard emits host-level policies for invariants a repository cannot lift. Covers both host envelopes (`experimental.policies` on 2.0.x, top-level `policies` on 2.1.x), precedence, and residual risk |
79
80
  | [**Managed Deployment**](docs/managed-deployment.md) | Administrator-managed OpenCode policy, platform locations, and startup diagnostics |
80
- | [**Troubleshooting**](docs/troubleshooting.md) | Diagnosing policy blocks, common false positives, and emergency override procedures |
81
+ | [**Troubleshooting**](docs/troubleshooting.md) | Diagnosing policy blocks, common false positives, and the live-system override |
81
82
  | [**Testing Architecture**](docs/testing.md) | Test harness design, adversarial regression matrix, and CI verification |
82
83
  | [**Contributing Guide**](CONTRIBUTING.md) | Development workflow, coding conventions, test requirements, and changeset PR rules |
83
84
  | [**Security Policy**](SECURITY.md) | Vulnerability disclosure policy and threat model |
@@ -2,15 +2,23 @@
2
2
 
3
3
  import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
4
4
  import { homedir } from "node:os";
5
- import { dirname, join } from "node:path";
6
- import { parse, modify, applyEdits } from "jsonc-parser";
5
+ import { join } from "node:path";
6
+ import { preparePluginUpdate, PACKAGE_NAME } from "../src/lib/config-writer.mjs";
7
7
 
8
- const PACKAGE_NAME = "opencode-workflow-guard";
9
8
  const PACKAGE_VERSION = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version;
10
9
  const SERVER_SPEC = `${PACKAGE_NAME}@${PACKAGE_VERSION}`;
11
10
  const TUI_SPEC = SERVER_SPEC;
12
11
  const LEGACY_TUI_SPEC = "opencode-workflow-guard/tui";
13
12
 
13
+ const USAGE = `Usage:
14
+ opencode-workflow-guard setup [--major 1|2]
15
+ opencode-workflow-guard policies --print [--format both|2.0|next]
16
+
17
+ setup writes \`plugins\` for OpenCode 2 (the default) and \`plugin\` for OpenCode 1.
18
+ An existing config's key wins unless --major is passed.
19
+ \`policies\` defaults to both, which the 2.0.x and 2.1.x hosts each read under
20
+ their own key. Pass --format to pin one host.`;
21
+
14
22
  function configHome() {
15
23
  return process.env.XDG_CONFIG_HOME || join(homedir(), ".config");
16
24
  }
@@ -27,49 +35,62 @@ function restoreConfig(path, previous) {
27
35
  else writeFileSync(path, previous);
28
36
  }
29
37
 
30
- function matchesPackageSpec(entry, spec) {
31
- const value = Array.isArray(entry) ? entry[0] : entry;
32
- return typeof value === "string" && (value === spec || value.startsWith(`${spec}@`));
33
- }
34
- function preparePluginUpdate(path, schema, spec, legacySpec) {
35
- const source = existsSync(path) ? readFileSync(path, "utf8") : `{
36
- "$schema": "${schema}"
37
- }\n`;
38
- const errors = [];
39
- const config = parse(source, errors, { allowTrailingComma: true, disallowComments: false });
40
- if (errors.length > 0 || !config || typeof config !== "object" || Array.isArray(config)) {
41
- throw new Error(`Cannot update invalid config: ${path}`);
38
+ /**
39
+ * Static policy posture. Prints to stdout only.
40
+ *
41
+ * A plugin that writes another tool's configuration has moved the static rails
42
+ * back inside the thing they are meant to outlive. So: emit, never install.
43
+ */
44
+ async function runPolicies() {
45
+ const args = process.argv.slice(3);
46
+ const formats = ["both", "2.0", "next"];
47
+ let print = false;
48
+ let format = "both";
49
+ for (let i = 0; i < args.length; i++) {
50
+ const arg = args[i];
51
+ if (arg === "--print") print = true;
52
+ else if (arg === "--format") {
53
+ format = args[++i];
54
+ if (!formats.includes(format)) throw new Error(`Unknown --format '${format}'. Use both, 2.0 or next.`);
55
+ } else if (arg.startsWith("--format=")) {
56
+ format = arg.slice("--format=".length);
57
+ if (!formats.includes(format)) throw new Error(`Unknown --format '${format}'. Use both, 2.0 or next.`);
58
+ } else throw new Error(`Unknown argument '${arg}'.`);
42
59
  }
43
- if (config.plugin !== undefined && !Array.isArray(config.plugin)) {
44
- throw new Error(`Cannot update ${path}: "plugin" must be an array`);
45
- }
46
- const plugins = config.plugin ?? [];
47
- const hasSpec = plugins.some((entry) => matchesPackageSpec(entry, PACKAGE_NAME));
48
- const updatedPlugins = legacySpec
49
- ? plugins.filter((entry) => !matchesPackageSpec(entry, legacySpec))
50
- : plugins;
51
- if (updatedPlugins.length !== plugins.length) {
52
- if (!hasSpec) updatedPlugins.push(spec);
53
- return applyEdits(source, modify(source, ["plugin"], updatedPlugins, {
54
- formattingOptions: { insertSpaces: true, tabSize: 2 },
55
- }));
56
- }
57
- if (hasSpec) return undefined;
58
- return applyEdits(source, modify(source, ["plugin", plugins.length], spec, {
59
- formattingOptions: { insertSpaces: true, tabSize: 2 },
60
- }));
60
+ if (!print) throw new Error("Refusing to write config. Pass --print and place the block yourself.");
61
+ const { renderPolicyPosture } = await import(new URL("../src/lib/policy-posture.ts", import.meta.url).href);
62
+ process.stdout.write(renderPolicyPosture({ format }));
61
63
  }
62
64
 
63
- if (process.argv[2] !== "setup" || process.argv.length > 3) {
64
- console.error("Usage: opencode-workflow-guard setup");
65
+ if (process.argv[2] === "policies") {
66
+ try {
67
+ await runPolicies();
68
+ } catch (error) {
69
+ console.error(error instanceof Error ? error.message : String(error));
70
+ console.error(USAGE);
71
+ process.exitCode = 1;
72
+ }
73
+ } else if (process.argv[2] !== "setup") {
74
+ console.error(USAGE);
65
75
  process.exitCode = 1;
66
76
  } else {
67
77
  try {
78
+ const args = process.argv.slice(3);
79
+ let explicitMajor;
80
+ for (let i = 0; i < args.length; i++) {
81
+ const arg = args[i];
82
+ const raw = arg === "--major" ? args[++i] : arg.startsWith("--major=") ? arg.slice("--major=".length) : undefined;
83
+ if (raw === undefined) throw new Error(`Unknown argument '${arg}'.`);
84
+ if (raw !== "1" && raw !== "2") throw new Error(`Unknown --major '${raw}'. Use 1 or 2.`);
85
+ explicitMajor = Number(raw);
86
+ }
68
87
  const base = join(configHome(), "opencode");
69
88
  const serverPath = existingConfig(base, "opencode");
70
89
  const tuiPath = existingConfig(base, "tui");
71
- const serverUpdate = preparePluginUpdate(serverPath, "https://opencode.ai/config.json", SERVER_SPEC);
72
- const tuiUpdate = preparePluginUpdate(tuiPath, "https://opencode.ai/tui.json", TUI_SPEC, LEGACY_TUI_SPEC);
90
+ const serverUpdate = preparePluginUpdate(serverPath, "https://opencode.ai/config.json", SERVER_SPEC, undefined, explicitMajor);
91
+ // `tui.json` keeps the singular `plugin` key on both majors: OpenCode 2
92
+ // reads that key and migrates it into `cli.json.plugins`.
93
+ const tuiUpdate = preparePluginUpdate(tuiPath, "https://opencode.ai/tui.json", TUI_SPEC, LEGACY_TUI_SPEC, explicitMajor, "plugin");
73
94
  const serverBefore = existsSync(serverPath) ? readFileSync(serverPath, "utf8") : undefined;
74
95
  const tuiBefore = existsSync(tuiPath) ? readFileSync(tuiPath, "utf8") : undefined;
75
96
  mkdirSync(base, { recursive: true });
@@ -20,18 +20,18 @@ Policy decisions and operational evidence are separate concepts.
20
20
 
21
21
  `PolicyDecision` is the result of evaluating whether an operation may proceed. It should provide a stable machine-readable status and code, an optional policy identifier, a human-readable message, and bounded structured details. Human block text, audit entries, `guard_why`, and controller-facing output should derive from this result rather than independently interpreting policy state.
22
22
 
23
- `EvidenceRecord` represents operational proof or observation such as verification, secondary review, tool outcome, policy evaluation, or lifecycle state. Evidence records should carry common project/session/worktree provenance where applicable. Existing purpose-built stores remain authoritative; this work should introduce a common representation rather than another database.
23
+ `EvidenceRecord` represents operational proof or observation. Examples are verification, secondary review, tool outcome, policy evaluation, and lifecycle state. Evidence records should carry common project, session, and worktree provenance where applicable. Existing purpose-built stores remain authoritative. This work should introduce a common representation rather than another database.
24
24
 
25
25
  Evidence must be bound to the subject it actually establishes. Where applicable, that means identifying the workspace/worktree, commit or worktree fingerprint, session/call, actor or reviewer, policy/version, and observation time. A historical fact such as "verification passed" is not sufficient to establish that the current worktree is verified unless its subject still matches current state.
26
26
 
27
27
  Operational evidence should distinguish confidence classes rather than treating all machine-readable claims as equivalent:
28
28
 
29
29
  - Deterministic observation: repository state, changed files, tool execution/outcome, test exit status, or another directly observed fact.
30
- - Attestation: a reviewer, model, or human records a judgment such as approval. The fact that the attestation occurred is observable; the judgment is not converted into deterministic truth.
30
+ - Attestation: a reviewer, model, or human records a judgment such as approval. The fact that the attestation occurred is observable. The judgment is not converted into deterministic truth.
31
31
  - Derived state: a reproducible conclusion such as "verification is fresh" computed from subject-bound observations and current state.
32
32
  - Agent assertion: natural-language claims such as "tests pass" or "review is complete". These may be useful diagnostics but are never sufficient evidence for a safety or finalization gate by themselves.
33
33
 
34
- Prefer outcome and state predicates over prescribed trajectories. Workflow Guard may require demonstrable end state or consequential checkpoints, but should not require one exact sequence of otherwise-valid agent actions when the desired state can be established directly.
34
+ Prefer outcome and state predicates over prescribed trajectories. Workflow Guard may require demonstrable end state or consequential checkpoints. It should not require one exact sequence of otherwise-valid agent actions when the desired state can be established directly.
35
35
 
36
36
  Project memory and learning evidence remain separate from `EvidenceRecord`. Project memory represents durable repository knowledge. Learning evidence represents observations from real Socratic interactions. Neither should be forced into an allow/block or operational-proof model.
37
37
 
@@ -46,7 +46,7 @@ Project memory and learning evidence remain separate from `EvidenceRecord`. Proj
46
46
  7. Expand adversarial tests for stable decision codes, provenance invalidation, option/profile precedence, and machine-readable status. Add headless/server coverage proving core enforcement does not depend on the TUI or interactive approval.
47
47
  8. Update policy, installation, and managed-deployment documentation as behavior becomes user-visible.
48
48
 
49
- The accountability contracts now support an opt-in bounded autonomous-continuation add-on ("Ralph mode"). Its initial implementation only continues an agent while explicitly owned native todos remain unfinished; it does not interpret configured documents or invent completion criteria. It is disabled by default (including under the `autonomous` profile), user-interruptible, iteration-bounded, and preserves all core guardrails. It reports observable outcomes such as `completed`, `budget_exhausted`, or `user_stopped`; `blocked` is reserved for a demonstrable guard state rather than inferred from repeated idle events. Ralph remains an optional orchestration consumer rather than part of the accountability kernel.
49
+ The accountability contracts now support an opt-in bounded autonomous-continuation add-on ("Ralph mode"). Its initial implementation only continues an agent while explicitly owned native todos remain unfinished. It does not interpret configured documents or invent completion criteria. It is disabled by default, including under the `autonomous` profile. It is user-interruptible, iteration-bounded, and preserves all core guardrails. It reports observable outcomes such as `completed`, `budget_exhausted`, and `user_stopped`. `blocked` is reserved for a demonstrable guard state rather than inferred from repeated idle events. Ralph remains an optional orchestration consumer rather than part of the accountability kernel.
50
50
 
51
51
  ## Configuration Boundary
52
52
 
@@ -54,13 +54,13 @@ Safety invariants stay core and are not weakened by operation profiles or option
54
54
 
55
55
  Capabilities that express user workflow preference, add continuity/context, or approach orchestration must be configurable. Examples include project memory, Socratic learning, recovery checkpoints, review/documentation requirements, bounded continuation behavior, and future autonomous-operation conveniences. Optional capabilities must fail independently where practical so disabling or failing an add-on does not disable core enforcement.
56
56
 
57
- Workflow Guard must not become responsible for planning work, choosing the next task, delegating tasks, autonomous retry loops, sequencing agents, or deciding when an external job should terminate. External SDK/server clients or dedicated add-ons own orchestration. The existing bounded `session.idle` continuation may enforce already-owned unfinished work but must not grow into an orchestration loop.
57
+ Workflow Guard must not become responsible for planning work, choosing the next task, or delegating tasks. It also must not own autonomous retry loops, agent sequencing, or the decision that an external job should terminate. Those are orchestration duties. External SDK or server clients and dedicated add-ons own them. The bounded `session.idle` continuation may enforce already-owned unfinished work. It must never grow into an orchestration loop.
58
58
 
59
59
  The same boundary applies to review. Workflow Guard can prove that a review occurred against a particular subject, preserve the reviewer's disposition and findings, and enforce a configured review requirement. It must not infer that an approval proves correctness, choose reviewers, launch review loops, negotiate findings, or decide what work should happen next.
60
60
 
61
61
  ## External Research Rationale
62
62
 
63
- Recent OpenAI and Anthropic engineering/research material independently supports this architecture. These sources are design inputs rather than normative dependencies; Workflow Guard should continue to rely on supported OpenCode APIs and its own tested contracts.
63
+ Recent OpenAI and Anthropic engineering and research material independently supports this architecture. These sources are design inputs rather than normative dependencies. Workflow Guard should continue to rely on supported OpenCode APIs and its own tested contracts.
64
64
 
65
65
  - OpenAI's February 2026 [Harness engineering: leveraging Codex in an agent-first world](https://openai.com/index/harness-engineering/) describes mechanically enforced architectural invariants around agent autonomy. The applicable inference for Workflow Guard is to enforce boundaries centrally while leaving implementation strategy and task execution to the agent.
66
66
  - OpenAI's March 2025 [Detecting misbehavior in frontier reasoning models](https://openai.com/index/chain-of-thought-monitoring/) reports coding agents exploiting evaluation loopholes, including weakening or altering verification. The applicable inference is to retain independent provenance about changed artifacts and verification subjects rather than trusting reported success or an undifferentiated test-pass signal.
@@ -71,11 +71,11 @@ Recent OpenAI and Anthropic engineering/research material independently supports
71
71
  - Anthropic's September 2025 [Effective context engineering for AI agents](https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents) recommends the smallest high-signal context and just-in-time retrieval. The applicable inference is that `guard_status` should expose compact current state and stable evidence references rather than inject an ever-growing accountability transcript.
72
72
  - Anthropic's December 2024 [Building effective agents](https://www.anthropic.com/engineering/building-effective-agents) separates deterministic workflows from model-directed agents, recommends simple composable mechanisms, and uses programmatic gates where checks are appropriate. The applicable inference is to keep Workflow Guard a guardrail/accountability layer, not an orchestrator-worker or evaluator-optimizer implementation.
73
73
 
74
- The combined design principle is: Workflow Guard should answer, "May this operation happen, given what can actually be demonstrated about the current state?" It should not answer, "What should the agent do next?"
74
+ The combined design principle is this. Workflow Guard answers one question: "May this operation happen, given what can actually be demonstrated?" It does not answer, "What should the agent do next?"
75
75
 
76
76
  ## OpenCode API Boundary
77
77
 
78
- Use supported semantic OpenCode APIs wherever they provide enough information. `tool.execute.before` is the supported deterministic tool-blocking boundary. Current `session.idle` events and `experimental.text.complete` do not expose an explicit supported completion-veto result, so Workflow Guard must not emulate a hard completion gate through undocumented exception behavior.
78
+ Use supported semantic OpenCode APIs wherever they provide enough information. `tool.execute.before` is the supported deterministic tool-blocking boundary. Current `session.idle` events and `experimental.text.complete` expose no explicit supported completion-veto result. Workflow Guard must therefore not emulate a hard completion gate through undocumented exception behavior.
79
79
 
80
80
  Machine-readable controller outcomes should therefore come from Workflow Guard's own structured contracts, tools, and audit trail until OpenCode exposes a stronger lifecycle decision hook.
81
81
 
@@ -84,7 +84,7 @@ Shell matching remains defense in depth rather than a sandbox. Prefer structured
84
84
  ## Compatibility And Non-Goals
85
85
 
86
86
  - Preserve existing human-readable block messages at the hook boundary while internal consumers migrate to structured decisions.
87
- - Preserve existing verification history, review state, project-memory databases, learning profiles, and review-follow-up storage; migrate representations before considering storage changes.
87
+ - Preserve existing verification history, review state, project-memory databases, learning profiles, and review-follow-up storage. Migrate representations before considering storage changes.
88
88
  - Do not merge project memory, learner evidence, and operational evidence into a universal event store.
89
89
  - Do not claim managed deployment proves plugin provenance when OpenCode cannot provide that guarantee.
90
90
  - Do not add a hard completion gate until OpenCode exposes a supported block/continue lifecycle hook.
@@ -6,9 +6,52 @@
6
6
 
7
7
  ## Requirements
8
8
 
9
- - **OpenCode >= 1.18** (V1: requires the `GET /session/:id/todo` endpoint and the V1 `PluginModule` loader) or **OpenCode 2.x** (V2: loads through the `@opencode/plugin` API; the current todo list is reconstructed from the session message history because V2 has no todo endpoint).
9
+ - **OpenCode >= 1.18** (V1: requires the `GET /session/:id/todo` endpoint and the V1 `PluginModule` loader) or **OpenCode 2.x**. V2 loads through the `@opencode/plugin` API. The current todo list is reconstructed from session message history, or read from the native `todowrite` on 2.1.x when `experimental.todo` is enabled.
10
10
  - **Node.js >= 22.18** or **Bun** (for running test scripts directly).
11
11
 
12
+ ### Version Qualification
13
+
14
+ The guard declares exact host dependency versions, so an unreviewed host API can
15
+ never float in. A host release is qualified only when it appears in this table
16
+ with the seats that were actually exercised against it. An unlisted version is
17
+ unqualified by definition. That does not mean it is broken. It means nobody has
18
+ verified it.
19
+
20
+ | Guard | OpenCode qualified | Hook seats exercised | Notes |
21
+ |---|---|---|---|
22
+ | 1.16.x | 2.0.15 | `permission.hook("evaluate")`, `tool.hook("execute.before"/"execute.after")`, `session.hook("context")`, `experimental.text.complete`, command registration, `mcp.list`, `worktree` client | Unit and package-loader verified. Live model-driven probes reached the provider and were classified `live unavailable` (`Model unavailable: openrouter/openrouter/auto`), so no guard verdict was produced. |
23
+ | 1.16.x | 2.1.3 | `permission.hook("evaluate")`, `tool.hook(...)`, `session.hook(...)`, `experimental.text.complete`, command registration, `mcp.list`, `worktree` client, `todowrite` behind `experimental.todo` | Package-loader and headless-runtime verified (`npm run test:install`: 30 passed). Live host probe confirmed `ctx.tool.list()` exposes `todowrite` with `experimental.todo: true` and omits it when false. Live static-policy denial canary produced `Blocked by configuration policy`. |
24
+ | 1.16.x | 1.18.32 (`@opencode-ai/plugin`) | V1 `PluginModule` loader, native todo endpoint | Compatibility export only; unit-tested, no live host probe. |
25
+
26
+ The 2.1.3 pins require the 2.1.x package set. `@opencode/plugin` and
27
+ `@opencode/ai` are pinned to `2.1.3`, which depends on `effect@4.0.1` and
28
+ requires `@opentui/solid >=0.6.6`. The guard pins `0.6.6`. A 2.0.x pin set on a
29
+ 2.1.x host fails to load at all. The plugin imports an `effect` path the host no
30
+ longer provides, and the host reports `failed to load plugin` while every
31
+ policy stays inert.
32
+
33
+ `guard_status` reports `pluginVersion`, `qualifiedHostVersion`, and the live
34
+ `hostVersion`, so a mismatch is visible at runtime rather than surfacing as a
35
+ silently inert hook.
36
+
37
+ ### Dependency Audit Overrides
38
+
39
+ Two transitive dependencies are pinned above the range their parent declares, to
40
+ clear advisories that would otherwise ship:
41
+
42
+ | Package | Parent constraint | Override | Why |
43
+ |---|---|---|---|
44
+ | `solid-js` | `@opentui/solid@0.6.6` peer-pins exactly `1.9.12` | `1.9.17` | `1.9.12` requires `seroval ~1.5.0`, which carries memory-exhaustion and thenable-assimilation advisories. `1.9.17` requires `seroval ~1.6.8`, which is patched. The TUI imports `solid-js` directly, so the bump is also the version the guard actually runs. |
45
+ | `toml` | `effect@4.0.0-beta.83` (via `@opencode-ai/plugin`) requires `^4.1.1` | `5.0.1` | `toml <=5.0.0` carries three quadratic-complexity denial-of-service advisories. `5.0.1` keeps the same single `parse` export the caller uses. The guard itself never parses TOML; this is host plumbing. |
46
+
47
+ Both are `overrides` entries in `package.json`, and `npm audit` reports zero
48
+ vulnerabilities for this repository. `npm overrides` apply only to the root
49
+ project, so they protect this repository's install and its CI, not a consumer's
50
+ transitive tree. A consumer that installs the published package resolves the
51
+ parent-declared ranges unless it declares the same overrides itself. Revisit both
52
+ when `@opentui/solid` or `@opencode-ai/plugin` widen their ranges, and drop the
53
+ override rather than carrying it forward.
54
+
12
55
  ---
13
56
 
14
57
  ## Installation Methods
@@ -23,11 +66,11 @@ opencode plugin add "opencode-workflow-guard@$VERSION" # Open
23
66
  opencode plugin "opencode-workflow-guard@$VERSION" --global --force # OpenCode 1.x
24
67
  ```
25
68
 
26
- OpenCode 2 documents `opencode plugin add <package>` as the command to install a plugin and add it to the global configuration; OpenCode 1 documents `opencode plugin <module>` with `--global` and `--force`. Workflow Guard exposes both server and TUI targets, so OpenCode detects both and updates their global configuration files (OpenCode 2 uses one global `cli.json` for terminal-client settings). Restart OpenCode after installation because configuration is loaded at startup. See the [OpenCode plugin CLI documentation](https://opencode.ai/docs/cli/#plugin).
69
+ OpenCode 2 documents `opencode plugin add <package>` as the command to install a plugin and add it to the global configuration. OpenCode 1 documents `opencode plugin <module>` with `--global` and `--force`. Workflow Guard exposes both server and TUI targets, so OpenCode detects both and updates their global configuration files. OpenCode 2 uses one global `cli.json` for terminal-client settings. Restart OpenCode after installation, because configuration is loaded at startup. See the [OpenCode plugin CLI documentation](https://opencode.ai/docs/cli/#plugin).
27
70
 
28
- Use the same two commands to upgrade after a Workflow Guard release. Keep the explicit version in OpenCode's configuration rather than relying on a bare package name or `@latest`: OpenCode caches npm plugins, and an already-populated `@latest` cache key may continue resolving to the version that originally populated it. An explicit new version creates a new package-cache key without manually deleting OpenCode's cache.
71
+ Use the same two commands to upgrade after a Workflow Guard release. Keep the explicit version in OpenCode's configuration rather than relying on a bare package name or `@latest`. OpenCode caches npm plugins, and an already-populated `@latest` cache key may keep resolving to the version that originally populated it. An explicit new version creates a new package-cache key without manually deleting OpenCode's cache.
29
72
 
30
- If you only want the server guard without the TUI companion, add the package to your project's `opencode.json` (or global `~/.config/opencode/opencode.json`). OpenCode 2 uses the plural `plugins` key; OpenCode 1.x uses the singular `plugin` key (V2 normalizes the singular form in memory, but the native shape is `plugins`):
73
+ If you only want the server guard without the TUI companion, add the package to your project's `opencode.json`, or to the global `~/.config/opencode/opencode.json`. OpenCode 2 reads only the plural `plugins` key. OpenCode 1.x uses the singular `plugin` key. The OpenCode 2 config schema drops a singular key without an error. The guard then appears configured and never loads:
31
74
 
32
75
  ```json
33
76
  {
@@ -40,11 +83,43 @@ If you only want the server guard without the TUI companion, add the package to
40
83
 
41
84
  OpenCode automatically installs configured npm plugins and their dependencies at startup. The npm package contains both server and TUI entrypoints, but OpenCode activates them from separate configuration files. See [OpenCode's npm plugin documentation](https://opencode.ai/docs/plugins/#from-npm) and [plugin installation behavior](https://opencode.ai/docs/plugins/#how-plugins-are-installed).
42
85
 
43
- A configured bare package name is resolved through Node's parent-directory lookup from OpenCode's configuration location: any `node_modules` above that location that contains a copy of the package (a pnpm-managed home directory, a manual `npm install`, or a workspace dependency) is loaded instead, shadowing both OpenCode's own plugin cache and a global `npm install -g` — updating the wrong copy leaves an old version running while the update appears to succeed, and OpenCode's own plugin-update check can fail with only a server-log record (`NpmInstallFailedError`). Update the copy that actually loads (for a pnpm-managed home directory: `pnpm add opencode-workflow-guard@<version>` in that directory, or remove the dependency so OpenCode manages its own copy), then restart the background service (`opencode service restart`) — the plugin loads there, so a TUI-only restart is not enough. Verify what loaded: `guard_status` reports the loaded version as `pluginVersion`, the startup app log states `Workflow Guard v<version> plugin initialized`, and the server log records the resolved module path in its `loading plugin` entry.
86
+ ### 2. Self-Configuring `setup` Command
87
+
88
+ The package ships a `setup` command that writes the plugin entry into the global
89
+ configuration for you:
90
+
91
+ ```bash
92
+ opencode-workflow-guard setup # OpenCode 2: writes `plugins`
93
+ opencode-workflow-guard setup --major 1 # OpenCode 1: writes `plugin`
94
+ ```
95
+
96
+ The command detects the target from the existing config: a `plugins` key means
97
+ OpenCode 2, a `plugin` key means OpenCode 1, and a fresh config defaults to
98
+ OpenCode 2. `--major` overrides the detection. The two keys are not
99
+ interchangeable: OpenCode 2's config schema drops a singular `plugin` key
100
+ without an error, so writing the wrong one produces a config that looks
101
+ configured and loads nothing. The writer refuses to clobber an unparseable
102
+ config or a non-array plugin key.
103
+
104
+ `tui.json` is the exception to the key rule. Its schema reads singular `plugin`
105
+ on both majors, and OpenCode 2 migrates that key into `cli.json.plugins`. So
106
+ `--major 2` writes `plugins` to the server config and still writes `plugin` to
107
+ `tui.json`. A plural key there is silently ignored, and the TUI badge never
108
+ loads.
109
+
110
+ ### Configure The Guard In One Location Only
111
+
112
+ Name the guard in exactly one place per OpenCode process: either the global configuration or a project configuration, never both. The global configuration is `~/.config/opencode/opencode.json[c]`, with `plugins` on 2.x or `plugin` on 1.x. OpenCode 2 merges plugin arrays instead of replacing them. Listing `opencode-workflow-guard` globally and again in a project's `.opencode/opencode.json` therefore loads the same entrypoint twice under two ids. Two instances register duplicate hooks and duplicate tool definitions. That evaluates and audits every tool call twice. One instance can overwrite the other's tool-description enrichment, and both prime the process-global workspace latch from two roots in the same tick.
113
+
114
+ The guard detects the duplicate instead of failing silently. The first instance for a given (module path, workspace root) pair owns enforcement, and a later instance registers no hooks or tools. The duplicate is recorded at warn level in the server log and as an audit record (`tool: "plugin.instance"`, `reason: "duplicate_plugin_load"`) in `workflow-guard.jsonl`. A server log with two `loading plugin.*workflow-guard` lines on a fresh start is therefore a configuration error, not a supported setup. As a second layer, the first instance to reach a given (session, call, tool) claims its policy decision. A guard loaded from two distinct paths, such as an npm cache copy plus a source checkout, still produces one decision per call.
115
+
116
+ A repository that ships its own `.opencode/opencode.json` with a `plugin`/`plugins` entry is the usual source of the second copy. Remove the entry there and let the global configuration carry the guard. Because OpenCode settings are tamper-protected from agent edits, that file must be changed by the operator.
117
+
118
+ A configured bare package name is resolved through Node's parent-directory lookup from OpenCode's configuration location. Any `node_modules` above that location that contains a copy of the package is loaded instead. That includes a pnpm-managed home directory, a manual `npm install`, and a workspace dependency. This shadows both OpenCode's own plugin cache and a global `npm install -g`. Updating the wrong copy leaves an old version running while the update appears to succeed. OpenCode's own plugin-update check can also fail with only a server-log record (`NpmInstallFailedError`). Update the copy that actually loads. For a pnpm-managed home directory, run `pnpm add opencode-workflow-guard@<version>` in that directory, or remove the dependency so OpenCode manages its own copy. Then restart the background service with `opencode service restart`. The plugin loads there, so a TUI-only restart is not enough. Verify what loaded in three places. `guard_status` reports the loaded version as `pluginVersion`. The startup app log states `Workflow Guard v<version> plugin initialized`. The server log records the resolved module path in its `loading plugin` entry.
44
119
 
45
120
  ---
46
121
 
47
- ### 2. Manual Local-File Installation (Server Plugin)
122
+ ### 3. Manual Local-File Installation (Server Plugin)
48
123
 
49
124
  The npm package entrypoint uses OpenCode's V1 plugin module shape (`{ id, server }`). For a source checkout, keep the modular server source outside the auto-loaded `plugins/` directory and add a small adapter with the same module shape:
50
125
 
@@ -55,15 +130,15 @@ cp -r src/lib src/policies .opencode/workflow-guard-source/
55
130
  printf '%s\n' 'import { WorkflowGuard } from "../workflow-guard-source/workflow-guard.ts";' 'export default { id: "workflow-guard-local", server: WorkflowGuard };' > .opencode/plugins/workflow-guard.ts
56
131
  ```
57
132
 
58
- OpenCode automatically discovers the adapter from the project-level `.opencode/plugins/` directory at startup. For deterministic development and test fixtures, it can also be registered explicitly in `.opencode/opencode.json` as `"./plugins/workflow-guard.ts"`; OpenCode deduplicates the resulting file URL. npm installation is recommended for normal use; the local-file form is primarily useful when developing from a source checkout.
133
+ OpenCode automatically discovers the adapter from the project-level `.opencode/plugins/` directory at startup. For deterministic development and test fixtures, it can also be registered explicitly in `.opencode/opencode.json` as `"./plugins/workflow-guard.ts"`. OpenCode deduplicates the resulting file URL. npm installation is recommended for normal use. The local-file form is primarily useful when developing from a source checkout.
59
134
 
60
135
  ---
61
136
 
62
- ### 3. TUI Visual Indicator (Prompt Box Badge)
137
+ ### 4. TUI Visual Indicator (Prompt Box Badge)
63
138
 
64
139
  To enable the static `Workflow Guard 🛡️` prompt-bar badge, with block details reported separately through warning toasts:
65
140
 
66
- The recommended `opencode plugin add "opencode-workflow-guard@$VERSION"` command configures this automatically: OpenCode detects both targets and registers the server plugin in the global `opencode.json(c)` and the TUI companion in the global `~/.config/opencode/cli.json` (OpenCode 2). For manual installation, use the same explicit package version in the TUI target of your OpenCode generation - global `~/.config/opencode/cli.json` with the `plugins` array on OpenCode 2:
141
+ The recommended `opencode plugin add "opencode-workflow-guard@$VERSION"` command configures this automatically. OpenCode detects both targets and registers the server plugin in the global `opencode.json(c)`. On OpenCode 2 it also registers the TUI companion in the global `~/.config/opencode/cli.json`. For manual installation, use the same explicit package version in the TUI target of your OpenCode generation. On OpenCode 2 that target is the global `~/.config/opencode/cli.json` with the `plugins` array:
67
142
 
68
143
  ```json
69
144
  {
@@ -85,7 +160,7 @@ or `~/.config/opencode/tui.json` with the `plugin` array on OpenCode 1.x:
85
160
  }
86
161
  ```
87
162
 
88
- The version above is illustrative; replace it with the version you intend to install. The package exposes separate server and TUI entrypoints. In the terminal-client config, configure the package spec; OpenCode resolves its exported `./tui` entrypoint automatically. Do not place `workflow-guard-ui.ts` under `plugins/`; the server loader rejects TUI-only modules.
163
+ The version above is illustrative. Replace it with the version you intend to install. The package exposes separate server and TUI entrypoints. In the terminal-client config, configure the package spec. OpenCode resolves its exported `./tui` entrypoint automatically. Do not place `workflow-guard-ui.ts` under `plugins/`, because the server loader rejects TUI-only modules.
89
164
 
90
165
  On OpenCode 2, `plugin check` and `plugin update` cover both server and TUI-only package plugins.
91
166
 
@@ -93,7 +168,7 @@ The TUI badge uses OpenCode's Solid slot API (`@opentui/solid`). It is a runtime
93
168
 
94
169
  ## Recovery Checkpoints
95
170
 
96
- Workflow Guard supports two optional operation profiles in `.opencode/workflow-guard.json` (JSONC is also supported): `interactive` is the default and preserves the standard behavior, while `autonomous` enables durable recovery checkpoints by default for longer-running agent work. Profiles only supply defaults for optional workflow behavior; they never weaken secret protection, protected-path protection, workspace confinement, protected-branch safeguards, or other deterministic policy checks.
171
+ Workflow Guard supports two optional operation profiles in `.opencode/workflow-guard.json`. JSONC is also supported. `interactive` is the default and preserves the standard behavior. `autonomous` enables durable recovery checkpoints by default for longer-running agent work. Profiles only supply defaults for optional workflow behavior. They never weaken secret protection, protected-path protection, workspace confinement, protected-branch safeguards, or other deterministic policy checks.
97
172
 
98
173
  ```json
99
174
  {
@@ -113,15 +188,15 @@ Durable recovery checkpoints can also be enabled directly per project:
113
188
 
114
189
  For each genuine user run in a root session, Workflow Guard captures the pre-run tracked and untracked workspace state in private Git objects. The objects are kept reachable under `refs/workflow-guard/checkpoints/` and do not add entries to the user's stash list. Subagent runs and Workflow Guard's synthetic continuation messages do not create or replace checkpoints.
115
190
 
116
- When the root session reaches idle, the checkpoint records the resulting workspace fingerprint. The `guard_recovery_restore` tool can then restore a selected run, but only for that same root session and only while the workspace still exactly matches the recorded idle boundary. Any intervening workspace change makes recovery refuse rather than overwrite newer work. Recovery checkpoints require an existing Git commit and deliberately fail open if a snapshot cannot be created.
191
+ When the root session reaches idle, the checkpoint records the resulting workspace fingerprint. The `guard_recovery_restore` tool can then restore a selected run. It works only for that same root session, and only while the workspace still exactly matches the recorded idle boundary. Any intervening workspace change makes recovery refuse rather than overwrite newer work. Recovery checkpoints require an existing Git commit, and they fail open if a snapshot cannot be created.
117
192
 
118
- With the optional TUI companion installed, open the command palette and choose **Workflow Guard: Project Options** (or run `/guard-options`) to toggle recovery checkpoints, project memory, learner mode, Ralph mode, or the title-settle workaround for the current project. The command writes the existing Workflow Guard project config location, defaulting to `.opencode/workflow-guard.json`; restart OpenCode after changing a setting so the server plugin can expose the matching tools.
193
+ With the optional TUI companion installed, open the command palette and choose **Workflow Guard: Project Options**, or run `/guard-options`. The command toggles recovery checkpoints, project memory, learner mode, Ralph mode, or the title-settle workaround for the current project. It writes the existing Workflow Guard project config location, which defaults to `.opencode/workflow-guard.json`. Restart OpenCode after changing a setting, so the server plugin can expose the matching tools.
119
194
 
120
- The title-settle workaround is on by default because Workflow Guard's automatic continuation can race OpenCode's asynchronous native title generation. Workflow Guard briefly waits for native title generation before sending an automatic continuation for unfinished root-session work; set `titleSettleWorkaround: false` to opt out. The wait is bounded, applies only when that session owns unfinished todos, and never changes the title or provider/model parameters itself.
195
+ The title-settle workaround is on by default, because Workflow Guard's automatic continuation can race OpenCode's asynchronous native title generation. Workflow Guard briefly waits for native title generation before sending an automatic continuation for unfinished root-session work. Set `titleSettleWorkaround: false` to opt out. The wait is bounded. It applies only when that session owns unfinished todos, and it never changes the title or provider and model parameters itself.
121
196
 
122
- Ralph mode is also off by default, including under the `autonomous` profile. Enable it explicitly with `ralphMode: true`; `ralphMaxIterations` sets its positive integer continuation budget from 1 through 100 (default 10; invalid values use the default). Ralph only continues a root session while that session already owns unfinished native todos. It does not create, prioritize, select, sequence, or delegate work, and every resumed run remains subject to the normal guardrails. Genuine user input stops an active Ralph run instead of resetting its budget. Reaching the budget with unfinished work reports `budget_exhausted`; completion is reported only when owned todos are actually finished. A `blocked` outcome is reserved for a demonstrable guard state and is never inferred merely from repeated idle events. `guard_status` exposes the effective Ralph configuration and current calling-session outcome.
197
+ Ralph mode is also off by default, including under the `autonomous` profile. Enable it explicitly with `ralphMode: true`. `ralphMaxIterations` sets its positive integer continuation budget from 1 through 100. The default is 10, and invalid values use the default. Ralph only continues a root session while that session already owns unfinished native todos. It does not create, prioritize, select, sequence, or delegate work, and every resumed run remains subject to the normal guardrails. Genuine user input stops an active Ralph run instead of resetting its budget. Reaching the budget with unfinished work reports `budget_exhausted`. Completion is reported only when owned todos are actually finished. A `blocked` outcome is reserved for a demonstrable guard state and is never inferred merely from repeated idle events. `guard_status` exposes the effective Ralph configuration and current calling-session outcome.
123
198
 
124
- OpenCode's model/provider request timeout is separate from Workflow Guard. In OpenCode 1.18+, configure it under the selected provider's `options.timeout` in project `opencode.json[c]`; the default is 300000 ms (5 minutes), and `false` disables the provider request timeout. Workflow Guard does not impose a whole-session wall-clock limit.
199
+ OpenCode's model and provider request timeout is separate from Workflow Guard. In OpenCode 1.18+, configure it under the selected provider's `options.timeout` in project `opencode.json[c]`. The default is 300000 ms (5 minutes), and `false` disables the provider request timeout. Workflow Guard does not impose a whole-session wall-clock limit.
125
200
 
126
201
  ---
127
202
 
@@ -129,7 +204,7 @@ OpenCode's model/provider request timeout is separate from Workflow Guard. In Op
129
204
 
130
205
  Workflow Guard enforces secondary review approval (`requireReview`) by default during PR creation (`gh pr create` and `az repos pr create`). Before a pull request can be opened, an agent must invoke a secondary review subagent and record passing review approval using `record_review` evaluated against `guard_review_rubric`.
131
206
 
132
- The recorded verdict is bound to the reviewed repository content (commit hash + tracked-content worktree fingerprint of the directory it was recorded against), and the PR preflight accepts any approval whose binding matches the tree the PR would publish. A review recorded by a subagent rooted in a linked git worktree therefore satisfies a `pr create` run from that worktree, even from a different session than the one that spawned the reviewer. Review freshness is scoped to tracked content: deleting an untracked scratch file does not invalidate an approval, but any tracked edit or new commit does. Because reviewer agents are frequently configured with read-only toolsets that exclude `record_review`, the tool accepts recorders of any session type and binds the verdict to the recording session's directory; the audit trail records who recorded it and where it is bound.
207
+ The recorded verdict binds to the reviewed repository content: the commit hash and the tracked-content worktree fingerprint of the directory it was recorded against. The PR preflight accepts any approval whose binding matches the tree the PR would publish. A review recorded by a subagent rooted in a linked git worktree therefore satisfies a `pr create` run from that worktree. That holds even from a different session than the one that spawned the reviewer. Review freshness is scoped to tracked content. Deleting an untracked scratch file does not invalidate an approval, and any tracked edit or new commit does. Reviewer agents are frequently configured with read-only toolsets that exclude `record_review`. The tool therefore accepts recorders of any session type and binds the verdict to the recording session's directory. The audit trail records who recorded it and where it is bound.
133
208
 
134
209
  To opt out of the PR secondary review gate for a project, set `requireReview: false` in `.opencode/workflow-guard.json`:
135
210
 
@@ -141,7 +216,27 @@ To opt out of the PR secondary review gate for a project, set `requireReview: fa
141
216
 
142
217
  Or set `WORKFLOW_GUARD_REQUIRE_REVIEW=0` in your environment. Conversely, `WORKFLOW_GUARD_REQUIRE_REVIEW=1` enforces the review gate regardless of repository configuration.
143
218
 
144
- For stricter environments, set `requireSubagentReview: true` in `.opencode/workflow-guard.json` to reject approvals recorded by the root session itself: a passing verdict must then be recorded from a subagent session (one with a parent session), never by the root session approving its own work. Changes-requested findings remain relayable from any session. `WORKFLOW_GUARD_REQUIRE_SUBAGENT_REVIEW=1` enables the same requirement for any project; `WORKFLOW_GUARD_REQUIRE_SUBAGENT_REVIEW=0` disables it even when project config enables it.
219
+ For stricter environments, set `requireSubagentReview: true` in `.opencode/workflow-guard.json`. That setting rejects approvals recorded by the root session itself. A passing verdict must then be recorded from a subagent session, which is one with a parent session. The root session can never approve its own work. Changes-requested findings remain relayable from any session. `WORKFLOW_GUARD_REQUIRE_SUBAGENT_REVIEW=1` enables the same requirement for any project. `WORKFLOW_GUARD_REQUIRE_SUBAGENT_REVIEW=0` disables it even when project config enables it.
220
+
221
+ ---
222
+
223
+ ## Live-Documentation Research Floor
224
+
225
+ Policy 25 is enabled by default. A session that accumulates failed tool calls with no live-documentation lookup since gets its next mutating or shell call blocked. The block holds until the agent consults a current source. Only non-research failures count, so a failed `websearch` never deepens the block. Reads, greps, and research tools are never gated.
226
+
227
+ The builtin research names cover `websearch`, `webfetch`, and common aliases. MCP documentation servers are matched on whole server names. That means a guard inspection tool or a generic file reader cannot clear the debt by name accident. If your docs server uses an unrecognised name, declare it:
228
+
229
+ ```json
230
+ {
231
+ "requireLiveDocs": true,
232
+ "liveDocFailureThreshold": 3,
233
+ "researchTools": ["acme-docs", "internal-wiki"]
234
+ }
235
+ ```
236
+
237
+ Or set `WORKFLOW_GUARD_REQUIRE_LIVE_DOCS=0` to disable the floor, `WORKFLOW_GUARD_LIVE_DOC_THRESHOLD` to retune the trigger, and `WORKFLOW_GUARD_RESEARCH_TOOLS` (comma-separated) to add research names. An unparseable, zero, or negative threshold falls back to the default of 3.
238
+
239
+ The acting agent must be permitted to call at least one research tool. An agent whose toolset denies `websearch`/`webfetch` and exposes no MCP docs server cannot satisfy the floor, so grant one or disable the policy.
145
240
 
146
241
  ---
147
242
 
@@ -184,7 +279,7 @@ Project memory and Socratic learning are context modules, not part of Workflow G
184
279
 
185
280
  ### Experimental Learning Mode
186
281
 
187
- Socratic learning is disabled by default. Enable its agent-facing tools with the `learning: true` project option (including through `/guard-options`), or set `WORKFLOW_GUARD_LEARNING=1` in your user environment before OpenCode starts to enable it regardless of the project setting.
282
+ Socratic learning is disabled by default. Enable its agent-facing tools with the `learning: true` project option, including through `/guard-options`. Alternatively, set `WORKFLOW_GUARD_LEARNING=1` in your user environment before OpenCode starts. That enables it regardless of the project setting.
188
283
 
189
284
  To customize the per-session intervention budget, add:
190
285
 
@@ -194,7 +289,7 @@ To customize the per-session intervention budget, add:
194
289
  }
195
290
  ```
196
291
 
197
- to `.opencode/workflow-guard.json` (JSONC is also supported). The intervention budget limits high-value checkpoints per session; learning remains advisory and does not alter policy enforcement or direct the workflow.
292
+ to `.opencode/workflow-guard.json` (JSONC is also supported). The intervention budget limits high-value checkpoints per session. Learning remains advisory and does not alter policy enforcement or direct the workflow.
198
293
 
199
294
  The global learner profile is stored at `$XDG_DATA_HOME/opencode/workflow-guard/learner-profile.json`, falling back to `~/.local/share/opencode/workflow-guard/learner-profile.json`. The parent directory and file are created with user-only permissions where supported. The profile contains distilled learning evidence and project/session references, so it can be backed up or removed separately from any repository.
200
295
 
@@ -204,10 +299,10 @@ The global learner profile is stored at `$XDG_DATA_HOME/opencode/workflow-guard/
204
299
 
205
300
  Project memory is optional supporting context, enabled by default, and requires no additional dependency or service. Set `projectMemory: false` (or toggle it through `/guard-options`) to disable its initialization and tools for a project without weakening policy enforcement. Its authoritative working index is a local SQLite database under `$XDG_DATA_HOME/opencode/workflow-guard/project-memory/`, falling back to `~/.local/share/opencode/workflow-guard/project-memory/`. Repositories use their Git common directory as the local identity where available, so linked worktrees share an index.
206
301
 
207
- The plugin exposes `project_memory_search`, `project_memory_record`, `project_memory_export`, and `project_memory_import`. Recording is intended only for durable facts, decisions, constraints, and lessons; recognized secret content is rejected. Routine tool/test/verification output, transient errors and retries, successful operational telemetry, todos, and session progress belong in their existing operational stores rather than project memory.
302
+ The plugin exposes `project_memory_search`, `project_memory_record`, `project_memory_export`, and `project_memory_import`. Recording is intended only for durable facts, decisions, constraints, and lessons. Recognized secret content is rejected. Routine tool, test, and verification output, transient errors and retries, successful operational telemetry, todos, and session progress belong in their existing operational stores. They do not belong in project memory.
208
303
 
209
- Project-memory maintenance runs opportunistically when a project index is opened, at most once per day. Superseded memories and resolved review follow-ups are retained for 90 days and then pruned; current memories are not expired just because they are old. Maintenance checkpoints and compacts the active SQLite database, and reaps coordination markers (`*.sqlite.active.*`) left behind by processes that died without closing their store; markers owned by live or unidentifiable processes are never removed. When the project-memory directory exceeds 100 MB, least-recently-used inactive project databases are evicted toward about 80 MB when eligible databases exist. The current project's database is never selected for global eviction, and maintenance failures do not disable core guard enforcement.
304
+ Project-memory maintenance runs opportunistically when a project index is opened, at most once per day. Superseded memories and resolved review follow-ups are retained for 90 days and then pruned. Current memories are not expired just because they are old. Maintenance checkpoints and compacts the active SQLite database. It also reaps coordination markers (`*.sqlite.active.*`) left behind by processes that died without closing their store. Markers owned by live or unidentifiable processes are never removed. When the project-memory directory exceeds 100 MB, least-recently-used inactive project databases are evicted toward about 80 MB when eligible databases exist. The current project's database is never selected for global eviction, and maintenance failures do not disable core guard enforcement.
210
305
 
211
306
  Portable project knowledge lives at the fixed path `.opencode/memory/project-memory.jsonl`. Importing that file bootstraps the local index, but exporting is always explicit and accepts selected record IDs rather than dumping private working memory. The plugin adds `.opencode/memory/` to `.git/info/exclude` for the local clone. To share promoted knowledge through Git, deliberately remove that local exclusion or force-add the JSONL file after reviewing its contents. Do not commit the SQLite database.
212
307
 
213
- Compaction receives a bounded set of recent current local memories. Imported portable records are never injected automatically and require an explicit memory search. Path-backed local memories with a recorded Git commit are excluded when those paths have committed, staged, or unstaged changes since that commit; repository contents remain authoritative over remembered context. Memory supplies context rather than instructions and does not select, prioritize, or sequence work.
308
+ Compaction receives a bounded set of recent current local memories. Imported portable records are never injected automatically and require an explicit memory search. Path-backed local memories with a recorded Git commit are excluded when those paths have committed, staged, or unstaged changes since that commit. Repository contents remain authoritative over remembered context. Memory supplies context rather than instructions. It does not select, prioritize, or sequence work.