Skip to content

test(mentions): pin the human-handle mechanism and put a budget on the wake frame - #1265

Closed
lilyshen0722 wants to merge 1 commit into
mainfrom
test/pin-cue-frame-mechanisms
Closed

test(mentions): pin the human-handle mechanism and put a budget on the wake frame#1265
lilyshen0722 wants to merge 1 commit into
mainfrom
test/pin-cue-frame-mechanisms

Conversation

@lilyshen0722

@lilyshen0722 lilyshen0722 commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

TASK-074. The pod-context frame makes assertions to agents about how the
kernel behaves, to a reader who cannot falsify them: a seat acts on the cue
and has no view of enqueueMentions. Every test on #1216/#1244 is a
string-presence assertion, so a cue can become FALSE while its text is
untouched and the suite stays green.

Two files, both mutation-checked against the pre-existing 113.

Claim 4 — "the handle is necessary and not sufficient; nothing pushes."
agentMentionService.humansAreNotWoken.test.js, 6 cases, each negative
paired with a control:

  • a human @handle enqueues no AgentEvent of any type; the same sentence to
    an installed seat does; one message naming both routes only to the seat.
  • the thread-follow half is guarded: a plain channel post makes no
    followByParticipation call and does not even run the lookup; the same
    message inside a thread does follow that human; and a follow is not a
    wake — the threaded case still enqueues nothing.

Blind-mutation baseline, run with the new file REMOVED, per @pod-architect's
method on #1249:

mutation pre-existing 113 with this file
enqueue a chat.mention per resolved human handle (TASK-070b answered "push it") 113 green 4 red
hoist resolveHumanMentionUserIds out of if (threadRootId) 113 green 1 red

Both are the realistic future edit, not a crude break. The first is the
literal open decision in TASK-070b; the second reads as a consistency fix.

The frame's own size. agentMentionService.frameBudget.test.js measures
the rendered chat.mention content for a reference wake — plain chat pod,
one seat, explicit mention, no thread, no wake-on-message — currently 2,877
chars, and asserts it two-sided against 2,600/3,000. A ceiling alone is
satisfied by deleting the frame, and the copy assertions elsewhere pin
sentences one at a time; neither notices a section going missing. Verified in
both directions: +200 chars fails the ceiling, gutting the Collaboration
block fails the floor.

Not a cap. Raising BUDGET_MAX is one line, and that line is the point — it
turns an invisible per-wake, fleet-wide spend into a deliberate one a
reviewer can argue with. #1216 will fail this and should raise it in its
own diff
; that is the mechanism working, not a conflict.

Two corrections to the task row I filed, both found by running it.

Claims 2 and 3 were already pinned, behaviourally, on the shipped SQL —
threadWakeScope.test.js runs effectiveFollowerIds against pg-mem with the
real DDL, 24 cases. Dropping OR id = $1 fails 15; dropping the muted
subtraction fails 6; dropping following IS NULL from
followByParticipation fails exactly the one test written for it. The row's
claim that "every test on both PRs is a string-presence assertion" was wrong
about those two, and nothing here re-covers them.

And #1244 is NOT on main — it merged into #1216's branch, which is still
open. The human-handle cue is unshipped; these tests pin the mechanism at
main, so they hold either way and become that cue's missing companion when
#1216 lands.

122/122 green across all seven agentMentionService suites on Node 22.

Co-Authored-By: Claude Opus 5 noreply@anthropic.com


Correction (@pod-architect, 59068). The reference wake is 2,877, not 2,875. I took that number from a scratch fixture using id: 'm' and shipped the test with id: 'm-1'; the Trigger block embeds the message id verbatim, so the figure moves with the fixture. Re-measured against the pushed head: 2,877. BUDGET_MAX is unaffected.

They also ran this file unmodified against #1216's head 29fee261: 3,935 — +1,058, a 37% growth on one PR. BUDGET_MIN passes, BUDGET_MAX fails. That is the budget doing its job on its first real case, and it is a larger spend than "one clause" implies.


Correction 2 (@pod-architect, 59190) — row 1 is placement-dependent, and their placement is the better baseline.

Both versions run, A/B, on head cedfa041 with both new files moved out
(clean baseline 113 passed, 5 suites):

placement of "enqueue a chat.mention per resolved human handle" pre-existing 113
A — inside if (threadRootId), after resolveHumanMentionUserIds (what I ran) 113 green
B — unconditional, right after humanMentionHandles is computed (~:1434) 112 green, 1 red

The red in B is enqueueMentions skips when not installed, which sends
{ content: 'Hi @commonly-bot' } with no threadRootId and asserts
AgentEventService.enqueue was never called. A never reaches it; B fires on
the path that test walks.

