Feature Request: sessions_send Event Hook for Guaranteed Handoff Visibility
Implement a deterministic event hook for sessions_send to ensure visible message delivery and delivery confirmation between agents.
π Symptoms
Current Limitation Manifestations
When agents perform inter-session handoffs using sessions_send, the following failure modes are commonly observed:
Silent Processing Without Confirmation
# Agent A initiates handoff
sessions_send(
target_session="project-alpha",
payload={"task": "review_pr_442", "priority": "high"}
)
# Agent B processes silently β no visible message in target topic
# User sees no indication that handoff occurred
Fragile Prompt-Level Enforcement
The current mitigation relies on prompt instructions that frequently fail:
# In Agent A's prompt (often ignored during compaction or high load):
# "After calling sessions_send, ALWAYS post a visible message to the target topic."
# Reality: Agent may compact context, lose this instruction, or:
# 1. Call sessions_send β
# 2. Forget to post visible message β
# 3. User has no visibility into handoff status
No Delivery Confirmation to Sender
# Agent A sends handoff
result = sessions_send(...)
# result provides no confirmation that:
# - Message was queued
# - Target session exists
# - Delivery was attempted
# Agent A operates in uncertainty
User-Facing Symptoms
- Users cannot determine if inter-session handoffs succeeded
- No audit trail of work distribution across agents
- Multi-agent workflows appear opaque and untrustworthy
- Debugging handoff failures requires manual inspection of logs
π§ Root Cause
Architectural Gap Analysis
The current sessions_send implementation has a fundamental design limitation:
1. Fire-and-Forget Message Delivery
# Current implementation (conceptual)
def sessions_send(target_session, payload, ...):
queue_message(target_session, payload)
return {"status": "queued"} # No hook, no side effects
The function delivers the payload but provides no extensibility point for:
- Side effects on delivery
- Cross-session notifications
- Audit logging
2. Separation of Transport and Presentation
The sessions_send transport layer is decoupled from the Telegram presentation layer:
βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ β Agent A β β Gateway β β Agent B β β sessions_send βββββββΆβ (transport) βββββββΆβ (processing) β βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ β β β β β βΌ βΌ βΌ βββββββββββββββββββ (no receipt) (no hook point) β Telegram Bot β β (presentation) β βββββββββββββββββββ
3. Prompt-Level Enforcement is Inherently Unreliable
Current mitigation relies on LLM behavior which cannot be guaranteed:
- Context window pressure causes instruction loss
- Compaction may strip critical behavioral prompts
- Agent autonomy means prompts are suggestions, not constraints
- No atomicity between sessions_send and message posting
4. No Event System for Cross-Session Coordination
The absence of an event hook means:
- Gateway cannot trigger side effects on delivery
- No opportunity for delivery confirmation back to sender
- Auto-acknowledgment requires manual agent implementation
- No declarative configuration path
π οΈ Step-by-Step Fix
Proposed Implementation: sessionReceive Event Hook
Step 1: Configure the Hook in Gateway Settings
Add the following to your OpenClaw gateway configuration:
{
"hooks": {
"sessionReceive": {
"autoAcknowledge": {
"enabled": true,
"message": "π¨ Handoff received from {sender} β processing now.",
"channel": "last",
"topic_id": "{incoming_topic}"
},
"deliveryConfirmation": {
"enabled": true,
"confirm_to_session": "{sender_session}",
"confirm_message": "β
Delivered to {target_session} at {timestamp}"
}
}
}
}
Step 2: Understanding Configuration Parameters
- `enabled`: Boolean to activate the hook
- `message`: Template string with placeholders:
- `{sender}` β Name of sending agent/session
- `{sender_session}` β Session ID of sender
- `{target_session}` β This session's ID
- `{incoming_topic}` β Telegram topic ID where message arrived
- `{timestamp}` β ISO 8601 delivery timestamp
- `channel`: Target for acknowledgment ("last", specific topic ID, or null)
- `confirm_to_session`: Session ID to send delivery receipt
- `confirm_message`: Receipt template message
Step 3: Workaround Implementation (Current Python Script)
Until native hook support is implemented, deploy the provided handoff.py script:
# handoff.py β Guaranteed Handoff Visibility Script
import os
import requests
from datetime import datetime
class HandoffManager:
def __init__(self, bot_token: str, gateway_url: str):
self.bot_token = bot_token
self.gateway_url = gateway_url
self.telegram_api = f"https://api.telegram.org/bot{bot_token}"
def send_with_guaranteed_visibility(
self,
target_session: str,
target_topic_id: int,
payload: dict,
sender_name: str = "System"
) -> dict:
"""
Executes handoff with guaranteed visible acknowledgment.
"""
# Step 1: Post visible message to target topic FIRST
confirmation_msg = f"π¨ Handoff incoming from {sender_name}"
self._post_telegram_message(target_topic_id, confirmation_msg)
# Step 2: Deliver payload to target session
delivery_result = self._deliver_to_session(target_session, payload)
# Step 3: Confirm delivery back to sender (if session provided)
if sender_session := payload.get("_sender_session"):
self._send_confirmation(sender_session, target_session)
return {
"status": "completed",
"visible_posted": True,
"payload_delivered": True,
"confirmation_sent": bool(payload.get("_sender_session"))
}
def _post_telegram_message(self, topic_id: int, text: str) -> dict:
"""Post message to specific Telegram topic."""
return requests.post(
f"{self.telegram_api}/sendMessage",
json={
"chat_id": os.environ["TARGET_CHAT_ID"],
"message_thread_id": topic_id,
"text": text
}
).json()
def _deliver_to_session(self, session_id: str, payload: dict) -> dict:
"""Deliver payload via gateway sessions_send."""
return requests.post(
f"{self.gateway_url}/sessions/{session_id}/receive",
json=payload,
headers={"Authorization": f"Bearer {os.environ['GATEWAY_TOKEN']}"}
).json()
def _send_confirmation(self, sender_session: str, target_session: str):
"""Send delivery receipt back to sender session."""
confirmation = {
"type": "handoff_confirmation",
"target": target_session,
"timestamp": datetime.utcnow().isoformat(),
"status": "delivered"
}
requests.post(
f"{self.gateway_url}/sessions/{sender_session}/receive",
json=confirmation,
headers={"Authorization": f"Bearer {os.environ['GATEWAY_TOKEN']}"}
)
Step 4: Integration with OpenClaw Agent
# In your agent's tool implementation or middleware
from handoff import HandoffManager
# Initialize manager (from environment or config)
hm = HandoffManager(
bot_token=os.environ["TELEGRAM_BOT_TOKEN"],
gateway_url=os.environ["GATEWAY_URL"]
)
def safe_sessions_send(target_session: str, target_topic: int, payload: dict):
"""
Drop-in replacement for sessions_send with guaranteed visibility.
"""
enriched_payload = {
**payload,
"_sender_session": current_session_id, # Enable confirmation
"_handoff_type": "cross_agent"
}
result = hm.send_with_guaranteed_visibility(
target_session=target_session,
target_topic_id=target_topic,
payload=enriched_payload,
sender_name=current_agent_name
)
if not result["status"] == "completed":
raise HandoffError(f"Delivery failed: {result}")
return result
π§ͺ Verification
Verification Steps for Workaround Implementation
Step 1: Test Single Handoff Visibility
# Execute handoff test
python -c "
from handoff import HandoffManager
import os
hm = HandoffManager(
bot_token=os.environ['TELEGRAM_BOT_TOKEN'],
gateway_url=os.environ['GATEWAY_URL']
)
result = hm.send_with_guaranteed_visibility(
target_session='test-agent-01',
target_topic_id=42,
payload={'task': 'verify_handoff', 'test': True},
sender_name='TestHarness'
)
print(f'Status: {result[\"status\"]}')
print(f'Visible Posted: {result[\"visible_posted\"]}')
print(f'Payload Delivered: {result[\"payload_delivered\"]}')
"
Expected Output:
Status: completed
Visible Posted: True
Payload Delivered: True
Payload Confirmation: True
Step 2: Verify Telegram Message Appears
Check the target Telegram topic for the visible confirmation message:
# Expected message in target topic:
π¨ Handoff incoming from TestHarness
Step 3: Verify Delivery Confirmation in Sender Topic
Check the sender’s session/topic for the delivery receipt:
# Expected message in sender topic:
β
Delivered to test-agent-01 at 2024-01-15T10:30:00+00:00
Step 4: Verify Gateway Delivery Log
# Check gateway logs for delivery confirmation
curl -s -H "Authorization: Bearer $GATEWAY_TOKEN" \
"$GATEWAY_URL/sessions/test-agent-01/history?limit=5" | jq '.[] | select(.type=="handoff_confirmation")'
Expected:
{
"type": "handoff_confirmation",
"target": "test-agent-01",
"timestamp": "2024-01-15T10:30:00+00:00",
"status": "delivered"
}
Step 5: End-to-End Multi-Agent Flow Test
# Simulate complete multi-agent workflow
python -c "
from handoff import HandoffManager
import os
hm = HandoffManager(
bot_token=os.environ['TELEGRAM_BOT_TOKEN'],
gateway_url=os.environ['GATEWAY_URL']
)
# Agent A β Agent B β Agent C chain
sessions = ['agent-a', 'agent-b', 'agent-c']
topics = [10, 20, 30]
for i in range(len(sessions) - 1):
result = hm.send_with_guaranteed_visibility(
target_session=sessions[i + 1],
target_topic_id=topics[i + 1],
payload={
'task': f'handoff_{i}',
'_sender_session': sessions[i],
'_chain_position': i + 1
},
sender_name=f'Agent-{chr(65+i)}'
)
assert result['status'] == 'completed', f'Handoff {i} failed'
print('Chain verification: ALL PASSED')
"
β οΈ Common Pitfalls
Environment and Configuration Pitfalls
1. Missing Environment Variables
# Required but often missing:
# TELEGRAM_BOT_TOKEN - Bot API token
# GATEWAY_URL - Gateway base URL
# GATEWAY_TOKEN - Gateway authentication
# TARGET_CHAT_ID - Default Telegram chat
# Symptom:
# KeyError: 'TELEGRAM_BOT_TOKEN'
# Fix: Ensure all env vars are set in deployment
export TELEGRAM_BOT_TOKEN="123456:ABC-..."
export GATEWAY_URL="https://gateway.example.com"
export GATEWAY_TOKEN="gw_..."
export TARGET_CHAT_ID="-1001234567890"
2. Topic ID Mismatch
# Symptom:
# {'ok': False, 'error_code': 400, 'description': 'Bad Request: chat not found'}
# Cause: message_thread_id (topic) doesn't exist or forum not enabled
# Fix: Verify target chat has topics enabled:
# /setname Your Forum Name β Enable Forum
# Then use numeric topic IDs from:
curl -s "https://api.telegram.org/bot$TOKEN/getForumTopicByChat" \
-d "chat_id=$CHAT_ID" -d "title=Target Topic"
3. Gateway Session Does Not Exist
# Symptom:
# {'ok': False, 'error_code': 404, 'description': 'Session not found'}
# Fix: Verify session exists before handoff:
curl -s -H "Authorization: Bearer $GATEWAY_TOKEN" \
"$GATEWAY_URL/sessions" | jq '.[] | select(.id=="agent-b")'
# Or create session proactively:
curl -s -X POST -H "Authorization: Bearer $GATEWAY_TOKEN" \
"$GATEWAY_URL/sessions/agent-b" \
-d '{"config": {"topic_id": 20}}'
4. Race Condition in Confirmation Delivery
# Symptom: Confirmation arrives AFTER sender processes next message
# (visual glitch where message appears out of order)
# Cause: Async delivery without ordering guarantee
# Fix: Use sequential delivery with acknowledgment:
result = send_visible_message(topic_id, message)
if result['ok']:
deliver_payload(session_id, payload) # Only after visible confirmed
send_confirmation(sender_session, receipt) # Only after payload delivered
Agent Behavior Pitfalls
5. Double Handoff (Original + Hook)
If native hook is implemented alongside agent prompt instructions, both may fire:
# Scenario: Native hook + old prompt instruction both fire
# User sees:
# π¨ Handoff received from Agent A β processing now. (hook fires)
# π¨ Handoff received from Agent A β processing now. (agent also fires)
# Fix: Remove prompt-level handoff instructions once native hook is enabled
# Or set agent to only handoff, never post
6. Topic ID Not Propagated Through Compaction
# Symptom: After context compaction, topic_id for handoff is lost
# Scenario:
# Agent has: sessions_to_topic = {'agent-b': 20}
# After compaction: sessions_to_topic = {} (lost)
# Fix: Store mapping in persistent config, not context:
# config.yaml:
# handoff_mappings:
# agent-b:
# topic_id: 20
# last_handoff: "2024-01-15T..."
7. Gateway Timeout on Visible Message
# Symptom:
# Handoff timeout after 30 seconds
# Telegram API responded slowly
# Visible message never posted
# Payload still delivered (inconsistent state)
# Fix: Implement timeout with retry:
def post_with_retry(topic_id, message, max_retries=3):
for attempt in range(max_retries):
try:
return requests.post(
TELEGRAM_API,
json={...},
timeout=10 # Hard timeout
).json()
except requests.Timeout:
if attempt == max_retries - 1:
raise HandoffDeliveryError(f"Failed after {max_retries} attempts")
Multi-Agent Design Pitfalls
8. Circular Handoff Detection
# Symptom: Infinite loop of handoffs between agents
# Agent A β Agent B β Agent A β Agent B β ...
# Fix: Implement handoff depth tracking:
payload = {
**payload,
'_handoff_depth': payload.get('_handoff_depth', 0) + 1,
'_handoff_chain': [...payload.get('_handoff_chain', []), current_session]
}
if payload['_handoff_depth'] > MAX_HANDOFF_DEPTH:
raise CircularHandoffError(f"Max depth exceeded: {payload['_handoff_chain']}")
9. Session Identity Confusion
# Symptom: Delivery confirmation goes to wrong session
# (user sees confirmation meant for another user)
# Cause: sender_session from shared/impersonated context
# Fix: Always resolve sender_session from authenticated context:
sender_session = authenticated_user.session_id # Not from payload
π Related Errors
Contextually Connected Issues
Cross-Reference Table
| Error Code | Description | Relationship |
|---|---|---|
SESS_001 | Session not found on delivery | Direct β handoff cannot complete without target session |
SESS_002 | Session capacity exceeded | Related β limits multi-agent scaling |
HOOK_001 | Hook configuration parse error | Direct β misconfigured hooks prevent feature implementation |
HOOK_002 | Hook execution timeout | Related β delivery confirmation may timeout |
TG_400 | Invalid topic/chat ID | Direct β Telegram delivery failure |
TG_429 | Telegram rate limit exceeded | Related β visible message rate limiting |
AUTH_401 | Gateway authentication failed | Direct β all handoff operations require auth |
AUTH_403 | Session access denied | Related β cross-session handoff permissions |
COMP_001 | Context compaction removes handoff state | Related β visibility instructions lost during compaction |
Related GitHub Issues
- [FEATURE] sessions_send return receipt β Earlier request for sender-side confirmation (closed, not implemented)
- [BUG] sessions_send silently fails when target session offline β Silent failure mode enabled current fragile workaround discussion
- [FEATURE] Event hook system for gateway lifecycle β Proposed generic hook architecture that could subsume this feature
- [DOCS] Multi-agent handoff patterns documentation β Missing guidance forces each team to rediscover patterns
- [PERF] sessions_send latency under high load β Hook overhead must be considered in performance design
Related Configuration Options
# Related OpenClaw config options that may interact with this feature:
{
"sessions": {
"handoff_timeout": 30000, // Timeout for handoff delivery
"require_acknowledgment": false, // Future: block until ack
"max_handoff_depth": 5 // Prevent circular handoffs
},
"telegram": {
"topic_mode": "required", // Ensure topics exist
"rate_limit_per_second": 30 // Affects auto-acknowledge rate
},
"hooks": {
"sessionSend": { }, // Future: sender-side hook
"sessionReceive": { } // This feature
}
}
External Dependencies
- Telegram Bot API β Used for visible message posting; subject to rate limits and availability
- Gateway Sessions API β Must support delivery confirmation endpoint
- Message Queue β If implemented, must guarantee delivery ordering