Agent World turns agent session state into a spatial UI. Instead of reading raw session transcripts, the operator sees agents moving through a semantic office:
libraryfor reading and reference workterminalfor coding, editing, tool use, and browser workdeskfor planning and writingcommsfor messages and direct interactionloungefor idle and waiting states
The key design goal is to separate runtime truth from visual metaphor. The world is a projection of agent behavior, not the source of truth itself.
server.py is the integration point for the standalone app.
It does four important things for Agent World:
- serves
/with the UI HTML - mounts
/agent-world-staticto this directory - exposes
/api/agent-world/*routes - delegates almost all Agent World logic into
backend/*
The backend adapter layer lives in backend/ and translates OpenClaw data into
Agent World payloads.
registry.pyDiscovers which named agents should appear in the worldstate_mapper.pyBuilds summaries, history, schedule, stash, and world snapshotscommand_router.pySends operator commands into live sessionsworld_layout.pyReads and writes room layout and movement override filesstream.pyProduces server-sent event snapshots for live UI updatesevent_mapper.pyThin wrapper around event derivation
The frontend is a plain browser app:
index.htmlPage structurestyles.cssVisual styling and layoutapp.jsState container, PixiJS renderer, API integration, tilemap editor, and interactions
PixiJS is bundled locally in vendor/pixi.min.js.
- Browser requests
/ - HTML loads
styles.css,vendor/pixi.min.js, andapp.js load()inapp.jsinitializes the Pixi renderer and fetches/api/agent-world/staterenderWorld()draws the room and agents- The default selected agent is set to
lucca-mainif present selectAgent()fetches/api/agent-world/agents/{agent_id}- The embedded inspector, chat log, schedule, and stash are populated
connectStream()subscribes to/api/agent-world/stream?agent_id=...
backend/stream.pypolls current world state every ~1.5 seconds- The SSE payload includes:
worldeventsdetailfor the selected agent when provided
handleStreamSnapshot()inapp.jsupdates the rendered world and side panels
- User types in the chat composer
sendCommandText()posts to/api/agent-world/agents/{agent_id}/commandbackend/command_router.pylaunchesopenclaw agent --agent ... --message ...- UI reloads and stream updates eventually reflect the changed session state
- User picks an anchor from
Move Selected Agent moveSelectedAgentToAnchor()posts to/api/agent-world/agents/{agent_id}/movebackend/world_layout.pywrites the movement override back intogame_state.json- World state starts reporting the chosen
targetAnchor - Frontend pathing animates the agent toward that goal tile
Agent World is not backed by a dedicated database. It derives state from a mix of:
- OpenClaw agent config in
~/.openclaw/openclaw.json - OpenClaw session index files in
~/.openclaw/agents/.../sessions/sessions.json - Session transcript files referenced by that index
- Cron metadata in
~/.openclaw/cron/jobs.json - Cron run session indexes in
~/.openclaw/cron/runsand agent session indexes - Local world layout files in
assets/tiles/office_world/ - Browser local storage for editor drafts and per-browser UI customizations
The UI maps tools and assistant text into room anchors and visual states. It is deliberately lossy. This makes the world readable and stable even though raw transcripts are noisy.
Most payloads are already multi-agent friendly even when only Lucca is active. Adding more named agents should not require redesigning the API shapes.
The room is composed from tile manifests, map files, and sprites. Dynamic state like selection, pathing, status labels, and bubbles is overlaid at runtime.
The tilemap editor mutates browser-local draft state first, then persists layout through the API when explicitly saved.
- Add a new visible agent by extending OpenClaw config handling in
backend/registry.py - Adjust anchor heuristics in
backend/state_mapper.py - Add new panels or controls in
index.htmlandapp.js - Expand the world editor via the
editorstate insideapp.js - Add richer server fields in
derive_agent_detail()and render them in the frontend