From 9e151c4343778dc227be58f8fa91411d178cb5b3 Mon Sep 17 00:00:00 2001 From: Benjamin Borbe Date: Sat, 17 Oct 2026 16:01:27 +0200 Subject: [PATCH 1/2] apply one-fix audit gaps in plan-task's fix loop instead of asking --- CHANGELOG.md | 4 ++++ commands/plan-task.md | 39 ++++++++++++++++++++++++++------------- 2 files changed, 30 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d1ab1d8d..77d197b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ Please choose versions by [Semantic Versioning](http://semver.org/). * MINOR version when you add functionality in a backwards-compatible manner, and * PATCH version when you make backwards-compatible bug fixes. +## Unreleased + +- feat: `vault-cli:plan-task`'s fix loop now applies a gap that carries a stated fix and no genuine alternative, and asks the operator only for a real fork. It previously put every auditor finding to the operator as an `AskUserQuestion`, so a gap whose only defensible outcome was the one already written down still cost an interrupt β€” three workers hit it on 2026-10-17, and in two of them the operator picked the recommended option every time while the goal's manager independently gave the same answers. The loop now classifies each gap by one question ("is there a second answer I would defend?"), applies one-fix gaps via `Edit` and reports them as `πŸ”§ Applied: …`, and reserves the question for two-or-more genuine outcomes or an operator-only decision. A gap whose options would read *"(Recommended) do X Β· 2. do nothing"* is named as a one-fix gap wearing a menu. Applied fixes still re-enter the same `Score: X β†’ Y` cycle and the `β‰₯ 8` gate is unchanged; the record is kept β€” every applied fix is printed and any that changed a criterion's claim is recorded in the task's `# Progress`, so the classification removes the question and never the audit trail. Two gap classes still always ask: an unevidenced premise (the fix is a read only the operator can vouch for) and a scope change. `--non-interactive` is untouched β€” its short-circuit still edits nothing. Rationale: `agent-cmd/no-user-prompts`, applied to plan-task's own fix loop. + ## v0.169.5 - fix: `vault-cli:work-on-task-assistant` no longer states conclusions from evidence it merely grepped, and its report block list is declared closed. Dispatched 2026-10-17 to find a task and its guides, the agent added an unsanctioned `πŸ”¬ Independent read-only corroboration` block asserting the router log held "exactly one request" in a window that held 16 (five PIDs across four builds), and that "the absence of any worker-attributable line means the request did not arrive" β€” the opposite of the truth, since that router sampler-gates successful requests out of the log while always logging failures. The owning session had to re-derive both claims, and taken at face value they would have inverted the diagnosis. A second rule requires any inference to be marked `(inference β€” unverified)`: a log the agent did not write may be sampled, truncated, or shared by concurrent writers, and its silence rarely means absence. diff --git a/commands/plan-task.md b/commands/plan-task.md index 84d091b6..880149d1 100644 --- a/commands/plan-task.md +++ b/commands/plan-task.md @@ -82,7 +82,7 @@ Task tool with: ### 5. Check the non-negotiables -Six checks beyond the auditor's general scoring β€” first five are hard (any failure β†’ mandatory question in step 6, can't exit on auditor score alone), sixth is a soft warning. +Ten checks beyond the auditor's general scoring β€” nine hard (any failure β†’ a step 6 gap, classified there as apply-or-ask; can't exit on auditor score alone), plus the KISS ceiling as the one soft warning. This list is the source of truth for the count; if you add or remove a check, update every "nine" below in the same pass. **Hard:** @@ -145,20 +145,20 @@ Six checks beyond the auditor's general scoring β€” first five are hard (any fai **Note the scope of this item.** It sits inside a block that is skipped for non-shipping-class tasks (next line), yet the defect it catches is not shipping-specific β€” the 2026-09-21 case was a diagnosis task, where this gate never ran. `agents/task-auditor.md` Β§ 15 carries the same check and runs on every task; this item is the shipping-class enforcement, not the universal home. Skip this whole check for non-shipping-class tasks (pure research, decision, doc-only with no published artifact). -- **Subtask-goal alignment** β€” every `# Tasks` checkbox must either (a) map by topic to β‰₯ 1 `# Success Criteria` outcome, or (b) be the e2e verify subtask. Flag any orphan as a scope-creep candidate; in step 6 the owner can link it to an SC, move it to `# Out of Scope`, or split it into a separate task. -- **Blast radius named** β€” if any subtask pushes to a registry, deploys, mutates a cluster, or needs a credential/secret, the task must name the external system AND the account written to (e.g. *"pushes `docker.io/bborbe/` under the bborbe Docker Hub account"*). A credential requirement with no named target is a scope gap: the owner discovers what was automated at the secrets request, after the work has shipped. Observed 2026-08-27 β€” a publish-on-tag CI was designed, merged and released; the owner objected (*"Is the agent trying to push a docker image? I don't think that I want this"*) only when its Docker Hub secrets were requested, costing two reversal PRs for a net deletion. Flag β†’ mandatory question in step 6. -- **Resolution steps are resolvable** β€” a subtask naming a *lookup, join or key* (*"resolve the gate to its store `item_id`"*, *"look up the record by X"*) must be checked against whether that key actually exists and is derivable **by the actor that will run it**. This is the one hard check that is about the plan's *premise* rather than its shape: a subtask can name a concrete verb, a concrete artifact and a concrete outcome and still be unsatisfiable by construction. Ask of each: *do the two records share a field, and does the caller hold it?* Observed 2026-09-21 β€” a subtask read *"the sweep resolves the gate it is about to publish to a store `item_id`"*. No such join existed: the store wrote `item_id` itself, the producer's dedup key was a hash of a value the manager never holds, and the third candidate field was documented as `` β€” two kinds of value in one field, so it could not anchor an identity either. The same session's e2e-verify subtask also named no procedure and no expected result. **Two unsatisfiable-by-construction subtasks in one task is the signal that this check is missing, not that the author was careless** β€” and the cost is paid at execution, where the premise has already collapsed and the task file needs revising mid-flight. Flag β†’ mandatory question in step 6. +- **Subtask-goal alignment** β€” every `# Tasks` checkbox must either (a) map by topic to β‰₯ 1 `# Success Criteria` outcome, or (b) be the e2e verify subtask. Flag any orphan as a scope-creep candidate; in step 6 the owner can link it to an SC, move it to `# Out of Scope`, or split it into a separate task β€” a step 6 fork, since those are three defensible outcomes. +- **Blast radius named** β€” if any subtask pushes to a registry, deploys, mutates a cluster, or needs a credential/secret, the task must name the external system AND the account written to (e.g. *"pushes `docker.io/bborbe/` under the bborbe Docker Hub account"*). A credential requirement with no named target is a scope gap: the owner discovers what was automated at the secrets request, after the work has shipped. Observed 2026-08-27 β€” a publish-on-tag CI was designed, merged and released; the owner objected (*"Is the agent trying to push a docker image? I don't think that I want this"*) only when its Docker Hub secrets were requested, costing two reversal PRs for a net deletion. Flag β†’ a step 6 gap, applied or asked per its classification there. +- **Resolution steps are resolvable** β€” a subtask naming a *lookup, join or key* (*"resolve the gate to its store `item_id`"*, *"look up the record by X"*) must be checked against whether that key actually exists and is derivable **by the actor that will run it**. This is the one hard check that is about the plan's *premise* rather than its shape: a subtask can name a concrete verb, a concrete artifact and a concrete outcome and still be unsatisfiable by construction. Ask of each: *do the two records share a field, and does the caller hold it?* Observed 2026-09-21 β€” a subtask read *"the sweep resolves the gate it is about to publish to a store `item_id`"*. No such join existed: the store wrote `item_id` itself, the producer's dedup key was a hash of a value the manager never holds, and the third candidate field was documented as `` β€” two kinds of value in one field, so it could not anchor an identity either. The same session's e2e-verify subtask also named no procedure and no expected result. **Two unsatisfiable-by-construction subtasks in one task is the signal that this check is missing, not that the author was careless** β€” and the cost is paid at execution, where the premise has already collapsed and the task file needs revising mid-flight. Flag β†’ a step 6 gap, applied or asked per its classification there. -- **Premise matches the ask** β€” when `# Impact` quotes the operator verbatim, re-read the quote against Summary + Success Criteria. Flag when the quote admits more than one reading, or when an SC/constraint excludes a plausible reading of it (e.g. *"must NOT be named X"* when the quote says X). A plan can score 9/10 on shape and still build the wrong thing β€” the task writer's interpretation is not the operator's words. Observed 2026-09-23: *"add a open skill"* was read as an open-items skill (name `open` banned); the operator meant moving `/open` β†’ `/supervisor:open` β€” found only after release. Flag β†’ mandatory question in step 6, quoting both readings. -- **Empirical premises are evidenced** β€” a `# Impact` or Summary asserting a fact about the running system (*"nothing else reads this DB"*, *"the data is disposable"*, *"X is unused"*, *"the functional window is one minute"*) is a premise the plan rests on, and the shape checks never reach it. This is the *system* sibling of the premise check above: that one compares the operator's quote to the plan, this one asks whether the plan's own factual premise is evidenced in the file. Ask of each: *does the task name the read that establishes this β€” a query, a log line, a config?* Absent it, flag β†’ mandatory question in step 6. Observed: a task claimed *"the data is disposable… nothing else reads this DB"* and was audited 9/10 three times; the claim was false, and the change was built, merged and deployed, then reverted. -- **Steps are runnable by the actor that owns the task** β€” a criterion or subtask naming a command the executing session's **tier** may not run is unsatisfiable by construction, however concrete it reads. `claude-supervisor`'s tiers are the common case: `/supervisor:open` and the `manager-*` / `fleet-*` verbs are manager-only, and a worker session must not run them. This is the *authority* sibling of the premise check above β€” that one asks whether the actor holds the **key**, this one whether it holds the **right**. Ask of each named command: *may the session that owns this task run it β€” and if not, does the criterion name who does?* Observed 2026-09-30 β€” an SC required *"one live run shows both halves … read from that session's own output"*. The command was `/supervisor:open --flagged`, the task was worker-anchored, and the criterion was satisfiable only after the run was routed to a manager session. The plan had passed every shape check. Flag β†’ mandatory question in step 6. +- **Premise matches the ask** β€” when `# Impact` quotes the operator verbatim, re-read the quote against Summary + Success Criteria. Flag when the quote admits more than one reading, or when an SC/constraint excludes a plausible reading of it (e.g. *"must NOT be named X"* when the quote says X). A plan can score 9/10 on shape and still build the wrong thing β€” the task writer's interpretation is not the operator's words. Observed 2026-09-23: *"add a open skill"* was read as an open-items skill (name `open` banned); the operator meant moving `/open` β†’ `/supervisor:open` β€” found only after release. Flag β†’ a step 6 fork, never a one-fix gap (the operator's own words are the decision), quoting both readings. +- **Empirical premises are evidenced** β€” a `# Impact` or Summary asserting a fact about the running system (*"nothing else reads this DB"*, *"the data is disposable"*, *"X is unused"*, *"the functional window is one minute"*) is a premise the plan rests on, and the shape checks never reach it. This is the *system* sibling of the premise check above: that one compares the operator's quote to the plan, this one asks whether the plan's own factual premise is evidenced in the file. Ask of each: *does the task name the read that establishes this β€” a query, a log line, a config?* Absent it, flag β†’ a step 6 fork (unevidenced premise β€” see Β§ 6 *Two gaps always ask*); apply only when the read is yours to run. Observed: a task claimed *"the data is disposable… nothing else reads this DB"* and was audited 9/10 three times; the claim was false, and the change was built, merged and deployed, then reverted. +- **Steps are runnable by the actor that owns the task** β€” a criterion or subtask naming a command the executing session's **tier** may not run is unsatisfiable by construction, however concrete it reads. `claude-supervisor`'s tiers are the common case: `/supervisor:open` and the `manager-*` / `fleet-*` verbs are manager-only, and a worker session must not run them. This is the *authority* sibling of the premise check above β€” that one asks whether the actor holds the **key**, this one whether it holds the **right**. Ask of each named command: *may the session that owns this task run it β€” and if not, does the criterion name who does?* Observed 2026-09-30 β€” an SC required *"one live run shows both halves … read from that session's own output"*. The command was `/supervisor:open --flagged`, the task was worker-anchored, and the criterion was satisfiable only after the run was routed to a manager session. The plan had passed every shape check. Flag β†’ a step 6 gap, applied or asked per its classification there. **Routing a step to another tier has the same test, in reverse.** A step re-assigned to a manager must be one the manager contract permits β€” a manager never performs a row's work (no verification, no investigation) and never spawns `role: manager`. A worker's own SC probe stays the worker's; when it cannot run it, route it to the operator, not up the tier. Observed 2026-10-10: SC probes routed to a topic manager were declined at execution, forcing a re-ask. **Soft:** - **KISS ceiling** β€” if `# Tasks` has > 8 checkboxes, warn: *"task may be too large for one session β€” consider splitting, moving items to `# Out of Scope`, or promoting to a goal."* Owner decides; task can still proceed to execution. -Any hard check failing β†’ mandatory question in step 6; can't exit on auditor score alone. Soft check failing β†’ surfaced as a question in step 6 but doesn't block exit if owner says proceed. +Any hard check failing β†’ a step 6 gap, resolved there by its classification (one-fix gaps applied, forks asked); can't exit on auditor score alone. Soft check failing β†’ surfaced in step 6 but doesn't block exit if owner says proceed. ### 6. Surface gaps + fix loop @@ -193,7 +193,20 @@ Any hard check failing β†’ mandatory question in step 6; can't exit on auditor s Only findings the auditor labelled `[template-level]` are verdict-coverable. An `[instance-level]` finding always asks, verdict or not. -Translate findings (auditor + non-negotiable checks) into questions. Rules: +**Classify every gap before you put it to anyone β€” a one-fix gap is applied, not asked.** Read each gap (auditor finding or failed non-negotiable) and sort it by one question: *is there a second answer I would defend?* (Under `--non-interactive` none of this applies β€” the NO-ASK short-circuit above still governs, and nothing is `Edit`ed.) + +- **One-fix gap β†’ apply it, then report it.** The gap carries a stated fix and no genuine alternative β€” a missing `# Definition of Done`, a baseline to rebase, a window to pin, a criterion whose evidence shape is wrong. Apply the stated fix via `Edit` (re-running the step 2 ownership gate first) and print `πŸ”§ Applied: β€” `. Do not open an `AskUserQuestion` for it. +- **Real fork β†’ ask.** Two or more genuinely defensible outcomes, or a decision only the operator can make β€” scope, a production-touching step, which of two homes a rule belongs in. These are what the rules below are for. + +**The distinguishing test: write the second answer down before you ask.** State the alternative you would have offered. Cannot name one you would defend β†’ one-fix gap, apply it. Can name two β†’ fork, ask. A gap whose options would read *"(Recommended) do X Β· 2. do nothing"* is a one-fix gap wearing a menu: "do nothing" is a refusal to decide, not an alternative, and an operator who picks option 1 every time is telling you the menu should not have been built. This is `agent-cmd/no-user-prompts` applied to a case it did not originally name β€” plan-task's own fix loop is the prompt. + +**Report; never silently rewrite.** The classification removes the *question*, never the *record*. Print every applied fix, and when one changed a criterion's claim, record it in the task's `# Progress` β€” the operator must be able to see what plan-task decided on their behalf and reverse it. A silent rewrite is the failure mode this trades against; do not reintroduce it for brevity. + +**Applied fixes re-enter the loop.** They take the same `Score: X β†’ Y` cycle as an answered question β€” re-run the auditor after each batch, and keep looping until score β‰₯ 8 AND all nine hard non-negotiables pass. The gate does not relax because nobody was asked. + +**Three gaps always ask, whatever they look like.** An **unevidenced premise** the plan rests on β€” the fix is a *read*, and only the operator can say whether it was taken. A **misread operator quote**, where `# Impact` quotes the operator and the plan's reading is one of two plausible ones β€” those are the operator's own words, so only they can settle it. And a **scope** change, where "apply the obvious fix" and "this task should not do that at all" are both live. For a premise, do the read first when it is cheap and in-scope, then report the result rather than asking; ask only when the read is not yours to run. + +Translate the remaining findings (auditor + non-negotiable checks) into questions. Rules: - Max 3 questions per turn - Each question is short (one sentence) + tight options (single yes/no OR 2-4 numbered options) @@ -201,7 +214,7 @@ Translate findings (auditor + non-negotiable checks) into questions. Rules: - Quote the offending line/section so owner sees what triggered the question - Use `AskUserQuestion` for the actual ask -Apply each answer via `Edit` β€” re-running the step 2 ownership gate first (see there). Re-run auditor after each batch. Print delta `Score: X β†’ Y`. Loop until score β‰₯ 8 AND all five hard non-negotiables pass OR owner says "good enough." A current template verdict (see the template-verdict check above) satisfies the score gate for the findings it covers. +Apply each answer via `Edit` β€” re-running the step 2 ownership gate first (see there). Re-run auditor after each batch. Print delta `Score: X β†’ Y`. Loop until score β‰₯ 8 AND all nine hard non-negotiables pass OR owner says "good enough." A current template verdict (see the template-verdict check above) satisfies the score gate for the findings it covers. **Propagate, don't just patch.** A reworded criterion is usually restated verbatim in `# Tasks` and `# Definition of Done`, and sometimes in the Summary and `# Out of Scope`. Edit every section that restates it in the same pass β€” a fix applied to the criterion alone leaves the plan still carrying the claim you just removed, and the next audit reports it as a *fresh* defect rather than the same one. That reads as a new problem, so the loop repairs the restatement instead of noticing the half-application, and burns a round per section. When an answer changes a criterion's claim, grep the task file for the old wording before re-running the auditor. @@ -225,11 +238,11 @@ Under NO-ASK the score is whatever the auditor returned (nothing was fixed, so t ## Notes -- **Scope is focused on what blocks safe execution.** Plan-task enforces five hard planning-gate checks (SC defined, subtasks reach goal, e2e verify subtask, subtask-goal alignment, blast radius named) plus the KISS ceiling as a soft sixth, because each one prevents a specific failure mode: missing outcomes, missing path, dishonest-tick verification, scope creep, an unnamed blast radius, oversize task. Other heuristics (MVP framing, Out-of-Scope capture quality, evidence shape) stay in `task-auditor` and `task-writing.md` as canonical rules β€” surfaced via the auditor in step 4, not promoted to dedicated gates. Letting the auditor enforce general structure while plan-task enforces the five named gates keeps the command short and the gates legible. +- **Scope is focused on what blocks safe execution.** Plan-task enforces nine hard planning-gate checks β€” SC defined, subtasks reach goal, e2e verify subtask, subtask-goal alignment, blast radius named, resolution steps resolvable, premise matches the ask, empirical premises evidenced, steps runnable by the owning actor β€” plus the KISS ceiling as a soft tenth, because each one prevents a specific failure mode: missing outcomes, missing path, dishonest-tick verification, scope creep, an unnamed blast radius, an unsatisfiable-by-construction premise, a misread operator quote, an unevidenced system claim, a step the owning tier may not run, and oversize task. Other heuristics (MVP framing, Out-of-Scope capture quality) stay in `task-auditor` and `task-writing.md` as canonical rules β€” surfaced via the auditor in step 4, not promoted to dedicated gates. Letting the auditor enforce general structure while plan-task enforces the nine named gates keeps the gates legible. - **Questions stay tight, with consequence visible.** 2-3 lines of setup β†’ short options. "Tight" doesn't mean stripping context β€” owner must see what each answer *changes*. Quote the offending line, name the trade-off, then options. - **Subtask granularity = session-sized.** When proposing or sharpening `# Tasks` items, target *work-block size* (a session's worth of work), not CLI-step size. Aim for 3-6 items per task. Reject auditor-suggested over-decomposition like "run precommit / open PR / merge PR" as separate subtasks β€” those collapse into one "ship the change" block. - **Reads `~/.claude/plugins/marketplaces/vault-cli/docs/task-writing.md` as the canonical rule source** β€” same rules `task-auditor` enforces. -- **Conversational on purpose.** Owner is the judge of substance. Plan-task never silently rewrites; every change comes from an explicit answer. This is exactly why `--non-interactive` refuses to guess: with no owner to answer, the honest move is to stop and list the gaps, never to invent answers and edit the task. +- **Conversational on purpose β€” about substance, not about ratification.** Owner is the judge of substance: a genuine fork always goes to them. What changed is that a gap carrying one stated fix and no alternative is *applied* and reported rather than put to them as a question β€” asking there was a request to ratify, not a question, and it cost an interrupt per gap. Plan-task still never *silently* rewrites: every applied fix is printed in the run's output, and any that changed a criterion's claim is recorded in `# Progress`. This is also why `--non-interactive` refuses to guess: with no owner to answer, the honest move is to stop and list the gaps, never to invent answers and edit the task. - **Entry contract.** A row at `phase: todo` is not planned by plan-task: it refuses, tells the operator the row is waiting in their approval inbox, and asks whether to approve it β€” running `vault-cli task approve` itself on the operator's explicit yes, and never without it. `vault-cli task approve` performs the `todo β†’ planning` flip and records `approved_by`/`approved_at` β€” plan-task itself never flips a phase. No `/work-on-task` prerequisite for the approval itself. An **empty** phase is a different state, not a stricter one: it is not the approval inbox, `task approve` cannot accept it, and the entry path (`vault-cli task work-on`) is what advances it β€” plan-task reports the row as unentered and stops, rather than approving it or advancing the phase itself. - **No phase flip.** plan-task never transitions phase; it validates and hands off to `/vault-cli:execute-task`, which owns the `planning β†’ execution` flip. The `todo β†’ planning` entry flip happens only through `vault-cli task approve` β€” step 3 writes no phase, it refuses an unapproved row and at most promotes `next` β†’ `in_progress`. - **Mechanical fixes stay in `/audit-task`.** This command is for substance (SC, subtasks, goal alignment), not formatting. @@ -240,7 +253,7 @@ Task lifecycle: 1. `/vault-cli:create-task` β€” capture (lenient) 2. `/vault-cli:work-on-task` β€” orient (status + guides + daily note), then auto-chain into this command (both modes; headless callers pass `--non-interactive` through) -3. **`/vault-cli:plan-task`** β€” sharpen (5 hard gates); never flips `planning β†’ execution` β€” this command +3. **`/vault-cli:plan-task`** β€” sharpen (9 hard gates); never flips `planning β†’ execution` β€” this command 4. `/vault-cli:execute-task` β€” gate planning β†’ execution; flips phase + prints first subtask + DoD reminder 5. Start work β€” while working, use any of: - `/vault-cli:update-task` β€” log completed work, sync to daily note / parent goal From d1de258bdbf6397146a9248e0f6fbd68781a9c45 Mon Sep 17 00:00:00 2001 From: Benjamin Borbe Date: Sat, 17 Oct 2026 16:12:00 +0200 Subject: [PATCH 2/2] fix stale gate counts and a refuted premise from review --- CHANGELOG.md | 2 +- commands/execute-task.md | 2 +- commands/plan-task.md | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 77d197b1..bfc0ac7c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ Please choose versions by [Semantic Versioning](http://semver.org/). ## Unreleased -- feat: `vault-cli:plan-task`'s fix loop now applies a gap that carries a stated fix and no genuine alternative, and asks the operator only for a real fork. It previously put every auditor finding to the operator as an `AskUserQuestion`, so a gap whose only defensible outcome was the one already written down still cost an interrupt β€” three workers hit it on 2026-10-17, and in two of them the operator picked the recommended option every time while the goal's manager independently gave the same answers. The loop now classifies each gap by one question ("is there a second answer I would defend?"), applies one-fix gaps via `Edit` and reports them as `πŸ”§ Applied: …`, and reserves the question for two-or-more genuine outcomes or an operator-only decision. A gap whose options would read *"(Recommended) do X Β· 2. do nothing"* is named as a one-fix gap wearing a menu. Applied fixes still re-enter the same `Score: X β†’ Y` cycle and the `β‰₯ 8` gate is unchanged; the record is kept β€” every applied fix is printed and any that changed a criterion's claim is recorded in the task's `# Progress`, so the classification removes the question and never the audit trail. Two gap classes still always ask: an unevidenced premise (the fix is a read only the operator can vouch for) and a scope change. `--non-interactive` is untouched β€” its short-circuit still edits nothing. Rationale: `agent-cmd/no-user-prompts`, applied to plan-task's own fix loop. +- feat: `vault-cli:plan-task`'s fix loop now applies a gap that carries a stated fix and no genuine alternative, and asks the operator only for a real fork. It previously put every auditor finding to the operator as an `AskUserQuestion`, so a gap whose only defensible outcome was the one already written down still cost an interrupt β€” two workers hit it on 2026-10-17, and in both the operator picked the recommended option every time while the goal's manager independently gave the same answers. (A third was reported by the manager but carries no `AskUserQuestion` in its transcript, so it is not counted.) The loop now classifies each gap by one question ("is there a second answer I would defend?"), applies one-fix gaps via `Edit` and reports them as `πŸ”§ Applied: …`, and reserves the question for two-or-more genuine outcomes or an operator-only decision. A gap whose options would read *"(Recommended) do X Β· 2. do nothing"* is named as a one-fix gap wearing a menu. Applied fixes still re-enter the same `Score: X β†’ Y` cycle and the `β‰₯ 8` gate is unchanged; the record is kept β€” every applied fix is printed and any that changed a criterion's claim is recorded in the task's `# Progress`, so the classification removes the question and never the audit trail. Three gap classes still always ask: an unevidenced premise (the fix is a read only the operator can vouch for), a misread operator quote, and a scope change. `--non-interactive` is untouched β€” its short-circuit still edits nothing. Rationale: `agent-cmd/no-user-prompts`, applied to plan-task's own fix loop. ## v0.169.5 diff --git a/commands/execute-task.md b/commands/execute-task.md index 408c8bba..95a54125 100644 --- a/commands/execute-task.md +++ b/commands/execute-task.md @@ -204,7 +204,7 @@ Task lifecycle: 1. `/vault-cli:create-task` β€” capture (lenient) 2. `/vault-cli:work-on-task` β€” orient (status + guides + daily note), then auto-chain plan β†’ execute (interactive) -3. `/vault-cli:plan-task` β€” sharpen (5 hard gates); never flips phase +3. `/vault-cli:plan-task` β€” sharpen (9 hard gates); never flips phase 4. **`/vault-cli:execute-task`** β€” the sole gate; flips planning β†’ execution + prints first subtask + DoD reminder β€” this command 5. Start work β€” while working, use any of: - `/vault-cli:update-task` β€” log completed work, sync to daily note / parent goal diff --git a/commands/plan-task.md b/commands/plan-task.md index 880149d1..e4c4ee47 100644 --- a/commands/plan-task.md +++ b/commands/plan-task.md @@ -18,7 +18,7 @@ Under NO-ASK, every point that would ask instead **reports and stops**: - **Step 6 gaps** β†’ do not enter the fix loop, do not `Edit` the task from assumed answers. Skip to step 7 and exit on the `⚠` branch, listing each unresolved gap as a bullet phrased as the question you would have asked, so the operator can answer it on resume. - **Step 5 soft KISS warning** β†’ print it as a note; it never blocks exit on its own. -Everything else is unchanged: entry-contract flips (step 3), the auditor run (step 4), and the five gate checks (step 5) all behave identically. A task whose gates pass clean produces the same `βœ… Plan ready` in both modes β€” NO-ASK only changes what happens to a task that has real gaps. +Everything else is unchanged: entry-contract flips (step 3), the auditor run (step 4), and the nine gate checks (step 5) all behave identically. A task whose gates pass clean produces the same `βœ… Plan ready` in both modes β€” NO-ASK only changes what happens to a task that has real gaps. ## When to use @@ -150,7 +150,7 @@ Ten checks beyond the auditor's general scoring β€” nine hard (any failure β†’ a - **Resolution steps are resolvable** β€” a subtask naming a *lookup, join or key* (*"resolve the gate to its store `item_id`"*, *"look up the record by X"*) must be checked against whether that key actually exists and is derivable **by the actor that will run it**. This is the one hard check that is about the plan's *premise* rather than its shape: a subtask can name a concrete verb, a concrete artifact and a concrete outcome and still be unsatisfiable by construction. Ask of each: *do the two records share a field, and does the caller hold it?* Observed 2026-09-21 β€” a subtask read *"the sweep resolves the gate it is about to publish to a store `item_id`"*. No such join existed: the store wrote `item_id` itself, the producer's dedup key was a hash of a value the manager never holds, and the third candidate field was documented as `` β€” two kinds of value in one field, so it could not anchor an identity either. The same session's e2e-verify subtask also named no procedure and no expected result. **Two unsatisfiable-by-construction subtasks in one task is the signal that this check is missing, not that the author was careless** β€” and the cost is paid at execution, where the premise has already collapsed and the task file needs revising mid-flight. Flag β†’ a step 6 gap, applied or asked per its classification there. - **Premise matches the ask** β€” when `# Impact` quotes the operator verbatim, re-read the quote against Summary + Success Criteria. Flag when the quote admits more than one reading, or when an SC/constraint excludes a plausible reading of it (e.g. *"must NOT be named X"* when the quote says X). A plan can score 9/10 on shape and still build the wrong thing β€” the task writer's interpretation is not the operator's words. Observed 2026-09-23: *"add a open skill"* was read as an open-items skill (name `open` banned); the operator meant moving `/open` β†’ `/supervisor:open` β€” found only after release. Flag β†’ a step 6 fork, never a one-fix gap (the operator's own words are the decision), quoting both readings. -- **Empirical premises are evidenced** β€” a `# Impact` or Summary asserting a fact about the running system (*"nothing else reads this DB"*, *"the data is disposable"*, *"X is unused"*, *"the functional window is one minute"*) is a premise the plan rests on, and the shape checks never reach it. This is the *system* sibling of the premise check above: that one compares the operator's quote to the plan, this one asks whether the plan's own factual premise is evidenced in the file. Ask of each: *does the task name the read that establishes this β€” a query, a log line, a config?* Absent it, flag β†’ a step 6 fork (unevidenced premise β€” see Β§ 6 *Two gaps always ask*); apply only when the read is yours to run. Observed: a task claimed *"the data is disposable… nothing else reads this DB"* and was audited 9/10 three times; the claim was false, and the change was built, merged and deployed, then reverted. +- **Empirical premises are evidenced** β€” a `# Impact` or Summary asserting a fact about the running system (*"nothing else reads this DB"*, *"the data is disposable"*, *"X is unused"*, *"the functional window is one minute"*) is a premise the plan rests on, and the shape checks never reach it. This is the *system* sibling of the premise check above: that one compares the operator's quote to the plan, this one asks whether the plan's own factual premise is evidenced in the file. Ask of each: *does the task name the read that establishes this β€” a query, a log line, a config?* Absent it, flag β†’ a step 6 fork (unevidenced premise β€” see Β§ 6 *Three gaps always ask*); apply only when the read is yours to run. Observed: a task claimed *"the data is disposable… nothing else reads this DB"* and was audited 9/10 three times; the claim was false, and the change was built, merged and deployed, then reverted. - **Steps are runnable by the actor that owns the task** β€” a criterion or subtask naming a command the executing session's **tier** may not run is unsatisfiable by construction, however concrete it reads. `claude-supervisor`'s tiers are the common case: `/supervisor:open` and the `manager-*` / `fleet-*` verbs are manager-only, and a worker session must not run them. This is the *authority* sibling of the premise check above β€” that one asks whether the actor holds the **key**, this one whether it holds the **right**. Ask of each named command: *may the session that owns this task run it β€” and if not, does the criterion name who does?* Observed 2026-09-30 β€” an SC required *"one live run shows both halves … read from that session's own output"*. The command was `/supervisor:open --flagged`, the task was worker-anchored, and the criterion was satisfiable only after the run was routed to a manager session. The plan had passed every shape check. Flag β†’ a step 6 gap, applied or asked per its classification there. **Routing a step to another tier has the same test, in reverse.** A step re-assigned to a manager must be one the manager contract permits β€” a manager never performs a row's work (no verification, no investigation) and never spawns `role: manager`. A worker's own SC probe stays the worker's; when it cannot run it, route it to the operator, not up the tier. Observed 2026-10-10: SC probes routed to a topic manager were declined at execution, forcing a re-ask. @@ -198,7 +198,7 @@ Only findings the auditor labelled `[template-level]` are verdict-coverable. An - **One-fix gap β†’ apply it, then report it.** The gap carries a stated fix and no genuine alternative β€” a missing `# Definition of Done`, a baseline to rebase, a window to pin, a criterion whose evidence shape is wrong. Apply the stated fix via `Edit` (re-running the step 2 ownership gate first) and print `πŸ”§ Applied: β€” `. Do not open an `AskUserQuestion` for it. - **Real fork β†’ ask.** Two or more genuinely defensible outcomes, or a decision only the operator can make β€” scope, a production-touching step, which of two homes a rule belongs in. These are what the rules below are for. -**The distinguishing test: write the second answer down before you ask.** State the alternative you would have offered. Cannot name one you would defend β†’ one-fix gap, apply it. Can name two β†’ fork, ask. A gap whose options would read *"(Recommended) do X Β· 2. do nothing"* is a one-fix gap wearing a menu: "do nothing" is a refusal to decide, not an alternative, and an operator who picks option 1 every time is telling you the menu should not have been built. This is `agent-cmd/no-user-prompts` applied to a case it did not originally name β€” plan-task's own fix loop is the prompt. +**The distinguishing test: write the second answer down before you ask.** State the alternative you would have offered. Cannot name one you would defend β†’ one-fix gap, apply it. Can name two β†’ fork, ask. A gap whose options would read *"(Recommended) do X Β· 2. do nothing"* is a one-fix gap wearing a menu: "do nothing" is a refusal to decide, not an alternative, and an operator who picks option 1 every time is telling you the menu should not have been built. This is `agent-cmd/no-user-prompts` reaching a case its own trigger already names β€” it covers `commands/**/*.md`, and plan-task's own fix loop is the prompt. **Report; never silently rewrite.** The classification removes the *question*, never the *record*. Print every applied fix, and when one changed a criterion's claim, record it in the task's `# Progress` β€” the operator must be able to see what plan-task decided on their behalf and reverse it. A silent rewrite is the failure mode this trades against; do not reintroduce it for brevity.