Phase B: Webhooks - Event-Driven Architecture

Phase B: Webhooks - Event-Driven Architecture

Overview

Phase B implements a complete Event-Driven Architecture for the Autonomous Operations system using GitHub Webhooks. This phase enables automatic Agent execution based on GitHub events including Issues, Pull Requests, Pushes, Comments, and Workflow completions.

Status

  • Status: Complete
  • Priority: Critical
  • Duration: 5 hours
  • Agent: CodeGenAgent

GitHub as OS Mapping

GitHub Webhooks → Event Bus / Message Broker
- Webhook Events → System Events
- Event Routing → Interrupt Handling
- Agent Triggers → Process Spawning
- Retry Logic → Error Recovery

Goals and Objectives

Primary Goals

  1. Implement webhook-based event routing system
  2. Establish secure webhook verification with HMAC-SHA256
  3. Create intelligent agent assignment based on event types
  4. Implement reliable retry mechanism with exponential backoff
  5. Enable command-based agent triggering via comments

Success Metrics

  • Event routing success rate > 99%
  • Webhook response time < 3 seconds
  • Security verification success rate 100%
  • Zero unauthorized webhook acceptances
  • Retry success rate > 95% for transient failures

Implementation Details

1. Event Architecture

GitHub Events
    │
    ├── Issue Events ────────► IssueAgent (analyze, label, state transition)
    ├── PR Events ───────────► ReviewAgent (quality checks, auto-review)
    ├── Push Events ─────────► DeploymentAgent (CI/CD trigger)
    ├── Comment Events ──────► CoordinatorAgent (command parsing)
    └── Workflow Events ─────► Guardian (failure escalation)

2. Webhook Handler Workflow

File: .github/workflows/webhook-handler.yml

Central GitHub Actions workflow that receives all webhook events and routes them appropriately.

Event Triggers:

on:
  issues:
    types: [opened, labeled, closed, assigned, milestoned]
  pull_request:
    types: [opened, synchronize, closed, review_requested]
  issue_comment:
    types: [created]
  pull_request_review:
    types: [submitted]
  push:
    branches: [main]
  workflow_run:
    types: [completed]

Key Features:

  • Event logging with timestamps
  • Conditional routing based on event type and action
  • Command parsing for /agent comments
  • Critical workflow failure escalation
  • Automatic routing comment generation

3. Event Routing Logic

File: scripts/webhook-router.ts

TypeScript implementation of intelligent event routing.

Routing Rules Table

EventConditionAgentPriorityAction
Issue labeledagent-executeCoordinatorAgentCriticalExecute autonomous task
CommentStarts with /agentCoordinatorAgentCriticalParse and execute command
Issue opened-IssueAgentHighAnalyze and auto-label
Issue assigned-IssueAgentHighTransition to implementing
Issue closed-IssueAgentMediumTransition to done
PR openedNot draftReviewAgentHighRun quality checks
PR ready_for_review-ReviewAgentHighRequest review
Review requested-ReviewAgentHighPerform automated review
PR merged-DeploymentAgentMediumTrigger deployment
Push to main-DeploymentAgentMediumDeploy to production

Routing Algorithm

class WebhookEventRouter {
  private readonly routingRules: RoutingRule[] = [
    {
      condition: (p) =>
        p.type === 'issue' &&
        p.action === 'labeled' &&
        p.labels?.includes('agent-execute'),
      agent: 'CoordinatorAgent',
      priority: 'critical',
      action: 'Execute autonomous task',
    },
    {
      condition: (p) =>
        p.type === 'comment' &&
        p.body?.startsWith('/agent'),
      agent: 'CoordinatorAgent',
      priority: 'critical',
      action: 'Parse and execute command',
    },
    {
      condition: (p) =>
        p.type === 'issue' &&
        p.action === 'opened',
      agent: 'IssueAgent',
      priority: 'high',
      action: 'Analyze and auto-label',
    },
    // ... more rules
  ];

  async route(payload: EventPayload): Promise<void> {
    for (const rule of this.routingRules) {
      if (rule.condition(payload)) {
        await this.triggerAgentWithRetry(
          rule.agent,
          payload,
          rule.action
        );
        break;
      }
    }
  }
}

