The two layers
Layer 1: ephemeral
Just normal Python module state. The supervisor runs one process per plugin, so adict or a list is your ephemeral store.
Layer 2: per-plugin disk
Every plugin gets a writable data directory. The runner hands the path to your plugin on the context: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
Setper_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:
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 includescloud.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:
- Disable the plugin if it is running or enabled. This stops the unit.
- Delete the unit file (and any declared extra-service units). Reload the daemon.
- Remove the install directory
/var/ados/plugins/<id>/. - Delete the plugin’s log file under
/var/log/ados/plugins/, unless--keep-datais passed. - 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: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.