MAVLink Governance¶
ADK governs MAVLink vehicle command authority the way runtime/ros2
governs robot-middleware actions: every command an agent wants to issue
(arm, takeoff, goto, set_mode, …) is evaluated by policy before the
runtime issues it on the autopilot link.
The framing: ROS 2 governs robot-middleware actions; MAVLink governs vehicle command authority.
The trust model (read this first)¶
The MAVLink transport — the TCP/UDP/serial link to the autopilot — is owned by
the runtime-side supervisor, never by the agent. The agent sends intent
(POST /v1/tool with a tool.mavlink.* kind and verb-specific fields). The
supervisor:
owns the connection and tracks autopilot state from heartbeats,
snapshots that trusted state at request time, and
after policy allows — and an engineer-edited safety floor passes — issues the command on the same trusted connection.
Because the agent has no path to open a connection or to supply the trusted fields, three properties are structural, not advisory:
SITL-vs-real binding. The
environment(sitl/real) is set by the operator at supervisor construction and is immutable for its lifetime; every snapshot returns it verbatim. An agent can never claimsitlon a real vehicle.Armed / GPS preconditions. Arm requires a fresh heartbeat and a 3D GPS fix; takeoff requires an autopilot-confirmed armed state.
Mission integrity.
upload_missionis bound to amission_hashthe supervisor verifies against the transmitted bytes.
The trusted fields the runtime injects (and forbids callers from supplying)
are: environment, armed_state, gps_fix, heartbeat_age_ms, sysid,
current_mode. A request that carries any of these is rejected with a
governance-protocol error before policy evaluation — the agent must send intent
only.
Two operator entry points (siblings, not nestable)¶
There are exactly two ways to reach a MAVLink-governed runtime. Both create a
single trusted runtime that owns the autopilot link and spawns the agent
directly. Do not nest them (e.g. autonomy run … autonomy demo …): the
inner autonomy run would start its own, unsupervised runtime and the agent
would fail closed with ErrMavlinkNotConfigured.
1. Bundled SITL demo¶
Bring up a SITL autopilot, then run the governed demo:
docker compose -f demo/docker-compose.mavlink.yml up -d
autonomy demo mavlink-sitl
Installed: the demo policy and agent are embedded in the binary —
autonomy demo mavlink-sitl needs no source clone (the SITL stack is the only
prerequisite). The in-repo convenience target make demo-mavlink-sitl brings up
the compose stack and runs the same command.
The agent (examples/mavlink_agent.py
— stdlib only, no pymavlink) issues five governed calls: arm, takeoff,
in-fence goto → ALLOW; out-of-fence goto → DENY (geofence); command_long →
DENY (privileged). Every decision is written to the local WAL.
2. Bring-your-own agent (production)¶
For your own agent, register a supervisor on autonomy run’s own runtime:
autonomy run \
--mavlink-endpoint tcp:127.0.0.1:5760 \
--mavlink-environment sitl \
python3 my_agent.py
--mavlink-endpoint— transport URL (tcp:,udp:,udpin:,serial:). Absent ⇒tool.mavlink.*is fail-closed (ErrMavlinkNotConfigured).--mavlink-environment—sitl|real, required when an endpoint is set; immutable for the run; passed as a flag (auditable in shell history) not an env var.
With no --policy, this defaults to the embedded MAVLink demo policy (which
governs tool.mavlink.*); pass --policy <oci-ref> for production rules. The
flags are not supported with ros2.launch (run MAVLink governance separately).
Tool kinds¶
The typed command surface. Each is a POST /v1/tool body of the form
{"kind": "<kind>", "params": {<intent>}} — intent fields only; the supervisor
injects the trusted fields.
Kind |
Intent |
Notes |
|---|---|---|
|
— |
Arm motors. Floor: fresh heartbeat + 3D fix. |
|
— |
Disarm motors. |
|
|
Floor: autopilot-confirmed armed. |
|
— |
Land. |
|
|
Mode transition. |
|
|
Guided-mode target. |
|
|
Floor: BLAKE3( |
|
— |
Clear the onboard mission. |
|
|
Parameter mutation. |
|
|
Privileged raw |
|
|
Privileged raw RC override. |
Example (the agent sends intent only):
curl -s "$AUTONOMY_RUNTIME_URL/v1/tool" -H 'content-type: application/json' \
-d '{"kind":"tool.mavlink.goto","params":{"lat":0.0005,"lon":0.0005,"alt":50}}'
Unlisted kinds under the tool.mavlink.* prefix (a future or misspelled kind)
are rejected before dispatch — the surface is a closed, typed set, not an open
prefix.
Note
upload_mission is issued via the multi-message MAVLink mission-protocol
handshake: the supervisor announces MISSION_COUNT, answers each
MISSION_REQUEST_INT with the matching MISSION_ITEM_INT, and completes on a
MISSION_ACK. mission is the canonical mission bytes (a JSON array of mission
items — see MAVLink Policy Authoring for the shape) and
mission_hash is their BLAKE3; the safety floor verifies the binding before any
item is sent, so the items that reach the autopilot are exactly the bytes the
policy authorized. A MISSION_ACK of MAV_MISSION_ACCEPTED is reported as
issued (HTTP 200); an autopilot NAK or a handshake timeout is reported as
allowed-but-not-issued (HTTP 400) — the plan was attempted but not stored.
The safety floor (engineer-edited, not policy-tunable)¶
The supervisor enforces a hard floor inside command issuance, after policy allow. Policy can add tighter restrictions but cannot lift these:
arm— heartbeat ≤ 1 s old and GPS fix ≥ 3D.takeoff— autopilot-confirmed armed within the prior 500 ms.upload_mission— transmitted bytes’ BLAKE3 matchesmission_hashbefore any mission item is sent on the handshake (see the note above).
Optional, operator-configured adapter floors (defense-in-depth, independent of
policy): a geofence (radius + ceiling on goto), a mode denylist, and
raw-command authority — command_long and rc_override are denied unless
the supervisor is explicitly granted raw authority. A floor rejection is a
fail-closed governance deny; the command is never issued.
Extending the policy¶
The demo policy is the negative example for command_long / rc_override
(denied) and the SITL-only template for arm. Author your own fail-closed
bundle for production — see MAVLink Policy Authoring.
Installed: point autonomy run --policy <oci-ref> at a bundle in the local
managed cache. The canonical demo source is
demo/policies/mavlink/mavlink.rego
(compiled into the binary as the embedded default; copy it as a starting point).
What this does not do¶
It does not certify the system for flight: the
realpath exists, but the operator owns the safety case. The floor is a floor, not a certification.It does not replace ROS 2 governance for vehicles running ROS 2 stacks — govern both
tool.ros2.*andtool.mavlink.*; they cover different layers.It does not support an agent-owned MAVLink connection for command issuance: any command must go through the supervisor.