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
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:
- Collect — fetch 8 RSS feeds concurrently, deduplicate against memory
- Score — batch-score 20+ article titles in a single LLM call for relevance
- Summarize — pass the top items through an LLM transform with a prompt template
- 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)
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
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
:lighttier (Gemini Flash free tier: 250 calls/day) - Research synthesis uses
:mediumtier - Deep reasoning uses
:heavytier (explicit only, never auto-selected) - The
:localtier 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:
- Written to PostgreSQL
- Updated in ETS cache
- Broadcast via Phoenix PubSub
- 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:
- Step-level
llm_tierandllm_model - Workflow-level
default_provider - 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 |
Skills implement the AlexClaw.Skill behaviour — two callbacks:
@callback description() :: String.t()
@callback run(keyword()) :: {:ok, term()} | {:error, term()}
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 comparison —
Plug.Crypto.secure_comparefor all secret comparisons
Admin UI
Phoenix LiveView — fully server-rendered, no JavaScript hooks. 12 pages covering every aspect of the system.
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
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)