Components are the extension mechanism used by k.LAB services to add capabilities without
rebuilding the service that hosts them. A component is a PF4J plug-in that implements the k.LAB
component contract and is packaged as a .kar archive by the k.LAB Maven packaging goal. The
.kar is intended to be a stripped, dependency-free archive: it contains the component code and
metadata, while common k.LAB and service dependencies are supplied by the hosting service.
Once imported, a component is registered in the service's local component registry. The registry loads the archive, scans it for k.LAB extension annotations, builds a descriptor, publishes the new capabilities through the service, and keeps enough source information to support maintenance and optional updates.
Each service keeps its own component repository. In the standard service data layout this is under:
services/<service-type>/components/
The repository contains three separate concerns:
components/
catalog.json
history.jsonl
cache/
catalog.json
...
plugins/
*.jar
plugins/ is the only directory scanned by PF4J. Imported .kar files are copied or installed
there with a .jar extension, because PF4J loads Java archives. Keeping loadable archives under
plugins/ prevents unrelated repository files, including the Maven cache, from being interpreted
as plug-ins.
cache/ is used for Maven-sourced artifacts. It records the component archive discovered in the
local Maven repository or downloaded from a remote repository, together with content hashes and
timestamps used to decide whether an update is needed.
catalog.json is the registry's local component catalog. It records the descriptors for components
known to the service, including the source archive, source hash, Maven coordinates when applicable,
usage rights, exported capabilities, and the time of registration or update.
history.jsonl is an append-only service-local audit trail. Each line is an independent JSON
event, so a damaged final write does not invalidate the earlier history. Events identify the
component and version, hosting service, import type, source service, outcome, message, and any
source-specific details.
When an older repository contains loadable .jar files directly in the repository root, startup
migrates them into plugins/ when possible. Files that are not plug-ins should remain outside
plugins/.
A component can contribute several kinds of service-visible functionality. A single component may bring any combination of these.
A library is a namespace for k.LAB service verbs and language extensions. It can define:
- k.IM, observation-language, or runtime functions through
@KlabFunction. - k.Actors verbs through
@Verb. - k.LAB annotations and handlers through
@KlabAnnotation. - Import schemata through
@Importer. - Export schemata through
@Exporter.
Library names become part of the public name of the contributed functions, verbs, annotations, importers, and exporters. This keeps component contributions distinguishable from core k.LAB extensions and from other components.
Actors are specialized libraries for k.Actors use. They can expose singleton actors or actor
classes that behaviors import with a using clause. Actor classes may also define verbs that
apply to the actor. Component actors should use explicit path-like names so they remain unique
outside the core distribution.
Resource adapters teach a Resources service how to understand, validate, contextualize, publish, inspect, or encode a class of resources. A resource adapter declares a unique adapter name, version, supported geometry, resource type, parameter schema, concurrency model, and optional runtime constraints such as split behavior or maximum geometry size.
Adapter methods can implement lifecycle operations such as:
- URN syntax validation.
- Local import and staging validation.
- Pre-contextualization checks and cache preparation.
- Encoding and contextualization.
- Inspection, sanitization, publication, and type attribution.
Adapters may be embeddable or service-bound. An embeddable adapter can be used locally by a runtime close to the data. A service-bound adapter must be invoked through the Resources service that provides it, which is appropriate when the adapter depends on server-side state or protected infrastructure.
Authorities connect the Reasoner to external terminologies and classifications that are too large
or too specialized to load into a worldview in full. Component classes implementing
org.integratedmodelling.klab.api.services.Authority are discovered through the Java @Authority
annotation. Their descriptors
advertise a stable provider URN, whether the provider is embeddable, and any known sub-authorities.
Resources services advertise and deliver authority components but do not host authority
implementations. A Reasoner instantiates them. Embeddable providers may be transferred from
Resources and installed by a Reasoner on demand; non-embeddable providers are available only on
Reasoners where their component was explicitly installed. A worldview binds the provider URN to a
local name and configuration, independently for each requires authority declaration. The full
provider, binding, and reasoning contract is documented in Authorities.
The intended source integrity rule limits authority offerings to the certified Resources services contributing to the loaded worldview, including authorized higher-tier contributors. Generic component distribution by an ordinary Resources service does not confer that role. Enforcement and an explicit full-local development association remain pending; see worldview provider integrity.
Importers and exporters describe transport formats for k.LAB assets. They may be defined in a library or attached to a resource adapter. A schema can be based on binary content, such as a file or stream, or on named properties.
The built-in component I/O library currently defines:
component.kar.import: import a component by uploading a.kararchive.component.maven.import: import a component from Maven coordinates.component.jar.export: export an installed component archive.
Other components can add domain-specific import and export schemata for resources or other assets.
@KlabFunction contributions become executable service implementations. Depending on their
signature and metadata, they may serve as contextualizers, filters, producers, value functions, or
other runtime functions used by k.IM, k.Actors, and the observation language. Their descriptors
include declared parameters, inputs, outputs, geometry constraints, artifact type, fill curve, and
parallelization hints.
A direct import uploads a component archive to a service. The registry validates that the archive
looks like a Java archive with a manifest, copies it into plugins/, loads it with PF4J, and scans
it for k.LAB contributions.
Direct imports are intentionally local. They do not record Maven coordinates, so the registry
cannot automatically rediscover a newer build. The hosting service therefore reports the imported
archive as up to date until a replacement .kar is uploaded. Updating a directly imported
component means uploading that replacement or importing a newer version. Services that copied the
component as a dependency can still discover the replacement through the hosting Resources
service's component descriptor.
A Maven import identifies the component with:
groupId:artifactId:version
The registry asks the Maven component cache for the artifact with classifier component and
suffix kar. The cache first looks in the local Maven repository, then in configured remote Maven
repositories when needed. The resulting .kar is cached, installed into plugins/ as a .jar,
loaded with PF4J, and registered with its Maven coordinates.
Maven-sourced components keep their coordinates in the component descriptor. That source information is what enables later update checks.
An installed component can be exported as a Java archive and imported by another service. A
component resolved from a Resources service is recorded as a DEPENDENCY, including that source
service id and timestamp. When known, Resources also transports the component's Maven coordinates
as provenance. Those coordinates permit a local Maven override; they do not turn the dependency
into an independently Maven-managed import. A manually exported archive uploaded without that
provenance is a direct local FILE import instead.
The four acquisition paths and their update owners are therefore:
| Acquisition path | Installed type | Update owner |
|---|---|---|
Local .kar uploaded to a Resources service |
FILE |
An administrator uploads a replacement. |
| Local Maven repository | MAVEN |
The importing service's Maven cache checks a changed local artifact for a SNAPSHOT. |
| Remote Maven repository | MAVEN |
The importing service's Maven cache checks remote SNAPSHOT metadata and downloads when required. |
| Local or remote Resources service | DEPENDENCY |
The consumer first accepts a newer same-version SNAPSHOT from its local Maven repository; otherwise the advertising Resources service is authoritative. |
The Resources host may itself have acquired the component through any of the first three paths. A
dependent service never queries or downloads from a remote Maven repository. Its complete priority
order is: a newer local Maven SNAPSHOT, then the descriptor and archive installed at the exact
advertising Resources service. Thus a developer's local mvn install takes precedence, while in
all other cases Resources remains authoritative even if a newer remote Maven artifact exists.
After PF4J loads a component, the registry scans it and builds a ComponentDescriptor. The
descriptor records:
- The component id, version, and description from the plug-in descriptor.
- The local source archive and its file hash.
- Maven coordinates, when the component came from Maven.
- Usage rights inferred from the plug-in manifest.
- Libraries, actors, adapters, authorities, services, annotations, verbs, importers, and exporters.
- The source service id, when applicable.
- The registration or update timestamp.
- The import type:
BUILT_IN,FILE,MAVEN, orDEPENDENCY. - The last computed update status and timestamp of the latest version known at the source.
The update status is one of NOT_UPDATEABLE, UNKNOWN, UP_TO_DATE, or UPDATE_AVAILABLE.
latestVersionTimestamp is zero when the source cannot provide an authoritative timestamp. These
fields are computed for advertised descriptors and do not alter or install components. Stable
Maven releases and the built-in service component are not updateable; Maven SNAPSHOTs, directly
imported .kar archives, and dependency copies are updateable through their respective sources.
The descriptor is saved to catalog.json and indexed by contribution type. Service verbs,
adapters, authorities, annotations, verbs, importers, and exporters can then be resolved by name.
Authorities are indexed by their provider URN; the worldview-local name is a separate Reasoner
binding and is not a component-registry key. When more than one descriptor can satisfy a request,
the registry prefers the highest version compatible with the requested version. Exact version
requests are matched exactly.
The registry also has a local service component. This makes built-in service libraries, adapters, and authorities visible through the same lookup mechanism used for imported components.
Each service records the lifecycle and synchronization events that affect its own copy of a
component. This applies equally to primary FILE or MAVEN installations and to DEPENDENCY
copies held by secondary services. Recorded events include registration, discovery of a newer
build, update start and completion, unload, deferred restart installation, rollback, and failures.
Routine update checks that find no new build are deliberately not recorded.
Events produced after an update candidate is selected include a machine-readable decision and
policy in details (for example LOCAL_MAVEN_PRECEDENCE or RESOURCES_AUTHORITY and
LOCAL_MAVEN_THEN_RESOURCES). This makes the selected source and its rationale visible in the IDE
History dialog. The typed event retains service IDs for correlation and also carries user-facing
service names when the service is visible; clients should display the name and use the ID only as a
fallback.
History is exposed as a typed projection through the generic service info API. A client can use:
service.info(
componentId + "@" + componentVersion,
KlabAsset.KnowledgeClass.COMPONENT,
ComponentHistory.class,
userScope);Omitting @version returns events for all retained versions of that component. Events are returned
newest first. The same visibility check used for component descriptors is applied before history
is returned, so an IDE can fetch this projection when a ComponentCard history action is opened
without a separate administration endpoint.
Component versions are independent from the k.LAB service version, but they must declare whether
they can run with the hosting k.LAB version. Compatibility is controlled through the plug-in
manifest's PF4J version requirement, conventionally Plugin-Requires.
At startup the component manager sets the PF4J system version to the current k.LAB version. During
update checks, a candidate replacement archive is opened before installation and its version
requirement is checked against the current k.LAB version. If the requirement is absent, blank, or
*, the archive is accepted as compatible. If it declares an incompatible requirement, the update
is skipped and the existing component remains installed.
For predictable maintenance:
- Use stable semantic versions for released components.
- Use
-SNAPSHOTonly for components that are expected to change in place. - Set
Plugin-Requiresnarrowly enough to prevent loading against incompatible k.LAB releases. - Keep the PF4J plug-in id stable across versions of the same component.
- Use unique names for libraries, actors, adapters, authorities, and service verbs.
Side-by-side descriptors for multiple component versions can exist in the catalog, but PF4J plug-in ids are unique in the running plug-in manager. In practice, the runtime should be maintained as one active version of a plug-in id at a time, with the registry selecting the best compatible descriptor for lookups.
Components can be updated manually or automatically. Scheduled repository polling applies only to Maven-sourced SNAPSHOT components. Dependency copies are checked on demand when a secondary service resolves a library service call, Java actor, adapter, or authority contribution.
Update discovery follows the component's import type:
- A hosted
MAVENSNAPSHOT checks the Maven component cache and configured repositories. Its latest timestamp comes from the selected local artifact or remote snapshot metadata. - A hosted
FILEcomponent has no external repository to poll. Its current registration timestamp is also its latest-known timestamp; uploading a replacement advances both. - A
DEPENDENCYcomponent with SNAPSHOT coordinates first checks only the local Maven repository. A different, newer local artifact makes its statusUPDATE_AVAILABLE. If there is none, it asks theResourcesServiceidentified bysourceServiceIdfor the matching hosted descriptor. A newer hosted registration or an update advertised by that service then makes the dependency's statusUPDATE_AVAILABLE. Remote Maven repositories are never consulted by this path. BUILT_INcomponents and stable Maven versions reportNOT_UPDATEABLE.
This separation is important operationally: a local Maven install is an explicit developer override. Otherwise the Resources service owns and serves hosted components, and Runtime, Reasoner, and other secondary services refresh dependency copies from that Resources service.
Before resolving a library service call, Java actor, adapter, or authority from a dependency component, the consuming service first checks for a newer local Maven SNAPSHOT. If none is selected, it checks whether the exact source Resources service is currently visible and compares the installed timestamp of the matching source descriptor with the timestamp of its local dependency copy. Only a source component that is already installed with a newer timestamp triggers replacement; an upstream update merely advertised by the source is not copied until the source has installed it.
Runtime services use this scope-aware lookup before compiling or executing a component-provided
ServiceCall reactor and before resolving a Java actor or agent adapter. Replacement therefore
precedes selection of reflective methods and classes, avoiding new executions retaining a
descriptor from the older build. Objects and agents already executing from the old class loader
are not migrated; update them during a quiet maintenance window when stateful implementations are
in use.
When the contribution is not installed at all, the consumer resolves its full ServiceCall URN as
SERVICE_IMPLEMENTATION through the scope's merged Resources client. A current Resources service
must return the embeddable COMPONENT descriptor that provides the implementation. After transfer,
the Runtime verifies that the requested function is registered and adds a
SERVICE_IMPLEMENTATION result to the requirements returned to the Resolver; this is what makes
the newly available prototype visible while the resolution graph and dataflow are compiled. All
services participating in a stack should therefore run the same version of this discovery
contract.
Resource URNs have a separate adapter-discovery path. A universal resource has the form
klab:<adapter>:...; it contains no stored data and therefore has no resource-catalog entry. When
Runtime resolves such a RESOURCE and the named adapter is not installed locally, it first uses any
COMPONENT dependency returned with the resource. If none was returned, it explicitly resolves the
second URN element as a RESOURCE_ADAPTER through the merged Resources client. That resolution
returns the providing COMPONENT, which is transferred and installed before dataflow compilation
continues. Runtime then verifies that the component registered an
embeddable adapter with the requested version. The synthetic RESOURCE descriptor is retained in the
resolution result, but it is not passed to catalog retrieval: the now-local adapter synthesizes the
Resource when the compiled dataflow uses it.
Embeddability is an execution choice, not a condition for resolving a universal resource. When the
hosting Resources service advertises a non-embeddable adapter, Runtime retains the synthetic
RESOURCE descriptor and its source service ID without requesting the component. The compiled
dataflow retrieves the synthesized Resource from that service and selects RemoteAdapterExecutor,
which obtains data through the Resources contextualization/Avro transport route. Component
synchronization is used only for an adapter advertised as embeddable.
An install or actual same-version build replacement sends informational start and completion notifications through the initiating scope. Runtime messaging subscribes to the Info queue by default, so these notifications can be displayed by the submitting client during a longer submission, resolution, or contextualization cycle. Checks that find no newer build remain silent and are not added to component history.
The source service is not required to be present when the consumer initializes. If it is absent, resolution continues with the local copy and the same check is made again on later resolutions. When a newer source installation is available, the replacement archive is exported and validated before the consumer attempts to unload the active component. A successful replacement is registered and started before resolution continues. The registry preserves or restores the installed component whenever possible if replacement fails. A scope-aware library service-call or Java actor lookup then rejects the request instead of selecting that known-stale component; update-check APIs that do not immediately consume the contribution simply report that no replacement was installed.
If the validated replacement cannot be installed immediately, for example because Windows still
holds the active archive open, it is saved in the component repository's pending-updates/
directory. On the next service restart it is copied into plugins/ before PF4J initializes and its
dependency provenance is restored from the local marker. This startup operation is entirely local
and does not depend on the source Resources service being available. If export or validation fails
before an archive can be staged, no restart action is possible and a later resolution retries the
source check instead.
Upload a new .kar archive with component.kar.import, or use the service path that verbs the
same importer. This is the right path for local development builds, private hand-offs, and
components that are not published to Maven.
The update is local to the service that receives the archive. The registry loads the replacement, rescans its contributions, updates the descriptor, and saves the catalog.
Import the desired Maven version with component.maven.import, or call the registry's Maven
installation path. This works for stable releases and snapshots.
For stable releases, prefer publishing a new version and importing that explicit version. Stable versions are not expected to change in place, so they are not part of the automatic SNAPSHOT update loop.
ComponentRegistry.checkForUpdates() performs a read-only update check for Maven-sourced SNAPSHOT
components. It walks the registered components and considers only descriptors that have Maven
coordinates whose version identifies a SNAPSHOT build. It does not synchronize an artifact,
unload a component, or install a replacement.
For each candidate, the Maven cache determines whether:
- The cached artifact is up to date.
- A different artifact exists in the local Maven repository.
- A newer snapshot appears to exist in a configured remote repository.
- The status cannot be established.
The result reports available updates and diagnostics. The component descriptors advertised by the
service provide the structured updateStatus and latestVersionTimestamp used by administration
clients.
ComponentRegistry.updateMavenSnapshotComponents() composes discovery with updates for all changed
Maven SNAPSHOTs, preserving the existing automatic behavior. updateComponent(id, version, scope)
is the service-aware targeted action: for dependencies it applies the same local-Maven-then-
Resources precedence used during resolution. When an update is indicated, these actions
synchronize the artifact, compare the candidate file hash to the installed descriptor hash,
verify k.LAB compatibility, unload the current component, install the replacement archive,
register the new descriptor, and save the updated catalog.
Administrators invoke targeted update and removal through the common settings API by posting a Map
to UPDATE_COMPONENT or REMOVE_COMPONENT. Both maps require component and accept version;
their result map contains result, message, action, component, and version. The settings
controller independently enforces CRUDOperation.ADMINISTER, and the IDE enables the matching
ComponentCard actions only when the target service advertises that permission. Removal of the
built-in service component is rejected. Explicit removals are recorded in component history with
an ADMINISTRATOR_SETTING trigger; update events retain the source-selection rationale described
above. A no-op update request that finds no newer build is not added to history.
Hash comparison is important because SNAPSHOT timestamps and repository metadata can be noisy. If the retrieved archive has the same content hash as the installed archive, the registry does not replace the component.
Services can request a one-time check and update after component registry initialization. The startup option is:
-updateComponents
The current service startup options enable this by default. Implementations that only use the
StartupOptions interface inherit the same default unless they override it.
Services can also schedule repeated SNAPSHOT checks and updates:
-autoUpdateComponents
-componentUpdateIntervalMinutes <minutes>
The interval defaults to five minutes and is clamped to at least one minute. Scheduled checks run quietly: successful updates are logged, and failures are reported through service logging without stopping the service.
Periodic updates should be enabled deliberately. They are useful for controlled development and integration environments where SNAPSHOT components are expected to move. Production services should normally prefer explicit stable versions or an explicit maintenance operation.
The Maven cache keeps a small catalog for each synchronized artifact. Its entries include the Maven coordinates, cached file, content hash, and last-modified time. The update strategy is:
- Prefer the local Maven repository when the requested artifact exists there.
- Compare the local artifact hash with the cached hash.
- For SNAPSHOT versions not available locally, check remote snapshot metadata.
- Download or copy only when the cache says an update is needed.
- Keep the previous cached artifact if retrieval fails.
This makes local development fast: installing a new SNAPSHOT into the local Maven repository is enough for the next explicit or scheduled check to notice a content change.
Component loading is hot-swappable, but the practical safety of replacing a component depends on what is using it. Updating a component that is actively serving contextualization, resource validation, or long-running operations can leave callers with references to classes from the old plug-in. Windows file locking can also prevent immediate replacement of archives in some cases.
For low-risk operation:
- Update during service startup or a quiet maintenance window when possible.
- Prefer explicit update checks over frequent automatic checks in production.
- Keep component archives dependency-free and small.
- Do not put non-component files in
plugins/. - Treat imported components as trusted code. Manifest usage rights govern resource visibility and sharing, but they are not a security sandbox.
- Keep stable releases immutable. Publish a new version instead of replacing an old release.
- Use SNAPSHOT automatic updates only where changing artifacts in place is acceptable.
For a new component, use the org.integratedmodelling:klab.component.archetype archetype rather
than assembling the PF4J and packaging boilerplate by hand. It always creates the required
KlabComponent entry point and can independently add compilable starters for libraries and
contextualizers, agents, resource adapters, importers/exporters, and authorities. The complete
command, property reference, build workflow, and Central deployment setup are documented in
Maven Builds And Component Archetype.
A component archive should include:
- A unique PF4J plug-in id that is also meaningful as the component id.
- A component version.
- A plug-in class implementing the k.LAB component contract.
- A human-readable description.
- A provider or vendor identity when available.
- A license or rights declaration that can initialize usage rights.
- A
Plugin-Requiresconstraint for the compatible k.LAB version range. - Only the classes and resources needed by the component itself.
- The project license; generated components include GNU Affero GPL version 3 by default.
The Maven artifact intended for component import should be published with classifier component
and suffix kar, so the registry can resolve it as:
groupId:artifactId:version:component:kar
The import-facing coordinate remains the simpler:
groupId:artifactId:version
Install a component from a local archive:
component.kar.import(file = my-component.kar)
Install a component from Maven:
component.maven.import(
groupId = org.example,
artifactId = my-component,
version = 1.2.0
)
Install a development SNAPSHOT and let the service check for newer local or remote builds:
component.maven.import(
groupId = org.example,
artifactId = my-component,
version = 1.3.0-SNAPSHOT
)
Then run the explicit bulk or targeted update action from the hosting service, or start the service with periodic updates enabled:
-autoUpdateComponents -componentUpdateIntervalMinutes 10
The automatic path will update only if the component is Maven-sourced, its version is a SNAPSHOT, the retrieved archive hash differs from the installed archive hash, and the candidate archive is compatible with the current k.LAB version.