> ## 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.

# Lifecycle

> Install, parse, enable, disable, remove. The states a plugin moves through and the operator actions that drive them.

A plugin moves through a small set of recorded states. The host is the
state machine; the plugin reacts to lifecycle hooks the runner calls.

## States

The supervisor records one status per installed plugin in
`/var/ados/state/plugin-state.json`:

| State          | Meaning                                                                                                                      |
| -------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `installed`    | Archive unpacked, manifest validated, signature accepted. Permissions recorded but not yet granted. The plugin is dormant.   |
| `enabled`      | Operator approved the permissions. The plugin is set to run.                                                                 |
| `running`      | The agent half is alive under its systemd unit; the GCS half is mounted in its iframe.                                       |
| `disabled`     | Operator stopped the plugin. The subprocess is dead and the iframe is unmounted. State on disk is preserved.                 |
| `failed`       | The unit could not stay up (it exceeded the systemd start limit). Operator intervention required.                            |
| `incompatible` | The running host version fell outside the manifest's compatibility range. The plugin will not start until the range matches. |

Removal deletes the install entry rather than moving it to a state.

## Parse before install

The install flow is deliberately two-stage:

1. **Parse**: the host 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 the requested
   permissions does the host commit the install. The agent unpacks the
   archive into `/var/ados/plugins/<id>/`; the GCS records the install in
   the operator's profile.

A malicious archive cannot leave traces just by being previewed. The
dialog runs parse; the **Install** button commits.

## Enable and disable

Enable is the first time the plugin actually runs. For a subprocess
plugin the agent generates a systemd unit (`ados-plugin-<id>.service`),
writes it under `/etc/systemd/system/`, reloads the daemon, and starts
the unit inside the shared `ados-plugins.slice`. The GCS host sets the
status to enabled and mounts the iframe in every contributing slot.

Disable stops the unit and unmounts the iframe. State on disk persists.
Re-enable resumes from where it left off.

## Failure and the start limit

The per-plugin unit restarts on failure (`Restart=on-failure`,
`RestartSec=2s`). If it exceeds the start limit (5 starts within 60
seconds) systemd stops restarting it and the supervisor records the
plugin as `failed`. The plugin does not auto-recover from that; the
operator inspects the logs and decides whether to re-enable, remove, or
investigate. Auto-recovery would mask the underlying bug.

## Lifecycle hooks (agent half)

Every hook is optional. The runner looks each one up on your plugin
instance at the right point in the process lifecycle and awaits it if it
returns a coroutine; a hook you do not define is simply skipped.

The runner is one process per plugin. Each time the unit starts, the
runner calls the startup hooks in a fixed order, waits for the shutdown
signal, then calls the teardown hooks. It does not persist a marker
across restarts, so the full sequence runs on every process start.

| Hook                        | When the runner calls it                                                                    |
| --------------------------- | ------------------------------------------------------------------------------------------- |
| `on_install(ctx)`           | First, on every process start. Treat the body as idempotent.                                |
| `on_enable(ctx)`            | Second, on every process start, after `on_install`.                                         |
| `on_configure(ctx, config)` | Third, on every process start, with the loaded config (an empty dict when none is on disk). |
| `on_start(ctx)`             | Fourth, on every process start. The usual place to subscribe to events and begin work.      |
| `on_stop(ctx)`              | On shutdown (SIGTERM or SIGINT), before the process exits.                                  |
| `on_disable(ctx)`           | On shutdown, immediately after `on_stop`.                                                   |

Most plugins only need `on_start` and `on_stop`. The runner keeps the
process alive between `on_start` and `on_stop` until the supervisor
stops the unit.

A built-in agent plugin is a plain class (no base class to subclass)
named by the `module:Class` entrypoint; a file-path entrypoint loads a
class named `Plugin`. The `ctx` argument is the plugin context (see
[event hooks and bus](/developers/event-hooks-and-bus)).

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
class TelemetryLoggerPlugin:
    async def on_start(self, ctx) -> None:
        await ctx.events.subscribe("vehicle.armed", self._on_armed)
        ctx.log.info("started")

    async def on_stop(self, ctx) -> None:
        ctx.log.info("stopped")
```

## Lifecycle hooks (GCS half)

The GCS half exposes `mount` and `unmount` through `definePlugin`:

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
import { definePlugin } from "@altnautica/plugin-sdk";

definePlugin({
  id: "com.example.lifecycle",
  version: "0.1.0",
  async mount(ctx, info) {
    console.info(`mounted ${info.id} v${info.version}`);
  },
  async unmount(ctx, info) {
    console.info(`unmounting ${info.id}`);
  },
});
```

`unmount` runs when the host disposes the iframe (operator disabled the
plugin, navigated away, or removed it). Use it to flush in-flight state.

## Remove

`ados plugin remove <id>` (or the GCS button) tears the plugin out:

1. Stop the unit if running.
2. Delete the unit file (and any declared extra-service units). Reload
   the daemon.
3. Remove the install directory `/var/ados/plugins/<id>/`.
4. Drop the install entry from the state file.

`--keep-data` additionally retains the plugin's log file. The plugin's
data directory under `/var/ados/plugin-data/<id>/` is not deleted by
remove, so a reinstall can pick up where it left off.

## Configuration changes

On the agent side, the config you receive is loaded once when the
process starts. The runner reads it from disk and passes it to
`on_configure(ctx, config)` during startup. To pick up a later edit
without a restart, read live values through `ctx.config` at the point of
use rather than caching the dict from `on_configure`.

On the GCS side the host emits a `config.changed` event that your plugin
subscribes to with `ctx.config.onChange`, so the iframe can react to an
edit while it stays mounted.

## State observability

Every transition is recorded. Operators see the plugin's history through
`ados plugin info <id>` and `ados plugin logs <id>` on the agent, and on
the plugin detail page in Mission Control.
