Delete identical comment campaigns across linked channel posts (#81) #37

Merged
backmeupplz merged 3 commits from fix/81-repeat-campaign into main 2026-10-12 17:31:05 +00:00
backmeupplz commented 2026-10-12 17:09:28 +00:00 (Migrated from github.com)

Agentboard #81 (https://agentboard.icefish-betta.ts.net/#81). Implements Nikita's option B ("yeah do B").

What

A bot-side repeat-campaign rule. When the same actor posts an identical comment (NFKC + lowercase + whitespace-collapsed, 40+ chars) under 3+ distinct linked channel posts within 10 minutes, every qualifying copy is deleted, including earlier copies the model kept. Later copies in the same window are deleted as they arrive.

  • Copies are tracked in a dedicated per-chat, per-actor CampaignLedger (10-minute TTL, 20 copies). It is independent of the classifier's kept-message history, so copies already deleted by the model, the cache or the rule still count towards the posts. A copy edited into an ineligible message is forgotten. A copy whose delete was attempted is never retried within the window. history.clear and the 10-message history cap don't hide copies, and a classifier failure doesn't skip the rule.

  • A "post" is only a same-chat is_automatic_forward reply whose sender_chat is a channel. Replies to another member's message (for example their own job request), non-linked channel chatter, and messages that are not replies never count and are never deleted by this rule.

  • Known gap (deliberate, conservative): a copy that replies to any other comment is exempt, because the bot can't tell a member's request from a random comment. A spammer replying to random comments under each post evades this rule; the model still judges each copy.

  • In scope by decision: one actor pasting any identical 40+ char text under 3 posts within 10 minutes is deleted whatever its content, whether an offer, a job search or a warning (Nikita accepted this for "looking for work"). Several different members echoing the same warning are untouched.

  • Only identical qualifying copies are deleted. Interleaved messages from the same actor and other actors' histories are left alone.

  • When the model deletes a copy itself, the rule also deletes the earlier kept copies that its weak links (~0.72 < 0.75) would have missed.

  • The Laya model, checkpoint, prompt, classifier input/context serialization and the 0.80 gate are all unchanged. No forced score. The classifier input and its kept-message history are untouched; the ledger is separate state.

  • Content-free repeat_campaign_detected log and rule on spam_deleted. Audit snapshots exist for every deleted copy. Stats source stays jev.

  • Cache: CACHE_POLICY_REVISION is a digest of bot.ts and friends, so it changes automatically. campaign.ts was added to the digest list.

Why the model can't do this

See the #81 findings: identical prior copies lower Laya's score, because its training history only ever held kept messages (survivorship bias). Retraining is forbidden by decision.

Tests

  • src/campaign.test.ts: normalization, post detection, ledger threshold/selection, deleted-copy counting, edits, TTL expiry and bound.
  • src/bot.test.ts: real grammY routing with channel-post reply entities. Covers the incident (model keep 0.79/0.61/0.54 → deletes 1,2,3, and a 4th copy deleted on arrival), the model-delete widening, a model-deleted first copy still counting, classifier failure, 11 messages of chatter between copies, members echoing a warning/report (kept), the same actor mass-posting a warning (deleted), an edited-away copy (forgotten), a failed delete (not retried), two posts / same post / edits / other actor (not triggered, other history kept), requested-offer replies to members, interleaved messages, short chatter, non-linked channel, distinct controls, and audit per copy.
  • src/campaign.live.test.ts (gated RUN_LIVE_LAYA=1 JEV_URL=…): before/after on the approved ckpt-v2 (laya-jev-ckpt-v2-examples-856e52e3, checkpoint sha d899eecd…) at 0.80, through the real handler. SYNTHETIC reconstruction:
    • incident, no bio: model keep 0.7723 / 0.6236 / 0.4989 → after: deletes 1,2,3
    • incident, decorative bio: keep 0.4711 / 0.4021 / 0.4456 → after: deletes 1,2,3
    • controls never trigger the rule. Pre-existing model verdicts are unchanged: requested offer to member requests 0.892 delete (no bio) / 0.6555 keep (bio); job seeker 0.9263 (no bio) / 0.8203 (bio) delete; wage, warning, report and chatter are kept.

bun run check: 486 pass, 0 fail.

Agentboard #81 (https://agentboard.icefish-betta.ts.net/#81). Implements Nikita's option B ("yeah do B"). ## What A bot-side repeat-campaign rule. When the same actor posts an identical comment (NFKC + lowercase + whitespace-collapsed, 40+ chars) under **3+ distinct linked channel posts** within 10 minutes, every qualifying copy is deleted, including earlier copies the model kept. Later copies in the same window are deleted as they arrive. - Copies are tracked in a dedicated per-chat, per-actor `CampaignLedger` (10-minute TTL, 20 copies). It is independent of the classifier's kept-message history, so copies already deleted by the model, the cache or the rule still count towards the posts. A copy edited into an ineligible message is forgotten. A copy whose delete was attempted is never retried within the window. `history.clear` and the 10-message history cap don't hide copies, and a classifier failure doesn't skip the rule. - A "post" is only a same-chat `is_automatic_forward` reply whose `sender_chat` is a channel. Replies to another member's message (for example their own job request), non-linked channel chatter, and messages that are not replies never count and are never deleted by this rule. - Known gap (deliberate, conservative): a copy that replies to *any* other comment is exempt, because the bot can't tell a member's request from a random comment. A spammer replying to random comments under each post evades this rule; the model still judges each copy. - In scope by decision: one actor pasting any identical 40+ char text under 3 posts within 10 minutes is deleted whatever its content, whether an offer, a job search or a warning (Nikita accepted this for "looking for work"). Several *different* members echoing the same warning are untouched. - Only identical qualifying copies are deleted. Interleaved messages from the same actor and other actors' histories are left alone. - When the model deletes a copy itself, the rule also deletes the earlier kept copies that its weak links (~0.72 < 0.75) would have missed. - The Laya model, checkpoint, prompt, classifier input/context serialization and the 0.80 gate are all unchanged. No forced score. The classifier input and its kept-message history are untouched; the ledger is separate state. - Content-free `repeat_campaign_detected` log and `rule` on `spam_deleted`. Audit snapshots exist for every deleted copy. Stats source stays `jev`. - Cache: `CACHE_POLICY_REVISION` is a digest of `bot.ts` and friends, so it changes automatically. `campaign.ts` was added to the digest list. ## Why the model can't do this See the #81 findings: identical prior copies *lower* Laya's score, because its training history only ever held kept messages (survivorship bias). Retraining is forbidden by decision. ## Tests - `src/campaign.test.ts`: normalization, post detection, ledger threshold/selection, deleted-copy counting, edits, TTL expiry and bound. - `src/bot.test.ts`: real grammY routing with channel-post reply entities. Covers the incident (model keep 0.79/0.61/0.54 → deletes 1,2,3, and a 4th copy deleted on arrival), the model-delete widening, a model-deleted first copy still counting, classifier failure, 11 messages of chatter between copies, members echoing a warning/report (kept), the same actor mass-posting a warning (deleted), an edited-away copy (forgotten), a failed delete (not retried), two posts / same post / edits / other actor (not triggered, other history kept), requested-offer replies to members, interleaved messages, short chatter, non-linked channel, distinct controls, and audit per copy. - `src/campaign.live.test.ts` (gated `RUN_LIVE_LAYA=1 JEV_URL=…`): before/after on the approved ckpt-v2 (`laya-jev-ckpt-v2-examples-856e52e3`, checkpoint sha d899eecd…) at 0.80, through the real handler. SYNTHETIC reconstruction: - incident, no bio: model keep 0.7723 / 0.6236 / 0.4989 → **after: deletes 1,2,3** - incident, decorative bio: keep 0.4711 / 0.4021 / 0.4456 → **after: deletes 1,2,3** - controls never trigger the rule. Pre-existing *model* verdicts are unchanged: requested offer to member requests 0.892 delete (no bio) / 0.6555 keep (bio); job seeker 0.9263 (no bio) / 0.8203 (bio) delete; wage, warning, report and chatter are kept. `bun run check`: 486 pass, 0 fail.
Sign in to join this conversation.
No description provided.