Nilo is an open backend platform for autonomous social robots with voice, vision, memory, personality, and embodied behavior.
Nilo Server is the brain that a small social robot talks to. Today it is a production-grade voice session server: a robot (or any compatible device) opens a WebSocket, streams Opus audio, and the server runs voice activity detection, speech recognition, an LLM with tool calling, and text-to-speech, streaming audio back. Tools reach the device through MCP, so the robot's own hardware capabilities are exposed to the agent as callable tools.
Around that core, Nilo is growing a robot domain layer — world state, behaviour, personality, an action executor and a safety policy — so the same server can drive an expressive robot rather than a smart speaker. The direction is described in docs/robot-architecture.md.
Implemented (in main/nilo-server/, verified against the code):
-
WebSocket device sessions with a JSON control channel and binary Opus audio (
core/connection.py) -
A Nilo-owned device protocol (
robot/protocol/): WebSocket on/nilo/v1/, OTA on/nilo/ota/; routes come from a registry, not from hard-coded paths, and unknown paths are rejected -
HTTP OTA bootstrap that hands devices their WebSocket URL, auth token and firmware updates from
data/bin/ -
Audio pipeline: Silero VAD, streaming and batch ASR, sentence-chunked streaming TTS, barge-in (
abort) -
Provider abstraction with 17 ASR, 21 TTS, 17 LLM, 3 vision-LLM, 4 memory and 3 intent configurations (docs/providers.md)
-
Unified tool system: server plugins, device IoT descriptors, device MCP tools, remote MCP endpoints, server-side MCP servers (docs/mcp.md)
-
Vision:
/mcp/vision/explainruns a vision-LLM over a device camera frame -
Configuration layering (
config.yaml→ user file →NILO_*environment), HMAC device tokens, structured logging -
Robot domain layer (
main/nilo-server/robot/): typed models for identity, capabilities, connection, telemetry and world state; an async registry of connected robots that survives reconnects; a state-store abstraction; a bounded in-process event bus; and capability discovery over the device MCP tool channel (docs/robot-domain.md) -
Test suite, lint (Ruff), strict typing for
robot/(mypy), Docker image and Compose file -
Robot simulator (
main/nilo-server/robot/simulator/): a complete simulated robot that connects over the real WebSocket route, publishes 16 hardware capabilities as device MCP tools, moves over time, senses a configurable room, renders camera frames, reports telemetry as MCP notifications, injects failures and plays scenarios (docs/robot-simulator.md) -
Device telemetry ingestion (
main/nilo-server/robot/telemetry.py):notifications/*frames become world state and typed events instead of a log line -
Robot action layer (
main/nilo-server/robot/actions/): ten semantic actions with an explicit lifecycle, a priority queue, resource claims overDRIVE / HEAD / LIFT / DISPLAY / AUDIO / CAMERAso two actions cannot command the same hardware, andawait robot.move(...)on top (docs/robot-actions.md) -
Robot world model and behaviour engine (
main/nilo-server/robot/state/world.py,main/nilo-server/robot/behavior/): tracked entities with a decay schedule, and deterministic utility-scored autonomy — sixteen behaviours, four autonomy modes, seeded randomness, andpython -m robot.behavior explainto ask why it chose what it chose. No LLM is involved in deciding what the robot does (docs/behavior-system.md) -
Robot personality and expressive animation (
main/nilo-server/robot/personality/,main/nilo-server/robot/animation/): six stable traits and seven internal control variables that decay towards trait-derived baselines, plus animations defined as YAML data — adding one is adding a file (docs/robot-personality.md, docs/robot-animation.md) -
Robot vision (
main/nilo-server/robot/vision/): a snapshot pipeline — capture, decode, detect, track, world model, events — behind five replaceable provider protocols, with normalized 0.0-1.0 coordinates, face identity that keeps embeddings in one place, frame retention that defaults to keeping nothing, and latency metrics. OpenCV and YOLO are optional and lazily imported; the defaults need nothing installed (docs/robot-vision.md) -
Robot memory (
main/nilo-server/robot/memory/): four stores — working, episodic, semantic and person — over SQLite with migrations behind an interface Postgres can implement, optional embeddings (the robot works without them), token-budgeted retrieval that explains every item it returns, consolidation that tracks provenance and never lets a model overwrite a fact it is less sure about, and real delete operations (docs/robot-memory.md) -
Robot management API and development dashboard (
main/nilo-server/robot/api/): its own aiohttp app, its own port, its own token. Inspect state, world, behaviour scores, memory, tools and the conversation; drive the robot; stream everything as server-sent events; and a single-file dashboard with a simulator panel that injects people, obstacles, cliffs, touch and low battery through the same doors real perception uses. Control endpoints are gated three ways — a token, a loopback rule and a rate limit — and every one of them submits a typed action to the same executor and the same safety policy as a behaviour's own command (docs/robot-api.md) -
Robot safety policy (
main/nilo-server/robot/safety/): deterministic admission against configurable limits and live sensor state, typed rejections (never silent clamping), a sticky emergency-stop latch, and a supervisory watchdog on its own thread. A policy filter, not a guarantee — firmware owns every guarantee (docs/safety-model.md) -
Robot agent (
main/nilo-server/robot/agent/): the LLM seam. Fourteen semantic tools with strict argument validation and live safety bounds in their schemas, four permission classes behind a configurable policy, a runtime context with no telemetry history in it, interruptible conversation, one arbitration path for proactive speech, and a fallback that keeps the robot working when the model is not there. It wraps the existing provider architecture rather than adding a second one, and every test runs against a scripted model (docs/robot-agent.md) -
Robot voice loop (
main/nilo-server/robot/voice/): microphone to speaker with a robot in the middle. Five audio states with checked transitions and an event per change, barge-in that keeps the conversation, one arbitrated mouth with four speech priorities, and a face that follows the conversation without twitching on every transition. The inherited VAD, ASR, TTS and Opus pipeline is reused unchanged; the seam is two calls, both of which never raise (docs/robot-voice.md) -
End-to-end integration and observability: ten scenarios that run a real server, a real socket and a real simulated robot with local fakes for ASR, TTS, LLM and vision — startup, conversation, a voice-commanded move, an autonomous greeting, boredom, a cliff, a low battery, a barge-in, a dead model and a reconnect — plus metrics and one correlation id from an utterance to the device's answer (docs/observability.md, docs/testing.md)
Planned: the physical robot firmware. See docs/robot-roadmap.md. What actually runs today, with the test that proves each line: PROJECT_STATUS.md.
robot / device ──WebSocket + Opus──▶ nilo-server
├─ inherited infrastructure (core/)
│ session handler · VAD → ASR → LLM → TTS · tool system · MCP · OTA · vision
└─ Nilo robotics layers (robot/)
protocol registry ✓ · robot registry ✓ · state ✓ · events ✓ · capabilities ✓
telemetry ✓ · simulator ✓ · actions · safety · behaviour · world map · memory
The inherited infrastructure is a stable, well-tested voice pipeline; Nilo adds robotics on top of it through a few explicit seams instead of rewriting it. Details: docs/architecture.md (what runs today) and docs/robot-architecture.md (where it is going).
Requirements: Python 3.12, ffmpeg on the PATH, an LLM API key (any OpenAI-compatible endpoint).
On Linux install libopus0; macOS and Windows use the bundled Opus libraries in libs/.
git clone https://github.com/AA-Box/Nilo.git nilo
cd nilo/main/nilo-server
python3.12 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txtDownload the local speech-recognition model (FunASR SenseVoiceSmall, ~900 MB) — or pick a cloud ASR in the next step and skip this:
modelscope download iic/SenseVoiceSmall model.pt --local-dir models/SenseVoiceSmallCreate your configuration file. It overrides keys from config.yaml; everything else keeps its default:
mkdir -p data && cat > data/.config.yaml <<'YAML'
selected_module:
LLM: MyLLM
LLM:
MyLLM:
type: openai
url: https://api.openai.com/v1/
model_name: gpt-4o-mini
api_key: sk-your-key
YAMLStart the server:
python app.pyThe log prints the OTA and WebSocket endpoints. Point a device at http://<host>:8003/nilo/ota/
and it receives the WebSocket URL to connect to, or check the routes yourself:
python ../../scripts/smoke_check.pyMore: docs/getting-started.md.
Three layers, later wins: main/nilo-server/config.yaml (shipped defaults, fully commented) →
the user file (data/.config.yaml, or the file named by NILO_CONFIG) → environment variables
NILO_SERVER_HOST, NILO_SERVER_PORT, NILO_HTTP_PORT, NILO_LOG_LEVEL. Providers are chosen in
selected_module; each provider block's type names the adapter module. Full reference:
docs/configuration.md.
Derived from main/nilo-server/core/providers/:
| Kind | Adapters |
|---|---|
| ASR | FunASR (local), FunASR server, sherpa-onnx (local), Vosk (local), OpenAI-compatible (incl. Groq), Doubao, Doubao streaming, Tencent, Aliyun, Aliyun streaming, Aliyun Bailian streaming, Baidu, Xunfei streaming, Qwen3 ASR Flash |
| TTS | Edge TTS, OpenAI-compatible, Doubao, Huoshan double-stream, Aliyun, Aliyun streaming, Aliyun Bailian streaming, Tencent, Minimax, SiliconFlow CosyVoice, Fish Speech, GPT-SoVITS v2/v3, Index TTS, PaddleSpeech, Xunfei streaming, Coze CN, custom HTTP |
| LLM | OpenAI-compatible (OpenAI, DeepSeek, Doubao, ChatGLM, LM Studio, Volces gateway…), Ollama, Gemini, Dify, Coze, FastGPT, Langflow, Xinference, Aliyun Bailian apps, Home Assistant conversation |
| Vision LLM | OpenAI-compatible (ChatGLM-4V, Qwen-VL, Xunfei Spark) |
| VAD | Silero |
| Memory | none, local short-term summary, mem0ai, PowerMem, report-only |
| Intent | function calling, LLM intent classifier, none |
Module and class names per adapter: docs/providers.md.
Robot
├── Perception (audio ✓, vision, sensors)
├── World Model
├── Memory
├── Behavior Engine
├── Agent (LLM + tools ✓)
├── Action Executor ✓
├── Safety ✓ (policy only — firmware owns every guarantee)
└── Device Protocol ✓
The rule that shapes everything: the LLM never controls motors. It requests semantic actions —
move, turn, look_at, follow, play_animation, stop — with bounded parameters; the
backend admits or rejects each one against a safety policy; the robot's firmware executes
trajectories and owns acceleration limits, collision and cliff avoidance, watchdogs and
emergency stop.
Backend safety is a second layer only. It rejects and supervises; it never guarantees. Cliff protection, the motor watchdog, current and thermal protection, physical motion bounds, acceleration constraints and a local emergency stop must be implemented independently in firmware, because this process can be killed or stalled mid-motion. Do not connect a machine that can hurt someone and rely on the backend to stop it. See docs/robot-actions.md, docs/robot-architecture.md and docs/safety-model.md.
cd main/nilo-server
pip install -r requirements-dev.txt # test/lint tooling + the slice the tests need
ruff check . # lint
mypy # strict types for robot/
pytest -q # tests (heavy ones skip without the full requirements)Nilo-specific code goes under main/nilo-server/robot/; inherited code under core/ is edited
as little as possible. Layout, conventions and where things go: docs/development.md.
pytest -q in main/nilo-server (the robot, integration and end-to-end suites included);
make test, make e2e, make lint, make typecheck from the repository root;
scripts/smoke_check.py against a running server. make e2e also writes
main/nilo-server/tmp/e2e-report.md: every state transition of all ten scenarios, taken
from the running system's own event bus. CI runs lint + mypy, the suite on Python 3.12 with
full and with dev-only dependencies, the scenarios on their own, and validates the Compose
file. Details: docs/testing.md.
echo "$(openssl rand -hex 32)" > secrets/nilo_admin_token
docker compose up # development: builds from the tree, no model needed
docker compose --profile demo up # ...and a simulated robot talking to it
make docker-build # ghcr.io/aa-box/nilo-server:base and :latest, locally
cd main/nilo-server && docker compose up -d # deployment: the published imageThe Compose file mounts ./data (your config) and the FunASR model file, exposes 8000 (WebSocket)
and 8003 (HTTP) and takes NILO_* variables. Deployment options: docs/deployment.md.
Protocol registry and migration (done) → robot skeleton, event bus, simulator (done) → device registry and session adapter (done) → action system and safety policy → semantic action vocabulary → vision and world model → behaviour engine and personality → management API, memory, multi-robot → hardware bring-up. Acceptance criteria per phase: docs/robot-roadmap.md.
The architecture on one page, with every flow and every failure mode, is docs/architecture-final.md.
Start at docs/getting-started.md for the server, or
docs/robot-getting-started.md to go from git clone to a
simulated robot that talks, moves and decides for itself. The full tree: architecture,
configuration, development, deployment, protocol, MCP, audio, providers, robot architecture,
robot simulator, safety, testing, upstream provenance, branding, migration — all under
docs/.
Nilo began from open-source infrastructure from xinnan-tech/xiaozhi-esp32-server and has since been adapted toward a general autonomous robotics backend; it no longer shares that project's device protocol or endpoints. The engineering record of the origin is in docs/upstream.md. Nilo is inspired by social robots such as Cozmo but is not affiliated with Anki, Digital Dream Labs or any upstream project.
Licensed under the MIT License; see LICENSE, which retains the original copyright notice.