Skip to main content

Istari Integration SDK 101 : Building Your First Module

Objective

In this tutorial, you'll create a simple Istari module called tutorial-module with a @<namespace>:copy function that:

  1. Accepts an input text file
  2. Copies it to the output with a timestamp header
  3. Can be executed through the Istari platform

Namespace: Throughout this tutorial, replace <namespace> with your own unique namespace (e.g. your name or team: alice, teamx). This keeps your module key unique when multiple developers share the same Istari control plane.
For example: module key @alice:integration-101, function key @alice:copy, tool key alice-tool.

By completing this tutorial, you'll learn:

  • How to structure an Istari module
  • How to define function inputs and outputs
  • How to test, deploy, and run your module

Prerequisites

  • Access to an Istari environment (e.g. dev.istari.app)
  • Docker and Docker Compose installed
  • The Istari CLI for Linux (amd64) — obtain it from your usual source (e.g. Artifactory or internal distribution)
  • The Istari Agent installer for Linux (amd64) — a .deb package or tarball from the same source

Key Concepts

Modules

A module is a package containing one or more functions that Istari can execute. Modules are language-agnostic but must follow Istari's file-based contract for communication.

Functions

Functions define:

  • Inputs: Models (files) and parameters
  • Outputs: Files or directories generated by the function
  • Execution: How the function runs (command, environment)

File Contract

Istari communicates with modules using:

  • Input file (input_file.json): Contains model paths and parameters
  • Output file (output_file.json): Where your module writes results
  • Temporary directory: For intermediate files

Step 1: Start the Docker Environment

All tutorial steps run inside a Docker container (Ubuntu 24.04, amd64). Your project files are mounted into the container so everything you create is visible on your host.

1.1 Create the Dockerfile

Create a file named Dockerfile in your project root:

FROM ubuntu:24.04

ENV DEBIAN_FRONTEND=noninteractive

RUN apt-get update && apt-get install -y --no-install-recommends \
python3 \
python3-pip \
python3-venv \
zip \
unzip \
ca-certificates \
iputils-ping \
binutils \
libpython3.12

RUN pip install --break-system-packages "pydantic>=2" poetry

WORKDIR /workspace

CMD ["sleep", "infinity"]

1.2 Create docker-compose.yml

Create a file named docker-compose.yml in your project root:

services:
istari-tutorial:
build: .
image: istari-tutorial:24.04
platform: linux/amd64
container_name: istari-tutorial
volumes:
- .:/workspace
- ./downloads:/downloads:ro
working_dir: /workspace
stdin_open: true
tty: true

Note: platform: linux/amd64 is required because the Istari CLI and Agent are only available for amd64. On Apple Silicon Macs this runs under Docker emulation.

1.3 Place the Linux CLI in downloads

mkdir -p downloads
# Copy your Linux CLI tarball here, e.g.:
# cp /path/to/stari_ubuntu_24_04-amd64-v<VERSION>.tar.gz downloads/

1.4 Start the container and open a shell

docker compose up -d --build
docker compose exec istari-tutorial bash

You are now in a bash shell inside Ubuntu 24.04 (amd64). The project root is mounted at /workspace, and downloads/ is at /downloads (read-only).

All remaining steps are run inside this container.


Step 2: Install and Initialize the CLI

2.1 Install the CLI

Extract and set up the CLI from /downloads:

cd /tmp
tar -xzf /downloads/stari_ubuntu_24_04-amd64-v<VERSION>.tar.gz
./stari_ubuntu_24_04-amd64/stari_ubuntu_24_04-amd64 setup

The CLI setup install Istari Digital CLI in your path, accept the option:

Istari Digital CLI Setup
==================================================

⚠ The 'stari' CLI is not currently on your PATH.
You're running it from: /tmp/stari_ubuntu_24_04-amd64/stari_ubuntu_24_04-amd64

Would you like to add 'stari' to your PATH now? [y/n] (y): y

