Skip to main content
A plugin’s agent half can be written in two languages. This page covers the Python SDK, ados.sdk. The other agent-half language is Rust, with the ados-sdk crate; both speak the same length-prefixed msgpack wire and capability-token handshake to the plugin host, so the choice is per plugin. Python is the right pick when the plugin leans on the Python ecosystem (machine-learning inference, scripting, fast hardware bring-up); Rust is the right pick for a tight, low-overhead, long-running service. A Python plugin’s agent half runs as a subprocess under ados-supervisor. The public author surface is the ados.sdk package: the typed driver base classes, the vision-engine client types, and an in-process test harness. Import from ados.sdk, not from internal agent modules.
The plugin lifecycle context (the ctx object passed to a plugin’s hooks) is not yet re-exported from ados.sdk. The SDK’s own docstring says the context, event, and MAVLink-component helpers “land here as the host APIs stabilise.” The runtime hands ctx to the plugin at call time and the plugin uses it duck-typed: there is no imported context class to construct or subclass. A plugin that wants static typing declares a local Protocol for the parts of ctx it touches, the way the reference extensions do. This page documents the real, current surface and does not invent an API that does not exist yet.

Install

The standalone ados-sdk PyPI package is still in flight. Until it publishes, plugin source vendors the contract types directly from the agent tree at altnautica/ADOSDroneAgent/src/ados, or develops against a checked-out agent so import ados.sdk resolves.

What ados.sdk exports today

Three groups:
  • Driver layer. The base classes hardware-driver plugins subclass, plus the dataclasses they exchange with the host. See driver layer and the per-kind pages.
  • Vision. VisionClient and the frame, detection, and model types a plugin uses to read frames from the vision engine and publish detections back.
  • Testing. PluginTestHarness, FakeVisionEngine, and load_fixture let you exercise a plugin in-process with no real agent.

The agent-half plugin shape

A plugin’s agent half is a plain Python class. There is no base class to import or subclass. The manifest’s agent.entrypoint points the runner at it, in one of two forms:
  • 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 lifecycle hooks it finds. Every hook is optional and may be sync or async, and ctx is duck-typed (see the warning above):
The full hook order the runner walks is on_install, on_enable, on_configure(ctx, config), on_start, then it waits for shutdown (SIGTERM), then on_stop, on_disable. Most plugins implement only on_start and on_stop. ctx is the runtime-provided plugin context. It exposes the host service helpers a plugin needs, including ctx.mavlink.subscribe(...) and ctx.mavlink.send(...), ctx.events.subscribe(...) and ctx.events.publish(...), ctx.vision.subscribe_detections(...) and ctx.vision.designate_track(...), ctx.peripheral_manager.register_*_driver(...), ctx.config, and ctx.process.spawn. Each call is gated on a manifest permission; calling a method whose capability was not granted is rejected. Because the context type is not yet published in ados.sdk, the reference extensions declare a local Protocol for the parts they use. Read the thermal camera and gimbal extensions for the current shape.

A real skeleton

This is the Follow-Me reference plugin, trimmed to its lifecycle shape. It is a plain class the runner instantiates with no arguments. on_start opens the subscriptions the plugin needs (FC pose over MAVLink, the operator’s designate clicks over the event bus, the detection stream from the vision engine) and launches a control loop; on_stop and on_disable tear it down. Each subscription matches a manifest permission (mavlink.read, event.subscribe, vision.detection.subscribe, vision.track.designate).
The complete plugin (image-to-world projection, the standoff-follow loop, the lock-state safety gate) lives in altnautica/ADOSExtensions under extensions/follow-me/.

IPC

The supervisor passes a Unix domain socket path in the ADOS_PLUGIN_SOCKET environment variable, the capability token in ADOS_PLUGIN_TOKEN, and the bound drone id in ADOS_PLUGIN_AGENT_ID. The runtime opens the socket and speaks msgpack RPC over it:
The envelope and the capability gate match the GCS bridge. Every privileged method is checked against the granted capability set before it reaches a handler; a call that exercises an ungranted capability is rejected with a capability_denied error naming the missing capability.

Resource limits

The supervisor enforces the resource block from the manifest:
On Linux these become cgroup v2 controllers in the plugin slice (ados-plugins.slice). The plugin sees them as hard caps; an out-of-memory kill is reported to the host as a crashed lifecycle event and trips the circuit breaker.

Testing

ados.sdk ships an in-process harness. It wires a real plugin context to fake host stubs so a single pytest run drives the plugin’s lifecycle hooks with no agent and no hardware:
The harness:
  • exposes harness.context, the PluginContext to pass to the plugin’s hooks;
  • injects events with await harness.publish_event(topic, payload) and captures the plugin’s output with harness.published_events();
  • replays recorded scenarios with await harness.replay_fixture(name_or_path) (load files with load_fixture);
  • grants and revokes capabilities at runtime with harness.grant(...) and harness.revoke(...).
For vision plugins, FakeVisionEngine stands in for the real engine and lets a test push synthetic frames into a VisionClient.

Where the rest of the contract lives

The agent host modules at altnautica/ADOSDroneAgent/src/ados/plugins/ define the install and runtime machinery the SDK sits on:
  • manifest (the Pydantic manifest schema)
  • signing (Ed25519 verification + revocation list)
  • archive (pack and unpack with traversal protection)
  • state (persistent install and permission state)
  • supervisor (lifecycle: install / enable / disable / remove)
  • runner (the process entrypoint that loads a plugin and calls its hooks)
Watch the GitHub releases for the ados-sdk PyPI publish, which will re-export the contracts above and add the context helpers once they stabilise.