# Aventail Link release notes

Historical release notes. Current procedures are in the setup and product guides.

Source: [https://docs.aventail.co.uk/docs/changelog/link](https://docs.aventail.co.uk/docs/changelog/link)

## Aventail Link 2.0.0 · installer observed 2 October 2026

The current [macOS installation guide](https://docs.aventail.co.uk/docs/link-app/install-macos) follows the supplied Aventail Link 2.0.0 package:

-   **New name:** Aventail Link replaces Locai Link (Legacy Aventail) for the new product.
-   **Current-user installation:** the macOS ARM64 package installs under `~/Library/Application Support/Aventail-Link/` and starts its user service automatically.
-   **New setup:** Connect → Models → Serving → Finish, with **Sign in with Aventail** and account model selection.
-   **New command:** `aventail`, with service actions under `aventail service`.

This entry records the inspected macOS package. On 2 October, the production release host `get.aventail.co.uk` also published 2.0.0 desktop and headless assets for macOS ARM64, Linux x64 and Windows x64. Headless commands now use `aventail`, `AVENTAIL_REGISTRATION_KEY` or `AVENTAIL_FLEET_KEY`, and the Control environment’s `LINK_API_BASE`. See [current setup](https://docs.aventail.co.uk/docs/getting-started/initial-setup#headless) and [release environments](https://docs.aventail.co.uk/docs/introduction/versions-and-supported-models#choose-the-correct-release-environment). Availability does not establish that every platform has been tested.

Locai Link (Legacy Aventail) 1.x files, commands and historical screenshots remain versioned separately.

**Locai Link (Legacy Aventail) 1.3.2 · 19 August 2026**

The [official release](https://github.com/locai-co-uk/locai-link/releases/tag/v1.3.2) reports fixes for headless engine-download certificates, macOS service startup, PATH guidance for non-admin macOS installs, and applying a new registration identity while the service is running.

For current installation and package availability, use [Aventail Link overview](https://docs.aventail.co.uk/docs/link-app/overview) and [Compatibility and versions](https://docs.aventail.co.uk/docs/introduction/versions-and-supported-models). The September inference examples used 1.3.2; release notes alone do not verify every platform or recovery path.

## Earlier announcements

These entries preserve historical release announcements. Commands, platform support and product claims describe their original context; use the current guides for operating instructions and security guidance. The old `get.locai.co.uk` installer address now serves Aventail 2.x; commands quoted below no longer install the historical release. Use a versioned 1.x GitHub release when maintaining a legacy installation.

**Read earlier release history**

## Locai Link (Legacy Aventail) 1.3.0 · 2026-08-07

One app, one service, and a one-line install for servers. First-run setup and Preferences are now a single Locai Link (Legacy Aventail) desktop app, the launcher and the app have merged into one binary managed by one background service, and Linux, macOS, and Windows servers can install Link with a single pasted command and register with just a key.

### Added

-   **Headless install for servers.** Install Locai Link (Legacy Aventail) on a machine with no desktop UI using one pasted command: `curl -fsSL https://get.locai.co.uk/install.sh | sh` on Linux and macOS, or `irm https://get.locai.co.uk/install.ps1 | iex` on Windows PowerShell. The installer needs no admin rights on any platform, verifies its downloads against the release checksums, sets the service to start at login or boot, puts the `locai` command on the PATH, and is safe to re-run: a second run reports status and self-heals the service instead of clobbering the install.
-   **Key-only registration.** Registration happens entirely on the terminal: `locai register --registration-key <KEY>` for a single device, or `locai register --fleet-key <KEY>` for fleet enrollment. The key is the only credential — no email, password, or browser on the box — and the device names itself and appears in your dashboard. For unattended provisioning, set `LOCAI_REGISTRATION_KEY` (or `LOCAI_FLEET_KEY`) in the environment before the install command and the device registers itself. See [Register a Device](https://docs.aventail.co.uk/docs/getting-started/initial-setup#connect-your-device).
-   **Engines on demand.** Headless installs no longer bundle inference engines, so the install is a fraction of the previous size. When a model is first deployed, the device fetches just the engine it needs, checksum-verified, and caches it — including the multi-model serving path, so headless devices get the same serve-through-proxy behaviour as desktop.
-   **One `locai` CLI everywhere.** `locai status`, `start`, `stop`, `restart`, `update`, and `uninstall` work identically on desktop and headless installs, on every platform. `locai uninstall` now removes the device from Control automatically, stops the service, and deletes every installed file. `locai --help` and `locai --version` answer on the terminal instead of opening the app window.
-   **No dead ends before setup finishes.** Closing the setup window before onboarding completes no longer strands you: the menu-bar icon now offers **Continue Setup…** and **Uninstall Locai Link…**.

### Changed

-   **Setup and Preferences are one desktop app.** First-run onboarding opens in a window of Locai Link (Legacy Aventail) itself; there is no separate Setup Assistant any more. One app, one bundle, one thing to update — and upgrading removes the old Setup Assistant automatically.
-   **One binary, one background service.** The launcher and the desktop app are now a single `locai-link` binary managed by one service, so there are fewer moving parts to install, supervise, and update. On macOS, an over-the-air upgrade from an older version switches to the single service immediately and asks once for your password to finish the change; declining leaves the device on the new version and asks again next time.
-   **Source installs are developer-only.** Field and headless devices use the pre-built releases. The old source-install one-liners, the `setup` and `install` CLI subcommands, and git-based updates have been removed; a source checkout is for development via `uv` and updates with `git pull`.
-   **Verified downloads.** Release downloads are verified against a single release-wide checksum file, and serving no longer reports success when an inference engine is missing — the serve fails with a clear error instead.

### Fixed

-   **Deploying the same model file twice no longer fails one of them.** Concurrent deploys that target the same download now take turns instead of racing, so both succeed.
-   **Update no longer hangs on "Updating".** Clicking Update when no installable release is available is declined immediately with a clear message instead of leaving Preferences stuck.
-   **Cancelling an uninstall is a clean no-op.** Dismissing the admin prompt no longer shows a false "uninstall failed" error and no longer deregisters the device — everything stays installed and registered.
-   **The menu-bar icon no longer vanishes.** The tray could disappear (and silently relaunch) when its menu updated, for example when a model started serving or a large download finished. It now updates safely, and the menu-bar window no longer minimises to the Dock when the tray refreshes.
-   **Preferences opens on the truth.** The window no longer flashes "Stopped" for a moment when reopened, and the Logs **Open** button now reveals the current log file.
-   **Uninstalling on macOS removes everything.** The uninstaller could stop halfway when it shut the app down, leaving files behind. It now always completes.
-   **Slow deploys during onboarding no longer report a false failure.** The deploy request now allows for a cold backend; the deploy is queued and the model still installs.
-   **Linux tray stays current.** The tray menu no longer freezes on stale state, such as a finished download stuck at "Downloading" or a serving count that stopped tracking serve and stop.

## Locai Link (Legacy Aventail) 1.2.2 · 2026-07-31

The companion now protects itself during automatic updates.

### Changed

-   **Controls lock during updates.** While an over-the-air update is in progress, the companion temporarily hides model controls and shows an **Updating…** status. Normal operation resumes automatically once the update completes.
-   **Internal contract hardening** across the config, state, and transport layers (no user-facing behaviour change).

## Locai Link (Legacy Aventail) 1.2.1 · 2026-07-29

Fleet enrollment lands on the one-line installer, and a device only calls itself online when it is truly ready.

### Added

-   **Fleet enrollment from the installer.** The one-line installer accepts a fleet key (`--fleet-key` on macOS and Linux, `-FleetKey` on Windows PowerShell) for non-interactive managed deployments, so IT teams can enroll devices silently without the Setup Assistant. See [Enrolling devices with a fleet key](https://docs.aventail.co.uk/docs/fleet/keys-and-analytics#enrolling-devices-with-a-fleet-key).

### Fixed

-   **Online means ready.** A device now reports "online" only after its command channel is fully ready, preventing Control from dispatching commands before the device can receive them.

## Locai Link (Legacy Aventail) 1.2.0 · 2026-07-24

Workspace access from the menu bar, model removal from the device, and a self-cleaning uninstall. The companion can now open your Aventail Workspace directly, remove individual models without opening Control, and uninstalling Link now tidies up after itself on the Control dashboard.

### Added

-   **Open a Workspace from the menu bar.** A new tray item opens [workspace.locai.co.uk](https://workspace.locai.co.uk/) in your default browser, taking you straight to [Aventail Workspace](https://docs.aventail.co.uk/docs/workspace/getting-started).
-   **Remove a model from the device.** Deployed models in the companion now have a per-model Remove action that deletes the model from the device and frees the disk space. Removing a serving model stops it before deletion, the Control dashboard updates to reflect the change, and the model stays available to re-download later. If the device is offline when you remove a model, the update is queued and reported to Control automatically once the connection is restored. See [Removing a model](https://docs.aventail.co.uk/docs/link-app/deploy-models#removing-a-model-from-this-device).

### Changed

-   **Uninstalling now deregisters the device.** Uninstalling the companion deregisters the device from Control, removing the stale offline dashboard row and freeing the device-limit slot. Per-user Setup Assistant data is also fully removed on uninstall. See [Deregistering a Device](https://docs.aventail.co.uk/docs/getting-started/initial-setup#deregistering-a-device).
-   **macOS installer advertises Apple Silicon only.** Intel Macs are no longer offered an incompatible build.
-   **Hardened archive extraction.** Archive extraction during router and plugin provisioning now uses the same path-traversal-guarded extractor as the updater. Service manager commands also no longer run through a shell, and the source-install service label now uses the same reverse-DNS namespace as the packaged app (internal hardening, no user-facing behaviour change).

### Fixed

-   **Version visible everywhere.** The Preferences window now shows the installed version string on all install layouts.
-   **No more duplicate tray icons.** Launching the companion when it is already running now focuses the existing window and exits cleanly.
-   **Startup online report no longer dropped on slow networks.** The device's first online report is now delivered reliably, so a freshly started device no longer lingers as offline in Control on a slow connection.

## Locai Link (Legacy Aventail) 1.1.0 · 2026-07-16

Whole-app over-the-air updates and in-app model downloads. Updating a device now refreshes the menu-bar companion and Setup Assistant alongside the agent runtime, macOS gets a working automatic-update path for the first time, and the companion can pull new models on demand without reopening the Setup Assistant.

### Added

-   **Automatic updates now cover the whole app.** Updating a device refreshes the menu-bar companion and Setup Assistant, not just the agent runtime. Only the parts that actually changed are reinstalled, and the companion restarts itself so the new version takes effect straight away.
-   **Automatic updates on macOS.** macOS devices can now update over the air for the first time. Update checks run through Control, so they are safe to use across a fleet, and each release ships a signed, notarised update package so an updated app stays trusted by macOS.
-   **Download models from the companion.** The menu-bar companion can now request and download extra models straight from its Available Models list, with live progress, without reopening the Setup Assistant.

### Changed

-   **Smarter model picker in the Setup Assistant.** The model list now highlights a recommended default and only shows the models this device can actually run, so it is easier to pick one that will work.

## Locai Link (Legacy Aventail) 1.0.19 · 2026-07-10

The first end-to-end graphical install path for macOS and Linux. A signed `.pkg` (macOS) or tarball (Linux) installs the runtime together with two apps: a **Setup Assistant** that signs you in, registers the device with Control, and deploys the models you pick, and a menu-bar **companion** that shows agent health, deployed models with live download progress, and a Preferences window. See [Using the Link App](https://docs.aventail.co.uk/docs/link-app/overview) for the full guide.

### Added

-   **Setup Assistant.** A first-run onboarding wizard: sign in with your Aventail account (browser sign-in flow), name the device, choose whether Link starts at login, register with Control, and optionally queue models for download at the end. Launching it again later detects the existing install and offers Continue, Re-register, or Uninstall.
-   **Menu-bar companion.** A tray app that shows whether the agent is online, lists in-flight downloads with live progress and deployed models with serve and stop toggles, opens the Control dashboard, and hosts a Preferences window with device identity, agent status and version, network status, per-model controls, and log access.
-   **Cancel a model download from the device.** In-flight downloads show a Cancel button in the companion's Preferences. Cancelling stops the download immediately and cleans up the partial file.
-   **macOS `.pkg` installer.** A signed, notarised installer that installs system-wide, launches the Setup Assistant automatically when it finishes, and ships a matching uninstaller that removes the runtime, both apps, and their data.
-   **Linux tarball installer.** An unprivileged `install.sh` that installs to your home directory, registers systemd user services, adds app-launcher entries and icons, starts the agent and companion, and launches the Setup Assistant. A matching `uninstall.sh` reverses all of it.
-   **Version reporting from first contact.** Registration now includes the installed agent version, so a new device no longer shows "Version unknown" in Control between registering and its first heartbeat.
-   **Local health API extensions.** `/healthz` now carries in-flight deployment progress alongside the models list and transport state, and the local API gains per-model serve, stop, and cancel-deploy actions (used by the companion).
-   **Release-channel label.** Pre-release builds show their channel next to the version, for example `1.0.19 · Alpha`, in the Setup Assistant, the companion tray, and Preferences. Production builds hide the label.

### Changed

-   **Local health API port.** The agent-state endpoints (`/healthz`, `GET /models`) now listen on `127.0.0.1:20505` instead of `8101`. Integrators probing the old port should update; see [Your Own Software](https://docs.aventail.co.uk/docs/integration/your-own-software) for the current surface.
-   **Stale queued deployments expire.** A deployment that sits in "Queued" for more than 5 minutes (for example when the deploy command never reached the device) is automatically marked failed instead of spinning forever. Cancel and redeploy to retry.
-   **Safer uninstall scripts.** The Linux uninstaller refuses to run against a folder that is not a real Aventail install root, and the macOS uninstaller now matches only Aventail's own apps when stopping processes.
-   **More reliable model downloads.** Cancelling now interrupts the download connection immediately rather than waiting for a timeout, truncated downloads are detected and reported as failed instead of leaving a short file behind, and late failures during install always report a final status.
-   **Release artifacts.** macOS releases now publish the `.pkg` (with its checksum) as the install path, replacing the runtime-only archive. Linux releases now contain the full installer layout (runtime, both apps, services, and install scripts). Windows remains a runtime archive.

### Fixed

-   **Faster, smoother Preferences.** The Preferences window no longer blocks on slow status checks, opens with a single local round trip, and refreshes every 2 seconds.
-   **Setup Assistant stays responsive.** Signing in and waiting for the agent to come online no longer freeze the setup window.

## Locai Link (Legacy Aventail) 1.0.18 · 2026-07-07

Groundwork for the graphical install path: the first beta of the menu-bar companion, and parallel, cancellable model deployments in the runtime.

### Added

-   **Menu-bar companion (beta).** A tray-only app for macOS that reflects whether the agent is up, lists models with per-model serve and stop toggles, and can start at login.
-   **Parallel model deployments.** Deploying a model no longer blocks other commands; each deployment runs on its own worker, so several models can download at once.
-   **Cancellable deployments.** A new cancel command lets an in-flight model download stop cleanly, with progress and cancellation reflected in telemetry from start to finish.

### Removed

-   **The `tui` subcommand and `--tui` setup flag.** The text-based UI was a developer utility, was never distributed as a user feature, and has been removed. The graphical Setup Assistant and menu-bar companion supersede it.

## Locai Link (Legacy Aventail) 1.0.17 · 2026-07-01

Bundled over-the-air updates land end-to-end, `GET /models` gives integrators per-pipeline serving state, and a launcher-based bootstrap makes first-launch installs work without pre-baking a bundle into your host installer.

### Added

-   **`GET /models` on port `8101`.** Returns per-pipeline serving state (pipeline id, alias, port, host, and `is_serving`), with one entry per configured servable-model pipeline. The response is snapshotted lazily per request, so it is always current. See [Your Own Software](https://docs.aventail.co.uk/docs/integration/your-own-software) for the recommended probe pattern.
-   **Bundled OTA update path, functional end-to-end.** Frozen `.pkg` and `.msi` deployments now self-update from GitHub Releases. Downloads are SHA256-verified against a sidecar file before anything is written to disk, two versions are kept on disk at a time (`current` and `previous`), and a small launcher binary auto-rolls back if a freshly installed version exits non-zero within 120 seconds of the swap. A pre-swap self-check boots config, transport, and plugins before committing, discarding the new version if it fails. Source (git) installs are unaffected and still update via `git pull`.
-   **First-launch bootstrap from a launcher and `boot.json`.** Partner installers no longer need to ship a pre-baked agent bundle. The launcher reads `boot.json`, fetches the latest matching release, verifies it, and starts the agent, so users on first run always get the current release regardless of how old the host installer is. Hosts that prefer to pre-seed a bundle can still do so.
-   **Launcher configurability for GUI installers.** The service manager gains `scope` and `label_prefix` options so a `.pkg` installer can register a system-wide service under `uk.co.locai.link.*` labels without disturbing existing developer flows.

### Changed

-   **Browser handoff on device-flow sign-in.** When onboarding runs detached from a terminal (for example a `.pkg` postinstall or a service-mode boot), the sign-in verification URL now opens in the system browser automatically. The terminal banner stays for shell invocations, so there is no change if you are at a prompt.

### Deprecated

-   **`/healthz.currently_serving` and `/healthz.model_id`.** Both fields stay in the response for backwards compatibility, but they only reflect pipelines started via an explicit `START_SERVING` command and cannot represent multi-model serving. New integrations should read `GET /models`. Integrations that only need "is any model serving?" can migrate to `models.some(m => m.is_serving)` without an API change.

## Locai Link (Legacy Aventail) 1.0.16 · 2026-06-25

Restores model-state reporting after an unclean restart, tightens input validation and the request-handling security perimeter, makes orphaned-process cleanup safer, and updates the bundled model server.

### Added

-   **Status reporting after auto-resume.** When a device restarts and automatically resumes a model that was serving (or running inference), it now re-announces that state to Control. Previously the dashboard could stay stuck on "not serving" until you issued a fresh command, even though the model was already back up.

### Changed

-   **Updated model server.** The bundled `llama.cpp` build moved from `b9222` to `b9789`, with the Windows CUDA prebuilt following the upstream `cuda-13.3` rename. macOS and Linux download patterns are unchanged.

### Fixed

-   **Safer model removal and deployment.** Model file names are now validated before they are used on disk, closing a path-traversal vector from malformed input. Deploy and update commands also reject payloads whose pipeline identifier does not match their configuration, preventing state from being saved under the wrong key.
-   **Tighter telemetry hygiene.** Rejected command details are no longer echoed into telemetry. A generic "Invalid command payload" is reported instead, and the full diagnostic stays in local logs only. Incoming request origins are also stripped of stray control characters at the boundary.
-   **Safer orphan cleanup.** After an unclean shutdown, Link now reclaims only its own previous model-server process and refuses to touch an unrelated process that happens to have reused the same process ID or port. A serving health check now correctly reports unhealthy when no server process is running.
-   **More robust serving.** The serving proxy now accepts both common streaming event formats, fails fast and loudly when its public port is already in use instead of appearing to start silently, and no longer drops a command from its duplicate-protection window when its handler fails, so legitimate retries can proceed.
-   **More reproducible builds.** Bundle manifests are now written in a stable, canonical order, so two builds of the same set of components produce identical output.

## Locai Link (Legacy Aventail) 1.0.15 · 2026-06-18

Moves inference observability onto HTTP-response interception, lands the **bundling subsystem** (signed and notarised macOS builds with drag-to-extract packaging), hardens orphaned-process cleanup, and ships a typed command contract.

### Added

-   **Inference telemetry through a serving proxy.** A reverse proxy now always sits in front of the model server, capturing per-inference token usage directly from chat completion responses (both streaming and non-streaming). This replaces the older log-parsing approach and reports usage reliably in production serving.
-   **Multi-model attribution.** When several models are served on the same port, each model now gets its own telemetry stream, so inferences are attributed to the correct model.
-   **Bundling subsystem.** A new build pipeline produces installable bundles per platform (Linux, Windows, macOS). On macOS, every binary is code-signed with a Developer ID, runs under a hardened runtime, is notarised by Apple, and ships as a drag-to-extract `.dmg`. Windows bundles are prepared but not yet signed.
-   **Consolidated release workflow.** Building, signing, notarising, and uploading release artifacts now happen in a single workflow run.
-   **Typed command contract.** New `UNINSTALL_MODEL` and `UPDATE_PIPELINE` commands, with golden-fixture round-trip tests covering every command type to keep the device and backend in sync.

### Changed

-   **The CORS shim is now a full serving proxy.** CORS headers and inference telemetry are two independently configurable features of the same proxy, which now always fronts the model server. The model server listens on a loopback-only internal port while the proxy owns the public port.
-   **Stricter command validation.** Every incoming command is validated against the typed command contract before it runs. Commands that fail validation are reported as `failed` rather than being silently dropped.
-   **Backend-defined pipelines.** Devices now apply ready-made pipeline definitions sent by the backend instead of deriving them locally.
-   **Quieter plugin installs.** Plugin install scripts return early without log noise when a binary is already installed at the pinned version.

### Removed

-   The legacy `REMOVE_MODEL` command alias. Use `UNINSTALL_MODEL` instead.
-   The old per-partner bundle "profiles". Bundles are now described by an explicit plugin list.
-   The separate release token requirement. The consolidated release workflow uses the default CI token.

### Fixed

-   **Orphaned model-server processes** surviving an unclean shutdown. Link now reliably reclaims only its own previous instance on the next start, and refuses to touch unrelated processes that may have reused the port.
-   **Inference telemetry going dark in production serving.** Telemetry is now captured through the serving proxy end to end, closing a gap where no telemetry was emitted when serving via the swap manager.
-   **Chats being attributed to the wrong model** when two models shared a server on the same port.

## Locai Link (Legacy Aventail) 1.0.9 · 2026-05-08

Adds deployment-progress reporting, broadens transport and installer options, and improves cross-platform temperature monitoring.

### Added

-   **Incremental deployment progress.** Model deployments now report `downloading`, `configuring`, and `completed` events with byte counts, throttled to 5% steps.
-   **More transport options.** The Zenoh client now accepts TLS root CA, username, and password settings; `tls_root_ca: "auto"` resolves to the bundled certificate store at runtime, so no certificate file needs to live on disk.
-   **Consistent installer flags.** The Linux/macOS and Windows CMD installers now honor `--branch` and `--repo-url` arguments, matching the PowerShell installer. Environment variables are still respected, with CLI flags taking precedence.
-   **Broader temperature monitoring.** Windows now reads CPU temperature without administrator rights via a performance counter (with an admin-only fallback), and macOS can optionally report temperature via the `osx-cpu-temp` Homebrew binary.
-   **End-to-end installer tests** across bash, PowerShell, and CMD.

### Changed

-   TLS is now applied based on the endpoint scheme rather than the connection mode, so peer-of-router setups also verify outbound TLS.
-   In pure client mode, the agent no longer provisions a local router binary it does not need.
-   Plugin install scripts return early and silently when a binary is already at the pinned version, removing repeated banner logs on every start.
-   The PowerShell installer now translates its idiomatic parameters into the underlying command arguments and propagates the real exit code, so installer crashes no longer report success.

### Removed

-   The Windows-only `wmi` dependency, whose fragile install and noisy teardown messages have been replaced by a direct PowerShell shell-out.

### Fixed

-   A Windows CMD installer bug where argument shifting inside a conditional block caused the installer to invoke Python incorrectly, fail silently, and still report success.

## Architecture

A modular, pipeline-based runtime. The **control plane** (lifecycle, configuration) is separated from the **data plane** (inference, telemetry) for performance and resilience.

```mermaid
flowchart TB

    %% =====================================================
    %% External Actors
    %% =====================================================
    User([User / CLI])
    Cloud([Aventail Control Plane])

    %% =====================================================
    %% Edge Device
    %% =====================================================
    subgraph Device["Edge Device"]
        direction TB

        %% -------------------------------------------------
        %% Entry
        %% -------------------------------------------------
        Entry["Entry Point<br/>link.main"]

        %% -------------------------------------------------
        %% Application Layer
        %% -------------------------------------------------
        subgraph App["Application Layer"]
            direction LR

            Runtime["Agent Runtime"]
            State["State Manager"]
            Onboard["Onboarding"]
        end

        %% -------------------------------------------------
        %% Infrastructure Layer
        %% -------------------------------------------------
        subgraph Infra["Infrastructure Layer"]
            direction LR

            Zenoh["Zenoh Client"]
            Service["Service Manager"]
            Provision["Provisioner"]
        end

        %% -------------------------------------------------
        %% Execution Layer
        %% -------------------------------------------------
        subgraph Exec["Execution Layer (Pipelines)"]
            direction TB

            Pipe["Pipeline Orchestrator"]

            Source(("Source"))
            Sink(("Sink"))

            %% ---------------------------------------------
            %% Plugins
            %% ---------------------------------------------
            subgraph Plugins["Optional Plugins"]
                direction LR

                LM["language_model"]
                AT["audio_transcriber"]
                IC["image_classifier"]
                AC["audio_classifier"]
                More["custom / other"]
            end
        end

        %% =================================================
        %% Internal Device Flow
        %% =================================================
        Entry --> App
        Entry -.-> Infra

        App --> Pipe
        Pipe --> Source
        Source --> Sink

        %% Dynamic Plugin Loading
        Source -.->|"Load active plugins"| Plugins
    end

    %% =====================================================
    %% External Communication
    %% =====================================================
    User --> Entry

    %% Data Plane
    Sink -->|"Telemetry / Inference Results"| Cloud

    %% Control Plane
    Runtime -->|"Logs / Status"| Cloud
    Onboard -->|"Registration / Activation"| Cloud

    %% =====================================================
    %% Plugin Optionality Styling
    %% =====================================================
    style LM stroke-dasharray: 5 5
    style AT stroke-dasharray: 5 5
    style IC stroke-dasharray: 5 5
    style AC stroke-dasharray: 5 5
    style More stroke-dasharray: 5 5,fill:#eff6ff

    %% =====================================================
    %% Modern Blue Theme
    %% =====================================================
    classDef layer fill:#eef4ff,stroke:#1f70e6,color:#0f172a,stroke-width:2px
    classDef core fill:#1f70e6,stroke:#1558b0,color:#ffffff,stroke-width:2px
    classDef cloud fill:#dbeafe,stroke:#1f70e6,color:#0f172a,stroke-width:2px
    classDef plugin fill:#f4f8ff,stroke:#5b9cff,color:#0f172a,stroke-width:1.5px

    class App,Infra,Exec layer
    class Entry,Pipe,Runtime,State,Onboard core
    class Cloud cloud
    class LM,AT,IC,AC,More plugin
```

### Changed

-   **Single-process model:** The previous two-process split (`manager.py` supervisor + `agent.py` worker) has been consolidated into a single `main.py`. OTA updates now use `os.execv()` to replace the process in place rather than relying on a parent supervisor loop — the PID is preserved across updates.
    
-   **Pipeline-based runtime:** The monolithic `agent.py` (1,496 lines), which handled command polling, inference dispatch, serving, and metrics, has been replaced by `AgentRuntime` + Pipeline threads. Each pipeline is a `Source → Sink` pair running on its own thread, composable from config.
    
-   **Component registry:** Components (HTTP sources, Zenoh sinks, system monitors, command handlers) self-register via a `@ComponentRegistry.register("name")` decorator and are instantiated from declarative config.
    
-   **Pydantic config models:** Ad-hoc JSON config handling has been replaced with typed `AgentConfig`, `PipelineConfig`, `TransportConfig`, and `GenericConfig` Pydantic models. Schema version is pinned at `2.1`.
    
-   **Session state persistence:** A new `StateManager` writes timestamped `configs/session_*.json` files for crash recovery. The agent automatically resumes the latest session on restart, and any running pipelines are re-started accordingly.
    

### Removed

-   `manager.py` (1,080 lines) subcommands have been folded into `main.py`.
-   `src/link/serving/` `LLMServer`, `WhisperServer`, and `BaseServer` moved into plugins.
-   `src/link/inference/` `dispatcher.py` and TFLite runners (`language_model_gguf.py`, `image_detection_cpy_tflite.py`, `audio_classification_yamnet_tflite.py`) replaced by plugin adapters.
-   `src/link/logger/` custom module replaced by `src/link/utils/logger.py` with structured async handlers.
-   `src/link/analytics.py` analytics now flow through the generic reporting handler system.
-   `src/link/components/buffers.py` unused `LocalBuffer` stub removed.

## Registration & Onboarding

Registration & Onboarding now supports **flexible identity resolution** and simplified device activation using keys. Authentication is updated to **JWT-based login with email/token**, and passwords are handled securely via prompt input.

### Added

-   **Four-tier identity resolution** in `main.py run` explicit `--config`, auto-resume of the latest session, just-in-time onboarding with `--registration-key`, or factory defaults.
-   `activate_device()` for re-activating existing devices using only `--device-id` and `--registration-key`.

### Changed

-   The `register_device()` function previously accepted `--username` and sent it in the request body. It now accepts `--email` (or a pre-obtained `--token`) and uses `login_and_get_token()` to obtain a JWT, which is sent as `Authorization: Bearer` on the `/devices/register-with-key` request matching the backend's expected auth flow.
-   Passwords are prompted securely via `getpass` when omitted from the command line.

## Installation

Installation is streamlined with **one-liner scripts** for Linux, macOS, and Windows, enabling quick setup across platforms. The new `main.py install` command automates the full flow clone, setup, register, and run in a single step. Setup is now managed via `main.py setup`, replacing the old manager-based approach.

### Added

-   **One-liner install scripts** for Linux/macOS (`install.sh`), Windows PowerShell (`install.ps1`), and Windows CMD (`install.cmd`). Each script bootstraps `uv`, detects local vs. remote `main.py`, and hands off to the new `install` subcommand.
-   **`Main.py install` subcommand** orchestrates the full flow clone/update repo → setup → register → run, in a single command.

### Changed

-   Setup is now handled via `main.py setup` (with `--dev` and `--tui` extras) instead of `manager.py setup --extras`.

## Plugins

The system now uses a **plugin-based architecture**, where each capability (LLM, audio, vision) is packaged as an independent, installable module. Core plugins like `language_model`, `audio_transcriber`, and classifiers enable flexible AI workloads with support for CUDA, ARM64, and optimized builds.

Enhancements like caching, improved loading, and smarter configuration make plugins more efficient, scalable, and easier to manage.

### Added

-   **Plugin architecture:** Plugins are standalone installable packages that register via the `locai.plugins` entry point. Each plugin has its own `pyproject.toml`, `adapter.py`, and `install.py`.
-   **`language_model`:** Plugin ports the LLM server logic (`llama-server` lifecycle). Pinned to `llama.cpp b8808`.
-   **`audio_transcriber`:** Plugin ports the Whisper server logic (`whisper-server` lifecycle). Pinned to `whisper.cpp v1.8.4`.
-   **`image_classifier`:** Plugin-vision inference via TFLite.
-   **`audio_classifier`:** Plugin-audio tagging via TFLite.
-   **CUDA build-from-source fallback:** On Linux when the CUDA toolkit (`nvcc`) is detected — enables `-DGGML_CUDA=ON` for optimal GPU performance.
-   **Tag-based caching:** Each plugin install uses a tag file to skip re-download/rebuild when already at the pinned version. Re-running `install.py` after an OTA update is lightweight.
-   **ARM64 Linux support:** For `llama.cpp` prebuilts (the old code was x64-only).
-   **macOS quarantine stripping:** `xattr -dr com.apple.quarantine` is applied after extraction to prevent Gatekeeper from blocking binaries.
-   **Symlink preservation:** When extracting tarballs versioned shared library names (e.g., `libmtmd.0.dylib`) now resolve correctly.

### Changed

-   Removed hardcoded `--chat-template chatml` the `language_model` plugin now only passes `--chat-template` when explicitly configured, allowing `llama.cpp` to auto-detect from the model's metadata.
-   Health-check timeout raised from `30s` to `120s` for plugin servers — large models on CPU can take longer to load.
-   `LD_LIBRARY_PATH` now walks subdirectories to locate `ggml*.so` / `whisper*.so` files, as CUDA shared libs may reside in nested folders.

## Configuration

Configuration is now fully declarative and flexible, supporting pipelines defined via config with dynamic source sink mapping. It introduces Zenoh as an alternative transport alongside HTTP, enabling more scalable communication. Logging and reporting are config-driven with template-based customization, allowing reusable and device-specific configurations.

### Added

-   **Zenoh transport** as an alternative to HTTP for the control plane and pipelines. Set `TransportConfig.type = "zenoh"` or `"http"`.
-   **Declarative pipelines in config** `pipelines: [{id, source, sink, active}]`. Sources and sinks are instantiated by name from the component registry.
-   **Config-driven logging and reporting handlers** `LoggingConfig.handlers` and `ReportingConfig.handlers` accept a list of typed handler configs (`console`, `http`, `zenoh`).
-   **Template substitution in handler args** `${identity.device_id}`, `{cid}`, and `{mid}` are resolved at runtime, allowing a single config to serve multiple devices.

## Pipelines

Pipelines now treat empty inputs as successful no-ops, preventing false failure warnings during idle polling. This improves stability by ensuring the pipeline loop handles inactive states correctly without unnecessary alerts.

### Changed

-   `AgentCommand` sink now returns `True` on empty input (previously returned `None`). The pipeline loop treats non-truthy sink results as failures and warns accordingly. Idle poll ticks (`http_poll` returning `[]` when no commands are pending) previously triggered a spurious `"Sink is returning False"` warning. An empty dispatch is now treated as a successful no-op.

## Transport, Logging & Reporting

Transport, Logging & Reporting now use a **structured, async system** with `LinkReporter` and non-blocking handlers for HTTP and Zenoh. Error handling is improved with **retry logic, clear classification (retryable vs fatal), and standardized log formats**, ensuring reliable and scalable observability.

### Added

-   **`LinkReporter`** a custom logger class exposing `report_lifecycle(status)`, `report_command(cmd_id, status, output)`, and `report_model(...)` for structured status reporting.
    
-   **`AsyncHandler` base class** with a worker thread and queue — all handlers are non-blocking. Subclasses include:
    
    -   `AsyncHTTPHandler` routes events to HTTP endpoints via template lookup (`PUT` for `lifecycle_status`, `POST` for everything else).
    -   `AsyncZenohHandler` publishes to Zenoh topics.
-   **`HttpError` exception class** with `status`, `reason`, and `retryable` fields allows callers to distinguish transient failures (timeout, 5xx, connection refused) from non-retryable ones (401, 403, 404). The previous `HttpClient` silently swallowed all errors as `None`/`False`.
    
-   **`agent_version` in lifecycle status payload** `report_lifecycle()` now includes the agent's installed version (read from `importlib.metadata.version("locai-link")`), satisfying the backend's `AgentStatusUpdate.agent_version` semver requirement.
    

### Changed

-   `HttpClient.get()` / `post()` now classifies errors: timeouts, 5xx responses, and connection errors return `None`/`False` (retryable); 4xx auth/client errors raise `HttpError`. `HttpPoller` and `HttpPublisher` catch `HttpError` and log with actionable context before re-raising.
    
-   HTTP log payload shape now matches the backend's `LogCreate` schema: `{message, severity, category}`. Severity is lowercase (`DEBUG` maps to `"info"`); category defaults to `"other"` and can be set via `logger.info("msg", extra={"category": "security"})`. This replaces the previous `{timestamp, level, message, logger}` shape.
    
-   `AsyncHTTPHandler` now retries on timeouts, connection errors, and 5xx responses with exponential backoff (`0.5s`, `1.5s`, capped at 2 retries). 4xx responses remain fatal and are not retried. Timeout is configurable per handler via `args.timeout` (default `10s`), split into `(connect=3s, read=timeout)` fast-fail on unreachable hosts, tolerant on slow responses.
    
-   `HttpClient.get()` timeout demoted from `WARNING` to `DEBUG` polling is self-healing (the next tick retries), and a flaky network was generating excessive console noise at warning level.
    

## Service Deployment

Service Deployment now includes a **cross-platform service manager**, enabling the agent to run as a native service on Linux, macOS, and Windows. It supports **production mode execution and graceful shutdown**, simplifying deployment and lifecycle management.

### Added

-   **Cross-platform service manager** (`src/link/infra/service.py`) — the `ServiceManager` factory selects the appropriate backend:
    -   **Linux** → systemd user service at `~/.config/systemd/user/locai-link.service`
    -   **macOS** → LaunchAgent plist at `~/Library/LaunchAgents/io.locai.locai-link.plist`
    -   **Windows** → Windows Service via `sc.exe` (requires admin privileges)
-   `main.py run --prod` installs and starts the agent as an OS service.
-   `main.py stop` gracefully stops the agent (and `zenohd`, if installed).

## OTA Updates

OTA Updates now support **seamless, zero-downtime upgrades** using in-place restarts via `os.execv()`, preserving the running process. Updates are **branch-aware, stash-safe**, and intelligently refresh only the plugins actually in use.

The new updater system ensures **clean pipeline shutdown, automated installs, and efficient version management** without needing an external supervisor.

### Added

-   **`UPDATE_AGENT` command** handled by `AgentRuntime`: On receipt reports completion, cleanly shuts down pipelines, and signals `main.py` to update.
-   **`src/link/app/updater.py`**: Provides `pull_and_update()` (`git fetch/stash/pull/pop` + `uv pip install -e .`), `reinstall_plugin_binaries()` (config-driven; see Changed below), `get_current_branch()`, and `get_local_version()`.
-   **In-place restart via `os.execv()`**: The process image is replaced while preserving the PID. `systemd`/`launchd` see a continuously running process with no downtime gap.
-   **Branch-aware updates**: Dev branches pull from `origin/<current-branch>`, not `origin/main`.
-   **Stash-safe updates**: Dirty working trees are stashed and reapplied around the pull.
-   **`-DGGML_NATIVE=OFF` on macOS:** For both `llama.cpp` and `whisper.cpp` builds avoids `ggml`'s `-mcpu=native` fallback, which AppleClang rejects on arm64. Metal + Accelerate handle performance-critical paths on Apple Silicon with no throughput regression. Linux and Windows are unchanged.
-   **Silenced detached-HEAD git advisory**: Tagged clones now pass `-c advice.detachedHead=false` inline to suppress cosmetic noise from OTA build logs.

### Changed

-   `Reinstall_plugin_binaries()` is now config-driven. Previously, every plugin under `plugins/` had its `install.py` re-run on every OTA — so a device running only `language_model` would still attempt to build `whisper.cpp`, TFLite, etc. The updater now walks the active
-   `AgentConfig.pipelines[*].source/sink.type`, maps each type to its owning plugin via that plugin's `[project.entry-points."locai.plugins"]` in `pyproject.toml`, and only refreshes plugins that are actually referenced. Unused plugins are silently skipped.

### Removed

-   The old `EXIT_CODE_UPDATE = 42` + subprocess-loop supervisor in `manager.py`. The new architecture requires no external supervisor.

## Testing & CI

Testing & CI now includes **77+ unit tests and full integration tests** across plugins, ensuring reliability of core features and model workflows. It also introduces a **multi-OS CI pipeline with version checks**, improving code quality, consistency, and release control.

### Added

-   **77 unit tests**: Covering HTTP client error classification, onboarding auth flow, state manager version handling, OTA updater logic, runtime command handling, service manager across all three oses, zenoh router, config loading, and platform detection.
-   **`ci` pytest marker**:For tests requiring external binaries or network access skipped locally by default, enabled in CI via `-m ""` override.
-   **Integration tests**:In each plugin directory download real models, spawn real server binaries, and verify full transcription/completion flows.
-   **Multi-OS CI matrix**: (Ubuntu, macOS, Windows) for both unit and integration jobs.
-   **Version-bump gate on PRs**: Fails if the `pyproject.toml` version has not been incremented.
-   `audio_transcriber` wired into the integration-test job alongside the other three plugins.

## CLI Reference

The CLI has been simplified by replacing `manager.py` with a unified `main.py`, consolidating all commands into a single entry point. It now supports **setup, install, run, stop, and plugin management**, making the workflow cleaner and easier to use.

**Before (old `manager.py`)**

```bash
manager.py install        # Full installation wizard
manager.py setup          # Configure venv and deps
manager.py reset          # Clean up artifacts
manager.py register       # Register device
manager.py activate       # Activate a pre-registered device
manager.py update         # Pull latest code
manager.py run            # Run agent (supervisor loop)
manager.py install-deps   # Install llama/whisper server binaries
```

**After (new `main.py`)**

```bash
main.py setup             # Install Python dependencies (--dev, --tui)
main.py install           # Full installation wizard
main.py run               # Run agent (in-process, handles OTA via execv)
main.py stop              # Stop all services
main.py reset             # Clean up environment (--hard)
main.py install-plugin    # Install a plugin by name
main.py tui               # Launch text UI (optional)
```

## Breaking Changes

Breaking Changes introduce updates like **`--email` replacing `--username`, a new config schema (v2.1), and removal of `manager.py` in favor of `main.py`**. Plugins must now be installed separately, and logging/reporting formats have been standardized through the new **LinkReporter system**.

info

Review these carefully before migrating.

-   Registration argument is `--email` (not `--username`).
-   Config schema version is `2.1`. Earlier state files are rejected and will not be loaded.
-   `manager.py` no longer exists; all commands must be run via `main.py`.
-   Plugins must be installed separately as editable packages (`uv pip install -e "plugins/<name>"`). They are not bundled with the core agent.
-   Agent status, command status, and model status payloads now flow through the new `LinkReporter` handler system direct `requests.post` calls to `/agent/{device_id}/status` have been removed.
-   HTTP log payload shape changed from `{timestamp, level, message, logger}` to `{message, severity, category}` to match the backend's `LogCreate` schema. Backends consuming `/logs` must accept the new shape; the old field names are no longer emitted.

You can refer to the [locai-link](https://github.com/locai-co-uk/locai-link) Github repositary.
