Repository navigation
Conversation
… before approval
A reply draft its plugin refused (result ok=false) used to end silently: the
route stayed "relayed", nobody was told, and plugin_draft_decide had told the
session the text would be sent.
- A refused draft (a failed result, or a refused pre-approval check) ends its
session route send_failed in the same transaction (new route state, 0111),
owing a hand-back. The route sweep hands the message back to the session that
wrote the reply as a NEW route whose paste carries the plugin's reason and the
refused text, at most plugins.MaxRouteRedrafts (2) times per message; after
that, or when the session is gone, it goes to the chief as a timed-out route
does. The owner is pushed on the draft's tag with the reason; a chief-filed
draft is noted to the chief. plugin_messages_list reports send_failed.
- New capability draft_checks (minted, like every capability): such a
plugin's pending drafts await its verdict before they can be approved.
GET /plugin/v1/drafts/checks lists them; POST /plugin/v1/drafts/{id}/check
records sendable (the owner is asked now) or refused (rejected, never
approvable, handed back). A plugin without it behaves as before.
- The routed preface tells the session to write plain text.
- plugin_draft_decide's approval note says the reply is queued, not sent, and
that a refusal comes back.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…gin's verdict Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
ZacxDev
marked this pull request as ready for review
November 11, 2026 07:18
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What was wrong
When a plugin reported a reply draft's result as
ok: false, muster recordedfailedand an audit row, and that was all. The route had already been closedfallbackwhen the draft was filed, soplugin_messages_liststill called the messagerelayed. Nobody was pushed, the chief was not told, and the session that wrote the reply was never asked for a new one. Before that,plugin_draft_decidehad told the session the approved text "sends exactly this text, once". Nothing checked a reply before the owner was asked to approve it. None of this was specific to one plugin.What changes
A refused reply comes back (a failed result, or a refused pre-check):
send_failed(migration 0112). This happens in the same transaction, and the route then owes a hand-back.plugin_messages_listreportssend_failed, notrelayed.routed_viaredraft of <route> to <session>). The new paste says the earlier reply was not sent, and quotes the plugin's reason and the refused text as fenced data. The chief gets a note.plugins.MaxRouteRedrafts(2) times per message. The decision is made once and stored on the row (session_routes.redraft). The next refusal goes to the chief as an ordinary hand-back, the same path a timed-out route takes. So does any case where the session's window is gone.plugin-draft-<id>), with the plugin's reason on one line. This happens for every failed send. For a pre-check refusal it happens only when the reply is not going back to its session.reply_draft, no route) is noted to the chief in its plugin session.FinishDraftnow reportsfresh, so a repeated result changes nothing.Pre-check before approval (
draft_checks, a new capability chosen when the token is minted, like every capability):check_state = 'awaiting'. While it waits, the owner gets no push and no attention item. The Asks card offers Reject only, andplugin_drafts_listmarks itawaiting_plugin_check.ErrDraftUnchecked, which has its own sentence. A CHECK constraint also blocks it.GET /plugin/v1/drafts/checkslists the drafts waiting.POST /plugin/v1/drafts/{id}/check {sendable, detail}records the plugin's verdict:sendable: the owner is asked now.rejectedand can never be approved, and it is handed back as above.draft_checksbehaves exactly as before.Wording:
plugin_draft_decide's approval note now says the reply is queued and not sent yet, and that a refusal comes back. The tool description now says "queues", not "sends".docs/plugins.mdgains "Check a reply before it can be approved" and "When the plugin refuses a reply". It also updates the capability, state and tool tables.Tests
internal/api/plugin_draft_refusals_pg_test.go:route_reply→ MCP approve → claim →ok=false)The route, capability, isolation and principal ledgers are updated for the two new routes.
Red at base, green at HEAD:
draft_checksdoes not exist and the capability CHECK refuses it.Local runs are change-scoped only; CI runs the rest.
🤖 Generated with Claude Code