Skip to content

Reply to a reaction by what its emoji means, and log why a reaction got no reply - #884

Merged
lavaman131 merged 1 commit into
mainfrom
reaction-intent-replies
Nov 13, 2026
Merged

lavaman131 merged 1 commit into
mainfrom
reaction-intent-replies

Conversation

@lavaman131

Copy link
Copy Markdown
Collaborator

Reactions read by the decisions model never got a reply

What was seen

On a hosted deployment, chopin answered the toolbar's ❓ and 👎 within about 5 seconds, in Chat and in threads. 🤔, 😕 and 🙋 on chopin's messages never got a reply, in 7 tries in Chat and threads. No reaction picked from the emoji picker started a Planner turn at all.

Cause

❓, 👎 and ✅ are read in code. Every other emoji goes to the decisions classifier with two questions: a Choice ("what does the reaction mean?") and a companion Noul ("Does the reaction ask the Planner to respond?"). A reply needed the Choice at 0.75 likely and 0.7 confident, and the Noul at 0.8.

I asked the same classifier the deployment uses (openai-decisions/gpt-6-luna) about the same kind of reaction from a local script. The Choice was right: 🤔 read as confused at 0.93, 😕 at 0.98. The Noul was not. A reaction doesn't literally "ask", so the model put a thinking face at 0.36 and a confused face at 0.39, far under 0.8. A magnifying glass read as "wants more" at 0.91 had a Noul of 0.03. So the Noul vetoed almost every reply. The call itself never failed.

🙋 had a second problem: none of the Choice's descriptions covered a raised hand, so it read as none (0.43 likely, 0.32 confident).

The failure was invisible. The log line for a reading had the scores but not why it didn't reply, and a failed call logged only Error.

Fix

  • One question. The reply is decided by the Choice alone. The companion Noul is gone from the request. The Choice already says whether the reaction is confusion, disagreement or a wish for more, which are the kinds that ask for a reply, and its probability and confidence are what is calibrated below.
  • Clearer descriptions.
    • "Confused" also covers doubting the message.
    • "More" covers having a question or something to add, with a raised hand as an example.
    • A plus sign moved from "more" to "approve", since a plus one agrees.
      The model still decides every emoji outside the toolbar's three.
  • Thresholds from measurements. A reply now needs a replying kind at 0.6 likely and 0.55 confident (below). A reading that is only logged needs 0.5 and 0.4.
  • Visible failures.
    • Every reading's log line now says why it didn't reply: unreadable, unsure, not-asking or below-reply-threshold.
    • A failed call logs [chat] reactions could not be read with a reason (HTTP 429, timeout, invalid-answer, invalid-request, unknown-model, …), the number of reactions and the size of the state. It never logs the messages.
  • Only reactions on chopin's own messages are read, in Chat and in threads, as before.

Measurements

These come from the classifier named above. There were five conversations, each with a Planner message and the messages before it. Each emoji was asked one at a time twice and in one batch twice, so 20 readings per emoji. The last column applies the new rule.

Emoji Read as (count of 20) Probability Confidence Replies now
🤔 confused 20 0.63–0.94 0.56–0.93 20 of 20
😕 confused 20 0.89–1.00 0.87–1.00 20 of 20
🙋 more 16, confused 4 0.65–1.00 0.58–1.00 20 of 20
🔍 more 20 0.79–0.96 0.75–0.95 20 of 20
➕ approve 20 0.80–0.97 0.76–0.96 0 of 20
👀 ack 20 0.45–0.99 0.34–0.99 0 of 20
👍 approve 20 0.90–1.00 0.88–1.00 0 of 20
🎉 approve 20 0.86–0.99 0.83–0.99 0 of 20
❤️ approve 18, none 2 0.50–0.96 0.40–0.95 0 of 20
🙏 approve 20 0.44–0.95 0.33–0.94 0 of 20
😐 none 16, confused 4 0.39–0.93 0.27–0.92 0 of 20
🤷 confused 10, none 10 0.39–0.58 0.27–0.50 0 of 20
  • The weakest reading that should reply was 🤔 at 0.63 / 0.56.
  • The strongest that should not was 🤷 read as confused at 0.58 / 0.50.
  • The thresholds sit between those two.