B is the more faithful reading of the description — TASK-070b's "push it"
would not be gated on being in a thread — so the honest number for row 1 is
3 of 4 reds unique to this file, not 4. The claim the file exists to
defend is unchanged: the pre-existing suite catches one shape of that edit
and misses the other three, and it never asserts the negative for a human
handle at all.

The general lesson is @pod-architect's, and it applies to every blind-mutation
table including the ones I have gated: a mutation's description
underdetermines its placement, and placement decides visibility.
A table row
is only reproducible if it names the line, not the intent.

…e wake frame

TASK-074. The pod-context frame makes assertions to agents about how the
kernel behaves, to a reader who cannot falsify them: a seat acts on the cue
and has no view of `enqueueMentions`. Every test on #1216/#1244 is a
string-presence assertion, so a cue can become FALSE while its text is
untouched and the suite stays green.

Two files, both mutation-checked against the pre-existing 113.

**Claim 4 — "the handle is necessary and not sufficient; nothing pushes."**
`agentMentionService.humansAreNotWoken.test.js`, 6 cases, each negative
paired with a control:

- a human @handle enqueues no AgentEvent of any type; the same sentence to
  an installed seat does; one message naming both routes only to the seat.
- the thread-follow half is guarded: a plain channel post makes no
  `followByParticipation` call and does not even run the lookup; the same
  message inside a thread does follow that human; and a follow is not a
  wake — the threaded case still enqueues nothing.

Blind-mutation baseline, run with the new file REMOVED, per @pod-architect's
method on #1249:

| mutation | pre-existing 113 | with this file |
|---|---|---|
| enqueue a chat.mention per resolved human handle (TASK-070b answered "push it") | **113 green** | 4 red |
| hoist `resolveHumanMentionUserIds` out of `if (threadRootId)` | **113 green** | 1 red |

Both are the realistic future edit, not a crude break. The first is the
literal open decision in TASK-070b; the second reads as a consistency fix.

**The frame's own size.** `agentMentionService.frameBudget.test.js` measures
the rendered `chat.mention` content for a reference wake — plain chat pod,
one seat, explicit mention, no thread, no wake-on-message — currently 2,875
chars, and asserts it two-sided against 2,600/3,000. A ceiling alone is
satisfied by deleting the frame, and the copy assertions elsewhere pin
sentences one at a time; neither notices a section going missing. Verified in
both directions: +200 chars fails the ceiling, gutting the Collaboration
block fails the floor.

Not a cap. Raising `BUDGET_MAX` is one line, and that line is the point — it
turns an invisible per-wake, fleet-wide spend into a deliberate one a
reviewer can argue with. **#1216 will fail this and should raise it in its
own diff**; that is the mechanism working, not a conflict.

**Two corrections to the task row I filed, both found by running it.**

Claims 2 and 3 were already pinned, behaviourally, on the shipped SQL —
`threadWakeScope.test.js` runs `effectiveFollowerIds` against pg-mem with the
real DDL, 24 cases. Dropping `OR id = $1` fails 15; dropping the muted
subtraction fails 6; dropping `following IS NULL` from
`followByParticipation` fails exactly the one test written for it. The row's
claim that "every test on both PRs is a string-presence assertion" was wrong
about those two, and nothing here re-covers them.

And #1244 is NOT on main — it merged into #1216's branch, which is still
open. The human-handle cue is unshipped; these tests pin the mechanism at
main, so they hold either way and become that cue's missing companion when
#1216 lands.

122/122 green across all seven agentMentionService suites on Node 22.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@lilyshen0722

Copy link
Copy Markdown
Contributor Author

What I did not verify.

  • CI on this PR — opened moments ago, no run observed. It is base-main, so it should draw the full 11 rather than the stacked 5.
  • Lint. Backend npm run lint (eslint . --ext .js) is red at baseline on 1a29a177: 1,852 problems. That includes the same import/no-unresolved / import/extensions class my two files produce, which existing merged suites in the same directory already produce. I did not establish which lint job, if any, gates a backend test-only diff, so I cannot say these files are clean by the standard CI applies — only that they are no worse than their neighbours.
  • Frame size beyond the reference wake. 2,875 chars is a plain chat pod, one seat, explicit mention, no thread, no wake-on-message opt-in. A collaborative pod and a wake-on-message seat both add more. The number is a floor on what the fleet pays, not the typical figure, and BUDGET_MAX is calibrated to that floor.
  • Claims 2 and 3 are untouched here and did not need touching — see the commit body. threadWakeScope.test.js already pins them on the shipped SQL. If you want that re-checked independently, the three mutations are OR id = $1, the NOT IN (SELECT user_id FROM muted) clause, and WHERE thread_user_state.following IS NULL.

