Loc.ai:Link Changelog
All notable changes migrating from locai-link-old to locai-link-new.
Loc.ai:Link has evolved into a single-process, pipeline-based architecture that replaces the older monolithic system, improving efficiency and maintainability. It now features a plugin-driven design with typed configuration, enabling flexible integration of AI models and components. With OTA updates, session recovery, and service deployment, it is a fully modular, production-ready agent framework.
Architecture
A modular, pipeline-based runtime. The control plane (lifecycle, configuration) is separated from the data plane (inference, telemetry) for performance and resilience.
Changed
-
Single-process model: The previous two-process split (
manager.pysupervisor +agent.pyworker) has been consolidated into a singlemain.py. OTA updates now useos.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 byAgentRuntime+ Pipeline threads. Each pipeline is aSource → Sinkpair 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, andGenericConfigPydantic models. Schema version is pinned at2.1. -
Session state persistence: A new
StateManagerwrites timestampedconfigs/session_*.jsonfiles 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 intomain.py.src/link/serving/LLMServer,WhisperServer, andBaseServermoved into plugins.src/link/inference/dispatcher.pyand 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 bysrc/link/utils/logger.pywith structured async handlers.src/link/analytics.pyanalytics now flow through the generic reporting handler system.src/link/components/buffers.pyunusedLocalBufferstub 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 runexplicit--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-idand--registration-key.
Changed
- The
register_device()function previously accepted--usernameand sent it in the request body. It now accepts--email(or a pre-obtained--token) and useslogin_and_get_token()to obtain a JWT, which is sent asAuthorization: Beareron the/devices/register-with-keyrequest matching the backend's expected auth flow. - Passwords are prompted securely via
getpasswhen 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 bootstrapsuv, detects local vs. remotemain.py, and hands off to the newinstallsubcommand. Main.py installsubcommand orchestrates the full flow clone/update repo → setup → register → run, in a single command.
Changed
- Setup is now handled via
main.py setup(with--devand--tuiextras) instead ofmanager.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.pluginsentry point. Each plugin has its ownpyproject.toml,adapter.py, andinstall.py. language_model: Plugin ports the LLM server logic (llama-serverlifecycle). Pinned tollama.cpp b8808.audio_transcriber: Plugin ports the Whisper server logic (whisper-serverlifecycle). Pinned towhisper.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=ONfor 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.pyafter an OTA update is lightweight. - ARM64 Linux support: For
llama.cppprebuilts (the old code was x64-only). - macOS quarantine stripping:
xattr -dr com.apple.quarantineis 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 chatmlthelanguage_modelplugin now only passes--chat-templatewhen explicitly configured, allowingllama.cppto auto-detect from the model's metadata. - Health-check timeout raised from
30sto120sfor plugin servers — large models on CPU can take longer to load. LD_LIBRARY_PATHnow walks subdirectories to locateggml*.so/whisper*.sofiles, 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.handlersandReportingConfig.handlersaccept 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
AgentCommandsink now returnsTrueon empty input (previously returnedNone). The pipeline loop treats non-truthy sink results as failures and warns accordingly. Idle poll ticks (http_pollreturning[]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
-
LinkReportera custom logger class exposingreport_lifecycle(status),report_command(cmd_id, status, output), andreport_model(...)for structured status reporting. -
AsyncHandlerbase class with a worker thread and queue — all handlers are non-blocking. Subclasses include:AsyncHTTPHandlerroutes events to HTTP endpoints via template lookup (PUTforlifecycle_status,POSTfor everything else).AsyncZenohHandlerpublishes to Zenoh topics.
-
HttpErrorexception class withstatus,reason, andretryablefields allows callers to distinguish transient failures (timeout, 5xx, connection refused) from non-retryable ones (401, 403, 404). The previousHttpClientsilently swallowed all errors asNone/False. -
agent_versionin lifecycle status payloadreport_lifecycle()now includes the agent's installed version (read fromimportlib.metadata.version("locai-link")), satisfying the backend'sAgentStatusUpdate.agent_versionsemver requirement.
Changed
-
HttpClient.get()/post()now classifies errors: timeouts, 5xx responses, and connection errors returnNone/False(retryable); 4xx auth/client errors raiseHttpError.HttpPollerandHttpPublishercatchHttpErrorand log with actionable context before re-raising. -
HTTP log payload shape now matches the backend's
LogCreateschema:{message, severity, category}. Severity is lowercase (DEBUGmaps to"info"); category defaults to"other"and can be set vialogger.info("msg", extra={"category": "security"}). This replaces the previous{timestamp, level, message, logger}shape. -
AsyncHTTPHandlernow 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 viaargs.timeout(default10s), split into(connect=3s, read=timeout)fast-fail on unreachable hosts, tolerant on slow responses. -
HttpClient.get()timeout demoted fromWARNINGtoDEBUGpolling 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) — theServiceManagerfactory 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)
- Linux → systemd user service at
main.py run --prodinstalls and starts the agent as an OS service.main.py stopgracefully stops the agent (andzenohd, 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_AGENTcommand handled byAgentRuntime: On receipt reports completion, cleanly shuts down pipelines, and signalsmain.pyto update.src/link/app/updater.py: Providespull_and_update()(git fetch/stash/pull/pop+uv pip install -e .),reinstall_plugin_binaries()(config-driven; see Changed below),get_current_branch(), andget_local_version().- In-place restart via
os.execv(): The process image is replaced while preserving the PID.systemd/launchdsee a continuously running process with no downtime gap. - Branch-aware updates: Dev branches pull from
origin/<current-branch>, notorigin/main. - Stash-safe updates: Dirty working trees are stashed and reapplied around the pull.
-DGGML_NATIVE=OFFon macOS: For bothllama.cppandwhisper.cppbuilds avoidsggml's-mcpu=nativefallback, 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=falseinline to suppress cosmetic noise from OTA build logs.
Changed
Reinstall_plugin_binaries()is now config-driven. Previously, every plugin underplugins/had itsinstall.pyre-run on every OTA — so a device running onlylanguage_modelwould still attempt to buildwhisper.cpp, TFLite, etc. The updater now walks the activeAgentConfig.pipelines[*].source/sink.type, maps each type to its owning plugin via that plugin's[project.entry-points."locai.plugins"]inpyproject.toml, and only refreshes plugins that are actually referenced. Unused plugins are silently skipped.
Removed
- The old
EXIT_CODE_UPDATE = 42+ subprocess-loop supervisor inmanager.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.
cipytest 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.tomlversion has not been incremented. audio_transcriberwired 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)
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)
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.
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.pyno longer exists; all commands must be run viamain.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
LinkReporterhandler system directrequests.postcalls to/agent/{device_id}/statushave been removed. - HTTP log payload shape changed from
{timestamp, level, message, logger}to{message, severity, category}to match the backend'sLogCreateschema. Backends consuming/logsmust accept the new shape; the old field names are no longer emitted.
You can refer to the locai-link Github repositary.