Skip to content

Architecture

Ember is four pieces. Three of them run on one Linux box on your network; the fourth runs on the robot. Each speaks to the others over an interface you can watch, curl and replace.

The division is deliberate. The robot owns anything with a deadline: motors, reflexes, animation timing, the wake word. The Go server owns the protocol and the audio path, because it has to be quick and it has to run for weeks. The Python brain owns everything that is thinking rather than reacting. The browser owns none of it and only ever asks the server.

Who talks to whom, and over what

This is the part Vector actually connects to. It is a fork of wire-pod, which is itself built on the chipper server Digital Dream Labs open-sourced.

It terminates the robot’s gRPC connection on port 443, so it needs the capability to bind a low port and it must be running in escape-pod mode — that is the mode in which the robot-facing listener starts at all. It takes the Opus audio stream from the microphones, sends it to a transcription endpoint, matches the text against a small intent file, and either performs the intent or hands the utterance to the language model and streams the reply back as speech.

On top of upstream, Ember adds:

  • An outbound event system (pkg/events). Every interesting thing the robot does is emitted as a JSON event and posted to subscribers. See Events.
  • An SSH channel to the robot (pkg/robotssh) for the things the robot’s SDK cannot do: installing firmware, wake-word models and behaviour presets, and pointing the robot at this server in the first place. It generates its own key pair on first use rather than relying on the widely published unlock key.
  • LAN discovery (pkg/robotfind) over mDNS, so the dashboard can find robots advertising _ankivector._tcp.
  • Behaviour presets (pkg/profiles) — folders of replacement behaviour JSON applied over SSH, with the originals kept alongside so a preset can be undone.
  • A Home Assistant bridge (pkg/mqtt) that turns events into MQTT discovery entities: docked, picked up, touched, speaking, last transcript, last intent, the cube, and a text entity that makes him talk.
  • The social engine (pkg/social), which decides what he does when a cat or a person comes right up to him. See Social reactions.
  • Web serving: the dashboard on port 8080 and, with its own certificate, on port 8443, because Web Bluetooth needs a secure context and the setup walkthrough uses Bluetooth.

The whole server is one static Go binary. There is no Node, no Python and no web server in front of it.

A FastAPI service on 127.0.0.1:8090. It holds everything that would be miserable to write in Go: the persona, a memory database, mood, greetings, idle chatter, the mode behaviour, and a small server-side image recogniser.

It is a subscriber, not a component. The voice server posts events to it and carries on. For a few events — being petted, picked up, put down, the cube being tapped — the server waits up to twenty seconds for the brain’s reply and speaks whatever it says. Everything else is fire and forget. If the brain is stopped, the robot still hears you, still answers, and simply stops being interesting.

The language model is any OpenAI-compatible endpoint, set by environment variables. The dashboard exposes the same settings through a reverse proxy at /brain/, so the browser never talks to the brain directly.

Per robot, the brain keeps a profile, a persona file, and its own mood, face and greeting state, keyed on the robot’s serial. The serial travels on every event.

Vite, React and Tailwind, hash-routed into six sections. It is a pure client of the two HTTP APIs above; it holds no state the server does not have.

Building it writes the bundle into the Go server’s web root, and that output is committed. The consequence is that deploying the server deploys the dashboard, and the machine running Ember never needs a JavaScript toolchain.

See The dashboard for what is on each page.

A firmware image built from WireOS, which is itself a rebuild of Anki’s own robot software. It is what makes the robot’s own capabilities reachable: styleable eyes, detectors that stay on without an app asking for them, a thermal daemon, and behaviour presets that survive a reboot.

Images are installed over the air from your own server. See EmberOS firmware.

A Python script and a systemd unit that start the Go server and the brain, watch them, and restart them when they die. It knows two things worth knowing: it will not bounce the voice server while the robot is downloading firmware — that truncates the download and the robot aborts the update — and it re-applies the capability that lets the server bind port 443 after every rebuild.

FromToHow
RobotVoice servergRPC over TLS on 443; audio up, actions and speech down
Voice serverRobotThe same gRPC session, plus SSH for firmware, presets and wake words
Voice serverBrainPOST of a JSON event to each subscriber URL; a reply is read for a few of them
BrainVoice serverPOST /api/say to speak, /api-sdk/* to do anything else
DashboardVoice server/api/* and /api-sdk/*
DashboardBrain/brain/*, reverse proxied by the voice server
Voice serverHome AssistantMQTT, with discovery
Voice serverTranscriptionAny OpenAI-compatible transcription endpoint
BrainLanguage modelAny OpenAI-compatible chat endpoint

Ember runs on one ordinary Linux machine on your own network. Nothing is measured as a minimum, because nothing has been trimmed to find one; what follows is what the working install actually uses.

HostA container or small server. The live one is 8 cores and 16 GB, comfortably more than it needs.
RobotAn Anki Vector, unlocked so it accepts your own certificate and firmware.
SpeechAny OpenAI-compatible transcription endpoint. A GPU elsewhere on the network is enough; the live setup uses a distil-whisper model on another machine.
LanguageAny OpenAI-compatible chat endpoint, local or hosted.
Vision recogniserOptional, and the heaviest part: an 85 MB int8 CLIP ViT-B/32 running on the CPU, about 0.4 s per 640x360 frame on eight cores.
NetworkEverything stays on the LAN. The robot reaches the server by mDNS, so the two must share a network segment.

The GPU work can sit on a different machine from the server, which is how the live install is arranged: the voice server and brain on one container, speech and language models on another with the graphics card in it.