agentverse 0.1.3

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.
Documentation

Rust Crates.io CI License: MIT GitHub Stars

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, OpenClaws, and any HTTP-capable agent.

Agentverse Demo

Author: Adolfo Usier | Website: agentvrs.com


Table of Contents


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

cargo install agentverse

Or build from source:

git clone https://github.com/adolfousier/agentverse.git
cd agentverse
cargo build --release

Usage

# 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

[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

Input Action
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

Key Action
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:

curl -H "X-API-Key: your-secret-key" http://127.0.0.1:18800/agents

Endpoints

Health (no auth required)

GET /health
# Response: {"status":"ok","version":"0.1.1","agents":4}

Agents

# 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

# 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.

# 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

# 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.

# 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)

# 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:

{"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.)

# 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 agents connect natively via A2A protocol and HTTP API.

# ~/.config/agentverse/config.toml
[a2a]
endpoints = ["http://localhost:18789"]
// 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 agents with a few lines of 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 or any Node.js agent.

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:

# ~/.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:

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