Update your PATH and check stari CLI:

source ~/.bashrc
stari --version

2.2 Generate credentials and create a .env file

  1. Open your Istari environment (e.g. dev.istari.app).
  2. Go to Developer Settings → generate an API Key and copy the Registry URL.
  3. Go to Admin Settings → generate an Agent PAT (shown only once; store it securely).

Note: To access Developer Settings and Admin Settings, click on your name at the bottom of the left sidebar.

Create a .env file in your project root (on the host) with these values:

REGISTRY_URL=<registry-url>
API_KEY=<your-api-key>
AGENT_PAT=<your-agent-pat>

Inside the container, load the variables:

cd /workspace
source /workspace/.env

You can also set these as environment variables directly instead of using a file.

2.3 Initialize the CLI

stari client init "$REGISTRY_URL" "$API_KEY" --yes
stari client ping

You should see a successful connection. This configuration is used for all stari commands (scaffold, lint, publish).

Successfully connected
Successfully connected to Istari Digital at https://fileservice-v2.dev2.istari.app.

2.4 Patch the config file

Workaround: The CLI does not write a default section to the config file, but the agent requires it. Without this step the agent will fail with KeyError: 'default' on startup.

echo "default: {}" >> /root/.config/istari_digital/istari_digital_config.yaml

Step 3: Scaffold the Module

cd /workspace
stari module scaffold tutorial-module \
--type python-module \
--author "Your Name" \
--email "you@example.com" \
--description "Tutorial module with a file copy function" \
--version "1.0.0"

This creates:

tutorial-module/
├── module_manifest.json
├── module/
│ ├── functions/
│ └── utils/
├── tests/
├── scripts/
└── README.md

Install dependencies:

cd tutorial-module
poetry install

Remove the scaffold example functions

The scaffold creates several example function files. Remove them so we start clean:

rm module/functions/data_extraction.py
rm module/functions/model_to_artifact_no_auth.py
rm module/functions/model_to_artifacts_no_auth.py
rm module/functions/model_to_artifacts_basic_auth.py
rm module/functions/model_to_artifacts_oidc_auth.py
rm module/functions/model_params_to_artifacts_no_auth.py

Keep module/functions/__init__.py and module/functions/registry.py — they are needed.


Step 4: Define the Function in the Manifest

Replace the contents of module_manifest.json with the following. Replace <namespace> with your chosen namespace.

{
"module_key": "@<namespace>:agent-101",
"module_checksum": "1234567890",
"module_version": "1.0.0",
"module_type": "function",
"module_display_name": "Tutorial Module",
"tool_key": "<namespace>-tool",
"tool_versions": ["1.0.0"],
"operating_systems": ["Ubuntu 24.04"],
"agent_version": ">=9.0.0",
"internal": false,
"authors": [
{
"name": "Your Name",
"email": "you@example.com"
}
],
"functions": {
"@<namespace>:copy": [
{
"entrypoint": "dist/python_module",
"run_command": "{entrypoint} FileCopy --input-file {input_file} --output-file {output_file} --temp-dir {temp_dir}",
"operating_systems": ["Ubuntu 24.04"],
"tool_versions": ["1.0.0"],
"function_schema": {
"inputs": {
"input_file": {
"type": "user_model",
"validation_types": ["@extension:txt"],
"optional": false,
"display_name": "Input File"
}
},
"outputs": [
{
"name": "copied_file",
"type": "file",
"required": true,
"upload_as": "artefact",
"display_name": "Copied File"
}
]
}
}
]
}
}

Key points:

  • module_key: @<namespace>:101 — unique per developer on the control plane.
  • tool_key: <namespace>-tool — shown in the UI when granting tool access.
  • The function key @<namespace>:copy must use the same namespace as module_key.
  • The run_command uses the form {entrypoint} FileCopy .... The agent resolves {entrypoint} to the binary path, and passes FileCopy as the function name — this must match the name used in register() in the Python code.

