# Istari Digital — Connect


---

<!-- Source: 01-Introduction.md -->

---
sidebar_position: 0
sidebar_label: Introduction
title: Connect
---

# Introduction

**Connect** is how engineering tools and data sources join an Istari Digital Platform that is already installed. You install **modules** for the tools you use, register the **third-party applications** your team signs in to — Teamwork Cloud, Windchill, Google Drive, Microsoft 365 — so jobs can act on their data, and register the Resources those workflows depend on.

Registering a Resource centralizes its **identity**: a UUID, a revision history, permissions, and a place in a System. The **contents** stay where they belong — in your own object store when you upload, or in the source application when you connect a file. That is what makes a digital thread possible without moving every file into a new repository.

Before you start, you need a running platform with at least one agent — see [Install](../icbs-install/01-Introduction.md). The OS agent guides end by deploying **Open Text** so you can test the agent without a licensed tool. The work here is everything after that: commercial CAD, MBSE, solvers, productivity suites, custom modules, and data ingest. Concepts: [Integrations, jobs, and agents](../intro/key-concepts/integrations.md).

## When you need another agent

A module only runs where an agent is already running. Add an agent when the tool or data you want to reach sits somewhere no current agent can see it — for example:

- a **new workstation or VM** that should start running jobs
- a **tool you just deployed** on a machine that has no agent
- a database or engineering application that has to be read from that host

The install is the same one your IT team ran the first time:

| Host    | Guide                                                                              |
| ------- | ---------------------------------------------------------------------------------- |
| Windows | [Install the agent on Windows](../icbs-install/04-Agents/02-Install-on-Windows.md) |
| Linux   | [Install the agent on Linux](../icbs-install/04-Agents/03-Install-on-Linux.md)     |
| macOS   | [Install the agent on macOS](../icbs-install/04-Agents/04-Install-on-macOS.md)     |

Start from [Agents overview](../icbs-install/04-Agents/01-Overview.md) if you are still choosing a host. Once the agent shows as online, [add the module](03-Modules/01-Module-Installation.md) on it.

## What you will find here

1. **Module deployment** — Put a module on an agent, publish its manifest, and grant users access to the tool. One procedure, used for every module you add. Publishing uses `stari`; [install and authenticate it](../icbs-install/05-CLI/01-get-started.md) if it is not already on the host. During Install, the last step of each [OS agent guide](../icbs-install/04-Agents/01-Overview.md) uses this procedure with **Open Text** to test the agent.
2. **Supported integrations** — Reference pages per tool: functions, prerequisites, and how to run them from the web app or Python.
3. **[Custom modules (Integrations SDK)](04-custom-modules/index.md)** — Build your own module when no supported integration covers your tool.

Administrators manage agents from **Agents** in the sidebar and register external applications under **Admin** in Settings. For each application, choose how people sign in: a shared **service account** managed by administrators or a **linked account** each user creates. See [App Integrations](../users/admin-guide/app-integrations.md).

If you get stuck, contact [support@istaridigital.com](mailto:support@istaridigital.com).


---

<!-- Source: 03-Modules/01-Module-Installation.md -->

---
sidebar_label: Module deployment
title: Module deployment
---

# Module deployment

