Skip to main content
An agent plugin keeps state in process memory and in its per-plugin data directory, and a plugin granted cloud.records can also keep small records in the operator’s cloud account. Pick the right one for the data; do not pile everything into one.

The layers

Ephemeral state

Normal Python module state. The host runs one process per plugin (per drone, for per-drone plugins), 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.

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, which is /tmp/<id> inside the unit’s private /tmp and is gone when the process exits. 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 unit can write only its own /var/ados/plugin-data/<id>/, the shared plugin log directory and its private /tmp. Another plugin’s data directory and /run/ados are not writable, and writes elsewhere fail. filesystem.host adds /srv, /mnt, /media and /boot. The full sandbox is on Agent runtime and sandbox.

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. 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, subscribe to changes with ctx.config.onChange.

Per-drone plugins

Set per_drone_config: true in the agent: block of manifest.yaml and the host 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 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 records

Request the cloud.records capability to keep small JSON records in the signed-in operator’s cloud account. Records are namespaced to your plugin by the host, so no other plugin can read them. A record body is at most 64 KiB of JSON, and a plugin holds at most 5000 records. The GCS half uses ctx.records:
Collection names are 1-64 characters of [a-z0-9_.-]. Pass deviceId to tie a record to one node, and filter list by it. A refused call rejects with a HostError whose message is unavailable (signed out or offline), invalid_args, too_large, limit_reached, not_permitted or failed. The agent half writes with the cloud.records.put host method under the same grant. Each record carries writtenBy (gcs or agent) and updatedAt, so either half can tell who wrote last.

Cleanup on remove

ados plugin remove <id>:
  1. Disables the plugin if it is running or enabled, which stops its units.
  2. Deletes the main unit and any declared service units.
  3. Removes the install directory /var/ados/plugins/<id>/ and the install entry from the state file.
  4. Unless --keep-data is passed, deletes /var/ados/plugin-data/<id>/ (including per-drone subdirectories and config/ files), the plugin’s logs, and its system user.
Pass --keep-data to keep both the data directory and the logs, so a later install of the same plugin id picks up its prior data.

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 directory.
  • Operator personal data without consent. The data directory is not encrypted at rest.

Next steps

Versioning and updates

Semver, compatibility ranges, automatic updates and rollback.
Last modified on October 11, 2026