Skip to main content
Version: 2026.09

Integration 301: Your First Module in 15 Minutes

Objective​

Create a minimal Istari Digital Platform module using a shell script — no Python framework, no build tools. The module:

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

By completing this tutorial, you'll learn:

  • How to structure a platform module
  • How to define function inputs and outputs using the file contract
  • How to test, deploy, and run your module
  • How to set up and configure the Istari Digital Agent to execute jobs

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 platform control plane.
For example: module key @alice:integration-301, function key @alice:copy, tool key alice-tool, tool display name Alice Tool.

Prerequisites​

  • Access to an Istari Digital Platform environment (e.g. dev.istari.app)
  • Admin privileges in the Istari Digital Platform, or access to an admin user to perform the required actions on your behalf
  • Docker and Docker Compose installed
  • The Istari Digital CLI — debian amd64 from the Customer Portal
  • The Istari Digital Agent installer — debian amd64 from the Customer Portal

This tutorial was checked on Ubuntu 24.04 in Docker with CLI 0.21.20 and Agent 11.11.2. Replace <VERSION> with the CLI version you downloaded, and X.Y.Z with the Agent version.

Key Concepts​

Modules​

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

Your module can be anything executable: a shell script, a Python script, a compiled binary. In this tutorial we use a simple bash script.

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​

The Agent runs your module functions by executing a command (run_command in the manifest). It passes three paths:

  • input file (input_file.json) — JSON with model file paths and parameters
  • output file (output_file.json) — your module writes a JSON array of output file metadata here
  • temp dir — Temporary directory for intermediate files

Step 1: Start the Docker Environment​

1.1 Create the Dockerfile​

FROM ubuntu:24.04

ENV DEBIAN_FRONTEND=noninteractive

RUN apt-get update && apt-get install -y --no-install-recommends \
zip \
unzip \
ca-certificates \
jq \
nano

WORKDIR /workspace

CMD ["sleep", "infinity"]

1.2 Create docker-compose.yml​

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 Digital CLI and Agent are only available for amd64. On Apple Silicon Macs this runs under Docker emulation.

1.3 Place downloads​

Create a downloads folder in the same path as your Docker files. Download the Istari CLI and agent packages from the Customer Portal and place the files in the newly-created downloads folder.

Linux/MacOS terminal example:

mkdir -p downloads
# cp ~/Downloads/stari_ubuntu_24_04-amd64-v<VERSION>.tar.gz downloads/
# cp ~/Downloads/istari-agent_X.Y.Z_amd64.deb downloads/

In this tutorial we are using

  • Istari Digital CLI - Debian AMD64 Ubuntu 24.04 tar.gz package
  • Istari Agent - Debian AMD64 (non-FIPS) .deb package

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.

Editing files: The Dockerfile includes nano, so you can edit files directly inside the container (e.g. nano module_manifest.json). You can also use your preferred editor on the host — because /workspace is a mounted volume, any changes you make on the host are immediately visible inside the 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
cd stari_ubuntu_24_04-amd64
./stari_ubuntu_24_04-amd64 path add --yes
source ~/.bashrc
stari --version

path add puts stari on your PATH. Run it as the account inside the container, not with sudo. See Get started with the CLI.

2.2 Download a CLI key​

The CLI signs in with a key file. Download the file whole. It is JSON with clientId, keyId, and key. Use the file as-is. Copying one of those fields out of it is not a credential the CLI can use.

Personal Access Tokens are deprecated as of the July 2026 release. This tutorial uses keys. The Agent gets its own key later, in Step 6, the same way as Install the agent on Linux.

  1. Open the Istari Digital web app.
  2. Click your avatar at the bottom of the sidebar. The hover label is User settings.
  3. Select Developer. The page title is Developer Settings.
  4. Click Generate Key. In the New Access Key dialog, enter a name if you want one, choose an expiration, and click Generate. See Generate a Key.
  5. Click Download credentials while the key is on screen. The browser saves istari-credentials-<keyId>.json. It is shown once.
  6. Copy that file into the project folder on the host, next to docker-compose.yml, so the container can read it at /workspace/.

