feat: implicit handover on owner reply + sliding TTL - #3
Conversation
Until now handover was activated only via the agent-callable
``trigger_handover`` tool, and the TTL was set once at activation and
never refreshed. That meant the handover could expire mid-conversation
while the owner was still actively typing — a footgun in long manual
conversations.
This adds two related behaviours, both driven by the
``event.metadata['whatsapp_from_owner']`` signal that the WhatsApp
adapter now sets when ``WHATSAPP_FORWARD_OWNER_MESSAGES=true`` on the
bridge (see hermes-agent SHA b1e7628d7):
1. Implicit activation. Owner-typed inbound on a cold customer chat
activates a handover with reason='owner_reply',
activated_by='owner_implicit', and no Telegram notify (the owner is
already in the chat — pinging them would be noise).
2. Sliding TTL. Owner-typed inbound on a hot chat slides expires_at to
now + cfg.timeout_minutes*60 via a single SQL UPDATE
(HandoverStore.touch). Idempotent, no-op on cold rows or rows with
no TTL set.
Order matters in the rule:
/takeback (with owner check) -> deactivate
whatsapp_from_owner -> activate-or-extend
active handover (customer) -> silent ingest (existing behaviour)
The /takeback branch wins so that an owner sending the exit command
ends the handover even though the same fromMe inbound also carries the
owner-flag metadata.
Behaviour is unchanged for any profile that doesn't opt into
WHATSAPP_FORWARD_OWNER_MESSAGES on the hermes-agent side: the metadata
flag never appears, and the new code paths are inert.
Tests:
- touch() is no-op on cold rows / no-TTL rows; slides expires_at
forward on hot rows.
- Owner-implicit activation on cold chat + no notify.
- TTL slide on hot chat without double-activate / overwriting
activated_by.
- Customer inbound (flag absent) doesn't touch TTL.
- /takeback with owner-flag still deactivates.
- Bot's own outbound (flag absent) is a no-op.
42 tests pass (was 34).
|
Warning Rate limit exceeded
To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (3)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Profiles where the owner notification platform is Telegram but the human owner actually types from the same WhatsApp Business account as the bot (e.g. basketball-jersey-demo) couldn't deactivate handover via /takeback because the rule only matched on (platform, sender_id) against the configured owner. With the WHATSAPP_FORWARD_OWNER_MESSAGES flag now propagating metadata["whatsapp_from_owner"] from the bridge LRU classifier, any inbound with that flag set is by definition owner-equivalent - same trust model as the implicit-owner-activate branch shipped in PR #3. No new config. Pre-existing platform/sender_id matching path is unchanged for profiles that don't enable owner forwarding.
Dependency
Requires hermes-agent commit
b1e7628d7(PR pebble-tech/hermes-agent#1) which adds the opt-inWHATSAPP_FORWARD_OWNER_MESSAGESenv var and propagatesMessageEvent.metadata[\"whatsapp_from_owner\"]from the WhatsApp bridge. Without that change the metadata flag never appears and the new code paths in this PR are inert.Why
Two gaps with the existing handover flow:
trigger_handovertool. If the agent missed the cue and the owner stepped in manually, the bot kept replying alongside the owner.How
Both behaviours hang off the same metadata signal —
event.metadata.get(\"whatsapp_from_owner\")— so a profile that doesn't enable the env flag on hermes-agent sees no behaviour change.state.py:HandoverStore.touch(platform, chat_id, ttl_seconds) -> boolSingle SQL UPDATE that slides
expires_attonow + ttl_seconds. Idempotent:Falseon cold rows.Falseon rows withexpires_at IS NULL(operator chose no TTL — don't surprise them).Trueand updates otherwise.rules/handover.py: owner-reply branch with explicit orderingThe
/takebackbranch beats the owner-implicit branch so an owner exiting via the command still wins, even though the same fromMe inbound also carries the metadata flag.Owner-implicit activation uses
reason=\"owner_reply\",activated_by=\"owner_implicit\", and never callsnotify_owner— the owner is already in the chat, so a Telegram ping would be noise. On a hot chat the rule just callstouch(...)and silent-ingests the event so the transcript captures it.Tests
8 new tests in
tests/test_rules.py(suite goes 34 → 42 passing):touch()no-op on cold and no-TTL rows; slides forward on hot rows.activated_by='owner_implicit', no notify.activated_by, no notify./takebackwith owner flag still deactivates (ordering guard).Profile config
After this merges, profiles that want the new behaviour add
WHATSAPP_FORWARD_OWNER_MESSAGES=trueto their.env. Profiles that don't are unaffected.