ELENA is a self-hosted, multi-agent assistant built on Google ADK (Agent Development Kit) with Gemini 2.5 models. A FastAPI backend exposes a hierarchical agent system; a Next.js frontend provides chat plus a browser-native voice interface (Gemini Live API).
Status: hardened prototype. Not production-grade yet, but security, resilience and CI foundations are in place (see below). See
CLAUDE.mdfor the engineering guide andSECURITY_AUDIT.mdfor the security posture.
The core lives in backend/main_simple.py::create_agents(). Each capability is
composed in three layers:
- Tool — a tool (migrated from
suna-main) wrapped viabackend/tools/base/suna_tool_adapter.py, grouped by domain underbackend/tools/<domain>_tools/. - Specialist
LlmAgent— a Gemini agent bound to one domain's tools (files, shell, browser, vision, docs, deploy, knowledge base, …). AgentTool— each specialist is wrapped and registered on a singlecoordinator(gemini-2.5-flash) that routes by delegation.
~22 specialists are wired today. Models: gemini-2.5-flash (coordinator + fast
specialists), gemini-2.5-pro (research), and a native-audio model for voice.
FastAPI. The canonical server is backend/main_simple.py on port 8002.
Key endpoints: /health, /agents, /initiate-agent, /api/chat,
/thread/{id}/agent/start, /auth/dev-token (dev only), plus voice/dashboard
routers. Sessions are in-memory by default and upgrade to Supabase when
configured.
Next.js (App Router) + React + TypeScript + Tailwind. Voice is client-side:
the browser connects directly to the Gemini Live API; text/agent calls go to the
backend at NEXT_PUBLIC_API_URL.
The codebase went through a full security audit + remediation
(SECURITY_AUDIT.md). Highlights:
- Authentication enforced on agent/chat endpoints; JWT signatures are
verified (no more
verify_signature=False).user_idis derived from the token — per-user session isolation, no shareddefault-user. - No hardcoded secrets in code. Keys live in
.env(git-ignored); a dev-onlyPOST /auth/dev-tokenmints short-lived signed tokens for local use (the old client-side JWT signing with a shared secret was removed). - CORS locked to explicit origins/methods/headers; rate limiting per IP; request size limits; generic error responses (no stack-trace leakage).
- SSRF guards on server-side fetches (blocks private/link-local/metadata
IPs); command-injection fixes across shell/files/kb/deploy/browser tools
(
shlexquoting, allowlists, workspace-confined paths); human-in-the-loop kill-switch for destructive ops (ALLOW_DESTRUCTIVE_OPS).
Never commit
.env. If a key was ever exposed, rotate it (e.g. the Gemini key in Google Cloud Console, restricted by referrer/IP).
backend/resilience/ — small, framework-agnostic primitives
(details):
host_health— dead-host cooldown tracker (thread-safe, monotonic clock).fallback— ordered candidate fallback that skips cooled hosts.verifier— independent fresh-context completion verifier (fails open).
These are wired into /api/chat: the coordinator runs with model fallback
(degrades to a no-tools answer instead of a 500), and an optional verifier
(gated to runs that actually used tools) double-checks the deliverable and can
trigger one corrective round.
cd backend
python3 -m venv ../venv && source ../venv/bin/activate
pip install -r requirements.txt
cp .env.example .env # then fill in GOOGLE_API_KEY, JWT_SECRET_KEY, …
python3 main_simple.py # serves on http://localhost:8002Smoke test (auth is on by default):
TOKEN=$(curl -s -X POST localhost:8002/auth/dev-token -d user_id=me | jq -r .access_token)
curl -s -X POST localhost:8002/api/chat \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"message":"17 * 23?","session_id":"s1"}'cd frontend
npm install
npm run dev # http://localhost:3000| Var | Required | Purpose |
|---|---|---|
GOOGLE_API_KEY |
✅ | Gemini API key |
JWT_SECRET_KEY |
✅ | Signs/verifies auth tokens |
ENV_MODE |
— | development (default) enables /auth/dev-token |
AUTH_ENABLED |
— | true (default); set false to bypass auth locally |
LLM_FALLBACK_MODELS |
— | CSV of fallback models for /api/chat |
AGENT_VERIFIER |
— | true enables the completion verifier |
RATE_LIMIT_PER_MIN |
— | Per-IP request cap (default 60) |
SUPABASE_*, DAYTONA_*, RAPID_API_KEY, COMPOSIO_* |
— | Optional integrations |
The Daytona client is initialized lazily, so the app imports and tests run without sandbox credentials.
cd backend
pytest tests/test_resilience.py tests/test_chat_integration.py -v # 28 tests- Resilience unit tests — pure stdlib, no network.
/api/chatintegration tests — network-free (coordinator + verifier mocked).- CI (
.github/workflows/ci.yml) runs both on every push/PR tomain.
backend/
main_simple.py # canonical server (:8002) — agent wiring + endpoints
auth/ # JWT manager + FastAPI auth dependencies
resilience/ # fallback / dead-host / verifier (+ tests)
tools/ # domain tools + base/ (adapter, security helpers)
services/ # sandbox (Daytona, lazy), supabase, logger
tests/ # resilience + chat integration tests
frontend/ # Next.js app (chat + voice)
SECURITY_AUDIT.md # audit findings + remediation checklist
CLAUDE.md # engineering guide for contributors / AI agents
Apache 2.0 (inherited from the upstream suna-main tooling).