Subprocess model
Every third-party agent plugin runs as its own process, started by systemd as a generated unit inside the sharedados-plugins.slice
cgroup slice. There is no in-process plugin tier for third parties;
isolation is mandatory. (Built-in plugins that declare
isolation: inprocess import into the supervisor and skip all of this.)
com.example.battery becomes
ados-plugin-com-example-battery.service.
Each subprocess receives a small fixed environment:
The plugin id is passed as the runner’s positional argument, not an
env var. Paths the plugin writes to (
ctx.data_dir, ctx.config_dir,
ctx.temp_dir) are resolved by the runner and handed to the plugin on
the ctx object, not through the environment.
IPC envelope
The supervisor opens the Unix socket before spawning the plugin and listens on it. The plugin connects on startup and speaks length-prefixed msgpack frames: a 4-byte big-endian length, then the msgpack-encoded envelope.capability field on the wire. It
re-resolves the required capability from the method name on every
request, then checks the granted set carried by the token. Forging the
field fails with a capability-denied error.
The plugin class and lifecycle hooks
A plugin’s agent half is a class named by the manifestentrypoint
(module:Class, or a Plugin class in a file-path entrypoint). The
runner imports it, constructs it, and calls async lifecycle hooks if
they are defined. The hooks are duck-typed; implement the ones you need:
on_start runs once after the IPC handshake. on_configure runs with
the plugin’s config. on_stop runs when the supervisor sends SIGTERM.
on_install, on_enable, and on_disable are also called if present.
The ctx is a PluginContext with capability-gated facades:
ctx.events, ctx.mavlink, ctx.peripheral_manager (alias
ctx.peripherals), ctx.camera, ctx.vision, ctx.telemetry,
ctx.config, and ctx.process. These are the host surfaces; the IPC
client underneath is an implementation detail.
The author SDK package
ados.sdk currently exports the typed driver
base classes, the vision types, and the test harness. The
PluginContext facade surface is host-side and not yet re-exported from
ados.sdk; the reference plugins in ADOSExtensions/extensions/ are
the working examples to copy from.Capability enforcement
Capabilities are enforced on the supervisor side, not in the plugin. Each method maps to a required capability; the supervisor checks the token’s granted set before the handler runs and rejects an ungranted caller before any work happens. From the plugin’s perspective, a call that needs a capability you were not granted raises aCapabilityDenied
error:
ados plugin perms <id> --revoke <cap>. The plugin loses access on the
next token rotation, and matching calls then fail with the same denial.
cgroup limits
Every plugin runs inside theados-plugins.slice slice, and the
generated unit carries resource caps drawn from the manifest’s
agent.resources block:
MemoryMax comes from max_ram_mb, CPUQuota from max_cpu_percent,
and TasksMax from max_pids. The kernel enforces the caps. A memory
breach is OOM-killed; a CPU breach throttles rather than kills; a tasks
breach refuses new threads or processes.
Read live numbers with:
Supervisor restart policy
The generated unit runsRestart=on-failure with a fixed RestartSec
of 2 seconds and a start-rate limit:
Five failed starts inside a 60-second window trip the limit: systemd
stops trying and the supervisor records the plugin as failed. The
operator sees the failure in the GCS event stream and decides whether to
disable, remove, or investigate. A clean exit (return from
on_start
with no error) is treated as “the plugin is done” and is not restarted;
if your plugin should run indefinitely, do not return from on_start
until on_stop is called.
Service unit generation
Service units are generated from the manifest at install time and written to/etc/systemd/system/ados-plugin-<id>.service. A typical
Python-plugin unit:
ados-plugin-runner is the SDK’s process entrypoint. It loads the
plugin’s manifest, puts the unpacked plugin source on sys.path,
imports the entrypoint class, opens the IPC socket, and dispatches into
your lifecycle hooks. A plugin that declares runtime: rust ships its
own compiled binary instead; the unit execs that binary directly and the
Python runner is not involved.
Hot reload
Plugins are not hot-reloaded across version bumps. Updating from v1.0 to v1.1 stops the old subprocess, regenerates the unit, and starts fresh. State on disk under the plugin’s data dir survives.Debugging tips
ados plugin logs <id> --followtails the plugin’s log file at/var/log/ados/plugins/<id>.log.systemctl status ados-plugin-<id>.serviceshows the live unit state.systemctl cat ados-plugin-<id>.serviceshows the generated unit.ados plugin info <id>prints granted permissions and install state.