DEV Community

Cover image for AlexClaw: A BEAM-Native Personal AI Agent Built on Elixir/OTP
Alessio Battistutta
Alessio Battistutta

Posted on

AlexClaw: A BEAM-Native Personal AI Agent Built on Elixir/OTP

AlexClaw: A BEAM-Native Personal AI Agent Built on Elixir/OTP

AlexClaw is a personal autonomous AI agent that monitors RSS feeds, web sources, GitHub repositories, and Google services — accumulates knowledge in PostgreSQL, executes multi-step workflows on schedule, and communicates via Telegram. It runs entirely on your infrastructure.

The key architectural decision: the BEAM VM is the runtime, not a container for Python-style orchestration. Supervision trees, ETS caching, GenServers, and PubSub are the actual building blocks — not abstractions bolted on top.

GitHub: github.com/thatsme/AlexClaw

AlexClaw Dashboard


What It Does

AlexClaw runs workflows — multi-step pipelines that combine skills (RSS collection, web search, LLM summarization, API calls, browser automation) and deliver results to Telegram. Workflows run on cron schedules or on demand.

A typical workflow:

  1. Collect — fetch 8 RSS feeds concurrently, deduplicate against memory
  2. Score — batch-score 20+ article titles in a single LLM call for relevance
  3. Summarize — pass the top items through an LLM transform with a prompt template
  4. Deliver — send the briefing to Telegram

This runs every morning at 7:00 with zero interaction.


Architecture

Telegram <──> Gateway (GenServer) <──> Dispatcher (pattern matching)
                                            │
                                      SkillSupervisor
                                     (DynamicSupervisor)
                                            │
                              ┌─────────────┼─────────────┐
                           RSS            Research      GitHub
                          Skill            Skill     Security Review
                                            │
                                       LLM Router
                              (Gemini / Anthropic / Local)
                                            │
                                ┌───────────┴───────────┐
                             Memory                  Config
                          (pgvector)             (DB + ETS + PubSub)
Enter fullscreen mode Exit fullscreen mode

Supervision Tree (13 children)

The application starts 13 supervised processes:

  • Repo — PostgreSQL connection pool (Ecto)
  • PubSub — config change broadcast to all processes
  • TaskSupervisor — supervised fire-and-forget tasks (workflow execution, background reviews)
  • UsageTracker — ETS owner for LLM call counters, persisted to DB
  • Config.Loader — seeds environment variables into DB, loads into ETS
  • LogBuffer — in-memory ring buffer (500 entries) attached to Erlang Logger
  • Google.TokenManager — OAuth2 token lifecycle with auto-refresh
  • RateLimiter.Server — ETS-based login rate limiting with periodic purge
  • SkillSupervisor — DynamicSupervisor for isolated skill execution
  • Scheduler — Quantum cron scheduler
  • SchedulerSync — syncs DB workflow schedules into Quantum jobs
  • Gateway — Telegram long-polling bot
  • Endpoint — Phoenix HTTP server (LiveView admin UI)

Every async operation (workflow runs, GitHub reviews, background tasks) executes under Task.Supervisor — crashes are reported, not silently lost.

Why the BEAM

The BEAM gives you things for free that other runtimes require libraries or infrastructure for:

  • Process isolation — a failed RSS fetch doesn't affect a concurrent research query. Each skill runs in its own process.
  • Supervision — if a GenServer crashes, it restarts. The application recovers without external health checks.
  • ETS — in-process shared memory tables for config cache, usage counters, rate limiting, and token caching. No Redis needed.
  • PubSub — config changes broadcast to all processes immediately. No polling.
  • Lightweight concurrency — RSS feeds are fetched concurrently with Task.async_stream. Workflow steps run sequentially but the workflow itself runs in a supervised task.

The LLM Router

Every LLM call in AlexClaw declares a tier: :light, :medium, :heavy, or :local. The router selects the cheapest available model for that tier and falls back automatically.

light:  gemini_flash → haiku → lm_studio → ollama → custom providers
medium: gemini_pro → sonnet → lm_studio → ollama → custom providers
heavy:  opus → lm_studio → ollama → custom providers
local:  lm_studio → ollama → custom providers
Enter fullscreen mode Exit fullscreen mode

Daily usage is tracked per provider in ETS and persisted to PostgreSQL. Each provider has a configurable daily limit. When a provider hits its limit, the router skips it and tries the next one.

Custom providers (any OpenAI-compatible endpoint) can be added via the admin UI. This means you can run multiple local models on LM Studio — same host, different model names, each with its own tier and limit.

A fully local deployment with zero API keys works — set LMSTUDIO_ENABLED=true and all tiers route to your local model.

Cost Tracking