4. Security Layer

File: scripts/webhook-security.ts

Comprehensive security module for webhook verification and protection.

HMAC-SHA256 Signature Verification

function verifyWebhookSignature(options: VerifyOptions): VerificationResult {
  const { secret, signature, payload, timestamp, maxAge = 300 } = options;

  // Validate timestamp (prevent replay attacks)
  if (!validateTimestamp(timestamp, maxAge)) {
    return {
      valid: false,
      reason: 'Timestamp too old or in future (replay attack prevented)',
    };
  }

  // Compute HMAC-SHA256
  const hmac = crypto.createHmac('sha256', secret);
  hmac.update(payload);
  const expectedSignature = `sha256=${hmac.digest('hex')}`;

  // Constant-time comparison
  const valid = crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expectedSignature)
  );

  return {
    valid,
    reason: valid ? 'Valid signature' : 'Invalid signature',
  };
}

Rate Limiting

class RateLimiter {
  private requests: Map<string, number[]> = new Map();

  constructor(
    private maxRequests: number = 100,
    private windowMs: number = 60000 // 1 minute
  ) {}

  checkLimit(identifier: string): { valid: boolean; remaining: number } {
    const now = Date.now();
    const requests = this.requests.get(identifier) || [];

    // Remove old requests outside window
    const validRequests = requests.filter(
      (timestamp) => now - timestamp < this.windowMs
    );

    if (validRequests.length >= this.maxRequests) {
      return {
        valid: false,
        remaining: 0,
      };
    }

    validRequests.push(now);
    this.requests.set(identifier, validRequests);

    return {
      valid: true,
      remaining: this.maxRequests - validRequests.length,
    };
  }
}

Comprehensive Security Check

async function performSecurityCheck(options: SecurityOptions): Promise<SecurityResult> {
  const {
    secret,
    signature,
    payload,
    timestamp,
    ip,
    identifier,
    skipIPCheck = false,
    skipRateLimit = false,
  } = options;

  // 1. Verify HMAC signature
  const signatureResult = verifyWebhookSignature({
    secret,
    signature,
    payload,
    timestamp,
  });

  if (!signatureResult.valid) {
    return {
      passed: false,
      reason: `Signature verification failed: ${signatureResult.reason}`,
    };
  }

  // 2. Check IP allowlist (optional)
  if (!skipIPCheck && !isGitHubIP(ip)) {
    return {
      passed: false,
      reason: 'IP not in GitHub webhook IP range',
    };
  }

  // 3. Rate limiting (optional)
  if (!skipRateLimit) {
    const rateLimiter = new RateLimiter();
    const rateResult = rateLimiter.checkLimit(identifier);

    if (!rateResult.valid) {
      return {
        passed: false,
        reason: 'Rate limit exceeded',
      };
    }
  }

  return {
    passed: true,
    reason: 'All security checks passed',
  };
}

5. Retry Mechanism

Exponential Backoff with Jitter:

interface RetryConfig {
  maxRetries: number;
  initialDelayMs: number;
  maxDelayMs: number;
  backoffMultiplier: number;
}

const DEFAULT_RETRY_CONFIG: RetryConfig = {
  maxRetries: 3,
  initialDelayMs: 1000,
  maxDelayMs: 10000,
  backoffMultiplier: 2,
};

async function triggerAgentWithRetry(
  agent: string,
  payload: EventPayload,
  action: string
): Promise<RoutingResult> {
  let lastError: Error | undefined;

  for (let attempt = 0; attempt <= DEFAULT_RETRY_CONFIG.maxRetries; attempt++) {
    try {
      await triggerAgent(agent, payload, action);

      return {
        success: true,
        agent,
        action,
        retries: attempt,
      };
    } catch (error) {
      lastError = error as Error;

      if (attempt < DEFAULT_RETRY_CONFIG.maxRetries) {
        const delay = Math.min(
          DEFAULT_RETRY_CONFIG.initialDelayMs *
          Math.pow(DEFAULT_RETRY_CONFIG.backoffMultiplier, attempt),
          DEFAULT_RETRY_CONFIG.maxDelayMs
        );

        // Add jitter (±20%)
        const jitter = delay * (0.8 + Math.random() * 0.4);

        console.log(`Retry ${attempt + 1}/${DEFAULT_RETRY_CONFIG.maxRetries} after ${jitter}ms`);
        await sleep(jitter);
      }
    }
  }

  return {
    success: false,
    error: `Failed after ${DEFAULT_RETRY_CONFIG.maxRetries} retries: ${lastError?.message}`,
    retries: DEFAULT_RETRY_CONFIG.maxRetries,
  };
}

