← Back to Index

Event Plugin System

Overview

Universal event router that dispatches events to plugin handlers. Auto-discovers plugins, configurable via JSON, extensible for any event source.

Architecture:

External Event → Pub/Sub/Webhook → OpenClaw → event-router.py → Plugin → Action

Quick Start

Test the router

cd ~/.openclaw/workspace/tools
echo '{"source":"gmail","account":"test@example.com","messages":[]}' | python3 event-router.py

Add a new plugin

  1. Copy _template.py to {source}.py (e.g., calendar.py)
  2. Implement can_handle() and handle() methods
  3. Add config to ../event-config.json
  4. Router auto-discovers plugin on next run

Plugin Interface

Every plugin must implement:

class MyEventHandler:
    def can_handle(self, event) → bool:
        """Return True if this plugin handles this event"""
        return event.get('source') == 'my_source'
    
    def handle(self, event, config) → dict:
        """Process event and return action"""
        return {
            'action': 'alert|digest|silent|error',
            'message': 'Alert text',
            'priority': 'critical|informational|noise'
        }
    
    def get_config_schema(self) → dict:
        """Return configuration schema"""
        return {'enabled': True, 'cost_estimate_monthly': 0.00}

Event Format

Standard event structure:

{
  "source": "gmail|calendar|drive|zoho|custom",
  "timestamp": "2026-02-11T06:00:00Z",
  "data": {
    ... source-specific data ...
  }
}

Gmail Events

{
  "source": "gmail",
  "account": "quan@ztag.com",
  "messages": [
    {
      "id": "19c4...",
      "from": "sender@example.com",
      "to": "quan@ztag.com",
      "subject": "Email subject",
      "snippet": "Preview text...",
      "date": "Mon, 10 Feb 2026 10:00:00 +0000",
      "unread": true
    }
  ]
}

Calendar Events (future)

{
  "source": "calendar",
  "event_type": "event_starting_soon",
  "data": {
    "summary": "Meeting with Brian",
    "start": "2026-02-11T14:00:00Z",
    "attendees": ["brian@calbt.com"]
  }
}

Drive Events (future)

{
  "source": "drive",
  "event_type": "file_created",
  "data": {
    "file_id": "1abc...",
    "name": "Meeting Notes 2026-02-11.md",
    "mime_type": "text/markdown",
    "folder": "/meeting-notes"
  }
}

Configuration

Edit ../event-config.json:

{
  "plugins": {
    "GmailEventHandler": {
      "enabled": true,
      "shadow_mode": false,
      "accounts": ["quan@ztag.com"],
      "model": "anthropic/claude-haiku-4-5"
    }
  },
  "router": {
    "default_model": "anthropic/claude-haiku-4-5",
    "flood_threshold": 30
  }
}

Testing

Unit test a plugin

cd ~/.openclaw/workspace/tools

# Create test event
cat > test-gmail-event.json << 'EOF'
{
  "source": "gmail",
  "account": "quan@ztag.com",
  "messages": [
    {
      "id": "test123",
      "from": "Faye Nesheiwat <faye@calbt.com>",
      "to": "quan@ztag.com",
      "subject": "Loan closing needs letter",
      "snippet": "We need a letter of explanation...",
      "date": "Mon, 10 Feb 2026 10:00:00 +0000",
      "unread": true
    }
  ]
}
EOF

# Route through event router
cat test-gmail-event.json | python3 event-router.py

Expected output:

{
  "action": "alert",
  "message": "📧 [quan@ztag.com] 1 critical: Faye Nesheiwat - Loan closing needs letter",
  "priority": "critical"
}

Integration test

# Test via OpenClaw webhook (requires gateway running)
curl -X POST http://127.0.0.1:18789/hooks/agent \
  -H 'Authorization: Bearer YOUR_HOOK_TOKEN' \
  -H 'Content-Type: application/json' \
  -d @test-gmail-event.json

Available Plugins

GmailEventHandler (Active)

CalendarEventHandler (Planned)

DriveEventHandler (Planned)

ZohoEventHandler (Planned)

Cost Tracking

Router logs per-plugin costs to ../../cost-tracking/event-costs.json:

{
  "date": "2026-02-11",
  "GmailEventHandler": {
    "events_processed": 45,
    "tokens_used": 27000,
    "cost_usd": 0.045,
    "alerts_sent": 2
  },
  "total_day": 0.045
}

Minnie-Review generates weekly ROI reports automatically.

Debugging

Enable verbose logging

# Run router with debug output
cat event.json | python3 event-router.py 2>&1 | tee debug.log

Check shadow log

# View Gmail classifications
cat ../../working/ops/gmail-shadow-log.json | jq '.[-5:]'

Verify alerted IDs

# Check which emails have been alerted
cat ../../working/ops/alerted-email-ids.json | jq '.[-10:]'

Plugin Development Guidelines

1. Idempotency

Plugins should be idempotent - processing the same event twice should be safe.

def handle(self, event, config):
    event_id = event.get('id')
    if self.already_processed(event_id):
        return {'action': 'silent'}
    # ... process event ...
    self.mark_processed(event_id)

2. Error Handling

Always catch exceptions and return error action:

def handle(self, event, config):
    try:
        # ... process event ...
    except Exception as e:
        return {
            'action': 'error',
            'message': f'Plugin failed: {str(e)}'
        }

3. State Management

Use files for persistent state:

def __init__(self):
    self.state_path = 'working/ops/my-plugin-state.json'
    self.load_state()

def load_state(self):
    try:
        with open(self.state_path, 'r') as f:
            self.state = json.load(f)
    except FileNotFoundError:
        self.state = {}

def save_state(self):
    with open(self.state_path, 'w') as f:
        json.dump(self.state, f, indent=2)

4. Configuration

All plugin behavior should be configurable:

def handle(self, event, config):
    enabled = config.get('enabled', True)
    if not enabled:
        return {'action': 'silent'}
    
    threshold = config.get('threshold', 10)
    model = config.get('model', 'anthropic/claude-haiku-4-5')

5. Cost Awareness

Log token usage for cost tracking:

def handle(self, event, config):
    result = self.process(event)
    
    # Log cost
    self.log_cost({
        'tokens': result['tokens_used'],
        'cost': result['tokens_used'] * 0.0000005  # Haiku rate
    })
    
    return result

Roadmap

Phase 1 (Now)

Phase 2 (Week 2)

Phase 3 (Week 3)

Phase 4 (Month 2)

Support

Issues or questions? Check:

  1. debug.log for router errors
  2. gmail-shadow-log.json for classification decisions
  3. event-config.json for plugin settings

For architecture questions, see ../../working/ops/tier1-implementation-plan.md