A reply whose whole answer is a reaction #991

Closed
opened 2026-08-18 22:30:29 +00:00 by coilyco-ops · 1 comment
Member

Kai's ask, alongside #750: the harness should be able to conclude that the output of a reply is just an emoji react, like a thumbs up.

Today every accepted turn either posts text or, since #895, posts nothing at all when it already answered through a tool. There is no way to say "the answer is a thumbs up" without spending a message on it, and a one-word acknowledgement in a channel is noise the reaction surface already exists to carry.

Shape

{{react:key}}, the same invocation shape a canonical phrase takes, resolved after the reply checks the way a phrase's text is. Keys are acknowledge, agree, and disagree, compiled in rather than configured.

Keyed rather than spelled as an emoji, so no literal rune reaches a member past the reply checks, and the set is disjoint from the four harness marks by test: an answer a member reads as a progress mark is worse than no answer.

One invocation is the whole reply. A reaction beside prose, two reactions, and an unknown key all refuse the turn rather than leaving a marker a member reads, which is the phrase rule for the phrase reason.

A mark is an answer, so it earns its silence with no tool call. The empty-reply case from #895 is unchanged and still the failure it always was when nothing ran.

Where words go instead

Three cases send the glyph as text, and none loses the answer:

  • A turn owing a service-authored suffix, because a receipt outranks a mark and a mark cannot carry one.
  • A transport with no reaction surface, HTTP included.
  • A mark Discord refuses, most likely a missing ADD_REACTIONS. Inert is the right answer for harness state and the wrong one here, because this mark is the delivery.

Telemetry

turn.reply.reaction on the outcome, response.reaction.invoked on the key. The key set is closed and compiled in, so it is safe as a label.

Kai's ask, alongside #750: **the harness should be able to conclude that the output of a reply is just an emoji react, like a thumbs up.** Today every accepted turn either posts text or, since #895, posts nothing at all when it already answered through a tool. There is no way to say "the answer is a thumbs up" without spending a message on it, and a one-word acknowledgement in a channel is noise the reaction surface already exists to carry. ## Shape `{{react:key}}`, the same invocation shape a canonical phrase takes, resolved after the reply checks the way a phrase's text is. Keys are `acknowledge`, `agree`, and `disagree`, compiled in rather than configured. **Keyed rather than spelled as an emoji**, so no literal rune reaches a member past the reply checks, and the set is **disjoint from the four harness marks by test**: an answer a member reads as a progress mark is worse than no answer. **One invocation is the whole reply.** A reaction beside prose, two reactions, and an unknown key all refuse the turn rather than leaving a marker a member reads, which is the phrase rule for the phrase reason. **A mark is an answer**, so it earns its silence with no tool call. The empty-reply case from #895 is unchanged and still the failure it always was when nothing ran. ## Where words go instead Three cases send the glyph as text, and none loses the answer: * A turn owing a service-authored suffix, because **a receipt outranks a mark** and a mark cannot carry one. * A transport with no reaction surface, HTTP included. * A mark Discord refuses, most likely a missing `ADD_REACTIONS`. Inert is the right answer for harness state and the wrong one here, because this mark is the delivery. ## Telemetry `turn.reply.reaction` on the outcome, `response.reaction.invoked` on the key. The key set is closed and compiled in, so it is safe as a label.
Author
Member

Built and landed on main as e13bdbd. Angie (ENG), seat claude.

Filed and closed together, because the work was already written when Kai asked for it alongside #750.

What landed

internal/community/reactreply.go holds the invocation, the closed key set, and the finish path. runTurn consults it after the #895 silent-turn check and before phrase rendering, so an ordinary reply is untouched and a phrase still renders exactly as it did.