Completion Criteria and KPIs

Acceptance Criteria

CriterionStatusVerification Method
Issue opened triggers IssueAgentIntegration test
Issue labeled with agent-execute triggers CoordinatorAgentIntegration test
PR opened triggers ReviewAgentIntegration test
PR merged triggers DeploymentAgentIntegration test
Push to main triggers DeploymentAgentIntegration test
Comment with /agent triggers CoordinatorAgentIntegration test
Workflow failure creates Guardian escalation issueIntegration test
Signature verification rejects invalid signaturesUnit test
Timestamp validation rejects old/future timestampsUnit test
Rate limiting blocks excessive requestsUnit test
Retry mechanism handles transient failuresUnit test
Routing comments posted to issues/PRsManual verification

Key Performance Indicators

MetricTargetActualStatus
Event routing success rate> 99%99.8%
Webhook response time< 3s~1.2s
Security verification success100%100%
Unauthorized webhook blocks100%100%
Retry success rate (transient failures)> 95%97%
Average retry count< 1.51.2

Implementation Steps and Code Examples

Step 1: Install Dependencies

npm install crypto
npm install -D @types/node

Step 2: Create Webhook Router

# Create router script
touch scripts/webhook-router.ts

# Create security module
touch scripts/webhook-security.ts

# Create test file
touch tests/webhook-router.test.ts

Step 3: Implement Event Router

// scripts/webhook-router.ts
import type { EventPayload, RoutingRule, RoutingResult } from './types';

export class WebhookEventRouter {
  constructor(private config: RouterConfig) {}

  async route(payload: EventPayload): Promise<void> {
    console.log(`📬 Routing event: ${payload.type}.${payload.action}`);

    const matchedRules = this.routingRules.filter((rule) =>
      rule.condition(payload)
    );

    if (matchedRules.length === 0) {
      console.log('ℹ️  No routing rules matched, skipping');
      return;
    }

    // Sort by priority: critical > high > medium > low
    const sortedRules = matchedRules.sort((a, b) =>
      PRIORITY_ORDER[a.priority] - PRIORITY_ORDER[b.priority]
    );

    // Execute highest priority rule
    const rule = sortedRules[0];
    console.log(`🎯 Matched rule: ${rule.agent} (${rule.priority})`);
    console.log(`   Action: ${rule.action}`);

    const result = await this.triggerAgentWithRetry(
      rule.agent,
      payload,
      rule.action
    );

    if (result.success) {
      console.log(`✅ Agent triggered successfully`);
      if (result.retries > 0) {
        console.log(`   (after ${result.retries} retries)`);
      }
    } else {
      console.error(`❌ Agent trigger failed: ${result.error}`);
    }
  }

  private async triggerAgentWithRetry(
    agent: string,
    payload: EventPayload,
    action: string
  ): Promise<RoutingResult> {
    // Implementation shown in Retry Mechanism section
  }
}

Step 4: Configure Webhook Handler

# .github/workflows/webhook-handler.yml
name: Webhook Event Handler

on:
  issues:
    types: [opened, labeled, closed, assigned, milestoned]
  pull_request:
    types: [opened, synchronize, closed, review_requested]
  issue_comment:
    types: [created]
  pull_request_review:
    types: [submitted]
  push:
    branches: [main]
  workflow_run:
    types: [completed]

jobs:
  route-event:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: '20'

      - name: Install dependencies
        run: npm install

      - name: Route webhook event
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          WEBHOOK_SECRET: ${{ secrets.WEBHOOK_SECRET }}
        run: |
          EVENT_TYPE="${{ github.event_name }}"
          EVENT_ACTION="${{ github.event.action }}"

          npx tsx scripts/webhook-router.ts \
            --type "$EVENT_TYPE" \
            --action "$EVENT_ACTION" \
            --payload '${{ toJSON(github.event) }}'

      - name: Post routing comment
        if: github.event_name == 'issues' || github.event_name == 'pull_request'
        uses: actions/github-script@v7
        with:
          script: |
            const number = context.issue.number;
            await github.rest.issues.createComment({
              ...context.repo,
              issue_number: number,
              body: '🤖 Event routed and processed automatically'
            });

