Skip to main content
A plugin moves through a small set of recorded states. The agent’s plugin host is the state machine; the plugin reacts to the lifecycle hooks its runtime calls.

States

The host records one status per installed plugin in /var/ados/state/plugin-state.json. The record describes the agent half; the GCS half has no recorded state of its own. Grants are recorded per permission and are independent of enable and disable. failed and incompatible are reserved states the current host does not assign. Removal deletes the install record rather than moving it to a state.

Parse before install

The install flow is two-stage:
  1. Parse: the agent opens the archive in memory, validates the manifest, verifies the Ed25519 signature, and returns a summary. Nothing is written to disk.
  2. Install: only after the operator approves does the agent commit. It checks compatibility (ados_version, board, tier, target_profiles against the node’s profile, and a binary for the host architecture), downloads and verifies any payloads, then unpacks the archive into /var/ados/plugins/<id>/.
Previewing an archive leaves no trace. See Permissions for what the operator sees.

Enable and disable

Enable is the first time the plugin runs. For an agent half the host writes a service unit and starts it. On Linux that is a systemd unit named ados-plugin-<id>-<hash8>.service (dots in the id as hyphens, <hash8> the first 8 hex digits of the SHA-256 of the id), plus one ados-plugin-<id>-<hash8>-<service>.service per declared service, in the shared ados-plugins.slice. On macOS it is a per-user LaunchAgent named co.ados.plugin.<id>-<hash8>; because launchd provides no sandbox, only first-party plugins install there. Mission Control mounts the GCS half in each slot it contributes to: in an iframe, or inline for a first-party gcs.isolation: inline module. Disable stops the unit. Data on disk persists, and re-enable resumes from where it left off.

Restarts

A plugin unit restarts on failure every 2 s with no start limit (Restart=on-failure, RestartSec=2s, StartLimitIntervalSec=0). A crash-looping plugin stays running in the record; check systemctl status and the plugin log (see Troubleshooting).

Lifecycle hooks (agent half)

Every hook is optional. The runner looks each one up on your plugin instance and awaits it if it returns a coroutine; a hook you do not define is skipped. The runner is one process per plugin (one per drone with per_drone_config: true). Each time the unit starts, the runner calls the startup hooks in a fixed order, waits for the stop signal, then calls the teardown hooks. It keeps no marker across restarts, so the full sequence runs on every process start. Most plugins only need on_start and on_stop. An agent plugin is a plain class with no base class to subclass, named by a module:Class entrypoint; a file-path entrypoint loads a class named Plugin. The ctx argument is the plugin context; see the Python SDK.
A Rust plugin implements the same hooks on the Plugin trait; see the Rust SDK.

Lifecycle hooks (GCS half)

The GCS half exposes mount and unmount through definePlugin:
mount runs once the bundle loads. When the operator closes the surface, switches drone, disables or removes the plugin, Mission Control asks the frame to unmount, waits up to 500 ms, and then destroys the iframe. Keep unmount short and do not rely on it to persist state; write state as it changes. For your own teardown, call the unmount() of the instance definePlugin returns; it runs your unmount.

Remove

ados plugin remove <id> (or Uninstall on the Extensions tab) tears the plugin out:
  1. Stop the unit if running.
  2. Delete the unit file and any declared service units, then reload the service manager.
  3. Remove the install directory /var/ados/plugins/<id>/.
  4. Drop the install record from the state file.
Without --keep-data, remove also deletes the plugin’s log, its data directory /var/ados/plugin-data/<id>/ and its system user. --keep-data keeps all three, so a reinstall picks up where it left off.

Configuration changes

On the agent side, ctx.config is the static dict loaded when the process starts and passed to on_configure. To pick up a later edit without a restart, read live values with await ctx.config_kv.get(key, default) at the point of use rather than caching the dict. On the GCS side, ctx.config.onChange delivers the plugin’s config on mount and after every accepted write. See GCS bridge methods.

State observability

ados plugin info <id> prints the record, and ados plugin logs <id> prints the agent half’s log. In Mission Control the Extensions tab shows each plugin’s status.

Next steps

Plugin CLI reference

Drive the lifecycle from a shell.

Troubleshooting

When a plugin will not install or will not stay up.
Last modified on October 11, 2026