Skip to content

CRU-133: recheck round-trip guidance in persona prompt, launch response, tool descriptions - #386

Merged
selfcontained merged 9 commits into
mainfrom
cru-133-recheck-guidance
Apr 23, 2026
Merged

CRU-133: recheck round-trip guidance in persona prompt, launch response, tool descriptions#386
selfcontained merged 9 commits into
mainfrom
cru-133-recheck-guidance

Conversation

@selfcontained

Copy link
Copy Markdown
Owner

Summary

Wires the opt-in review round-trip (CRU-127) through the three text carriers that agents actually see at runtime. No backend behavior changes beyond the text — the state machine and poll logic landed in CRU-131/CRU-132 (PRs #381, #382).

  • Persona prompt: assemblePersonaPrompt now accepts an options.allowRecheck flag. When the launching review has allowRecheck: true, the assembled prompt includes a "Recheck round-trip" block that tells the reviewer to stay alive, call dispatch_await_recheck, handle the pending / ready / cancelled branches, and submit a round‑2 verdict. Absent otherwise. The launch path in server.ts passes the flag through.
  • dispatch_launch_persona response: when allowRecheck is true, the tool result now includes a parent‑facing guidance block explaining the full driver loop (read feedback → resolve each → submit resolution → poll for round‑2 verdict). Present only when opt‑in.
  • Tool description rewrites: dispatch_launch_persona, dispatch_resolve_feedback, dispatch_submit_resolution, and dispatch_await_recheck descriptions tightened to carry the constraints the server enforces and tell the agent what to do next.
  • Rejection message rewrites: submit_resolution errors for open items / missing ignore reasons now name the feedbackIds and point at dispatch_resolve_feedback. The three "round 2 already" paths (complete_review × 2, submit_resolution × 1) now share the ticket-specified copy: "Round 2 already complete. This review only supports a single round-trip."

Acceptance criteria from the ticket:

  • Reviewer prompt includes the round-trip block only when launched with allowRecheck: true (new tests in persona-loader.test.ts).
  • dispatch_launch_persona response includes the parent guidance block only when allowRecheck: true.
  • All tool descriptions updated; schema validation / rejection messages updated to match.
  • pnpm --filter @dispatch/server check passes. Full server test suite (394 tests) passes. Web tsc failures are pre-existing on main (react-day-picker, @radix-ui/react-popover, implicit any in activity-pane.tsx:868) and unrelated to this change.

Test plan

  • Manual: launch a persona with allowRecheck=true, inspect the generated reviewer --append-system-prompt — "Recheck round-trip" block is present and mentions dispatch_await_recheck, pollAgainInSeconds, responds_to_feedback_id.
  • Manual: launch the same persona without allowRecheck — block is absent.
  • Manual: call dispatch_submit_resolution with an open feedback item — rejection names the feedbackId and points at dispatch_resolve_feedback.
  • Manual: call dispatch_complete_review a third time — rejection uses the new "Round 2 already complete." copy.

🤖 Generated with Claude Code

selfcontainedand others added 9 commits April 22, 2026 16:55
… launch response, and tool descriptions (CRU-133)
Wires the opt-in review round-trip through the three text carriers that
agents actually see:
- Persona prompt: append a round-trip guidance block to
assemblePersonaPrompt when the review is launched with allowRecheck.
- dispatch_launch_persona response: when allowRecheck is true, return a
block that tells the parent how to drive the loop (get/resolve
feedback, submit resolution, poll for round 2).
- Tool descriptions: rewrite dispatch_launch_persona,
dispatch_resolve_feedback, dispatch_submit_resolution, and
dispatch_await_recheck so each carries the constraints the server
enforces and tells the agent what to do next.
- Rejection messages: update complete_review and submit_resolution
errors so a fresh agent can self-correct on the next turn.
No backend behavior changes beyond text — state machine and poll logic
remain as landed in CRU-131/CRU-132.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Address two items from backend-security-review:
- dispatch_launch_persona response (allowRecheck=true): replace
"poll dispatch_get_feedback until the reviewer's round-2 verdict is
complete" — dispatch_get_feedback only returns agent_feedback rows,
not persona_review.verdict/status, so a round-2 approve-with-no-new-
items would look indistinguishable from "still running." New text
describes what the API actually surfaces: watch for items linked via
responds_to_feedback_id, and treat "nothing new after a reasonable
window" as an approval signal.
- dispatch_resolve_feedback description: the "the reviewer will see it
in a recheck pass" clause was unconditional, but the tool is also
used by parents whose reviews were launched with allowRecheck=false
(the default), where no recheck happens. Qualify the clause.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…hints (CRU-133)
Close the test-coverage gaps identified in the CRU-133 self-review:
- Extract the dispatch_launch_persona response-text assembly into a pure
`buildLaunchPersonaResponseText` exported from shared/mcp/server.ts.
The handler now delegates to it, so the allowRecheck branching is
unit-testable without spinning up the MCP server. New tests in
test/mcp-launch-persona-response.test.ts cover: base-only when
allowRecheck is false, full driver-loop block when true, required
tool references (dispatch_get_feedback, dispatch_resolve_feedback,
dispatch_submit_resolution, responds_to_feedback_id), and the
"no verdict signal" regression guard for the fix in ee45e96.
- Strengthen the open-items and ignored-missing-reason rejection tests
in both agent-manager.test.ts and resolution-capture-integration.test.ts
to assert the new recovery hint text ("Call dispatch_resolve_feedback",
status names, and the reason-explanation phrase). The existing regexes
only proved IDs were named.
- Lock the ticket-spec copy for the third-completion rejection in
agent-manager.test.ts — toBe the exact string, not just a substring,
so the rejection message itself (which is guidance) can't drift.
No production behavior change beyond the pure-function refactor.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…dispatch_get_feedback (CRU-133)
Live dogfood against the dev stack surfaced a real bug in the CRU-133
text: the persona prompt and the launch-response guidance both tell
agents to link round-2 follow-up findings to the originals using
`responds_to_feedback_id`, but the MCP contract had no such field.
- `dispatch_feedback` zod schema had no `respondsToFeedbackId` input, so
reviewers couldn't actually set the linkage — the instruction was
inert.
- `FeedbackItem` (the type surfaced by `dispatch_get_feedback`) omitted
the field, so parents couldn't read it either. The DB column
`responds_to_feedback_id` existed and the SELECTs already aliased it
to `respondsToFeedbackId`, but neither type exported that name.
- Prompt and launch-response used snake_case (`responds_to_feedback_id`)
while the rest of the MCP surface is camelCase (`feedbackId`,
`personaAgentId`, `mediaRef`).
End-to-end fix:
- Add `respondsToFeedbackId?: number` to `FeedbackInput` (both
`manager.ts` and `mcp/server.ts`).
- Add `respondsToFeedbackId: number | null` to `FeedbackItem`.
- Add the zod parameter to `dispatch_feedback` and thread it through to
`AgentManager.submitFeedback`, which now persists it via the existing
DB column.
- Update persona prompt and launch-response copy to camelCase
`respondsToFeedbackId`, and expand the persona-side explanation so a
reviewer understands *why* the link matters (parent can map round-2
follow-ups back to round-1 concerns).
- New regression test: `submitFeedback` persists `respondsToFeedbackId`
on a round-2 finding. Existing tests updated for the camelCase form.
Found via live-dogfood review against the dev stack running this branch
(feedback items #1 and #2 from agt_35cac0176460).
Co-Authored-By: gpt-5.4 review agent <noreply@anthropic.com>
…recheck round-trip (CRU-133)
Live dogfood against the dev stack exposed five concrete gaps in the
recheck round-trip that the text-only CRU-133 guidance couldn't close
on its own. Fixing all five here so the round-trip can actually run
end-to-end without manual intervention.
1. **Parent-side status-watch primitive** — new `dispatch_await_review`
MCP tool (parent-only, AGENT_TOOLS) and `AgentManager.awaitReview`.
Mirrors `dispatch_await_recheck` for the opposite side of the
round-trip. Returns 'pending' with `pollAgainInSeconds` while the
reviewer works, 'feedback_ready' on round-1 close for an
allowRecheck review, 'complete' when the final round is done (or
when a single-pass review closes), or 'cancelled'. Uses the same
`pollCadenceSeconds` helper as the reviewer side for consistency,
but doesn't auto-cancel (that's owned by the reviewer flow).
2. **Launch-response guidance rewrite** — `buildLaunchPersonaResponseText`
now produces a survival kit mirroring the reviewer block: explicit
"do not emit a terminal dispatch_event yet," explicit `ScheduleWakeup`
mechanism, numbered steps pointing at `dispatch_await_review`
instead of polling `dispatch_get_feedback` (which was the original
bug — it fires on the first item that lands and misleads agents
into thinking the review is done). Also adds an explicit step 3:
commit fixes before `dispatch_submit_resolution`, otherwise the
reviewer's round-2 diff is empty.
3. **`dispatch_event` added to PERSONA_TOOLS** — standard guidance told
personas to emit dispatch events but the tool wasn't exposed, so the
first call always errored. Fixed.
4. **Consolidated completion paths** — removed `status: "complete"` from
`review_status`'s enum. Completion now goes exclusively through
`dispatch_complete_review`. Standard guidance updated to match. The
dogfood reviewer called both and the second was rejected with a
state-machine error that fresh agents can't self-correct from.
5. **Reviewer round-2 obligation sharpened** — `RECHECK_ROUND_TRIP_GUIDANCE`
is now explicit that round 2 has a mandatory close via
`dispatch_complete_review`, and that the terminal `dispatch_event`
must wait until then. The dogfood reviewer woke from its recheck but
exited without submitting a round-2 verdict, leaving the review
permanently in `awaiting_recheck`.
6. **`dispatch_submit_resolution` description** — added the
commit-before-submit warning so it's visible from the tool surface,
not just the launch-response text.
Tests:
- New `awaitReview` manager describe block covering all five status
transitions (pending/reviewing, feedback_ready, complete single-pass,
pending/awaiting_recheck, complete round-2, cancelled, and the
parent-mismatch / missing-review error paths).
- `mcp-launch-persona-response.test.ts` rewritten for the new text:
survival-kit sleep mechanism, status-watch pattern, commit-before-
submit clause, and regression guards against the old "poll feedback
items" pattern.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… Claude-specific sleep hints (CRU-133)
Two fixes from user feedback on the first survival-kit pass:
1. **`dispatch_await_review` now supports multi-reviewer parents.**
`personaAgentId` is optional; omit it and the server returns
whichever of the caller's launched reviewers most needs attention
(priority: feedback_ready > cancelled > complete > pending; ties
broken by most recently updated). Every non-`no_reviews` response
now includes the `review` it refers to so the parent knows which
one changed — previously the pending branch returned bare
`reviewStatus`/`roundNumber` with no review ID, which was useless
when several reviews were in flight. Adds a new `no_reviews`
terminal status for parents that haven't launched anything.
2. **Remove Claude-Code-specific sleep hints.** The guidance text
(launch response, reviewer persona prompt, dispatch_await_recheck
/ dispatch_await_review descriptions) previously named
`ScheduleWakeup` as the sleep mechanism. Dispatch runs claude /
codex / opencode — that's only correct for the Claude path. Now
the text says "wait N seconds using whatever sleep mechanism your
agent runtime provides" and each runtime can figure out its own
primitive. The `pollAgainInSeconds` contract is unchanged; only
the prose around it is now runtime-neutral.
Tests: existing awaitReview tests updated for the new response shape
(review now present on pending/cancelled); new coverage for the
null-personaAgentId branches (no_reviews on empty, feedback_ready
preference over in-progress, pending when all are reviewing). New
assertion in mcp-launch-persona-response.test.ts that the text does
NOT contain "ScheduleWakeup" and does describe the sleep mechanism
in agent-runtime-neutral terms.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- dispatch_await_review: handle feedbackCount=0 on a round-1 allowRecheck
approve so the parent is told to submit_resolution/cancel_recheck
instead of looping on an empty dispatch_get_feedback.
- awaitReview multi-review path: fold the feedback COUNT into the
primary SELECT via a correlated subquery and let classifyReviewForAwait
accept a prefetched count, eliminating the N+1 COUNT queries.
- submitFeedback: validate respondsToFeedbackId within the existing
transaction — reject 400 when unknown, cross-review, or pointing at a
round-2 item so the linkage invariant is enforced server-side.
…ound 2 (CRU-133)
End-to-end dogfood on the dev stack surfaced a state-machine bug: the
reviewer agent pinged review_status('reviewing', 'round 2 begin') per
the standard progress-ping guidance while it was actually in round 2.
updatePersonaReviewStatus unconditionally SET status = 'reviewing',
which downgraded the review out of 'awaiting_recheck' back to
'reviewing'. The next dispatch_complete_review call then landed in the
`status === 'reviewing'` branch of completePersonaReview, which sets
nextRoundNumber = 1 — so round_number stuck at 1 even though the
reviewer had just finished round 2. Downstream, dispatch_await_review
saw complete + round_number=1 + allowRecheck=true and would have
returned feedback_ready a second time, sending the parent into a loop
over already-fixed findings.
Two complementary fixes:
1. updatePersonaReviewStatus now leaves the status field untouched
when the review is in a non-working state (complete, cancelled, or
awaiting_recheck). The progress-ping channel only updates the
message. This matches the intent: a reviewer pinging "still
working" during round 2 shouldn't look identical to the round-1
start.
2. completePersonaReview is defensively tolerant of a 'reviewing'
status that sits on top of an existing round-1 resolution. It
infers the next round number from the most recent submitted
resolution rather than trusting the status label alone. If any
future path manages to put the review back into 'reviewing' after
a resolution, the completion still lands as round 2.
Tests: regression covering the review_status clobber (status stays
awaiting_recheck, subsequent complete lands as round 2) and the
defense-in-depth path (forcibly setting status='reviewing' after a
resolution still yields round_number=2 on the next complete).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…QL (CRU-133)
PR #386 review comment called out the CASE WHEN in updatePersonaReviewStatus:
logic in SQL is hard to unit-test and can't produce contextual errors. Fair —
pulled it into TypeScript.
- New exported pure helper `resolveProgressPingStatus(requested)` validates
the progress-ping status at the tool boundary. Unit-tested in its own file
(no DB). Throws a 400 AgentError with the valid-set listed for any bad
value.
- `updatePersonaReviewStatus` now opens a transaction, SELECTs the current
status FOR UPDATE, then branches in code:
- `complete` / `cancelled`: rejected with a specific 409 — "Cannot ping
review_status on a <status> review" — agents get a clear signal that
pings on terminal reviews aren't allowed, instead of a silent no-op.
- `awaiting_recheck`: preserves the status label (reviewer is still
doing work; round-2 round-trip must stay intact) and just refreshes
the message.
- any other status: transitions to `reviewing` as before.
Tests: two new integration tests that exercise the terminal-state 409 paths
(complete and cancelled), plus a dedicated unit-test file for the pure helper.
The existing round-2 regression test still passes — the awaiting_recheck
preservation moved from SQL to code without changing behavior.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@selfcontained
selfcontained marked this pull request as ready for review April 23, 2026 14:29
@selfcontained
selfcontained merged commit b56498f into mainApr 23, 2026
1 check passed
@selfcontained
selfcontained deleted the cru-133-recheck-guidance branch April 23, 2026 15:27
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@selfcontained