Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changes/discovery/work-resolve-issue-issue-8466-b52e8d04.md
Original file line number Diff line number Diff line change
@@ -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.
11 changes: 11 additions & 0 deletions .changes/planning/work-resolve-issue-issue-8466-3f9c1a7e.md
Original file line number Diff line number Diff line change
@@ -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`.
7 changes: 7 additions & 0 deletions .changes/review/work-resolve-issue-issue-8466-6a1d73c9.md
Original file line number Diff line number Diff line change
@@ -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.
53 changes: 53 additions & 0 deletions docs/contracts/async-round-presentation/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Async round presentation: the latest message holds every open item
Comment thread
kyle-sexton marked this conversation as resolved.
Resolved

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.
9 changes: 9 additions & 0 deletions docs/contracts/recommendation-basis/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
15 changes: 8 additions & 7 deletions docs/contracts/recommendation-basis/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Comment on lines +101 to +103

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This bumps the contract's re-emit shape to 3.0.0, but the plugin copies still give the old shape, so the shipped planning plugin contradicts itself.

This section's own "Plugin-shipped copies" rule (L118-125) says each adopting plugin must state the re-emit shape in its own shipped text, because installed plugins cannot read docs/. The canonical copy plugins/discipline/context/recommendation-basis.md and its 11 byte-identical copies were not updated. For example, plugins/planning/context/recommendation-basis.md:34-36 still says:

When evidence changes a pending recommendation, restate it as old → new → why … name the unchanged ones in one line.

It has no exception for dispatched work. /planning:interview points at that shipped copy as "the contract this plugin ships" (SKILL.md:205, and SKILL.md:201 for re-emitting). In-flight lookups are exactly the dispatched work that now needs a full restate, so the interview's two rules disagree: loop.md says restate every open row in full, the shipped contract says one line. The new 3.0.0 CHANGELOG entry admits the copies are stale. The drift check (check-cross-plugin-source-drift.sh) only compares the copies with each other, so CI will not catch this.

Fix: add the dispatched-work exception (with its absolute-URL pointer to async-round-presentation) to plugins/discipline/context/recommendation-basis.md and sync the copies listed in scripts/cross-plugin-source-registry.txt. Or, if this is a deliberate follow-up, track it in an issue and drop "have not yet been updated" from a released major entry.

claude-lane: claude-review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pr-unblock / address-feedback: UNCERTAIN, left open

Evidence: docs/contracts/recommendation-basis/README.md:100, plugins/discipline/context/recommendation-basis.md:34, plugins/planning/context/recommendation-basis.md:34, docs/contracts/recommendation-basis/CHANGELOG.md:13

The finding is accurate. The contract's re-emit section now hands dispatched work to async-round-presentation (README.md:100-103), and the CHANGELOG 3.0.0 entry calls that a re-emit shape change and says the plugin-shipped copies have not yet been updated (CHANGELOG.md:13-14). The canonical copy plugins/discipline/context/recommendation-basis.md:34-36 and its byte-identical copies in 12 other plugins (architecture, code-tidying, debugging, developer-experience, github, harness-ops, naming, planning, session-flow, source-control, user-experience, work-items) still give the one-line form with no dispatched-work exception. The README's own Plugin-shipped copies rule (README.md:120-125) requires the re-emit shape in each copy. The fix is to add the exception, with an absolute-URL pointer to async-round-presentation, to the canonical copy and sync all 13 files. This lane's path check refused every one of those 13 files as outside this thread's scope, so I made no edit. The other option the reviewer offers, filing a follow-up issue and rewording the CHANGELOG entry, needs a tracker item, which this lane cannot file. Left open for a person to choose between syncing the copies in this PR and deferring to a tracked issue.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pr-unblock / adjudicate-feedback: the finding stands

A fresh-context adjudicator read the review comment, the locations both sides cited and the code at eb440e4fa82f69611fcdb06fc4721389775b6b1b, without the fixing lane's reasoning, and found that the finding holds. The next address-feedback round fixes it; a round that leaves this thread open again ends the lane in a failing status.

Evidence: docs/contracts/recommendation-basis/README.md:100, plugins/discipline/context/recommendation-basis.md:34, plugins/planning/context/recommendation-basis.md:34, docs/contracts/recommendation-basis/CHANGELOG.md:13

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pr-fix lane: please fix this finding. Add the dispatched-work exception, with its absolute-URL pointer to docs/contracts/async-round-presentation/README.md, to plugins/discipline/context/recommendation-basis.md (re-emit text at lines 34-36), and sync every byte-identical copy listed in scripts/cross-plugin-source-registry.txt in this PR. Then drop "have not yet been updated" from the 3.0.0 entry in docs/contracts/recommendation-basis/CHANGELOG.md.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pr-unblock / address-feedback: UNCERTAIN, left open

Evidence: plugins/discipline/context/recommendation-basis.md:34, plugins/planning/context/recommendation-basis.md:34, docs/contracts/recommendation-basis/README.md:100, docs/contracts/recommendation-basis/CHANGELOG.md:13, scripts/cross-plugin-source-registry.txt:123

The direction asks for three edits. First, add the dispatched-work exception, with an absolute-URL pointer to the async-round-presentation contract, to the re-emit text of the canonical copy plugins/discipline/context/recommendation-basis.md (lines 34-36). Second, sync the byte-identical copies registered at scripts/cross-plugin-source-registry.txt:123: architecture, code-tidying, debugging, developer-experience, github, harness-ops, naming, planning, session-flow, source-control, user-experience and work-items. Third, drop 'have not yet been updated' from the 3.0.0 CHANGELOG entry. The finding still holds at this head: the canonical copy and the 12 others still state only the one-line form, and README.md:100-103 now hands dispatched work to async-round-presentation. This lane's path check refused all 13 context/recommendation-basis.md files as outside the kept thread's scope. Those files are not among the pull request's changed files, and the fix request did not add them as named paths. Only the CHANGELOG is in scope, and editing it alone would claim the copies were updated when they were not, so I made no edit. This needs either a person to make the 13-file sync in this PR, or the copies added to the PR's changed files (for example by pushing the canonical edit) so that a later round can sync the rest.

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

Expand Down
1 change: 1 addition & 0 deletions docs/plugin-philosophy.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 2 additions & 0 deletions plugins/discovery/skills/explore/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions plugins/discovery/skills/research/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion plugins/planning/skills/interview/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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".

Expand Down
2 changes: 1 addition & 1 deletion plugins/planning/skills/interview/context/loop.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<N>` 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.

Expand Down
Loading
Loading