Validate the Manifest

stari module lint module_manifest.json

Fix any reported issues until validation passes.


Step 5: Implement the Function

Create module/functions/file_copy.py:

import logging
from pathlib import Path
from datetime import datetime
from typing import List

from pydantic import BaseModel, ConfigDict, Field, ValidationError

from module.functions.base.function_io import Input, Output, OutputType
from module.functions.registry import register

logger = logging.getLogger(__name__)


class FileCopyInput(BaseModel):
input_file: Input[str] = Field(..., description="The text file to copy.")

model_config = ConfigDict(extra="allow")


def file_copy(input_json: str, temp_dir: str) -> List[Output]:
"""Copies input file to output with a timestamp header."""
try:
input_data = FileCopyInput.model_validate_json(input_json)
except ValidationError as e:
raise ValueError("Invalid input for file_copy.") from e

input_path = Path(input_data.input_file.value)
content = input_path.read_text(encoding="utf-8")

output_path = Path(temp_dir) / "copied_file.txt"
header = f"# Processed at {datetime.now().isoformat()}\n\n"
output_path.write_text(header + content, encoding="utf-8")

logger.info(f"Copied {input_path} to {output_path}")

return [Output(name="copied_file", type=OutputType.FILE, path=str(output_path))]


# The registered name must match the second argument in run_command
register("FileCopy", file_copy)

Important: The name "FileCopy" in register(...) must match the function name in the manifest's run_command ({entrypoint} FileCopy ...).


Step 6: Test Your Module Locally

Run the following from the tutorial-module directory.

6.1 Create test files

mkdir -p test_run
echo "Hello, Istari!" > test_run/input.txt

Create test_run/input.json:

{
"input_file": {
"type": "user_model",
"value": "test_run/input.txt"
}
}

6.2 Run the function

python3 -m module FileCopy \
--input-file test_run/input.json \
--output-file test_run/output.json \
--temp-dir test_run

6.3 Verify output

cat test_run/output.json
# [{"name": "copied_file", "type": "file", "path": "test_run/copied_file.txt"}]

cat test_run/copied_file.txt
# # Processed at 2026-02-25T12:00:00.000000
#
# Hello, Istari!

Step 7: Build, Package, and Publish

7.1 Build the binary

Build a self-contained executable with PyInstaller. This is what the agent runs via {entrypoint} in the manifest:

cd /workspace/tutorial-module
poetry run poe build_binary

This creates dist/python_module (matching the entrypoint in module_manifest.json).

7.2 Package the module

cd /workspace/tutorial-module
zip -r /workspace/tutorial-module.zip .

7.3 Generate checksum

sha256sum /workspace/tutorial-module.zip

Update the module_checksum field in module_manifest.json with this value.

7.4 Publish to Istari

stari client publish module_manifest.json

7.5 Provide access

The user who publishes the module has access by default. To give access to other users:

  1. Open Admin Settings (click your name at the bottom of the left nav bar).
  2. Select User → select a user → open the ellipsis menu (⋯).
  3. Select Manage Tool Access.
  4. Check the tool that matches your tool_key (e.g. alice-tool).

Step 8: Set Up the Agent

The Istari Agent runs on the machine (or container) and polls the platform for jobs. When a job arrives, the agent executes your module locally. Both the agent and the module must be installed on the same machine.

8.1 Install the Agent

dpkg -i /workspace/downloads/istari-agent_10.1.2_amd64.deb

The agent is installed to /opt/local/istari_agent/.

8.2 Initialize the Agent

Use the variables from your .env file (loaded in Step 2.2):

source /workspace/.env
stari agent init "$REGISTRY_URL" "$AGENT_PAT"

Expected result:

√ Agent configuration saved successfully!
Configuration file: /root/.config/istari_digital/istari_digital_config.yaml
Configuration: Top-level agent section

