Architecture Overview¶
Defined Robotics is a capability-first platform for autonomous robots. It decouples robot description, task authoring, and runtime execution into three independent layers that compose at compile time and runtime.
Three Compilable Inputs¶
Every mission run requires three inputs:
| Input | Format | Describes |
|---|---|---|
| RDF (Robot Description Framework) | robot.rdf.yaml |
What the robot is: hardware modules, capabilities, physical geometry |
| Task | tasks/patrol.task.yaml |
What the robot should do: a sequence of named verb steps |
| World | World model (POIs in ~/.defined/state.yaml) |
Where it happens: named Points of Interest with coordinates |
These inputs are independent — the same task can run on different robots (if capabilities match), and the same robot can run different tasks without recompilation.
Compile-Time vs Runtime Pipeline¶
graph TD
subgraph COMPILE["Compile Time"]
RDF["robot.rdf.yaml"] --> RDF_PKG["defined-rdf<br/><small>Parse YAML → Pydantic models<br/>Capability Registry</small>"]
TASK["task.yaml"] --> COMP_PKG["defined-compiler<br/><small>1. Parse task<br/>2. Expand verbs<br/>3. Gate capabilities<br/>4. Render Jinja2</small>"]
WORLD["world (POIs)"] --> RESOLVER["mission/resolver.py<br/><small>Expand $world.pois.X<br/>→ concrete (x, y)</small>"]
RDF_PKG --> COMP_PKG
RESOLVER --> COMP_PKG
COMP_PKG --> XML["task.bt.xml"]
end
subgraph RUNTIME["Runtime"]
CLI["defined-cli<br/><small>DefinedSession</small>"] -->|"WebSocket :9090"| BRIDGE["rosbridge"]
CLI -->|"XML"| EXECUTOR["defined_runtime<br/><small>BT.CPP executor</small>"]
EXECUTOR -->|"events"| CLI
EXECUTOR --> NAV2["Nav2 + Gazebo/hardware"]
end
XML --> CLI
style COMPILE fill:#1a2332,stroke:#2dd4bf,color:#e6edf3
style RUNTIME fill:#1a2332,stroke:#3b82f6,color:#e6edf3
Key Compile-Time Rules¶
- Capability gating is compile-time only (for v0.1.0). If a task requires
differential_drivebut the robot's RDF doesn't declare it, compilation fails immediately with a clear error. $world.pois.dockreferences in task YAML are resolved to concrete(x, y)coordinates before BT XML is emitted. There are no runtime POI lookups.- The compiler output is a self-contained BT XML file — it has no dependency on the RDF or task YAML at runtime.
Package Responsibilities¶
defined-rdf¶
Owns the robot description domain. Pure Python, no ROS2.
- Pydantic models for
Robot, hardware modules, and capabilities - YAML parser (
defined_rdf.parser.load) CapabilityRegistryfor querying declared capabilitiesRobotConfigdataclass for extracting sim/hardware config (DR-022 Phase 1)
defined-compiler¶
Owns YAML-to-BT-XML translation. Pure Python, no ROS2.
- Parses task YAML (
steps:+{verb:, params:}schema) - Resolves verb definitions from a three-tier lookup: local
verbs/→ built-in registry → future remote registry - Gates each verb against
CapabilityRegistry - Renders Jinja2 templates to produce BehaviorTree.CPP XML
Depends on defined-rdf as a package dependency. Never imports ROS2 (DR-010).
defined-cli¶
Owns mission orchestration and the user interface. No ROS2 imports (DR-010).
defined-cli
├── CLI Layer (Click)
├── Mission Control
│ ├── DefinedSession unified lifecycle API (connect, compile, deploy, monitor)
│ ├── Events ConnectionStatus, MissionStatus, SessionEvent
│ └── POI Resolver $world.pois.X expansion at compile time
├── State Layer
│ ├── StateStore persists to ~/.defined/state.yaml
│ ├── Blackboard dot-path KV store (3 layers)
│ └── Models RobotState, WorldState
├── Compiler Wrapper
├── Protocols
│ ├── TargetBase start/stop/status/resolve_xml_path
│ └── TransportBase connect/disconnect/send_task/subscribe_status
├── Implementations
│ ├── SimTarget manages Docker sim lifecycle
│ └── RosbridgeTransport roslibpy WebSocket client
└── TUI (Textual)
├── DefinedApp shell + key bindings
├── CommandBar command parser (no TUI dep)
└── Panels: StatusBar, MissionPanel, WorldPanel, DiagnosticsPanel
defined_platform¶
The only component that contains ROS2 code.
defined_runtime: BT.CPP executor (C++) — receives task XML, drives Nav2 actionsdefined_navigation: Nav2 lifecycle manager, costmap config, planner/controller paramsdefined_description: URDF/xacro, parametric sensor inclusion (DR-022)defined_gazebo: Gazebo Ionic world files, sensor pluginsdefined_bringup: Launch files, rosbridge node, TF bridge
Exposed to the rest of the platform only through rosbridge WebSocket and volume-mounted BT XML (DR-010).
Visualization¶
The platform does not bundle its own visualization tool. Instead, the simulation stack exposes a Foxglove WebSocket bridge at ws://localhost:8765, and any compatible client can connect.
Foxglove Studio is the recommended viewer. It runs on macOS, Linux, and Windows with no ROS2 installation required. Connect to the WebSocket and you get live access to:
- 3D view — robot model, laser scan, costmap, navigation path
- Map — SLAM-generated occupancy grid with robot pose
- Topics — raw message inspection for any ROS2 topic exposed via the bridge
Other options work too: Lichtblick (open-source fork), RViz (if you have a ROS2 install), or any rosbridge-compatible WebSocket client.
Note
The Foxglove bridge runs inside the Docker container alongside rosbridge. Port 8765 is for Foxglove (visualization), port 9090 is for rosbridge (CLI control). Both are WebSocket — different protocols, different purposes.
CLI-to-ROS2 Connection¶
The host machine (macOS or Linux) never imports ROS2. All communication goes through rosbridge WebSocket at ws://localhost:9090 (or a remote robot IP).
graph LR
subgraph HOST["Host (macOS / Linux)"]
TRANSPORT["RosbridgeTransport<br/><small>roslibpy.Ros()</small>"]
end
subgraph DOCKER["Docker / Raspberry Pi"]
ROSBRIDGE["rosbridge_server<br/><small>ws://localhost:9090</small>"]
RUNTIME["defined_runtime"]
NAV2["Nav2"]
CAPS["capability_reporter<br/><small>(Phase 2)</small>"]
end
TRANSPORT -->|"/task_file_path"| RUNTIME
RUNTIME -->|"/mission_status"| TRANSPORT
NAV2 -->|"/odom"| TRANSPORT
CAPS -->|"/robot_capabilities"| TRANSPORT
TRANSPORT <-->|"WebSocket"| ROSBRIDGE
style HOST fill:#1a2332,stroke:#2dd4bf,color:#e6edf3
style DOCKER fill:#1a2332,stroke:#f59e0b,color:#e6edf3
roslibpy (Python) handles all serialization. The host never needs rclpy, colcon, or a ROS2 installation.
roslibpy threading
roslibpy 2.0 requires all operations to use callFromThread. Subscriptions and publishes issued outside the reactor thread will silently fail. RosbridgeTransport handles this internally.
State Layer¶
The state layer is in defined-cli with zero imports from Click, Rich, or Textual, so it can be extracted to a defined-core package in Milestone 2.
StateStore¶
Persists to ~/.defined/state.yaml on every state transition. Atomic writes (write-then-rename).
graph TD
subgraph STATE["~/.defined/state.yaml"]
ROBOT["robot:<br/>status: OFFLINE | IDLE | ON_MISSION | ERROR<br/>current_mission: task name | null"]
WORLD["world:<br/>pois:<br/> name: {center, radius, type}"]
HISTORY["history: [MissionRecord, ...]"]
end
SESSION["DefinedSession"] -->|"atomic write"| STATE
style STATE fill:#1a2332,stroke:#2dd4bf,color:#e6edf3
Blackboard (3-Layer)¶
The Blackboard is a dot-path key-value store with three namespaced layers:
| Layer | Namespace | Lifetime | Contents |
|---|---|---|---|
| Constant | $const.* |
Permanent | Dock position, fixed infrastructure POIs |
| Static | $world.* |
Across missions | Survey waypoints, named areas |
| Dynamic | $mission.* |
Single mission | Current target, scan results, ephemeral state |
POIs are areas (center + radius), not points. They live in world.pois.*.
POI Resolver¶
mission/resolver.py walks the task YAML before compilation and substitutes $world.pois.<name> references with concrete {x, y} values from the Blackboard. This keeps the BT XML self-contained — no live POI lookups at runtime.
TUI Architecture¶
The TUI is built on Textual and launches when defined is run with no subcommand.
DefinedApp (tui/defined_app.py)
├── StatusBar connection status, robot status, mission name
├── MissionPanel step-by-step mission progress with icons
├── WorldPanel POI list with coordinates and types
└── DiagnosticsPanel 3-level detail: messages → suggestions → full trace
CommandBar bottom bar: /run, /stop, /world add, etc.
Key bindings: 1/2/3 focus panels, d cycles diagnostics detail, / activates command bar, q quits.
CommandBar (tui/command_bar.py) is pure Python with no Textual imports — it parses command strings and returns structured command objects. This makes it testable without a running TUI.
The TUI connects to DefinedSession via an observer pattern. Session events (ConnectionStatus, MissionStatus, SessionEvent) are emitted from background threads and consumed by the TUI on the Textual event loop via app.call_from_thread.