Skip to content

Latest commit

 

History

History
229 lines (170 loc) · 7.97 KB

File metadata and controls

229 lines (170 loc) · 7.97 KB

Charon Architecture

System Components

┌────────────────────────────────────────────────────────────────────────────┐
│                                 CHARON                                     │
│                                                                            │
│  ┌──────────────┐     ┌──────────────┐     ┌──────────────┐                │
│  │   Ingress    │     │  Processor   │     │   Egress     │                │
│  │              │     │              │     │              │                │
│  │ - Webhooks   │────►│ - Sanitizer  │────►│ - Handlers   │──────► Output  │
│  │ - Scheduler  │     │ - Composer   │     │              │                │
│  │              │     │              │     │              │                │
│  └──────────────┘     └──────────────┘     └──────────────┘                │
│         │                    │                    │                        │
│         ▼                    ▼                    ▼                        │
│  ┌─────────────────────────────────────────────────────────┐               │
│  │                    Observability                        │               │
│  │   [Event Log]  [State Store]  [Metrics]  [Audit Trail]  │               │
│  └─────────────────────────────────────────────────────────┘               │
│                                                                            │
└────────────────────────────────────────────────────────────────────────────┘

Component Details

1. Ingress

Responsible for receiving and validating incoming triggers.

Webhook Handler

  • Exposes HTTP endpoints: POST /webhook/{trigger-id}
  • Validates trigger exists and is enabled
  • Captures raw payload and headers
  • Emits trigger.received event

Scheduler

  • Manages cron expressions for all configured triggers
  • Fires at scheduled times
  • Emits trigger.scheduled event with trigger context

2. Processor

The core logic pipeline for transforming triggers into task descriptors.

Sanitizer

  • Loads and executes sanitization scripts from sanitizers/ folder
  • Direct TypeScript imports (no sandboxing)
  • Handles script errors gracefully
  • Returns extracted context or null (skip)
  • This is the decision point: returning null means "no action needed"

Composer

  • Loads task template for trigger
  • Interpolates placeholders with sanitized context
  • Merges static context with dynamic payload
  • Produces composed task description string

3. Egress

Passes Task Descriptors to the configured handler.

Handler Loading

  • Handlers are loaded dynamically from egress/ folder
  • Each trigger config specifies which handler to use
  • Handler receives the Task Descriptor and acts on it

Built-in Handlers

  • console - Logs the Task Descriptor (for development/debugging)
  • cli - Executes shell commands (Claude CLI, Aider, etc.) - see EGRESS_CLI.md

Data Flow

Webhook Flow

1. HTTP POST /webhook/github-issues
   Body: { "action": "opened", "issue": { ... } }

2. Ingress validates trigger "github-issues" exists
   → Emits: trigger.received { trigger_id, payload, timestamp }

3. Sanitizer executes sanitizers/github-issue.ts
   → Input: raw payload
   → Output: { issue_number: 123, title: "Bug...", ... }
   → Emits: trigger.sanitized { trigger_id, context }

   (If sanitizer returns null)
   → Emits: trigger.skipped { trigger_id, reason: "sanitizer_null" }
   → Flow ends, no task emitted

4. Composer interpolates template
   → Template: "Fix issue #{issue_number}: {title}"
   → Result: "Fix issue #123: Bug in login form"
   → Emits: trigger.composed { trigger_id, description }

5. Egress passes TaskDescriptor to configured handler
   → Handler executes (e.g., console.log, POST, etc.)
   → Emits: trigger.completed

Cron Flow

1. Scheduler fires at 09:00 for trigger "daily-review"

2. Ingress creates trigger event
   → Context: { trigger_time: "2025-01-05T09:00:00Z", trigger_id: "daily-review" }
   → Emits: trigger.scheduled { trigger_id, context }

3. (No sanitizer for cron triggers - always proceeds)

4. Composer interpolates template
   → Template: "Review commits since yesterday and summarize changes"
   → Merges with static context from config
   → Emits: trigger.composed { trigger_id, description }

5. Egress passes TaskDescriptor to configured handler

State Management

Trigger Registry

  • In-memory cache of trigger configurations
  • Hot-reloaded on config file changes
  • Schema validated on load

Run Log

  • Persistent log of all trigger executions
  • Indexed by: trigger_id, timestamp, status
  • Retained for configurable period (default: 30 days)

Dynamic Handlers

  • Sanitizers loaded from sanitizers/ folder
  • Egress handlers loaded from egress/ folder
  • Hot-reloaded on file changes

Technology Choices

Component Technology Rationale
Runtime Bun Fast, built-in SQLite, TypeScript native
Backend Hono Lightweight, fast, Bun-native HTTP framework
Frontend React SPA (Vite) Fast builds, HMR, simple distribution
Database SQLite (bun:sqlite) Simple, embedded, sufficient for single-node
Scheduler node-cron Lightweight, well-tested
UI shadcn/ui + Tailwind Modern, accessible, composable
Sanitizers Direct TypeScript imports Simple, testable, no sandboxing overhead

Configuration Schema

# triggers.yaml

triggers:
  - id: github-issues
    name: GitHub Issue Handler
    type: webhook
    enabled: true
    template: |
      New issue opened in {repo}:

      #{issue_number}: {title}

      {body}

      Determine if this requires code changes.
    sanitizer: github-issue    # loads from sanitizers/github-issue.ts
    egress: console            # loads from egress/console.ts
    context:
      priority: normal

  - id: daily-standup
    name: Daily Standup Summary
    type: cron
    schedule: "0 9 * * 1-5"
    enabled: true
    template: |
      Generate a standup summary for today.

      Review recent commits and open PRs.
      Highlight any blockers or items needing attention.
    egress: console
    context:
      team: platform

settings:
  retention:
    days: 30

Error Handling

Sanitizer Errors

  • Script syntax errors → log error, emit trigger.error, skip trigger
  • Runtime exceptions → log error, emit trigger.error, skip trigger

Composer Errors

  • Missing placeholder in context → log warning, use empty string
  • Template syntax error → log error, emit trigger.error, skip trigger

General Principle

When in doubt, don't emit a task. False negatives (missing a task) are better than false positives (spurious tasks).


Scaling Considerations

This design assumes single-node deployment. For scaling:

  1. Horizontal API scaling - Stateless webhook handlers behind load balancer
  2. Shared state - Move SQLite → PostgreSQL for shared run log
  3. Distributed scheduling - Use external scheduler (e.g., Temporal, Bull) for cron
  4. Rate limiting - Add per-trigger rate limits at ingress

These are future concerns, not initial requirements.