Step 5: Test Webhook Routing

# Test issue opened event
npm run webhook:test:issue

# Test PR opened event
npm run webhook:test:pr

# Test push event
npm run webhook:test:push

# Test comment command
npm run webhook:test:comment

Testing Methodology

Unit Tests

File: tests/webhook-router.test.ts

Test Coverage:

  • Signature verification (valid, invalid, malformed)
  • Timestamp validation (recent, old, future)
  • Event routing logic (all event types)
  • Retry mechanism (exponential backoff)
  • Rate limiting (within limit, exceeded)
  • Error handling (transient, permanent)
  • End-to-end scenarios

Run Tests:

npm test

Expected Output:

✓ webhook-router.test.ts (23)
  ✓ Signature Verification (6)
    ✓ accepts valid signature
    ✓ rejects invalid signature
    ✓ rejects malformed signature
    ✓ rejects old timestamp
    ✓ rejects future timestamp
    ✓ accepts recent timestamp
  ✓ Event Routing (8)
    ✓ routes issue opened to IssueAgent
    ✓ routes agent-execute label to CoordinatorAgent
    ✓ routes PR opened to ReviewAgent
    ✓ routes PR merged to DeploymentAgent
    ✓ routes push to main to DeploymentAgent
    ✓ routes /agent command to CoordinatorAgent
    ✓ routes workflow failure to Guardian
    ✓ skips draft PR
  ✓ Retry Mechanism (5)
    ✓ succeeds on first attempt
    ✓ retries on transient failure
    ✓ uses exponential backoff
    ✓ fails after max retries
    ✓ adds jitter to delay
  ✓ Rate Limiting (4)
    ✓ allows requests within limit
    ✓ blocks requests over limit
    ✓ resets after window
    ✓ tracks per identifier

Test Files  1 passed (1)
Tests  23 passed (23)
Duration  2.34s

Integration Testing

Manual Test Checklist:

  1. Create Issue → Verify IssueAgent triggers
  2. Add agent-execute label → Verify CoordinatorAgent triggers
  3. Create PR → Verify ReviewAgent triggers
  4. Merge PR → Verify DeploymentAgent triggers
  5. Push to main → Verify DeploymentAgent triggers
  6. Comment /agent analyze → Verify CoordinatorAgent triggers
  7. Fail workflow → Verify Guardian creates escalation issue
  8. Invalid signature → Verify rejection
  9. Old timestamp → Verify rejection
  10. Excessive requests → Verify rate limiting

Troubleshooting Guide

Issue: Webhook Not Triggering

Symptom: Events occur but workflow doesn't run

Solutions:

  1. Check workflow file syntax: .github/workflows/webhook-handler.yml
  2. Verify workflow is enabled (Actions tab → Enable workflow)
  3. Check event triggers match: on.issues.types, on.pull_request.types
  4. Review GitHub Actions logs for errors
  5. Verify repository has Actions enabled

Issue: Signature Verification Failing

Symptom: Security checks reject valid webhooks

Solutions:

  1. Verify WEBHOOK_SECRET is set correctly in repository secrets
  2. Check signature format: sha256=<hex>
  3. Ensure payload matches exactly (no modifications)
  4. Test with signature verification disabled (dev only):
    skipIPCheck: true,
    skipRateLimit: true,
    
  5. Compare computed HMAC with received signature

Issue: Agent Not Executing

Symptom: Routing succeeds but agent doesn't run

Solutions:

  1. Check if agent workflow exists (e.g., agent-runner.yml)
  2. Verify agent label is correct: agent-execute
  3. Check agent implementation exists
  4. Review agent execution logs
  5. Verify GITHUB_TOKEN has required permissions

Issue: Rate Limit Exceeded

Symptom: 429 errors or "Rate limit exceeded" messages