A **module** is the package that delivers an **integration** (functions users run). Each [Istari Digital Agent](../../icbs-install/04-Agents/01-Overview.md) can host any number of modules. A **DE tool** is the separate vendor or open-source application on the host when the module needs it — installing the module does not install that tool. See [Terminology](../../intro/terminology.md#module).

Use this page whenever you put a module on an agent: unpack it, confirm the agent loaded it, configure it if needed, publish the manifest, and grant access. During **Install**, each OS agent guide ends with this procedure for **Open Text**, so you can test the agent before licensed tools.

**What you will do**

1. Unpack the package into the agent’s `istari_modules` folder.
2. Confirm the agent loaded it.
3. Add configuration only if that module’s page requires it.
4. Publish the manifest with the CLI and grant users access.

Repeat the unpack steps for each module on each agent that should run it. Publish and grant access once per module version.

## Prerequisites

Complete these before you unpack a package:

1. **An agent on this host.** A module runs only on an agent that is already installed and online. If this is a new machine, or the tool was just deployed on a host with no agent, install the agent first:

   | Host    | Guide                                                                                 |
   | ------- | ------------------------------------------------------------------------------------- |
   | Windows | [Install the agent on Windows](../../icbs-install/04-Agents/02-Install-on-Windows.md) |
   | Linux   | [Install the agent on Linux](../../icbs-install/04-Agents/03-Install-on-Linux.md)     |
   | macOS   | [Install the agent on macOS](../../icbs-install/04-Agents/04-Install-on-macOS.md)     |

   See [Agents overview](../../icbs-install/04-Agents/01-Overview.md) if you are still choosing which machine should run the tool.

2. **The module package** for this OS and version from the [Customer Portal](../../icbs-install/02-customer-portal.md). Confirm the host OS is listed in the module’s Prerequisites (example: Cameo → Windows only; Open Text → Windows, Linux, and macOS).

3. **The DE tool** on that same host, when the module docs say so.

4. **The Istari Digital CLI (`stari`) on this host.** Publishing the module uses `stari` against the `module_manifest.json` already on the agent. Install and authenticate `stari` on the same Linux, Windows, or macOS host before you reach the publish step — [Get started with the CLI](../../icbs-install/05-CLI/01-get-started.md).

## Install the package on the agent {#how-to-install-the-package-on-the-agent}

Download the package from the portal, unpack it under `istari_modules`, and the running agent picks it up.

### Download

In the [Customer Portal](../../icbs-install/02-customer-portal.md), open **dist** → **modules**, find the module, open **Asset details**, then **Download** the archive for your OS and version.

### Find `istari_modules`

| OS            | Typical `istari_modules` location                                                                                                                                                                         |
| ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Windows       | `%LOCALAPPDATA%\istari_agent\istari_modules` (per-machine install: `%ProgramFiles%\IstariDigital\istari_agent\istari_modules`)                                                                            |
| RHEL / Ubuntu | `/opt/local/istari_agent/istari_modules` (also check `~/.local/share/istari_agent/` for runtime layout on newer agents)                                                                                   |
| macOS         | `~/Library/Application Support/istari_agent/istari_modules` (the agent support directory — see [The files an agent uses](../../icbs-install/04-Agents/05-Agent-Configuration.md#the-files-an-agent-uses)) |

Create `istari_modules` if it does not exist. It belongs to the account that runs the agent, so do this as that account — on RHEL and Ubuntu, where the folder sits inside the root-owned install, `sudo` instead.

On Windows, a **per-machine install** puts `istari_modules` under Program Files, so installing or updating a module there is an administrator action: unpack from an elevated prompt, not as the operating account. The installer creates the folder, so you should not need to. The same applies to updating a module the agent already has — the agent's own module auto-update cannot write to Program Files, and when it finds it cannot, it says so in the log and leaves the installed module alone rather than half-replacing it. The running agent picks a newly unpacked module up on its next poll, as the lifecycle table below says; restart it only when you replaced files of a module it had already loaded.

### Unpack

The portal package is an archive: **`.tar.gz` on macOS and Linux**, **`.zip` on Windows**. Unpack it so `istari_modules` contains a folder with `module_manifest.json` inside — not the archive itself.

#### macOS

```bash
MODULES="$HOME/Library/Application Support/istari_agent/istari_modules"
mkdir -p "$MODULES"
mv ~/Downloads/<module>_<version>_macos-arm64.tar.gz "$MODULES/"
tar -xzf "$MODULES/<module>_<version>_macos-arm64.tar.gz" -C "$MODULES"
rm "$MODULES/<module>_<version>_macos-arm64.tar.gz"
```

Substitute the filename from the portal (for example `open_text_1.2.0_macos-arm64.tar.gz`). Confirm the extracted folder contains `module_manifest.json`. Where the module ships a binary, make it executable:

```bash
chmod +x "$MODULES/<extracted-folder>/<binary>"
```

Use the binary name from the manifest if you are unsure.

#### Windows

1. Copy `<module>_<version>_win-amd64.zip` onto the host.
2. Extract the ZIP into `istari_modules`.
3. Confirm there is a `module_manifest.json` inside the unpacked folder.

Use these steps rather than `stari module install` on a per-machine install. In this release `stari module install` and `stari module update` write only to the per-user folder, `%LOCALAPPDATA%\istari_agent\istari_modules`, so a module they install on a per-machine host lands where the agent does not look, and the command still reports success. `stari module list` does not show a per-machine agent's modules either. The 2026.09 release notes carry this as a Known Issue.

#### Linux

```bash
sudo mkdir -p /opt/local/istari_agent/istari_modules
sudo tar -xzf /path/to/<module>_<version>_linux-amd64.tar.gz -C /opt/local/istari_agent/istari_modules
```

Confirm there is a `module_manifest.json` inside the unpacked folder, then `chmod +x` the binary named in the manifest.

Example layout after a successful unpack:

```
istari_modules/
├── dassault_cameo/
│   ├── module_manifest.json
│   └── …
├── textract/   # Open Text
│   ├── module_manifest.json
│   └── …
└── open_spreadsheet/
    ├── module_manifest.json
    └── …
```

## Confirm the agent loaded the module

Watch the agent log — the install guide for [Windows](../../icbs-install/04-Agents/02-Install-on-Windows.md#5-confirm-it-registered), [Linux](../../icbs-install/04-Agents/03-Install-on-Linux.md#5-confirm-it-registered), or [macOS](../../icbs-install/04-Agents/04-Install-on-macOS.md#5-confirm-it-registered) gives its path. Each module the agent picks up is listed with its version:

```text
Loading local modules
Adding module @istari:open_text 1.2.0
```

A **new** folder under `istari_modules` is loaded without a restart. If the version in the log is not the one you just unpacked, you replaced an already loaded module and the agent is still running the previous binary — restart it. On Linux hosts where the agent runs as a service: `sudo systemctl restart istari-agent`.

If the module is missing from the list:

- the module directory must contain a `module_manifest.json` and, where the module ships a binary, that the binary is executable;
- the manifest's `operating_systems` array must include the host OS (for example `"Ubuntu 24.04"` or `"macOS 15"`) — otherwise the agent skips it.

You do **not** re-register the agent when you add or update modules.

### Module lifecycle on the agent

| Change                                                  | Restart agent?                                                                         |
| ------------------------------------------------------- | -------------------------------------------------------------------------------------- |
| Add a new module folder under `istari_modules/`         | **No** — the running agent auto-reloads new folders                                    |
| Remove a module folder from `istari_modules/`           | **No** — the running agent drops it                                                    |
| Replace or update files in an already installed module  | **Yes** — the running process keeps the previous module binary in memory until restart |
| Edit `istari_digital_config.yaml` (auth or module keys) | **Yes**                                                                                |

## Configure the module {#configure-the-module}

**Most modules need no configuration.** For [Open Text](../../integrations/05-Productivity/07-open_text/index.mdx), [Open Spreadsheet](../../integrations/05-Productivity/02-open_spreadsheet/index.mdx), [Open PDF](../../integrations/05-Productivity/05-pdf/index.mdx), and other modules that carry their own tooling, skip to [Publish and grant access](#publish-and-grant-access).

Modules that drive a licensed desktop tool need site-specific values — where the tool is installed, which license server to use, and so on. [Dassault Cameo](../../integrations/03-Systems%20Engineering/03-dassault_cameo/index.mdx) is the common example; Creo, CATIA, and MATLAB are similar.

**Copy the key names from the module's own page** (its **Installation** section). A misspelled key is silently ignored and the module fails at job time. See [Module documentation](#module-documentation).

1. Open `istari_digital_config.yaml` on the agent host — [Editing the configuration file safely](../../icbs-install/04-Agents/05-Agent-Configuration.md#editing-the-configuration-file-safely).
2. Add the module's keys under `istari_digital_agent_module_configurations`, nested inside the existing `agent:` block.
3. **Restart the agent.** Configuration is read at startup.

Some modules read credentials or endpoints from **environment variables** instead. A module runs as a child of the agent, so it inherits the agent's process environment — a `.env` file next to the module binary is not read. Start the agent from a shell where those variables are set, or, when the agent runs as a service, declare them in the unit with `Environment=` or `EnvironmentFile=`. See [Run the agent as a service](../../icbs-install/04-Agents/03-Install-on-Linux.md#run-the-agent-as-a-service).

Legacy per-module `module_config.json` files still work for some older modules but are deprecated; prefer the centralized YAML block.

## Publish and grant access

Until you publish the manifest and grant access, the agent can load the module locally, but users will not see or run its functions in the web app. Do both **once per module version**.

### Publish the manifest {#publishing-the-module-manifest}

The module release includes a `module_manifest.json`. Publish that file to the Module registry so the integration is available on the platform.

Use the Istari Digital CLI on the **Linux or Windows** agent host, against the manifest **as deployed on the agent**, so what you publish matches what the agent loads. If `stari` is not installed yet, complete [Get started with the CLI](../../icbs-install/05-CLI/01-get-started.md) first.

```sh
stari module lint /path/to/istari_modules/<module>/module_manifest.json
stari client publish /path/to/istari_modules/<module>/module_manifest.json
```

The registry refuses to overwrite a version that already exists, so a new build needs a new `module_version` in the manifest before it can be published — see [`Module version already exists`](../../integrations/07-troubleshooting.md#module-version-100-already-exists). Until you publish, the platform still offers the previous version's function list.

The manifest you publish must be an "inlined" manifest that contains the function schemas in the manifest file. Contact [support@istaridigital.com](mailto:support@istaridigital.com) if you do not have an inlined manifest for a module.

### Grant tool access {#managing-module-function-access}

Administrators grant access in the web app: [Manage Tool Access for a User](../../users/admin-guide/user-management.md#manage-tool-access-for-a-user).

- Access granted for a module applies to later versions of that module; you do not re-add users for each new version.
- The person who publishes a module is on its access list. Everyone else must be added.

In the **Tool Access** dialog, each tool checkbox has three states:

- **Checked** — access to the tool, including functions added later.
- **Partially checked** — only the functions you selected; new functions are not included.
- **Unchecked** — no access to the tool or its functions.

A fully checked tool (all current functions) is not the same as tool-level access unless the tool checkbox itself is fully checked. Uncheck and re-check the tool if you want future functions included automatically.

To prove a **first install** with Open Text, finish step 6 of the [Windows](../../icbs-install/04-Agents/02-Install-on-Windows.md#6-test-with-an-open-source-module), [Linux](../../icbs-install/04-Agents/03-Install-on-Linux.md#6-test-with-an-open-source-module), or [macOS](../../icbs-install/04-Agents/04-Install-on-macOS.md#6-test-with-an-open-source-module) agent guide.

See each integration for Prerequisites (including OS), Installation, and configuration keys:

- [Ansys HFSS](../../integrations/04-Solvers/05-ansys_hfss/index.mdx)
- [Ansys STK](../../integrations/04-Solvers/08-ansys_stk/index.mdx)
- [Dassault 3DExperience CATIA](../../integrations/02-CAD/01-dassault_3dexperience/index.mdx)
- [Dassault 3DExperience Enovia](../../integrations/03-Systems%20Engineering/05-dassault_3dexperience_enovia/index.mdx)
- [Dassault Cameo](../../integrations/03-Systems%20Engineering/03-dassault_cameo/index.mdx)
- [Dassault Catia v5](../../integrations/02-CAD/02-dassault_catia_v5/index.mdx)
- [Matlab](../../integrations/04-Solvers/01-matlab/index.mdx)
- [Matlab Simulink](../../integrations/04-Solvers/02-simulink/index.mdx)
- [Microsoft Office Excel](../../integrations/05-Productivity/01-microsoft_office_excel/index.mdx)
- [Microsoft Powerpoint](../../integrations/05-Productivity/04-powerpoint/index.mdx)
- [Microsoft Word](../../integrations/05-Productivity/03-word/index.mdx)
- [MSC Nastran Simulation](../../integrations/04-Solvers/03-msc-nastran/index.mdx)
- [Nastran Extraction](../../integrations/04-Solvers/04-nastran-extraction/index.mdx)
- [Open PDF](../../integrations/05-Productivity/05-pdf/index.mdx)
- [Open Spreadsheet](../../integrations/05-Productivity/02-open_spreadsheet/index.mdx)
- [Open Text](../../integrations/05-Productivity/07-open_text/index.mdx)
- [PTC Creo](../../integrations/02-CAD/03-ptc_creo/index.mdx)


---

<!-- Source: 04-custom-modules/01-integrations-sdk-intro.md -->

---
sidebar_label: Module concepts
sidebar_position: 1
title: Module concepts
---

# Introduction to Istari Integrations

Istari provides a powerful integration platform that allows you to connect tools, libraries, and workflows directly into the Istari ecosystem.

Istari maintains a number of official integrations that we publish and support, but you can also create **your own custom integrations**. These integrations let you:

- Connect Istari to external tools or APIs
- Perform custom data extraction or computation
- Run domain-specific analysis or workflows

Custom integrations in Istari are called **modules**. A module is essentially a **package** that contains one or more **functions**. Modules get installed onto the **Istari Agent**, which executes user jobs. The **contract** between the agent and the module is declared in a **module manifest** file.

---

## How Modules Work

When a user initiates a job in Istari, the request is executed by the Istari Agent. The Agent calls functions inside your modules by using a **file-based contract**.

The Agent provides your module with:

1. **Input file** — _path to a JSON file_ containing all inputs declared in your function schema
2. **Output file** — _path to a JSON file_ your function must populate with references to generated outputs
3. **Temp directory** — scratch space for intermediate or working files
4. **Optional config file** — JSON containing configuration values defined in the agent's `istari-digital-config.yml`

This design is intentionally **configuration-driven** and **language-agnostic**, giving you maximum flexibility in how you implement your functions.

When a function needs to sign in to an external system (for example Teamwork Cloud, Google Drive, or a Windchill server), it declares an **authentication input** and reads the delivered sign-in from the input file at run time. See [Authenticating to external systems from a module](./03-authenticating-from-a-module.md).

---

## Module Lifecycle

Modules follow a simple lifecycle:

1. **Create** — write the code for your function(s) using any language or SDK
2. **Scaffold** — use the Istari CLI to generate a module skeleton and manifest
3. **Version** — modules follow **Semantic Versioning ([SemVer](https://semver.org/))**; update the version whenever changes are made
4. **Package** — bundle your function code and manifest into a distributable format (e.g., `.zip` or `.tar`)
5. **Publish** — upload your module via the Istari CLI or the Agent system tray
6. **Grant access** — assign permissions in the Istari management UI to make your module available to users
7. **Install on an agent** — once published and access is granted, the module can be installed on an Istari Agent. Modules are installed into the `istari_modules` directory on the agent. See [Module deployment](../03-Modules/01-Module-Installation.md) for the same flow used for Istari Digital and custom Integrations SDK modules.

---

## Module Package Layout

A module is a self-contained directory containing your function code and a manifest file. A typical **distributed** module might look like this:

```text
my_cad_integration/
├─ module_manifest.json
├─ bin/
│  └─ cad_extractor.exe
└─ lib/
   └─ helper.dll
```

### Minimal layout requirements

- The **manifest** must always live at the root of the package
- The rest of the directory contains your **build output** (executables, binaries, or libraries) needed to run your functions

You may also include **source code** and any supporting files inside the package. Istari does not prescribe how you organize your project — the only requirement is that your module can be invoked successfully using the `run_command` defined in the manifest.

Once published, the package is installed onto the Istari Agent and can be executed as part of user jobs.

---

## Functions and Variants

Functions inside a module are structured as **objects of keyed functions**. Each key corresponds to a function name, and each function can have **multiple variants**.

### Examples of variants

- One variant targeting Windows, another targeting Ubuntu
- One variant targeting SDK version 2023, another targeting SDK version 2024
- One variant accepts XLSX files, another Sharepoint links to XLSX files

The Istari platform resolves which variant to run based on the declared **operating system** and **tool version**.

This structure gives you flexibility to maintain multiple compatible function implementations in a single module.

---

## Scopes for Modules and Functions

Modules and functions are identified using a **scoped key** syntax:

```text
@scope:module_name
@scope:function_name
```

- Istari owns the **`@istari`** scope, which denotes official first-class integrations
- You own your own scopes and can define them for local or organizational use. For example:
  - `@local:my_module` for internal testing
  - `@acme_corp:cad_tools` for an organization-specific integration

Scopes are important for **future distribution and marketplace support**. Consistency is recommended, even though enforcement only applies once modules are distributed.

---

## Best Practices

- **Keep everything in the module** — include build, install, test, and clean scripts in the manifest’s `scripts` section
- **Use the CLI** — the Istari CLI provides commands to generate manifests, scaffold modules, run scripts, and publish integrations
- **Iterate locally first** — get your function working standalone before wrapping it in a module
- **Leverage semantic versioning** — publish each new version explicitly to ensure reliable distribution
- **Sign your packages and generate checksums** — it is recommended that you cryptographically sign your distributed modules and generate checksums (e.g., SHA256).
  - These checksums can then be referenced in your module manifest under `module_checksum` to provide integrity guarantees.
  - Signing helps ensure the authenticity of your package and prevents tampering.

- **Make separate function variants for connect files and local files** - If a function can process a file or a link to a file do not include these inputs in the same function variant, especially if the link requires an auth input from the agent.

For functions that read from or write to a third-party app or DE tool — reporting failures in the job output, making repeated runs safe, and checking preconditions — see [Module design patterns](./04-design-patterns.md).

---

Next, we'll explore how the **Istari CLI** supports you in authoring integrations. After that, we'll dig into the [Module Manifest API Reference](./api-reference/01-api-reference.md) and walk you through how to **build your first module**.


---

<!-- Source: 04-custom-modules/02-istari-cli-integrations-management.md -->

# Authoring Integrations with the Istari CLI

The **Istari CLI** (`stari`) is a powerful command-line tool used across the Istari Platform for managing agents, modules, and integrations. It is used both by **end users and IT administrators**, but it also provides a dedicated set of utilities for **module authors**.

We continue to expand the CLI with more functionality, but even today it provides everything you need to scaffold, author, validate, and test integrations.

---

## Why use the CLI for authoring?

For module authors, the CLI provides:

- **Scaffolding** — Generate complete starter projects with working module templates.
  - Currently supported: **Python** scaffolds
  - Coming soon: **.NET, Java, C++, and more**

- **Interactive manifest creation** — Walk through step-by-step prompts to generate a `module_manifest.json`.
  - Includes author metadata, function schemas, dependencies, scripts, and more
  - Helpful for new authors as a guided workflow

- **Example manifests** — Quickly generate manifests with placeholder values or empty keys for experimentation.

- **Schema generation** — Export the underlying JSON schemas (e.g., manifest, function input/output) to use in your IDE, validator tools, or even with an LLM for assisted editing.

- **Script execution** — Run the scripts defined in your manifest’s `scripts` block.
  - Example:

    ```sh
    stari module run build
    stari module run test --os "Ubuntu 22.04"
    ```

  - Supports cross-platform execution and OS-specific variants
  - Encourages best practices for managing builds, tests, cleans, and installs inside your module

- **Linting and validation** — Validate your manifest for **correctness and completeness**.
  - Ensures required keys and contracts are satisfied
  - Surfaces errors interactively until the manifest is valid
  - If your manifest is lintable, it’s publishable

- **Mock input/output generation** — Generate realistic **sample files** (input and output JSON) based on your function schema.
  - Speeds up local testing and development cycles
  - Lets you see exactly what the agent will hand your module

---

## Workflow summary

When authoring a module with the CLI, you can:

1. Scaffold a new module project
2. Generate or interactively create a manifest
3. Author or import your function schemas
4. Lint and validate the manifest
5. Run build/test/install/clean scripts via `stari module run`
6. Generate mock input/output files for local testing
7. Publish the validated module

This combination of utilities enables both **rapid prototyping** and **robust production-ready development**.

---

## Next Steps

- See the [CLI Reference](../../icbs-install/05-CLI/02-istari-digital-cli.md#cli-commands) for the full list of commands, flags, and examples.
- After understanding the CLI workflow, continue on to the [Module Manifest API Reference](./api-reference/01-api-reference.md) to learn the full details of the manifest contract.


---

<!-- Source: 04-custom-modules/03-authenticating-from-a-module.md -->

---
sidebar_label: Authenticating from a module
sidebar_position: 3
title: Authenticating from a module
---

# Authenticating to external systems from a module

When a function needs to sign in to an external system — a Teamwork Cloud server, Google Drive, a Windchill server, or any API behind a login — your module declares an **authentication input**. At run time the agent hands your function a ready-to-use sign-in, and your code reads it like any other input.

## The contract in two steps

1. **Declare** an input of type `auth_info` in your `module_manifest.json`, tagged with the kind of sign-in your function needs.
2. **Read** that input at run time: its value is a path to a JSON file the agent prepared for this job. Open the file and use the secret inside.

Everything else — securely encrypting the secret, delivering it only to the agent that runs the job, refreshing tokens, and scrubbing the credentials — is handled by the platform.

## Step 1 — Declare the authentication input

Add an input with `"type": "auth_info"` to your function's input schema. Tag it with a **validation type** of the form `@<standard>:<provider>`, which tells the platform what kind of sign-in to deliver:

```json
"twc_login": {
  "type": "auth_info",
  "validation_types": ["@oauth2:teamwork_cloud"],
  "display_name": "Teamwork Cloud sign-in"
}
```

The input name (`twc_login` above) is yours to choose — it is the key you read back at run time.

**Standards** (`<standard>`):

| Standard | Use it for                                      |
| -------- | ----------------------------------------------- |
| `oauth2` | OAuth2 / OIDC browser sign-in (access tokens)   |
| `basic`  | Username-and-password sign-in                   |
| `token`  | A self-managed API key or personal access token |

**Providers** (`<provider>`) name the external application:

| Provider               | Application      |
| ---------------------- | ---------------- |
| `teamwork_cloud`       | Teamwork Cloud   |
| `google_accounts`      | Google Workspace |
| `windchill`            | Windchill        |
| `dassault_3d_passport` | 3D Experience    |
| `microsoft_entra`      | Microsoft 365    |

For example, `@oauth2:teamwork_cloud` requests an OAuth token for Teamwork Cloud, and `@basic:windchill` requests a Windchill username and password. The provider must match an [integration an administrator has configured](../../users/admin-guide/app-integrations) for the kind of credential you need.

:::tip

Keep functions that take a connected (linked) file separate from functions that take a local file. If a function reads a file from an external service it usually needs a matching `auth_info` input; a function that only processes a local upload does not. Put them in separate function variants so the platform can route each job correctly.

:::

## Step 2 — Read the credential at run time

The agent writes all of your function's inputs into the input JSON file. Your authentication input appears there as `{"type": "auth_info", "value": "<path>"}`, where **`value` is the path to a JSON file** the agent decrypted to disk for this job. Open that file and read the sign-in:

```python
import json
import sys

# The agent invokes your entrypoint with the paths named in the manifest's
# run_command — "{entrypoint} {input_file} {output_file} {temp_dir}" — so the
# first argument is the input JSON file holding all of your function's inputs.
input_file = sys.argv[1]

with open(input_file) as f:
    inputs = json.load(f)

# The auth input's value is a path to the prepared sign-in file.
secret_path = inputs["twc_login"]["value"]
with open(secret_path) as f:
    secret = json.load(f)

access_token = secret["token"]
auth_server = secret["authentication_server"]
```

The keys in the sign-in file depend on the standard you requested:

| Standard | Keys in the delivered JSON                                                                                                               |
| -------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `oauth2` | `token` (the access token) and `authentication_server` (the issuer URL). The token is also available as `access_token` and `auth_token`. |
| `basic`  | `username` and `password`.                                                                                                               |
| `token`  | `token` (also available as `api_key`).                                                                                                   |

A `basic` sign-in is read the same way — open the file path, parse the JSON:

```python
secret_path = inputs["windchill_login"]["value"]
with open(secret_path) as f:
    secret = json.load(f)

username = secret["username"]
password = secret["password"]
```

The pattern is identical in any language. For example, in Java:

```java
String json = new String(Files.readAllBytes(
    Paths.get(functionInput.getTwcLogin().getValue())));
TwcLogin login = Serialization.deserializeJson(json, TwcLogin.class);
// login.getToken() + login.getAuthenticationServer() for OAuth2,
// or login.getUsername() + login.getPassword() for Basic.
```

## Where the credential comes from

Your module reads the credential the same way in both of the cases below.

### Using an admin-configured integration

This is the typical case for a shared system such as a Teamwork Cloud connection or a Windchill service account.

1. An administrator [connects the application](../../users/admin-guide/app-integrations) once and either creates a shared **service account** or lets users link their own accounts.
2. When a user launches a job that uses your function, they pick a credential in the launcher. The platform delivers it to the agent.
3. Your function reads it from the `auth_info` input as shown above.

Declare the input with the provider for that application — for example `@oauth2:teamwork_cloud` or `@basic:windchill`.

### Using a token each user supplies

Use this when each user should authenticate with their own token rather than a shared, admin-managed account — for example a personal access token for a third-party API.

1. The user adds their own token to their **Linked Accounts** (a personal API token or a username-and-password login).
2. When launching a job, the user selects that personal credential. Nothing is stored on the queued job; the token is delivered to the agent only when the job runs.
3. Your function reads it from the `auth_info` input exactly as above.

Declare the input with `@token:<provider>` for an API key or personal access token, or `@basic:<provider>` for a username and password. No administrator setup is required beyond the user having a token to supply.

## Security notes

- The sign-in your function receives is **short-lived and single-use**, encrypted so that only the agent running the job can read it, and removed when the job finishes — even if it fails.
- Treat the secret like any other credential: use it for the sign-in and **never write it to logs, outputs, or artifacts**.

## Related pages

- [App Integrations](../../users/admin-guide/app-integrations) — how administrators connect applications and create service accounts.
- [Module Manifest API Reference](./api-reference/01-api-reference.md) — the full input schema, including `auth_info`.
- [Integrations, jobs, and agents](../../intro/key-concepts/integrations#authenticated-jobs) — the concepts behind authenticated jobs.


---

<!-- Source: 04-custom-modules/04-design-patterns.md -->

---
sidebar_label: Design patterns
sidebar_position: 4
title: Module design patterns
---

# Module design patterns

This page collects design patterns for modules that interact with a **third-party app** or a **DE tool**: how to report what happened, how to make repeated runs safe, and how to check the target is ready before writing to it.

Both bring conventions your function has to bridge to the platform's [file-based contract](01-integrations-sdk-intro.md#how-modules-work). A third-party app has its own data schema, its own object identifiers, and its own rules for what makes a valid record. A DE tool has its own API and its own error handling, which rarely map cleanly onto a job that either succeeded or failed.

The examples are Python, matching `stari module scaffold --type python-module`, but nothing here depends on the language.

## Always return outputs, even when the function fails

The user can only read what the job uploaded. A function that raises out of its entry point leaves them with a failed job and nothing to open — the most expensive failure mode to support, because every occurrence becomes a request for someone with agent host access to go read a local log.

Declare `logs` and `errors` as **required** outputs in the manifest:

```json
"outputs": [
  { "name": "requirements", "type": "file", "required": true, "upload_as": "model" },
  { "name": "logs", "type": "file", "required": true, "upload_as": "artifact" },
  { "name": "errors", "type": "file", "required": true, "upload_as": "artifact" }
]
```

Then catch everything at the entry point and return the output list either way:

```python
def push_requirements(input_json: str, temp_dir: str) -> list[Output]:
    """Never raises -- failures are captured in logs.txt / errors.txt
    so the job always completes with visible output."""
    temp = Path(temp_dir)
    log_handler, err_handler = add_file_handlers(temp)   # logs.txt at INFO, errors.txt at ERROR
    output_path = temp / "requirements.json"

    try:
        function_input = PushRequirementsInput.model_validate_json(input_json)
        process(function_input, output_path)
    except Exception:
        logger.exception("PushRequirements failed")
        if not output_path.exists():
            output_path.write_text(json.dumps(empty_document()), encoding="utf-8")

    flush_and_detach(log_handler, err_handler)
    return [
        Output(name="requirements", type=OutputType.FILE, path=str(output_path)),
        Output(name="logs", type=OutputType.FILE, path=str(temp / "logs.txt")),
        Output(name="errors", type=OutputType.FILE, path=str(temp / "errors.txt")),
    ]
```

Two file handlers on the root logger — `logs.txt` at `INFO`, `errors.txt` at `ERROR` — give the reader a full trace and a short "what went wrong" without having to skim. An empty `errors.txt` is a meaningful result: the job ran clean.

### Never overwrite a good revision with a failed run

When a function's output is uploaded as a new **revision** of the input model, the failure path needs care. Writing a placeholder `{}` so the job can complete would commit an empty revision over content someone depends on — the job reports success and the model is gone.

Fall back to the input revision instead, so a failed run is a no-op rather than a deletion:

```python
def preserve_input_on_failure(input_model_path: Path, output_path: Path) -> None:
    """Copy the input revision to output so a failed run does not wipe the model."""
    if input_model_path.is_file() and input_model_path.stat().st_size > 0:
        output_path.write_text(input_model_path.read_text(encoding="utf-8"), encoding="utf-8")
    else:
        output_path.write_text("{}", encoding="utf-8")
```

The new revision is byte-identical to the previous one, `errors.txt` explains why, and the reader can compare revisions to confirm nothing changed.

:::tip
The agent names an uploaded model revision from the **basename of the output file** you return. Default the output filename to the input revision's filename, so repeated runs keep one consistent name instead of renaming the model every time.
:::

## Make repeated runs converge

Jobs get repeated — by someone who did not see the first one finish, by a scheduled sync, by someone testing. If a repeat duplicates records in the third-party app, every one of those is an incident, and it is discovered by the app's users rather than by you.

Derive object identity from stable keys rather than letting the app assign one, then update-or-create:

```python
def record_id(project_id: str, qualified_name: str) -> str:
    """Same input path always yields the same UUID, so re-runs converge."""
    return str(uuid.uuid5(NAMESPACE, f"{project_id}::{qualified_name}"))
```

With `uuid5` over a stable path plus an upsert write, running the same sync five times leaves the app in the same state as running it once. That property is what lets you tell someone "just run it again" when a job fails halfway — without it, every partial failure needs manual cleanup before the retry.

The same idea applies to documents you write back to the platform: stamp a `$schema` key into every document your module produces. The next run can then recognize its own output instead of guessing from the shape, which is what makes a round trip re-runnable.

## Default writes to a dry run

A `dry_run` parameter that **defaults to `true`** lets someone point a new function at real data and see exactly what it would do, with no possibility of damage.

The pattern only works if the dry run is informative, which means the payload is written either way:

```python
document = build_document(project_name, payloads)
output_path.write_text(json.dumps(document, indent=2), encoding="utf-8")

if post:
    return sync_to_app(client, document["requirements"])

logger.info("DRY RUN -- payloads generated but NOT posted to the app")
return {"created": 0, "updated": 0, "unchanged": 0}
```

Reviewing the payload is the entire point, so write it before the branch, not inside the `if`. The reader inspects the artifact on the job, then re-runs with `dry_run=false`.

Apply the same default-to-safe rule to anything your module can create in the third-party app. Where creating structure is unambiguous — a folder that only has to exist, a tag created empty — put it behind an explicit input that defaults to off:

```json
{
  "create_missing_folders": {
    "type": "parameter",
    "validation_types": ["@boolean"],
    "optional": true
  }
}
```

Where it is not unambiguous, do not offer it at all. Structure in a third-party app usually encodes decisions your module has no basis to make — what the category levels mean, which taxonomy the organization agreed on. A module that invents them produces records that look right to the module and wrong to everyone using the app, and the mistake surfaces long after the job succeeded.

## Check preconditions before the first write

A precondition is anything that must already be true in the third-party app for your writes to land correctly. Check them all **before the first write**, and order the checks so the cheap ones fail first. A job that dies on a local JSON parse costs nothing; a job that dies halfway through a hierarchy upsert leaves records someone has to clean up by hand.

| Order | Check                                                   | Why it comes here                                   |
| ----- | ------------------------------------------------------- | --------------------------------------------------- |
| 1     | Input payload shape                                     | Local, instant, no credentials needed               |
| 2     | Credentials present                                     | Fails without a network round trip                  |
| 3     | Target container resolves (project, program, workspace) | One cheap API call; catches the most common mistake |
| 4     | Required structure exists (categories, tiers, levels)   | Determines whether records can be created at all    |
| 5     | Cross-references resolve                                | Catches functions that were run in the wrong order  |

### Report every missing credential at once

Credentials for a third-party app belong in the agent's environment, not in job inputs, so a misconfigured agent is a common failure. Report **every** missing variable in one message, and say where they belong — reporting them one at a time turns a single fix into four job runs.

```python
missing = [name for name, value in required_vars if not value]
if missing:
    raise RuntimeError(
        f"Missing credentials: {', '.join(missing)}. "
        "Set them as environment variables on the agent host, "
        "or in a .env file next to the module binary."
    )
```

Log the presence of each variable as `set` or `MISSING` — never the value. That single log line resolves most credential tickets without anyone having to reproduce the job.

:::warning
Credentials read from the agent environment apply to every job that agent runs. When a function should act as the **user** who submitted the job rather than as the agent, declare an authentication input instead and let the platform deliver the sign-in. See [Authenticating from a module](03-authenticating-from-a-module.md).
:::

### Check the required structure, not just existence

Resolving a project by name proves the project exists. It does not prove it can hold the records you are about to create. Third-party apps commonly have a structural prerequisite — category levels, a required taxonomy, a root node — that a new project does not have yet, and whose absence surfaces as an opaque API error on your first write.

Check it explicitly, and name the setup step in the error:

```python
def validate_tier_levels(tiers: dict[int, str], project_name: str) -> None:
    """Three levels are required: Program -> Assembly -> Part."""
    if len(tiers) >= 3:
        return
    raise ValueError(
        f"Project {project_name!r} must have at least three equipment tiers "
        f"(used as Program -> Assembly -> Part). "
        f"The API returned {len(tiers)} tier(s) from equipment-tiers/byProject. "
        f"Create three equipment tier levels for this project, then retry."
    )
```

## Write errors the reader can act on

The person reading your error message is looking at a failed job in the web app. They do not have your source, your log level, or your mental model of the third-party app. Assume the message is all they get.

Every failure message should answer three questions:

1. **What was checked** — name the object and the constraint, not the function that raised.
2. **What was observed** — the actual count, name, or value that failed the check.
3. **What to do next** — the concrete action, and where it has to happen.

The third part is the one most modules omit, and the one that decides whether the message saves a support round trip:

| Instead of                    | Write                                                                                                                                                                                         |
| ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `KeyError: 'equipmentTierId'` | `Project 'Ares' must have at least three equipment tiers (used as Program -> Assembly -> Part). The API returned 1 tier(s). Create three equipment tier levels for this project, then retry.` |
| `Project not found`           | `Project 'Ares ' not found. Available: Ares, Ares Test, Helios.`                                                                                                                              |
| `ValidationError: 12 errors`  | `12 equipment reference(s) missing: A-100, A-101, ... (and 7 more). Sync equipment first with @acme:push_equipment.`                                                                          |

Two habits produce most of that improvement:

- **List the valid alternatives when a lookup fails.** Name lookups fail constantly, and almost always for the same reasons — a trailing space, a near-duplicate project, an environment holding different data than the one the reader was looking at. You already hold the list you searched, so print it.
- **Truncate long lists but keep the count.** `12 missing: A-100, A-101, ... (and 7 more)` stays readable when a whole hierarchy is absent, and still conveys the scale.

Finally, keep "nothing to do" separate from "something is wrong". A query that legitimately returns zero rows is not a failure, and raising on it teaches readers to distrust your errors. Succeed, and spend a `WARNING` on the diagnosis they are about to need — most often, that they picked a name that exists but is not the one holding the data.

## Checklist

Before publishing a module that writes to a third-party app:

- The entry point never raises; `logs` and `errors` are required outputs.
- A failed run preserves the previous revision instead of committing a placeholder.
- Object IDs come from stable keys, so a repeat converges instead of duplicating.
- `dry_run` defaults to `true`, and the payload is written in both modes.
- Anything the module creates is behind an input that defaults to off.
- Preconditions are checked in cost order, and all of them run before the first write.
- Every missing credential is reported in one message.
- Every error says what was checked, what was observed, and what to do next.
- Failed lookups list the valid alternatives.

## Related pages

- [Module concepts](01-integrations-sdk-intro.md) — the file-based contract, lifecycle, and packaging conventions these patterns build on.
- [Authenticating from a module](03-authenticating-from-a-module.md) — acting as the job's user instead of the agent.
- [Module Manifest API Reference](api-reference/01-api-reference.md) — input types, `optional`, `required`, and `upload_as`.
- [Integration 302 — Build a Python module](../../tutorials/extend/302-build-module-python.md) — a worked build of the scaffold these examples assume.


---

<!-- Source: 04-custom-modules/api-reference/01-api-reference.md -->

# Module Manifest API Reference

The **module manifest** defines the contract between your module and the Istari Platform. This file communicates essential information about your module, including:

- How the platform should run it
- Which operating systems and agent versions are supported
- What functions and schemas are defined
- What dependencies or scripts are required

Some fields are **required**, while others are **optional**. For optional fields, note that specifying them may introduce **additional required subkeys**.

At a minimum, the manifest must tell Istari:

- **What this module is**
- **How to run it**
- **Which dependencies it has**

There are also sections for development dependencies (helpful for module authors) and runtime dependencies (needed when the module runs inside an agent).

---

## Module Properties

| Property                            | Type                   | Required   | Description                                                                  |
| ----------------------------------- | ---------------------- | ---------- | ---------------------------------------------------------------------------- |
| `module_type`                       | `ModuleTypes`          | Yes        | Type of module (e.g., `function`, `authentication`).                         |
| `module_version`                    | `string`               | Yes        | Module version (must follow [SemVer 2.0.0](https://semver.org/)).            |
| `module_checksum`                   | `string`               | Yes        | SHA256 checksum of the module.                                               |
| `internal`                          | `boolean`              | Yes        | Indicates if the module is confined to the authoring group.                  |
| `operating_systems`                 | `[OS]`                 | Yes        | Supported OS (must align with function-level OS support).                    |
| `agent_version`                     | `string`               | Yes        | Supported agent version range (accepts semver syntax: `^`, `~`, `>=`, etc.). |
| `authors`                           | `[ContributorInfo]`    | Yes        | Authors of the module.                                                       |
| `functions`                         | `object`               | Yes        | Declares module functions (see [Function Info](#function-info)).             |
| `dependencies`                      | `[Dependency] \| null` | No         | Runtime dependencies not bundled with the module.                            |
| `dev_dependencies`                  | `[Dependency] \| null` | No         | Dependencies needed for build/test.                                          |
| `scripts`                           | `[ScriptInfo] \| null` | No         | Build/test/clean/install scripts.                                            |
| `module_key`                        | `string`               | See notes. | Unique key in format `@scope:module`.                                        |
| `module_name`                       | `string`               | No         | Deprecated; use `module_key`.                                                |
| `tool_key`                          | `string`               | No         | Unique key of software tool used.                                            |
| `tool_versions`                     | `[string]`             | No         | Required if `tool_key` present.                                              |
| `additional_configuration_required` | `boolean`              | No         | Indicates if further configuration is needed.                                |
| `module_display_name`               | `string \| null`       | No         | Optional user-facing display name.                                           |
| `tool_display_name`                 | `string \| null`       | No         | Display name for the referenced tool.                                        |

---

## Contributor Info

| Property | Type             | Required | Description                                              |
| -------- | ---------------- | -------- | -------------------------------------------------------- |
| `name`   | `string`         | Yes      | Contributor’s full name.                                 |
| `email`  | `string`         | Yes      | Contributor’s email.                                     |
| `group`  | `string \| null` | No       | Org namespace + group (e.g., `@edison:innovation_team`). |

---

## Dependencies

| Property       | Type             | Required | Description                                   |
| -------------- | ---------------- | -------- | --------------------------------------------- |
| `name`         | `string`         | Yes      | Unique dependency identifier.                 |
| `version`      | `string`         | Yes      | Dependency version.                           |
| `tool_type`    | `ToolType`       | Yes      | `auxiliary_tool`, `istari_module`, or `auth`. |
| `display_name` | `string \| null` | No       | Human-readable name.                          |
| `command`      | `string \| null` | No       | Command to run/install.                       |
| `reason`       | `string \| null` | No       | Why this dependency is required.              |
| `url`          | `string \| null` | No       | Documentation or download URL.                |

---

## Function Info

Represents a function inside the module.

| Property            | Type                       | Required | Description                                                                                                       |
| ------------------- | -------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------- |
| `entrypoint`        | `string`                   | Yes      | Path/module/class for function execution (relative to module folder).                                             |
| `run_command`       | `string`                   | Yes      | CLI command to run. Must include placeholders like `{entrypoint}`, `{input_file}`, `{output_file}`, `{temp_dir}`. |
| `function_schema`   | `string \| FunctionSchema` | Yes      | Schema definition (inline JSON or file path).                                                                     |
| `display_name`      | `string \| null`           | No       | Human-readable name.                                                                                              |
| `operating_systems` | `[OS] \| null`             | No       | OS support (must align with module-level).                                                                        |
| `tool_versions`     | `[string] \| null`         | No       | Tool versions required.                                                                                           |

---

## Function Schemas

Functions define their **inputs** and **outputs** using schemas.

### Input Schema

| Property           | Type              | Required | Description                                                                        |
| ------------------ | ----------------- | -------- | ---------------------------------------------------------------------------------- |
| `type`             | `InputType`       | Yes      | One of: `parameter`, `user_model`, `auth_info`, `user_link`.                       |
| `validation_types` | `[string]`        | Yes      | Validators (e.g., `@extension:txt`, `@mime_type:json`, `@oauth2:google_accounts`). |
| `display_name`     | `string \| null`  | No       | Friendly name for UI.                                                              |
| `optional`         | `boolean \| null` | No       | Defaults to `false`.                                                               |

An input of type `auth_info` declares that the function signs in to an external system. Its `validation_types` use the form `@<standard>:<provider>` (for example `@oauth2:teamwork_cloud` or `@basic:windchill`). At run time the input's `value` is a path to a JSON file containing the sign-in. See [Authenticating to external systems from a module](../03-authenticating-from-a-module.md) for the supported standards and providers and how to read the credential.

### Output Schema

| Property       | Type               | Required | Description                             |
| -------------- | ------------------ | -------- | --------------------------------------- |
| `name`         | `string`           | Yes      | Output identifier.                      |
| `type`         | `OutputType`       | Yes      | Either `directory` or `file`.           |
| `required`     | `boolean`          | Yes      | Whether this output must be produced.   |
| `display_name` | `string \| null`   | No       | Friendly display name.                  |
| `upload_as`    | `UploadAs \| null` | No       | Defaults to `artifact`.                 |
| `version_of`   | `string \| null`   | No       | If output is a new version of an input. |

---

## Script Info (Optional)

| Property            | Type                  | Required | Description                    |
| ------------------- | --------------------- | -------- | ------------------------------ |
| `operating_systems` | `[OS]`                | Yes      | OS compatibility.              |
| `build`             | `BuildScript \| null` | No       | Build script with output path. |
| `install`           | `string \| null`      | No       | Install dependencies.          |
| `clean`             | `string \| null`      | No       | Cleanup script.                |
| `test_unit`         | `string \| null`      | No       | Run unit tests.                |
| `test_integration`  | `string \| null`      | No       | Run integration tests.         |

---

## Enums

- **ModuleTypes**: `function`, `authentication`
- **ToolType**: `istari_module`, `auxiliary_tool`, `auth`
- **UploadAs**: `artifact`, `model`
- **InputType**: `parameter`, `user_model`, `auth_info`, `user_link`
- **OutputType**: `directory`, `file`
- **OS**: Windows 10/11/Server 2019/2022, Ubuntu 20.04/22.04/24.04, RHEL 8/9

---

## Entrypoint, Run Command, and Schema

The `entrypoint`, `run_command`, and `function_schema` combine to define how the agent invokes a function.

- **Entrypoint**: path to the executable (relative to module folder)
- **Run Command**: full command to execute, with placeholders like `{entrypoint}`, `{input_file}`, `{output_file}`, `{temp_dir}`
- **Schema**: defines the expected inputs and outputs

---

Examples of **input file**, **output file**, and **config file** JSON are provided the **next section**


---

<!-- Source: 04-custom-modules/api-reference/02-manifest-examples.md -->

# Module Manifest Examples

This page provides **practical examples** of how to implement the module manifest and its related files. These examples illustrate how the contract between your module and the Istari Agent works in practice.

---

## Entrypoint

The `entrypoint` is the path to the executable that the agent will run. Paths must be **relative to the module folder**.

Example directory layouts:

```text
your_module/
├─ module_manifest.json
├─ your_module.exe
```

Entrypoint: `your_module.exe`

```text
your_module/
├─ module_manifest.json
└─ executables/
   └─ your_module.exe
```

Entrypoint: `executables/your_module.exe`

---

## Run Command

The `run_command` defines how the agent invokes your function. It may include placeholders:

| Placeholder       | Value                                        | Required? | Notes                                             |
| ----------------- | -------------------------------------------- | --------- | ------------------------------------------------- |
| `{entrypoint}`    | Absolute path to the entrypoint              | Yes       | Always required                                   |
| `{function_name}` | Name of the function                         | No        |                                                   |
| `{input_file}`    | Absolute path to the input JSON file         | Yes       | Required                                          |
| `{output_file}`   | Absolute path to the output JSON file        | Yes       | Required                                          |
| `{temp_dir}`      | Path to a temporary working directory        | Yes       | Cleaned up after execution                        |
| `{module_config}` | Absolute path to the module config JSON file | No        | Only if `additional_configuration_required: true` |

---

## Input File

The **input file** is a JSON object. Each key corresponds to an input defined in the Function Schema.

### Example 1

```json
{
  "input_workbook": {
    "type": "user_model",
    "value": "C:/TEMP/16b3e166-59b4-464a-8522-195d3ee7b8c0/in/workbook_a.xlsx"
  },
  "cell_row": {
    "type": "parameter",
    "value": 15
  },
  "cell_column": {
    "type": "parameter",
    "value": "N"
  }
}
```

### Example 2

```json
{
  "input_sheet": {
    "type": "user_link",
    "value": "C:/TEMP/1e57365e-c3bc-4574-a08b-7c3e7563eaf2/in/presentation.istari_google_drive_metadata_presentation"
  },
  "api_auth": {
    "type": "auth_info",
    "value": "C:/TEMP/1e57365e-c3bc-4574-a08b-7c3e7563eaf2/in/gdrive_key.dat"
  }
}
```

---

## Output File

The **output file** is a JSON array of objects. Each output corresponds to an output defined in the Function Schema.

### Example 1

```json
[
  {
    "name": "updated_workbook",
    "type": "file",
    "value": "C:/TEMP/16b3e166-59b4-464a-8522-195d3ee7b8c0/out/updated.xlsx"
  }
]
```

### Example 2

```json
[
  {
    "name": "primary_sheet",
    "type": "file",
    "value": "C:/TEMP/1e57365e-c3bc-4574-a08b-7c3e7563eaf2/out/primary_sheet.csv"
  },
  {
    "name": "cells",
    "type": "directory",
    "value": "C:/TEMP/1e57365e-c3bc-4574-a08b-7c3e7563eaf2/out/cells"
  },
  {
    "name": "summary",
    "type": "file",
    "value": "C:/TEMP/1e57365e-c3bc-4574-a08b-7c3e7563eaf2/out/summary.txt"
  }
]
```

---

## Module Config File

If `additional_configuration_required: true` is set, the agent generates a **config JSON file** containing user-supplied values.

### Example 1

```json
{
  "paraview_home": "/path/to/paraview/home/"
}
```

### Example 2

```json
{
  "cameo_home": "/path/to/cameo/installation/dir/",
  "cameo_version": "2021x-refresh2",
  "lmutil_executable_path": "/path/to/lmutil/executable",
  "server_host": "localhost",
  "port": 27000,
  "license_name": "cameoenterprisearchitectureent"
}
```

---

## Function Schema Examples

### Inputs

```json
"inputs": {
  "input_workbook": {
    "type": "user_model",
    "validation_types": ["@extension:xlsx"],
    "optional": false,
    "display_name": "Input Workbook"
  },
  "cell_row": {
    "type": "parameter",
    "validation_types": [],
    "optional": true,
    "display_name": "Row of cell (optional)"
  },
  "cell_column": {
    "type": "parameter",
    "validation_types": [],
    "optional": true,
    "display_name": "Column of cell (optional)"
  }
}
```

### Outputs

```json
"outputs": [
  {
    "name": "updated_workbook",
    "type": "file",
    "required": true,
    "upload_as": "model",
    "version_of": "input_workbook"
  }
]
```

```json
"outputs": [
  {
    "name": "primary_sheet",
    "type": "file",
    "required": true,
    "upload_as": "model",
    "display_name": "Primary Sheet - The primary sheet as a CSV file"
  },
  {
    "name": "cells",
    "type": "directory",
    "required": true,
    "display_name": "Cells - Each cell as a separate JSON file"
  },
  {
    "name": "summary",
    "type": "file",
    "required": false,
    "upload_as": "artifact",
    "display_name": "Summary - Summary of sheet contents as a TXT file"
  },
  {
    "name": "statistics",
    "type": "file",
    "required": false,
    "display_name": "Statistics - Summary of sheet statistics as a JSON file"
  }
]
```

---


---

<!-- Source: 04-custom-modules/index.md -->

---
sidebar_label: Overview
sidebar_position: 0
title: Integrations SDK
---

# Integrations SDK

The **Integrations SDK** is the toolkit and contract for building custom **modules** that extend the Istari Digital Platform. A module packages one or more **functions** that an Istari Digital agent can execute as part of a user job — typically wrapping a CAD tool, simulation solver, in-house script, or external service so it becomes a first-class platform capability.

This section is for integration developers — designing, implementing, validating, and packaging modules. The [Istari Digital CLI](../../icbs-install/05-CLI/index.md) is the tool used at every step of the workflow; this section explains the **contract**, the **authoring framework**, and the **end-to-end workflow** that the CLI supports.

## Key features

- **Language-agnostic, file-based contract.** A module is a directory containing a `module_manifest.json` and the executable that runs your function. The agent passes inputs and outputs as JSON files on disk — your function can be written in any language. See [Module concepts](./01-integrations-sdk-intro.md).
- **Two authoring paths.** Hand-roll a manifest plus any executable (shell, Go, .NET, …) for maximum flexibility, or scaffold a structured **Python module** with `stari module scaffold --type python-module` to get a Pydantic-typed function framework, a function registry, build scripts, hooks, and pre-built templates out of the box.
- **Function variants for OS and tool versions.** A single module can declare multiple variants of the same function targeting different operating systems (Windows 10/11, Ubuntu 20.04/22.04/24.04, RHEL 8/9) or different tool versions; the platform resolves which to invoke at runtime.
- **Manifest-driven publishing.** A versioned manifest declares everything the platform needs — entrypoint, run command, function schemas, dependencies, supported OS, build / test / install / clean scripts.
- **Scoped naming.** Modules and functions use a `@scope:name` convention. `@istari` is reserved for first-party integrations; pick your own scope (e.g. `@acme_corp:cad_tools`) for internal or distributed modules.

## Authoring workflow

The end-to-end module-authoring workflow:

1. **Scaffold or hand-roll the module structure.** For Python: `stari module scaffold <name> --type python-module` generates a project with Pydantic `Input` / `Output` types, a function registry, function templates for common patterns (1-input → 1-output, parameter sweep, OIDC auth, etc.), `pyproject.toml` build configuration, hooks, and tests. For other languages: create a directory with `module_manifest.json` and your executable.
2. **Define the function schema in the manifest.** Inputs declare types (`@string`, `@number`, `@object`, validation types like `@extension:txt`); outputs declare files or directories. Export the JSON schema with `stari schemas get function_input` for editor tooling or LLM-assisted editing.
3. **Implement the function.** Read the input JSON, run your logic, write the output JSON. The Python scaffold provides a registry-based dispatch and Pydantic validation; non-Python modules parse `--input-file` and write `--output-file` directly.
4. **Validate the manifest** — `stari module lint module_manifest.json`. If it lints, it's publishable.
5. **Generate sample inputs and outputs** — `stari function generate test_files --function @scope:<fn>` produces realistic JSON files for local testing. See [Function test file generation](../../icbs-install/05-CLI/03-function-test-files-generation.md).
6. **Run the manifest's scripts** — `stari module run build`, `stari module run test_unit`, `stari module run install`, `stari module run clean`. The Python scaffold wires these to Poetry; non-Python modules wire them however they like.
7. **Package** — for Python, build a self-contained binary with [PyInstaller](https://pyinstaller.org) (or your preferred bundler) and zip the result. For other languages, zip the directory.
8. **Publish to the registry** — `stari client publish module_manifest.json`. See the [Istari Digital CLI](../../icbs-install/05-CLI/index.md) for credentials configuration.
9. **Grant access** — through the platform UI, assign the published module to users or pools so they can run it.

After publishing, deploying the module onto agent hosts is [Module deployment](../03-Modules/01-Module-Installation.md): unpack the package into the agent's `istari_modules` folder.

## What's in this section

| Page                                                                 | What it covers                                                                                                                          |
| -------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| [Module concepts](./01-integrations-sdk-intro.md)                    | Modules, functions, manifests, the file-based agent contract, lifecycle, package layout, function variants, scopes, and best practices. |
| [Authoring with the CLI](./02-istari-cli-integrations-management.md) | How `stari` supports module authors — scaffolding, manifest creation, schema generation, script execution, linting, mock test data.     |
| [Design patterns](./04-design-patterns.md)                           | Patterns for modules that interact with a third-party app or DE tool — reporting failures, repeatable writes, dry runs, preconditions.  |
| [Module Manifest API reference](./api-reference/01-api-reference.md) | Every manifest field — required vs optional, types, semver-aware version ranges, function info, scripts, dependencies, enums.           |
| [Manifest examples](./api-reference/02-manifest-examples.md)         | Worked examples of `entrypoint`, `run_command`, function schemas, input / output / config file payloads.                                |

:::tip
For a guided first build:

- [Integration 301 — First module (shell)](../../tutorials/extend/301-build-module.md): hand-rolled manifest plus a shell script. Teaches the file contract end-to-end without a Python framework.
- [Integration 302 — Build a Python module](../../tutorials/extend/302-build-module-python.md): uses `stari module scaffold --type python-module` to generate a Pydantic-typed framework, then compiles to a standalone binary with PyInstaller.
  :::