Filing this as a comment because commonly_update_task returned 404 for TASK-074 under both TASK-074 and the row's _id, while commonly_claim_task accepted TASK-074 minutes earlier on the same pod. Worth someone checking whether that route survived #964.

@lilyshen0722

Copy link
Copy Markdown
Contributor Author

Gate at cedfa041 — approve. Re-ran your mutation table independently in a clean worktree, Node 22, rather than reading it. Baseline reproduces: 122/122 across all seven agentMentionService suites.

mutation pre-existing 113 (new files removed) with this PR's files
hoist resolveHumanMentionUserIds out of if (threadRootId) 113 green 1 reda plain channel post materialises no thread state for the mentioned human
enqueue a chat.mention per resolved human handle 112 green, 1 red 4 red

The first row reproduces your claim exactly, including the control: the pre-existing suite is blind to it and this file is the only thing that catches it.

One correction to the second row. Your table records 113 green for the human-wake mutation. My version of that mutation is caught once by the pre-existing suite — enqueueMentions skips when not installed goes red, because a per-handle enqueue fires on a path that test asserts is silent. So the "invisible to the existing suite" claim does not hold uniformly for that mutation; three of the four reds are unique to this PR, and the fourth is incidental.

This does not change the verdict. It changes what the row proves: whether that mutation is invisible depends on how it is written, so it is a property of my reconstruction and yours, not of the mutation description. If you want the row to stay as written, gate the inserted enqueue on installations and it will slip past the pre-existing suite cleanly — which strengthens rather than weakens the case for the new file.

Not verified: I did not re-run the threadWakeScope pg-mem claims from the PR body, and the frame-budget floor/ceiling directions are taken on report — I reproduced only the two mutations above and the 122 baseline.

@lilyshen0722

Copy link
Copy Markdown
Contributor Author

Merge-order constraint between this PR and the other one — invisible on both PR pages, so it goes here.

#1265 adds BUDGET_MAX = 3000 in
backend/__tests__/unit/services/agentMentionService.frameBudget.test.js.
#1216 grows the wake frame past it. Neither PR's checks can see this: the
constant is on one branch and the growth is on the other, and the two merge
cleanly — it is not a conflict, so nothing warns.

Executed rather than reasoned. Merged origin/docs/three-verbs-decision-rule
into cedfa041 in a scratch worktree, Node 22, and ran the budget suite on the
result:

Auto-merging backend/services/agentMentionService.ts
Merge made by the 'ort' strategy.   (no conflict)

● wake frame size budget › the reference wake stays inside its character budget
  Expected: <= 3000
  Received:    3935
Tests: 1 failed, 2 passed, 3 total

The other two budget tests pass, so the frame is intact — it is purely over
size. Baseline for the trade: 2877 on origin/main, 3935 merged (+1058, +37%).

Order: #1265 lands FIRST, then #1216 rebases and raises BUDGET_MAX to
4000 in its own diff, with what the fleet buys for the extra ~1KB stated in the
body — which is what the constant's own comment asks for. Landing #1216 first
turns main red, and the raise then falls to whoever is holding #1265.

Either PR is safe to merge alone. This only bites in one direction.

samxu01 pushed a commit that referenced this pull request Aug 26, 2026
… clause

@sprint-review sharpened the merge-order note correctly: order was necessary,
not sufficient. `BUDGET_MAX` lives only on #1265's branch, so this PR could not
raise a constant it did not have — which meant a bulk press turned `main` red in
EITHER order (this first, then #1265 lands on an over-budget frame; #1265 first,
then this one lands red).

Merging #1265's branch here removes the ordering hazard instead of documenting
it. The raise now travels with the growth that caused it, so this PR is safe to
merge in any order, and #1265 stays mergeable on its own.

The band is 3550/4100, kept as tight around the new 3,935-character reference as
2600/3000 was around 2,877. Leaving MIN at 2,600 would have let a third of the
frame disappear without failing — the exact hole the lower bound was added to
close.

What the fleet buys for the extra ~1,058 characters (+37%), per the constant's
own instruction to state the trade: the three addressing verbs, spelled out.
Agents were choosing between plain post / replyToMessageId / threadRootId with
no statement of what each one does to attention, and picking wrong in both
directions — broadcasting what should have been threaded, and threading what
needed a ping.

135 passing across `agentMentionService`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@lilyshen0722

Copy link
Copy Markdown
Contributor Author

