Day 2 12 min read

Understanding the Architecture

Explore ThePopeBot's event-driven architecture, learn how the Event Handler and Agent Engine work together, and trace the full lifecycle of a request.

Event Handler + Agent Engine Explained

ThePopeBot is built on two core pillars that work together to process every incoming request:

The Event Handler

The Event Handler is the entry point for all interactions. It receives raw events from various channels (web UI, Telegram, webhooks, cron jobs) and normalizes them into a unified event format.

[Telegram Message] ──┐
[Web UI Input]     ─────▢ Event Handler ──▢ Normalized Event
[Webhook Payload]  ───
[Cron Trigger]     β”€β”€β”˜

The Event Handler is responsible for:

  • Authentication: Verifying the source of incoming events
  • Normalization: Converting channel-specific formats into a standard event object
  • Routing: Directing events to the appropriate agent or pipeline
  • Rate limiting: Protecting the system from abuse

The Agent Engine

The Agent Engine is the brain of the system. It receives normalized events and orchestrates the full reasoning and execution cycle:

  1. Context Assembly β€” Gathers relevant history, tools, and system prompts
  2. Planning β€” Determines what steps are needed to fulfill the request
  3. Execution β€” Runs each step, calling tools and LLM APIs as needed
  4. Observation β€” Evaluates results and decides whether to continue or respond
  5. Response β€” Sends the final output back through the Event Handler

File Structure Walkthrough

Understanding the project layout helps you navigate and extend the codebase:

thepopebot/
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ agents/          # Agent definitions and configurations
β”‚   β”‚   β”œβ”€β”€ default.ts   # The default general-purpose agent
β”‚   β”‚   └── reviewer.ts  # Code review specialist agent
β”‚   β”œβ”€β”€ engine/          # Core agent engine
β”‚   β”‚   β”œβ”€β”€ planner.ts   # Task planning logic
β”‚   β”‚   β”œβ”€β”€ executor.ts  # Step execution and tool calling
β”‚   β”‚   └── context.ts   # Context assembly and management
β”‚   β”œβ”€β”€ events/          # Event handler and channel adapters
β”‚   β”‚   β”œβ”€β”€ handler.ts   # Main event handler
β”‚   β”‚   β”œβ”€β”€ telegram.ts  # Telegram adapter
β”‚   β”‚   └── webhook.ts   # Webhook adapter
β”‚   β”œβ”€β”€ tools/           # Tool definitions for agents
β”‚   β”‚   β”œβ”€β”€ filesystem.ts
β”‚   β”‚   β”œβ”€β”€ git.ts
β”‚   β”‚   └── search.ts
β”‚   └── config/          # Configuration files
β”‚       β”œβ”€β”€ agents.yaml  # Agent registry
β”‚       └── tools.yaml   # Tool registry
β”œβ”€β”€ docker-compose.yml
β”œβ”€β”€ .env.example
└── package.json

Request Lifecycle

Let us trace a message from the moment it arrives until the response is sent back. Consider a user sending β€œReview the latest PR” through Telegram:

Step 1: Event Ingestion

The Telegram adapter receives the message and creates a normalized event:

const event: AgentEvent = {
  source: 'telegram',
  userId: 'user-12345',
  message: 'Review the latest PR',
  timestamp: Date.now(),
  metadata: { chatId: 67890 }
};

Step 2: Routing

The Event Handler inspects the event and routes it to the appropriate agent. Based on the message content mentioning β€œPR”, it routes to the reviewer agent.

Step 3: Context Assembly

The engine gathers:

  • The agent’s system prompt
  • Recent conversation history
  • Available tools (git, filesystem, GitHub API)
  • Any relevant project context

Step 4: Planning and Execution

The agent engine calls the LLM with the assembled context. The LLM produces a plan:

  1. Fetch the latest PR using the GitHub tool
  2. Read the changed files
  3. Analyze the diff for issues
  4. Generate a review summary

Each step is executed sequentially, with the results fed back to the LLM for the next step.

Step 5: Response Delivery

The final review summary is sent back through the Event Handler, which formats it for the Telegram channel and delivers it to the user.

Key Configuration Files

agents.yaml

This file defines which agents are available and their properties:

agents:
  default:
    name: "General Assistant"
    model: "gpt-4"
    temperature: 0.7
    tools:
      - filesystem
      - search

  reviewer:
    name: "Code Reviewer"
    model: "gpt-4"
    temperature: 0.3
    tools:
      - git
      - filesystem
    systemPrompt: "./prompts/reviewer.md"

tools.yaml

This file registers available tools and their configurations:

tools:
  filesystem:
    description: "Read and write files"
    permissions:
      - read
      - write

  git:
    description: "Git operations"
    permissions:
      - read
      - diff

How Messages Flow Through the System

Here is the complete flow visualized:

User Input
    β”‚
    β–Ό
Channel Adapter (Telegram / Web / Webhook / Cron)
    β”‚
    β–Ό
Event Handler ──▢ Authentication ──▢ Normalization ──▢ Routing
    β”‚
    β–Ό
Agent Engine
    β”œβ”€β”€ Context Assembly
    β”œβ”€β”€ LLM Planning
    β”œβ”€β”€ Tool Execution (loop)
    └── Response Generation
    β”‚
    β–Ό
Event Handler ──▢ Channel Formatting ──▢ Delivery
    β”‚
    β–Ό
User Response

Each layer is designed to be independent and replaceable. You can swap out the Telegram adapter for a Slack adapter without touching the Agent Engine. You can change the LLM provider without modifying the Event Handler. This separation of concerns is what makes ThePopeBot extensible and maintainable.