Ai-OPs
ai-ops.com
Docs
/
Python Packages
/

Component Builder SDK

Component Builder SDK

koios-component-builder is the SDK and command-line tool for building component libraries. A component is a reusable block of logic with typed inputs and outputs that Koios wires together and executes at a configurable scan rate.

This page is the author's reference. For using components once they are installed, see Components and The Component Canvas.

Installation

pip install koios-component-builder

Requires Python 3.12 or newer.

Your first component

# my_library/math_ops.py
from koios_component_builder import (
    Component,
    ComponentCategory,
    ComponentIcon,
    Input,
    Output,
)


class SimpleAdder(Component):
    """A component that adds two numbers."""

    class Meta:
        icon = ComponentIcon.SUM
        category = ComponentCategory.MATH

    a: Input[float] = Input(default=0.0, description="First number")
    b: Input[float] = Input(default=0.0, description="Second number")
    result: Output[float] = Output(default=0.0, description="Sum of a and b")

    def execute(self) -> None:
        self.result = self.a + self.b

Group components into a library:

# my_library/__init__.py
from koios_component_builder import ComponentLibrary
from .math_ops import Multiplier, SimpleAdder


class MyLibrary(ComponentLibrary):
    """My custom component library."""

    name = "my-library"
    major = 1
    minor = 0
    patch = 0
    description = "Custom math components"

    components = [SimpleAdder, Multiplier]


__all__ = ["MyLibrary", "SimpleAdder", "Multiplier"]

Then package it:

koios-component-builder export my_library/

That writes a .kcl package into dist/, ready to upload. See Component Libraries.

How a component runs

Each execution cycle does four things:

  1. Input and configuration values are set on the component
  2. setup() runs, once, before the first execution
  3. execute() runs with the current input values
  4. Output values are sent wherever they are wired

You write setup() and execute(). Koios handles wiring and data flow.

Doing expensive work once

Override setup() for anything that should not repeat every cycle — loading a model, building a registry, parsing configuration. Every field value is available by the time it runs.

class UnitConverter(Component):
    """Converts Fahrenheit to Celsius."""

    class Meta:
        icon = ComponentIcon.TRANSFORM
        category = ComponentCategory.TRANSFORM

    value: Input[float] = Input(default=0.0, description="Temperature in °F")
    decimals: NumberConfig = NumberConfig(
        default=2, min_value=0, max_value=6, description="Decimal places"
    )
    result: Output[float] = Output(default=0.0, description="Temperature in °C")

    def setup(self) -> None:
        from pint import UnitRegistry
        self._ureg = UnitRegistry()

    def execute(self) -> None:
        temp = self._ureg.Quantity(self.value, self._ureg.degF)
        self.result = round(temp.to(self._ureg.degC).magnitude, int(self.decimals))

If setup() raises, the instance is marked failed and retried next cycle.

For something shared across every instance of a class rather than per instance, guard at class level instead:

class MyComponent(Component):
    def execute(self) -> None:
        if not hasattr(MyComponent, "_shared_model"):
            MyComponent._shared_model = load_model()
        self.result = MyComponent._shared_model.predict(self.input)

Fields

Inputs and outputs

Supported types are float, int, bool, str, list, and dict.

class ExampleComponent(Component):
    temperature: Input[float] = Input(default=0.0, description="Temperature in Celsius")
    enabled: Input[bool] = Input(default=True, description="Enable processing")

    alarm: Output[bool] = Output(default=False, description="High temperature alarm")
    status: Output[str] = Output(default="ok", description="Current status")

    def execute(self) -> None:
        if self.enabled and self.temperature > 100:
            self.alarm, self.status = True, "overtemp"
        else:
            self.alarm, self.status = False, "ok"

Configuration

Configuration fields are set when an operator creates the instance and stay constant while it runs. They appear as controls on the component node.

from koios_component_builder import (
    BoolConfig, ChoiceConfig, Component, Input, NumberConfig, Output, StringConfig,
)

class ConfigurableComponent(Component):
    threshold: NumberConfig = NumberConfig(
        default=75.0, min_value=0.0, max_value=100.0, description="Alert threshold"
    )
    mode: ChoiceConfig = ChoiceConfig(
        default="average", choices=["average", "median", "max"],
        description="Calculation mode",
    )
    label: StringConfig = StringConfig(default="Sensor", description="Display label")
    verbose: BoolConfig = BoolConfig(default=False, description="Enable verbose output")

    value: Input[float] = Input(default=0.0)
    alert: Output[bool] = Output(default=False)

    def execute(self) -> None:
        self.alert = self.value > self.threshold

Files

FileConfig gives the operator an upload control. At runtime your component receives a handle, not a raw path, pointing at the file uploaded for that instance. Each instance has its own file, and uploads are versioned, so an operator can swap a model and revert.

from koios_component_builder import Component, FileConfig, Input, Output

