---
title: "Version 1.2"
description: "All releases in the Koios 1.2 line: the v1.2.0 feature release and the v1.2.1 and v1.2.2 hotfixes"
source_url: https://ai-ops.com/docs/release-notes/v1-2
---

# Koios 1.2

<ReleaseTable
  releases={[
    { release: "v1.2.2", anchor: "#v122", date: "September 20, 2026", summary: "Component stacks, the installer, and the pre-upgrade backup" },
    { release: "v1.2.1", anchor: "#v121", date: "September 19, 2026", summary: "Installation guide, prediction history, device fault reporting, service logs, license reporting, record pickers, component packaging, and the component canvas" },
    { release: "v1.2.0", date: "September 16, 2026", summary: "The feature release — indexed below", isGa: true },
  ]}
/>

**Components**

- [Components are built in one workspace](#components-are-built-in-one-workspace): one three-pane view, and saving an environment that is running keeps its timers and buffers
- [Environments file into folders](#environments-file-into-folders): nested up to five levels, re-filed by dragging
- [Components run in isolated package stacks](#components-run-in-isolated-package-stacks): a component can use a library version Koios does not ship
- [A stack is checked before any component uses it](#a-stack-is-checked-before-any-component-uses-it): a build that would break the component runtime never gets a process
- [Numbers convert between component pins](#numbers-convert-between-component-pins): whole numbers, decimals and true/false now connect in any direction
- [Backups rebuild stacks](#backups-rebuild-stacks): built trees are left out of the archive and rebuilt on restore
- [Components can solve an optimization problem](#components-can-solve-an-optimization-problem): size a blend, schedule a run, or allocate capacity
- [A component can ask for a file](#a-component-can-ask-for-a-file): a trained model or lookup table per instance, versioned and revertible

**Models**

- [A model can take differently-sampled inputs](#a-model-can-take-differently-sampled-inputs): each input with its own window length, sample rate and interpolation
- [Invalid input configurations stop the model and say why](#invalid-input-configurations-stop-the-model-and-say-why): checked when the model loads, rather than discovered mid-scan

**Devices and protocols**

- [Koios reaches serial and GPIO hardware](#koios-reaches-serial-and-gpio-hardware): without a world-writable device node or a privileged container
- [A Modbus device says how much it will answer at once](#a-modbus-device-says-how-much-it-will-answer-at-once): Max Registers Per Read, set per device
- [An EDS file attaches by drag and drop](#an-eds-file-attaches-by-drag-and-drop): dropped onto the device, and attachable while creating one
- [OPC-UA runs on the client library's first full release](#opc-ua-runs-on-the-client-librarys-first-full-release): replacing the pre-release build earlier versions shipped
- [An OPC-UA tag reports what the server said about each reading](#an-opc-ua-tag-reports-what-the-server-said-about-each-reading): an uncertain reading keeps flowing with a warning, and a bad one says why

**Lists and trends**

- [Every list filters, sorts, pages and counts on the server](#every-list-filters-sorts-pages-and-counts-on-the-server): rebuilt tables, and filters that silently did nothing now work
- [A CSV import is reviewed as a full grid](#a-csv-import-is-reviewed-as-a-full-grid): every row in the file, and only the columns it changes
- [An empty trend offers the first step](#an-empty-trend-offers-the-first-step): it asks for the first tag instead of showing an empty grid
- [A trend export downloads as it is produced](#a-trend-export-downloads-as-it-is-produced): it streams straight to your browser, and larger ranges complete

**Security and administration**

- [Certificates are managed in one place](#certificates-are-managed-in-one-place): install your own web interface certificate, with OPC-UA certificates beside it
- [Every accountable action is recorded](#every-accountable-action-is-recorded): an append-only trail of who did what, with its own retention and export
- [Roles can grant every permission Koios enforces](#roles-can-grant-every-permission-koios-enforces): new permissions, and the ones the role editor was leaving out
- [A backup already on the server restores in place](#a-backup-already-on-the-server-restores-in-place): no download-and-upload round trip for an archive that is already there

**Diagnostics and performance**

- [Logs keep days of history and say more when something fails](#logs-keep-days-of-history-and-say-more-when-something-fails): repeated messages recorded once, credentials masked, silent failures reported
- [Pages load faster, and an outage says so honestly](#pages-load-faster-and-an-outage-says-so-honestly): less to download, faster status filtering, one clear unreachable banner
- [The public docs keep a copy for each release](#the-public-docs-keep-a-copy-for-each-release): the pages you read match the version you run

**For integrators**

- [API changes](#api-changes): removed and changed operations for integrations

**Fixes and upgrading**

- [Fixes](#fixes): corrections across lists and filters, trends, device reads and writes, logging, and restore
- [Upgrade notes](#upgrade-notes): what to review before upgrading, including models that stop and roles that need permissions granted by hand

---

The biggest change in 1.2 is how you build and run components: a single workspace for editing environments, folders for organizing them, and isolated package stacks so a component can use a library version Koios does not ship.

---

## Components are built in one workspace

Component environments now open in one three-pane workspace: an environment rail on the left, the canvas in the middle, and a Library/Inspector panel on the right, with a dockable log pane beneath the canvas. Instance and connector configuration appears in the Inspector beside the canvas instead of a drawer covering it, and the log pane follows whatever is selected on the canvas. Pane sizes are remembered per user.

Saving an environment that is currently running now asks for confirmation and lists exactly what changed — components, connectors, and wires added, modified, or removed. Saved changes apply in place within one scan cycle, so component timers and buffers are preserved rather than reset.

> [!WARNING] The environment list and detail pages are gone
> Environments are now opened and managed from the workspace rail. Overview, Execution, Configuration, Parameters, and Cross References open from a settings dialog on the environment bar. The environment list table — and with it the bulk enable, disable, and delete actions for environments — no longer exists. Enable, disable, and delete an environment from the workspace instead.

See [Component Environments](https://ai-ops.com/docs/components/environments.md) and [The Canvas](https://ai-ops.com/docs/components/canvas.md).

---

## Environments file into folders

Environments can be filed into nested folders, up to five levels deep. Drag an environment or a folder onto a folder to re-file it, onto another environment to file it alongside, or into the blank space below the tree to lift it back to the top level. Folder names must be unique among their siblings, folders sort before environments, expansion is remembered as you navigate, and searching flattens the tree to matching environments with their folder path.

Deleting a folder lifts everything inside it up to the parent rather than deleting it, and moving an environment between folders is purely organizational — nothing running restarts or reloads.

---

## Components run in isolated package stacks

A **stack** is a named, isolated set of Python packages built from wheels you upload. Attach a component environment to one and its components run against exactly those library versions, independently of what the platform ships and of every other stack.

- **Stack pages** live under Components, with a list and a detail page showing live build status, the build log, installed size per package, how many environments are attached, worker memory, and a searchable inventory of the packages the platform already provides.
- **Adding wheels**: upload them one at a time, or import a prepared `.kps` bundle built on a development machine.
- **Uploads are checked up front**: a wheel built for the wrong architecture, a wheel that would replace a package Koios itself runs on, or a duplicate filename is refused at upload with an explanation. A wheel that simply matches the platform's own version is shown as "Same as platform" rather than flagged.
- **Its own process**: each stack in use runs its own worker process, and environments attached to no stack share one process of their own. Component code no longer runs inside the component engine service, so a process taken down by a faulty component only affects the environments in that process. Those environments are marked failed with the reason, the process restarts automatically with a growing delay up to a minute, and repeated crashes collapse into a single crash-loop event rather than filling the event log.
- **Rebuilds keep components running**: the existing build keeps serving until the new one is ready, and a rebuild that fails leaves components running on the previous version. Only a build that actually replaces installed packages recycles the process, so renaming a stack or editing its description no longer restarts live components.

> [!TIP] Every stack in use costs a process
> A stack with an enabled environment attached holds a worker process for as long as it is in use, and each process keeps a small pool of connections to the configuration database. Reuse a stack across environments rather than creating one per environment, and budget memory, connections, and disk when planning a server that will host many. See [System Requirements](https://ai-ops.com/docs/installation/system-requirements.md).

---

## A stack is checked before any component uses it

A stack can install cleanly and still contain a package that replaces something the component runtime needs for itself. After each build, Koios boots the component runtime on that stack's own interpreter and exercises the component API before any component is allowed to use it. A stack that fails is marked **Incompatible**, never gets a process, and the message names the file that broke it. The previously working build keeps running until the offending wheel is removed, and the stack page offers a one-click remove-and-rebuild when a specific wheel has been blamed.

See [Component Stacks](https://ai-ops.com/docs/components/stacks.md) for the full guide, including the status table and troubleshooting.

---

## Numbers convert between component pins

Whole numbers, decimals, and true/false values now connect in any direction on the canvas, and two of those pairs convert the value on the way in: a decimal arriving at a whole-number pin rounds with halves going up (`2.5` arrives as `3`), and a number arriving at a true/false pin becomes true when it is non-zero. The converted value is both what the component runs on and what the canvas shows. Earlier documentation stated these connections were rejected — see the wire validation table in [The Canvas](https://ai-ops.com/docs/components/canvas.md).

---

## Backups rebuild stacks

Built stack trees are excluded from backups and rebuilt automatically from the uploaded wheels the backup does contain. This keeps archive size down and avoids restoring an environment built for a different machine. The rebuild runs as part of the restore, before services come back up.

Stacks are stored alongside your other uploaded files, so **no new storage volume is needed** on an existing install. See [Backup & Restore](https://ai-ops.com/docs/system/backup.md) and [Backing Up Volumes](https://ai-ops.com/docs/installation/backup-restore.md).

---

## Components can solve an optimization problem

Koios now includes a linear and mixed-integer programming solver, so a
component can size a blend, schedule a run, or allocate capacity rather than
only transform values. Components reach it through `pyomo`, which is not
included and is added the way any other package is: as a library dependency or
in a stack. See [Component Builder SDK](https://ai-ops.com/docs/python-packages/component-builder.md).

---

## A component can ask for a file

Components can now ask for a **file** instead of just a value — a trained
model, a lookup table, a calibration curve. A component that declares one gets
an upload area on its Configuration tab in the Inspector, and the file you
upload belongs to that instance: two instances of the same component can run
different models.

The upload area states the accepted file types and the size limit up front, so
you are not discovering the rule by being refused. Both are set by the
component and checked when the file arrives, not only in the browser. Koios
never runs an uploaded file — it stores the bytes and hands them to the
component that asked for them.

**Uploads are versioned rather than overwritten.** Every file you upload to a
field is kept and one is active. Version history shows them all with who
uploaded each and when, and lets you download any version, make an older one
active again, or delete one. Deleting the active version promotes the most
recent remaining upload, so a component that requires a file does not lose it
while you prune history.

Replacing or reverting a file takes effect within one scan cycle. The component
reloads it in place — the environment does not restart, and timers and buffers
are preserved.

A component decides whether its file is required. If it is and none has been
uploaded, the instance reports an error naming the field rather than starting
and failing obscurely. If it is optional, the component runs without one.

Upload is unavailable on an instance you have just placed and not yet saved,
because there is nothing on the server to attach a file to yet — save the
environment first. Files are removed along with the instance that owns them,
and along with the component library if you delete that.

See [Building Components](https://ai-ops.com/docs/components/building-components.md) for declaring
one, and [The Canvas](https://ai-ops.com/docs/components/canvas.md) for using it.

---

## A model can take differently-sampled inputs

A model file can now declare a **structured input space**: one flat input vector carved into named inputs, each with its own history window length, sample rate, and interpolation method. A model trained on several differently-sampled signals no longer has to force them all into one window — a flow reading sampled every second and a temperature sampled every hour can feed the same model.

**Uploading a model file** now offers a Shape step — Flat, Time-series, Structured, or Multi-input — pre-selected from what was detected in the file. For structured models there is a visual builder for carving the flat vector into named slices, with live slot ranges and a check that the slices exactly tile the model's input dimension. A model file gains a **Space** tab showing the resulting structure, and reports its structured shape and flattened dimension.

**Bindings** for a structured model group by input. Each binding gets its own sample rate, window length, and interpolation method in place of the model-wide setting, and reports the effective sample rate, window size, and lookback actually in use — for example "12 samples × 1 hr", with the full window detail in the tooltip.

At scan time Koios resamples every input on its own cadence against a single shared reference instant and lays the windows end to end into the vector the model was trained on. History depth, enable-time warm-up backfill, prediction-history retention, and the staleness and depth checks are all sized per input, so a fast input can no longer gate a slow one.

> [!WARNING] Limits in this release
> - **Memory Only history is not available** for a model with a structured input space. Turn it off, or use a flat or time-series model. Flat and time-series models keep working with Memory Only exactly as before.
> - **PCHIP is the only interpolation applied.** An input can carry another selection, but this release resamples every input with PCHIP and records a warning in the model log when the configuration loads. Plan for PCHIP behavior when choosing window lengths and sample rates.
> - **Prediction history retention grows** for models whose inputs use different window lengths, because retention is now sized from the longest input window rather than one rate multiplied by a depth. Expect a modest increase in time-series storage for those models.

---

## Invalid input configurations stop the model and say why

Koios now checks a model's input configuration when it loads instead of discovering the problem mid-scan. If the configuration cannot produce a valid input — the number of input bindings does not match what the model file expects, a structured model's declared window lengths disagree with the file, a structured model sits on a non-flat file, or an input has a zero or negative window length or sample rate — the model is left **Stopped** with "Model configuration is invalid" and a detail naming the exact problem. Its bindings are marked stopped too, and it is skipped by the scheduler and by any scan group it belongs to. Pressing Start no longer briefly shows Running on a model that will never scan. Correcting the configuration clears the error on the next reload.

> [!CAUTION] Duplicate input binding order is now rejected
> Two input bindings on the same model can no longer share a binding order. Input columns are keyed by that order, so a duplicate silently merged two sensors into one column and the model ran on a quietly wrong input. Models configured this way will stop after upgrading. Binding order is assigned by Koios from the model file rather than chosen on the Bindings page, so renumbering is a binding CSV export, edit, and import — see [Model Configuration Errors](https://ai-ops.com/docs/models/troubleshoot-configuration-errors.md).

**Double-precision model files now run.** Koios feeds each file the numeric type its graph declares rather than always sending single-precision values, so files exported with double-precision inputs no longer fail at inference with a type mismatch. Files with quantized integer inputs are still rejected loudly rather than having their normalized values silently truncated.

See [Model Configuration Errors](https://ai-ops.com/docs/models/troubleshoot-configuration-errors.md), [Model Inference Requirements](https://ai-ops.com/docs/models/inference-requirements.md), [Managing Model Files](https://ai-ops.com/docs/models/model-files.md), and [Assigning Bindings](https://ai-ops.com/docs/models/assigning-bindings.md).

---

## Koios reaches serial and GPIO hardware

Koios can now reach serial, GPIO, I2C, and SPI hardware attached to the host without a world-writable device node or a privileged container.

Expose a device node with `--device`, and Koios grants its services access through the node's owning group. Standard serial devices (the `dialout` group) work with no configuration. For other peripherals, the new `KOIOS_EXTRA_GROUPS` variable takes a comma-separated list of group names and/or numeric group IDs. Numeric IDs are the reliable choice, because a device's group ID varies between hosts. Root (group ID `0`) is refused, and unresolvable entries are skipped rather than blocking startup.

See [Environment Variables](https://ai-ops.com/docs/installation/environment-variables.md) for `KOIOS_EXTRA_GROUPS` and the setup steps.

---

## A Modbus device says how much it will answer at once

Modbus TCP devices gain a per-device **Max Registers Per Read** setting (1–125, default 125) that caps the largest contiguous block Koios requests in a single read. Lower it for gateways and older controllers that reject large block reads even though each register reads fine individually. Koios splits an oversized request into several smaller ones; the values returned are unchanged, at the cost of more requests per scan. The register browser's Count box clamps to the device's cap, so it can no longer offer a scan the device will reject, and it reports the device's own limit instead of always quoting 125.

> [!WARNING] Reads now span up to the configured value
> Previously a second, hidden limit capped every read at 50 registers, so 125, 100, and 64 all behaved identically. Max Registers Per Read is now the only limit on a read. Devices that accept large reads make fewer round trips per scan. A device whose real limit sits between 50 and 125 was previously protected by the hidden cap and may now need Max Registers Per Read lowered to its published limit.

See [Creating a Modbus TCP Device](https://ai-ops.com/docs/devices/creating-modbus-tcp.md) and [Device Parameters](https://ai-ops.com/docs/devices/device-parameters.md).

---

## An EDS file attaches by drag and drop

A Generic CIP device's EDS file can now be dragged onto the page instead of picked through a file browser, and it can be attached while the device is being created rather than only afterwards. Replacing a file already attached works the same way, and a file that is not an EDS is refused as you drag it rather than after you pick it.

A file dropped on an existing device uploads straight away. One attached while creating a device uploads once the device itself has been created; if that upload does not complete, the device is still created and the file can be added from its configuration afterwards.

See [Creating an EtherNet/IP Device](https://ai-ops.com/docs/devices/creating-ethernet-ip.md).

---

## OPC-UA runs on the client library's first full release

OPC-UA connectivity — browsing servers, testing connections, and reading tag values — now runs on the first general-availability release of the underlying client library, replacing the pre-release build earlier versions shipped. The slow-server connection handling and certificate fixes introduced in v1.1.4 are now standard behavior in the library itself, so servers that connect today continue to connect the same way.

---

## An OPC-UA tag reports what the server said about each reading

An OPC-UA tag now reports what the server said about each reading, and a failed reading says whether the server reported bad data or the read itself failed.

- **An uncertain reading keeps flowing and now shows a warning.** When the server marks a reading uncertain, the tag keeps running with the value, as before, and now shows a warning naming the server's status and, when the server reports one, the low or high limit the value has reached. The tag's quality is the full status name, such as `UncertainEngineeringUnitsExceeded`, where a limit previously shortened it to `Uncertain`.
- **A bad reading fails and now says why.** When the server reports the data itself bad (a sensor or device failure, a point out of service, a value out of range, lost communication between the server and its own data source, or a configuration problem on the server or its data source), the tag fails with error code `107`. Any other bad status, such as a node that does not exist or a read the server does not permit, fails with `106` as before. The error detail gives the full status name, its low or high limit, and the status code. Previously, a status carrying a low or high limit showed only as an unknown status.
- **An empty value reports its own code.** A reading the server marks good or uncertain but sends without a value fails with `105` instead of `106`.
- **Testing a tag agrees with collection.** **Test** on an OPC-UA tag's Configuration tab now passes an uncertain reading and names its status, and fails a bad reading with the same description the tag reports while collecting.

> [!WARNING] Anything that reads a tag
> Expressions, components, integrations, and tag mappings that read an OPC-UA tag's error code, including a mapping that writes it to a device output, now see `107` or `105` for readings that reported `106` before. Review any logic that compares against `106`.

> [!NOTE] An over-range reading the server reports as bad still fails
> A bad status carries no usable value, so a server that reports an over-range reading as bad still fails the tag, now with code `107` and the out-of-range status in the error detail. Many servers and control systems can be configured to report over-range as uncertain instead, which keeps the reading flowing with a warning.

See [Bad, Missing, or Frozen Tag Values](https://ai-ops.com/docs/troubleshoot/tag-values.md) and [Creating an OPC-UA Tag](https://ai-ops.com/docs/tags/creating-opc-ua.md#read-status-and-quality).

---

## Every list filters, sorts, pages and counts on the server

Every list in Koios moved onto a rebuilt table, and the filters behind them were fixed end to end.

- **Filters open from the column header** in a popover instead of a permanent filter row, with the condition shown as a labelled dropdown and range and date bounds stacked and labelled.
- **A filter summary** in the toolbar names everything currently filtering in plain language, with **Clear all**.
- **Columns drag to reorder** from anywhere in the header, and empty and loading states look the same on every list.
- **Everything filters, sorts, pages, and counts on the server.** The Devices, Users, and API Clients lists previously fetched only the first 200 rows and worked on those, so filters matched an incomplete set, paging stopped at 200, and "Select all" reported the wrong number. All three now behave like every other list.
- **Filters that silently did nothing now work**, including Status, Depth, Inputs, Outputs, and Scan Group on Models; Active Device Status on Device Sets; Device Set on Tags; Name on Trends; and Description on Devices. The Scan Groups list's Status column is now an **Enabled** column that both filters and sorts.
- **More columns are filterable and sortable**: created and updated dates across Devices, Tags, Models, Scan Groups, Device Sets, Trends, and Component Environments; scan rate on Devices, Scan Groups, and Component Environments; device set on Tags; scan group name, input and output count, and input depth on Models. Users filter and search on displayed full name and on role, including the derived Superuser and View Only roles, and API Clients filter on client id and last-used date.
- **Live status filtering** on the Devices and Models lists, the way Tags already had it, applied on the server so it works correctly across pages — and noticeably faster on large systems.
- **Scan Rate filters understand units.** Scan Rate columns show sub-second rates in milliseconds while the filter compared seconds, so typing `500` matched nothing. There is now a unit picker: choose msec, type 500, get 0.5 s. The Models list also shows, sorts, and filters the rate actually in effect, including one inherited from a scan group.
- **Date range filters** work on every list, include the whole of the end day, and read the dates in your local time zone rather than UTC.
- **Filter conditions are remembered.** On lists that let you pick a condition (contains, starts with, is empty, and so on), the condition is now saved alongside the value, and "is empty" / "is not empty" actually filter.

See [Devices](https://ai-ops.com/docs/devices/introduction.md), [Models](https://ai-ops.com/docs/models/introduction.md), [Users](https://ai-ops.com/docs/system/users.md), and [API Clients](https://ai-ops.com/docs/system/endpoints.md).

---

## A CSV import is reviewed as a full grid

Importing devices, tags, or models opens a review window instead of a side drawer. The preview is a grid of every row in your file, showing only the columns the file actually changes — a file that edits one column no longer asks you to scan past thirty-seven that it does not.

Rows you cannot import sort to the top and are marked, and a column names the reason for each. Search the rows, narrow the grid to a single action, or switch it to show every column. Selecting a row opens a panel listing each changed field with its current value above the new one, so the two are readable in full rather than merged into one line.

The import button now states how many rows are blocking when the file cannot be applied, and stays disabled until they are fixed — previously it stayed active and the import failed at the server.

Re-importing a file you just exported without editing it now reports no changes at all. The `created_at` and `updated_at` columns are still exported, but they are no longer read back in: they are set by Koios when a row is saved, so a file could never control them and every row appeared to change. Existing files that still carry those columns import exactly as before.

See [Importing and exporting tags](https://ai-ops.com/docs/tags/importing-exporting.md).

---

## An empty trend offers the first step

A trend with nothing on it now says what to do next: the chart area offers to add the first tag and opens the tag search for you, rather than showing an empty grid.

Adding a second Y-axis has moved out of the trace list and into the trace panel's actions menu, beside the trace count. Adding a tag is what most people open a trend to do, and it now has that column to itself; the axis count is shown on the menu item, and axis editing, drag-between-axes, and everything else about axes is unchanged.

---

## A trend export downloads as it is produced

Exporting a trend no longer ends with a file you have to remember to collect.

**The download starts immediately, and your browser saves it as it is produced.** Confirming an export begins the download there and then — it appears in your browser's downloads and fills in as the data is read, with that browser's own progress, pausing and resuming. Previously the export was built on the server first and left behind a button, so anyone who moved on while one ran came back to find nothing had been saved and had to run the whole export again.

**The export panel shows how far along it is.** A large export reads for several minutes, and a browser download of unknown size can only count bytes. The panel reports the share of the range that has been read, so a long export is visibly working rather than merely started. Closing the panel does not stop the download.

**Nothing is kept on the server.** Because the export is produced as it is sent, there is no copy left afterwards, nothing to expire, and no second place for your data to sit. Exports used to be held for a day whether or not anyone collected them.

**An interrupted export does not leave a partial file.** If the connection drops or the data cannot be read part-way through, the download is reported as failed rather than saved half-written, so a file that finishes is a complete export.

**Larger ranges complete, and the limit is ten times what it was.** Data is read and sent in slices rather than assembled whole first, so an export's size is no longer bounded by what has to fit anywhere. Exports estimated above 10 GB are refused, up from 1 GB, and the check still runs before the export starts.

**What is limited instead is how many run at once.** Each export occupies part of the server while it produces data, so one started while several others are already running is refused — and the export panel says so rather than starting a download that fails.

**Exporting the full history works.** Choosing to export all available data, without setting a date range, previously failed after the export had already been accepted. It now runs.

**Parquet exports carry real timestamps.** The timestamp column is written as a timestamp rather than as text, so analysis tools read it back typed instead of parsing it.

**Every selected tag gets a column.** A tag with no readings in the range used to be left out of the file altogether, so a five-tag export could arrive with three columns and no indication of which two were missing. It is now present and empty. In JSON, columns of readings are also declared as numbers rather than as text when the range contains gaps. CSV rows are otherwise unchanged.

**Exporting requires the *Can export trend data* permission**, granted on upgrade to every role that can already manage trends.

**Integrations export the same way.** The operations that started an export, polled it and downloaded the result are gone, along with the record they relied on. A single request now returns the data as it is produced — see the API changes below.

See [Settings & Export](https://ai-ops.com/docs/trends/settings-and-export.md).

---

## Certificates are managed in one place

**System > Certificates** is a new page for the TLS certificate browsers see when they connect to Koios, with OPC-UA client certificates on a second tab beside it.

- **Install your own certificate**: upload a CA-signed certificate, its private key, and an optional chain instead of living with the built-in self-signed one. Koios validates the pair up front — rejecting an expired or not-yet-valid certificate, a key that does not match, a passphrase-protected key, or a file with a private key mixed in — then installs it and reloads the web server. If anything fails, the previous certificate stays in place. The private key is never readable back out.
- **Status at a glance**: source, common name, issuer, subject alternative names, validity with an expiry countdown, serial number, and fingerprint.
- **The built-in certificate is now generated for your installation** on first start, rather than being the same one on every Koios. It covers the addresses Koios can detect, and `KOIOS_TLS_SAN` adds names it cannot — a DNS alias, for example. Because it carries those names, browsers now show only the expected untrusted-issuer warning instead of an additional name mismatch. Upgrading generates the certificate once, so anyone who previously stored a browser exception for this installation will be asked to accept the new one.
- **An expired certificate no longer keeps being served.** Koios checks the expiry date at every boot and falls back to the built-in certificate if it has passed, so a restart always reaches a login page you can click through to.
- **Certificates without a subject alternative name are refused at upload.** Browsers ignore the common name, so users would get a name-mismatch warning to click through on every connection. Uploading one now fails with an explanation rather than installing a certificate that warns. This is a check on the upload form: a certificate already installed, or placed on the certificates volume directly, is unaffected.
- **Revert** returns to the built-in self-signed certificate. The page is read-only when a certificate has been placed on the server filesystem directly.
- **OPC-UA certificates** are managed on their own tab of the same page and are directly linkable; the Certificates tab on an OPC-UA protocol page now points there instead of duplicating the manager. Those actions are gated on their own permission, granted to Engineer and Administrator on upgrade.
- **Uploading a certificate works for Engineers.** Upload was previously restricted to superusers, so a role that could generate and delete certificates could not upload one issued by your own CA. It now uses the same permission as every other certificate action.
- **Create a certificate without leaving the device form**: the Client Certificate picker on the OPC-UA device create and edit forms has an inline **Add** menu to generate or upload a certificate, and selects the new one automatically. The device list marks devices using Sign or Sign & Encrypt with a shield icon naming the security mode.

Installing a web interface certificate is gated by a new permission granted to Administrator on upgrade. See [OPC-UA Certificates](https://ai-ops.com/docs/protocols/opc-ua-certificates.md), [Creating an OPC-UA Device](https://ai-ops.com/docs/devices/creating-opc-ua.md), and [Roles & Permissions](https://ai-ops.com/docs/system/roles-permissions.md).

---

## Every accountable action is recorded

**System > Audit Log** is a new, read-only, append-only record of who did what, kept separately from the operational event feed.

Every accountable action is recorded with the actor and how they authenticated, source address, client, timestamp, what changed, and whether it succeeded, failed, or was denied for lack of permission. Coverage includes:

- Logins and failed logins, and API client authentication
- Permission, user, role, and password changes
- Licence upload and activation
- Backup and restore
- Service start and stop, log level changes, and log downloads
- Network scans and diagnostic tools
- Certificate actions
- Configuration changes across devices, tags, models, and components
- Clearing or deleting entries in the event feed
- Audit exports themselves

Rows open a detail panel with the field-level before-and-after diff, source IP, user agent, and request id, and a link straight to the object's own page while it still exists. Filter by actor, action, outcome, entity type, entity name, and time range, search over message and detail text, and export the filtered view as CSV or JSON. Records cannot be edited or deleted through Koios.

**Retention is separate** from event and log retention: one year by default, with a hard 90-day floor that cannot be lowered, so the trail stays inspection-ready. Expired records are deleted by the nightly maintenance run, and a run that removes records writes its own purge entry to the trail. See [Data Retention](https://ai-ops.com/docs/system/retention.md).

**Request ids**: every response now carries a unique request id header, and the same id is stored on the matching audit record — quote it when reporting a problem and it points straight at what the server recorded. See [Collecting Diagnostics](https://ai-ops.com/docs/troubleshoot/collecting-diagnostics.md).

**Gaps are visible**: audit writes never block the action they record. A failed write raises an **Audit write failed** entry in the events feed, and a running count is reported on the system-information API as `auditWriteFailures` — it is not shown on the System Overview page in this release. A non-zero value means the trail is incomplete and needs investigation.

Viewing and exporting are separate permissions, both granted to Administrator on upgrade, so export can be given only to the roles that need to produce compliance evidence. See [Audit Log](https://ai-ops.com/docs/system/audit-log.md) and [Roles & Permissions](https://ai-ops.com/docs/system/roles-permissions.md).

> [!NOTE] The trail starts at the upgrade
> The Audit Log records what happens from the upgrade onward. It is empty the
> first time you open it, and earlier activity is not carried into it — there is
> no record of it in the form the trail needs. Existing events are unaffected
> and stay in the events feed.

---

## Roles can grant every permission Koios enforces

- **New permissions**: manage component stacks, view audit records, export audit records, export trend data, manage the web interface certificate, manage OPC-UA certificates, and change or delete discovered network hosts. Trend data export is granted on upgrade to every role that can already manage trends, so no role loses access it had. Component Stacks and OPC-UA Certificates are granted to Engineer and Administrator on upgrade; the audit and web interface certificate permissions are granted to Administrator.
- **Permissions that were enforced but ungrantable are now in the role editor.** Some permissions Koios enforced were missing from the editor entirely, so an administrator asked to change who could do something found no control at all. The editor now lists Component Stacks, the Audit Trail permissions, web interface certificate management, and seven settings permissions (retention, backup settings, advanced metrics, licence settings, and others) that were being filtered out. Under Network Diagnostics it still exposes only **Scan the network**, so editing and deleting discovered hosts remain limited to the built-in Engineer and Administrator roles.
- **OPC-UA private keys require certificate management.** Downloading an OPC-UA certificate's private key now requires the OPC-UA certificate permission rather than only being signed in. Anyone holding that key can impersonate Koios to every server that trusts the certificate, so review who holds the permission after upgrading — and note that a backup archive also contains the key, so the backup settings permission reaches it too. The public certificate file stays downloadable by any signed-in user.
- **Network host actions respect permissions.** Editing and deleting hosts discovered by a network scan were available to anyone who could open the page. Both are now gated and shown disabled with an explanation rather than hidden.
- **User changes appear in the event feed.** Editing a user account, and enabling or disabling one, now raise an entry the way device, tag, and model changes always did.

> [!WARNING] Renamed built-in roles
> If you renamed a built-in role such as Engineer, earlier upgrades silently recreated an empty role under the original name and granted new permissions to that instead of to your renamed role — the upgrade reported success, your users gained nothing, and an official-looking phantom role appeared in the list. Upgrades now only add permissions to roles that actually exist, and print a warning naming the permission and where to add it yourself. Check the new permissions above against your renamed roles after upgrading.

See [Roles & Permissions](https://ai-ops.com/docs/system/roles-permissions.md), [Network Diagnostics](https://ai-ops.com/docs/system/network.md), and [Updating Koios](https://ai-ops.com/docs/updates/general.md).

---

## A backup already on the server restores in place

Every row of the backup history on **System > Backup & Restore** now has a **Restore** action, so a backup already on the server can be restored in place. Previously the only route was to download the archive and upload the identical bytes back — a round trip of many gigabytes for a Full backup, over whatever link the operator happened to be on.

The confirmation step names the tier, when the backup was taken, and who took it, and offers the time-series option only for Full backups, which are the only ones that carry that data. The upload route is unchanged and is still the way to restore onto a fresh install or new hardware.

The action needs the same **edit backup settings** permission as Delete. See [Backup & Restore](https://ai-ops.com/docs/system/backup.md).

---

## Logs keep days of history and say more when something fails

Logs in 1.2 keep more history, say more when something goes wrong, and no longer carry credentials.

- **Device logs keep days of history instead of hours.** A failing device used to write the same messages on every scan cycle — an unreachable device with 50 failing tags produced hundreds of megabytes a day, so the connect failure you opened the log to read had already rotated away. Repeated identical messages are now recorded once and re-recorded only when the message or the device status actually changes. The first occurrence and any recovery still appear immediately.
- **Device logs no longer claim a connection that never happened.** Status is recorded only when it genuinely changes, so a stuck device reports its failure once instead of logging Running and then a failure on every cycle.
- **On-demand reads and writes now appear in the device log**, including batch expiry, lock waits, failures, and requests dropped because a tag belonged to no loaded device.
- **Configuration changes are traceable.** Device, tag, and log-level changes are recorded in the affected device's own log as they arrive, so an edit that did not appear to take effect can be traced rather than guessed at.
- **One unreadable Modbus register no longer stops the rest.** Koios reads neighbouring registers together in a single request. When that request failed because the device did not answer at all — a block larger than a gateway will serve, or one address the device will not return — the whole block failed on every scan, and every tag in it stayed in error until the device was restarted. Koios now re-reads the block's tags one at a time before giving up and keeps every value the device returns, so only an address that genuinely cannot be read is reported as failed. A device that is actually offline gets that one extra round too, with no additional retries, and is then reported as failed. Those reads fail at once when the connection is refused or dropped; when the device stops answering without closing its connection, each one first waits out the device's timeout. The failing block's address range and size are written to the device log — that range is what to size **Max Registers Per Read** against.
- **Failed writes now say why.** OPC-UA per-tag write failures were entirely silent; Modbus write rejections — a register type that does not accept writes, a coil tag not configured as boolean, a device reporting the write unsuccessful — were silent too. Each now names the specific reason and the tag.
- **A misconfigured Modbus device names the setting at fault.** A device with no hostname, or an out-of-range port or unit ID, reported a connection problem and advised checking the network — so the one setting that needed attention was buried behind advice to check cabling and firewalls. The setting is now named directly, and genuine connection failures keep the network advice.
- **Previously silent failures are recorded**: collection stopped because the licence is invalid, enabled tags with no active device, on-demand requests being rejected after repeated failures, connection-cleanup failures, and a device deleted mid-scan.
- **Protocol client output is attributed to its device** and obeys that device's log level, so raising one device to Debug surfaces its protocol-level detail without touching the others. Modbus retry, batch-read, and conversion diagnostics reach the device log for the first time.
- **Third-party library noise is capped** at Warning (the Modbus library at Error), so a Debug capture gives you Koios's own detail instead of a raw packet trace. The cap is a floor, not an override — setting a device to Error still silences everything on it.
- **Log level changes are announced** while the more verbose level is still in effect, and report level names rather than raw numbers.
- **Credentials are masked before anything reaches a log**, at the sink rather than in each protocol, so a library that logs a request payload is covered without relying on it to redact its own output.

### New environment settings

```ini
KOIOS_LOG_MAX_BYTES=10485760       # size a log file reaches before it rotates
KOIOS_LOG_BACKUP_COUNT=5           # rotated copies kept
KOIOS_LIBRARY_LOG_LEVELS=asyncua=DEBUG,zeep=INFO
KOIOS_DB_POOL_SIZE=10              # connections per background service
KOIOS_DB_MAX_OVERFLOW=20           # extra connections allowed under load
```

Defaults are unchanged. Widen the log ring to capture an overnight Debug trace on a busy device without it wrapping, and use the library level override to turn a specific protocol library's detail back on while debugging a live site. Pool sizing matters on installs running several component stacks, because each stack adds a worker process with its own pool.

See [Logs](https://ai-ops.com/docs/system/logs.md), [Environment Variables](https://ai-ops.com/docs/installation/environment-variables.md), and [Collecting Diagnostics](https://ai-ops.com/docs/troubleshoot/collecting-diagnostics.md).

---

## Pages load faster, and an outage says so honestly

- **Faster first load.** The interface ships roughly 1 MB less JavaScript, so pages load and become interactive sooner.
- **Faster event lists.** The event list's total count no longer forces the whole filtered set through a grouping step, so paging and counting stay fast as event history grows.
- **Faster status filtering.** Filtering a tag, device, model, scan-group, or component-environment list by live status reads one value per row from the live data cache instead of thirteen, and reads them in bulk. Filtering for failed items is answered from a maintained set without reading rows at all.
- **Honest banner when the server is unreachable.** The interface now shows a single **Server Unreachable** banner instead of quietly dropping every other alert and claiming the system has no licence. It keeps checking in the background and refreshes the page's data as soon as the server answers, so you recover without a reload. A dismissed outage banner reappears if the server drops again.
- **No duplicate component execution after an unclean restart.** Component processes left behind by a previous run are stopped before new ones start, and a component process that loses its parent stops itself. Previously two copies of the same components could run side by side, each writing the same tags.
- **Losing the licence stops component processes** outright, with each affected environment recording that it stopped because the licence is invalid. They start again on their own when a valid licence is restored.
- **Component engine events say why components stopped** — the stack was rebuilt, nothing is attached to it, it was deleted, the service is shutting down, or the licence is invalid — with a child entry per environment that genuinely stopped. Shutdown records one entry instead of a warning fanned out over every running environment.
- **Documentation search on phones** is reachable from the header on small screens, and the search dialog opens full screen with a close button.

---

## The public docs keep a copy for each release

The public documentation at [ai-ops.com/docs](https://ai-ops.com/docs) now keeps a copy for each release, so the pages you read match the version you run. The current release stays at the same addresses as before. Earlier releases are under their version, such as `ai-ops.com/docs/v1-1`, where every page says which release it describes and links to the current one. A version menu on every page switches between them.

The documentation built into Koios always describes the version you have installed.

---

## API changes

> [!WARNING] Integrations need review before upgrading
> 1.2 removes API operations and inputs that could not work correctly. Review any integration that writes events, groups events, or passes the fields listed below.

- **Events can no longer be created or edited through the API.** The operational event feed is read-and-manage only. The calls that let a client create events, rewrite an existing event, or open and close a grouping "root event" are removed, along with the request header that attached new events to a root event. Operators can still acknowledge, delete, and clear events, and every clear or delete is written to the audit trail. On upgrade, the now-meaningless "change events" permission is revoked from every role. Integrations writing events into Koios, or using the root-event header for grouping, must be updated.
- **Unusable filter and sort options are removed.** A few list inputs advertised filters and sorts that could never work because they pointed at values calculated at read time rather than stored: `status` on model bindings and on models, and `filename` on model files. Supplying them raised an error, and on model bindings the filter input could not be used at all without one. The Protocols list also no longer advertises a sort input that no query accepted. Clients passing these fields need to drop them.
- **Trend exports are a single streaming request.** The operations that started an export, reported its progress, listed past exports and downloaded a finished one are removed, along with the record they depended on — nothing about an export is stored now. One request returns the data as it is produced, so a client writes it to a file as it arrives instead of polling and collecting it afterwards. There is nothing to resume: a connection that drops part-way means running the export again. The Python client replaces the four calls with a single streaming one.
- **Bulk component create calls report errors differently.** Bulk creation of component definitions, environments, instances, and wires now reports validation failures as a request error rather than as a structured result alongside the created items, matching how the other bulk operations already behaved. The whole batch still succeeds or fails together.

See [API Clients](https://ai-ops.com/docs/system/endpoints.md) and [Events](https://ai-ops.com/docs/events/introduction.md).

---

## Fixes

- **Clearing a filter no longer blanks the page.** Sending an empty, zero, or false value to a list filter — clearing a search box, deselecting every event type, setting a yes/no filter to No — could return an empty response for the entire request, so the table and everything else on the page came back blank. Filters used through a related entity no longer error out either.
- **Users and Trends lists no longer repeat or skip rows** across pages; both now have a stable default order.
- **Explorer finds models, device sets, and local values again.** Typing in the entity picker returned nothing for those three because the search was sent in a form the server rejected. All five entity types now match on any part of the name.
- **An expression's entity picker lists tags again.** Typing `@` in an expression opened the picker empty — no tags, devices, bindings, or models, whatever filter or search was used — as soon as a single tag within reach held a reading that was not a number, such as text read from a device. One such reading discarded the whole list rather than just its own value. The picker now lists every entity, and a tag holding text is listed with the rest.
- **The Events list stops resetting while you type**, waits until you stop typing, keeps what it has scrolled through, and opens sorted newest-first.
- **A decimal typed into a whole-number filter** (Depth, Inputs, Outputs) is rounded instead of blanking the page.
- **The Tags list's Protocol filter** no longer excludes every tag that belongs to a device set.
- **A list no longer pushes the page past the bottom of the window.** With a notice strip showing along the bottom of the window, a list sized itself as though the strip were not there: the page grew taller than the window and drew a second scrollbar beside the list's own, and the paging controls under the list were pushed out of view. Because the strip comes and goes with the condition it reports, so did the second scrollbar. A list now sizes itself to the room actually left to it.
- **Column headings stay in place as you scroll a list.** Scrolling down a long list took the headings with it, leaving rows with nothing naming the columns. The heading row now stays at the top of the list while the rows move under it, including on lists with a column held to the left.
- **Box-zoom on a trend works on Windows and Linux.** Hold Ctrl on Windows and Linux, or Cmd on macOS, and drag across the chart to zoom into that time range. Off macOS the drag needed a key the desktop claims before the browser sees it, so dragging to zoom into a chosen region did nothing.
- **Panning a trend back through history keeps what it loads.** Once a trend holds as many points as it keeps in the browser, it now thins the whole series evenly instead of discarding the oldest points, which could blank the region you had just panned to. Readings either side of a stop or a fault are kept ahead of other readings, so outages stay visible.
- **A pen moved onto a newly added axis stays there.** Adding a Y-axis to a saved trend and moving a pen onto it straight away now saves the assignment; previously the pen was back on its original axis the next time the trend was opened.
- **A trend's hover readout lists only pens with data near the cursor.** A pen whose device went quiet hours earlier no longer quotes its last reading under the cursor's time, and the guide line follows the pointer instead of jumping to the nearest sample. A pen that has stopped or is in a fault state now says so rather than showing a number, where a stopped pen used to vanish from the readout. A pen that records less often than the chart can show appears in the readout only near its own readings, and a stopped or faulted pen says so near the point where it stopped or faulted.
- **Trend settings show the sampling window the chart actually requested.** With the sampling window left on automatic, the settings panel worked its figure out separately from the data request, so on a long range or a zoomed view it could name a finer resolution than the data had. It now reports the window the chart asked for, and follows it as you zoom, pan, or change the time range.
- **A tag's sparkline shows its readings in the colour of its current status.** The points kept whichever colour the status had when the chart was first drawn — a tag that recovered kept red points under a green line, and one that failed while you were watching kept green points under a red line. The line always followed the status; the points did not.
- **An export over the size limit is refused by the server as well as the page.** Exports above the limit were blocked only in the export panel, so a request sent directly to the API could still start one. The server now applies the same estimate and limit to every export request, and a refused export says how large it was estimated to be. A raw export whose size cannot be estimated at the time is refused rather than started.
- **The dark theme uses the Koios colors whatever your system is set to.** Selecting the dark theme on a computer or browser set to a light appearance gave you a washed-out gray background and gray surfaces rather than the intended dark ones. Because the choice is stored per browser, the same install could look right in one browser and wrong in another on the same computer.
- **A change to a device's connection settings now takes effect without a restart.** An edit to an address, port, timeout, Unit ID, OPC-UA endpoint, credentials, or any other setting Koios uses to open the connection was saved and shown back correctly, while the device carried on using the previous setting until Koios was restarted. Whether the change took hold depended on whether it arrived while that device happened to be mid-scan, so the busiest devices were the most likely to keep the old setting, and nothing indicated anything was outstanding — correcting a wrong Unit ID, for example, appeared to do nothing at all. Koios now reconnects on the next scan after any such change, on every protocol.
- **Modbus tags sharing a register no longer show stale values.** When two or more tags read the same register, only one received the new value each scan; the others kept an old value, still marked good quality and still reporting as running.
- **Bit-level Boolean tags on a shared register all read correctly.** In the standard PLC status-word pattern, only one tag received a value and the rest were neither given a value nor reported as failed, behind a generic "operations failed" error naming none of them. Every tag now gets its own result, and a tag producing neither a value nor an error is reported explicitly.
- **Coil and Discrete Input tags no longer read backwards.** Byte Swap and Bit Number describe the layout of a 16-bit register and never applied to single-bit points, but were being applied anyway — a point that was physically ON could report OFF as a successful read. Both settings are now ignored for coils and discrete inputs, in the collected value and in the register browser, which previously disagreed with each other.
- **Testing a Modbus tag** against a coil or discrete input now reports the bit as read, and testing a holding-register tag no longer fails with a "register not found in response" error when the device answers on a different address than requested.
- **Duplicating a generic EtherNet/IP tag keeps its addressing.** Assembly instance, byte offset, data type, and bit number were all reset on the copy, so a tag duplicated to create the next one on a device pointed nowhere until each field was entered again.
- **Testing an EtherNet/IP tag that holds text now shows the value**, and says that text is not stored and needs a value mapping to collect. Reading a string tag reported it as a non-numeric type instead, and a string of digits was rounded to the tag's decimal places rather than shown as stored.
- **An EtherNet/IP device set up as a Logix controller can be changed to Generic CIP.** The controller slot was still being checked after the switch, on a setting the Generic form no longer shows, so saving the change did nothing and nothing on screen explained why. The slot is now required only while Logix is selected, and changes made to the other settings before the device type is changed are kept rather than reverted.
- **A new generic EtherNet/IP tag starts at byte offset 0 rather than unset.** The field showed 0 before anything was typed into it but held no value, so a tag left at that apparent 0 was created with no offset at all and then failed to read, reporting that its byte offset was not configured.
- **One bad Modbus tag no longer fails every tag on the device.** An invalid data type and byte/word swap combination is now contained to its own tag, which reports a configuration error while the rest of the device keeps collecting.
- **A Modbus TCP device that stops answering reads is now reported as failed.** A device that rebooted, lost power, or dropped off the network could keep showing as running, with no error, while every one of its tags failed to read. A device set never switched away from it, and after some connection losses collection did not resume on its own when the device came back. Now a device that answers none of the read requests in a scan fails. Its tags show a failed read on that scan and then that their parent device failed, a device set fails over to its highest-priority running member, and Koios reconnects on its own once the device is reachable again. A device that stops answering without closing its connection is marked failed once that scan's reads have waited out their timeouts. A register the device rejects, such as an address it does not have, still fails only that tag. A device that is reachable but discards every read, such as one set to the wrong Unit ID, is now reported as failed too. See [Troubleshoot a Connection](https://ai-ops.com/docs/troubleshoot/connection.md).
- **Modbus writes that partly succeed are recorded as written.** Tags that did write in a partly failed batch were treated as still pending and re-sent on every following scan.
- **CAREL BOSS integer output tags with value mapping on write** now convert correctly instead of failing with a write error.
- **SQL tags whose column disappears are flagged instead of freezing.** A tag missing from the query result set silently kept its last good value and reported as running; it now fails with a read error, so a query or schema change on the source system is visible.
- **A tag that fails to read from a reachable device no longer passes its last good value off as current.** A failed read marked the tag as failed but left its previous reading in place, stamped with the time of the failed read, so a failing tag looked like one whose value had simply stopped changing. Until its next successful reading such a tag now has no value for anything that uses it: its value through the API, tag mappings, expressions, and models. The tag list, the tag's Overview tab, and the dashboard widget that shows a tag's value show the reading it last took instead, set apart from a current value, with how long ago it was taken, next to the tag's failed status; hover over it for the time it was taken and that reading's quality. The API reports that reading separately from the tag's value, for display only, and pages that are already open update too. Once a device has failed, its tags keep whatever reading they last had, with the time it was taken, and those same places set that reading apart in the same way. A calculated tag whose expression fails to evaluate has no value and still shows none. A tag mapping that takes its value from a tag with no value now fails instead of passing the old reading on, and its error includes the source's read error: a device output it feeds is not written again until the source reads successfully, and an in-memory tag it feeds has no value. A mapping failure alarm is raised each time mappings start failing after running cleanly, so a source whose reads fail intermittently raises one each time.
- **An in-memory tag is read/write however it was created.** In-memory tags exist to receive values from models, mappings, and components, and the tag form has always created them that way. Created or edited through the API instead, one could be stored read-only, and nothing said so: a read-only in-memory tag is left out of every list that offers a tag as an output, so it could not be bound to the producer it was created for, and its usage could not be corrected from the tag's own page. Usage now follows the source type wherever the tag is written. A tag already stored read-only is corrected the next time it is saved, so open it and save it to bring it back into use. See [Tags](https://ai-ops.com/docs/tags/introduction.md).
- **Component package uploads reject oversized manifests** rather than trying to load a manifest that expands to an unbounded size.
- **A value set on a component input no wire feeds now holds.** Such a value could be discarded moments after it was set, leaving the component running on its previous value with nothing to say the new one had been lost. Because it happened only sometimes, setting the value a second time usually appeared to fix it. A value set this way now takes effect on the following scan and stays until it is changed again. See [The Canvas](https://ai-ops.com/docs/components/canvas.md).
- **A configuration problem is no longer reported as a certificate problem.** When the web server refused to start for a reason that had nothing to do with HTTPS, Koios blamed the installed certificate, replaced it with the built-in one, and recorded it as invalid — pointing you at the Certificates page for something that page could not fix. Koios now confirms the certificate is at fault before falling back, leaves a working certificate in place when it is not, and records what the web server actually reported.
- **Environment status stays correct when a stack is switched** — an environment no longer reports stopped for a scan period while the new process is already running it.
- **Per-device and per-model log lines are no longer duplicated** by a race that could attach two writers to the same log file, which also caused two rotators to fight over one set of backups and lose history early.
- **An entity's name no longer disappears from its own log lines** after an internal lookup that did not carry the name.
- **Service health failures now say why**, including the underlying error rather than only stating that the operation failed.
- **Per-scan Modbus and CAREL BOSS log entries moved to Debug.** On a one-second device these wrote roughly 86,000 lines a day at the default level with no way to quiet them. Failures and disconnects are still reported at the default level.
- **Service logs no longer grow without bound.** The logs recording each background service's own output were never trimmed on an installed system, so a long-running install could eventually fill its disk. They now rotate once they reach a size cap, keeping a fixed number of compressed copies. A log that has just rotated can start with a run of empty bytes; this is expected and clears at the next rotation.
- **Clearing a log no longer stops the service writing it.** Deleting the file a service was actively writing to left that service logging into a file that no longer had a name: nothing could read it, and no replacement appeared until the service was restarted, so the log looked permanently empty. Clearing the live file now empties it in place and the service keeps writing, so new lines appear straight away. Archived copies are still removed outright. See [Logs](https://ai-ops.com/docs/system/logs.md).
- **Automatic log cleanup leaves live log files alone.** The nightly cleanup applied the retention window to every file it found, including the ones services were writing to, which broke logging for those services in the same way. It now only removes archived copies, which is what the retention setting has always described. A cleanup that cannot remove a file now reports it rather than passing over it in silence.
- **Deleting a device, model, scan group or component instance reclaims its log files.** Each left its log and every archived copy on disk, with nothing in the interface able to reach them. On a system where these are added and removed regularly this accumulated indefinitely. They are now removed with the entity.
- **Secured OPC-UA connections survive the server being re-created.** Koios announced an identity built from the machine name each time it connected, while the identity inside the certificate it presented was fixed when that certificate was generated. Where the machine name is not stable between restarts the two drifted apart, and a conforming OPC-UA server rejected every connection with only a generic security failure to go on. Koios now announces the identity recorded in the certificate it is presenting.
- **A restore that cannot replace time-series data now fails instead of reporting success.** If the existing time-series data could not be listed or cleared first, the restore carried on, wrote none of that data back, and still finished with a success message. It now stops and reports what failed.
- **Keyboard shortcuts no longer depend on Caps Lock.** With Caps Lock on, copy, cut, paste, undo and save on the component canvas did nothing, saving a new trend from the keyboard did nothing, and redo (Ctrl+Shift+Z or Cmd+Shift+Z) never triggered at all. All of them now respond whatever the Caps Lock state.
- **Offline install archives arrive already named.** An archive downloaded from the portal now loads carrying its version, so it appears in your image list straight away and the service can be pointed at it without an extra step. Previously the load succeeded but produced an unnamed image, which did not show up when listing images by name and had to be named by hand before the service would start. The download is named for its release as well, so an archive that has sat on a drive for a month still says which version it holds. Archives published before this change still load unnamed; name the image after loading using the identifier the load reports. See [Pulling the Koios Image](https://ai-ops.com/docs/installation/pulling-koios-image.md).
- **The Server Time card no longer reads as clock drift.** The card showed the server's clock rendered in your own timezone but labelled with the server's, so where the two differ it looked like the server was hours out when it was not. It now shows that reading in your timezone, matching the time axis on trends and every other chart, and repeats the same moment in the server's timezone underneath. Time synchronization guidance now also covers running Koios in a virtual machine, pointing at a time server on an isolated network, and what to do when a time server is rejected for reporting a large uncertainty. See [System Overview](https://ai-ops.com/docs/system/information.md) and [Time Synchronization](https://ai-ops.com/docs/installation/time-synchronization.md).

- **A device with many tags reads again on servers that limit how much they answer at once.** Some OPC-UA servers cap how many points a single request may ask for, and refuse a request over that cap outright rather than answering the part that fits. A device with more tags than the cap therefore collected nothing at all — every tag on it failed, on every scan, rather than one tag failing. Koios now asks each server what it will accept when it connects, and splits its reads and writes to fit. A server that sets no cap is read exactly as before.

---

## Upgrade notes

> [!TIP] No new volumes
> Component stacks are stored alongside your other uploaded files, so upgrading needs no service file change and no new volume mount. On Kubernetes, the default media volume size increases because it now also backs stacks; pin the previous value if your storage class does not allow volume expansion.

Before upgrading, review:

1. **Models with duplicate input binding order** will stop after upgrading. Binding order is not chosen by hand, so renumbering means exporting the model's bindings to CSV, giving each input a distinct order, and importing the file back. See [Model Configuration Errors](https://ai-ops.com/docs/models/troubleshoot-configuration-errors.md).
2. **Renamed built-in roles** do not receive the new permissions automatically. Grant Component Stacks, Audit Trail, and web interface certificate management by hand. See [Roles & Permissions](https://ai-ops.com/docs/system/roles-permissions.md).
3. **Modbus TCP devices left at the default** now issue reads of up to 125 registers where a hidden cap previously limited them to 50. Lower **Max Registers Per Read** to the device's published limit if it rejects large blocks. See [Device Parameters](https://ai-ops.com/docs/devices/device-parameters.md).
4. **Integrations** that create or edit events, use the root-event grouping header, or pass the removed filter and sort fields must be updated. See [API changes](#api-changes).
5. **Device passwords used with Debug logging** on earlier versions should be rotated. See [Logs keep days of history and say more when something fails](#logs-keep-days-of-history-and-say-more-when-something-fails).
6. **The component environment list page is gone.** Environments are managed from the workspace, and environment bulk actions no longer exist. See [Component Environments](https://ai-ops.com/docs/components/environments.md).
7. **Logic that reads an OPC-UA tag's error code**, in an expression, a component, an integration, or a mapping that writes it to a device output, sees `107` for data the server reports bad and `105` for an empty value, where it saw `106`. See [what the server said about each reading](#an-opc-ua-tag-reports-what-the-server-said-about-each-reading).
8. **The Core Library is now version 1.0.0**, without the release-candidate tag it carried before. The components are unchanged, so this is a version label rather than new behavior. On an existing install the new version is added alongside the one you are running and left inactive, so your environments keep running exactly what they run today; the library list will show that a newer version is available until you activate it. Activating it is safe — the preview shown before you confirm reports no component changes. New installs start on 1.0.0. See [Component Libraries](https://ai-ops.com/docs/components/libraries.md).

Step-by-step instructions for this release are in [Upgrading to v1.2.0](https://ai-ops.com/docs/updates/v1-2-0.md), and general upgrade guidance is in [Updating Koios](https://ai-ops.com/docs/updates/general.md).

---

## Patch releases

### v1.2.1

`September 19, 2026`

Hotfix release for model prediction history, device fault reporting, service logs, license reporting, record pickers, component packaging, and the component canvas, with a rewritten installation guide. All deployments are affected.

- **Models keep the prediction history their configuration calls for.** The amount of prediction history a model retains is worked out from its input window, but where that retention had drifted below what the model needs, nothing brought it back — and the model's own past predictions were being discarded before it could read them. Every start of the prediction service now checks each model's retention against the window that model requires, restores it where it has drifted, and records what it corrected. Retention you set yourself is left alone.

- **A failing OPC-UA tag no longer reports a more recent time than a working one.** On a tag set to take its timestamp from the server, a successful reading carries the server's own production time, which for a backdated signal such as a lab result can be hours or weeks behind the present. A reading that failed was recorded at the time of the attempt instead, so a tag that stopped reading jumped forward to now, appeared fresher than the tags around it, and jumped back when it recovered. A failed reading now keeps the time of the last reading the tag took, so the tag stays on one clock whether it is reading or failing. Tags left on collector time are unchanged, and a tag that has never read successfully still records its first failure at the collector time. See [Creating an OPC-UA Tag](https://ai-ops.com/docs/tags/creating-opc-ua.md).

- **A Modbus device that fails for an internal reason no longer reports a network problem.** Any unexpected fault while a Modbus connection was being set up came back as a connection failure, advising a check of the hostname, port and network — so a fault with no network involvement sent the reader somewhere it could never be found. That advice is now reserved for genuine connection and protocol failures. Anything else is reported as what it actually was, with the full detail recorded in the device log. A device missing its hostname, port or unit ID continues to say which setting is missing.

- **Service logs keep everything the service produces.** Most background services discarded any message that did not come from the service itself or from a device, model or component — including messages from the shared components every service uses and from the libraries that reach controllers and storage. Anything below Warning was dropped, and Warning and above was written with no timestamp, level or source, so it could neither be read alongside the rest of the log nor matched to a moment. Those messages now appear in full, in the same format as every other line. Expect these logs to be busier than before — the detail was always being produced, it was only being discarded — so review **Log File Retention** if disk is tight. See [Logs](https://ai-ops.com/docs/system/logs.md).

- **The alert dot on the browser tab stays lit for license and resource alerts.** The dot marks the tab whenever an alert is active, but for the two kinds that do not come from a failed device, tag, model, scan group or service it was cleared again within seconds — so an expiring license or a resource alarm left the tab unmarked most of the time. It now stays lit for as long as the alert is active, and a background check that cannot reach the server leaves the dot alone instead of clearing it.

- **A component environment stopped by a license problem says so.** Where the license is missing, expired, or bound to different hardware, component environments stop — but they stopped with nothing stating the reason, so an environment looked idle rather than blocked. Each one now names the license as the cause, and goes on saying so for as long as the license problem lasts. See [Licensing Problems](https://ai-ops.com/docs/troubleshoot/licensing.md).

- **Delete removes every wire you have selected on the canvas.** Selecting several wires and pressing Delete removed only those attached to an instance being deleted at the same time; the rest stayed. Every selected wire is now removed. A wire you have taken back out of the selection is left alone, including when several instances are selected alongside it. See [Component Canvas](https://ai-ops.com/docs/components/canvas.md).

- **A component connector moved to a different instance survives an undo and redo.** Moving a connector's source to another instance and saving straight away always worked, but if you undid and redid the move first, the canvas stopped counting the connector as changed — the save then left it on the original instance, and the confirmation did not list it among what was about to be written. The move is now recognised whichever way you arrive at it. See [Component Canvas](https://ai-ops.com/docs/components/canvas.md).

- **Panning a system chart back through history keeps what it loads.** Once a CPU, memory, disk, or network chart holds as many points as it keeps in the browser, it now thins the whole series evenly instead of discarding the oldest points, which could blank the region you had just panned to and kept blanking it on every further step back. Reset the view, or change the time range, to return to following the present. See [System Health](https://ai-ops.com/docs/system/performance.md).

- **Capturing a component instance's live values as its offline defaults shows them straight away.** The values were captured and kept, but the instance went on showing the previous ones until the page was reloaded — and saving the canvas before reloading wrote those previous values back over what had just been captured. The captured values now appear as soon as they are captured, and a later canvas save keeps them. Capturing across a whole environment at once was already correct. See [Component Canvas](https://ai-ops.com/docs/components/canvas.md).

- **Stack bundles resolve against the packages this version actually provides.** The component builder carries a list of the packages Koios already includes, and it still described the previous release — so a bundle was resolved against versions this release had moved past. A stack that needed a package newer than one of those entries could not be built, even though this version already ships the newer one, and the bundles that did build recorded the wrong platform version, which Koios reported as a possible conflict when they were imported. Update to `koios-component-builder` 1.3.1 or later to pick this up. See [Component Builder SDK](https://ai-ops.com/docs/python-packages/component-builder.md).

- **Dropdowns that pick a device, model or other record no longer stop at the first two hundred.** On a larger install a record past that point was missing from the list with nothing to say so, and typing to search did not reach it either, because the search matched only the part already loaded. These lists now load up to five thousand records, which is the most the server will return in one request. Where an install holds more of one kind of record than that, the list no longer presents itself as complete — reaching the remainder from a dropdown comes in a later release, and in the meantime a record past the limit can still be opened from its own page.

- **A setting written through the API with the wrong value type is refused rather than silently ignored.** Each setting holds its value in the place its data type names, and only that place is ever read back. A write that put the value somewhere else — sending a whole-number setting as a decimal, for instance — was accepted and reported success, but the setting itself never changed, and the value skipped the minimum and maximum checked for that setting. Such a write is now refused, naming the field that does not match. Settings changed from within Koios were never affected, and neither were integrations that send the value in the place the setting's own type names.

- **The installation guide covers the whole path.** The Installation section now runs start to finish for someone who has not used Linux, Docker, or a hypervisor before: creating the virtual machine, installing Ubuntu screen by screen, giving the machine a permanent address, installing the container runtime, and starting Koios so it survives a reboot. Every command is written out rather than left to another vendor's documentation. New pages cover [choosing between a virtual machine and a physical computer](https://ai-ops.com/docs/installation/virtual-machine.md), [creating the virtual machine](https://ai-ops.com/docs/installation/hyper-v.md), [network configuration](https://ai-ops.com/docs/installation/network-configuration.md), and [what to do when an install goes wrong](https://ai-ops.com/docs/installation/install-troubleshooting.md), with an [overview](https://ai-ops.com/docs/installation/overview.md) that lays out the order.

- **Installation commands can show your own settings.** Enter your address, gateway, adapter name and username once, and the commands throughout the installation pages display them in place of placeholders — including the network address, which is worked out from what you entered. Until you do, every value that has to be replaced reads as an obvious placeholder rather than an example address that would otherwise be copied verbatim. The values stay in your browser.

- **Installing without internet access is documented properly.** A [new page](https://ai-ops.com/docs/installation/offline-install.md) covers both routes for a machine with no route out: building it on a connected computer and carrying its virtual disk to the site, which is the one to prefer, and transferring the files by hand where that is not possible. It covers the parts that are easy to get wrong — matching the machine's firmware settings to the disk, the adapter name changing after the move, and leaving licensing until the machine is in its final home.

### v1.2.2

`September 20, 2026`

Hotfix release for component stacks, for the version the installer installs, and for the automatic pre-upgrade backup.

- **Component stacks build again.** On v1.2.0 and v1.2.1 every stack failed to build — at startup and from the Stacks page alike — because the build wrote to a location the account running Koios does not own, so a component environment attached to a stack had nothing to run in. Builds now use a location of their own, alongside the stacks themselves, and a stack that failed on an earlier version is rebuilt the first time you start this one. There is nothing to re-import and nothing to reconfigure. See [Component Stacks](https://ai-ops.com/docs/components/stacks.md).

- **Asking for a specific version installs that version, or stops.** Naming a version by number did not match the way releases are tagged, so the installer could not find it and quietly installed the newest release instead, with a single warning line in a long install log. Both spellings are now accepted — `1.2.1` and `v1.2.1` — and a version that cannot be found stops the install rather than substituting a different one. This matters most when upgrading: a newer version than the one you asked for migrates the database on first start, and the version you actually wanted will then refuse to run against it.

- **The automatic pre-upgrade backup survives a failed upgrade.** Koios takes a database backup before applying database changes, so an upgrade that fails part-way has something to go back to. Where one did fail, Koios restarted and immediately took another backup — of the partly-upgraded database, over the first one — and repeated that on every restart, so the copy worth keeping was gone within seconds of the failure it existed for. That backup is now left untouched until the database changes complete, and the startup summary says whether it was taken or kept. Taking your own backup before upgrading is still the right habit.