The router doesn't just fall back — it actively minimizes cost:

  • RSS relevance scoring uses :light tier (Gemini Flash free tier: 250 calls/day)
  • Research synthesis uses :medium tier
  • Deep reasoning uses :heavy tier (explicit only, never auto-selected)
  • The :local tier bypasses all cloud providers

Usage counters reset at midnight UTC via Process.send_after in the UsageTracker GenServer.


Runtime Configuration

All settings live in PostgreSQL, cached in ETS, editable at runtime via the admin UI. No restart required for any change.

On first boot, Config.Loader seeds default values from environment variables. After that, the database is the source of truth. When a value changes:

  1. Written to PostgreSQL
  2. Updated in ETS cache
  3. Broadcast via Phoenix PubSub
  4. All subscribed processes see the change immediately

Categories include: identity/persona, LLM API keys and limits, Telegram settings, GitHub tokens, Google OAuth, rate limiting thresholds, prompt templates, and skill-specific config.

The system prompt is fully configurable — persona name, base prompt, and per-skill context fragments are all config keys. Zero hardcoded strings.


Workflow Engine

Workflows are multi-step pipelines stored in PostgreSQL. Each step specifies:

  • Skill name — which registered skill to execute
  • Config JSON — step-level overrides (different repo, different API token, different prompt)
  • LLM tier/provider — override the default routing for this step
  • input_from — pull input from a specific earlier step (not just the previous one)

Provider routing has three levels of specificity:

  1. Step-level llm_tier and llm_model
  2. Workflow-level default_provider
  3. Global tier-based fallback chain

12 Registered Skills

Skill Description
rss_collector Fetch RSS feeds, batch-score relevance, notify
web_search Search DuckDuckGo, fetch results, synthesize answer
web_browse Fetch URL, extract text, answer questions
research Deep research with memory context
conversational Free-text LLM conversation
telegram_notify Send workflow output to Telegram
llm_transform Run a prompt template through the LLM
api_request Authenticated HTTP requests with retries
github_security_review PR/commit diff security analysis
google_calendar Fetch upcoming Google Calendar events
google_tasks List and create Google Tasks
web_automation Browser recording and headless replay

AlexClaw Skills

Skills implement the AlexClaw.Skill behaviour — two callbacks:

@callback description() :: String.t()
@callback run(keyword()) :: {:ok, term()} | {:error, term()}
Enter fullscreen mode Exit fullscreen mode

Adding a new skill is one module, one registry entry.


Memory System

PostgreSQL + pgvector for persistent knowledge storage.

Each memory entry has a kind (:news_item, :summary, :conversation, :security_review), content, optional source URL, JSONB metadata, optional vector embedding, and optional TTL.

Search uses cosine similarity on pgvector when embeddings are available, with keyword (ILIKE) fallback. Deduplication by URL prevents the same article from being scored and notified twice.

The RSS collector stores every worthy item. The research skill stores summaries. The conversational skill stores both user messages and assistant responses for context continuity.


Security Features

  • Session-based authentication on all admin routes
  • TOTP 2FA — optional two-factor for sensitive workflow execution (setup via Telegram, 2-minute challenge expiry)
  • Login rate limiting — ETS-based, configurable max attempts and block duration
  • HMAC-SHA256 webhook verification — raw body cached before JSON parsing for correct signature verification
  • Telegram chat_id filtering — rejects messages from unauthorized users
  • Timing-safe comparisonPlug.Crypto.secure_compare for all secret comparisons

Admin UI

Phoenix LiveView — fully server-rendered, no JavaScript hooks. 12 pages covering every aspect of the system.

AlexClaw Workflows


Deployment

Single docker compose up -d. The stack:

  • Elixir release — compiled OTP release (Alpine-based, ~125 MB runtime)
  • PostgreSQL 17 + pgvector — persistent storage
  • Web automator (optional) — Python/Playwright sidecar for browser automation

Minimum requirements: Docker, a Telegram bot token, and at least one LLM provider (can be fully local).

git clone https://github.com/thatsme/AlexClaw.git
cd AlexClaw
cp .env.example .env
# Edit .env with your credentials
docker compose up -d
Enter fullscreen mode Exit fullscreen mode

Tech Stack

Component Technology
Runtime Elixir 1.19 / OTP 28 / BEAM
Web framework Phoenix 1.7 + LiveView
HTTP server Bandit
Database PostgreSQL 17 + pgvector
HTTP client Req
Cron scheduler Quantum
RSS parsing SweetXml
HTML parsing Floki
2FA NimbleTOTP + EQRCode
Browser automation Playwright (Python sidecar)

What's Next

The ROADMAP.md in the repository tracks planned features. Current priorities include embedding integration for semantic memory search, additional LLM providers, and workflow branching logic.


AlexClaw is open source under the Apache License 2.0.

GitHub: github.com/thatsme/AlexClaw

Built by Alessio Battistutta.

Top comments (0)