You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/working/hook-harness-design-spec.md
+56-13Lines changed: 56 additions & 13 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -8,6 +8,17 @@ This document defines a deterministic worktree harness for OpenCode using plugin
8
8
9
9
The current worktree workflow relies on skills and orchestrator prompts to convince agents to call `worktree_prepare` and `worktree_cleanup` at the right time. That behavior is not reliable enough because skills are still prompt text, and agents can skip or misapply them. The result is nondeterministic workspace selection and inconsistent cleanup behavior.
10
10
11
+
## Validated Runtime Constraints
12
+
13
+
Validation against current OpenCode runtime behavior showed an important host boundary:
14
+
15
+
-`tool.execute.before` does fire for built-in tools
16
+
- hook payload mutation is only reliable when mutating the existing object in place
17
+
- replacing `output.args` or `output.parts` wholesale is not a safe assumption
18
+
- the documented plugin model is centered on the host-selected `worktree`, not on plugins acting as a general multi-worktree router for every built-in tool
19
+
20
+
This means the harness can still use hooks as an enforcement layer, but only for capabilities it can actually control. Transparent routing of all built-in tools across multiple task worktrees is not a host-guaranteed primitive today.
21
+
11
22
## Goals
12
23
13
24
- Make worktree creation deterministic for non-trivial editable work.
@@ -16,6 +27,7 @@ The current worktree workflow relies on skills and orchestrator prompts to convi
16
27
- Keep the primary orchestrator as the only authority that can create, switch, fork, or cleanup-manage worktrees.
17
28
- Keep the skill policy-oriented and move enforcement into plugin hooks.
18
29
- Keep the existing `worktree_prepare` and `worktree_cleanup` tool contract as the public capability surface.
30
+
- Preserve an enforceable routing story for mutating work even if OpenCode does not provide a fully general built-in tool redirection contract.
19
31
20
32
## Non-Goals
21
33
@@ -74,6 +86,8 @@ Recommended OpenCode hooks:
74
86
-`command.execute.before`: slash-command normalization and parity
75
87
-`experimental.chat.system.transform`: model-facing context injection only
76
88
89
+
Hooks remain the right place for classification, state adoption, prompt context, and live payload mutation. They should not be treated as proof that the plugin can transparently re-home every built-in tool into an arbitrary task worktree.
90
+
77
91
### Runtime State Layer
78
92
79
93
The authoritative runtime state should live outside the repo in plugin-managed storage keyed by canonical repo root and `sessionID`. Optional diagnostic mirrors inside `.opencode/` are acceptable, but they must not be authoritative.
@@ -182,11 +196,13 @@ Responsibilities:
182
196
- classify the call as read-only, mutating, or delegation
183
197
- resolve task continuity
184
198
- create or select a bound worktree if isolation is required
185
-
- rewrite execution context and repo-scoped paths into the bound worktree
199
+
- rewrite execution context and repo-scoped paths into the bound worktree when the runtime contract safely permits it
186
200
- block when safe provisioning or safe rewriting is not possible
187
201
188
202
This hook is the main choke point because it runs immediately before the action happens.
189
203
204
+
For hard guarantees, this hook should prefer plugin-controlled execution paths over assumptions about undocumented built-in routing semantics.
205
+
190
206
### `tool.execute.after`
191
207
192
208
Responsibilities:
@@ -204,6 +220,8 @@ Responsibilities:
204
220
- route `/wt-new` and `/wt-clean` through the same state model
205
221
- keep slash commands as human UX only, not a separate control plane
206
222
223
+
This hook should mutate existing prompt parts in place when possible. It should not assume replacing `output.parts` wholesale is honored by the host.
224
+
207
225
### `experimental.chat.system.transform`
208
226
209
227
Responsibilities:
@@ -214,6 +232,8 @@ Responsibilities:
214
232
215
233
This hook improves model behavior but is not part of enforcement.
216
234
235
+
It should be treated as advisory context only, never as the mechanism that makes routing correct.
236
+
217
237
## Delegation Policy
218
238
219
239
Task-tool calls are the canonical delegation boundary.
@@ -287,16 +307,34 @@ Provenance should affect messaging and cleanup recommendations, but not whether
287
307
288
308
## Path and Workdir Rewriting
289
309
290
-
The harness should rewrite execution into the bound worktree by default.
310
+
The harness should rewrite execution into the bound worktree only where the runtime contract is actually enforceable.
291
311
292
312
### Rewrite Rules
293
313
294
-
- always rewrite repo-scoped workdir or cwd into the active worktree
295
-
- always rewrite repo-scoped file paths into the active worktree
314
+
- mutate live path-like arguments in place when the runtime exposes a stable mutable payload
315
+
- prefer plugin-owned mutating tools when execution location must be guaranteed
316
+
- rewrite repo-scoped workdir or cwd only when the called tool actually honors those fields as execution inputs
317
+
- rewrite repo-scoped file paths only when the path is an explicit structured argument that can be rewritten safely
296
318
- never rewrite clearly external absolute paths
297
319
- block rather than guess when a path cannot be safely classified
298
320
299
-
This should make workspace isolation deterministic without depending on agents to remember worktree-local paths.
321
+
This can make parts of workspace isolation deterministic without depending on agents to remember worktree-local paths, but only for tools whose execution semantics are actually under plugin control.
322
+
323
+
### Enforceable Routing Boundary
324
+
325
+
The harness should distinguish three classes of execution:
326
+
327
+
1. Plugin-owned tools
328
+
2. Built-in tools with safe in-place mutable structured arguments
329
+
3. Built-in tools whose effective execution context is owned by OpenCode
330
+
331
+
For class 1, the plugin can guarantee execution in the bound task worktree.
332
+
333
+
For class 2, the plugin can often steer execution by mutating the live payload in place, but must still treat that as host-contract-sensitive behavior.
334
+
335
+
For class 3, the plugin may only provide context, advisory blocking, or explicit failure rather than claim transparent routing.
336
+
337
+
If hard routing guarantees are required for mutating work, the plugin should own the relevant mutating tools rather than rely on undocumented built-in routing semantics.
300
338
301
339
## Privilege Model
302
340
@@ -421,14 +459,15 @@ This makes workspace context portable and inspectable across fresh-context subag
421
459
422
460
## Acceptance Criteria
423
461
424
-
- A mutating tool call in a non-trivial task does not execute in repo root when no bound worktree exists.
462
+
- A mutating task that requires hard workspace isolation is either routed through a plugin-controlled execution path or blocked when no safe enforcement path exists.
425
463
- A Task-tool editable delegation causes worktree binding before delegation.
426
464
- Planner, implementer, and reviewer reuse the same worktree for one linear task unless the orchestrator explicitly forks.
427
465
- Manual `worktree_prepare` is adopted into managed runtime state.
428
466
- Manual `worktree_cleanup` passes through the same harness policy layer.
429
467
- Cleanup advice appears only at defined advisory moments.
430
468
- Provisioning or rewrite failures block execution unless the user explicitly overrides.
431
469
- Model prompt injection improves context but is not required for enforcement correctness.
470
+
- The plugin does not claim transparent built-in multi-worktree routing beyond what the validated host/runtime contract can enforce.
432
471
433
472
## Test Matrix
434
473
@@ -445,6 +484,8 @@ This makes workspace context portable and inspectable across fresh-context subag
445
484
- rewrite ambiguity blocks execution
446
485
- explicit override allows the scoped root fallback and is visibly recorded
447
486
- cleanup advice fires at completion but not on every successful command
487
+
- hook payload rewrites mutate existing `args` and `parts` objects in place
488
+
- plugin-owned mutating tools execute against the bound task worktree
448
489
449
490
## Recommended Implementation Sequence
450
491
@@ -453,12 +494,13 @@ This makes workspace context portable and inspectable across fresh-context subag
453
494
3. Implement task continuity and binding APIs.
454
495
4. Add `tool.execute.before` enforcement.
455
496
5. Add Task-tool delegation handling.
456
-
6. Add repo-scoped path and workdir rewriting.
457
-
7. Add `tool.execute.after` adoption and advisory signaling.
458
-
8. Add `command.execute.before` parity handling.
459
-
9. Extend the handoff schema with workspace fields.
0 commit comments