Agent Platform
A Nix-based platform for running AI agents as MicroVMs.
Architecture
High-Level Architecture
graph TB
subgraph Browser["🌐 Web Browser"]
FE["Web Frontend<br/>(Vue.js + Element Plus)"]
end
subgraph Host["🖥️ Host Machine (NixOS)"]
subgraph Nginx["nginx (reverse proxy)"]
NGINX[":80 / :443"]
end
subgraph AgentWeb["agent-web (Axum/Rust)"]
AW[":3000<br/>OIDC Auth + LLM Proxy<br/>Agent CRUD + SSE Streams<br/>Secret Management"]
end
subgraph MicrovmMgr["microvm-manager (Axum/Rust)"]
MM[":9090<br/>VM Lifecycle API"]
end
POSTGRES["PostgreSQL<br/>agents, secrets,<br/>skill_packs, api_keys"]
subgraph MicrovmHost["MicroVM Host Infrastructure"]
BRIDGE["br-microvm<br/>bridge interface"]
NAT["NAT / Firewall<br/>iptables + ip6tables"]
CH["cloud-hypervisor<br/>hypervisor binary"]
end
subgraph MicroVMs["📦 Agent MicroVMs"]
MV1["microvm-1<br/>pi-agent (Rust)"]
MV2["microvm-N<br/>pi-agent (Rust)"]
end
LLM["External LLM API<br/>(OpenAI / Ollama / etc.)"]
AUTH["OIDC Provider<br/>(Authentik)"]
end
FE -->|proxy /api| NGINX
NGINX -->|proxy /| AW
NGINX -->|proxy /| FE
AW -->|HTTP REST| MM
AW -->|SQL queries| POSTGRES
AW -->|OpenAI-compatible API| LLM
AW -->|OIDC introspect| AUTH
AW -->|WebSocket| MV1
AW -->|WebSocket| MV2
MM -->|spawn| CH
CH -->|virtio| MV1
CH -->|virtio| MV2
MV1 -.->|eth0 over veth| BRIDGE
MV2 -.->|eth0 over veth| BRIDGE
BRIDGE -->|NAT masquerade| NAT
style Browser fill:#e8f4fd,stroke:#2196F3
style Host fill:#f5f5f5,stroke:#9e9e9e
style MicroVMs fill:#fff3e0,stroke:#FF9800
Agent Web Internal
classDiagram
class AppState {
+PgPool db
+AuthClient auth_client
+LlmProxy llm_proxy
+AdminService admin_service
+AgentService agent_service
+WsManager ws_manager
+MicrovmManager microvm_manager
+ModelCache model_cache
+String auth_mode
+Option admin_api_key
}
class AuthClient {
+provider_url
+client_id
+client_secret
+http_client
+jwks_cache
+introspect_token()
+validate_id_token()
+exchange_code()
+get_jwks()
+verify_jws_signature()
}
class LlmProxy {
+http_client
+api_url
+api_key
+default_model
+chat()
+list_models()
+forward_for_agent()
+get_usage()
}
class AgentService {
+pool
+microvm_manager
+create()
+list()
+get()
+delete()
+update()
+assign_skill_pack()
+update_status()
+generate_api_key()
+store_api_key()
+validate_api_key()
+start_agent_vm()
}
class AdminService {
+pool
+create_skill_pack()
+list_skill_packs()
+get_skill_pack()
+update_skill_pack()
+delete_skill_pack()
+create_secret()
+list_secrets()
+get_secret()
+update_secret()
+delete_secret()
+configure_agent()
+encrypt_value_with_versioning()
+decrypt_value_with_versioning()
}
class WsManager {
+agent_senders
+agent_receivers
+key_registry
+connected
+agent_service
+register()
+unregister()
+send()
+receive()
+is_connected()
+notify_response()
}
class ModelCache {
+cache
+ttl_seconds
+get()
+list()
}
class AuthExtractor
class require_auth
class require_admin
class OidcProvider {
+issuer_url
+jwks_url
+authorize_url
+token_url
+userinfo_url
}
class LlmApi {
+base_url
+api_key
+models()
+chat_completion()
}
class PostgreSQL {
+connection_pool
+query()
+execute()
+transaction()
}
class BroadcastChannels {
+channels
+send()
+receive()
}
AppState --> AuthClient
AppState --> LlmProxy
AppState --> AgentService
AppState --> AdminService
AppState --> WsManager
AppState --> ModelCache
AppState --> MicrovmManager
AuthClient --> OidcProvider
LlmProxy --> LlmApi
AgentService --> MicrovmManager
AgentService --> PostgreSQL
AdminService --> PostgreSQL
WsManager --> BroadcastChannels
note for AppState "Central application state, shared via Arc"
note for AuthClient "Handles OIDC flow: PKCE, introspect, JWKS, JWS verify"
note for AdminService "AES-256-GCM encryption for secrets at rest"
MicroVM Manager Internal
classDiagram
class VmManager {
+vms
+child_processes
+config_loader
+network_manager
+start()
+stop()
+destroy()
+list()
+get_logs()
+configure()
}
class NetworkManager {
+assigned_ips
+vms_dir
+setup_network()
+teardown_network()
+create_bridge()
+assign_nic_to_bridge()
+setup_ipv6()
+setup_iptables_nat()
}
class ConfigLoader {
+vms_dir
+api_key
+allowed_origin
+default_image
+generate_vm_config()
+save_vm_config()
+load_vm_config()
}
class AppState {
+vm_manager
+config_loader
+api_key
+allowed_origin
}
class VmState {
<<enumeration>>
Stopped
Starting
Running
Stopping
}
class VmInfo {
+name
+state
+ip_address
+pid
+config_path
+created_at
}
class VmConfig {
+name
+image
+memory_mb
+cpu_count
+network
+agent_config
+api_key
+config_hash
}
class HashMapVmEntry {
+VmEntry
+HashMap<VmName, VmEntry>
}
VmManager --> NetworkManager
VmManager --> ConfigLoader
VmManager --> HashMapVmEntry
AppState --> VmManager
VmManager --> VmState
VmManager --> VmInfo
VmManager --> VmConfig
ConfigLoader --> VmConfig
NetworkManager --> VmConfig
note for VmManager "Manages cloud-hypervisor processes"
note for NetworkManager "Creates bridges, veth pairs, IPv6, NAT"
note for ConfigLoader "Generates VM config JSON + API keys"
Frontend Architecture
graph TB
subgraph Frontend["🖥️ Web Frontend (Vue 3 + TypeScript)"]
subgraph AppLayer["App Layer"]
APP["App.vue"]
ROUTER["router/index.ts<br/>Vue Router + guards"]
end
subgraph ViewLayer["View Layer"]
LOGIN["LoginView.vue"]
CALLBACK["AuthCallbackView.vue"]
AGENTS_LIST["AgentsView.vue"]
AGENT_CREATE["AgentCreateView.vue"]
AGENT_CHAT["AgentChatView.vue"]
ADMIN["AdminView.vue"]
end
subgraph ComponentLayer["Component Layer"]
SECRET_MGR["SecretManager.vue"]
SKILL_MGR["SkillPackManager.vue"]
end
subgraph StoreLayer["State Layer (Pinia)"]
AUTH_STORE["auth store<br/>user, roles, token"]
AGENT_STORE["agent store<br/>agents, currentAgent, models"]
ADMIN_STORE["admin store<br/>secrets, skillPacks"]
end
subgraph ApiLayer["API Layer"]
CLIENT["api/client.ts<br/>axios instance + interceptors"]
AGENTS_API["api/agents.ts<br/>agent CRUD + ws + SSE"]
MODELS_API["api/models.ts<br/>LLM model list"]
ADMIN_API["api/admin.ts<br/>secrets + skill packs"]
end
subgraph TypeLayer["Types"]
TYPES["types/index.ts<br/>Agent, Secret, SkillPack"]
end
end
ROUTER --> LOGIN
ROUTER --> CALLBACK
ROUTER --> AGENTS_LIST
ROUTER --> AGENT_CREATE
ROUTER --> AGENT_CHAT
ROUTER --> ADMIN
AGENTS_LIST --> AGENT_STORE
AGENTS_LIST --> SECRET_MGR
AGENTS_LIST --> SKILL_MGR
AGENT_CHAT --> AGENT_STORE
AGENT_CHAT --> AGENTS_API
ADMIN --> ADMIN_STORE
ADMIN --> SECRET_MGR
ADMIN --> SKILL_MGR
AGENT_STORE --> AGENTS_API
ADMIN_STORE --> ADMIN_API
AUTH_STORE --> CLIENT
CLIENT --> TYPES
style Frontend fill:#e3f2fd,stroke:#1976D2
style ViewLayer fill:#fff,stroke:#ccc
Multihost Architecture
graph TB
subgraph WebServiceHost["🖥️ Web-Service Host"]
subgraph Nginx["nginx"]
NGINX[":80 / :443<br/>TLS termination"]
end
subgraph AgentWebService["agent-web (Axum)"]
AW[":3000<br/>Web API + OIDC + LLM Proxy"]
end
subgraph MicrovmMgrSvc["microvm-manager (Axum)"]
MM1[":9090<br/>VM lifecycle API"]
end
POSTGRES["PostgreSQL"]
LLM["External LLM API"]
AUTH["OIDC Provider<br/>(Authentik)"]
end
subgraph MicrovmHost["📦 Dedicated MicroVM Host"]
subgraph MicrovmMgrHost["microvm-manager (Axum)"]
MM2[":9090<br/>VM lifecycle API"]
end
subgraph HostInfra["Host Infrastructure"]
CH["cloud-hypervisor"]
BRIDGE["br-microvm<br/>bridge"]
NAT["NAT / Firewall"]
end
subgraph GuestVMs["Agent MicroVMs"]
MV1["microvm-1<br/>pi-agent"]
MV2["microvm-2<br/>pi-agent"]
MVN["microvm-N<br/>pi-agent"]
end
end
NGINX -->|proxy /api/| AW
NGINX -->|proxy /| FE["Web Frontend"]
AW -->|HTTP REST| MM1
AW -->|SQL| POSTGRES
AW -->|API calls| LLM
AW -->|OIDC| AUTH
MM1 -->|HTTP| MM2
MM2 -->|spawn| CH
CH -->|virtio| MV1
CH -->|virtio| MV2
CH -->|virtio| MVN
MV1 -.->|eth0/veth| BRIDGE
MV2 -.->|eth0/veth| BRIDGE
MVN -.->|eth0/veth| BRIDGE
BRIDGE --> NAT
style WebServiceHost fill:#e3f2fd,stroke:#1976D2
style MicrovmHost fill:#fff3e0,stroke:#FF9800
style GuestVMs fill:#f3e5f5,stroke:#9C27B0
| Component |
Description |
flake.packages.x86_64-linux.agent-web |
Axum web service with OIDC auth, PostgreSQL, agent/chat management. |
flake.packages.x86_64-linux.microvm-manager |
Axum API for starting/stopping/configuring MicroVMs. |
flake.packages.x86_64-linux.agent-microvm-image |
NixOS closure for MicroVMs (cloud-hypervisor + microvm.nix). |
flake.packages.x86_64-linux.web-frontend |
Vue.js web application. |
nixosModules.web-service |
NixOS module that deploys the agent-web systemd service. |
nixosModules.microvm-host |
NixOS module that configures the host running MicroVMs. |
nixosModules.microvm-image |
NixOS module that configures the MicroVM guest image. |
nixosConfigurations.example-single-host |
Example NixOS configuration for single-host deployment. |
Getting started
Show available packages
nix flake show .#flake.packages.x86_64-linux
Build everything
nix build .#flake.packages.x86_64-linux.agent-web .#flake.packages.x86_64-linux.microvm-manager .#flake.packages.x86_64-linux.agent-microvm-image
Enter the development shell
nix develop
Use the example single-host configuration
# Evaluate the configuration
nix eval .#nixosConfigurations.example-single-host.config.system.build.toplevel
# Test the configuration
nixos-rebuild test --flake .#example-single-host
# Switch to the configuration
nixos-rebuild switch --flake .#example-single-host
See examples/single-host/README.md for details.
Module options
services.agent-web
| Option |
Type |
Default |
Description |
enable |
bool |
false |
Enable the agent-web systemd service. |
host |
str |
"0.0.0.0" |
Bind address. |
port |
port |
8080 |
Bind port. |
authentik.clientId |
str |
– |
OAuth2 client ID. |
authentik.clientSecret |
str |
– |
OAuth2 client secret. |
authentik.issuerUrl |
str |
– |
OpenID Connect issuer URL. |
llmApiUrl |
str |
– |
Base URL of the LLM API endpoint. |
maxAgentsPerUser |
int |
3 |
Maximum number of agents per user. |
microvmManagerUrl |
str |
– |
Base URL of the microvm-manager service. |
services.microvm-manager
| Option |
Type |
Default |
Description |
enable |
bool |
false |
Enable the microvm-manager systemd service. |
host |
str |
"0.0.0.0" |
Bind address. |
port |
port |
9090 |
Bind port. |
networking.bridge (custom)
| Option |
Type |
Default |
Description |
enable |
bool |
false |
Create a bridge for MicroVMs. |
name |
str |
"br-microvm" |
Bridge interface name. |
ipv6Addresses |
list of str |
[] |
IPv6 addresses for the bridge. |
natIpv4 |
bool |
true |
Enable NAT masquerade for IPv4. |
microvm-host
| Option |
Type |
Default |
Description |
hypervisor |
str |
"cloud-hypervisor" |
Hypervisor binary. |
defaultVcpu |
int |
2 |
Default vCPU count per MicroVM. |
defaultMem |
int |
1024 |
Default memory (MiB) per MicroVM. |
sharesHostStore |
bool |
false |
Share /nix/store via virtiofs. |
image |
path |
"" |
Path to the MicroVM disk image. |
autostart |
bool |
false |
Autostart managed MicroVMs on boot. |
microvm-image
| Option |
Type |
Default |
Description |
sharesHostStore |
bool |
false |
Share host /nix/store via virtiofs. |
Directory layout
.
├── flake.nix # Flake entry point
├── examples/
│ └── single-host/ # Example single-host deployment
│ ├── configuration.nix
│ └── README.md
├── packages/
│ ├── agent-web/ # Web service source (Rust/Axum)
│ ├── microvm-manager/ # Manager API source (Rust/Axum)
│ └── web-frontend/ # Vue.js web application
├── nixosModules/
│ ├── web-service.nix # Module: deploy agent-web
│ ├── microvm-host.nix # Module: configure MicroVM host
│ └── microvm-image.nix # Module: configure MicroVM guest
└── IMPLEMENTATION.md # Detailed implementation summary
Security
- Root login uses key-only authentication (
prohibit-password).
users.mutableUsers = false prevents runtime user creation.
- Both services run as dedicated system users with sandboxed systemd units.
- ACME + nginx terminates TLS before reaching the services.
State version
Both NixOS configurations use system.stateVersion = "24.05". Update this
when rolling to a newer NixOS release.