Repair a refused reply, then redact the block, before discarding the message #804
No reviewers
Labels
No labels
move-to-repo
coilyco-bridge-deploy
move-to-repo
coilyco-flight-deck-agent-compose
move-to-repo
coilyco-gaming-eco-app
move-to-repo
coilysiren-inbox
move-to-repo
unknown
🔒⚠️📦⚠️🔒 SANDBOXED 🔒⚠️📦⚠️🔒
autonomy
async-consult
autonomy
epic
autonomy
headless
autonomy
live-collab
c#
priority
P0
priority
P1
priority
P2
priority
P3
priority
P4
role/ai
role/creator
role/design
role/director
role/engineer
role/exec
role/human
role/ops
role/qa
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
coilyco-gaming/sirens-echo!804
Loading…
Reference in a new issue
No description provided.
Delete branch "aos/claude/yt58-partial-refusal"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #796. Two rungs, in the order they run.
Rung one: repair (the issue's option 1)
The issue preferred "tell the model what failed so the repair loop can retry". Reading the path first: that loop already exists, and already names the refusing check to the model (
responseRepairPrompt, #549). It covered two checks,parseandresponse_style, because those two are duplicated insideComplete.The other five run in
runReplyChecksafterCompletereturns, so they had no repair. A grounding refusal ended a 51-second turn outright while a style refusal got a retry, for no reason beyond which side of a function boundary the check sat on.ProxyClient.ValidateReplytakes the harness check set and the runtime wires it torunReplyChecks, so all ten rules reach the loop.Repair is advisory.
runReplyChecksafterCompleteis still the only thing that refuses, and the harness checks drop out of the loop once the budget is spent rather than failing there, which would reportstage=modelfor a harness refusal.Rung two: redaction (the issue's option 2)
Repair covers the model that will fix the block. Redaction covers the model that will not: the block goes, the rest is delivered, and a harness notice stands where it was.
The unit is a block, not a sentence. A block is a paragraph or one list item. The observed reply was twelve bullets with no blank line in it, so a paragraph-only split would have found one block and saved nothing. The issue's objection 2 was right about sentences:
[.!?]+splits prose, not reasoning, and separates a claim from the qualifier bounding it. A block holds both.Only rules a block can carry alone, by allowlist. The four grounding rules, tool-call markup, self-attributed claim.
response_styleandidentity_claimare properties of the whole voice.parsemeans there was no reply to cut.identifier_disclosurelooks redactable and is not: it collapses digits and spelled numbers across the whole reply, so a guarded value can span two blocks and neither fails alone. That one would have been a real hole.Two passes, and the second is the authority. The first marks blocks failing the same rule that refused the message. The second runs every check again over the surviving prose, so what is delivered passed the full set on its own rather than merely lacking the removed block. That is what makes an imperfect first pass safe: it picks candidates and licenses nothing. A remainder that fails is refused whole, exactly as before.
The mark cannot be forged. It renders in the existing harness notice shape, whose alphabet (
noticeAllowed) strips anything a model could use to imitate it. This is the first notice that stands inside a reply rather than replacing one.On the issue's three objections
Option 3, preserving a refused reply for an operator, is still not implemented: a reply refused by
identifier_disclosurecarries the value that check exists to keep out of logs. Worth its own issue if wanted.Verified
internal/community/replyrepair_test.go(4) andredaction_test.go(10), driving the observed trace's shape:runTurn: the member receives the redacted reply with the mark, and a clean turn recordsresponse.redacted.blocks: 0.The repair tests were confirmed to fail against
mainfirst.ward exec gategreen.Notes for review
Telemetry semantics changed slightly.
response.checkon a delivered turn can now name a rule, meaning "this reply lost a block to it", where before a named check always meant a discarded turn.response.redacted.blocksdisambiguates and is present on every turn, zero included.Truncation can reach the mark. It is part of the answer rather than a service suffix, so a redaction in the last block can lose its mark when the reply is also being cut for length. The removal is never at risk, only the disclosure, and only on a reply already visibly truncated. Restructuring the assembly budget to protect it seemed worse than the edge.
docs/sirens-echo-notices.mdis at its 80-line cap, so the "a notice can now stand inside a reply" case is documented indocs/sirens-echo-reply-redaction.mdand cross-linked rather than added there. If it belongs in the notices doc, that doc needs a split first.Not decided here: whether
maxResponseRepairsshould rise from 1, and whethermaxRedactedBlocks(2) is the right bound. Both are tuning, anddocs/sirens-echo-tuning-overrides.mdtreats those as deliberate.Repair a refused reply instead of discarding every correct lineto Repair a refused reply, then redact the block, before discarding the message