Skip to main content
An agent plugin has two durable places to keep state today: process memory and its per-plugin data directory. Pick the right one for the data; do not pile everything into one.

The two layers

Layer 1: ephemeral

Just normal Python module state. The supervisor runs one process per plugin, so a dict or a list is your ephemeral store.
This is the fastest layer and the least durable. Use it for anything you can recompute on restart.

Layer 2: per-plugin disk

Every plugin gets a writable data directory. The runner hands the path to your plugin on the context:
The context also carries ctx.config_dir (/var/ados/plugin-data/<id>/config/) and ctx.temp_dir (/run/ados/plugins/<id>). When the plugin is bound to a specific drone, ctx.data_dir is scoped per drone (/var/ados/plugin-data/<id>/drones/<agent-id>/), while ctx.config_dir stays shared and ctx.agent_id carries the drone’s id. The systemd unit grants write access only to /var/ados/plugin-data, /var/log/ados/plugins, and /run/ados/plugins. Writes elsewhere fail with EROFS.

Atomicity

There is no transaction primitive. For a safe single-file write, write to a <name>.tmp, fsync, and rename over the target. For anything needing ACID, use a single JSON file you rewrite atomically or SQLite (sqlite3 is in the Python standard library). There is only one plugin process, so the only concurrency is your own asyncio tasks. Add a lock if you fan out writes.

Format guidance

  • Configuration: JSON or YAML, typed keys.
  • Time-series: append-only .jsonl (one JSON object per line).
  • Binary: plain files. Many small files beat one large blob for the page cache.
  • Database: SQLite is fine for non-trivial state.

Configuration

The agent context exposes config two ways. ctx.config is the static config dict the host delivered at start (read from /var/ados/plugin-data/<id>/config.yaml, or config/<agent-id>.yaml when the plugin is bound to a drone). ctx.config_kv is the live key-value facade. Its read order is drone scope when bound, then global, then your default:
set defaults to scope="drone"; pass scope="global" to write the fleet-wide value. On the GCS half, the host pushes a config.changed event the plugin subscribes to with ctx.config.onChange.

Per-drone plugins

Set per_drone_config: true in the agent: block of manifest.yaml (schema v2) and the supervisor runs one process instance per connected drone, each with its own ctx.agent_id and its own scoped ctx.data_dir. Read each drone’s live settings through ctx.config_kv and fall back to the static dict for unset keys:
The shipped Follow-Me extension uses this pattern: live per-drone keys override the static defaults each control loop. A write with the default scope="drone" updates only the bound drone; scope="global" updates the fleet-wide default the other instances fall back to.

Cloud-visible state

The GCS capability catalog includes cloud.read and cloud.write for plugins that need fleet-visible state. A dedicated cloud-storage facade is not yet part of the plugin SDK, so cloud-backed plugin state is a forward-looking surface rather than a current API. For now, keep durable plugin state in the per-plugin data directory and surface readings to the operator through telemetry and notifications.

Cleanup on remove

ados plugin remove <id> does the following:
  1. Disable the plugin if it is running or enabled. This stops the unit.
  2. Delete the unit file (and any declared extra-service units). Reload the daemon.
  3. Remove the install directory /var/ados/plugins/<id>/.
  4. Delete the plugin’s log file under /var/log/ados/plugins/, unless --keep-data is passed.
  5. Drop the install entry from the state file.
--keep-data preserves the plugin’s log file. The data directory under /var/ados/plugin-data/<id>/ (including any per-drone subdirectories and config/<agent-id>.yaml files) is never deleted by remove, with or without the flag, so reinstalling the same plugin id can pick up its prior data. If you want a clean slate, clear the data directory yourself.

Migration between versions

Plugins own their own data migrations. There is no SDK migration helper, so carry a small version marker in your data directory and migrate forward on start:
Handle skipped versions; an upgrade can jump more than one version at once. Forward migration is the contract; reading new data with an older plugin is best-effort.

What not to put on disk

  • Secrets you did not generate yourself. The host manages pairing keys, MAVLink session keys, and signer keys.
  • Large media. Anything over a gigabyte belongs in a recording, not the data dir.
  • Operator PII without consent. The data dir is not encrypted at rest by default.

See also