On that Developer Settings page, under Endpoints, copy the API URL (for example https://api.your-instance.istari.app). That is the address the CLI and the Agent call. The address in the browser, where you sign in, is the web app (for example https://your-instance.istari.app). They are different hosts.

Create .env in the project folder. These names are only for the commands in this tutorial. The CLI does not read .env.

API_URL=https://api.your-instance.istari.app
WEB_APP_URL=https://your-instance.istari.app
CLI_CREDENTIALS_FILE=/workspace/istari-credentials.json

Inside the container:

cd /workspace
source /workspace/.env

2.3 Initialize the CLI​

stari client init "$API_URL" --identity-service --credentials-file "$CLI_CREDENTIALS_FILE" --yes
stari client ping

This is the same command as Get started with the CLI. On CLI 0.21.20, --identity-service and --credentials-file belong on stari client init.

You should see a successful connection.

Successfully connected
Successfully connected to Istari Digital at <api_url>.

stari client init writes a cli: section into /root/.config/istari_digital/istari_digital_config.yaml. The Agent uses the same file. Keep that cli: section when you add the Agent configuration in Step 6.2.

2.4 Add the default section once​

The Agent reads a default: section. stari client init leaves it out, and startup then fails with KeyError: 'default'. Add the section when it is missing. Appending it again creates a duplicate key.

CONFIG=/root/.config/istari_digital/istari_digital_config.yaml
grep -q '^default:' "$CONFIG" || printf 'default: {}\n' >> "$CONFIG"

The top of the file should contain:

default: {}

Step 3: Create the Module​

No scaffolding needed. Create the module directory with just two files: a manifest and a shell script.

mkdir -p /workspace/my-module
cd /workspace/my-module

3.1 Create the shell script​

Create copy.sh:

#!/usr/bin/env bash
# Istari module: copies input file to output with a timestamp header.
# Usage: copy.sh --input-file <path> --output-file <path> --temp-dir <path>
set -e

# Parse arguments
while [[ $# -gt 0 ]]; do
case "$1" in
--input-file) INPUT_FILE="$2"; shift 2 ;;
--output-file) OUTPUT_FILE="$2"; shift 2 ;;
--temp-dir) TEMP_DIR="$2"; shift 2 ;;
*) shift ;;
esac
done

# Read the input file path from the input JSON
FILE_PATH=$(jq -r '.input_file.value' "$INPUT_FILE")

# Copy with timestamp header
OUTPUT_PATH="${TEMP_DIR}/copied_file.txt"
echo "# Processed at $(date -Iseconds)" > "$OUTPUT_PATH"
echo "" >> "$OUTPUT_PATH"
cat "$FILE_PATH" >> "$OUTPUT_PATH"

# Write the output JSON. The file field is path, not value.
printf '[{"name": "copied_file", "type": "file", "path": "%s"}]\n' "$OUTPUT_PATH" > "$OUTPUT_FILE"

The Agent reads path from each output item. A field named value, or a path that is empty, makes the later upload fail.

Make it executable:

chmod +x copy.sh

3.2 Create the manifest​

Create module_manifest.json (replace <namespace>):

{
"module_key": "@<namespace>:agent-301",
"module_version": "1.0.0",
"module_checksum": "placeholder",
"module_type": "function",
"module_display_name": "301 Tutorial",
"tool_key": "<namespace>-tool",
"tool_display_name": "<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": "copy.sh",
"run_command": "bash {entrypoint} --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": "artifact",
"display_name": "Copied File"
}
]
}
}
]
}
}

Key points:

  • module_key: @<namespace>:agent-301 — unique per developer on the control plane.
  • tool_key: <namespace>-tool — a unique identifier for the tool.
  • tool_display_name: <Namespace> Tool — the human-readable name shown in the Istari Digital web app when granting tool access.
  • The function key @<namespace>:copy must use the same namespace as module_key.
  • run_command calls bash {entrypoint} ... — the Agent resolves {entrypoint} to the path of copy.sh.
  • The shell script parses --input-file, --output-file, --temp-dir like any CLI tool.
  • We are using a simple shell script but the same approach works with any executable, including a compiled binary.

Validate the manifest​

stari module lint module_manifest.json

Step 4: Test Locally​

cd /workspace/my-module
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"
}
}

Run the script:

bash copy.sh \
--input-file test_run/input.json \
--output-file test_run/output.json \
--temp-dir test_run

Check the results before you publish. A script can exit 0 and still write an output JSON the Agent will reject.

cat test_run/output.json
jq . test_run/output.json
OUTPUT_PATH=$(jq -r '.[0].path' test_run/output.json)
test -n "$OUTPUT_PATH" && test -s "$OUTPUT_PATH" && echo "output file exists: $OUTPUT_PATH"
cat test_run/copied_file.txt

output.json should look like this, with a real path in path:

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

The copied file should start with a timestamp header.

Step 5: Package and Publish​

Publishing and installing are two steps.

  • Publish sends module_manifest.json to the platform, so the function shows up when you create a job.
  • Package builds the zip you unpack onto the Agent in Step 6. Publishing does not copy copy.sh onto the Agent.

5.1 Package​

cd /workspace/my-module
zip -r /workspace/my-module.zip . -x "test_run/*"

5.2 Publish to the Platform Registry​

stari client publish module_manifest.json

5.3 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_display_name (e.g. Alice Tool).

Step 6: Set Up the Agent​

Follow Install the agent on Linux. The container is the host. It runs as root, so the commands below omit sudo. The Agent and the module must be installed in this same container.

6.1 Install the agent​

On Ubuntu, install the prerequisite, then the package. Use the non-FIPS .deb.

apt-get update && apt-get install -y libmpv1
dpkg -i /downloads/istari-agent_X.Y.Z_amd64.deb

The agent installs to /opt/local/istari_agent/.

6.2 Give the agent its credentials​