Tests

  • TestAReactionInvocationMarksInsteadOfReplying - posts nothing, marks the member's message.
  • TestAReactionIsNotUnchosenSilence - every key, no tool call needed.
  • TestATransportThatCannotMarkSendsTheGlyph - the HTTP turn gets the glyph as its reply.
  • TestATurnOwingAReceiptRepliesRatherThanMarks - a tool turn keeps its footer and sends words.
  • TestAMarkThatCannotLandIsSentAsWords - a refused reaction becomes the reply.
  • TestAReactionBesideOtherTextIsRefused - prose, two invocations, and an unknown key.
  • TestReplyReactionsAreDisjointFromTheHarnessMarks - the alphabet stays separable by test rather than by care.
  • TestTheEvaluationPromptCarriesTheReactionPolicy - the eval renders the deployed prompt.

just gate green.

One thing worth a second opinion

The glyph reaches a member without passing the reply checks. That is the phrase precedent exactly, since it is service-authored and resolved after the checks, and it is also how a thumbs up gets past the neutral profile's emotive-emoji refusal. On Discord that reads correctly, because a reaction was never governed by the response contract. On the HTTP fallback it is arguable: a profile whose contract refuses emotive emoji in a reply would receive one as the reply body.

I took that over the alternatives - refusing the turn on a transport that cannot mark would turn a model choice into a failure, and gating the prompt on DiscordEnabled would make the eval measure a prompt no lane renders. Say the word if you would rather the HTTP path refused, and it is a small change.

Documented

Folded into phrases, which owns the invocation shape, with the claim in progress that a reaction is never model output corrected to name the exception. The docs folder is at its 40-page cap, so no new page.

**Built and landed on `main` as `e13bdbd`. Angie (ENG), seat `claude`.** Filed and closed together, because the work was already written when Kai asked for it alongside #750. ## What landed `internal/community/reactreply.go` holds the invocation, the closed key set, and the finish path. `runTurn` consults it after the #895 silent-turn check and before phrase rendering, so an ordinary reply is untouched and a phrase still renders exactly as it did. ## Tests * `TestAReactionInvocationMarksInsteadOfReplying` - posts nothing, marks the member's message. * `TestAReactionIsNotUnchosenSilence` - every key, no tool call needed. * `TestATransportThatCannotMarkSendsTheGlyph` - the HTTP turn gets the glyph as its reply. * `TestATurnOwingAReceiptRepliesRatherThanMarks` - a tool turn keeps its footer and sends words. * `TestAMarkThatCannotLandIsSentAsWords` - a refused reaction becomes the reply. * `TestAReactionBesideOtherTextIsRefused` - prose, two invocations, and an unknown key. * `TestReplyReactionsAreDisjointFromTheHarnessMarks` - the alphabet stays separable by test rather than by care. * `TestTheEvaluationPromptCarriesTheReactionPolicy` - the eval renders the deployed prompt. `just gate` green. ## One thing worth a second opinion **The glyph reaches a member without passing the reply checks.** That is the phrase precedent exactly, since it is service-authored and resolved after the checks, and it is also how a thumbs up gets past the neutral profile's emotive-emoji refusal. On Discord that reads correctly, because a reaction was never governed by the response contract. **On the HTTP fallback it is arguable**: a profile whose contract refuses emotive emoji in a reply would receive one as the reply body. I took that over the alternatives - refusing the turn on a transport that cannot mark would turn a model choice into a failure, and gating the prompt on `DiscordEnabled` would make the eval measure a prompt no lane renders. Say the word if you would rather the HTTP path refused, and it is a small change. ## Documented Folded into [phrases](https://forgejo.coilysiren.me/coilyco-gaming/sirens-echo/src/branch/main/docs/sirens-echo-phrases.md), which owns the invocation shape, with the claim in [progress](https://forgejo.coilysiren.me/coilyco-gaming/sirens-echo/src/branch/main/docs/sirens-echo-progress.md) that a reaction is never model output corrected to name the exception. The docs folder is at its 40-page cap, so no new page.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
coilyco-gaming/sirens-echo#991
No description provided.