Skip to main content
A plugin’s agent half can be written in Python or Rust. This page covers the Python SDK, ados.sdk. The other option is the ados-sdk crate; both speak the same length-prefixed msgpack wire and capability-token handshake to the plugin host. Python suits a plugin that leans on the Python ecosystem (inference, scripting, fast hardware bring-up); Rust suits a tight, long-running service. A Python agent half runs in its own systemd unit, started by the plugin runner. Import the author surface from ados.sdk, not from internal agent modules.

Install

There is no PyPI package. Develop against a checkout of altnautica/ADOSDroneAgent installed in your environment (pip install -e . in the checkout) so import ados.sdk resolves. On a node, the runner provides the same package.

What ados.sdk exports

  • Driver layer. The base classes hardware-driver plugins subclass, plus the dataclasses they exchange with the host. See the driver layer.
  • Vision. VisionClient and the frame, detection and model types a plugin uses to read frames and publish detections.
  • Camera selection. Helpers that resolve a camera parameter (a camera id or auto) to the camera the plugin should use.
  • Offload gates and lock tracking. The freshness and lock gates and LockedTargetTracker that a behavior plugin uses to act only on a fresh, locked target.
  • Testing. PluginTestHarness, FakeVisionEngine and load_fixture exercise a plugin in-process with no agent.
ados.sdk does not export a context class. The runner hands ctx to your hooks at call time; a plugin that wants static typing declares a local Protocol for the parts of ctx it uses, as the reference extensions do.

The agent-half plugin shape

An agent half is a plain Python class with no base class. The manifest’s agent.entrypoint points the runner at it:
  • module:Class, for code packaged as an importable module (follow_me:FollowMePlugin).
  • A path inside the archive (agent/plugin.py), in which case the class must be named Plugin.
The runner constructs the class with no arguments, then calls the hooks it finds. Every hook is optional and may be sync or async:
The hook order is on_install, on_enable, on_configure(ctx, config), on_start, then the runner waits for shutdown, then on_stop and on_disable. See Lifecycle for when each runs.

The plugin context

Every facade call is gated on a capability; a call whose capability was not granted is rejected with capability_denied: <cap>.

A real skeleton

This is the Follow-Me reference plugin, trimmed to its lifecycle shape. Each subscription matches a manifest permission (mavlink.read, event.subscribe, vision.detection.subscribe).
The complete plugin lives in altnautica/ADOSExtensions under extensions/follow-me/.

IPC

The unit passes the Unix socket path in ADOS_PLUGIN_SOCKET and the node profile in ADOS_NODE_PROFILE. The token, the bound agent id and the data dir arrive in a systemd credential at $CREDENTIALS_DIRECTORY/ados-plugin-token, never in the environment; the runner reads it for you. If the socket or the credential is not there yet, the runner keeps retrying until the plugin host accepts the connection. It then speaks msgpack RPC:
Every privileged method is checked against the granted capability set before it reaches a handler.

Resource limits

The plugin host enforces the manifest’s resource block:
On Linux these become MemoryMax, CPUQuota and TasksMax on the plugin’s unit in ados-plugins.slice. An OOM kill ends the process and systemd restarts it after 2 s, with no start limit.

Testing

PluginTestHarness wires a real plugin context to fake host stubs, so a pytest run drives your hooks with no agent and no hardware:
The harness:
  • exposes harness.context, the context to pass to your hooks;
  • delivers events with await harness.publish_event(topic, payload) and captures the plugin’s output with harness.published_events(), a list of (topic, payload) tuples;
  • replays recorded scenarios with await harness.replay_fixture(name_or_path) (load files with load_fixture);
  • grants and revokes capabilities with harness.grant(...) and harness.revoke(...).
FakeVisionEngine stands in for the vision engine and pushes synthetic frames into a VisionClient. ados plugin test <dir> runs a plugin’s pytest suite with the manifest validated first; see the Plugin CLI reference.

Where the rest of the contract lives

  • src/ados/plugins/manifest.py: the strict manifest model the CLI’s lint, sign and test enforce.
  • src/ados/plugins/runner.py: the process entrypoint that loads a plugin and calls its hooks.
  • crates/ados-plugin-host/src/{signing,state,supervisor,archive}.rs: signature verification and the revocation list, persistent install and grant state, the install and enable lifecycle, and archive unpacking with traversal protection.

Next steps

Your first plugin

Build, sign and install a plugin end to end.

Event hooks and bus

Topics and the agent event bus.

Vision plugins

Frames, models and detections.

Rust SDK

The same surface in Rust.
Last modified on October 11, 2026