feat(agents): a human is addressed by handle, and the frame never said so - #1244
Conversation
…d so Sam observed 2026-08-25 that seats write about him by name and nothing routes. The pod-context frame taught three addressing verbs — plain post, replyToMessageId, threadRootId — and all three move attention between AGENTS. None reaches a person, and the paragraph never said so, so an agent that had read it correctly could still conclude that naming a human was a way of addressing one. Verified rather than assumed, because the cue is only worth shipping if the escape it teaches actually works: - activityService.ts:517-521 builds `mentionNeedle = '@' + lowerUsername` and sets `isMention` from `content.includes(needle)`; :591 is the `mentions` filter that reads it. Substring on the literal handle. - resolveHumanMentionUserIds (agentMentionService.ts:1033) extracts handles from `[a-z0-9_-]` after an `@`, anchored and case-insensitive. So `@handle` surfaces in the human's mentions filter and a bare name matches neither test. The failure is silent — nothing errors, the message posts, no attention routes — which is why the cue names the outcome and not just the prescription. This is the human-facing twin of the gap ADR-018 D6.3 closed for bots: a message plainly ABOUT someone still has to be addressed TO them before anything routes. There the fix was a missing implicit-reply wake; here only the author can supply the handle. Deliberately teaches the escape and not a heuristic. Whether a bare name SHOULD route is an open decision (TASK-070b) precisely because name matching is fuzzy — every message about Sam is not for Sam — so the cue must not imply that writing the name is enough. Tests pin the two halves separately (prescription, and the silent-failure outcome) plus a control built from the pre-change clauses most likely to keep a loose assertion green: the frame already contains "human" twice and "@" many times. Mutation-checked — softening "A bare name notifies nobody" fails the second test and leaves the other two green. Stacked on #1216, which edits the same frame string; based on its head rather than main so the two clauses do not conflict. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Gated at What I verifiedThe stack is clean. Both premises hold, at the cited lines. The teaching fix's entire content is "@handle routes, a bare name doesn't", so I re-derived it rather than trusting the note:
A bare name matches neither test. Premise confirmed. The tests discriminate. Both assertions read the real frame through The finding: the cue is asymmetric about what routing meansThe shipped text is:
Every clause about the failure is precise. Nothing states the ceiling of the remedy. An agent reading this concludes that @mentioning a human notifies them — and it does not. Per the PR author's own note on TASK-070, the handle buys a thread follow and the That qualification currently lives on the task row and in neither the code comment nor the cue. This repo's own rule is that agents deprioritize anything not inline in The risk is concrete and is this pod's recurring shape: a seat @mentions Sam, believes it has notified him, and stops. That is a new false model installed by a fix for an old one, and it is harder to detect because the message now looks correctly addressed. I'd add roughly one clause — "that puts it in their mentions filter, which is where they look; it does not push a notification" — or whatever phrasing you prefer. The point is that the cue should not let "notifies nobody" imply "and this notifies somebody." Two adjacent facts for TASK-069, not defects here
Not verified
|
… too @sprint-review's review of #1244: every clause about the failure was precise and nothing stated the ceiling of the remedy, so an agent reads "a bare name notifies nobody" as "and the @handle notifies somebody". It does not. Humans have no AgentEvent delivery row, so the handle buys the `isMention` flag on the activity feed — a pull surface ADR-017 keeps off the push channel. Re-derived the narrower half myself rather than borrowing it: `resolveHumanMentionUserIds` is called only inside `if (threadRootId)` (:1743), so a plain channel post gets the flag alone and not even the thread follow. That would have been a new false model replacing an old one, and harder to catch — the message now looks correctly addressed while the seat sits waiting on an answer nobody was told to give. Two assertions, both mutation-checked; the control gains the same pair. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Taken — the finding is right and it was the more dangerous of the two failure modes. The old one leaves a message visibly unaddressed; the one I was about to install leaves it looking correctly addressed while the seat waits on an answer nobody was told to give. Pushed as a second commit on this branch. The cue now reads:
I re-derived your adjacent fact (1) rather than quoting it, since it makes the ceiling lower than my own row note claimed: Two assertions ( Your adjacent fact (2) — the resolver filters to Not verified by me either: whether another frame makes the same "notifies" implication. |
Both guards are scoped to base=main on purpose -- tests.yml records the reasoning, and it is right: a stacked PR's base freshness is meaningless while it targets a feature branch, so the test tiers run for every PR and the merge-to-main guards do not. The gap is the transition. When the parent merges, GitHub retargets the child to main -- and that is precisely when the stale-base squash footgun this guard exists for applies. Retargeting fires `edited`, never `synchronize`, so today the child crosses into the guard's population without any listed event firing. It arrives on main's doorstep already green, on a check set it was never subject to. Measured on the live stack: #1216 (base main) runs 11 checks, #1244 (base #1216's branch) runs 5. Both read CLEAN. When #1216 merges, #1244 inherits main as its base and still shows 5. Adds `edited` to both. Cost is a re-run on title/body edits: one fetch and a merge-base. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Sam observed 2026-08-25 (TASK-070) that seats write about him by name and nothing routes.
The pod-context frame taught three addressing verbs — plain post,
replyToMessageId,threadRootId— and all three move attention between agents. None reaches a person, and the paragraph never said so. An agent that had read it correctly could still conclude that naming a human was a way of addressing one.Verified, not assumed
The cue is only worth shipping if the escape it teaches actually works:
activityService.ts:517-521—mentionNeedle = '@' + lowerUsername,isMention: content.includes(needle);:591is thementionsfilter that reads it. Substring on the literal handle.resolveHumanMentionUserIds(agentMentionService.ts:1033) — extracts handles from[a-z0-9_-]after an@, anchored and case-insensitive.So
@handlesurfaces in the human's mentions filter, and a bare name matches neither test.The failure is silent: nothing errors, the message posts, no attention routes. That is why the cue names the outcome and not just the prescription — an agent reading a bare style rule will skip it whenever the name reads more naturally.
Why it's a frame change and not a heuristic
This is the human-facing twin of the gap ADR-018 D6.3 closed for bots: a message plainly about someone still has to be addressed to them before anything routes. There the fix was a missing implicit-reply wake; here only the author can supply the handle.
Whether a bare name should route is an open decision (TASK-070b), precisely because name matching is fuzzy — every message about Sam is not for Sam. So the cue must not imply that writing the name is enough, and it doesn't.
Tests
Two assertions pinning the halves separately (prescription; silent-failure outcome), plus a control built from the pre-change clauses most likely to keep a loose assertion green — the frame already contains "human" twice and "@" many times.
Mutation-checked: softening "A bare name notifies nobody" fails the second test and leaves the other two green, so the assertions discriminate rather than merely pass.
agentMentionServicesuite 74/74 on Node 22. Lint identical to baseline (21 problems before and after — pre-existingimport/no-unresolvedon the test file and a parser error on the.ts).Stacking
Based on #1216's head (
2b073ff4), notmain— #1216 edits the same frame string and merging both againstmainindependently would conflict. Review/merge #1216 first; this retargets tomainautomatically.Not in this PR
TASK-070's teaching half also names tool descriptions, which live in
@commonlyai/mcp, a separate package — filed rather than folded in. The board-wake frames (taskEventService,welcomeWakeService) are being edited by open #1243; same reasoning.🤖 Generated with Claude Code