Skip to main content
The actual work of calling the module runtime performs the module. This is because we use module runtimes as a way to provide a container for the module. Our Module Runtimes are therefore similar to build packs. They represent a Linux environment which contains a defined set of libraries (OS and Python). This means that we can provide a consistent environment for the module to run in.The module runtime is also responsible for the actual execution of the module. It is the module runtime that will call the module and pass the document to it. The contract between the module and Kodexa is basically defined by the module runtime.

How do Modules interact with the Module Runtime?

When you deploy a module into Kodexa you include the module runtime that you want to use. Today, all Kodexa module runtimes have the same interface, but this may change in the future. There are two primary forms of interaction between the module runtime and the module. The first is inference, the second is training. How the module runtime calls your module is based on how you have declared your module in the module.yml file.

Inference

The most common starting point with working with a module is learning how inference works. Let’s take a simple example of a module.yml:
# A very simple first module

slug: my-module
version: 1.0.0
orgSlug: kodexa
type: store
storeType: MODEL
name: My Module
metadata:
  state: TRAINED
  moduleRuntimeRef: kodexa/base-module-runtime
  type: module
  contents:
    - module/*
The key thing to note here is the moduleRuntimeRef which is set to kodexa/base-module-runtime. This means that the Robotic Assistant will use the base module runtime to run the module. In fact, it will look up the module runtime with the ref kodexa/base-module-runtime. Then it will look at the module runtime to determine which action it uses for inference, and build a pipeline including that action. The platform will then schedule that pipeline and then module runtime action will be called. Module Runtimes When the module runtime is called it will be passed a document and also all the options that have been captured in the UI for the module. The module runtime will then look at the module.yml file and determine the entry point for the module. By default, the module runtime will expect a package called Module and then look for a function in that package called infer. The module runtime will pass the document that we are processing to the module and then the module will return a document. The module runtime will then pass the document back to the platform for further processing.

Inference with Options

In the previous example, we saw how the module runtime would pass the document to the module. In this example, we will see how the module runtime will pass options to the module. First, let’s add some inference options to our module.yml file:
# A very simple first module

slug: my-module
version: 1.0.0
orgSlug: kodexa
type: store
storeType: MODEL
name: My Module
metadata:
  moduleRuntimeRef: kodexa/base-module-runtime
  type: module
  inferenceOptions:
    - name: my_option
      type: string
      default: "Hello World"
      description: "A simple option"
  contents:
    - module/*
Here we can see we have added an inference option to the module.yml file. This option will be displayed in the UI when the module is used. The user can then change the value of the option and that value will be passed to the module runtime. When we deploy this module update, we now can use that new option in our inference code.
import logging

logger = logging.getLogger(__name__)

def infer(document, my_option):
    logger.info(f"Hello from the module, the option is {my_option}")
    return document
As we can see, the option is passed to the module as a parameter.

Overriding Entry Points

If you want to override the entry point for your module, you can do so by specifying the moduleRuntimeParameters property in the module.yml file.
moduleRuntimeParameters:
  module: my_module
  function: custom_infer
  training_function: custom_train

Magic Parameter Injection

When a module function is called by the Kodexa bridge, parameters are automatically injected based on the function signature. You only need to declare the parameters you want — the bridge inspects your function signature and passes matching values automatically.

Available Parameters

ParameterTypeDescription
documentDocumentThe Kodexa document being processed (inference only)
document_familyDocumentFamilyThe document family being processed, if available
model_basestrPath to the model’s base directory on disk
pipeline_contextPipelineContextThe pipeline execution context
model_storeModelStoreThe model store for persisting/loading model artifacts
assistantAssistantThe assistant associated with this execution
assistant_idstrThe assistant ID
projectProjectThe project this execution belongs to
execution_idstrThe current execution ID
status_reporterStatusReporterHelper for posting live status updates to the UI
eventdictThe triggering event (event handler only)

Usage

Declare only the parameters your function needs:
def infer(document, document_family=None, model_base=None):
    # Only document, document_family, and model_base are injected
    ...

Inference Options

In addition to the magic parameters above, any inference options declared in your module.yml are also injected by name. If you have an inference option called my_option then you will get a parameter called my_option passed to your inference function.
def infer(document, my_option):
    logger.info(f"Hello from the module, the option is {my_option}")
    return document

StatusReporter

The status_reporter parameter provides fire-and-forget status updates that appear in the UI during execution. All calls are safe — errors are logged but never propagated.
status_reporter.update(title, subtitle=None, status_type="processing")
ArgumentRequiredDescription
titleYesPrimary status message
subtitleNoSecondary detail text
status_typeNoOne of: thinking, searching, planning, reviewing, processing, analyzing, writing, waiting
Updates are rate-limited to one per second.
def infer(document, status_reporter=None, model_base=None):
    if status_reporter:
        status_reporter.update("Extracting tables", status_type="processing")

    # ... do work ...

    if status_reporter:
        status_reporter.update("Running classification",
                               subtitle="Page 3 of 12",
                               status_type="analyzing")

    # ... more work ...
    return document

Pipeline Context Status Handler

For step-level progress tracking (progress bars in the UI), use the pipeline_context.status_handler callback:
def infer(document, pipeline_context=None):
    pages = document.get_nodes()
    for i, page in enumerate(pages):
        pipeline_context.status_handler(
            f"Processing page {i+1}",  # message
            i + 1,                      # progress
            len(pages)                  # progress_max
        )
        # ... process page ...
    return document