Agent Services
The ADOS Drone Agent uses a multi-process architecture where each service runs as an independent systemd unit. A supervisor service manages the lifecycle. Services communicate through Unix domain sockets and share no memory. The agent is a Rust-first hybrid. Most long-running services are native Rust binaries: the supervisor, the MAVLink router, the cloud relay, the video pipeline, the radio data plane, the ground-side receiver, the physical-UI display, the uplink router, the logging daemon, and the vision host. Port 8080 is owned by the native Rust front,ados-control, which answers migrated routes directly and reverse-proxies the rest to a residual FastAPI process on an internal Unix socket. Python stays where the ecosystem lives: AI and vision inference, the plugin runtime, the setup webapp, HAL board detection and first-boot bootstrap, the config layer, the health monitor, and some ground-station hardware glue (Ethernet, WiFi client, modem, buttons, peripherals). The supervisor never spawns these processes itself: it issues systemctl against a fixed catalog, so systemd remains the process manager and owns the cgroup, restart, and journald wiring. Many unit names are stable shims that exec a Rust binary, so the commands you type (systemctl restart ados-oled, ados-wfb) keep working while the implementation underneath is native.
Why multi-process
A single in-process design is simpler, but it has real drawbacks for a drone:- A crashed video encoder takes down the MAVLink proxy. The flight controller loses its companion link.
- No per-service resource limits. A memory leak in one service starves the others.
- No per-service restart. Fixing a video pipeline issue requires restarting everything.
ados-video gets restarted by systemd in 3 seconds. The MAVLink proxy never notices.
Service tree
Distributed-receive roles add three more ground-station units when enabled:ados-batman (mesh carrier), ados-wfb-relay (relay role), and ados-wfb-receiver (receiver role).
The supervisor starts child services based on the active profile (air or ground-station) and the hardware detected. Services that depend on hardware not present are masked, not started.
Systemd unit structure
Each service has a unit file in/etc/systemd/system/. Native Rust services run a binary from /opt/ados/bin/; the Python-backed units run through the virtual environment in /opt/ados/venv/. The unit name stays stable even when the implementation is native: ados-mavlink runs ados-mavlink-router, ados-oled runs ados-display, ados-wfb runs ados-radio, ados-uplink-router runs ados-net, and ados-wfb-rx runs ados-groundlink.
- PartOf: service stops when the supervisor stops
- Restart=on-failure: automatic restart on crash
- MemoryMax / CPUQuota: cgroup limits prevent any single service from starving the system
IPC: Unix domain sockets
Services communicate through two Unix domain sockets in/run/ados/:
MAVLink socket (/run/ados/mavlink.sock)
Binary protocol. Each frame is a 4-byte little-endian length prefix followed by raw MAVLink bytes.
/run/ados/api-internal.sock (the internal socket the residual FastAPI binds, reached only through the ados-control front) and /run/ados/logd-query.sock (the on-box query socket for the logging daemon, used by ados logs query).
State socket (/run/ados/state.sock)
JSON protocol at 10 Hz. Each frame is a newline-delimited JSON object:
Circuit breaker
The supervisor implements a circuit breaker pattern for each service. If a service crashes 5 times within 60 seconds, the breaker opens and the service is not restarted until a manual reset or a supervisor restart.- Logs a CRITICAL event
- Sends a notification to Mission Control
- Continues running all other services
Profile detection
On first boot (or whenagent.profile: auto is set), the profile_detect module runs a score-based hardware fingerprint:
Decision rules:
- Ground score
>= 4AND air score<= 2: ground-station profile - Air score
>= 4AND ground score<= 2: air profile - Ambiguous: unconfigured, show pick-profile UI
/etc/ados/profile.conf with the full fingerprint snapshot. Explicit agent.profile: in config.yaml always overrides detection.
Service lifecycle per profile
- Air profile
- Ground station profile
Always started:
ados-supervisor,ados-mavlink,ados-control,ados-api,ados-cloud,ados-health,ados-logd
ados-video(if camera detected)ados-wfbin TX mode (if RTL8812EU detected)ados-vision(if the vision engine is provisioned)ados-peripherals(if USB sensors detected)ados-gpio(off until enabled, for a status buzzer or LED)
ados-discovery,ados-plugin-host
- All ground-station services (hostapd, oled, buttons, kiosk, etc.)
ados-cloud unit is a single cross-profile service. On the ground-station profile it also runs the cloud-relay bridge that forwards a drone’s telemetry and video signaling on to the cloud, so there is no separate relay unit.
HTTP control surface
Port 8080 is served by the native Rust front,ados-control. It answers migrated routes directly and reverse-proxies everything else to the residual FastAPI process (the ados-api unit) over an internal Unix socket at /run/ados/api-internal.sock. From a client’s point of view there is one HTTP surface on :8080. The split is internal, and the request paths, response bodies, and schemas stay the same across the boundary.
The surface provides:
/api/statusand/api/status/fullfor agent state/api/video/*for video pipeline status and MediaMTX integration/api/v1/ground-station/*for ground-station-specific endpoints (WiFi, pairing, OLED, buttons, uplinks)/api/commandfor drone commands (arm, disarm, mode change)/api/configfor reading and writing agent configuration
X-ADOS-Key header with a key stored in /etc/ados/config.yaml, generated at install time. The MAVLink WebSocket on :8765 (served by ados-mavlink-router) uses a short-lived HMAC ticket instead.
The residual FastAPI keeps the features that stay in Python: AI and vision endpoints, the plugin runtime surface, the setup webapp, the device-discovery and peripherals routes, and the WHEP video bridge. As more routes move to the Rust front, the FastAPI footprint shrinks toward those Python-bound features only.
HAL board profiles
Each supported SBC has a YAML profile insrc/ados/hal/boards/. The profile defines the SoC, the UART and GPIO map, the video codec support, and the navigation hardware:
/proc/device-tree/model, /proc/cpuinfo, and an optional /etc/ados/board_override. Unknown boards fall back to safe generic-arm64 defaults.
Resource budget
Memory use is dominated by the Python residual (FastAPI), the video encoder buffers, and Chromium when the HDMI kiosk runs. The native Rust services are lean: each orchestrator sits in the tens of MB. Indicative figures for the ground-station profile on a Pi 4B (4 GB RAM):
On a 4 GB Pi 4B this leaves multiple GB free with or without the kiosk. A lean flight node can run a zero-Python core (the MAVLink router, camera encode, radio, and the
ados-control front) for a much smaller footprint.
What is next
- Video Stack for the camera-to-browser pipeline
- Cloud Infrastructure for the three relay layers
- Project Structure for the codebase layout