Under the old rule, the same readings gave 56 wrong outcomes out of 260, all of them missing replies. Under the new rule there are none. (❓ is read in code and never reaches the model.)

Tests

  • reaction-intent.test.ts:
    • the request is one Choice per reaction;
    • routing by probability and confidence, with each reason;
    • a table of recorded classifier readings for 🤔 😕 🙋 🔍 (reply) and 👀 👍 🎉 ❤️ ➕ 🤷 😐 (no reply);
    • failures log a reason and sizes, never the message.
  • reaction-reply.test.ts: updated to one question per reaction. A likely intent with too little confidence earns no turn.
  • Browser specs' fake classifier (e2e/reaction-judgments.ts): it now answers with readings like the real model's, and covers 😕 and 🙋. Before, it answered a Noul of 0.96 that the real model never gives.

Checks run

  • bun run types: passes.
  • bun run ci: passes.
  • bun test apps/server/src/chat: 1008 pass, 0 fail (before rebasing onto 8cb7e62; after it, the three reaction suites pass 81 of 81).
  • Playwright, --workers=2: message-reactions, chat-thread-reactions, reaction-reader and reaction-access: 11 passed, 1 failed. The failure was chat-thread-reactions' toolbar test, whose click on Chat's toolbar timed out because the transcript stack intercepted the pointer under two workers. Run alone, chat-thread-reactions passed 4 of 4. This change touches no web code.

Judgement calls

  • Noul removed, not reworded. I also tried a reworded Noul ("would the person want the Planner to answer it…"). It fixed confused faces (0.69–0.94) but still read 🙋 and 🔍 anywhere from 0.03 to 0.68. Any Noul bar either kept those from replying or let nothing through, so it couldn't be a reliable gate.
  • The rule's margin is narrow: about 0.03 on likelihood, between a thinking face in one conversation and a shrug. A shrug that sometimes gets a reply would be a small cost: one short Planner turn.
  • Hosted log not checked. I didn't read the hosted server's log. The local readings reproduce the reported behaviour exactly, with the same model and the same question.

…a thinking face or a raised hand gets one

A reaction outside the toolbar's three was asked two questions: a Choice of what it meant and a
companion Noul, "Does the reaction ask the Planner to respond?", which had to reach 0.8. Asked of
the classifier a hosted deployment uses, the Choice read a thinking face as confused at 0.93 and a
confused face at 0.98, but the Noul put them at 0.36 and 0.39, and a magnifying glass at 0.03: a
reaction never literally asks, so no picked emoji ever started a turn. A raised hand had no Choice
that described it and read as none.

The reply now follows the Choice alone: confused, disagree or more, at least 0.6 likely and 0.55
confident, thresholds set from 20 readings per emoji across five conversations (the weakest that
should reply was 0.63/0.56, the strongest that should not 0.58/0.50). The descriptions now cover
doubt, a question or something to add, and a raised hand, and count a plus one as approval. Each
reading's log line says why it started no reply, and a failed call logs a reason and sizes, never
the messages. The browser specs' fake classifier answers with readings like the real model's.

Assistant-model: Claude Opus 5.5
Assistant-workflow: inline (subagent)
Assistant-verification: bun run types
Assistant-verification: bun run ci
Assistant-verification: bun test apps/server/src/chat (1008 pass)
Assistant-verification: playwright message-reactions, chat-thread-reactions, reaction-reader, reaction-access (11 passed, 1 load flake; chat-thread-reactions alone 4 of 4)
Assistant-verification: 260 readings from openai-decisions/gpt-6-luna, old rule 56 wrong, new rule 0 wrong
User-preference: did you make sure that the decision model decides to act on the emojii where relevant
Co-authored-by: Alex Lavaee <lavaman131@github.com>
@lavaman131
lavaman131 merged commit df0fb43 into main Nov 13, 2026
24 checks passed
@lavaman131
lavaman131 deleted the reaction-intent-replies branch November 13, 2026 00:46
Sign up for free to 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