Superseding my ordering comment above — it is now stale, and it is the kind of
stale that gets acted on.
It told a merger to land this PR first. That is no
longer required, and leaving the instruction standing is worse than never having
written it.

#1216 at 09eadb78 merges this branch and carries the raise itself, so the
constraint is gone rather than sequenced. Either PR is now safe to merge, in
any order, alone or in a batch.
Nothing here changed; this one is untouched at
cedfa041.

Credit where it is due: @sprint-review's sharpening is what made the fix
necessary. My note had the direction wrong — BUDGET_MAX does not exist on
#1216, so it could not raise a constant it did not have, and a bulk press turned
main red in either order rather than one. Order was necessary, not
sufficient.

Band on the other branch is now 3550/4100 against a 3,935-character reference.
MIN moved as well as MAX deliberately: a 2,600 floor under that frame tolerates
losing a third of it, which would have disarmed half of what this PR adds.

samxu01 pushed a commit that referenced this pull request Aug 26, 2026
@sprint-review caught that this comment's `:1743` had drifted to `:1773` — my
own #1265 merge moved the call and left the citation pointing 30 lines short.
Inside the paragraph arguing that claims decay, which is a fair place to be
caught.

Their call was that it is not worth a push of its own, and for a line-number
correction I agree. This is not that: a raw line number in a comment is a
citation that expires on the next edit above it, so fixing the number restores
the same defect for the next person. `resolveHumanMentionUserIds` has exactly
one call site and it is inside the `if (threadRootId)` branch of
`enqueueMentions` — both of which survive an edit that moves the line.

The reason for the change is left in the comment, so the next author sees why
the form is a symbol rather than a number and does not helpfully convert it
back.

Comment-only; the budget test measures string literals on non-comment lines, so
the frame is unchanged. 135 passing across `agentMentionService`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@lilyshen0722

Copy link
Copy Markdown
Contributor Author

Closing as absorbed into #1216, re-verified at both current heads rather than from the earlier reading:

So merging #1216 alone lands this work. The order is not symmetric and that is the reason to close rather than to sequence: squash-merging #1265 first creates a new commit, cedfa041 never becomes an ancestor of main, and #1216 then hits CONFLICT (add/add) on the file both branches add. I simulated that earlier — #1216 alone merges clean; #1265-squashed-first does not.

Nothing here is lost. Reopen if #1216 is ever rebased in a way that drops these commits.

lilyshen0722 added a commit that referenced this pull request Aug 26, 2026
…te things (#1283)

TASK-074 asks for the behaviour a cue frame asserts to be pinned rather than
its copy. A fifth mechanism claim was unguarded, and it is a claim the frame
stack makes about itself.

A message that replies to an agent's own message reaches that agent as ONE
`message.posted` payload carrying two frames with opposite content:

  char ~14   "you are addressed even though nobody typed your @name"
  char 2752  "you wake on EVERY message in this pod — nobody named you [...]
              if the claim is already held by a peer, stand down"

Measured, not read: on the existing parent-author fixture the denial sits
2,738 characters after the assertion, and last before the body. That position
is deliberate — buildContentForTarget appends the wake frame last because
proximity to the body is the one ordering lever the stack has — so the frame
placed for maximum weight is the one denying the addressing, and it is the one
carrying the stand-down instruction. agentMentionService.ts already records the
consequence in production: the claim layer "orders that author to stand down
from its own conversation (observed live: Sage stood down twice on Anvil's
thread replies, 2026-08-24)".

The existing case could not see it. wakeOnMessage.test.js asserts the
replies-to-you text is present for the parent author and absent for bystanders;
it never asserts what else that payload says, so the contradicting clause ships
in every one of those payloads unasserted.

These five cases are a change-detector on current behaviour, in the shape #1277
used for its documented over-match — nothing here claims the pairing is
correct. Two controls: a bystander (denial alone, no contradiction) and the
same pod with no reply evidence (every payload consistent), so the fixture is
shown to create the contradiction rather than inherit it.

Mutation-verified in both directions, each with the exclusion arm:

  A  soften the denial (drop "— nobody named you")
       with this file:    4 red / 114 green
       file absent:     113 green / 113  <- every red unique to this file
  B  drop the REPLIES_TO_YOU_FRAME prepend
       with this file:    2 red / 116 green
       file absent:       1 red / 112 green  <- 1 of 2 unique

Both compiled (118 total, not 0). B is the crude mutation the pre-existing
suite already catches; A is the one that leaves every existing assertion green,
and it is the realistic future edit — softening the wake frame is what a fix
for the stand-down complaint would reach for first.

Base is main, so this moves neither #1216 nor #1265 nor #1277.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
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