From eb440e4fa82f69611fcdb06fc4721389775b6b1b Mon Sep 17 00:00:00 2001 From: "melodic-automation-lanes[bot]" <337595936+melodic-automation-lanes[bot]@users.noreply.github.com> Date: Sun, 1 Nov 2026 06:56:59 +0000 Subject: [PATCH] Resolve issue #8466 Lane-Activity: work-resolve-issue/resolve-issue --- .../work-resolve-issue-issue-8466-b52e8d04.md | 7 +++ .../work-resolve-issue-issue-8466-3f9c1a7e.md | 11 ++++ .../work-resolve-issue-issue-8466-6a1d73c9.md | 7 +++ .../async-round-presentation/README.md | 53 +++++++++++++++++++ .../recommendation-basis/CHANGELOG.md | 9 ++++ docs/contracts/recommendation-basis/README.md | 15 +++--- docs/plugin-philosophy.md | 1 + plugins/discovery/skills/explore/SKILL.md | 2 + plugins/discovery/skills/research/SKILL.md | 2 + plugins/planning/skills/interview/SKILL.md | 2 +- .../planning/skills/interview/context/loop.md | 2 +- .../skills/interview/evals/evals.json | 18 ++++++- .../planning/tests/interview-defenses.test.sh | 18 ++++--- plugins/review/skills/fanout/SKILL.md | 4 ++ 14 files changed, 133 insertions(+), 18 deletions(-) create mode 100644 .changes/discovery/work-resolve-issue-issue-8466-b52e8d04.md create mode 100644 .changes/planning/work-resolve-issue-issue-8466-3f9c1a7e.md create mode 100644 .changes/review/work-resolve-issue-issue-8466-6a1d73c9.md create mode 100644 docs/contracts/async-round-presentation/README.md diff --git a/.changes/discovery/work-resolve-issue-issue-8466-b52e8d04.md b/.changes/discovery/work-resolve-issue-issue-8466-b52e8d04.md new file mode 100644 index 00000000000..b537220a194 --- /dev/null +++ b/.changes/discovery/work-resolve-issue-issue-8466-b52e8d04.md @@ -0,0 +1,7 @@ +--- +bump: patch +--- + +### Changed + +- `/discovery:research` and `/discovery:explore`: when dispatched work returns after open questions were shown and before the user answers, the next message that presents anything ends with every open question in full, per `docs/contracts/async-round-presentation/README.md` in the marketplace. diff --git a/.changes/planning/work-resolve-issue-issue-8466-3f9c1a7e.md b/.changes/planning/work-resolve-issue-issue-8466-3f9c1a7e.md new file mode 100644 index 00000000000..52f70db0b49 --- /dev/null +++ b/.changes/planning/work-resolve-issue-issue-8466-3f9c1a7e.md @@ -0,0 +1,11 @@ +--- +bump: minor +--- + +### Changed + +- `/planning:interview`: when a dispatched agent's or background task's output lands after a round was shown, the next message that presents a question ends with every open question in full, the changed one marked, in place of a one-line pointer for the untouched questions. Questions that do not wait on a pending result are still asked without waiting. The rule is `docs/contracts/async-round-presentation/README.md` in the marketplace. + +### Added + +- Eval case `late-return-restates-every-open-question-in-full`. diff --git a/.changes/review/work-resolve-issue-issue-8466-6a1d73c9.md b/.changes/review/work-resolve-issue-issue-8466-6a1d73c9.md new file mode 100644 index 00000000000..13f0d9c65bd --- /dev/null +++ b/.changes/review/work-resolve-issue-issue-8466-6a1d73c9.md @@ -0,0 +1,7 @@ +--- +bump: patch +--- + +### Changed + +- `/review:fanout`: when a surface returns after findings were presented for a decision and before the user answers, the next message that presents findings ends with every open finding in full, per `docs/contracts/async-round-presentation/README.md` in the marketplace. diff --git a/docs/contracts/async-round-presentation/README.md b/docs/contracts/async-round-presentation/README.md new file mode 100644 index 00000000000..0cf6a6ad0df --- /dev/null +++ b/docs/contracts/async-round-presentation/README.md @@ -0,0 +1,53 @@ +# Async round presentation: the latest message holds every open item + +A skill can show the user open items (questions, decisions, findings that wait on a choice) and +also dispatch async work: sub-agents, background tasks, lookups. That work can return after the +items are shown and before the user answers. This contract owns what the next message shows. + +## The rule + +When a skill has presented open items and dispatched work returns before the user answers, the +next message that presents anything ends with one block that restates every open item in full: +the question, its options, and the recommendation with its basis. The block marks each item the +returned work changed. + +- **An item that depends on a pending result may be held.** It stays out of the block until the + result lands; the block may name it in one line as waiting. +- **A status line while waiting is fine.** A message that only reports progress (a return touched + no open item, work is still running) presents nothing, so it needs no block. +- **A partial set is never the last word.** A message that shows the changed item in full and + points back to an earlier message for the rest breaks this rule. + +## Why + +The user reads the latest message. A pointer to an earlier block makes them scroll back to +rebuild the open set, and with several returns that set spreads across several messages, so an +item can go unanswered because it was never in view. A full restate costs length; the cost of a +pointer is an answer given against a set the user could not see whole. + +## What this does not require + +The rule does not hold a round until every dispatch drains. Items that do not depend on pending +work are still asked at once, so the session is not serialized behind its slowest lookup. Only +the items that need a pending result wait for it. + +## Who is bound + +Any skill that presents open items for the user's answer while its own dispatched work can still +return. A bound skill cites this file by its repository path, `docs/contracts/async-round-presentation/README.md` +in the marketplace, rather than restating a narrower rule. Adopters: + +- `/planning:interview`: `plugins/planning/skills/interview/context/loop.md` "Out-of-band drift", + with its pointer in the skill's `SKILL.md`. +- `/discovery:research`: `plugins/discovery/skills/research/SKILL.md` "Outcome gate (run before + presenting)". +- `/discovery:explore`: `plugins/discovery/skills/explore/SKILL.md` "Outcome gate (before + EXPLORE.md handoff)". +- `/review:fanout`: `plugins/review/skills/fanout/SKILL.md` "Step 2: Normalize". +- The [recommendation-basis](../recommendation-basis/README.md) re-emit shape defers to this + contract when dispatched work moved a recommendation. + +## Format changes + +This contract carries no version. A change to the rule updates every adopter in the same pull +request. diff --git a/docs/contracts/recommendation-basis/CHANGELOG.md b/docs/contracts/recommendation-basis/CHANGELOG.md index 7f771282b0c..2f5a271bfe7 100644 --- a/docs/contracts/recommendation-basis/CHANGELOG.md +++ b/docs/contracts/recommendation-basis/CHANGELOG.md @@ -4,6 +4,15 @@ Notable changes to the recommendation-basis contract (SemVer). Changing the grou label's values, or the re-emit shape is a major bump; additive guidance is a minor bump; docs-only clarification is a patch. +## [3.0.0] - 2026-11-01 + +Major: the re-emit section defers to the async-round-presentation contract when the changed +recommendation comes from dispatched work that returned while open items wait on the user. That +message ends with every open item in full, the moved one marked, in place of the one-line list of +unchanged recommendations. The re-emit shape for any other evidence is unchanged. The +`/planning:interview` out-of-band drift rule carries the change; the plugin-shipped copies still +state the one-line form and have not yet been updated. + ## [2.0.1] - 2026-10-10 Patch: the conforming-surfaces table folds the `/discipline:do-your-research-deep` row into diff --git a/docs/contracts/recommendation-basis/README.md b/docs/contracts/recommendation-basis/README.md index 3316c1d7476..8f649b70a52 100644 --- a/docs/contracts/recommendation-basis/README.md +++ b/docs/contracts/recommendation-basis/README.md @@ -98,13 +98,14 @@ and may ground a code edit, but its label carries the flag, When evidence changes a recommendation the user still has pending, restate it as **old → new → why**: the superseded recommendation named as superseded, the replacement in its own outcome (a `Basis:`-labeled recommendation, or the withheld open question), and the evidence that moved it. Re-state only the recommendations that moved; name the rest as -unchanged in one line. A pending recommendation the session has disproved is worse than none, -because the user decides against it. - -The shape is adopted from `/planning:interview`'s out-of-band drift rule, outcome 2 and "Re-present -narrowly" in -[`plugins/planning/skills/interview/context/loop.md`](../../../plugins/planning/skills/interview/context/loop.md) -(lines 267-270). That file owns the wording; this doc generalizes it beyond an interview round. +unchanged in one line. When the evidence is dispatched work that returned while open items wait on +the user, [async-round-presentation](../async-round-presentation/README.md) governs instead: the +message ends with every open item in full, the moved one marked. A pending recommendation the +session has disproved is worse than none, because the user decides against it. + +The shape is adopted from `/planning:interview`'s out-of-band drift rule, outcome 2, in +[`plugins/planning/skills/interview/context/loop.md`](../../../plugins/planning/skills/interview/context/loop.md). +That file owns the wording; this doc generalizes it beyond an interview round. ## Routing diff --git a/docs/plugin-philosophy.md b/docs/plugin-philosophy.md index 317525efa2d..ec4c12a68ac 100644 --- a/docs/plugin-philosophy.md +++ b/docs/plugin-philosophy.md @@ -888,6 +888,7 @@ doc before a second plugin adopts it. Fleet audits check conformance per row. | Retired-convention detection and cleanup (manifest + shared helper) | [`docs/contracts/retired-conventions/`](contracts/retired-conventions/README.md) | | Recommendation basis: grounding bar, `Basis:` label, and old → new → why re-statement | [`docs/contracts/recommendation-basis/`](contracts/recommendation-basis/README.md) | | Authoring formats: acceptance-criteria format and diagram dialect by artifact kind, read by `/planning:interview`, `/planning:prd`, and `/planning:design` | [`docs/contracts/authoring-formats/`](contracts/authoring-formats/README.md) | +| Async round presentation: the latest message restates every open item in full after dispatched work returns | [`docs/contracts/async-round-presentation/`](contracts/async-round-presentation/README.md) | ## Cross-platform contract diff --git a/plugins/discovery/skills/explore/SKILL.md b/plugins/discovery/skills/explore/SKILL.md index b139b2a6464..31306fa85e9 100644 --- a/plugins/discovery/skills/explore/SKILL.md +++ b/plugins/discovery/skills/explore/SKILL.md @@ -162,6 +162,8 @@ Before writing EXPLORE.md (or returning the summary), check the artifact against - **Paths are machine-agnostic**. No finding in the artifact echoes an absolute machine path (notably the project root gathered above); every path it records is written relative to the repo root, or, when there is no repo root, to the current working directory, so the handoff stays portable across machines. - **Open questions handed off, never dropped**. Surfaced to the user inline, or carried in the payload's `open_questions` for the parent to surface under dispatch. Each with a recommended default. +When dispatched work returns after you presented open questions and before the user answers, the next message that presents anything ends with every open question in full: `docs/contracts/async-round-presentation/README.md` in the marketplace. + ## Scope Explore the following: $ARGUMENTS diff --git a/plugins/discovery/skills/research/SKILL.md b/plugins/discovery/skills/research/SKILL.md index 06796256a76..509baa9ccde 100644 --- a/plugins/discovery/skills/research/SKILL.md +++ b/plugins/discovery/skills/research/SKILL.md @@ -87,6 +87,8 @@ Each criterion is binary. **Any FAIL returns to the named phase (a verifier-owne **A claim that cannot pass the gate is a Gap, not a finding**, never laundered into the answer. Report the gate result (pass, or which criterion failed and what you re-ran); rework bounds: "Effort, source breadth". Tier-3 reconciliation: "Reconciling sources at the gate" below. +When dispatched work returns after you presented open questions and before the user answers, the next message that presents anything ends with every open item in full: `docs/contracts/async-round-presentation/README.md` in the marketplace. + ## Topic Research the following topic: $ARGUMENTS diff --git a/plugins/planning/skills/interview/SKILL.md b/plugins/planning/skills/interview/SKILL.md index 61c23b1dda5..736680d5071 100644 --- a/plugins/planning/skills/interview/SKILL.md +++ b/plugins/planning/skills/interview/SKILL.md @@ -192,7 +192,7 @@ Alternatives: **Register at ask-time; a reply that does not answer is not an answer.** The moment a round is asked, before any reply, write one `open` row per question into the ledger's open-question register. Then, after EVERY user reply and before doing anything else, check the reply against the register's `open` rows and restate any it did not address, in one line, even when the reply changed the subject entirely. Conversational drift is never consent, and the register, not the transcript, which a compaction can empty, is the authority. The one exception is the acceptance-criteria coverage prompt, which gets no row even inside a round while every real question beside it does ([`context/coverage-prompt.md`](context/coverage-prompt.md)). Row shape, statuses, and the drift-restate wording: [`context/loop.md`](context/loop.md) "The open-question register". -**Out-of-band output gets the same check, keyed on relevance.** A round can be overtaken by content the user did not write: a dispatched sub-agent's return, a background task notification, a team report, a Monitor firing. That is the ordinary consequence of not blocking the round. Check it against the `open` and `superseded-by-plan` rows (a superseded row is restated, never resolved by the return): a return touching nothing gets one line and the round stands, a return that contradicts an asked question's recommendation forces a restate naming the superseded recommendation, and a return that answers an open row from the environment resolves it. Re-present narrowly (a one-line pointer for the untouched, the full shape only for the row that moved), never hold the round, and never depend on being woken. The floor is the next user reply. Outcomes, shape, and the floor: [`context/loop.md`](context/loop.md) "Out-of-band drift". +**Out-of-band output gets the same check, keyed on relevance.** A round can be overtaken by content the user did not write: a dispatched sub-agent's return, a background task notification, a team report, a Monitor firing. That is the ordinary consequence of not blocking the round. Check it against the `open` and `superseded-by-plan` rows (a superseded row is restated, never resolved by the return): a return touching nothing gets one line and the round stands, a return that contradicts an asked question's recommendation forces a restate naming the superseded recommendation, and a return that answers an open row from the environment resolves it. When a row moved, the next message that presents a question restates every open row in full, marking the moved one (`docs/contracts/async-round-presentation/README.md` in the marketplace); never hold the round, and never depend on being woken. The floor is the next user reply. Outcomes, shape, and the floor: [`context/loop.md`](context/loop.md) "Out-of-band drift". **Rounds fire at phase boundaries.** When reached from inside another workflow's phase, emit the whole open set where the caller hands over, not partway through its phase; a mid-phase blocking question is the exception and states its justification in one line. Rationale: [`context/loop.md`](context/loop.md) "Where a round may fire". diff --git a/plugins/planning/skills/interview/context/loop.md b/plugins/planning/skills/interview/context/loop.md index 76ed4dff64d..0398b0693f4 100644 --- a/plugins/planning/skills/interview/context/loop.md +++ b/plugins/planning/skills/interview/context/loop.md @@ -394,7 +394,7 @@ The drift check above fires on a user reply. A round can also be overtaken by ou 2. **It contradicts a recommendation under a question already asked.** Restate that question, naming the superseded recommendation as superseded and giving the replacement its own basis. A recommendation the session has since disproved is worse than no recommendation, because the user is answering against it. 3. **It answers an open row from the environment.** Resolve it and STATE the answer; do not leave it standing as a question. "Facts are yours; decisions are the user's" does not stop applying because the fact arrived late. -**Re-present narrowly.** One line carrying the untouched questions (*"Still open, unchanged: Q5, Q6, Q8"*), and the full question shape ONLY for the row that actually moved. Never re-print the whole round: with several lookups in flight that is several full re-prints under one open set, which buries the round it is trying to surface. The user answers by `Q` against a block they can scroll to; restoring visibility is not worth the noise, and the changed recommendation is the part they cannot recover by scrolling. +**Restate every open row in full.** When outcome 2 or 3 moved a row, the next message that presents a question before the user replies ends with one block holding every `open` and `superseded-by-plan` row in the full question shape (Context, recommendation with `Basis:`, alternatives), with the row that moved marked as changed. A row held for a pending result may be named in one line as waiting. The user reads the latest message: a one-line pointer to an earlier block sends them scrolling to rebuild the open set, and with several returns that set spreads across messages. Outcome 1 stays one line, because it presents nothing. Rule: `docs/contracts/async-round-presentation/README.md` in the marketplace. **This does not hold the round.** The scoped barrier stays exactly as it is (SKILL.md "In-flight lookups"): of the frontier questions, those that need a pending result are held and the others are asked, and a question off the frontier keeps waiting on its unanswered input. Holding a round until every dispatch drains would trade this failure for a serialized interview. diff --git a/plugins/planning/skills/interview/evals/evals.json b/plugins/planning/skills/interview/evals/evals.json index 69e1be6dbf6..114ee417282 100644 --- a/plugins/planning/skills/interview/evals/evals.json +++ b/plugins/planning/skills/interview/evals/evals.json @@ -350,13 +350,13 @@ "id": 25, "name": "out-of-band-return-supersedes-before-the-reply", "prompt": "/planning:interview me — I want to add rate limiting to our public REST API.\n\n[the first round is asked as Q1 (limit scope: per API key or per IP), Q2 (response when throttled), Q3 (where counters live — recommended: in-process memory, because the survey found a single API instance), and Q4 (whether internal callers are exempt). A sub-agent is dispatched to confirm the deployment topology; Q5 (counter consistency across instances) is downstream of that lookup and is held. Before the user replies, the sub-agent's return is queued: the production manifest runs the API as three replicas behind a load balancer. In the same turn, the user replies:] Q1 per API key. Q3 go with your recommendation. Haven't thought about the others yet.", - "expected_output": "The queued sub-agent return is processed before the reply, against the register as it stood before the reply, while Q3 was still `open`. The return contradicts Q3's recommendation, so the skill restates Q3, names in-process memory as the superseded recommendation, and gives a replacement (a shared store such as Redis) with its own basis in the three-replica manifest. The user's \"go with your recommendation\" answered the superseded recommendation, so it is revalidated against the replacement rather than recorded as settled. Q1 is recorded `answered` as per API key. Q2 and Q4 get one line naming them as still open and unchanged, and only Q3 is re-presented in full. Q5 was downstream of the lookup and is now asked; nothing else waited on the lookup.", + "expected_output": "The queued sub-agent return is processed before the reply, against the register as it stood before the reply, while Q3 was still `open`. The return contradicts Q3's recommendation, so the skill restates Q3, names in-process memory as the superseded recommendation, and gives a replacement (a shared store such as Redis) with its own basis in the three-replica manifest. The user's \"go with your recommendation\" answered the superseded recommendation, so it is revalidated against the replacement rather than recorded as settled. Q1 is recorded `answered` as per API key. The response ends with one block restating every open question in full (Q2, Q3, Q4 and Q5), with Q3 marked as changed. Q5 was downstream of the lookup and is now asked; nothing else waited on the lookup.", "files": [], "expectations": [ "The queued return is evaluated before the same-turn reply: Q3 is NOT recorded `answered` with in-process memory, and the response does not treat the user's \"go with your recommendation\" as settling Q3", "The restated Q3 names in-process memory as superseded and gives the replacement recommendation its own basis drawn from the three-replica manifest", "The user's Q3 answer is revalidated against the replacement recommendation, for example by asking whether the recommendation they accepted still stands now that it has changed", - "Re-presentation is narrow: Q2 and Q4 appear as a one-line pointer (still open, unchanged), and only Q3 is re-printed in full; the round is not re-printed as a whole", + "The response ends with one block restating every open question in full, each with its Context, recommendation with Basis and alternatives, and Q3 is marked as the changed one; Q2 and Q4 are not reduced to a one-line pointer", "Q1 is recorded `answered` (per API key) from the reply", "Asking Q2 and Q4 was not held for the lookup, and Q5, which was downstream of it, is asked once the return lands" ] @@ -605,6 +605,20 @@ "No PLAN.md Brief, ADR or glossary entry is written", "The next step is recommended by name and no other skill is invoked" ] + }, + { + "id": 43, + "name": "late-return-restates-every-open-question-in-full", + "prompt": "/planning:interview me Add a nightly export of order data to the analytics warehouse.\n\n[Session state: terminal lane. Round 1's Q1 to Q3 are `answered`. Round 2 was shown as Q4 (export format; recommended: Parquet, because the warehouse loader reads Parquet), Q5 (how far back the first export reaches; recommended: 90 days, from the retention setting the survey read), Q6 (whether refunds are exported as negative rows or a separate table; recommended: negative rows, from the existing finance report's shape) and Q7 (the export window; recommended: 02:00 UTC, because the survey found no other nightly job). A sub-agent was dispatched to list the scheduled jobs on the production cluster; no question waits on it. Before the user replies, its return lands: a database backup job runs on that cluster from 01:30 to 03:00 UTC every night.]", + "expected_output": "The return contradicts Q7's recommendation, so Q7 is restated with 02:00 UTC named as superseded and a replacement window outside the 01:30 to 03:00 UTC backup, with its basis in the sub-agent's job list. The message ends with one block restating every open question in full: Q4, Q5, Q6 and Q7, each with its Context, its recommendation with `Basis:` and its alternatives, and Q7 is marked as the changed one. Q4, Q5 and Q6 are not reduced to a one-line pointer such as \"still open, unchanged\", and no question is recorded as answered, since the user has not replied.", + "files": [], + "expectations": [ + "Q7 is restated with 02:00 UTC named as the superseded recommendation and a replacement window outside 01:30 to 03:00 UTC whose basis is the backup job from the sub-agent's return", + "The final block of the message restates Q4, Q5, Q6 and Q7, each with its Context, its recommendation with a Basis line, and its alternatives", + "Q7 is marked in that block as the question whose recommendation changed", + "Q4, Q5 and Q6 are not collapsed into a one-line pointer to the earlier round", + "No question is recorded as answered, because the user has not replied" + ] } ] } diff --git a/plugins/planning/tests/interview-defenses.test.sh b/plugins/planning/tests/interview-defenses.test.sh index e6508c35bbb..9e3fbb20cc5 100755 --- a/plugins/planning/tests/interview-defenses.test.sh +++ b/plugins/planning/tests/interview-defenses.test.sh @@ -494,11 +494,12 @@ done # The round-close sentence words the closing probe "something else", never "anything else" (#11191); it changes how the probe is phrased, resolves no row, and the no-silent-resolve line is byte-identical # Question shape (#7475): every question opens with a referenced Context block, alternatives carry gain and cost, open questions are restated in full, a terminology matrix is agreed as a registered question, and codes show with their plain names (Checked against names constraints in words); each adds a requirement or a registered question, resolves no row, and the no-silent-resolve rule, the constraint-ledger rows and their re-check, and the late-commitment numbering are unchanged # Review fixes on the #7475 question shape: the terminology matrix is the interview's first item and is not repeated at the top of a round, a user-stated claim cites its round, the page puts facts in context, and Withheld replaces five parts; each adds or clarifies a requirement, resolves no row, and the no-silent-resolve rule is unchanged +# Full restate after an out-of-band return (#8466): the next message that presents a question ends with every open row in full, the moved row marked, in place of a one-line pointer; case 25 grades that restate and case 43 adds its single-return twin; neither resolves a row the user did not answer, and the no-silent-resolve, gap and blocker defenses are unchanged pin_section "SKILL.md Stance section is unchanged (the in-round no-silent-resolve rule lives here)" \ "$SKILL" \ "## Stance: supportive, depth-first, opinionated" \ "## The interview loop" \ - "4f2aaa25e1c94b0575a5fd5480816ba7b861fae88a93b5834e2e2892bc797b6e" + "564de96c226257dc41ed5c949313fea118499f2f575ebe728e08b6bb811c9db4" pin_section "SKILL.md interview-loop preamble is unchanged (it governs every step below it)" \ "$SKILL" \ "## The interview loop" \ @@ -528,11 +529,12 @@ pin_section "SKILL.md interview-loop preamble is unchanged (it governs every ste # Re-pinned for #11039: an own answer now withdraws a replaced recommendation's parts like an alternative; an accepted or hedged row with an unticked part still exports open, so the register gate still holds it. # Adds the read-back of an answer that departs from the recommendation, or arrives dictated or unclear (#11187); its row stays open until the user confirms, so it resolves no row on the user's behalf, and the gap and blocker bindings are unchanged # Both sections only gain the page-surface Step 4 command with --surface and a pointer to surface.md Wrap-up; every gate line stays, and --surface adds checks, so neither defense is weakened. +# Full restate after an out-of-band return (#8466): the next message that presents a question ends with every open row in full, the moved row marked, in place of a one-line pointer; case 25 grades that restate and case 43 adds its single-return twin; neither resolves a row the user did not answer, and the no-silent-resolve, gap and blocker defenses are unchanged pin_section "loop.md open-question register section is unchanged (it binds gaps and blockers to the gate)" \ "$LOOP" \ "## The open-question register" \ "## Step 3. Recognize the stop condition" \ - "83310a75a7a3041b1f15df32b3edafbf7f6bfdd988197b51dfb99b2953430880" + "fcd6bc75e1faced60a0c10925501c43777d1d3685ca24497f4a07e129c8f182f" # loop.md carries TWINS of two SKILL.md lines that are byte-pinned there: the # confirmation-gate `lock` line ("`lock` confirms the problem … its STOP-on-gap rule still applies") in # Step 3, and the `USER-RESERVED` arbiter guidance in Step 4. A twin with no pin is a @@ -771,8 +773,9 @@ pin_case_digest "eval case B is unchanged (no criterion added that licenses the # Re-pin for #8293: case 39 (sweep-decision-item-not-settled-by-unsupported-fact) requires an undecided `decision` sweep item to become an `open` register row, asked, never a fact or a captured assumption, and blocks the lock while it is open, so it agrees with cases 15 and 16. # Re-pin for #8365: case 40 (reused-address-stated-literally-and-owner-confirmed) requires an unstated value to become an `open` register row asking the user to confirm the literal, and blocks the lock while it is open; it licenses no silent capture or fudged gap, so it agrees with cases 15 and 16. # Adds case 41 product-first-reply-is-one-question-with-strong-answer and case 42 product-run-ends-in-six-item-verdict, which retires open rows as deferred and runs the register gate before the verdict; neither licenses a silent capture or fudged gap, so cases 15 and 16 still hold +# Full restate after an out-of-band return (#8466): the next message that presents a question ends with every open row in full, the moved row marked, in place of a one-line pointer; case 25 grades that restate and case 43 adds its single-return twin; neither resolves a row the user did not answer, and the no-silent-resolve, gap and blocker defenses are unchanged pin_case_set "the eval-case roster is unchanged (no sibling case added that contradicts 15 or 16)" \ - "68cb522d7950419009b3e7a1718c7c8100f509bc498b437fbbb1d573180410c0" + "c6aa3fa28b7b0ef824fc10eec0c33f780735f6a865c5156986ef103190148e7a" # The roster pin catches a case ADDED. It cannot see an existing sibling REWRITTEN in # place: case 3 kept its name `lock-mode-does-not-fudge-gap` while its body was rewritten @@ -801,9 +804,10 @@ pin_case_digest "case 8 still asks decisions and only looks up facts" \ pin_case_digest "case 12 still refuses to read drift as consent" \ "open-question-survives-an-unrelated-reply" \ "e45fe64a00cf78e6dd089837e81c52d8a0947ccf924be6472c6f47eeadcd5942" +# Full restate after an out-of-band return (#8466): the next message that presents a question ends with every open row in full, the moved row marked, in place of a one-line pointer; case 25 grades that restate and case 43 adds its single-return twin; neither resolves a row the user did not answer, and the no-silent-resolve, gap and blocker defenses are unchanged pin_case_digest "case 25 still refuses to settle a row on a superseded recommendation" \ "out-of-band-return-supersedes-before-the-reply" \ - "60964b5db1076b8be673e75b7a0bd5c247b6e87fa62c5da0b34531deca7d22f0" + "186a511bef86bbc7fe9679359c350c39111c3eab0c8761eb8f98196882f40b9f" pin_case_digest "case 1 still resolves codebase-answerable questions without asking, and only those" \ "relentless-me-mode-frontier-rounds" \ "5e38782253a410890320cddc3c5c44d71ab64666bb596bc954167f1447059bdd" @@ -886,7 +890,7 @@ pin "loop.md binds \`lock\`'s surfaced gap into the register" "$LOOP" \ # a sub-agent return, a background notification, a team report — falls outside it and # goes un-restated. These pins are the out-of-band rule's own defense: the trigger, # relevance vs arrival, the superseded-recommendation outcome, the environment-resolve -# outcome, the narrow re-presentation, the hold-refusal, the floor, the same-turn +# outcome, the full restate, the hold-refusal, the floor, the same-turn # queued-output-first order, and the SKILL.md pointer. Drop any one and the rule # reads as advisory. pin "loop.md fires the out-of-band check on non-user content" "$LOOP" \ @@ -897,8 +901,8 @@ pin "loop.md names a contradicted recommendation as superseded" "$LOOP" \ "naming the superseded recommendation as superseded" pin "loop.md resolves an environment-answered open row" "$LOOP" \ "Resolve it and STATE the answer" -pin "loop.md restates only the moved row, never the whole round" "$LOOP" \ - "**Re-present narrowly.**" +pin "loop.md restates every open row in full after out-of-band output" "$LOOP" \ + "**Restate every open row in full.**" pin "loop.md refuses to hold the round for a pending dispatch" "$LOOP" \ "**This does not hold the round.**" pin "loop.md floors the out-of-band restate on the next user reply" "$LOOP" \ diff --git a/plugins/review/skills/fanout/SKILL.md b/plugins/review/skills/fanout/SKILL.md index 63b18661951..2e07aaee140 100644 --- a/plugins/review/skills/fanout/SKILL.md +++ b/plugins/review/skills/fanout/SKILL.md @@ -138,6 +138,10 @@ what the cited line shows. The finding itself stays in `## Findings` at its demo section lists demotions only; no finding leaves the report, so nothing is listed as dropped. With no demotion, the section reads `none`. The fix action does not parse it. +When a surface returns after findings were presented for a decision and before the user answers, +the next message that presents findings ends with every open finding in full: +`docs/contracts/async-round-presentation/README.md` in the marketplace. + ## Step 3: Persist findings Run the self-ignore guard ("Shared inputs"), then write the ranked report into the resolved findings location as `-.md`, with `` sanitized to `[a-z0-9._-]`. The timestamp format is in "Shared inputs". Relativize machine paths BEFORE writing. Findings cite `file:line` repo-relative only. File-name collision rule and file shape contract: [`${CLAUDE_PLUGIN_ROOT}/reference/findings-file-shape.md`](../../reference/findings-file-shape.md) "Findings-writer contract" and "Findings-file shape".