Skip to content

[codex] Add WhatsApp observed group context - #38750

Open
sweetcornna wants to merge 2 commits into
NousResearch:mainfrom
sweetcornna:codex/worker-38710-whatsapp-observe-unmentioned
Open

sweetcornna wants to merge 2 commits into
NousResearch:mainfrom
sweetcornna:codex/worker-38710-whatsapp-observe-unmentioned

Conversation

@sweetcornna

@sweetcornna sweetcornna commented Jun 4, 2026 •

Copy link
Copy Markdown

Summary

  • add WhatsApp observe_unmentioned_group_messages config/env bridging
  • store unmentioned WhatsApp group chatter as observed context when mention-gating skips dispatch
  • align later mentioned group turns to the shared group source so they can see observed context
  • preserve normal group sender attribution when observe mode is enabled but mention gating does not apply

Root 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_chats could lose the sender user_id / user_name and 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 -> passed
  • git diff --check -> passed

@sweetcornna
sweetcornna marked this pull request as ready for review June 4, 2026 05:38
@alt-glitch alt-glitch added type/feature New feature or request comp/gateway Gateway runner, session dispatch, delivery platform/whatsapp WhatsApp Business adapter P3 Low — cosmetic, nice to have labels Jun 4, 2026
@sweetcornna
sweetcornna force-pushed the codex/worker-38710-whatsapp-observe-unmentioned branch from 7a79937 to 1c7bb35 Compare June 4, 2026 10:05
sweetcornna and others added 2 commits June 4, 2026 23:00
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.
@sweetcornna
sweetcornna force-pushed the codex/worker-38710-whatsapp-observe-unmentioned branch from 1c7bb35 to 746bed2 Compare June 4, 2026 15:00

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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. 5600105478ffde29d7566b45421b100eaa29c4ef renamed gateway/platforms/whatsapp.py to plugins/platforms/whatsapp/adapter.py and moved the YAML bridge into the plugin hook at plugins/platforms/whatsapp/adapter.py:1712; the PR cannot apply as-is.
  • The proposed shared session source at PR gateway/platforms/whatsapp.py:1481 has no channel_prompt. The runner only separates observed records when it sees the Telegram marker (gateway/run.py:779-790); otherwise observed rows 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.

Comment thread gateway/config.py
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:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@teknium1 teknium1 added sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 14, 2026
FarelRA added a commit to FarelRA/hermes-agent that referenced this pull request Sep 6, 2026
…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.
FarelRA added a commit to FarelRA/hermes-agent that referenced this pull request Sep 6, 2026
…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.
FarelRA added a commit to FarelRA/hermes-agent that referenced this pull request Sep 6, 2026
…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.
FarelRA added a commit to FarelRA/hermes-agent that referenced this pull request Sep 8, 2026
…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.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/gateway Gateway runner, session dispatch, delivery P3 Low — cosmetic, nice to have platform/whatsapp WhatsApp Business adapter sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: Add observe_unmentioned_group_messages to WhatsApp platform

3 participants