A high-performance, single-binary AI agent runtime written in Zig.
Connect any LLM to any chat channel. Workspace-scoped tools, hybrid flat-file / SQLite memory, cron-scheduled tasks, and a circuit-breaker that survives LLM outages.
Eva is built on the Zero framework and inspired by the multi-channel agent architecture in nanobot.
It is independent of any LLM vendor — any OpenAI-compatible API works (OpenAI, OpenRouter, Groq, Together, llama.cpp, vLLM, Ollama, etc.).
- Multi-channel — WebSocket and Telegram out of the box. Discord and more behind a stable vtable.
- Workspace-scoped tools —
read_file,write_file,list_dir,shell,web_fetch. All operations stay inside a configured directory. SSRF protection onweb_fetchblocks loopback / RFC1918. - Hybrid memory — SQLite for queries, human-editable
MEMORY.md/SOUL.md/USER.mdfor direct editing. A "dream" thread periodically extracts insights; a consolidator summarizes long conversations. - Cron scheduler — 6-field cron (sec min hour day month dow) with
one-shot reminders. LLM can
create_reminder,list_reminders,disable_remindervia natural language. - Reliability — Circuit breaker, token-bucket rate limiter, tool-call
loop detection, structured
key=valuelogging. - Telegram-native HTTPS — Custom BearSSL-based TLS client for
long-polling. No external
curl/wgetneeded. - Single binary — Static or
gcompat-linked. ~7 MB stripped. No runtime dependencies beyondlibrdkafka.so.1andca-certificates. - Configuration by env vars — No config files. All knobs are documented in GETTING_STARTED.md.
flowchart TD
%% External clients
WS_Client["WebUI Client"]
TG_Client["Telegram Client"]
%% Channels
WS_Channel["WebSocket Channel"]
TG_Channel["Telegram Channel"]
%% Message bus
Inbound["Inbound Queue"]
Outbound["Outbound Queue"]
%% Agent loop
Agent["Agent Loop"]
ContextBuilder["Context Builder"]
LLM["LLM Provider"]
Tools["Tool Registry"]
%% Session manager
SessionMgr["Session Manager"]
%% Reliability
CB["Circuit Breaker"]
RL["Rate Limiter"]
%% Memory
MemoryStore["Memory Store<br/>(SQLite + flat files)"]
Consolidator["Consolidator<br/>(auto-summarize)"]
Dream["Dream<br/>(background insights)"]
MemoryFiles["MEMORY.md / SOUL.md / USER.md"]
%% Scheduler
Cronz["Cronz<br/>(*/30s tick)"]
Scheduler["Scheduler"]
%% SQLite
SQLite[("SQLite<br/>sessions / messages /<br/>memories / cron_jobs")]
%% Flow: clients → channels
WS_Client --> WS_Channel
TG_Client --> TG_Channel
%% Flow: channels → inbound
WS_Channel --> Inbound
TG_Channel --> Inbound
%% Flow: inbound → agent
Inbound --> Agent
%% Flow: agent internals
Agent --> ContextBuilder
ContextBuilder --> LLM
LLM -->|"tool calls"| Tools
Tools -->|"results"| Agent
LLM -->|"response"| Agent
%% Flow: reliability
Agent --> CB
CB --> LLM
Agent --> RL
RL --> LLM
%% Flow: agent ↔ session manager
Agent <--> SessionMgr
SessionMgr <--> SQLite
%% Flow: memory
Agent --> Consolidator
Consolidator --> MemoryStore
Dream --> MemoryStore
MemoryStore <--> MemoryFiles
MemoryStore -.-> ContextBuilder
%% Flow: agent → outbound
Agent --> Outbound
%% Flow: outbound → channels
Outbound --> WS_Channel
Outbound --> TG_Channel
%% Flow: cronz → scheduler → outbound
Cronz --> Scheduler
Scheduler -->|"reminder"| Outbound
Scheduler -.-> SQLite
subgraph Zero["Zero Framework"]
WS_Channel
TG_Channel
Inbound
Outbound
Agent
ContextBuilder
LLM
Tools
SessionMgr
SQLite
Cronz
Scheduler
end
classDef client fill:#f0f0f0,stroke:#666
classDef zero fill:#e8f4fd,stroke:#2196F3,stroke-width:2px
classDef data fill:#e8f5e9,stroke:#4CAF50
classDef memory fill:#efebe9,stroke:#795548
classDef scheduler fill:#fff8e1,stroke:#FFC107
classDef reliability fill:#fce4ec,stroke:#E91E63
class WS_Client,TG_Client client
class WS_Channel,TG_Channel,Inbound,Outbound,Agent,ContextBuilder,LLM,Tools,SessionMgr zero
class SQLite data
class MemoryStore,Consolidator,Dream,MemoryFiles memory
class Cronz,Scheduler scheduler
class CB,RL reliability
See docs/ARCHITECTURE.md for a detailed walkthrough of every module.
# 1. Install Zig 0.15.2 and librdkafka-dev
# (see GETTING_STARTED.md for details)
zig version # must be 0.15.1 or 0.15.2
# 2. Clone and build
git clone https://github.com/<owner>/eva.git
cd eva
zig build
# 3. Run with the bare minimum
export EVA_API_KEY="sk-your-key"
export EVA_WS_PORT=8765
./zig-out/bin/eva
# 4. Connect
wscat -c ws://localhost:8765/ws
> {"type":"message","chat_id":"u1","content":"Hi!"}The bot responds in ~1-2 seconds (depending on LLM latency). All
sessions are persisted in <workspace>/eva.db and the JSONL files
under <workspace>/sessions/.
podman build -t eva .
podman run -d --name eva \
-p 8765:8765 \
-e EVA_API_KEY="sk-..." \
-e EVA_WS_PORT=8765 \
-v eva-data:/app/workspace \
evaSee GETTING_STARTED.md for the full multi-stage build explanation, Telegram setup, and Lightpanda integration.
zig build test --summary allExpected: 32/32 tests passed.
Coverage is run with make ut locally, and on every push / PR by .github/workflows/ci.yml (using the same imng/zero-kcov:0.1 container as the zero framework). The badge above is auto-updated on PR merge.
See CONTRIBUTING.md for how to add new tests.
All configuration is via environment variables. The complete list:
| Variable | Default | Description |
|---|---|---|
EVA_API_KEY |
required | LLM provider API key |
EVA_API_BASE |
https://api.openai.com |
OpenAI-compatible endpoint |
EVA_MODEL |
gpt-4o-mini |
Default model name |
EVA_WORKSPACE |
~/.eva/workspace |
Sessions, memory files, SQLite DB |
EVA_SYSTEM_PROMPT |
see source | Initial system prompt |
EVA_MAX_TOKENS |
8192 |
Per-response token cap |
EVA_CONTEXT_WINDOW |
65536 |
Total context budget (50% triggers compact) |
EVA_MAX_TOOL_ITERATIONS |
10 |
Max LLM tool-call loops per turn |
EVA_WS_PORT |
unset | Set to enable WebSocket channel |
EVA_TELEGRAM_TOKEN |
unset | Set to enable Telegram channel |
EVA_TELEGRAM_ALLOWED_USERS |
unset | Comma-separated user IDs. Required for TG to accept any messages |
EVA_CONSOLIDATE_AFTER_TURNS |
20 |
Auto-summarize history after N turns |
EVA_TG_MIN_INTERVAL_MS |
500 |
Telegram edit-message throttle |
EVA_TG_IDLE_TIMEOUT_MS |
300000 |
Drop TG edit state after 5 min idle |
EVA_SESSION_IDLE_TIMEOUT_MS |
300000 |
Evict session locks after 5 min idle |
EVA_DREAM_INTERVAL_SECONDS |
1800 |
Background insight loop interval |
EVA_CIRCUIT_BREAKER_THRESHOLD |
5 |
Failures before circuit opens |
EVA_CIRCUIT_BREAKER_COOLDOWN_MS |
60000 |
Wait before half-open probe |
EVA_RATE_LIMIT_RPM |
60 |
Max LLM calls per minute |
EVA_LIGHTPANDA_PATH |
lightpanda |
Path to lightpanda binary |
EVA_WEB_FETCH_MAX_SIZE |
51200 |
Max bytes per web_fetch response |
configs/.env.sample is a complete example.
eva/
├── src/ # All Zig source (~7,500 LOC across 28 files)
│ ├── main.zig # Process entry, signal handling
│ ├── root.zig # Public API: @import("eva")
│ ├── eva.zig # App, AppConfig, Messages, kvLog
│ ├── bus.zig # Cross-thread message bus
│ ├── agent_loop.zig # Main LLM loop with tool execution
│ ├── gateway.zig # Process orchestrator
│ ├── context_builder.zig # System prompt + history + memory assembly
│ ├── session.zig # SessionManager, SessionLock
│ ├── memory.zig # SQLite + flat-file memory
│ ├── circuit_breaker.zig # Failure protection
│ ├── rate_limiter.zig # Token-bucket rate limiter
│ ├── scheduler.zig # 6-field cron with one-shots
│ ├── http_client.zig # OpenAI-compat HTTP (Connection: close)
│ ├── tls_client.zig # BearSSL-based TLS (Telegram long-poll)
│ ├── channel_manager.zig # Channel ↔ bus wiring
│ ├── providers/ # LLM providers (base, openai_compat, anthropic stub)
│ ├── channels/ # Channels (base, websocket, telegram, discord stub)
│ ├── tools/ # LLM-callable tools (base, registry, filesystem, shell, web)
│ └── migrations/ # SQLite schema migrations (001-006)
├── build.zig # Build configuration (test_module pattern)
├── build.zig.zon # Dependency manifest
├── configs/ # Sample .env files
├── scripts/ # Stress / integration test scripts
├── docs/ # Architecture guide + assets
├── Dockerfile # Multi-stage container (Debian → Alpine+gcompat)
├── docker-compose.yml # Compose deployment
├── AGENTS.md # Development guidelines (for AI agents)
├── CHANGELOG.md # Release notes
├── GETTING_STARTED.md # Setup walkthrough
├── LICENSE # Apache 2.0
├── CONTRIBUTING.md # Contribution guide
├── CODE_OF_CONDUCT.md # Community guidelines
├── SECURITY.md # Vulnerability reporting
└── README.md # This file
# Run tests
zig build test --summary all
# Build with coverage
make ut
# Build optimized release
make release # --release=fast
make release-prod # --release=small
# Format code
zig fmt src/
# Watch tests (use entr or similar)
find src -name '*.zig' | entr -c zig build testSee CONTRIBUTING.md for the full development workflow and code style.
AI agents should read AGENTS.md.
MIT License — see LICENSE.
- Built on the Zero framework
- Inspired by nanobot
- Uses WebSocket server from http.zig
- Telegram HTTPS via zig-bearssl
- Web fetching via Lightpanda
See attribution.md for the full list of dependencies and asset credits.
This project completely built using Qwen 3.6/MiniMax M3 (Free API versions) and with little manual coding. Just for fun and personal use, nothing big to commit on.
It helped me to connect how the agentic engineering works on loop, and achieves with results. It serving well with local models (qwen 3.5 9B/Gemma 4 12B)