April 25, 2026

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

Contextually Connected Issues

Cross-Reference Table

Error CodeDescriptionRelationship
SESS_001Session not found on deliveryDirect β€” handoff cannot complete without target session
SESS_002Session capacity exceededRelated β€” limits multi-agent scaling
HOOK_001Hook configuration parse errorDirect β€” misconfigured hooks prevent feature implementation
HOOK_002Hook execution timeoutRelated β€” delivery confirmation may timeout
TG_400Invalid topic/chat IDDirect β€” Telegram delivery failure
TG_429Telegram rate limit exceededRelated β€” visible message rate limiting
AUTH_401Gateway authentication failedDirect β€” all handoff operations require auth
AUTH_403Session access deniedRelated β€” cross-session handoff permissions
COMP_001Context compaction removes handoff stateRelated β€” visibility instructions lost during compaction
  • [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 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

Evidence & Sources

This troubleshooting guide was automatically synthesized by the FixClaw Intelligence Pipeline from community discussions.