Skip to content

Architecture

How the Automate-E runtime turns a character.json into a running Discord agent.

Component Overview

graph TB
    subgraph "Automate-E Runtime"
        CL[Character Loader<br/>character.js]
        DG[Discord Gateway<br/>discord.js]
        AL[Agent Loop<br/>agent.js]
        TD[Tool Dispatcher<br/>agent.js]
        MS[Memory Store<br/>memory.js]
        UT[Usage Tracker<br/>usage.js]
        DB[Dashboard<br/>dashboard/]
    end

    CF[character.json<br/>ConfigMap] --> CL
    DC[Discord] <--> DG
    DG --> AL
    AL --> TD
    AL <--> MS
    AL --> UT
    TD --> API[Tool APIs]
    AL <--> Claude[Claude API]
    DB <--> WS[WebSocket Clients]
    UT --> DB

Startup Sequence

sequenceDiagram
    participant R as Runtime
    participant CL as Character Loader
    participant MS as Memory Store
    participant DG as Discord Gateway
    participant DB as Dashboard

    R->>CL: Load CHARACTER_FILE
    CL->>CL: Validate required fields
    CL->>CL: Apply defaults
    R->>MS: Connect to Postgres (or init in-memory)
    R->>DG: Login with DISCORD_BOT_TOKEN
    DG->>DG: Register messageCreate handler
    R->>DB: Start HTTP + WebSocket server
    Note over R: Agent is ready

Message Processing

When a Discord message arrives, the runtime processes it through these stages:

flowchart TD
    MSG[Discord messageCreate] --> FILTER{Channel match?}
    FILTER -->|No| DROP[Ignore]
    FILTER -->|Yes| BOT{From allowed bot<br/>or human?}
    BOT -->|No| DROP
    BOT -->|Yes| LOAD[Load conversation<br/>history from memory]
    LOAD --> BUILD[Build system prompt:<br/>personality + lore +<br/>user facts + style]
    BUILD --> CALL[Call Claude API<br/>with tools]
    CALL --> TOOL{Tool use<br/>response?}
    TOOL -->|Yes| EXEC[Execute HTTP call<br/>to tool API]
    EXEC --> RESULT[Return result<br/>to Claude]
    RESULT --> CALL
    TOOL -->|No| TEXT[Extract text response]
    TEXT --> SAVE[Save messages<br/>to memory]
    SAVE --> REPLY[Post reply<br/>in Discord thread]

Key Design Decisions

Tool Calling via HTTP

Tools are HTTP endpoints, not code plugins. This means:

  • Agents can call any REST API without runtime changes
  • Tool definitions are pure configuration (no code deployment)
  • APIs can be written in any language
  • Tools are independently scalable Kubernetes services

Character as Configuration

The entire agent personality and behavior is defined in character.json:

  • No agent-specific code in the runtime
  • Multiple agents share the same runtime image
  • Character changes deploy via ConfigMap update (no image rebuild)
  • Version control and review for personality changes

Memory Layers

The memory system has three layers:

Layer Scope Retention Purpose
Conversations Per thread Configurable (default 30d) Context for ongoing conversations
Facts Per user Indefinite Learned preferences and patterns
Patterns Per entity (e.g., merchant) Indefinite Auto-approval confidence scores

Agent Loop Constraints

  • Maximum 5 tool calls per message (prevents runaway loops)
  • Each tool call is an independent HTTP request
  • The agent loop is synchronous per message (no parallel tool calls)
  • Failed tool calls return error text to Claude (does not crash the loop)

File Structure

automate-e/
  src/
    index.js          # Entry point, startup orchestration
    character.js      # Loads and validates character.json
    agent.js          # Agent loop, tool dispatch, prompt building
    memory.js         # Postgres + in-memory storage
    usage.js          # Token counting and cost calculation
    dashboard/
      server.js       # HTTP server + WebSocket
      index.html      # Dashboard UI
  Dockerfile
  package.json