The agent is now ready for use with the configured settings.

You can verify the config: cat /root/.config/istari_digital/istari_digital_config.yaml

Enable headless mode: Open the config file (path shown above) and add istari_digital_agent_headless_mode: true under the agent: section. This is required when running without a graphical display (e.g. Docker, SSH).

echo "    istari_digital_agent_headless_mode: true" >> /root/.config/istari_digital/istari_digital_config.yaml

8.3 Install the Module on the Agent

mkdir -p /opt/local/istari_agent/istari_modules/tutorial-module
cp /workspace/tutorial-module.zip /opt/local/istari_agent/istari_modules/tutorial-module/
unzip -o /opt/local/istari_agent/istari_modules/tutorial-module/tutorial-module.zip -d /opt/local/istari_agent/istari_modules/tutorial-module

8.4 Start the Agent

/opt/local/istari_agent/istari_agent_10.1.2

Your agent version may vary.

Expected result (successful start): You should see agent logs with basic information:


- MainThread - INFO - Agent initialized
- MainThread - INFO - Istari agent version 10.1.2
- MainThread - INFO - Configuration: istari_digital_agent_poll_interval=15
...
- MainThread - INFO - Agent ID: 063d23f7-49aa-4386-9f58-260d2ed7cb58
- registration_thread - INFO - Got display name 'trusty-oin-4579' from server
- MainThread - INFO - No compatible jobs
- MainThread - INFO - Checking for available jobs

The agent is polling; leave it running

Step 9: Run the Function via Istari

In the Istari UI,

  • upload a .txt file.
  • Select the file and create job
  • In the Select a tool/function drop down, you should find the tool and function you just created.
  • Execute the function

This will start a Job that you can monitor in the Jobs section

Check the Agent logs from where you have the Agent running.

You should see entries like:

Found available job 6b1d8333-d0c8-4d9b-992e-975724a5e8c6
CLAIMING JOB STATE: job 6b1d8333...
EXECUTING JOB STATE: job 6b1d8333...
Running process 6b1d8333... in cwd /opt/local/istari_agent/istari_modules/tutorial-module with command: ['/opt/local/istari_agent/istari_modules/tutorial-module/python_module', 'FileCopy', '--input-file', '...input.json', '--output-file', '...output.json', '--temp-dir', '...out']
Process 6b1d8333... completed succesfully
Return code for job 6b1d8333...: 0
Job 6b1d8333... completed successfully
EXECUTION SUCCESS STATE: job 6b1d8333...

9.3 Check results

In the Istari UI:

  1. Go to the Files tab.
  2. Find your file and view the artefact generated by the job.

Cleaning Up

When you are done, exit the container and stop it:

exit
docker compose down

Summary: What You’ve Achieved

By completing this tutorial, you’ve built and deployed your first Istari module wiith a @<namespace>:copy function that:

  • Accepts a text file as input,
  • Adds a timestamp header to the content,
  • Returns the processed file as an artifact,
  • Runs seamlessly on the Istari platform.

Key Skills You’ve Learned

  • Module Structure: How to organize and scaffold an Istari module.
  • Function Design: Defining inputs, outputs, and execution logic.
  • Local Testing: Validating your function before deployment.
  • Deployment Workflow: Packaging, publishing, and granting access to your module.
  • Agent Integration: Setting up and running the Istari Agent to execute jobs.

Next Steps

  • Experiment: Try adding more functions (e.g., file transformation, data extraction).
  • Automate: Explore workflows using the Istari SDK.
  • Scale: Deploy modules for team collaboration or production use.

Troubleshooting

DisplayNameError: Bad display name

The agent tries to start a system-tray icon, which requires a graphical display. In Docker (or SSH / headless servers), there is no display and it crashes.

Fix: Enable headless mode in your agent config. Add the following under agent: in istari_digital_config.yaml:

agent:
istari_digital_agent_headless_mode: true