Koios Component Builder
SDK and CLI for building component libraries for the Koios platform. Components are reusable logic blocks with typed inputs and outputs that get wired together and executed by the Koios Component Engine at configurable scan rates.
Installation
pip install koios-component-builder
Requires Python 3.12+.
Quick Start
1. Write a 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
2. Define a library
# my_library/__init__.py
from koios_component_builder import ComponentLibrary
from .math_ops import SimpleAdder, Multiplier
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"]
3. Export
koios-component-builder export my_library/
This creates a .kcl (Koios Component Library) package in dist/ that can be uploaded to Koios.
Component Lifecycle
When deployed to Koios, each execution cycle:
- Field Injection — Input and config values are set on the component
- Setup —
setup()runs once before the firstexecute()(if overridden) - Execute —
execute()runs with current input values - Output Propagation — Output values are sent to wired destinations
Your component implements setup() (optional) and execute() — the engine handles all wiring and data flow.
setup() — One-Time Initialization
Override setup() for expensive work that should only happen once, such as loading models, creating registries, or parsing configuration. All field values are available when setup() runs.
If setup() raises an exception, the component is marked FAILED and retried on the next cycle.
class UnitConverter(Component):
"""Converts Fahrenheit to Celsius using pint."""
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() # ~80ms — only runs once
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))
For resources shared across all instances of the same component class, use a class-level guard 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)
Field Types
Input / Output
from koios_component_builder import Component, Input, Output
class ExampleComponent(Component):
# Supported types: float, int, bool, str, list, dict
temperature: Input[float] = Input(default=0.0, description="Temperature in Celsius")
count: Input[int] = Input(default=0, description="Item count")
enabled: Input[bool] = Input(default=True, description="Enable processing")
mode: Input[str] = Input(default="auto", description="Operating mode")
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 = True
self.status = "overtemp"
else:
self.alarm = False
self.status = "ok"
Config Fields
Config fields are set when the component instance is created in the Koios UI and remain constant during execution. They appear as configuration controls on the component node.
from koios_component_builder import (
Component, Input, Output,
NumberConfig, StringConfig, ChoiceConfig, BoolConfig,
)
class ConfigurableComponent(Component):
# Numeric with min/max validation
threshold: NumberConfig = NumberConfig(
default=75.0, min_value=0.0, max_value=100.0,
description="Alert threshold"
)
# Dropdown with predefined options
mode: ChoiceConfig = ChoiceConfig(
default="average", choices=["average", "median", "max"],
description="Calculation mode"
)
# Text with optional regex validation
label: StringConfig = StringConfig(
default="Sensor", description="Display label"
)
# Boolean toggle
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
File Fields
FileConfig renders as an upload control in the Koios UI. At runtime the
component receives a ComponentFile handle — not a raw path — pointing at the
file the operator 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"], # leading dot optional, case ignored
mime_types=["application/octet-stream"],
max_bytes=200 * 1024 * 1024,
required=True,
)
def setup(self) -> None:
# required=True, so Koios guarantees the file is here before setup runs
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(), not execute() — setup() runs once, and Koios
re-runs it after an operator replaces the file, so a swap takes effect without
a restart.
required decides what happens when no file is uploaded, so you never
write that check yourself:
| Behavior | |
|---|---|
required=True |
Koios fails the instance with a specific message before setup() runs. Component code can assume the file is present. |
required=False |
The field is None. Guard with if self.field:. |
Handle API — path, name, suffix, size_bytes, content_type,
sha256, uploaded_at, version, plus read_bytes(), read_text(),
open() and exists(). Reading through the handle rather than calling the
open builtin keeps your component clear of the security audit's file-I/O
review tier.
class Lookup(Component):
table: FileConfig = FileConfig(extensions=[".csv"]) # optional
def setup(self) -> None:
self.rows = self.table.read_text().splitlines() if self.table else []
Koios enforces the constraints server-side, so your declaration holds no
matter what uploads the file. Extension is the deterministic gate; MIME 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 — and a value above that ceiling is clamped down to it, so a field can
lower the cap but never raise it. Files are never executed by Koios; they are
handed to the component that asked for them.
HistoryInput
HistoryInput fields provide access to historical tag data via InfluxDB. They must be wired to a HISTORY connector on the environment 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 # Not wired to a history connector
# Fetch last hour of data, max 200 samples
df = self.sensor_history.get_history(
period_seconds=3600,
num_samples=200,
)
# df has columns: timestamp, value
if not df.empty:
values = df["value"].tolist()
self.trend = values[-1] - values[0]
Component Metadata
Customize how components appear in the Koios UI:
class MyComponent(Component):
"""Component description shown in the UI."""
class Meta:
icon = ComponentIcon.CHART_LINE # Tabler icon name
category = ComponentCategory.ANALYSIS # UI grouping
canvas_width = 8 # Node width (4–15 grid units, default: 6)
canvas_minimal = False # Compact mode (no header/footer)
# Optional: component-specific version (overrides library version)
major = 2
minor = 1
patch = 0
prerelease = "beta"
Icons — Any Tabler icon name in kebab-case. Common constants: SUM, CALCULATOR, CHART_LINE, GAUGE, THERMOMETER, FILTER, WAVE_SINE, TOGGLE_LEFT, ALERT_TRIANGLE, TRANSFORM.
Categories — Standard constants: MATH, STATISTICS, LOGIC, ANALYSIS, TRANSFORM, FILTER, CONTROL, MONITORING. Custom strings are also accepted.
Pin Layout
By default pins are laid out on the canvas in the order they appear in your class body. For control-systems blocks where grouping matters — setpoints together, tuning constants together, status outputs apart — you can declare an explicit arrangement in Meta and insert gaps between pins. The arrangement ships in the .kcl manifest so every instance of the component starts with the same visual default. Users can further tweak the layout per-instance from the Koios UI.
from koios_component_builder import (
Component,
ComponentCategory,
ComponentIcon,
Gap,
Input,
NumberConfig,
Output,
)
class PIDController(Component):
"""Discrete PID controller with anti-windup."""
class Meta:
icon = ComponentIcon.GAUGE
category = ComponentCategory.CONTROL
canvas_width = 8
# Group setpoint + process variable, give the tuning constants their
# own visual block, separate the enable/reset inputs at the bottom,
# and keep the diagnostic outputs distinct from the control output.
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")
output_min: NumberConfig = NumberConfig(default=-100.0, description="Output lower bound")
output_max: NumberConfig = NumberConfig(default=100.0, description="Output upper bound")
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")
def execute(self) -> None:
... # PID math
Gap(size=1) — Insert a vertical spacer between pins. size is in grid units (1 row = the height of one pin); defaults to 1.
Validation — Strings in inputs_layout / outputs_layout are checked against your declared Input / Output / HistoryInput fields at class-creation time. A typo raises ComponentDefinitionError on import, not at runtime.
Partial layouts — Pins declared on the class but absent from the layout list are appended in declaration order, so you can add new pins to a component without touching the layout.
Empty layouts — If neither list is declared, pins use class-body order with no gaps — the same behavior components had before this feature.
Note on
Input(order=...)— The old per-fieldorder=parameter is deprecated forInput,Output, andHistoryInput. UseMeta.inputs_layout/Meta.outputs_layoutinstead — it gives finer control (gaps, explicit grouping) and the contract is clearer to read at the top of the class. Theorder=parameter onConfigfamily fields is unaffected.
Dependencies
Libraries can declare third-party Python package dependencies. The builder resolves them against the Koios platform manifest to determine what's pre-installed in the container vs. what needs bundling.
class MyProtocolLibrary(ComponentLibrary):
name = "my-protocol-library"
major = 1
minor = 0
dependencies = ["crcmod", "minimalmodbus>=2.0"]
components = [MyDevice]
Three tiers:
| Tier | Description | Example |
|---|---|---|
| Platform | Pre-installed in the Koios container | numpy, pandas, scipy |
| Bundled | Downloaded and included in the .kcl |
crcmod, pint |
| SDK | Always available (koios-component-builder itself) | pydantic, click |
# Export without bundling (warns about non-platform deps)
koios-component-builder export my_library/
# Bundle non-platform dependencies into the .kcl (deprecated — prefer a
# package stack attached to the component environment in Koios)
koios-component-builder export my_library/ --include-deps
# Target specific platforms
koios-component-builder export my_library/ --include-deps \
--platform manylinux2014_x86_64 --platform manylinux2014_aarch64
# List available platform packages
koios-component-builder platform-packages
System binaries
platform-packages lists Python distributions only. Some libraries shell out to
a solver or CLI tool instead, and those are installed as system packages in the
Koios image — they will never appear in that listing, and declaring them as a
dependency will not work.
| Binary | Provides | Available from |
|---|---|---|
glpsol |
GLPK — LP and MIP solver | Koios 1.2.0 |
A pyomo model can call it directly:
import pyomo.environ as pyo
results = pyo.SolverFactory("glpk").solve(model)
pyomo itself is not pre-installed — declare it as a dependency and export with
--include-deps.
For a solver that ships as a Python wheel and needs no system package, use
HiGHS via highspy: add it to dependencies and it bundles
like any other third-party package. HiGHS is generally faster than GLPK on larger
LP/MIP problems.
Exporting a stack bundle (.kps)
A component stack is a named, isolated set of packages inside Koios that
component environments can be attached to. It exists for dependencies that
conflict with the platform's own — a library capping pandas<3 when the
container ships pandas 3, for example.
Wheels can be uploaded one at a time in the Koios UI, or exported here as a
single .kps bundle:
# Bundle a requirements file (repeatable; --name defaults to the file's name)
koios-component-builder export-stack -r requirements.txt
# Bundle everything installed in the environment you build in
koios-component-builder export-stack --from-env --name ml-tools
# Target a specific platform / Python version, and allow a longer download
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 + arm64 by
default — never copied out of your local site-packages, so a bundle built on a
Mac installs on the on-prem Linux container. Packages the Koios container
already provides are pinned during resolution (a hard conflict fails the
download rather than shipping a mismatched transitive dependency) and then
excluded from the bundle, exactly as --include-deps treats them. Each target
platform is resolved in its own download, so a package with per-architecture
wheels ships one for each.
Every target platform must resolve. A package with no aarch64 wheel fails
the whole export rather than producing a bundle that installs on one
architecture and silently claims both. Two ways forward: pass --platform manylinux2014_x86_64 (or whichever architecture you deploy on) to bundle for
just that one, or pin the package to a version that publishes wheels for both.
The same applies when the platforms resolve a package to different versions —
the manifest records one version per package, so the export names the package
and the versions and stops.
Requirements files may include other files with -r other.txt; other pip
option lines (--index-url, -e, -f) are rejected. --from-env additionally
leaves out the SDK's own dependencies — pydantic, click, packaging and
what they pull in are always installed alongside the builder, and the container
already provides them. Every export ends by listing what it withheld and why:
--from-env accounts for every installed name, and a requirements export names
the requirements you gave it that the container already provides. Dependencies
pulled in transitively are resolved by pip and reported only in the log.
No Koios distribution is ever bundled. koios-component-builder,
koios-component-engine, koiosutility and koioslicense are the packages the
engine loads your components with; a stack carrying its own copy replaces them
at import time and the worker cannot start. --from-env leaves every koios*
distribution out — the machine you build on always has the SDK installed — and
naming one of the four in a requirements file stops the export.
Import the resulting file in the Koios UI under Components > Stacks.
ml-tools.kps
├── manifest.json # Bundle metadata
└── wheels/
├── mlflow-3.13.0-py3-none-any.whl
└── pandas-2.3.3-cp312-cp312-manylinux2014_x86_64.whl
manifest.json records the bundle format version, stack name, build time,
target Python version and platforms, the requirements it was resolved from, and
every bundled package with its wheel filenames.
Trust — code installed into a stack runs with full platform privileges on the Koios server. Only bundle wheels from sources you trust.
Bundling a library's dependencies into its .kcl with --include-deps is
deprecated and will be removed in a future major release. Provide
third-party packages by attaching a package stack to the component environment
in Koios instead: stacks are curated per environment, isolated from the
platform's versions, and shared across every library that runs there, where
bundled wheels are injected into whichever worker runs the component.
--include-deps continues to work in the meantime and prints a warning.
Package Format
The export command creates a .kcl (Koios Component Library) package — a ZIP archive:
my-library-1.0.0.kcl
├── manifest.json # Library metadata
├── my_library-1.0.0-py3-none-any.whl # Python wheel
└── deps/ # Bundled dependency wheels (if any)
├── crcmod-1.7-cp312-...-x86_64.whl
└── crcmod-1.7-cp312-...-aarch64.whl
Security Audit
Every .kcl export automatically runs a static security analysis on your source code. The audit classifies patterns into three tiers:
| Tier | Meaning | Effect |
|---|---|---|
| ✅ ALLOW | Safe — no friction | Not reported |
| ⚠️ REVIEW | Flagged for attention, but build continues | Logged as warning, yellow badge in Koios UI |
| ❌ DENY | Blocked | Export fails unless --allow-unsafe is used |
What gets flagged
DENY — patterns that have no legitimate use in a component:
- Dangerous imports:
os,subprocess,socket,threading,pickle,ctypes,sys, etc. - Unsafe builtins:
eval(),exec(),compile(),__import__() - Sandbox escape patterns:
__subclasses__,__builtins__,__code__,__globals__
REVIEW — patterns that are often legitimate but worth being aware of:
- Filesystem imports:
pathlib,io,csv(common for loading AI model files insetup()) open()calls (useful for reading model weights or config files)- Unknown third-party packages not in the platform allow-list
Audit command
Run the audit without building a .kcl:
# Audit and print results
koios-component-builder audit my_library/
# Output as JSON (for CI integration)
koios-component-builder audit my_library/ --json
# Audit with a custom policy
koios-component-builder audit my_library/ --policy security_policy.yaml
Exit code is 1 if any DENY findings are present, 0 otherwise — suitable for CI pipelines.
Custom policies
Override the defaults by providing a YAML policy file:
# security_policy.yaml
policy_version: "1.0"
imports:
allow:
- my_internal_sdk # Trust your own internal package
- requests # Allow HTTP if your components genuinely need it
deny:
- pandas # Tighten if you want to forbid heavy deps
builtins:
review:
- open # Already REVIEW by default; shown here for reference
attributes:
deny:
- __dict__ # Add to deny if you want stricter introspection rules
Policy overrides work bidirectionally — you can move items to a less restrictive tier (e.g. DENY → ALLOW) or a more restrictive one (e.g. ALLOW → DENY). Items are removed from conflicting tiers automatically.
koios-component-builder export my_library/ --policy security_policy.yaml
Overriding for power users
If your library has a legitimate reason for a flagged pattern (e.g. loading an ONNX model from disk in setup()), the recommended approach is to use pathlib / io which are REVIEW-tier rather than DENY. For edge cases that genuinely need a denied import:
koios-component-builder export my_library/ --allow-unsafe
The .kcl is still created, but the security_audit in manifest.json records passed: false — the Koios UI will display a warning badge when the library is uploaded.
CLI Reference
# Export a library to a .kcl package
koios-component-builder export <source_path> [OPTIONS]
Options:
-o, --output PATH Output directory (default: dist/)
--include-deps Bundle non-platform dependencies (deprecated)
--platform TEXT Target platform tag (repeatable)
--allow-unsafe Create .kcl even with DENY findings
--policy PATH Custom security policy YAML file
# Export a stack bundle to a .kps package
koios-component-builder export-stack [OPTIONS]
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 (default: container's)
--timeout INTEGER Seconds to allow the download (default: 900)
# Audit a library without building
koios-component-builder audit <source_path> [OPTIONS]
Options:
--policy PATH Custom security policy YAML file
--json Output results as JSON
# List pre-installed platform packages
koios-component-builder platform-packages
Local Testing
Test components locally before deploying:
from my_library.math_ops import SimpleAdder
adder = SimpleAdder("test-instance")
adder.a = 5.0
adder.b = 3.0
adder.execute()
print(f"Result: {adder.result}") # Result: 8.0
License
Copyright Ai-OPs, Inc. All rights reserved.
Metadata
Release files for koios-component-builder 1.3.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| koios_component_builder-1.3.1.tar.gz | 136.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| koios_component_builder-1.3.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 217.3 kB
Release files / koios_component_builder-1.3.1.tar.gz
| Download URL | koios_component_builder-1.3.1.tar.gz |
|---|---|
| Size | 136.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
3dfd986bed69909f9c209d78a7ede8ddf5d7e06898a15eb8ecdc26a64182dd77
|
|
BLAKE2b-256 checksum How to use checksums |
e6c4ea439fde8c519eb0d19b18457aa86c6a564fc7dbc197c49ebb5bd60def97
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 19, 2026.
Transparency logRelease files / koios_component_builder-1.3.1-py3-none-any.whl
| Download URL | koios_component_builder-1.3.1-py3-none-any.whl |
|---|---|
| Size | 80.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
4f4ad5775856d56cf076d1794fff95ce1bb9352d77d8e219e934b7ef4362613d
|
|
BLAKE2b-256 checksum How to use checksums |
0e62e83f09ef670efbddab7e9d2f3b7c03ce63d8eb572f0377d514c6c3116312
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 19, 2026.
Transparency log