Repository navigation
[codex] Add WhatsApp observed group context - #38750
sweetcornna wants to merge 2 commits into
Conversation
7a79937 to
1c7bb35
Compare
Only apply observed-group source attribution when the group is actually in mention-gated observe mode. Normal open groups and free-response groups should keep the sender source and unprefixed text.
1c7bb35 to
746bed2
Compare
teknium1
left a comment
There was a problem hiding this comment.
Thanks for the focused WhatsApp parity work. The reported intake gap is still present on current main: plugins/platforms/whatsapp/adapter.py:1333 drops mention-gated messages.
Problems
- This branch predates the WhatsApp plugin migration.
5600105478ffde29d7566b45421b100eaa29c4efrenamedgateway/platforms/whatsapp.pytoplugins/platforms/whatsapp/adapter.pyand moved the YAML bridge into the plugin hook atplugins/platforms/whatsapp/adapter.py:1712; the PR cannot apply as-is. - The proposed shared session source at PR
gateway/platforms/whatsapp.py:1481has nochannel_prompt. The runner only separates observed records when it sees the Telegram marker (gateway/run.py:779-790); otherwiseobservedrows replay as normal user history (gateway/run.py:859-860). Telegram adds its context-only prompt when it attributes a triggering event (plugins/platforms/telegram/adapter.py:7135-7149).
Suggested changes
- Port the adapter/config work into the WhatsApp plugin and implement a platform-neutral observed-context marker/wrapper (or a deliberately bounded WhatsApp buffer) with an end-to-end runner test.
- Document the opt-in setting and retention behavior in
website/docs/user-guide/messaging/whatsapp.md.
The #38710 discussion also identifies this storage-design choice. This is an automated hermes-sweeper review.
| if "exclusive_bot_mentions" in platform_cfg: | ||
| bridged["exclusive_bot_mentions"] = platform_cfg["exclusive_bot_mentions"] | ||
| if plat == Platform.TELEGRAM and "observe_unmentioned_group_messages" in platform_cfg: | ||
| if plat in {Platform.TELEGRAM, Platform.WHATSAPP} and "observe_unmentioned_group_messages" in platform_cfg: |
There was a problem hiding this comment.
WhatsApp config bridging is now plugin-owned: current main registers plugins/platforms/whatsapp/adapter.py::_apply_yaml_config() for this path. Please port this setting into that hook instead of restoring a WhatsApp-specific branch in gateway/config.py.
| media_types=media_types, | ||
| ) | ||
| if not skip_gate: | ||
| event = self._apply_whatsapp_group_observe_attribution(event) |
There was a problem hiding this comment.
Sharing the source is insufficient here: no channel_prompt marks this as context-only. Current gateway/run.py:779-790 extracts observed rows only for the Telegram marker, so these persisted rows will replay as ordinary user turns. Add a platform-neutral observed-context handoff and an end-to-end runner test.
…a, and read-time bounds - gateway/run.py: generalize the Telegram-only observed-context marker check to a platform-neutral marker tuple so WhatsApp observed rows are separated from replayable history the same way Telegram's are (previously they replayed as ordinary user turns); header text is platform-neutral. - Read-time bounds: gateway.observed_context_max_chars (default 8000) / observed_context_max_rows (default 200) from config.yaml cap the API-only observed block to the most recent rows, with an omission note. The block is API-only on the current turn, so compression can never shrink it — the bound must be structural. - Attribution scoping: _apply_whatsapp_group_observe_attribution now applies only to mention-gated, allowlisted groups; open / free-response groups keep per-user source and clean text (ports the NousResearch#38750 post-audit fix). - Real media in observations: the observe path downloads media to the local cache and records inspectable references ([image: path] + vision_analyze pointer, [voice note: path], [document: path]) instead of kind placeholders; failed downloads degrade to an unavailable note. - End-to-end runner tests through the real _build_gateway_agent_history + wrapper: separation for both platforms, fail-closed replay without a marker, bound relationships, alternation survival, unwrapped persistence contract.
…ng compaction - gateway/observed_context.py (new platform-neutral sibling): marker constants (single source for Telegram + WhatsApp prompts), injection limits (gateway.observed_context_max_chars default 512,000 ~ 128K tokens for common 256K-window models; observed_context_max_rows default 4,000), read assembly, and rolling compaction. - gateway/run.py: generalize the Telegram-only observed-context marker check to the shared predicate so WhatsApp observed rows are separated from replayable history the same way Telegram's are (previously they replayed as ordinary user turns); header text is platform-neutral; assembly delegates to the sibling. - Rolling compaction instead of truncation: when the verbatim observed set overflows roughly half the char budget, a background pass summarizes the OLDEST rows via the aux compression LLM (task=routing, secret redaction, per-row and total input bounds) into ONE iterative summary row appended to the transcript. Originals stay searchable; nothing is destroyed. Later passes update the summary in place. Read path serves latest summary + verbatim rows after the watermark; the hard cap (oldest dropped, omission note) remains as the last-resort valve. Per-session lock + failure cooldown; cheap pending-chars gate so a busy group does not reload its transcript per message. - Write-side trigger in the shared WhatsApp mixin and the Telegram adapter (fire-and-forget, never raises); prompt markers built from the shared constants. - Attribution scoping: _apply_whatsapp_group_observe_attribution applies only to mention-gated, allowlisted groups; open / free-response groups keep per-user source and clean text (ports the NousResearch#38750 post-audit fix). - Real media in observations: the observe path downloads media to the local cache and records inspectable references ([image: path] + vision_analyze pointer, [voice note: path], [document: path]) instead of kind placeholders; failed downloads degrade to an unavailable note. - End-to-end runner tests through the real _build_gateway_agent_history + wrapper (separation for both platforms, fail-closed replay without a marker, cap relationships, alternation survival, unwrapped persistence contract) plus compaction unit tests (watermark assembly, iterative update, redaction bounds, aux-failure safety, scheduling gate). Sabotage-checked.
…ng compaction - gateway/observed_context.py (new platform-neutral sibling): marker constants (single source for Telegram + WhatsApp prompts), injection limits (gateway.observed_context_max_chars default 512,000 ~ 128K tokens for common 256K-window models; observed_context_max_rows default 4,000), read assembly, and rolling compaction. - gateway/run.py: generalize the Telegram-only observed-context marker check to the shared predicate so WhatsApp observed rows are separated from replayable history the same way Telegram's are (previously they replayed as ordinary user turns); header text is platform-neutral; assembly delegates to the sibling. - Rolling compaction instead of truncation: when the verbatim observed set overflows roughly half the char budget, a background pass summarizes the OLDEST rows via the aux compression LLM (task routing, secret redaction, per-row and total input bounds) into ONE iterative summary row appended to the transcript. Originals stay searchable; nothing is destroyed. Later passes update the summary in place. Read path serves latest summary + verbatim rows after the watermark; the hard cap (oldest dropped, omission note) remains as the last-resort valve. Per-session lock + failure cooldown; cheap pending-chars gate so a busy group does not reload its transcript per message. - Write-side trigger in the shared WhatsApp mixin and the Telegram adapter (fire-and-forget, never raises); prompt markers built from the shared constants. - Attribution scoping: _apply_whatsapp_group_observe_attribution applies only to mention-gated, allowlisted groups; open / free-response groups keep per-user source and clean text (ports the NousResearch#38750 post-audit fix). - Real media in observations: the observe path downloads media to the local cache and records inspectable references ([image: path] + vision_analyze pointer, [voice note: path], [document: path]) instead of kind placeholders; failed downloads degrade to an unavailable note. - End-to-end runner tests through the real _build_gateway_agent_history + wrapper (separation for both platforms, fail-closed replay without a marker, cap relationships, alternation survival, unwrapped persistence contract) plus compaction unit tests (watermark assembly, iterative update, redaction bounds, aux-failure safety, scheduling gate). Sabotage-checked.
…ng compaction - gateway/observed_context.py (new platform-neutral sibling): marker constants (single source for Telegram + WhatsApp prompts), injection limits (gateway.observed_context_max_chars default 512,000 ~ 128K tokens for common 256K-window models; observed_context_max_rows default 4,000), read assembly, and rolling compaction. - gateway/run.py: generalize the Telegram-only observed-context marker check to the shared predicate so WhatsApp observed rows are separated from replayable history the same way Telegram's are (previously they replayed as ordinary user turns); header text is platform-neutral; assembly delegates to the sibling. - Rolling compaction instead of truncation: when the verbatim observed set overflows roughly half the char budget, a background pass summarizes the OLDEST rows via the aux compression LLM (task routing, secret redaction, per-row and total input bounds) into ONE iterative summary row appended to the transcript. Originals stay searchable; nothing is destroyed. Later passes update the summary in place. Read path serves latest summary + verbatim rows after the watermark; the hard cap (oldest dropped, omission note) remains as the last-resort valve. Per-session lock + failure cooldown; cheap pending-chars gate so a busy group does not reload its transcript per message. - Write-side trigger in the shared WhatsApp mixin and the Telegram adapter (fire-and-forget, never raises); prompt markers built from the shared constants. - Attribution scoping: _apply_whatsapp_group_observe_attribution applies only to mention-gated, allowlisted groups; open / free-response groups keep per-user source and clean text (ports the NousResearch#38750 post-audit fix). - Real media in observations: the observe path downloads media to the local cache and records inspectable references ([image: path] + vision_analyze pointer, [voice note: path], [document: path]) instead of kind placeholders; failed downloads degrade to an unavailable note. - End-to-end runner tests through the real _build_gateway_agent_history + wrapper (separation for both platforms, fail-closed replay without a marker, cap relationships, alternation survival, unwrapped persistence contract) plus compaction unit tests (watermark assembly, iterative update, redaction bounds, aux-failure safety, scheduling gate). Sabotage-checked.
Summary
observe_unmentioned_group_messagesconfig/env bridgingRoot cause
WhatsApp mention-gated group messages were dropped at intake when
_should_process_message()returned false. Unlike Telegram, there was no observe-only path to append skipped group chatter to the shared group transcript for a later direct mention.Post-audit, the observe attribution path also needed to be scoped to the same mention-gated observe mode. Without that, open groups and
free_response_chatscould lose the senderuser_id/user_nameand receive observed-context text prefixes even though they are normal dispatching conversations.Fixes #38710.
Validation
.venv\\Scripts\\python.exe -m pytest -o addopts='' tests/gateway/test_whatsapp_group_gating.py -q --timeout-method=thread->30 passed.venv\\Scripts\\python.exe -m ruff check gateway/config.py gateway/platforms/whatsapp.py tests/gateway/test_whatsapp_group_gating.py->All checks passed!.venv\\Scripts\\python.exe -m py_compile gateway/config.py gateway/platforms/whatsapp.py tests/gateway/test_whatsapp_group_gating.py-> passedgit diff --check-> passed