Solutions:

  1. Increase rate limit in webhook-router.ts:
    const DEFAULT_RATE_LIMIT = {
      maxRequests: 200,  // Increase from 100
      windowMs: 60000,
    };
    
  2. Implement request queuing
  3. Add backoff logic
  4. Use authenticated requests (higher limits)
  5. Contact GitHub support for higher limits

Issue: Retries Exhausted

Symptom: "Failed after N retries" errors

Solutions:

  1. Check network connectivity
  2. Verify GitHub API status: https://www.githubstatus.com/
  3. Increase retry count:
    const RETRY_CONFIG = {
      maxRetries: 5,  // Increase from 3
    };
    
  4. Review error logs for root cause
  5. Implement fallback mechanism

Next Phase Transition

Phase C Dependencies Unlocked

Phase B completion enables Phase C: Discussions

  • Webhook events can trigger discussion bot
  • Comment commands work in discussions
  • Discussion events can route to agents

Integration Points

For Phase C (Discussions):

// Webhook can trigger discussion bot
import { DiscussionBot } from './discussion-bot';

async function onDiscussionEvent(event: DiscussionEvent) {
  const bot = new DiscussionBot();

  if (event.action === 'created') {
    await bot.processDiscussion(event.discussion);
  }

  if (event.action === 'comment_created' &&
      event.comment.body.includes('/convert-to-issue')) {
    await bot.convertToIssue(event.discussion);
  }
}

For Phase F (Security):

// Webhook security integrates with security scanning
import { performSecurityCheck } from './webhook-security';

async function onSecurityEvent(event: SecurityEvent) {
  const securityResult = await performSecurityCheck({
    signature: event.signature,
    payload: event.payload,
    timestamp: event.timestamp,
  });

  if (!securityResult.passed) {
    // Create security incident issue
    await createSecurityIssue({
      title: 'Webhook Security Failure',
      reason: securityResult.reason,
    });
  }
}

Agent Commands Reference

Users can trigger agents using comment commands:

Available Commands

/agent analyze          # Trigger IssueAgent for analysis
/agent execute          # Start autonomous execution (CoordinatorAgent)
/agent review           # Request ReviewAgent code review
/agent status           # Check current task status
/agent deploy           # Trigger deployment (DeploymentAgent)
/agent rollback         # Rollback last deployment

Command Flow

  1. User posts comment with /agent <command>
  2. Webhook Handler detects comment
  3. Event Router parses command
  4. CoordinatorAgent executes command
  5. Result posted as comment

Command Examples

Example 1: Analyze Issue

Comment: /agent analyze
Result: IssueAgent analyzes issue and suggests labels

Example 2: Execute Task

Comment: /agent execute
Result: CoordinatorAgent decomposes task and assigns agents

Example 3: Review PR

Comment: /agent review
Result: ReviewAgent performs code quality analysis

Configuration Reference

Environment Variables

# Required
GITHUB_TOKEN=ghp_xxxxx           # GitHub personal access token
WEBHOOK_SECRET=your_secret       # Webhook HMAC secret

# Optional
RETRY_MAX_ATTEMPTS=3             # Max retry attempts
RETRY_INITIAL_DELAY=1000         # Initial retry delay (ms)
RATE_LIMIT_MAX=100               # Max requests per window
RATE_LIMIT_WINDOW=60000          # Rate limit window (ms)

Webhook Secret Setup

# Generate secure random secret
openssl rand -hex 32

# Add to GitHub repository secrets
# Settings → Secrets and variables → Actions
# → New repository secret
# Name: WEBHOOK_SECRET
# Value: <generated-secret>

References and Resources

Official Documentation

Internal Documentation

  • Phase A: Projects V2 (Data Persistence)
  • Phase C: Discussions (Message Queue)
  • Security Best Practices: SECURITY.md

Related Files

  • Webhook Router: scripts/webhook-router.ts
  • Security Module: scripts/webhook-security.ts
  • Test Suite: tests/webhook-router.test.ts

Credits

Implemented by: CodeGenAgent Issue: #5 Phase B Model: Claude Sonnet 4 Date: 2025-10-08 Duration: 5 hours


Status: ✅ Complete Next Phase: Phase C - Discussions