feat(opencode): hide TODO paths from orchestrator (ADR-22)
In recent runs the orchestrator skipped @pm and edited TODO/ files
itself, despite the workflow.md anti-pattern warning. Root cause: the
workflow doc literally taught the orchestrator the path layout
(`./TODO/<ID>.md`), making self-help a discoverable shortcut.
Fix: remove the recipe. The orchestrator now never constructs or reads
any per-issue TODO path. All TODO operations go through @pm dispatches;
@pm returns the absolute file path of every issue it touches, and the
orchestrator captures and reuses those paths downstream.
- Phase 1 loses the TODO-existence and depends-on checks (former steps
3 and 9 of the recent edit) — Phase 1 is now git/worktree-only.
- Phase 2 expands @pm's existing dispatch into a `Validate run
prerequisites` operation that returns either {ok: true,
issue_file_path, issue: {...}} or {ok: false, error_code, message}
with error_code in {tracker_missing, issue_not_found,
dependency_unmet, dependency_missing}. depends-on enforcement moves
here.
- Phase 7 split_needed exit, Phase 9 TODO Update, Phase 9 Commit TODO
Changes, and Failure Handler all reference @pm-returned paths or use
`git add ./TODO/` blanketly (safe because Phase 1 verified clean tree
and only @pm writes there during a run).
- pm.md gains a path-return rule: every read returns issue_file_path,
every write returns the modified paths. Run-Prerequisite Output
format documented with all four error codes.
- ADR-22 captures the rationale; routing matrix updates Phase 1/2 rows;
pipeline diagram labels updated.
The fix is discoverability-only — no permission deny on TODO/, per
explicit user direction. The schema lives in agents/pm.md, which the
orchestrator does not load.
Refs: config/opencode/workflow-design.md ADR-22
This commit is contained in:
parent
3e515d54eb
commit
a3e0de6d04
3 changed files with 140 additions and 44 deletions
|
|
@ -40,8 +40,8 @@ High-level happy path with the major escalation arms. The workflow runs **one ta
|
|||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
P1["Phase 1: Sanity Check<br/>incl clean tree + depends-on"]
|
||||
P2["Phase 2: Issue Context<br/>pm reads TODO/ID.md"]
|
||||
P1["Phase 1: Sanity Check<br/>git/worktree only, no TODO reads"]
|
||||
P2["Phase 2: Issue Context<br/>pm validates prereqs + returns path"]
|
||||
P3["Phase 3: Plan<br/>write plan.md"]
|
||||
P4{"Phase 4: Review Plan<br/>check blocking, simplify advisory<br/>max 3 cycles"}
|
||||
P5["Phase 5: Split into Tasks<br/>write task-N.md"]
|
||||
|
|
@ -135,10 +135,13 @@ Every observed `(phase, signal) → action`. Empty cells are gaps. Walking this
|
|||
|
||||
| Phase | Signal source | Signal | Action |
|
||||
|---|---|---|---|
|
||||
| 1 | Sanity checks | Bare repo / detached HEAD / missing `TODO/<ID>.md` / branch == base | Stop with error |
|
||||
| 1 | Sanity checks | Bare repo / detached HEAD / branch == base | Stop with error |
|
||||
| 1 | Sanity checks | Working tree dirty (`git status --porcelain` non-empty) | Stop with error (ADR-20) |
|
||||
| 1 | Sanity checks | `depends-on:` issue not in `Done` status | Stop with error (ADR-21) |
|
||||
| 2 | `@pm` | Issue not found | Stop with error |
|
||||
| 2 | `@pm` (Validate run prerequisites) | `ok: true` | Capture `issue_file_path` and full issue context; proceed |
|
||||
| 2 | `@pm` (Validate run prerequisites) | `error_code: tracker_missing` | Stop with error using `@pm`'s message verbatim (ADR-22) |
|
||||
| 2 | `@pm` (Validate run prerequisites) | `error_code: issue_not_found` | Stop with error using `@pm`'s message verbatim (ADR-22) |
|
||||
| 2 | `@pm` (Validate run prerequisites) | `error_code: dependency_unmet` | Stop with error using `@pm`'s message verbatim (ADR-21 / ADR-22) |
|
||||
| 2 | `@pm` (Validate run prerequisites) | `error_code: dependency_missing` | Stop with error using `@pm`'s message verbatim (ADR-22) |
|
||||
| 2 | `@pm` | Status is `Todo` | Flip to `In Progress`; propagate to README.md / parent's Sub-issues |
|
||||
| 3 | Orchestrator | Plan drafted | Apply Dispatch Hygiene; write `plan.md`; verify `test -f` |
|
||||
| 4 | `@check` | ACCEPTABLE (regardless of `@simplify`) | Proceed to Phase 5 |
|
||||
|
|
@ -352,6 +355,13 @@ The model carries five sub-decisions:
|
|||
**Alternatives:** (a) keep N-task runs, add mid-flight re-splitting via P7→P5 re-entry — doesn't solve big-diff or cross-task regression; (b) keep N-task runs, accept the gaps — leaves three known-bad routes; (c) always one task per issue (skip Phase 5 entirely) — loses the planning-phase split heuristic that's catching legitimate over-scoping at design time.
|
||||
**Consequences:** runs become shorter and more focused. Each commit/PR carries a bounded scope. Sub-issue fan-out becomes the primary scaling mechanism for multi-step work. `TODO/` sees more sub-issue files; `@pm`'s split-time filing path becomes a hot code path. Concurrent runs in different worktrees on the same repo become trivially safe because each worktree has its own `TODO/` checkout (file conflicts surface as standard git merge conflicts at integration time, not as mid-run race conditions).
|
||||
|
||||
### ADR-22 (2026-05-08) — TODO path resolution lives with `@pm`; orchestrator never constructs TODO paths
|
||||
|
||||
**Context:** in early runs of the one-task-per-run workflow, the orchestrator sometimes did `@pm`'s job itself — reading `./TODO/$ISSUE_ID.md` directly to inspect the issue, instead of dispatching `@pm`. The text-level "anti-patterns" warning (workflow.md §Roles & Dispatch) wasn't enough to prevent it: once the workflow document told the orchestrator that issue files lived at `./TODO/<ID>.md`, the recipe was discoverable and tempting. Phase 1's sanity check (former steps 3 + 9 — TODO-tracker existence and `depends-on` enforcement) was the most blatant offender, since it required the orchestrator to read TODO files directly.
|
||||
**Decision:** the orchestrator does not read, write, or construct any path under `TODO/` at any phase. All TODO operations — including prerequisite validation that used to live in Phase 1 — go through `@pm` dispatches. `@pm`'s response always includes the absolute file path of every issue file it touched (or read); the orchestrator captures these paths and uses them downstream (Phase 9 staging, Failure Handler comments, etc.) instead of constructing them. Phase 1 keeps only git/worktree-shaped checks; Phase 2 expands `@pm`'s existing dispatch into a "Validate run prerequisites" operation that returns either `{ok: true, issue_file_path, issue: {...}}` or a structured error.
|
||||
**Alternatives:** (a) permission-deny `TODO/**` for the orchestrator — would force-fail orchestrator self-help but adds a permission layer the user prefers to avoid; (b) leave the doc warnings in place and hope the orchestrator complies — already shown to be insufficient; (c) keep Phase 1's TODO checks and just discipline the orchestrator harder — same problem as (b).
|
||||
**Consequences:** discoverability of the path layout disappears from `commands/workflow.md` — the orchestrator literally never sees a `TODO/<ID>.md` template to imitate. The schema and path layout live in `agents/pm.md`, which the orchestrator does not load. `@pm`'s capabilities table grows by one ("Validate run prerequisites") and every existing capability now mandates including the absolute file path in the response. The orchestrator's Phase 9 staging step changes from constructing paths to using `@pm`-returned paths (or, equivalently, `git add ./TODO/` since the working tree was clean at Phase 1 and only `@pm` writes to TODO during a run).
|
||||
|
||||
---
|
||||
|
||||
## 6. Open Questions / Known Gaps
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue