A plugin has three places to keep state. Pick the right one for the data; do not pile everything into one layer.Documentation Index
Fetch the complete documentation index at: https://docs.altnautica.com/llms.txt
Use this file to discover all available pages before exploring further.
The three layers
| Layer | Where | Survives | Use for |
|---|---|---|---|
| Ephemeral | RAM, in your plugin process | Until next restart | Counters, caches, in-flight subscriptions. |
| Per-plugin disk | /var/lib/ados/plugins/<id>/data/ | Restarts, upgrades. Wiped on remove. | Local config, calibration, log files, cached models. |
| Shared Convex | Convex tables, scoped to your plugin id | Across devices, across reinstalls (key by drone id) | Cloud-visible state: dashboards, reports, fleet-wide config. |
Layer 1: ephemeral
Just normal Python or TypeScript module state. The supervisor guarantees one process per plugin, so a Pythondict or a
TypeScript Map is your ephemeral store.
Layer 2: per-plugin disk
Every plugin gets a writable directory at install time:ReadWritePaths= is set to the data/ subdir
only. Writes outside it fail with EROFS.
The agent SDK exposes the path:
IndexedDB keyed by plugin id:
Atomicity expectations
- Single-file writes: write to
<name>.tmp, fsync, rename. The SDK’sctx.storage.atomicWrite(path, bytes)helper does this. - Multi-file transactions: the SDK does not provide a transaction primitive. Use a single JSON file or SQLite if you need ACID.
- Concurrent writers: there is only one plugin process, so the only concurrency is your own asyncio tasks. Add a lock if you fan out writes.
Quotas
The default per-plugin disk quota is 100 MB. Plugins that need more declare it in the manifest:StateDirectory= plus a periodic du -s poll. Breaches raise
a disk_quota_exceeded lifecycle event; the plugin keeps running
but writes start failing.
Format guidance
- Configuration: JSON or YAML. Keep keys typed.
- Time-series: append-only
.jsonl(one JSON object per line). - Binary: just files; do not try to pack many records into one blob. The kernel page cache works better with many small files.
- Database: SQLite is fine for non-trivial state. The SDK does not
bundle it; declare
sqlite3(Python stdlib) orbetter-sqlite3(TS) in your dependencies.
Layer 3: shared Convex
Plugins that need cloud-visible state declare a Convex schema in their manifest:plg_com_example_battery_battery_reports) and exposes
typed handles to the plugin:
- The plugin can only read and write rows in its own namespaced tables.
- The plugin can only filter by drone or fleet ids that the current operator has access to.
- The plugin cannot run a Convex action; only writes and queries.
Cleanup on uninstall
ados plugin remove <id> does the following in order:
- Stop the systemd unit if running.
- Delete the unit file. Reload daemon.
- Drop every permission grant.
- Remove
/var/lib/ados/plugins/<id>/(config, manifest, data). - Drop every namespaced Convex table the plugin owned.
- Drop the install row from the operator’s Convex profile.
--keep-data skips steps 4 and 5 and moves the data dir to
/var/lib/ados/plugins-graveyard/<id>-<timestamp>/. Reinstalling
the same plugin id offers to restore from the graveyard; the
operator picks yes or no in the install dialog.
Migration between versions
Plugins are responsible for their own data migrations. The SDK exposes adata_version helper backed by a small JSON file at
<data_dir>/.version:
What not to put on disk
- Secrets you did not generate yourself. The host already manages pairing keys, MAVLink session keys, signer keys.
- Large media. Anything over 1 GB belongs in a recording or in cloud object storage, not in the plugin’s data dir.
- PII the operator has not consented to. The plugin’s data dir is not encrypted at rest by default. Sensitive config stays in the host’s encrypted config store and is delivered to your plugin via the normal config channel.