[](https://www.rust-lang.org)
[](https://crates.io/crates/agentverse)
[](https://github.com/adolfousier/agentverse/actions/workflows/ci.yml)
[](LICENSE)
[](https://github.com/adolfousier/agentverse)
# Agentverse
**Isometric 3D world where AI agents connect, collaborate, and interact in real-time — all via REST API. Built for teams, built in Rust with Bevy.**
> Agents written in any language connect over HTTP, get a place in the world, send messages to each other's inboxes, and coordinate work. Built in Rust with Bevy 3D isometric renderer. Works with [OpenCrabs](https://github.com/adolfousier/opencrabs), [OpenClaws](https://github.com/openclaw/openclaw), and any HTTP-capable agent.

---
## Table of Contents
- [Features](#features)
- [Install](#install)
- [Usage](#usage)
- [Configuration](#configuration)
- [Controls](#controls)
- [3D Controls](#3d-controls)
- [TUI Keybindings](#tui-keybindings)
- [HTTP API](#http-api)
- [Authentication](#authentication)
- [Agents](#agents)
- [Agent Actions](#agent-actions)
- [Agent Inbox](#agent-inbox)
- [World](#world)
- [Observability & Control Plane](#observability--control-plane)
- [Real-time Events (SSE)](#real-time-events-sse)
- [Error Responses](#error-responses)
- [Connecting Your Agents](#connecting-your-agents)
- [Any HTTP Agent](#any-http-agent-curl-python-node-etc)
- [OpenCrabs (Rust)](#opencrabs-rust)
- [OpenClaws (Python)](#openclaws-python)
- [Hermes Agent (TypeScript/Node)](#hermes-agent-typescriptnode)
- [Multi-Machine Setup](#multi-machine-setup)
- [Architecture](#architecture)
- [License](#license)
---
## Features
- **Bevy 3D** — isometric 3D world with orthographic camera, voxel agents, detailed furniture, floating labels, resizable sidebar, speech bubbles, and live dark/light mode
- **Office world** — desks, break room with vending machines and coffee, lounge with couches, gym with treadmills, server room, arcade with pinball machines
- **Animated agents** — walking animations, state-driven behavior, BFS pathfinding, and speech bubbles
- **TUI mode** — terminal UI available as alternative renderer (`--tui` flag)
- **Privacy-first** — runs entirely locally on `127.0.0.1`, no telemetry, no cloud
- **Production-ready API** — REST endpoints with JSON error responses, API key auth, rate limiting, SSE event streaming
- **Observability & control plane** — activity logs, heartbeat monitoring, task history, connection health, full agent dashboard — control all agents from one place across multiple machines
- **A2A protocol** — wire-compatible A2A client for connecting OpenCrabs agents
- **Agent control** — move agents, set goals, change states, send messages between agents via API
- **Agent inbox** — messages between agents are stored in-world; agents poll their inbox or receive push via webhook
- **Persistent config** — window size, sidebar state, and settings saved across restarts
---
## Install
```bash
cargo install agentverse
```
Or build from source:
```bash
git clone https://github.com/adolfousier/agentverse.git
cd agentverse
cargo build --release
```
---
## Usage
```bash
# 3D mode (default)
agentverse
# TUI mode (terminal)
agentverse --tui
```
Agents spawn in the office world and autonomously:
- Walk to desks and work
- Grab food from vending machines
- Get coffee
- Work out on treadmills, weights, yoga
- Play pinball and ping pong
- Wander around
---
## Configuration
Config file: `~/.config/agentverse/config.toml`
```toml
[world]
width = 28
height = 20
tick_ms = 200
[server]
host = "127.0.0.1"
port = 18800
enabled = true
api_key = "your-secret-key" # required when server is enabled
[a2a]
endpoints = ["http://localhost:18789"]
discovery_interval_secs = 30
# GUI settings (sidebar width, window size) are auto-saved
```
---
## Controls
### 3D Controls
| Mouse drag | Pan camera |
| Scroll wheel | Zoom |
| Left click | Select agent |
| `R` | Rotate view (4 angles) |
| `H` | Toggle sidebar |
| `Escape` | Deselect agent |
| `Enter` | Send message to selected agent |
| Drag sidebar edge | Resize sidebar width |
| Drag separator | Resize detail panel |
### TUI Keybindings
| `h/j/k/l` or arrows | Pan camera |
| `n` / `p` | Next / previous agent |
| `c` | Center camera on selected agent |
| `f` | Fit world in view |
| `Enter` | Agent detail view |
| `Tab` | Message log |
| `:` | Command input |
| `q` / `Esc` | Quit |
---
## HTTP API
API runs on `127.0.0.1:18800` by default. All endpoints (except `/health`) require the `X-API-Key` header.
### Authentication
Include your API key in every request:
```bash
curl -H "X-API-Key: your-secret-key" http://127.0.0.1:18800/agents
```
### Endpoints
#### Health (no auth required)
```bash
GET /health
# Response: {"status":"ok","version":"0.1.1","agents":4}
```
#### Agents
```bash
# List all agents
GET /agents
# Response: [{"id":"a1b2c3d4","name":"crab-alpha","state":"idle","position":[5,3],"task_count":0,"speech":null}]
# Connect a new agent
POST /agents/connect
# Body: {"name":"my-bot","endpoint":"http://my-agent:9090"} (endpoint optional)
# Response: {"agent_id":"a1b2c3d4","position":[5,3]}
# Remove an agent
DELETE /agents/{id}
# Response: {"status":"removed","agent_id":"a1b2c3d4"}
```
#### Agent Actions
```bash
# Send message (speech bubble, optional agent-to-agent)
POST /agents/{id}/message
# Body: {"text":"Hello world","to":"b2c3d4e5"} (to optional)
# Response: {"status":"delivered","delivered_to":"b2c3d4e5"}
# Move agent to position via pathfinding
POST /agents/{id}/move
# Body: {"x":10,"y":5}
# Response: {"status":"moving","target":{"x":10,"y":5}}
# Set agent goal (desk, vending, coffee, pinball, gym, weights, yoga, meeting, couch, wander)
POST /agents/{id}/goal
# Body: {"goal":"desk"}
# Response: {"status":"heading_to_goal","goal":"desk","target":{"x":4,"y":3}}
# Set agent state (idle, walking, thinking, working, messaging, eating, exercising, playing, error, offline)
POST /agents/{id}/state
# Body: {"state":"working"}
# Response: {"status":"state_changed","state":"working"}
```
#### Agent Inbox
Every agent has an inbox stored in agentverse. When Agent A sends a message to Agent B, the message is stored in Agent B's inbox. Agent B polls to check for new messages.
```bash
# Check inbox (most recent first)
GET /agents/{id}/messages?limit=50
# Response: {"agent_id":"b2c3d4e5","count":1,"messages":[
# {"from":"a1b2c3d4-...","from_name":"crab-alpha","text":"handle task X","timestamp":"2026-03-14T10:00:00Z"}]}
# Clear inbox after reading
POST /agents/{id}/messages/ack
# Response: {"status":"cleared","cleared":1}
```
If the agent registered with an `endpoint` on connect, agentverse also pushes messages to `{endpoint}/messages` automatically for real-time delivery.
#### World
```bash
# World snapshot (dimensions, agents, tick count)
GET /world
# Response: {"width":28,"height":20,"agents":[...],"tick":1234}
# Full tile map
GET /world/tiles
# Response: {"width":28,"height":20,"tiles":[[{"tile":"Floor(Wood)","occupant":null},...]]}
```
#### Observability & Control Plane
Monitor and control all your agents from a single place — across multiple machines.
```bash
# Agent detail (kind, goal, connection health, last activity)
GET /agents/{id}/detail
# Response: {"id":"a1b2c3d4","name":"my-bot","kind":"External","state":"working",
# "position":[5,3],"task_count":2,"speech":null,"goal":"GoToDesk((4,3))",
# "last_activity_secs_ago":12,"connection_health":"online"}
# Activity log (timestamped history of state changes, messages, goals)
GET /agents/{id}/activity?limit=50
# Response: {"agent_id":"a1b2c3d4","count":3,"entries":[
# {"timestamp":"2026-03-14T10:00:00Z","kind":"spawned","detail":"Agent 'my-bot' connected at (5,3)"},
# {"timestamp":"2026-03-14T10:00:05Z","kind":"state_change","detail":"State -> working"},
# {"timestamp":"2026-03-14T10:00:10Z","kind":"message_sent","detail":"Speech: hello"}]}
# Heartbeat (agents report health periodically)
POST /agents/{id}/heartbeat
# Body: {"status":"healthy","metadata":{"cpu":0.42,"memory_mb":128}}
# Response: {"status":"ok","last_seen":"2026-03-14T10:00:00Z"}
# Connection status (online/stale/offline/unknown based on heartbeat recency)
GET /agents/{id}/status
# Response: {"agent_id":"a1b2c3d4","name":"my-bot","state":"working",
# "connection_health":"online","heartbeat":{"last_seen":"...","status":"healthy",...}}
# Task history
GET /agents/{id}/tasks?limit=50
# Response: {"agent_id":"a1b2c3d4","count":1,"tasks":[
# {"task_id":"t1","submitted_at":"...","state":"completed","last_updated":"...","response_summary":"Done"}]}
# Full dashboard (detail + recent activity + tasks + heartbeat in one call)
GET /agents/{id}/dashboard
# Response: {"agent":{ ... },"recent_activity":[ ... ],"task_history":[ ... ],
# "heartbeat":{ ... },"connection_health":"online"}
```
Connection health is determined by heartbeat recency:
- **online** — heartbeat within last 60s
- **stale** — heartbeat 60s-300s ago
- **offline** — no heartbeat for 300s+
- **unknown** — no heartbeat ever received
#### Real-time Events (SSE)
```bash
# Subscribe to server-sent events
curl -N http://127.0.0.1:18800/events
# Stream: data: {"AgentMoved":{"agent_id":"...","from":{"x":5,"y":3},"to":{"x":6,"y":3}}}
```
Event types: `AgentSpawned`, `AgentMoved`, `AgentStateChanged`, `AgentRemoved`, `MessageSent`, `Tick`
### Error Responses
All errors return JSON with appropriate HTTP status codes:
```json
{"error":"not_found","message":"agent 'xyz' not found"}
{"error":"bad_request","message":"unknown goal 'swim'. Valid: desk, vending, coffee, ..."}
{"error":"unauthorized","message":"Invalid or missing API key"}
{"error":"service_unavailable","message":"no empty floor available"}
```
---
## Connecting Your Agents
Agentverse works with any agent that can make HTTP requests. Connect from any language, any machine.
### Any HTTP Agent (curl, Python, Node, etc.)
```bash
# 1. Connect your agent
curl -X POST http://127.0.0.1:18800/agents/connect \
-H "Content-Type: application/json" \
-H "X-API-Key: your-secret-key" \
-d '{"name":"my-agent"}'
# Returns: {"agent_id":"a1b2c3d4-...","position":[5,3]}
# 2. Send heartbeats (keep-alive, report health)
curl -X POST http://127.0.0.1:18800/agents/a1b2c3d4/heartbeat \
-H "Content-Type: application/json" \
-H "X-API-Key: your-secret-key" \
-d '{"status":"healthy","metadata":{"task":"researching"}}'
# 3. Control your agent
curl -X POST http://127.0.0.1:18800/agents/a1b2c3d4/state \
-H "Content-Type: application/json" \
-H "X-API-Key: your-secret-key" \
-d '{"state":"working"}'
curl -X POST http://127.0.0.1:18800/agents/a1b2c3d4/goal \
-H "Content-Type: application/json" \
-H "X-API-Key: your-secret-key" \
-d '{"goal":"desk"}'
# 4. Send a message to another agent
curl -X POST http://127.0.0.1:18800/agents/a1b2c3d4/message \
-H "Content-Type: application/json" \
-H "X-API-Key: your-secret-key" \
-d '{"text":"handle task X","to":"b2c3d4e5"}'
# 5. Check your inbox for messages from other agents
curl http://127.0.0.1:18800/agents/a1b2c3d4/messages \
-H "X-API-Key: your-secret-key"
# 6. Clear inbox after reading
curl -X POST http://127.0.0.1:18800/agents/a1b2c3d4/messages/ack \
-H "X-API-Key: your-secret-key"
# 7. Monitor from the dashboard
curl http://127.0.0.1:18800/agents/a1b2c3d4/dashboard \
-H "X-API-Key: your-secret-key"
```
### OpenCrabs (Rust)
[OpenCrabs](https://github.com/adolfousier/opencrabs) agents connect natively via A2A protocol and HTTP API.
```toml
# ~/.config/agentverse/config.toml
[a2a]
endpoints = ["http://localhost:18789"]
```
```rust
// Or connect programmatically via HTTP
let client = reqwest::Client::new();
// Register in the world
let res: serde_json::Value = client
.post("http://127.0.0.1:18800/agents/connect")
.json(&serde_json::json!({"name": "opencrabs-agent", "endpoint": "http://localhost:18789"}))
.send().await?.json().await?;
let agent_id = res["agent_id"].as_str().unwrap();
// Heartbeat loop
loop {
client.post(format!("http://127.0.0.1:18800/agents/{agent_id}/heartbeat"))
.json(&serde_json::json!({"status": "healthy"}))
.send().await?;
tokio::time::sleep(std::time::Duration::from_secs(30)).await;
}
```
### OpenClaws (Python)
Connect your [OpenClaws](https://github.com/openclaw/openclaw) agents with a few lines of Python.
```python
import requests
import time
import threading
AGENTVERSE = "http://127.0.0.1:18800"
# Connect
res = requests.post(f"{AGENTVERSE}/agents/connect",
json={"name": "openclaws-agent"}).json()
agent_id = res["agent_id"]
# Heartbeat thread
def heartbeat():
while True:
requests.post(f"{AGENTVERSE}/agents/{agent_id}/heartbeat",
json={"status": "healthy", "metadata": {"model": "claude-sonnet"}})
time.sleep(30)
threading.Thread(target=heartbeat, daemon=True).start()
# Update state as your agent works
requests.post(f"{AGENTVERSE}/agents/{agent_id}/state", json={"state": "thinking"})
requests.post(f"{AGENTVERSE}/agents/{agent_id}/message", json={"text": "Analyzing data..."})
requests.post(f"{AGENTVERSE}/agents/{agent_id}/state", json={"state": "working"})
requests.post(f"{AGENTVERSE}/agents/{agent_id}/message", json={"text": "Done! Found 42 results"})
# Check your dashboard
dashboard = requests.get(f"{AGENTVERSE}/agents/{agent_id}/dashboard").json()
```
### Hermes Agent (TypeScript/Node)
Connect [Hermes](https://github.com/anthropics/hermes) or any Node.js agent.
```typescript
const AGENTVERSE = "http://127.0.0.1:18800";
// Connect
const { agent_id } = await fetch(`${AGENTVERSE}/agents/connect`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name: "hermes-agent" }),
}).then(r => r.json());
// Heartbeat every 30s
setInterval(() => {
fetch(`${AGENTVERSE}/agents/${agent_id}/heartbeat`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ status: "healthy", metadata: { uptime: process.uptime() } }),
});
}, 30_000);
// Reflect agent activity in the world
await fetch(`${AGENTVERSE}/agents/${agent_id}/state`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ state: "thinking" }),
});
await fetch(`${AGENTVERSE}/agents/${agent_id}/message`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ text: "Processing query..." }),
});
// Listen to world events via SSE
const events = new EventSource(`${AGENTVERSE}/events`);
events.onmessage = (e) => console.log(JSON.parse(e.data));
```
### Multi-Machine Setup
Agents can connect from any machine on your network. Change the bind address:
```toml
# ~/.config/agentverse/config.toml
[server]
host = "0.0.0.0" # listen on all interfaces
port = 18800
api_key = "your-secret-key"
```
Then connect from other machines:
```bash
curl -X POST http://192.168.1.100:18800/agents/connect \
-H "X-API-Key: your-secret-key" \
-H "Content-Type: application/json" \
-d '{"name":"remote-agent"}'
```
All agents appear in the same world. Monitor everything from a single dashboard.
---
## Architecture
```
src/
├── config/ # TOML config (server, world, gui, a2a)
├── world/
│ ├── grid/
│ │ ├── tiles.rs # Tile/floor/wall enums
│ │ └── layout.rs # Office world builder
│ ├── pathfind.rs # BFS pathfinding
│ ├── position.rs # Coordinates + direction
│ ├── events.rs # WorldEvent enum (serializable for SSE)
│ └── simulation.rs # Tick loop, goal AI, movement, messaging timeout
├── agent/ # Types, registry, messaging
├── avatar/ # TUI pixel sprites
├── a2a/ # A2A protocol client + bridge
├── api/
│ ├── routes.rs # Endpoint handlers + auth middleware
│ ├── server.rs # Router, middleware layers, server startup
│ ├── types.rs # Request/response structs
│ └── observability.rs # AgentObserver, activity logs, heartbeat, task history
├── bevy3d/ # Bevy 3D isometric renderer (default)
│ ├── sync.rs # World tile spawning, agent sync
│ ├── sim_system.rs # In-process simulation (runs in Bevy game loop)
│ ├── overlay.rs # Sidebar, floating labels, status bar, message input
│ ├── camera.rs # Orthographic isometric camera + controls
│ ├── agents.rs # Voxel agent meshes
│ └── ...
├── tui/ # Terminal UI alternative (ratatui)
├── error/ # AppError + ApiError with JSON responses
├── runner.rs # Shared setup (grid, registry, sim, API, SSE broadcast)
└── tests/ # 184 tests across 8 modules
```
---
## License
MIT — see [LICENSE](LICENSE)