Skip to main content
A plugin can ship two halves in one archive:
  • An agent half: a subprocess on the node, written in Python or Rust.
  • A GCS half: a TypeScript bundle that Mission Control runs in a sandboxed iframe.
Each half has its own runtime, SDK and sandbox. The shared manifest, id, version and install state make them one plugin. The agent half declares its language with agent.runtime: python (the default) or agent.runtime: rust. A Python half uses the ados.sdk package; a Rust half links the ados-sdk crate. Both speak the same length-prefixed msgpack wire to the agent’s plugin host. The GCS half uses @altnautica/plugin-sdk whatever language the agent half is written in.

When to ship two halves

Ship two halves when the plugin needs both:
  • Hardware or system access on the node.
  • An operator-facing UI in Mission Control.
The panel in Your first plugin is GCS-only because it reads normalized telemetry and renders a readout. The first-party Vision Navigation extension is hybrid: a Rust agent half reads camera frames and writes MAVLink, and a GCS half renders the Vision Nav tab. Keep work in one half when it fits there. A plugin that only reads telemetry and renders a panel is GCS-only. A plugin that runs background work with no operator UI is agent-only.

Lifecycle

Both halves install from one .adosplug archive, and the operator approves both halves’ permissions in one grid. The agent half runs under the agent’s supervisor as its own sandboxed unit; the unit and sandbox names are on Agent runtime and sandbox. The GCS half mounts whenever a slot it contributes to renders in Mission Control. The halves start and stop independently. A GCS half can mount while its agent half is stopped, so render an offline state until state arrives.

Cross-half communication

The halves never call each other. The agent half publishes events on its own topics; the plugin host keeps the latest event per topic in the plugin’s state sidecar, and Mission Control reads that sidecar.
The GCS sees the latest payload per topic, not every event; carry a sequence or timestamp if you need ordering. Mission Control forwards a topic only when its timestamp changes, so a steady value does not replay. Publish on your plugin’s own plugin.<id>. prefix from the agent half:
Publishing under your own plugin.<id>. prefix needs no capability. Reserved, never publishable: vehicle., mavlink., mission., safety., agent., swarm., gps., vision., telemetry., and any plugin. topic outside your own plugin.<id>. prefix or your declared shared topics. Other topics need event.publish. The full rules are on Agent event bus. The GCS half reads these events through ctx.events.subscribe; it never opens a network connection of its own.

Shared manifest

One manifest with an agent block and a gcs block. The field reference is Manifest.
The top-level fields (id, name, version, license, risk) are flat. Permissions are - id: <capability> entries. Both halves share one id and version, so they form one install row.

Version compatibility between halves

Both halves ship in the same archive, so they always carry the same version. What can drift between releases is the shape of the data they exchange:
  • Event payloads published by the agent and read by the GCS. Put a schema_version field in each payload and branch on it in the GCS.
  • Stored data written by one half and read by the other. Carry a data_version in each record. See State and storage.
The SDKs do not inspect payload schemas; keeping them backward compatible within a major version is your job.

Driver code in the agent half

Driver code lives in the agent half. In Python, subclass one of the driver base classes (CameraDriver, GimbalDriver, LidarDriver, GpsDriver, EscDriver, PayloadActuatorDriver); in Rust, implement the matching trait. The facade is ctx.peripheral_manager:
The agent does not route drivers today: register_*_driver calls return not_implemented. A driver plugin opens and runs its own device from its lifecycle hooks. See Driver layer.

Worked example: hybrid plugin skeleton

For a file-path entrypoint such as agent/plugin.py, the runner loads the file directly and puts its directory on sys.path. Import sibling modules by name, not with a relative import:
The GCS half sets up its UI from the single mount call; the manifest decides which slot each panel renders in:

Failure modes

Next steps

State and storage

Where each half keeps data and how it survives updates.

Agent event bus

Topics, publish and subscribe rules, and shared topics.
Last modified on October 11, 2026