class Scorer(Component):
    reading: Input[float] = Input(default=0.0)
    score: Output[float] = Output(default=0.0)

    model_file: FileConfig = FileConfig(
        description="Trained ONNX model",
        extensions=[".onnx", ".tflite"],
        mime_types=["application/octet-stream"],
        max_bytes=200 * 1024 * 1024,
        required=True,
    )

    def setup(self) -> None:
        import onnxruntime
        self.session = onnxruntime.InferenceSession(str(self.model_file.path))

    def execute(self) -> None:
        self.score = float(self.session.run(None, {"x": [[self.reading]]})[0])

Load the file in setup() rather than execute(). Koios re-runs setup() after an operator replaces the file, so a swap takes effect without a restart.

required decides what happens when nothing is uploaded, so you never write that check yourself:

Behavior
required=TrueThe instance fails with a specific message before setup() runs, so your code can assume the file is there
required=FalseThe field is None. Guard with if self.field:

The handle exposes path, name, suffix, size_bytes, content_type, sha256, uploaded_at, and version, plus read_bytes(), read_text(), open(), and exists(). Reading through the handle instead of the open builtin keeps your component out of the security audit's file-I/O tier.

class Lookup(Component):
    table: FileConfig = FileConfig(extensions=[".csv"])

    def setup(self) -> None:
        self.rows = self.table.read_text().splitlines() if self.table else []

Koios enforces your constraints on the server, so the declaration holds no matter what uploads the file. Extension is the reliable gate; MIME type only filters the file dialog, since a browser can claim anything and .onnx is in no MIME registry. max_bytes is optional — omit it and a platform ceiling applies. A value above that ceiling is clamped to it, so a field can lower the cap but never raise it. Files are never executed; they are handed to the component that asked for them.

See Files for components.

Historical data

A HistoryInput reads recorded values from historical storage. It must be wired to a history connector on the canvas.

from koios_component_builder import Component, HistoryInput, Output

class TrendAnalyzer(Component):
    """Calculates trend from historical data."""

    sensor_history: HistoryInput = HistoryInput(description="Historical sensor readings")
    trend: Output[float] = Output(default=0.0, description="Trend slope")

    def execute(self) -> None:
        if self.sensor_history is None:
            return

        df = self.sensor_history.get_history(period_seconds=3600, num_samples=200)
        if not df.empty:
            values = df["value"].tolist()
            self.trend = values[-1] - values[0]

The returned frame has timestamp and value columns.

Appearance

class MyComponent(Component):
    """Component description shown to operators."""

    class Meta:
        icon = ComponentIcon.CHART_LINE
        category = ComponentCategory.ANALYSIS
        canvas_width = 8          # 4-15 grid units, default 6
        canvas_minimal = False    # compact mode, no header or footer

        major = 2                 # optional, overrides the library version
        minor = 1
        patch = 0
        prerelease = "beta"

Icons take any Tabler icon name in kebab-case; constants include SUM, CALCULATOR, CHART_LINE, GAUGE, THERMOMETER, FILTER, WAVE_SINE, TOGGLE_LEFT, ALERT_TRIANGLE, and TRANSFORM. Categories include MATH, STATISTICS, LOGIC, ANALYSIS, TRANSFORM, FILTER, CONTROL, and MONITORING, and custom strings are accepted.

Arranging pins

Pins follow class-body order by default. Where grouping matters — setpoints together, tuning constants together, status outputs apart — declare the arrangement in Meta and insert gaps. It ships in the package, so every instance starts from the same layout, and an operator can still adjust one instance.

from koios_component_builder import Component, ComponentIcon, Gap, Input, Output

class PIDController(Component):
    """Discrete PID controller with anti-windup."""

    class Meta:
        icon = ComponentIcon.GAUGE
        canvas_width = 8

        inputs_layout = [
            "setpoint",
            "process_variable",
            Gap(),
            "kp", "ki", "kd",
            Gap(size=2),
            "enable", "reset",
        ]
        outputs_layout = ["control_output", Gap(), "saturated", "integral"]

    setpoint: Input[float] = Input(default=0.0, description="Target value")
    process_variable: Input[float] = Input(default=0.0, description="Measured value")
    kp: Input[float] = Input(default=1.0, description="Proportional gain")
    ki: Input[float] = Input(default=0.0, description="Integral gain")
    kd: Input[float] = Input(default=0.0, description="Derivative gain")
    enable: Input[bool] = Input(default=True, description="Enable control")
    reset: Input[bool] = Input(default=False, description="Reset integrator")

    control_output: Output[float] = Output(default=0.0, description="Manipulated variable")
    saturated: Output[bool] = Output(default=False, description="Output is at a limit")
    integral: Output[float] = Output(default=0.0, description="Current integrator state")

Gap(size=1) inserts a spacer measured in pin heights. Names are validated when the class is created, so a typo raises on import rather than at runtime. Pins you leave out of a layout are appended in declaration order, so adding a pin does not force a layout edit.

Dependencies

A library can declare third-party packages. The builder resolves them against the platform manifest to work out what is already available.

class MyProtocolLibrary(ComponentLibrary):
    name = "my-protocol-library"
    major = 1
    minor = 0
    dependencies = ["crcmod", "minimalmodbus>=2.0"]
    components = [MyDevice]
