feat(telegram): a tagged group turn knows the group's recent conversation (abilityai/trinity-enterprise#600) - #2728
trinity-ability wants to merge 1 commit into
Conversation
…tion (abilityai/trinity-enterprise#600) In a Telegram group the agent still speaks only per the group's trigger mode, but it now knows what the group has been saying when it does. Un-tagged messages the bot receives are recorded as attributed context without an agent turn (no reply, typing, reaction or rate-limit charge); every group turn is built as sender identity + a bounded, attributed "recent group conversation" block + the tagged message. The group session is keyed per chat, not per sender — the re-decision #1649 pinned a test to force — so proactive broadcasts now land in the session a reply reads. DMs keep their per-user key: DM history structurally cannot reach a group reply, which is what the old fresh-context rule protected. Honest per-group status (sees all / tagged only / not confirmed / off) with the next action named; getMe's can_read_all_group_messages stored at connect, Verify and bot-added. Zero-config reply-quote slice works with Privacy Mode on. Per-group context_enabled off switch (human-only arm). The transport gates its command branch on observe_only: once un-tagged messages parse instead of returning None, a bare /reset from any member would otherwise have fired — trigger rules must not change. /reset@bot is now recognised as tagged. The tagged user turn is persisted before execution so stored order matches what the group saw. Dual-track schema: can_read_all_group_messages on telegram_bindings, last_untagged_seen_at + context_enabled on telegram_group_configs (migrations.py telegram_group_context + Alembic 0059). MCP list_channel_groups passes context_status. J12 journey skeleton. Fixes abilityai/trinity-enterprise#600 Follow-up filed: #2727 (pre-existing update_id dedup race across workers) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Resolve by running |
|
Resolve by merging |
|
merge-train 2026-09-12: not on this train. Three things, all the PR's own:
Rides the next train once the test is green, the ratchet is flat or paid, and |
|
merge-train 2026-09-13: not on this train. Three failures the PR owns, plus a schema fork:
Also CONFLICTING with |
|
🚧 Alembic head check could not run — this PR conflicts with
Merge Advisory — this check does not block merge. · head_sha: |
|
merge-train: ejected this run — rides the next train once fixed. Three things, all on the branch: (1) Alembic fork (#2068 class): |
merge-train: not on this train — two blockersRides the next train once both are fixed. Neither is visible on this PR's checks, which is the notable part. 1. Live #2068 Alembic fork —
|
Summary
mention,all,observe— is built as sender identity + a bounded, attributed "recent group conversation" block (newest 40 within 24 h) + the tagged message. Trigger rules are unchanged.{bot_id}:group:{chat_id},:topic:{thread}in forum supergroups). That is the re-decision bug: proactive group messages (send_group_message) never persisted to channel session history #1649 pinned a test to force, and it closes bug: proactive group messages (send_group_message) never persisted to channel session history #1649's "agent can't recall its own broadcast" limitation for free. DMs keep their per-user key, so DM history structurally cannot reach a group reply — the reason the old fresh-context rule existed still holds. MEM-001 stays group-excluded.getMeflag, so admin bots read right) / Tagged messages only (Privacy Mode on; hint names/setprivacy→ Disable, re-add the bot, or make it admin) / Not confirmed yet / Context off.getMe.can_read_all_group_messagesis stored at connect, Verify, and when the bot is added to a group.[Replying to X: "…"], which works with Privacy Mode on; and a per-groupcontext_enabledoff switch (default on, owner/human-only likeallow_proactive) so "how do I turn this off for the legal channel" is never "remove the bot".None, a bare/resetor/helpfrom any member would have fired un-tagged. The command branch is gated onobserve_only;/reset@bot(now recognised as tagged) is the deliberate group-reset gesture. Both independent reviewers found this; the lesson is indocs/memory/learnings.md.Changes
Backend
services/telegram_group_context.py(new) — bounds + env overrides,format_group_history(NO_REPLY filter, one-line + 500-char clamp, label/delimiter neutralisation,[agent]marker),reply_quote_line,group_context_status,fetch_can_read_all_group_messages.adapters/telegram_adapter.py— group session key;parse_messagemarksuntagged/observe_only, treats/cmd@botas tagged, sets group speaker labels +topic_id;group_context_enabled/note_untagged_seenhooks; getMe refresh on bot-added (best-effort, after the config write).adapters/base.py— the two hook defaults.adapters/transports/telegram_webhook.py— command branch gated onobserve_only.adapters/message_router.py— step 0_record_observed_message; step 7 group history block + reply quote + persist-before-execute; step 11 skips the already-persisted user row; untagged evidence stamp.db/public_chat.py(since=,prune_session),db/telegram_channels.py(columns,touch_group_untagged_seen,set_can_read_all_group_messages,context_enabled),database.pyfacade.db/schema.py,db/tables.py,db/migrations.py::telegram_group_context,migrations/versions/0059_telegram_group_context.py—telegram_bindings.can_read_all_group_messages,telegram_group_configs.last_untagged_seen_at,telegram_group_configs.context_enabled DEFAULT 1. Single Alembic head verified.routers/telegram.py— flag stored at connect/Verify; listing + PUT returncontext_status/context_hint;context_enabledarm is human-only.models.pyfields.services/channel_history.pydocstring.Frontend / MCP
TelegramChannelPanel.vue—BaseBadgestatus + hint per group, Group context checkbox, Verify reloads groups.channels.tslist_channel_groupspassescontext_status(Invariant feat: SMARTS trading pipeline with Telegram notifications and Miro visualization #13).Docs / journey
TGRAM-GROUP-CTX(§15.1e-ctx), feature flow (Group Conversation Context section, flow diagram, trigger matrix, schema, UI), user docs (trigger table gains a Context column; status badges;/reset@bot), J12 skeleton intests/journeys/catalog.yaml(built: no; the generator now renders a fully-qualified private-tracker ref),docs/memory/learnings.md, CSO diff report.Test Plan
tests/unit/test_ent600_telegram_group_context.py— 49 tests: parser marks, session keys, renderer (bounds, NO_REPLY, forgery), status matrix, reply quote, router observe path (records / no execute / no rate-limit / locked group / toggle off / bare command / prune cadence / db failure swallowed), group turn context + persist ordering + DM unchanged, transport command gate, adapter hooks, getMe best-effort,since/prune on a tmp SQLite engine. Written red-first: 44/49 failed on behaviour before the implementation.scripts/ci/check_alembic_heads.py— 60 revisions, 1 head.@bot summariseanswers from it; panel shows Sees all messages. Same bot with Privacy Mode on → today's behaviour, panel shows Tagged messages only with the BotFather hint.Notes for review
last_update_idcross-worker dedup race the engineering review raised is pre-existing and needs a different mechanism — filed separately as bug(telegram): update_id dedup is read-then-write per worker, so an out-of-order webhook delivery drops a legitimate message #2727.Fixes abilityai/trinity-enterprise#600
Also lifts the recall limitation #1649 recorded (see
test_1649_group_message_history.py)🤖 Generated with Claude Code