Frona is a single application with a Rust backend and a Next.js frontend, backed by an embedded SurrealDB database.
Repository layout
The repo is a Cargo workspace with the frontend alongside:
| Path | Contents |
|---|---|
crates/frona-server/ | Backend server crate (binary frona, library frona) |
crates/frona-cli-mcp/ | The mcpctl CLI used inside MCP sandboxes |
crates/frona-api-types/ | Shared API contract types (used by mcpctl and the bridge) |
crates/frona-model-catalog/ | Shared model and parameter metadata, bundled snapshots, validation, and refresh |
crates/frona-derive/ | Procedural macros (#[derive(Entity)], #[agent_tool], etc.) |
web/ | Next.js frontend |
resources/ | Prompt templates, policy schema, channel manifests, agent definitions |
Engine (Rust backend)
The engine is the core of the platform. It handles:
- HTTP API: built on Axum, serves REST endpoints and SSE streams
- Agent orchestration: loads agent configs, manages tool loops, coordinates delegation
- Tool execution: runs tools server-side, manages browser sessions, sandboxes CLI/Python/Node.js commands
- Policy engine: Cedar-based authorization for tool calls, delegation, messaging, and sandbox actions
- Channels: adapter framework for inbound messaging (Telegram, SMS, Markdown)
- Signals: matcher service that fires watching agents when an inbound matches their criteria
- MCP servers: lifecycle, sandboxing, registry caching, and bridge mode for installed Model Context Protocol servers
- Authentication: JWT-based auth with cookie and header support, optional SSO via OpenID Connect, ephemeral tokens for sandboxed processes
- Database: SurrealDB embedded with RocksDB storage
- Scheduling: background task runner for scheduled tasks, heartbeats, compaction, and maintenance
- Inference: multi-provider LLM abstraction with fallback and retry support (via rig-core)
- App management: agent-deployed applications with sandboxed execution and health monitoring
Key modules
| Module | Purpose |
|---|---|
api/ | HTTP routes, middleware, request/response types |
agent/ | Agent models, service, tasks, signals, skills, workspaces, prompt loading |
app/ | Agent-deployed application lifecycle and sandboxing |
auth/ | Authentication, JWT, SSO, ephemeral tokens, principals |
call/ | Voice call management |
chat/ | Chat sessions, messages, streaming, channel adapters |
contact/ | Contact management |
credential/ | Vault providers, encrypted managed credentials, account login and refresh, grants, bindings |
inference/ | Named provider connections, model directories, protocol adapters, credential resolution, retries, and tool loop |
memory/ | Fact storage, compaction |
notification/ | User notifications |
policy/ | Cedar policy engine, schema, sandbox-policy reconciliation |
scheduler/ | Background task execution, heartbeats, signal timeout sweep |
space/ | Space context management, SSE broadcast |
storage/ | File storage, workspaces, virtual paths |
tool/ | Tool implementations, MCP integration, registry, sandbox |
Frontend (Next.js)
The frontend is a Next.js application using the App Router with static export. It provides:
- Chat interface with real-time streaming
- Agent management and configuration UI
- Workspace and space navigation
- File attachments and tool result rendering
- App management and approval flows
- Settings and vault management
- Authentication flows (login, register, SSO)
- Setup wizard for first-time configuration
Key libraries
- Next.js 16 with React 19 and TypeScript
- Tailwind CSS for styling
- Shiki for syntax highlighting
- SSE client for real-time message streaming
Database (SurrealDB)
SurrealDB runs embedded inside the engine process. It uses RocksDB as the storage backend, so there's no separate database server to manage.
Tables
| Table | Stores |
|---|---|
user | User accounts |
agent | Agent definitions and configuration |
chat | Chat sessions |
message | Chat messages |
task | Tasks (direct, delegated, scheduled) |
space | Spaces and their context |
memory | Memory scopes |
memory_entry | Individual memory facts |
credential | Locally stored credentials |
managed_credential | Encrypted managed secrets and account credential state |
vault_connection | External vault provider connections |
vault_grant | Credential access grants |
vault_access_log | Credential access audit trail |
app | Deployed applications |
tool_call | Tool call records |
keypair | JWT signing keypairs (encrypted at rest) |
api_token | Personal access tokens |
oauth_identity | SSO identity links |
notification | User notifications |
contact | Contacts |
call | Voice call records |
channel | Connected channels (Telegram, SMS, etc.) and their status |
mcp_server | Installed MCP servers, transports, and tool caches |
policy | Cedar policies (system + user) |
principal_credential_binding | Per-principal mappings from credentials to env vars |
runtime_config | Runtime configuration |
Data flow
A typical request flows like this:
- User sends a message through the frontend
- Frontend makes a POST to the engine API
- Engine loads the agent config, conversation history, and memory
- Engine sends the prompt to the configured LLM provider
- LLM response streams back. If it includes tool calls, the engine executes them in the agent's sandbox.
- Tool results feed back into the LLM for the next iteration (the tool loop)
- Final response tokens stream to the frontend via SSE
- Messages are persisted to SurrealDB
Configuration system
The engine loads configuration from a YAML file and environment variable overrides. Environment overrides take precedence over YAML. The FRONA_ prefix is followed by the component, a single underscore, and the field name (e.g., FRONA_SERVER_PORT maps to server.port). Deeper nesting uses double underscores: FRONA_CHANNEL_RETRY__MAX_RETRIES maps to channel.retry.max_retries. See Environment Variables.
The configuration service keeps the active startup configuration separate from the saved document. UI saves apply patches with revision checks and atomic file replacement. Saved changes become active after restart; provider credentials are resolved separately at use time.
Prompts are loaded from .md files in the shared config directory, not hardcoded. This makes it easy to customize agent behavior without modifying code.