TierMeaningExample
PlatformAlready installednumpy, pandas, scipy
BundledDownloaded into the packagecrcmod, pint
SDKAlways availablepydantic, click
koios-component-builder export my_library/
koios-component-builder platform-packages

Tools that are not Python packages

platform-packages lists Python distributions only. Some libraries shell out to a solver instead, installed as a system package — those never appear in that listing, and declaring them as a dependency will not work.

ToolProvidesAvailable from
glpsolLinear and mixed-integer programming solverKoios 1.2.0
import pyomo.environ as pyo

results = pyo.SolverFactory("glpk").solve(model)

pyomo itself is not pre-installed, so declare it as a dependency. For a solver that ships as a wheel and needs no system package, highspy bundles like any other package and is generally faster on larger problems.

Building a stack bundle

A stack is a named, isolated set of packages that component environments can attach to. It exists for dependencies that conflict with the platform's own — a library capping a package below the version the platform ships, for example.

Wheels can be uploaded individually, or exported here as a single .kps bundle:

koios-component-builder export-stack -r requirements.txt

koios-component-builder export-stack --from-env --name ml-tools

koios-component-builder export-stack -r requirements.txt \
    --platform manylinux2014_x86_64 --python-version 3.12 --timeout 1800

Wheels are downloaded for the target platforms — linux amd64 and arm64 by default — never copied from your local environment, so a bundle built on a Mac installs on the server. Packages the platform already provides are pinned during resolution and then left out.

Every target platform must resolve. A package with no wheel for one architecture fails the whole export rather than producing a bundle that installs on one and silently claims both. Either pass --platform for the architecture you deploy on, or pin the package to a version publishing wheels for both. The same applies if the platforms resolve to different versions: the manifest records one version per package, so the export names them and stops.

Requirements files may include others with -r other.txt; other option lines are rejected. Every export ends by listing what it withheld and why.

No Koios distribution is ever bundled — those are the packages the engine loads your components with, and a stack carrying its own copy replaces them at import time and the worker cannot start. Naming one in a requirements file stops the export.

ml-tools.kps
├── manifest.json
└── wheels/
    ├── mlflow-3.13.0-py3-none-any.whl
    └── pandas-2.3.3-cp312-cp312-manylinux2014_x86_64.whl

Package format

my-library-1.0.0.kcl
├── manifest.json
├── my_library-1.0.0-py3-none-any.whl
└── deps/
    ├── crcmod-1.7-cp312-...-x86_64.whl
    └── crcmod-1.7-cp312-...-aarch64.whl

Security audit

Every export runs a static analysis of your source and sorts what it finds into three tiers.

TierMeaningEffect
AllowSafeNot reported
ReviewWorth attentionLogged as a warning, and marked when the library is uploaded
DenyBlockedExport fails unless you override it

Denied are patterns with no legitimate use in a component: imports such as os, subprocess, socket, threading, pickle, ctypes and sys; the eval, exec, compile and __import__ builtins; and sandbox-escape attributes like __subclasses__, __builtins__, __code__ and __globals__.

Reviewed are patterns that are often legitimate: pathlib, io and csv, calls to open, and third-party packages outside the platform allow-list.

koios-component-builder audit my_library/
koios-component-builder audit my_library/ --json
koios-component-builder audit my_library/ --policy security_policy.yaml

The audit exits 1 when anything is denied, which makes it usable as a build step.

Custom policies

policy_version: "1.0"

imports:
  allow:
    - my_internal_sdk
    - requests
  deny:
    - pandas

builtins:
  review:
    - open

attributes:
  deny:
    - __dict__

Overrides work in both directions: move something to a looser tier or a stricter one, and it is removed from any conflicting tier automatically.

If a component genuinely needs a flagged pattern — loading a model from disk in setup(), say — prefer pathlib or io, which are reviewed rather than denied. For the rare case that needs a denied import, --allow-unsafe still produces the package, but the manifest records that the audit did not pass and the library is marked accordingly when uploaded.

Command reference

koios-component-builder export <source_path> [OPTIONS]
  -o, --output PATH        Output directory (default: dist/)
  --include-deps           Bundle non-platform dependencies (deprecated)
  --platform TEXT          Target platform tag (repeatable)
  --allow-unsafe           Package even with denied findings
  --policy PATH            Custom security policy file

koios-component-builder export-stack [OPTIONS]
  -r, --requirements PATH  Requirements file to bundle (repeatable)
  --from-env               Bundle the current environment instead
  --name TEXT              Stack name (required with --from-env)
  -o, --output PATH        Output directory (default: dist/)
  --platform TEXT          Target platform tag (repeatable)
  --python-version TEXT    Python version to resolve for
  --timeout INTEGER        Seconds to allow for the download (default: 900)

koios-component-builder audit <source_path> [OPTIONS]
  --policy PATH            Custom security policy file
  --json                   Output results as JSON

koios-component-builder platform-packages

Testing locally

Components run outside Koios, so you can exercise one directly:

from my_library.math_ops import SimpleAdder

adder = SimpleAdder("test-instance")
adder.a = 5.0
adder.b = 3.0
adder.execute()

print(adder.result)