This is the same sequence as step 3 of the Linux guide. The CLI is not part of it. stari client init already wrote the cli: section and does not configure the Agent.

  1. In the web app, open Agents and select Generate Key. Select Download credentials while the key is on screen. The browser saves agent-credentials-<key id>.json. It is shown once. Generating the key needs an administrator of the tenant.
  2. Copy that file into the project folder on the host, next to docker-compose.yml.
  3. Inside the container, move it into root's home and close it to everyone else. Root is the account that will run the agent:
mkdir -p ~/.istari_digital
mv /workspace/agent-credentials-<key id>.json ~/.istari_digital/
chmod 600 ~/.istari_digital/agent-credentials-<key id>.json
  1. Open /root/.config/istari_digital/istari_digital_config.yaml (nano is in the image). It already has default: and cli: from Step 2. Add an agent: section and leave cli: in place.

The container has no desktop session, so include headless mode, as the Linux guide does before you start the agent:

agent:
istari_digital_agent_digital_api_url: "https://api.example.istari.app"
istari_digital_agent_identity_service_secret_file: "/root/.istari_digital/agent-credentials-<key id>.json"
istari_digital_agent_identity_service_enabled: true
istari_digital_agent_headless_mode: true

Put the API URL from Step 2.2 in istari_digital_agent_digital_api_url. The configuration stores the path to the credentials file. The agent reads that file on every start.

6.3 Install the module on the agent​

The Agent only runs a module installed on the same machine. In this tutorial that machine is the container.

mkdir -p /opt/local/istari_agent/istari_modules/my-module
cp /workspace/my-module.zip /opt/local/istari_agent/istari_modules/my-module/
unzip -o /opt/local/istari_agent/istari_modules/my-module/my-module.zip -d /opt/local/istari_agent/istari_modules/my-module
ls /opt/local/istari_agent/istari_modules/my-module/module_manifest.json \
/opt/local/istari_agent/istari_modules/my-module/copy.sh

Both files must be listed. If either path is missing, the Agent cannot run the function.

6.4 Start the Agent​

/opt/local/istari_agent/istari_agent_X.Y.Z

Expected result (successful start):

- MainThread - INFO - Agent initialized
- MainThread - INFO - Istari agent version X.Y.Z
- 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

Got display name is the name the platform assigned. The same name appears under All Agents in the web app. That is how Install the agent on Linux confirms the agent registered.

No compatible jobs and Checking for available jobs repeat about every 15 seconds. That is the Agent waiting for work. Leave this terminal running.

A compatibility warning at startup still matters when the Agent registers and polls. Check that the CLI, the Agent, and the platform release are a supported set before you submit a job. See Troubleshooting.

Step 7 needs a second shell. On the host, in the project folder:

docker compose exec istari-tutorial bash

Step 7: Run the function in the web app​

Sign in at WEB_APP_URL from your .env (for example https://your-instance.istari.app). That host is the web app. API_URL (https://api...) is only for the CLI and the Agent.

On your computer, make a small text file to upload:

printf 'Hello, Istari!\n' > input.txt

In the web app:

  1. Open Resources.
  2. Select Add resources → Upload, and choose input.txt. See Add Resources.
  3. Open the Resource and select Create Job.
  4. In Tool/Function Combination, select the tool and function you published (<Namespace> Tool / @<namespace>:copy).
  5. Select Execute Function.

Watch the job under Jobs. Details: Jobs and Automation.

In the terminal where the Agent is 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/my-module with command: ['bash', '/opt/local/istari_agent/istari_modules/my-module/copy.sh', '--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...

Check results​

In the web app, open Resources and open the Resource you uploaded. The job adds the copied file as an artifact on that Resource. The run itself is listed under Jobs.


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 Platform module with 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 as a job on the Istari Digital Platform.

Key Skills You've Learned​

  • Module Structure: How to create a module with just a script and a manifest.
  • 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 Agent to execute jobs.

Troubleshooting​

DisplayNameError: Bad display name ""​

The Agent tries to start a system-tray icon, which requires a graphical display. In Docker or over SSH (headless), there is no display and the Agent 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

KeyError: 'default'​

The CLI doesn't write a default section but the Agent expects it.

Fix: Add default: {} only when it is missing. See Step 2.4.

Output JSON has an empty path​

The Agent uploads the file named in path. This value fails that upload:

[{ "name": "copied_file", "type": "file", "path": "" }]

Fix: In copy.sh, set OUTPUT_PATH to the file you wrote, and pass that variable to printf. Re-run the checks in Step 4 before you package again.

Output JSON uses value instead of path​

The Agent validates each output with OutputItem.path.

Fix:

[{ "name": "copied_file", "type": "file", "path": "/tmp/copied_file.txt" }]

Compatibility warning at startup​

A warning that the Agent or CLI does not match the registry API means the versions are not a supported set. The Agent can still register and poll. Treat the warning as something to fix: use the CLI and Agent versions for your platform release. This tutorial was checked with CLI 0.21.20 and Agent 11.11.2.

Module version 1.0.0 already exists​

HTTP response body: {"detail":"Malformed Manifest: \"Module version 1.0.0 already exists.\""}

The registry rejects a publish if that exact version has already been uploaded. Module versions are immutable once published.

Fix: Bump module_version in module_manifest.json (e.g. 1.0.0 → 1.0.1) and publish again.