# Documentation Documentation for the Griptape Nodes engine: scripting (retained mode), the project system, custom node development, the built-in node reference, and the engine MCP surface. Use these files to ground an agent in the engine's actual API rather than scraping rendered HTML or fetching unrenderered raw markdown from `main`. # Get started # Unleash Your AI Superpowers with Griptape Nodes Imagine being able to harness the full power of today's most advanced AI models without writing a single line of complex code. That's exactly what Griptape Nodes delivers. What if you could: - Create an AI that automatically summarizes your research papers - Build a visual art generator that brings your wildest ideas to life - Design a virtual assistant that knows exactly how you like things done - Construct intelligent workflows that do in minutes what used to take days With Griptape Nodes, you can connect visual building blocks that represent powerful AI capabilities, drag-and-drop them into place, and watch as they work together to accomplish extraordinary things. It's like having a visual superpower for orchestrating AI. Whether you're a creative who wants to explore new AI-powered possibilities, a knowledge worker looking to automate tedious tasks, or a curious mind wanting to experiment with cutting-edge technology, Griptape Nodes opens doors you might have thought were locked to all but professional programmers. And for those who want to go deeper? A little Python knowledge goes a long way here. Our [simple scripting interface](https://docs.griptapenodes.com/development/retained_mode/index.md) lets you customize and extend what's possible, turning your big ideas into working solutions without requiring an engineering degree. Griptape Nodes is where your imagination meets AI capability. Let's build something amazing together. # Installing Griptape Nodes There are two ways to get Griptape Nodes running: - **[Griptape Nodes Desktop](#griptape-nodes-desktop-recommended)** (recommended) — a fully managed desktop application that bundles the engine and the editor. It handles sign-in, provisions an API key for you, and keeps everything up to date. No terminal required. - **[Manual engine install](#advanced-manual-engine-install)** — for power users who want to install and run the engine themselves from the command line, paired with the web-based editor. If your organization gave you a **license key** instead of a Griptape account, see [Activating with a license](#activating-with-a-license) below. ## Griptape Nodes Desktop (Recommended) Griptape Nodes Desktop is a single application that includes both the engine and the editor. It automatically installs and manages the engine, signs you in, and provisions a Griptape API key on your behalf — there is nothing to configure by hand. ### 1. Download and install Download Griptape Nodes Desktop for your platform from [griptapenodes.com](https://griptapenodes.com). It is available for macOS, Windows, and Linux. ### 2. Sign in Launch the application and click **Login or Sign-Up** to sign in with your Griptape account. > If you've already signed up for [Griptape Cloud](https://cloud.griptape.ai), your existing credentials will work here! If your organization issued you a license key instead of a Griptape account, click **Activate with a License** instead — see [Activating with a license](#activating-with-a-license). ### 3. Choose your workspace On first run, you'll be asked to choose a *workspace directory*. Your workspace directory is where Griptape Nodes saves your [project files](https://docs.griptapenodes.com/glossary/#project-files) and [generated assets](https://docs.griptapenodes.com/glossary/#generated-assets). Accept the default or pick any location you prefer. That's it! The application takes care of the rest — installing the engine, generating an API key, and opening the editor. You're ready to move on to the [tutorials](https://docs.griptapenodes.com/tutorials/index.md). You can change your workspace, manage your license, and choose how the application updates itself later from [App Settings](https://docs.griptapenodes.com/guides/desktop/app_settings/index.md). ## Advanced: Manual Engine Install Prefer to manage the engine yourself? With this approach, you install the engine from the command line and use the editor in your web browser at . The Editor and the Engine are decoupled and communicate with each other through an event service, so the engine doesn't have to run on the same machine as your browser. If you want the engine to have access to more resources than your laptop provides, you can run it on a separate machine — the instructions below work the same for either approach. ### 1. Sign up or log in To get started, visit and click the sign-in button. > If you've already signed up for [Griptape Cloud](https://cloud.griptape.ai), your existing credentials will work here! Once you've logged in, the editor opens with an engine setup dialog (it appears automatically whenever no engine is connected). Select the **Get Started** tab and expand **Install Engine Manually** to follow along with the steps below. ### 2. Install the engine 1. Install [uv](https://docs.astral.sh/uv/getting-started/installation/) if you don't already have it. 1. Run the following command to install the Griptape Nodes Engine: ``` uv tool install griptape-nodes ``` After installation, run `griptape-nodes` (or the shorthand `gtn`) in the terminal *for the first time* and you'll be walked through a series of configuration questions. ### 3. Configuration **First**, you'll be prompted for your Griptape API Key. This key allows the engine to communicate with the editor. 1. Return to the editor tab in your web browser. In the engine setup dialog, under the **Get Started** tab's **Install Engine Manually** section, open the **Generate an API Key** step. 1. Click the **Generate API Key** button. Your new key appears in place of the button. 1. Copy the key and paste it into the terminal prompt. The key is only displayed once in the browser, so copy it before moving on. ``` ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ Griptape API Key │ │ A Griptape API Key is needed to proceed. │ │ This key allows the Griptape Nodes Engine to communicate with the Griptape Nodes Editor. │ │ In order to get your key, return to the https://nodes.griptape.ai tab in your browser and click the button │ │ "Generate API Key". │ │ Once the key is generated, copy and paste its value here to proceed. │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ Griptape API Key (YOUR-KEY-HERE): ``` Info If you've previously run `gtn init` your key might be presented to you in this dialog. You can accept it by pressing Enter or use a different value as required. **Second**, you'll be prompted to set your *workspace directory*. Your workspace directory is where the engine will save [project files](https://docs.griptapenodes.com/glossary/#project-files) and [generated assets](https://docs.griptapenodes.com/glossary/#generated-assets). It will also contain a [.env](https://docs.griptapenodes.com/glossary/#.env) for your Griptape Nodes [secret keys](https://docs.griptapenodes.com/glossary/#secret-keys). ``` ╭───────────────────────────────────────────────────────────────────╮ │ Workspace Directory │ │ Select the workspace directory. This is the location where │ │ Griptape Nodes will store your saved workflows. │ │ You may enter a custom directory or press Return to accept │ │ the default workspace directory │ ╰───────────────────────────────────────────────────────────────────╯ Workspace Directory (/Users/user/Documents/GriptapeNodes) ``` Pressing Enter will use the default: `/GriptapeNodes`, where `` is the directory from which you're running the `gtn` command. Alternatively, you can specify any location you prefer. **Finally**, you'll be offered a few optional configuration steps. Each can be skipped by accepting the default, and all of them can be revisited later: - **Storage backend** — store static files locally (the default) or in a Griptape Cloud bucket. - **Griptape Cloud bucket** — used for syncing workflows and assets across multiple machines. - **Hugging Face token** — needed only if you plan to download gated models from the Hugging Face Hub. - **Additional libraries** — optionally install the Diffusers and Griptape Cloud node libraries. > You can always return to this configuration flow using the `gtn init` command if you need to make changes in the future. ### 4. Start your engine Your installation is now complete and you're ready to proceed to creating your first workflow or trying out one of the sample workflows. To get started, run `griptape-nodes` (or `gtn`) in your terminal, then return to your browser. Your browser tab at will update to show *Create from scratch*, allowing you to start from a blank canvas, together with several sample Griptape Nodes workflows that you can experiment with! ## Activating with a License Organizations can run Griptape Nodes on license keys instead of individual Griptape accounts. If your organization admin sent you a license key, you don't need to sign up for anything: 1. Install [Griptape Nodes Desktop](#griptape-nodes-desktop-recommended) as described above. 1. On the login screen, click **Activate with a License** instead of signing in. 1. Paste your license key and activate. If your organization runs an on-premises [Admin Server](https://docs.griptapenodes.com/enterprise/admin_server/index.md), you'll also point the application at it during activation. See [Using the Admin Server](https://docs.griptapenodes.com/enterprise/using_the_admin_server/index.md) for the full walkthrough of the license activation flow. Organization admins looking to issue and manage license keys should start with the [Admin Dashboard](https://docs.griptapenodes.com/enterprise/admin_dashboard/index.md) guide. For a diagram of how the pieces fit together in either deployment model, see [Architecture](https://docs.griptapenodes.com/architecture/index.md). ## Next Steps Next, on to learning how to actually work inside Griptape Nodes! [Begin](https://docs.griptapenodes.com/tutorials/index.md) To remove Griptape Nodes, see [Uninstalling Griptape Nodes](https://docs.griptapenodes.com/uninstalling/index.md). # Uninstalling Griptape Nodes Start with the section that matches how you installed: - **[Griptape Nodes Desktop](#griptape-nodes-desktop)** — the desktop application you downloaded from [griptapenodes.com](https://griptapenodes.com). - **[Manual engine install](#manual-engine-install)** — the engine you installed yourself with `uv tool install griptape-nodes`. Then finish with [What's left behind](#whats-left-behind). Your workflows are not removed for you No uninstaller touches your workspace directory, so your projects, workflows, and generated assets stay on disk until you delete them yourself. Copy anything you want to keep somewhere safe *before* you get to [Remove your workspace](#remove-your-workspace). ## Griptape Nodes Desktop The application bundles the engine and manages the engine's directories inside its own data folder. ### macOS 1. Quit Griptape Nodes. 1. Open **Applications** in Finder and drag **Griptape Nodes** to the Trash. 1. Empty the Trash. This leaves the data folder in place. See [Remove the application's data](#remove-the-applications-data-macos-and-linux). ### Windows 1. Quit Griptape Nodes. 1. Open **Settings → Apps → Installed apps**. 1. Find **Griptape Nodes**, open its **...** menu, and choose **Uninstall**. The Windows uninstaller removes: - The application, from `%LOCALAPPDATA%\ai.griptape.nodes.desktop`, including any downloaded update packages kept alongside it. - Start menu and desktop shortcuts. - The **Installed apps** entry. - Both of the application's data folders, `%APPDATA%\Griptape Nodes` and `%LOCALAPPDATA%\Griptape Nodes`. That covers the app's settings and logs plus everything the engine wrote, including your API keys. Continue with [What's left behind](#whats-left-behind). ### Linux Griptape Nodes Desktop ships as an AppImage: a single file that isn't installed anywhere in particular. 1. Quit Griptape Nodes. 1. Delete the `.AppImage` file you downloaded. 1. If you registered the AppImage with a helper such as AppImageLauncher, remove it through that tool instead so the launcher entry under `~/.local/share/applications` goes with it. Then continue with [Remove the application's data](#remove-the-applications-data-macos-and-linux). ### Remove the application's data (macOS and Linux) On macOS and Linux, deleting the application leaves its data behind. Delete these folders too. On Windows the uninstaller already removed the equivalents. | What | macOS | Linux | | -------------------------- | ----------------------------------------------------- | ------------------------------- | | Application data | `~/Library/Application Support/Griptape Nodes` | `~/.config/Griptape Nodes` | | Log files | `~/Library/Logs/Griptape Nodes` | `~/.config/Griptape Nodes/logs` | | Downloaded update packages | `~/Library/Caches/velopack/ai.griptape.nodes.desktop` | — | The application data folder holds app settings, your sign-in session, any license keys you activated, and the files the engine wrote: `griptape_nodes_config.json`, the `.env` file with your API keys and other secrets, agent conversation threads, model download records, and the ffmpeg binaries the engine downloads for video previews. Your node libraries are not in here; they live in your workspace, which is [handled separately](#remove-your-workspace). On Linux, updates are staged in a temporary directory, so there is nothing to clean up there. Find the exact path from inside the app Before you uninstall, open **App Settings** and scroll to [App Data Locations](https://docs.griptapenodes.com/guides/desktop/app_settings/#app-data-locations). It shows the application data folder for your machine, with an **Open** button to reveal it in Finder or your file manager. Log files and update packages aren't listed there, so use the table above for those. Then continue with [What's left behind](#whats-left-behind). ## Manual engine install Run the uninstaller: ``` gtn self uninstall ``` Press Enter when it prints `When done, press Enter to exit.` so it can finish removing the executable. It removes: - The engine's **configuration** and **data** directories, `~/.config/griptape_nodes` and `~/.local/share/griptape_nodes`, including the `.env` file with your API keys and other secrets. - The `griptape-nodes` and `gtn` commands, by running `uv tool uninstall griptape-nodes`. If it prints a **Caveats** section, read it. It lists the configuration files it left alone, the ones inside your workspace and project folders, along with anything it couldn't delete and you should remove by hand. Where those folders are on Windows The engine uses the same layout on every platform, so on Windows the two folders are `%USERPROFILE%\.config\griptape_nodes` and `%USERPROFILE%\.local\share\griptape_nodes`. Both sit under your home folder, not in `AppData`. If `gtn` no longer runs, usually because a previous uninstall left a broken virtual environment behind, do the same work by hand: ``` uv tool uninstall griptape-nodes rm -rf ~/.config/griptape_nodes ~/.local/share/griptape_nodes ``` On Windows, in PowerShell: ``` uv tool uninstall griptape-nodes Remove-Item -Recurse -Force "$env:USERPROFILE\.config\griptape_nodes" Remove-Item -Recurse -Force "$env:USERPROFILE\.local\share\griptape_nodes" ``` Then continue with [What's left behind](#whats-left-behind). ## What's left behind ### Remove your workspace Your [workspace directory](https://docs.griptapenodes.com/guides/projects/workspace/index.md) holds your projects, workflows, generated assets, and node libraries, and is never deleted automatically. It defaults to: - `Documents/GriptapeNodes` if you used the desktop application. - A `GriptapeNodes` folder inside whichever directory you first ran `gtn` from, if you installed the engine yourself. You may have pointed it somewhere else, so note the path before you uninstall. Copy out anything you want to keep, then delete the folder. ### Remove downloaded models Models that nodes pull from the Hugging Face Hub go into the shared Hugging Face cache, not into a Griptape Nodes directory. | Platform | Path | | ------------- | -------------------------------------- | | macOS / Linux | `~/.cache/huggingface/hub` | | Windows | `%USERPROFILE%\.cache\huggingface\hub` | If anything else on your machine downloads from Hugging Face, don't delete the whole folder. With a manual engine install you can clear out just the Griptape Nodes models before you uninstall: ``` gtn models list gtn models delete ``` You can also do this from the editor's **Model Management** window while Griptape Nodes is still installed. See [Managing models and libraries](https://docs.griptapenodes.com/guides/editor/managing_models_and_libraries/index.md). ### Remove uv Griptape Nodes uses [uv](https://docs.astral.sh/uv/) to build the virtual environments for node libraries, and uv keeps caches and Python interpreters of its own. If uv is on your machine only because you installed Griptape Nodes, follow uv's [uninstallation instructions](https://docs.astral.sh/uv/getting-started/installation/#uninstallation) to remove those along with uv itself. Those instructions leave your `PATH` alone. uv's installer added its executable directory (`~/.local/bin`, or `%USERPROFILE%\.local\bin` on Windows) to it, so once uv is gone you can drop that entry: on macOS and Linux delete the line uv added to your shell profile, and on Windows remove it under **Settings → System → About → Advanced system settings → Environment Variables**. ## Reinstalling To come back, follow [Installing Griptape Nodes](https://docs.griptapenodes.com/installation/index.md) again. If you kept your workspace, point the new install at it and your projects and workflows are where you left them. # Architecture Griptape Nodes is two programs: an **editor** where you build workflows, and an **application** that executes them on a machine you control. The application does the work, holds the data, and calls out to Griptape Cloud for the few things it cannot do itself. That shape does not change between deployments. What changes is the network path from your machines to Griptape Cloud: - **[SaaS configuration](#saas-configuration)** is the default. Your machine reaches Griptape Cloud directly. - **[On-premises configuration](#on-premises-configuration)** keeps your machines off the public internet. They reach Griptape Cloud only through a single [Admin Server](https://docs.griptapenodes.com/enterprise/admin_server/index.md) you run. Both run identical software. Moving between them changes a route, not a product. ## The pieces | Component | What it is | Where it runs | | --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------- | | **Editor** | The visual canvas where you build workflows. | In your browser, or bundled inside Griptape Nodes Desktop. | | **Application** | The program that runs your workflows, distributed as [`griptape-nodes`](https://pypi.org/project/griptape-nodes/). It executes the graph, manages your workspace, and enforces the permissions your license grants. | On your machine. | | **Engine** | The open source workflow runtime, distributed as [`griptape-nodes-engine`](https://pypi.org/project/griptape-nodes-engine/) with source at [griptape-nodes-engine](https://github.com/griptape-ai/griptape-nodes-engine). Loads node libraries and executes the graph. | Inside the application. | The engine is where workflow execution actually happens, and it is fully open source, so anything you want to verify about how a workflow runs or where it writes files is auditable. It is not a separate deployment target: an editor connects to the application, never to the engine directly. [Griptape Nodes Desktop](https://docs.griptapenodes.com/installation/#griptape-nodes-desktop-recommended) bundles the editor and ships the application with a pinned Python interpreter, so there is nothing to install separately. ## Where your data lives Almost everything you create is written to your **workspace**: a single root directory that the application reads and writes, and from which relative paths are resolved. Its location is yours to choose. It defaults to a folder beside the application, but you can point it anywhere the machine can reach, including network attached storage such as a NAS mount or LucidLink, which is a common arrangement for studios that keep project data on shared infrastructure. See [Workspace](https://docs.griptapenodes.com/guides/projects/workspace/index.md). That makes the workspace the thing to look at when you are asking where data goes. The application writes to the path you configured, and nothing in this table is sent to Griptape Cloud by default. | What | Where it lives | Sent to Griptape Cloud? | | -------------------------------------- | --------------------------------------------------------------------------------------------------- | ----------------------------------------------------------- | | Workflows and project files | Your workspace | No | | Generated assets: images, video, audio | Your workspace | No, unless you switch the storage backend to Griptape Cloud | | Secrets and API keys | Environment variables, then a `.env` file in your workspace, then one in your user config directory | No | | Conversation history | Your workspace | No | | Node libraries | Installed on the machine running the application, each in its own virtual environment | No | Two settings can change this, both off by default and both documented in [Configuration](https://docs.griptapenodes.com/guides/configuration/index.md): - **Storage backend.** Defaults to `local`, which writes assets to your workspace. Set it to `gtc` and generated assets go to a Griptape Cloud bucket instead. - **Synced workflows.** Opt in to sync workflows through a Griptape Cloud bucket to share them across machines. Beyond those, data leaves the workspace only where a workflow sends it: a node that calls a model provider sends that node's inputs to that provider. Which is the next section. ## What talks to Griptape Cloud The application depends on Griptape Cloud for a small number of things. This is the complete list. | Capability | Required? | What crosses the boundary | | -------------------------- | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | | **Licensing and sessions** | **Yes.** The application will not run without it. | Your license, and session allocate, renew, and release calls. No workflow content. | | **Entitlements** | Yes, resolved with the session | The permissions your license grants, returned to the application, which enforces them locally | | **Model proxy** | No | The prompts and inputs of nodes you point at it, forwarded to the model provider | | **Buckets and Assets** | No, opt in | Assets and workflows you choose to sync | | **Nodes API WebSocket** | No | Editor and application events, when the two are not on the same machine | | **Admin Dashboard** | No, administrators only | License and permission administration, from a browser. See [Admin Dashboard](https://docs.griptapenodes.com/enterprise/admin_dashboard/index.md). | **Licensing is the only hard dependency.** A license is what permits the application to run: the application validates it, asks Griptape Cloud to allocate a session, receives the permissions attached to it, and enforces those locally while it runs. Workflow content is never part of that exchange. See [Using the Admin Server](https://docs.griptapenodes.com/enterprise/using_the_admin_server/index.md) for activation. Model calls do not have to involve Griptape Cloud at all. You can point nodes at a third party provider with your own API key, or at a model running locally under Ollama or LM Studio, in which case prompts never leave the machine. See [AI Providers](https://docs.griptapenodes.com/guides/agent/providers/index.md). ### How the editor reaches the application The editor and the application are separate programs, so events between them need a transport: - **Direct WebSocket.** The application listens locally and the editor connects straight to it. Events never leave the machine. This is what Griptape Nodes Desktop uses when you activate with a license. - **Nodes API WebSocket.** The editor and the application each open their own outbound connection to Griptape Cloud, which relays events between them. This is what lets an editor in a browser drive an application on a different machine. The distinction matters for privacy, because editor traffic carries workflow content: node graphs, parameter values, and previews of generated assets. On the direct WebSocket none of it is exposed to the network. Either way your workspace stays on the machine that runs the workflows. The [SaaS configuration](#saas-configuration) section below draws both arrangements. ## SaaS configuration The default. Your machine reaches Griptape Cloud directly, so nothing needs to be deployed on your side. This configuration comes in two variations, which differ only in how the editor reaches the application. In all three diagrams on this page, solid lines are required and dotted lines are optional, and a double headed arrow means data comes back along the same path. The only traffic Griptape Nodes cannot run without is licensing. ### Editor and engine on the same machine The arrangement you get from Griptape Nodes Desktop. The editor connects straight to the application that hosts the engine, so no workflow content touches the network. ``` flowchart TB subgraph machine["Your machine"] direction TB editor["Editor"] app["Griptape Nodes application
engine + policy enforcement"] workspace[("Workspace
workflows, assets, secrets")] local["Local models
Ollama, LM Studio"] editor <-->|"direct WebSocket"| app app <--> workspace app <-.-> local end subgraph cloud["Griptape Cloud"] direction TB control["Licensing and sessions
Entitlements"] modelproxy["Model proxy"] buckets["Buckets and Assets"] end providers["Third-party model providers"] app <==>|"license + session out
entitlements back"| control app <-.->|"prompts out
completions back"| modelproxy app <-.->|"optional sync"| buckets modelproxy <--> providers app <-.->|"your own API key"| providers ``` ### Editor and engine on different machines Use this when you want the engine on a bigger box than the one you are sitting at, or the editor in a browser on a laptop. The editor and the application each open an **outbound** connection to the Nodes API WebSocket, which relays events between them. ``` flowchart TB subgraph remote["Any other machine"] editor["Editor
in a browser"] end subgraph machine["Your machine"] direction TB app["Griptape Nodes application
engine + policy enforcement"] workspace[("Workspace
workflows, assets, secrets")] local["Local models
Ollama, LM Studio"] app <--> workspace app <-.-> local end subgraph cloud["Griptape Cloud"] direction TB wsapi["Nodes API
WebSocket"] control["Licensing,
sessions,
entitlements"] modelproxy["Model
proxy"] buckets["Buckets
and Assets"] end providers["Third-party model providers"] editor <-.->|"outbound"| wsapi app <-.->|"outbound"| wsapi app <==>|"license out
entitlements back"| control app <-.->|"model calls"| modelproxy app <-.->|"optional sync"| buckets modelproxy <--> providers app <-.->|"your own API key"| providers ``` Both sides dial out, so this arrangement needs no open inbound port, and neither machine needs to know the other's address. The trade is that editor traffic now crosses the internet: node graphs, parameter values, and asset previews pass through Griptape Cloud. Your workspace does not move, and the workflow still runs on the machine hosting the engine. **Neither variation applies on premises.** The Nodes API WebSocket is a Griptape Cloud service, so an on-premises deployment uses the direct connection, which is what keeps workflow events inside your network. ## On-premises configuration Your machines have no route to the public internet. They reach Griptape Cloud through an [Admin Server](https://docs.griptapenodes.com/enterprise/admin_server/index.md) you run inside your network, so you allow one host through your firewall instead of one per machine. ``` flowchart TB subgraph network["Your network"] direction TB editor["Editor
bundled with Desktop"] app["Griptape Nodes application
engine + policy enforcement"] workspace[("Workspace
workflows, assets, secrets")] local["Local models
Ollama, LM Studio"] admin["Admin Server
single egress point
optional path filtering"] editor <-->|"direct WebSocket
never leaves your network"| app app <--> workspace app <-.-> local app <-->|"HTTPS, in-network"| admin %% Invisible link: keeps the Admin Server on its own rank below the %% local models, so the egress edge does not pass behind them. local ~~~ admin end subgraph cloud["Griptape Cloud"] direction TB control["Licensing and sessions
Entitlements"] modelproxy["Model proxy"] end admin <==>|"HTTPS: the only traffic
leaving your network"| control admin <-.->|"permitted only if you allow it"| modelproxy ``` Three properties carry the configuration. **Nothing about building or running a workflow crosses the perimeter.** Griptape Nodes Desktop bundles the editor, so no browser fetches it over the internet, and it connects to the application over the direct WebSocket. Workflows, assets, and secrets sit in a workspace on the machine. Combined with a local model runner, a workflow can execute start to finish with no packet leaving your network. **One host egresses, and you decide what it may carry.** Every application points at the Admin Server instead of `cloud.griptape.ai`, giving you one firewall rule and one place to audit outbound traffic. The Admin Server can also restrict which Cloud paths are permitted, so you can keep model calls in network while still permitting licensing. See [forwarding rules](https://docs.griptapenodes.com/enterprise/admin_server/#forwarding). It forwards rather than decides: it validates no licenses, allocates no sessions, and caches nothing, and each application's own credential passes through untouched for Griptape Cloud to accept or reject. **What must egress is a fixed, inspectable list.** Licensing needs the routes below, and nothing else is required. The Admin Server refuses to start if your configuration would block them: | Route | Why it is needed | | ---------------------- | -------------------------------------------------------------------- | | `/api/sessions/*` | Allocate and manage the session that permits the application to run. | | `/api/session-renew` | Keep that session alive. | | `/api/session-release` | End the session cleanly and free the seat. | | `/api/users` | Identify the license holder at startup and on each heartbeat. | | `/api/organizations` | Resolve the owning organization at startup and on each heartbeat. | Why the Cloud connection is worth having That connection does more than allocate a session. It is also how an application picks up the current contents of its license, so when an administrator changes what a license permits, running deployments get the new permissions on their next session rather than waiting for a reissue or a redeploy. Managing entitlements centrally and having them take effect immediately is the practical benefit of keeping the route open. It does mean on premises is not a disconnected mode. On premises means your machines, workflows, and assets stay inside your network; it does not mean Griptape Nodes runs with no route out. Sessions are allocated by Griptape Cloud, so the Admin Server needs to reach it. If that route is unavailable, the Admin Server returns an error rather than serving stale approvals, and applications cannot start new sessions. ## Related pages - [Installation](https://docs.griptapenodes.com/installation/index.md) covers installing Desktop or the application by hand. - [Configuration](https://docs.griptapenodes.com/guides/configuration/index.md) covers the workspace, storage backend, and static server settings. - [Assets and Outputs](https://docs.griptapenodes.com/guides/assets/index.md) explains where generated files go and how the editor previews them. - [Using the Admin Server](https://docs.griptapenodes.com/enterprise/using_the_admin_server/index.md) walks through activating with a license key. - [Admin Server](https://docs.griptapenodes.com/enterprise/admin_server/index.md) covers deploying and configuring the on-premises proxy. - [Admin Dashboard](https://docs.griptapenodes.com/enterprise/admin_dashboard/index.md) covers issuing license keys and building permission templates. # Frequently Asked Questions Tip Chasing a specific problem or error? See [Troubleshooting](https://docs.griptapenodes.com/troubleshooting/index.md) for commonly encountered issues and how to recover from them. ## Where is my workspace (where do my files save)? Files such as saved workflows, etc., are saved in a the Workspace Directory. The path for the Workspace Directory can be found in the Griptape Nodes Editor: 1. Open the Griptape Nodes Editor. 1. Open an existing workflow or create a blank one. 1. Click "Settings". 1. Select "Configuration Editor". 1. Click "Griptape Nodes Settings" on the leftmost column, if not already selected. 1. The path is listed under "Workspace Directory". If you are not running the Editor, run this command and it will report back your Workspace Directory: ``` gtn config show | grep workspace ``` ## Can I run the Engine on a different machine than the Editor? The Engine and Editor can run on completely separate machines. Remember that any files you save or libraries you register will be stored on the machine where the Engine is running. So if you're looking for your files and can't find them right away, double-check which machine the Engine is running on. ## Where is Griptape Nodes installed? Looking for the exact installation location of your Griptape Nodes? This command will show you precisely where it's installed: === "macOS / Linux" ```` ```bash dirname $(dirname $(readlink -f $(which griptape-nodes))) ```` ``` === "Windows (PowerShell)" ``` ```powershell Split-Path (Split-Path (Get-Command griptape-nodes).Source) ``` ``` ## Can I see or edit my config file? To get a path to the file, go to the top Settings menu in the Editor, and select **Copy Path to Settings**. That will copy the config file path to your clipboard. If you prefer working in the command line, you can also use: ``` gtn config show ``` ## How do I install the Advanced Media Library after Initial Setup? For the broader story on installing and managing libraries (including non-Advanced-Media libraries), see [Libraries](https://docs.griptapenodes.com/guides/libraries/index.md). If you initially declined to install the Advanced Media Library during setup but now want to add it, you can do so by running: ``` gtn init ``` This will restart the configuration process. You can press Enter to keep your existing workspace and Griptape Cloud API Key settings. When prompted with: ``` Register Advanced Media Library? [y/n] (n): ``` Press **y** to install the Advanced Media Library, or **n** to skip installation. Note Some nodes in the Advanced Media Library require specific models to function properly. You will need to install these models separately. Refer to each node's documentation to determine which nodes need which models; they each have links to specific requirements. ## What happened to deprecated nodes from the Advanced Media Library? Version 0.64.0 removed deprecated nodes from the Advanced Media Library. These nodes were previously marked for deprecation and have been replaced with more flexible alternatives. If you have workflows that use deprecated nodes, please refer to the [MIGRATION.md](https://github.com/griptape-ai/griptape-nodes/blob/main/MIGRATION.md) guide. This comprehensive guide provides: - A complete list of removed nodes and their replacements - Step-by-step migration instructions - Visual examples of replacement nodes - Details about the new Diffusion Pipeline Builder system The migration guide includes replacements for all deprecated image processing, diffusion pipeline, upscaling, and LoRA nodes. ## How do I uninstall Griptape Nodes? See [Uninstalling Griptape Nodes](https://docs.griptapenodes.com/uninstalling/index.md). ## How do I update Griptape Nodes? Griptape Nodes will automatically check if it needs to update every time it runs. If it does, you will be prompted to answer with a (y/n) response. Respond with a y and it will automatically update to the latest version of the Engine. If you would like to *manually* update, you can always use either of these commands: ``` griptape-nodes self update griptape-nodes libraries sync ``` or ``` gtn self update gtn libraries sync ``` ## Where can I provide feedback or ask questions? You can connect with us through several channels: - [Website](https://www.griptape.ai) - Visit our homepage for general information - [Discord](https://discord.gg/gnWRz88eym) - Join our community for questions and discussions - [GitHub](https://github.com/griptape-ai/griptape-nodes) - Submit issues or contribute to the codebase These same links are also available as the three icons in the footer (bottom right) of every documentation page. ## How can I test out unreleased features? If you're interested in testing out unreleased features, you can install the pre-release builds of Griptape Nodes. Warning Pre-release builds are not guaranteed to be stable and may contain bugs or incomplete features. Use them at your own risk. If you use Griptape Nodes Desktop, switch the **Release Channel** to **Nightly** in [App Settings](https://docs.griptapenodes.com/guides/desktop/app_settings/#release-channels). Nightly builds are published daily and bundle pre-release engine and editor builds along with the app. You can switch back to **Stable** at any time, without reinstalling. If you install the engine yourself, the pre-release engine is published to the [latest](https://github.com/griptape-ai/griptape-nodes/releases/tag/latest) tag twice a day. To switch to the pre-release update channel, run the following commands: ``` uv tool uninstall griptape-nodes uv tool install git+https://github.com/griptape-ai/griptape-nodes.git@latest --reinstall --force --python 3.12 ``` This will uninstall the current version of Griptape Nodes and install the latest pre-release build from the GitHub repository. Info Uninstalling using `uv tool uninstall griptape-nodes` will not remove your existing projects or settings. It only removes the Griptape Nodes engine itself. You can confirm it went through by running `gtn self version`. Your version number should show a reference to a git commit: ``` gtn self version v0.31.0 (git - e172e80) ``` To switch back to the stable release channel, run the following commands: ``` uv tool uninstall griptape-nodes uv tool install griptape-nodes ``` ``` # Troubleshooting This page collects the issues and states people run into most often, along with what causes them and how to recover. If you hit something that isn't here, check the [FAQ](https://docs.griptapenodes.com/faq/index.md) or reach out through one of the channels at the bottom of the [FAQ](https://docs.griptapenodes.com/faq/#where-can-i-provide-feedback-or-ask-questions). ## Images or videos aren't showing in the editor **Symptoms** - Load Image, Save Image, or a media preview node shows an empty area instead of the image. - The file clearly exists on disk (for example in `{outputs}/images/...`), but the editor won't display it. - Uploading a new image fails with an error like: ``` Error: CreateStaticFileUploadUrl Failed Description: Failed to create presigned URL for file ...: Client error '404 Not Found' for url 'http://localhost:8124/static-upload-urls' ``` **Cause** Media in the editor is served by a local **static file server** that the engine starts on port `8124`. If that port is already taken, usually by a **second (or stray) Griptape Nodes engine that is still running**, the new engine's static server falls back to a different, OS-assigned port. Media requests then end up split across the two engines, with the stray engine holding the default port while the engine you're actually working with serves from another one, so previews fail to load and uploads fail with `404` errors. **Fix** 1. Refresh the editor first with Ctrl+R (Windows/Linux) or Cmd+R (macOS). This clears simple display glitches. 1. If media still won't show, make sure **only one engine is running**. Fully quit Griptape Nodes, then look for a leftover engine process: - **Windows**: Open Task Manager and look for a stray `Python` process. End it. - **macOS / Linux**: In a terminal, run `pgrep -fl griptape` (or look for `python` running the engine) and stop the leftover process. 1. If you can't find or stop the stray process, **restart your computer**. This reliably clears any leftover engine holding the port. 1. Start Griptape Nodes again. On a clean start there should be a single engine process, and media will display normally. Tip After restarting, confirm there is only one engine process before reopening your workflow. A single leftover engine from a previous session, especially after an update, is the most common cause of this issue. Running the engine on a remote machine? If your engine runs on a different machine than the editor (or behind a tunnel or reverse proxy), missing media is expected until you point the editor at the right address. Set `static_server_base_url` as described in [Static File Server Configuration](https://docs.griptapenodes.com/guides/configuration/#static-file-server-configuration). ## Imported images or videos are 0 bytes **Symptoms** - Imported images and videos land in `{inputs}/images/` or `{inputs}/videos/` as **0 bytes** (File Explorer shows `0 KB`). - The media doesn't display in the editor, and reopening the workflow shows no media. - Quitting and reopening Griptape Nodes doesn't help. **Cause** Bringing a file into a project happens in two steps. The engine first reserves the destination filename by creating an empty file, then the editor sends the file's contents to the local static file server the engine runs on port `8124`. When that second step doesn't complete, the reserved empty file is all that's left. This has been traced to a browser-level fault on the machine, and has only been seen on Windows 10. Restarting the application doesn't clear it. **Fix** - Restart your computer. Restarting Griptape Nodes on its own is not enough. ## "Address already in use" / the engine won't start **Symptoms** You see a startup error similar to: ``` The 'websocket_direct' driver could not start: its address is already in use. Another Griptape Nodes engine is probably already running. Stop the other engine (or change this driver's port) and try again. ``` **Cause** Another Griptape Nodes engine is already running and holding the port this engine needs. **Fix** 1. Stop the other engine. Quit any other Griptape Nodes windows and check for stray engine processes using the steps in the [media section above](#images-or-videos-arent-showing-in-the-editor). 1. If you intentionally want to run more than one engine on the same machine, see [Running multiple engines on one machine](#running-multiple-engines-on-one-machine). ## Running multiple engines on one machine **Symptoms** With two or more engines running on the same machine, you see lots of weird, seemingly unrelated errors: requests answered by the wrong engine (or answered twice), workflows and state crossed between editor sessions, engines that look like one engine in the editor, port errors like the [address-in-use error above](#address-already-in-use-the-engine-wont-start), or media failing to load like the [images issue above](#images-or-videos-arent-showing-in-the-editor). **Cause** Two separate problems stack here: - **Shared identity.** When `GTN_ENGINE_ID` isn't set, every engine launched on the machine resolves to the same default engine identity. Engines sharing an identity listen for the same requests and share the same session state, so they both try to answer requests meant for one of them. This is what produces the flood of strange errors. - **Port conflicts.** The first engine takes the default ports (such as `8124` for the static file server); later engines fall back to other ports, which breaks anything still pointing at the defaults. **Fix** Give every additional engine **its own identity and its own ports**: ``` GTN_ENGINE_ID=second-engine STATIC_SERVER_PORT=9000 GTN_MCP_SERVER_PORT=9928 gtn engine ``` If you never intended to run more than one engine, find and stop the extra one using the steps in the [media section above](#images-or-videos-arent-showing-in-the-editor). ## "No sessions available" — the engine won't start for license users **Symptoms** You activated with a license (rather than logging in through Griptape Cloud), and on launch the engine fails license allocation with an error like `No sessions available`. **Cause** Your organization has a fixed pool of license sessions (seats). A seat is held for as long as an engine is running and is released when the engine shuts down cleanly. `No sessions available` means every seat in the pool is currently held — either legitimately (everyone is using theirs) or by a **stale session**: an engine that crashed, was force-killed, or is still running orphaned in the background keeps holding (and renewing) its seat. **Fix** 1. Check for an orphaned engine on your own machine, especially after a crash or a force-quit, using the steps in the [media section above](#images-or-videos-arent-showing-in-the-editor). Stopping it releases your seat. 1. If a seat is stuck, an organization owner can release it: in the [Admin Dashboard](https://docs.griptapenodes.com/enterprise/admin_dashboard/#sessions), open the **Sessions** modal and **Release** the stale session to free the seat. 1. Otherwise, a stale session frees itself once it expires — seats time out when they stop being renewed, so waiting a few minutes and trying again also works. Note A related error, `No session pool configured`, means your organization isn't set up with license sessions at all — contact whoever administers your Griptape Nodes licenses. ## The editor is black or blank **Symptoms** The editor window goes black or blank, often after the machine has been idle or asleep, or after a brief network interruption. **Fix** - Refresh the editor with Ctrl+Shift+R (Windows/Linux) or Cmd+Shift+R (macOS). A hard refresh reloads the editor and reconnects to the engine. ## Libraries or nodes are missing, or you see errors from another engine **Symptoms** - No libraries show up, or a node you expect (such as the Agent node) is gone. - The editor shows errors that reference a different engine or workflow than the one you're looking at. **Cause** Usually one of two things: - **Something prevented a library from loading.** When a library fails to load (a missing dependency, a broken node file, an import error, and so on), its nodes silently won't appear. **The logs are the source of truth here.** Export or open the engine logs and look for errors around library loading at startup. - **The Libraries To Register setting isn't what you think.** The engine only loads the libraries listed in the **Libraries To Register** setting (**Configuration Editor → Libraries → Library Registration**, stored as `app_events.on_app_initialization_complete.libraries_to_register` in `griptape_nodes_config.json`). If a library isn't in that list, is toggled off, or its entry is stale, its nodes won't show up. - You may also simply be connected to a different engine than you think, and it surfaces that engine's libraries and errors. **Fix** 1. **Check the logs first.** Look for errors emitted while libraries load on startup. The reported error usually names the library and the reason it failed. See [Exporting engine logs](#exporting-engine-logs). 1. Confirm which engine the editor is connected to. If you have engines on multiple machines, the editor may have connected to the wrong one. 1. Open the **Configuration Editor**, go to the **Libraries** view, and check **Library Registration → Libraries To Register**. If the library you expect is missing, toggled off, or points somewhere stale, fix the entry, or re-add the library via **Manage → Library Management → Add Library**. See [Toggling and removing libraries](https://docs.griptapenodes.com/guides/libraries/#toggling-and-removing-libraries) and [Installing a library](https://docs.griptapenodes.com/guides/editor/managing_models_and_libraries/#installing-a-library). 1. Check the **Libraries** panel with the filter set to **Errors** for libraries that failed to install or load. See ["I installed the library but I don't see its nodes"](https://docs.griptapenodes.com/guides/libraries/#i-installed-the-library-but-i-dont-see-its-nodes). 1. Make sure your libraries are up to date. Open **Manage → Library Management**, expand the library, and click **Check for Updates**, then **Update** when one is offered. See [Updating a library](https://docs.griptapenodes.com/guides/editor/managing_models_and_libraries/#updating-a-library). To update the engine itself, see the [FAQ](https://docs.griptapenodes.com/faq/#how-do-i-update-griptape-nodes). ## "failed to locate pyvenv.cfg" / the engine won't start **Symptoms** On launch, the engine fails to start with: ``` failed to locate pyvenv.cfg: The system cannot find the file specified. ``` **Cause** A previous uninstall didn't fully complete, leaving Griptape Nodes' virtual environment in a broken state. **Fix** 1. Uninstall Griptape Nodes again to clear the broken install: ``` griptape-nodes self uninstall ``` The broken virtual environment can also stop `griptape-nodes` itself from starting, since the command runs out of that same environment. If you get the same error trying to uninstall, remove the install by hand using the steps in [Uninstalling Griptape Nodes](https://docs.griptapenodes.com/uninstalling/#manual-engine-install). 1. Reinstall by following the [installation](https://docs.griptapenodes.com/installation/index.md) instructions. ## "Attempted to create a Flow with a parent 'None'" / usually harmless **Symptoms** You see this error, often while loading or building a workflow: ``` Attempted to create a Flow with a parent 'None', but no parent with that name could be found. ``` **Cause** A known, elusive bug. In almost all cases it's harmless and doesn't affect your work. **Fix** 1. You can usually disregard it and keep working. 1. If it's blocking you, restart the engine and it should clear up. 1. If you can reproduce it, we'd be grateful if you'd [log a bug](https://github.com/griptape-ai/griptape-nodes/issues/new?template=bug_report.yml&title=Attempted%20to%20create%20flow%20with%20a%20parent%20%27None%27) with any context about what led to it. ## "ssl.SSLCertVerificationError" / the engine won't run **Symptoms** When you try to run Griptape Nodes, you see: ``` ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self-signed certificate in certificate chain (_ssl.c:1000) ``` **Cause** The Python installation on your machine doesn't have access to verified SSL certificates. **Fix** 1. Reinstall Python using the [python.org](https://www.python.org/downloads/) installer. Griptape Nodes requires Python 3.12. 1. At the end of installation, choose to **Install Certificates**. - If the installer doesn't offer it, run `/Applications/Python\ 3.12/Install\ Certificates.command`. ## Exporting engine logs When you report an issue (or dig into one yourself), the engine logs are usually the first thing to look at. Where to get them depends on how you run the engine. ### From the desktop application The desktop application keeps its own log files for the local engine it manages, and can export logs for a time range, not just the current session. This is especially useful when the problem happened a while ago or spans an engine restart. 1. Click **Engine** in the header (the button that shows the engine status) to open the engine popover. 1. Under **Managed Engine**, click **Logs** to open the engine logs window. 1. Click **Export**. 1. In the **Export Logs** dialog, choose: - **Current Engine Session** — logs since the engine was last started. - **Time Range** — logs between specific timestamps, with a **From** time and either a **To** time or a **Now** checkbox. When an issue just happened, exporting the last half hour or so is usually more useful than the whole session. 1. Choose where to save the `.txt` file. Note Exporting requires the **Write engine logs to file** setting, found in the desktop application's [App Settings](https://docs.griptapenodes.com/guides/desktop/app_settings/#logging-and-diagnostics). It is enabled by default; if the **Export** button is disabled, use the **Manage** link next to it to jump to that setting. ### From the terminal If you run the engine manually (with `gtn` or `gtn engine`), logs print directly to that terminal. Scroll back and copy the relevant portion from there. If the logs don't show enough detail, raise the engine's log level: open the Configuration Editor (**Settings → All Settings**), search for "log level", set it to `DEBUG`, and reproduce the issue (see [Editing Settings in the Editor](https://docs.griptapenodes.com/guides/configuration/#editing-settings-in-the-editor)). When running headless with no editor attached, you can set it through an environment variable instead: ``` GTN_CONFIG_LOG_LEVEL=DEBUG gtn ``` # Griptape Nodes Glossary for Artists & Creators ## What is Griptape Nodes? Griptape Nodes is a toolkit that enables artists and creators to build AI-powered projects without the need for deep technical expertise. You can think of Griptape Nodes as a set of building blocks that you can connect together to create art, generate images, process text, or even build other workflow-centric applications. ### Core Griptape Nodes Concepts - **Workflow**: A document containing nodes, connections, and values. While technically a Workflow is also a Script, avoid calling them Scripts, so that the term Script can communicate a clearly different thing than the term Workflow. This is also what we call the saved file. - **Workflow Editor**: The workspace where nodes are added, connected, and configured. - **Flow**: A collection of connected nodes that form a functional unit. A Workflow can contain 0-n Flows. - **Sub-Flow**: A contained set of nodes that executes within a Loop, Branch, Reference (and sometimes Groups). This refers to any logically distinct, internally-contained portion of a flow that functions as a cohesive unit. - **Reference**: A link to another Flow, embedding it as a Sub-Flow within the current context. - **Group**: A visual clustering of nodes. A Group may act as a Sub-Flow if it is executed as a unit, but grouping alone does not imply execution and may simply be a subjectively related set of nodes. - **Script**: A Python script that runs code. This term should be avoided when describing a Workflow; instead, script refers to tools, macros, or flow-building aids. - **Libraries**: Collections of node definitions and/or scripts that extend functionality. See [Libraries](https://docs.griptapenodes.com/guides/libraries/index.md) for installing and managing them. - **Node**: A single piece of the puzzle in your workflow. Nodes are like LEGO blocks that you can connect to create something bigger. Each node does one specific thing (like generating an image or processing text). There are **types** of nodes: - **DataNode**: A node that handles information — it can transform data from one form to another, or simply hold data you set. - **ControlNode**: A node that will "do some work". Control Nodes usually take longer to run than DataNodes because, they're usually doing more work. - **DriverNode**: A node that connects your project to outside services (like image generators or search engines). - **Tool**: A ready-to-use component that performs a specific function. Tools are like brushes in your digital toolkit — each designed for a specific purpose. - **CalculatorTool**: Does math calculations for you. - **DateTimeTool**: Works with dates and times. - **FileManagerTool**: Helps manage files (saving, loading, etc.). - **WebScraperTool**: Collects information from websites. - **PromptSummaryTool**: Creates summaries of longer text. - **Driver**: The connector between your project and external AI services or databases. Drivers are like adapters that let your project talk to specialized services. - **PromptDriver**: Communicates with AI text generators (like GPT models). - **WebSearchDriver**: Connects to search engines to find information. - **EmbeddingDriver**: Transforms words and concepts into numerical form that AI can understand. - **ImageGenerationDriver**: Connects to AI image generators (like DALL-E). - **AudioTranscriptionDriver**: Converts spoken audio to written text. - **Engine**: The powerhouse that processes your creative requests. Engines are specialized components that transform inputs into creative outputs. - **RagEngine**: Processes questions and generates answers based on provided information. - **PromptSummaryEngine**: Creates concise summaries of longer text. - **CsvExtractionEngine**: Pulls information from spreadsheet-like files. - **JsonExtractionEngine**: Pulls information from structured data files. - **Artifact**: A piece of content created during your project, like a generated image or text. Artifacts are the outputs of your creative process. - **ErrorArtifact**: A notification when something goes wrong. - **Agent**: A helper that can perform tasks on your behalf, often using a combination of tools. Agents are like assistants that can navigate a sequence of operations for you. - **Ruleset**: A set of guidelines that control how your project behaves. Rulesets are like recipes that tell your project how to respond in different situations. - **Rule**: A single instruction within a ruleset, like "if the user asks for an image, generate one." ### Node Contents and Activities - **Parameter**: A setting or value you can adjust on a node. Parameters are like the knobs and sliders in a music synthesizer — they let you fine-tune how things work. - **ParameterMode**: Describes if a parameter is for input, output, internal, or any combination. - **ParameterValue**: The "internal" value for a parameter (this is the data the node works with internally) - **ParameterOutputValues**: The results or "output" value for your parameters (this is the data that results from what the node did) - **ParameterUIOptions**: Settings for how parameters appear in the user interface. - **Port**: The circular indicators displayed on the left and/or right sides of a parameter. Ports on the left side indicate the parameter can accept incoming connections, while ports on the right side indicate the parameter supports outgoing connections. - **Pin**: Interchangeable with "Port" - **Default Value**: The pre-set value a parameter has before you change it. This is like the factory settings on a device. - **Parameter Validation**: A check that ensures the values you enter make sense. This prevents errors like trying to use text where a number is needed. - **Off-prompt**: A Griptape secret-sauce way to keep some information private, even when working with LLMs. - **Stream Mode**: A continuous processing mode, like a live video feed rather than taking separate photos. - **Type Hints**: Labels that suggest what kind of information a parameter expects. These are like labels on art supply containers telling you what's inside. - **Any**: A type hint meaning a parameter can accept any kind of information. - **List**: A type hint for a collection of items (like an array of colors or shapes). - **Literal**: A type hint indicating a parameter only accepts specific preset values. - **Union type**: A type hint showing a parameter can accept multiple specific types of information. - **Connection**: The link that allows nodes to communicate with each other. Connections are like the cables connecting different pieces of equipment. ## Technical Terms Made Simple - **Data Types**: - **Dictionary (dict)**: A way to store information as pairs of labels and values. Dictionaries are like organized containers where each item has a unique label. - **Key-Value Pair**: A label (key) paired with its corresponding information (value). Dictionaries are just a collection of Key-Value Pairs. - **Integer (int)**: Whole numbers without decimal points, like 5, -10, or 1000. - **Float (float)**: Numbers with decimal points, like 3.14, -0.5, or 2.0. - **String (str)**: Text enclosed in quotes, like "hello", 'Python', or "123". - **Boolean (bool)**: Represents either True or False. - **List (list)**: An ordered collection of items that can be modified, like [1, 2, 3] or ["apple", "banana", "cherry"]. - **Tuple (tuple)**: An ordered collection of items that cannot be modified after creation, like (1, 2, 3) or ("red", "green", "blue"). - **Set (set)**: An unordered collection of unique items, like {1, 2, 3} or {"apple", "banana", "cherry"}. - **None (NoneType)**: Represents the absence of a value or a null value. - **Environment Variable**: Like secret notes that your computer keeps to help programs know important information. - **API Key**: A special password that grants your project access to external services like AI image generators. Think of it like a membership card that lets you use specific online services. - **Secret Keys**: Private credentials such as API tokens, passwords, and access keys that need to be kept secure but are required for certain operations. - **Secrets Manager**: This is the actual mechanism by which your secret keys are handled so that they *stay* secret. - **Configuration Settings**: Parameters and options that control how the Griptape Nodes engine and its components operate. - **Project Files**: Documents, scripts, and other resources that make up a Griptape project. - **Generated Assets**: Files and data produced by the Griptape Nodes engine during execution, such as outputs, reports, or visualizations. - **.env**: A special file used to store environment variables, particularly sensitive information like API keys. ## AI Terms for Artists - **Temperature Control**: A setting that controls how creative or predictable an AI's responses will be. Lower temperature means more predictable, higher temperature means more creative and varied. - **Embedding Model**: An AI tool that converts words, images, or other content into numbers that capture their meaning. This helps AI understand relationships between different concepts. - **LLM (Large Language Model)**: A type of AI system trained on vast amounts of text data to understand and generate human-like language. LLMs can write text, answer questions, summarize information, and even generate creative content. - **NLP (Natural Language Processing)**: The field of AI focused on helping computers understand and generate human language. - **Prompt**: The input text you provide to guide an AI model. For artists, this is similar to a creative brief or instructions to a collaborator. - **Vector Store**: A specialized database that stores information in a way that captures relationships and meaning, not just the information itself. - **Diffusion Models**: AI systems that create images by gradually transforming random noise into detailed visuals based on text descriptions. Popular examples include Stable Diffusion and Midjourney. - **Text-to-Image Generation**: Technology that creates images based on written descriptions, allowing artists to visualize concepts through text prompts. - **Style Transfer**: AI technique that applies the visual style of one image (like a famous painting) to the content of another image. - **Inpainting/Outpainting**: Tools that can fill in missing parts of an image or extend it beyond its original borders based on surrounding context. - **Fine-tuning**: The process of adapting a pre-trained AI model to recognize and generate specific styles, subjects, or artistic elements. - **Latent Space**: A mathematical representation where AI models organize concepts; artists can explore this space to find creative variations and transitions between ideas. - **Tokens**: The basic units that AI language models process text in, similar to words or parts of words. Understanding token limits helps artists craft effective prompts. - **Multimodal AI**: Systems that can work with multiple types of content (text, images, audio) simultaneously, enabling more integrated creative workflows. - **LoRA (Low-Rank Adaptation)**: A technique that allows artists to train AI models on their specific style without extensive computing resources. - **Sampling Methods**: Different algorithms (like DDIM, Euler, etc.) that control how AI generates images, affecting detail levels, creativity, and coherence. ## AI Companies and Models (Updated Apr 9,2025) ### Anthropic - **Claude 3 Opus**: Anthropic's most capable model for complex creative tasks and detailed content generation - **Claude 3.5 Sonnet**: Balanced model offering strong creative capabilities with faster performance - **Claude 3.7 Sonnet**: Advanced reasoning-focused model for complex creative problem-solving - **Claude 3.5 Haiku**: Fastest model for rapid ideation and real-time creative collaboration ### OpenAI - **GPT-4o**: OpenAI's most advanced multimodal model for text, image, and audio processing - **GPT-4 Turbo**: Powerful large language model for sophisticated text generation and creative writing - **GPT-3.5 Turbo**: More economical model for standard creative text tasks - **DALL-E 3**: Text-to-image generation model for creating visual art from descriptions - **Whisper**: Speech recognition model that can transcribe audio for creative projects ### Stability AI - **Stable Diffusion XL**: Text-to-image model with high-quality image generation capabilities - **Stable Diffusion 3**: Latest generation text-to-image model with improved coherence - **Stable Video**: Text-to-video generation model for creating short animated sequences ### Google - **Gemini Pro**: Google's advanced model for text generation and reasoning - **Gemini Ultra**: Google's most capable model for complex creative tasks - **Imagen**: Text-to-image generation model with photorealistic capabilities ### Cohere - **Command**: Specialized for instruction following and precise creative tasks - **Embed**: Creates semantic representations of text for organizing creative content ### Midjourney - **Midjourney V6**: Image generation model popular among artists for its distinctive aesthetic ### Meta - **Llama 3**: Open-weight foundation model adaptable for various creative applications - **Llama 3.1**: More advanced version with improved capabilities - **Segment Anything**: AI model for precise image segmentation in visual arts ### Ollama - **Local model hosting service**: Allows running various open-source AI models locally for creative projects - - ## Helpful Programming Concepts - **Callback**: A function that automatically runs when something specific happens. This is like setting up a camera to take a photo when motion is detected. - **Custom Parameter Types**: Specialized settings you can define for your specific project needs. - **Exception Handling**: The way your project deals with errors. This is like having backup plans for when things go wrong. - **Authentication Error**: An error that occurs when your project can't prove it has permission to use a service. - **KeyError**: An error when your project tries to access information using an incorrect label. - **SyntaxError**: An error caused by incorrect formatting in code. - **ValueError**: An error when your project tries to use an inappropriate value. - **Metadata**: Extra information about your content or components. This is like the information stored in digital photo files (camera type, date taken, etc.). ## Additional Terms - **Python**: A popular programming language that Griptape is built with. Python is known for being relatively easy to read and understand compared to other programming languages. - **API (Application Programming Interface)**: A set of rules that allows different software applications to communicate with each other. For artists, this is like the standardized connections that allow different audio equipment to work together. - **JSON (JavaScript Object Notation)**: A common format for storing and transmitting data. This is like a standardized template for organizing information. - **CSV (Comma-Separated Values)**: A simple file format used to store tabular data, such as spreadsheet data. Each line represents a row, and commas separate the values in each column. - **UI (User Interface)**: The visual elements and controls that allow users to interact with software. This includes buttons, sliders, text fields, and other interactive elements. In Griptape Nodes, we call our UI the "Editor" - **Framework**: A pre-built set of code that provides structure and functionality to build applications more easily. Frameworks are like art supply kits that come with the basic materials and tools you need. # For Agents This page documents the machine-readable surface that [docs.griptapenodes.com](https://docs.griptapenodes.com/) exposes for AI coding agents, MCP skills, and any tool that wants to ground its work in the engine's actual API. The same files that render this site are also published as post-processed markdown so an agent can fetch them directly. Snippets, macros, and mkdocstrings output are already expanded, which `raw.githubusercontent.com` does not give you. ## Surface - [`/llms.txt`](https://docs.griptapenodes.com/en/stable/llms.txt) is the curated index per the [llms.txt convention](https://llmstxt.org/). It groups the high-value pages (scripting, the project system, custom node development, MCP integration, and the node reference) under named sections with short descriptions and absolute URLs. - [`/llms-full.txt`](https://docs.griptapenodes.com/en/stable/llms-full.txt) is the full concatenated post-processed markdown of every page in the nav. Use this when you want to drop the entire engine docs into a single context window. - Every doc page is also available as standalone markdown next to its HTML rendering. The URL is the rendered page URL with `/index.md` appended, for example [`/development/custom_nodes/parameters/index.md`](https://docs.griptapenodes.com/en/stable/development/custom_nodes/parameters/index.md) and [`/retained_mode/index.md`](https://docs.griptapenodes.com/en/stable/development/retained_mode/index.md). Top-level pages live at `//index.md`; nested pages mirror the rendered URL. ## When to use which - Reach for **`/llms.txt`** to discover what's available without pulling the whole corpus. It's small enough to skim and the section descriptions tell you which page covers which topic. - Reach for **`/llms-full.txt`** when you want a single-shot grounding document and have the context budget for it. It is the same content as the per-page markdown files, concatenated in the order declared in the llms.txt sections. - Reach for **a single per-page `.md`** when you already know which page you need (e.g. you're writing a custom node and want the parameters reference, or you're scripting and want `retained_mode.md`). ## High-value pages for engine grounding If you are pointing an agent at a small set of pages to bootstrap, these five cover most of the engine's first-party API surface: - [Scripting (retained mode)](https://docs.griptapenodes.com/en/stable/development/retained_mode/index.md) - [Custom node development overview](https://docs.griptapenodes.com/en/stable/development/custom_nodes/index.md) - [Getting started with node development](https://docs.griptapenodes.com/en/stable/development/custom_nodes/getting_started/index.md) - [Project system overview](https://docs.griptapenodes.com/en/stable/guides/projects/index.md) - [MCP integration overview](https://docs.griptapenodes.com/en/stable/guides/mcp/index.md) ## Stability - The surface tracks `main` via the existing docs deploy pipeline. There is one live surface; versioned URLs (e.g. `/v0.40/llms.txt`) are not currently published. - The set of pages is driven by the `llmstxt` plugin block in `mkdocs.yml`. Adding a new doc page does not automatically include it in `/llms.txt` or `/llms-full.txt`; the page must be added under a section there. Reference pages under `nodes/` are picked up via a glob. # Tutorials # Tutorial Overview Welcome to your journey with Griptape Nodes! This learning path will guide you through everything from basic setup to creating complex, coordinated workflows with multiple agents. Each tutorial builds upon the knowledge gained in previous lessons, gradually introducing more complex concepts and techniques. - # [Lesson 1. Getting Started](https://docs.griptapenodes.com/tutorials/00_tour/index.md) Now that you've [installed](https://docs.griptapenodes.com/installation/index.md) the Griptape Nodes Editor, it's time to launch and work in Griptape Nodes. Explore the interface, and understand how to add nodes to your workspace. This simple but foundational tutorial will prepare you for all future workflows. - # [Lesson 2: Prompt an Image](https://docs.griptapenodes.com/tutorials/01_prompt_an_image/index.md) Learn how to use the powerful [GenerateImage](https://docs.griptapenodes.com/nodes/image/create_image/index.md) node to transform text descriptions into vivid visual creations. This tutorial introduces you to one of the most popular and versatile nodes in the Griptape ecosystem. - # [Lesson 3: Coordinating Agents](https://docs.griptapenodes.com/tutorials/02_coordinating_agents/index.md) Discover how to coordinate multiple agents to perform sequential tasks. Examine a workflow that translates content between languages, teaching you the fundamentals of agent connections and execution chains. This tutorial introduces the concept of multi-step workflows. - # [Lesson 4: Compare Prompts](https://docs.griptapenodes.com/tutorials/03_compare_prompts/index.md) Take your image generation to the next level by exploring a few different prompting methods. Compare basic prompts, GenerateImage "enhanced prompts", and custom agent-enhanced prompts to see how each affects your results. Learn how to craft detailed instructions for more precise creative control. - # [Lesson 5: Build a Photography Team](https://docs.griptapenodes.com/tutorials/04_photography_team/index.md) Build an advanced system with specialized agents working together to create spectacular image prompts. Learn about rule sets and tools, and discover how to convert agents into reusable tools for enhanced functionality. This advanced tutorial brings together everything you've learned so far. ## What You'll Learn By completing this learning path, you'll master: - Setting up and navigating the Griptape Nodes interface - Creating and connecting different types of nodes - Working with AI image generation - Implementing rule sets and tools for specialized agents - Converting agents into reusable tools - Coordinating multiple agents in a single system Ready to start your journey with Griptape Nodes? Let's learn about the interface in [Lesson 1: Getting Started](https://docs.griptapenodes.com/tutorials/00_tour/index.md) # Lesson 1: Getting Started Welcome to Griptape Nodes! This tutorial will guide you through the basics of this powerful visual workflow tool. You'll learn how to launch the application, navigate the interface, add and connect nodes, and run your first AI agent. By the end of this guide, you'll have the foundational knowledge needed to continue with subsequent tutorials. ## What We'll Cover In this tutorial, topics include: - Launching Griptape Nodes - Navigating through the landing page to a workflow - Getting familiar with the Griptape Nodes Editor - Adding nodes to the workspace - Learning what can connect to what - Run an agent ## Launch Griptape Nodes To launch Griptape Nodes, open your terminal and run one of the following commands: ``` griptape-nodes ``` Or use the shorter version: ``` gtn ``` After executing the command, your browser should automatically open to . If it doesn't, simply ctrl-click (or cmd-click on Mac) the link displayed in your terminal and select "Open in browser" if prompted. Of course, you can always simply bookmark it in your browser, and come back to it that way. Tip For the best experience, keep two browser windows open side-by-side: this tutorial in one, and your Griptape Nodes session in the other. ## The Landing Page When your browser opens, you'll be greeted by the Griptape Nodes landing page. This page displays several template workflows that showcase different things we want to introduce you to. Once you start saving your own workflows, they will appear here in order of newest-to-oldest. These sample workflows are excellent resources for learning about Griptape Nodes' capabilities, but for now, let's start "from scratch". ## Create a new workflow from scratch On the landing page, locate and click on the **"Create from scratch"** tile. This action opens a blank workspace where you can build workflows. ## Get familiar with the Griptape Nodes interface Once you're in the Workflow Editor, take a moment to familiarize yourself with the interface: ### Libraries The most important area to focus on initially is the left panel, the node library. At the top, you'll find the **Create Nodes** section. This panel houses all the standard nodes that come pre-packaged with Griptape Nodes. Each node serves a specific function. As you become familiar with Griptape Nodes, you'll learn how these nodes work and how to combine them to create powerful automations. ## Adding Nodes to the Workspace There are three interactive methods to creating nodes (and even more in [Retained Mode](https://docs.griptapenodes.com/development/retained_mode/index.md)) **Drag and Drop**: Click and hold on a node from the left panel, then drag it onto your workspace. #### Drag and Drop **Double-Click**: Simply double-click any node in the left panel to automatically place it in the center of your workspace. #### Double Click **Shift+A or Double-Click**: Pressing Shift+A or double-clicking on the flow editor brings up a search field. You can type to find the node you want, and enter to create it. #### Shift+A or Double-Click Search After adding a node, you can: - Click and drag to reposition it on the workspace - Edit its values and behaviors - Connect it to other nodes ## Connecting Nodes Let's create some nodes using the first technique mentioned above - dragging and dropping a node from the library to the workspace to create three nodes. 1. An **Agent** ( agents > Agent ) - This is an agent that interacts with LLMs (Like OpenAI ChatGPT or Anthropic Claude) 1. Open the agents category in the sidebar 1. Drag the Agent node to the workspace and release to create it Info For brevity, we'll describe this as ( category > Node ), so for an Agent, we'd shorthand the above with ( agents > Agent ). Try the same process with the next two nodes. 1. A **FloatInput** ( number > FloatInput ) - A node to input decimal numbers (floats) 1. A **TextInput** ( text > TextInput ) - A node to input text Experiment with connections by dragging from ports on both input nodes to various ports on the Agent. Try multiple combinations and observe that not all connections succeed. This happens because parameters can only connect directly when their data types are compatible: - The **TextInput** node outputs **text**, so it can connect to any Agent parameter that accepts text. - The **FloatInput** node outputs decimal numbers (**floats**), which can't connect to any parameter on the Agent. Don't worry that you can't connect the FloatInput to anything - that's exactly the point. This node was included *here* solely to demonstrate how not all parameters can connect to each other. The FloatInput node is indeed very useful, it's just not one we can use with the other nodes currently in *this* workflow. Pro Tip Use the port colors as a visual guide for compatibility. Ports with matching colors can connect to each other. ## Use an Agent For now, lets try another method to wipe the slate clean, and get a real AI interaction under our belt: 1. Go to the File menu and choose **New**. 1. In your new workflow, make an Agent any way you prefer 1. Type a question into the agent's prompt field. You can use "Who trained you?" to verify the AI service, or simply enter any question you'd normally ask a chatbot. 1. Click the play button icon in the top right corner of the agent to run the node 1. When text appears, read the output. You just interacted with a Large Language Model (LLM). If you kept the default settings, you specifically used OpenAI ChatGPT (GPT-4.1). While this experience might seem similar to using OpenAI ChatGPT or Anthropic Claude on the web, the real power comes from using LLMs alongside other components in Griptape Nodes. Take another look at the library panel on the left to see all the other nodes available. We're just getting started—there's so much more to explore! ## Summary In this tutorial, we covered how to: - Launch Griptape Nodes - Navigate through the landing page to a workflow - Get familiar with the Griptape Nodes Editor - Add your first nodes to the workspace - Learn about what can connect to what - Run an agent ## Next Up In the next section: [Lesson 2: Prompt an Image](https://docs.griptapenodes.com/tutorials/01_prompt_an_image/index.md), we'll start in on the good stuff: making images! # Lesson 2: Prompt an Image Welcome to the second tutorial in our Griptape Nodes series! This guide focuses on getting familiar with the GenerateImage node. While the workflow itself is exceedingly simple (consisting of just this single node), the GenerateImage node will be your primary workhorse for image generation in your early projects. This powerful node truly is the cornerstone for creating a wide variety of visual content. ## What we'll cover In this tutorial, you will: - Learn how to open saved workflows - Learn about the GenerateImage node - Generate images using text prompts ## Navigate to the Landing Page To begin this tutorial, you'll need to return to the main landing page. Click on the navigation element at the top left of the editor to go back to where all the template workflows and your own saved files are displayed. ## Open the Image Prompt Example On the landing page, locate and click on the **"Prompt an Image"** tile to open this example workflow. ## Understand the GenerateImage Node When the example loads, you'll notice it consists of just a single node. Don't be fooled by its simplicity – this node is one of the most powerful tools in Griptape Nodes and will likely feature prominently in your future flows. This node has been configured to handle many tasks that would typically require a more complex flow, making it perfect for getting started with AI image generation. ## Generate Images Using Text Prompts The primary point of interaction for this node is the text prompt field where you describe what image you want the AI to create. To generate your first image: 1. Locate the text prompt field in the node 1. Type a description for the image you want to create Now, run your node. There are three UI buttons but those perform only two distinct operations: 1. Run the *whole* workflow: 1. Click the **Run Workflow** button at the top of the editor. This executes the entire workflow from start to finish. All nodes will be processed in sequence according to their connections. 1. Run a single node in the workflow (two methods that do the same thing): 1. Click the **Run Node** button in the top right corner of a specific node. 1. Or, select a node and click **Run Selected** from the toolbar. Running a single node can be very useful for testing or debugging specific parts of your workflow, or simply getting things to run faster if you don't care about updating other parts of the workflow just yet. The difference is in scope - the first option runs everything, while the second options run just the selected node and everything that precedes it that it may need. Generation times For those new to image generation: generation operations can take time. Please notice the yellow outlines and spinning **Running** icon on nodes, where the **run node** icon was before execution began. These visual hints tell you which nodes are currently resolving, letting you know that things are functioning as expected. ## Experiment with Different Descriptions Let's try generating some images with different prompts: 1. **First Example**: The workflow loads with "A potato making an oil painting" in the prompt field. Run the flow 1. **Second Example**: Change the prompt to "A potato doing aerobics in 70s workout attire" and run the flow again Notice how dramatically different the results are just by changing a few words in your prompt. This demonstrates the flexibility and power of the GenerateImage node. Anything you can describe, you can generate. ## Summary In this tutorial, we covered: - How to open saved workflows - The GenerateImage node - How to generate images using text prompts The GenerateImage node is a fundamental building block for creative flows in Griptape Nodes. As you progress, you'll discover how to combine it with other nodes to develop even more powerful applications. ## Next Up In the next section: [Lesson 3: Coordinating Agents](https://docs.griptapenodes.com/tutorials/02_coordinating_agents/index.md), we'll learn how to get AIs to team up and form a bucket-brigade through a workflow. # Lesson 3: Coordinating Agents Welcome to the third tutorial in our Griptape Nodes series! In this guide, you'll learn how to coordinate multiple agents within a workflow to perform sequential tasks—specifically, translating stories between languages and summarizing them. ## What we'll cover In this tutorial, we will: - Study then recreate a translation workflow between agents working sequentially - Discover how to "merge texts" to take outputs and modify them into new prompts - Learn about the "exec chain" for controlling workflow execution order - Build more into the template workflow to add a summarization task By the end of this journey, you'll understand how to create workflows where agents build upon each other's work, passing information seamlessly between specialized tasks. This foundation will prepare you for creating sophisticated AI systems that can handle multi-step processes requiring different types of intelligence at each stage. Let's begin by examining a simple translation workflow that demonstrates these principles in action. ## Navigate to the Landing Page To begin this tutorial, go to the landing page via the nav bar with the Griptape Nodes logo in the top left. Locate and open the example workflow called "coordinating_agents" at the top of the page. ## Explore the Template Workflow When the template loads, you'll see a workflow with the following components: 1. **Agent Node (spanish_story)**: Generates a four-line story in Spanish 1. **Merge Text Node**: Combines the Spanish story with "Rewrite this in English" 1. **Second Agent Node (to_english)**: Translates the merged prompt into English 1. **Display Text Node**: Shows the final English translation This workflow demonstrates how multiple agents can each perform their own distinct "jobs." By connecting one agent's output to another through a **MergeTexts** node, you create *new* prompts that direct the next agent's behavior. ## How we're using the MergeText node here All a **MergeTexts** node does is combine incoming texts using the "merge string" as a separator. The default merge string is two newlines: `\n\n`. In this example, I've typed "Rewrite this in English:" into **input_1** of the MergeTexts node and connected the output of my **spanish_story** node to **input_2**. When run, the **MergeTexts** node will output: > Rewrite this in English: > > Bajo la luna, el río cantó, Un secreto antiguo en su agua dejó. La niña lo escuchó y empezó a soñar, Que el mundo era suyo, listo para amar. You can see how this method of creating new prompts out of the results of other nodes can allow for a sophisticated multi-agent workflow where the first agent writes a Spanish story, and the second agent translates it to English. Your final output will be the English translation of whatever unique Spanish story was generated. Info You should expect variability in these from run-to-run. That's okay! Remember, talking with an agent can in a way be like talking to a person. You may get slightly different answers if you ask them the same question many times. ## Build a sibling workflow This is what we're aiming to get to: Now it's time to build your own workflow. Create another nearly identical flow just below this one to practice creating and connecting nodes. Add the following to your workflow: 1. Two **Agents** ( agents > Agent ) - These are agents that interact with LLMs (Like ChatGPT, or Claude) 1. A **MergeTexts** node ( text > MergeTexts ) - A node to accept multiple texts and output them "merged" 1. A **DisplayText** ( text > DisplayInput ) - A node to simply display text output for easier viewing ## Configure the First Agent Set up your first agent to generate content in your chosen language: 1. In the first agent node, enter: `Write me a four line story in [your chosen language]` (e.g., Mandarin, French, etc.) 1. This agent will generate the initial story that we'll translate ## Connect to the MergeTexts Node Next, prepare the translation prompt: 1. Type directly into the MergeTexts node's **input_1** field and enter: `Rewrite this in English` 1. Connect the output from the first Agent to **input_2** of the MergeTexts node ## Configure the Second Agent Set up the translator agent: Connect the output of the MergeTexts node to the second Agent node's **prompt**. This agent will now receive both the original story, and the instruction to translate it ## Display the Result To see the final translation: 1. Connect the output of the second Agent to the DisplayText node 1. Run your workflow. 1. After the workflow runs, this node will show the translated English text: ## Understanding Execution Order (Exec Chain) A key concept in Griptape Nodes is the execution chain. As workflows become more complex, controlling the order of execution becomes important. Let's explore this concept. 1. Notice the "exec in" and "exec out" pins (half-circle connectors) on nodes 1. These define the order in which nodes run 1. For complex workflows, connect the exec ports in the order you want execution to occur 1. This ensures nodes run in the intended sequence, even with complex data flows Info Griptape Nodes will automatically determine the execution order of nodes by analyzing their dependencies. However, when you need more precise control over the execution sequence, you can use the exec chain feature. This provides a way to explicitly define the order you want when the automatic dependency detection might not align with your intended behavior. There is no cost or penalty to using the exec chain anytime you want, except for the possibility of forcing things to execute in a faulty order. For most simple flows, it is unnecessary. ## Expand the Workflow: Summarize Multiple Stories Let's enhance our workflow to handle summarization of *all* the stories: 1. Add another new **MergeTexts** node that combines both English translations 1. In this merge text node, enter: `Summarize both these stories` in **input_1** 1. Connect both the translation nodes' **outputs** into **input_1** and **input_2** on the MergeTexts node 1. Add another **Agent** node 1. Connect the MergeTexts **output** into the **prompt** for your new agent 1. Connect the agent **output** to a new **DisplayText** node 1. Optionally, use exec chain connections to ensure this summary step runs last (you can even connect *everything* up to run in the order you want) ## Run the Complete Workflow Execute your expanded workflow and observe the process: 1. The first agents generate stories in different languages 1. The merge text nodes create prompts to translate them 1. The second agents translate the stories into English 1. The summary agent combines and summarizes both translations 1. The display nodes show all the results Info Again, remember! Look for this *construction* in the response you get, not that it matches what you see here - it is likely to be wildly different! ## Summary In this tutorial, we covered: - How a workflow can hand things off between agents to perform tasks like translation - Discovered how "merge texts" allows you to take outputs and modify them into new prompts - Learned about the "exec chain" for controlling workflow execution order - Built more into the template workflow to add a summarization task ## Next Up In the next section: [Lesson 4: Compare Prompts](https://docs.griptapenodes.com/tutorials/03_compare_prompts/index.md), we'll learn how to get AIs to bucket-brigade, where agents pass work sequentially, through flows! # Lesson 4: Compare Prompts Welcome to our fourth Griptape Nodes tutorial! Building on what you already know about multi-agent systems and image generation, we're diving into the exciting world of prompt engineering. You'll discover how to use ready-made solutions and create your own prompt-building workflows to get consistently better results. Plus, we'll peek behind the curtain to see how the "Enhance Prompt" feature actually works its magic! ## What we'll cover In this tutorial, we will: - Compare three different prompting approaches for image generation - Understand the "Enhance Prompt" feature - Learn about custom prompt enhancement flows ## Navigate to the Landing Page To begin this tutorial, return to the main landing page by clicking on the Griptape Nodes logo at the top left of the interface. ## Open the Compare Prompts Example On the landing page, locate and click on the **"Compare Prompts"** tile to open this example workflow. ## Understand the Workflow Structure When the example loads, you'll see a workflow with multiple nodes: This workflow contains: - Two TextInput nodes - **basic_prompt** - **detail_prompt** - A MergeTexts node, **assemble_prompt** - Three GenerateImage nodes: - **basic_image** - **enhanced_prompt_image** - **bespoke_prompt_image** - An agent node, **bespoke_prompt** We'll run each part of the workflow individually to compare the results of different prompting techniques. ## Comparing Different Prompting Methods ### Method 1: Basic Prompt Let's start with the most straightforward approach: 1. Locate the TextInput node with our simple prompt: "A capybara eating with utensils" 1. Follow the connection to the first GenerateImage node 1. Notice that **Enhance Prompt** is set to **False** on this node 1. Run just this node by clicking the **run node** button at the top right of the node Observe the resulting image. This shows how the AI interprets your direct, simple prompt. ### Method 2: Using Enhance Prompt Feature For the second method, we'll use the same simple prompt but using the GenerateImage's built-in pronpt enhancement: 1. Find the second GenerateImage node that receives the same simple prompt 1. Notice that **Enhance Prompt** is set to **True** on this node 1. Run this node using the it's **Run Node** button Compare this result with the first image. You should see a much more complex and artistic interpretation. The difference is striking—same simple prompt, but the enhanced version produces a significantly more detailed and visually appealing image. ### Method 3: Bespoke Agent-Enhanced Prompt The third method demonstrates how we can create our own custom prompt enhancement: 1. Take a look at how we're using the MergeTexts node and an agent to create a prompt: - This detailed prompt is connected into the first input of the MergeTexts node - The same simple prompt from the other examples is connected to the second input of the MergeTexts node - the "Merge Texts" node then combines these - An agent node then processes this combined prompt to generate another prompt for image generation 1. Run the agent node 1. Examine the output of the agent (if you want a closer look, try adding a DisplayText node and hook it up!) You'll see that the agent has created a much more elaborate prompt that addresses all the specifications: - Unique details about the capybara - Specific time of day (late afternoon sunlight) - Depth of field information - Color palette guidance - Professional photography elements - Finally, run the third GenerateImage node (with **"Enhance Prompt** set to **False**). It uses this agent-enhanced prompt we just covered. Notice how this image contains specific details and artistic elements compared to the first, but is about the same level of sophistication as the second. ## Understanding What's Happening Behind the Scenes Here's the key insight: When you toggle the **Enhance Prompt**"\*\* feature to **True**, Griptape is automatically doing what we just demonstrated manually with the bespoke route. It is: 1. Taking your basic prompt and a detailed prompt 1. Running it through an agent with enhancement instructions (verbatim what we wrote) 1. Using the enhanced output for image generation By creating our own explicit enhancement flow, we gain *full* control over exactly how we want the prompt to be improved or changed. ## Applications and Best Practices Based on what we've learned, consider these approaches for your own projects: - Use basic prompts (with Enhance Prompt off) for quick, straightforward image generation - Enable "Enhance Prompt" when you want general improvements with minimal effort - Create custom agent-based enhancement flows when you need precise control over specific artistic elements or want to emphasize particular aspects. ## Summary In this tutorial, we covered: - Three different prompting approaches for image generation - Learned about the "Enhance Prompt" feature - Learned about custom prompt enhancement flows These techniques demonstrate the power of prompt engineering—the art of crafting and refining prompts to achieve specific, high-quality outputs from AI systems. ## Next Up In the next section: [Lesson 5: Build a Photography Team](https://docs.griptapenodes.com/tutorials/04_photography_team/index.md), we'll learn about Rulesets, Tools, and converting Agents into tools to achieve even more sophisticated coordination! # Lesson 5: Build a Photography Team Welcome to the fifth and final tutorial in our Griptape Nodes New-Users series! In this guide, we'll build on our prompt engineering concepts and introduce additional nodes that provide greater precision and control. We'll examine a sophisticated system of coordinated agents that collaborate like a team to generate spectacular image prompts, while also learning several important new nodes along the way. ## What we'll cover In this tutorial, we will: - Learn about what rule sets are and what they do - Learn about tools - Learn about "list" parameters and interacting with them - See how we can convert agents *into* tools - Coordinate multiple agents-as-tools through a single orchestrator - Generate high-quality image prompts through team collaboration ## Navigate to the Landing Page To begin this tutorial, return to the main landing page by clicking on the Griptape Nodes logo at the top left of the interface. ## Open the Photography Team Example On the landing page, locate and click on the **"Build a Photography Team"** tile to open this example workflow. ## Overview of the Workflow When the example loads, you'll notice this is the most complex workflow we've seen so far: The workflow consists of several key components: - Multiple specialized Agents - Cinematographer - Color Theorist - Detail Enthusiast - Image Generation Specialist - Rulesets for each RuleSetList - RuleSetLists for each Agent - AgentToTool converters - A ToolList - An orchestrator Agent - A GenerateImage node You'll notice that the pattern upstream of the TooList repeats the same structure four times. Each upstream segment follows identical logic and structure, differing only in the specific data being processed. Let's examine just one instance in detail, as understanding this single example will give you the conceptual framework for all four occurrences. **RuleSet->RuleSetList->Agent->AgentToTool** ## RuleSets RuleSets are nodes that define how agents should approach their tasks. Within each RuleSet, you can have multiple rules. Rules are understood to be separated by appearing on their own line using intentional line breaks. Don't worry about word wrapping - if text simply flows to the next line, it's still considered part of the same rule. Every RuleSet requires a unique name, which agents use to identify and distinguish between different RuleSets. When working with multiple RuleSets, choosing clear, distinctive names becomes especially important for proper organization and reference. Tip This workflow is constructed so that each agent only has one RuleSet, so the chance for a RuleSet name collision is zero, but it's still good practice to name these well. The Cinematographer RuleSet you see above contains detailed instructions covering framing, composition, and visual storytelling techniques. These rules influence how the agent tends to respond—establishing preferred patterns for its outputs. For more predictable performance, you guide the agent through these RuleSets. It's helpful to ensure your rules maintain reasonable consistency with one another. Contradictory or conflicting instructions may lead to mixed results, as the agent attempts to balance competing priorities in its responses. ## RuleSetLists RuleSetLists function as collectors or containers, similar to another node we'll talk about momentarily, ToolLists. RuleSetLists serve a specific organizational purpose: allowing you to gather multiple Rulesets into a single collection that can connect to "list" parameters—parameters designed to accept multiple items of the same type. I've included RuleSetLists here primarily to introduce you to them rather than for any immediate functional value. Optional Playtime If you'd like to independently explore changing the graph harmlessly to work around the RuleSetList: 1. On the Agent, uncollapse the **Advanced Options** 1. Disconnect the RuleSetList from the Agent's **ruelsets** parameter 1. The agent's display will change slightly, and you should see **add item to rulesets** underneath the **rulesets** parameter. Click that, and you should see a new port will appear. This new port takes in a *single* RuleSet. 1. Connect directly from the RuleSet's **ruleset** parameter to the new port. ## Understanding Tools in Griptape Nodes **Tools** extend an agent's capabilities by connecting it to external functions and services. When faced with tasks beyond its internal knowledge—like retrieving current data or performing calculations—tools provide the bridge. The agent decides when to use a tool, formats the appropriate request, and interprets the returned results. This transforms agents from isolated text generators into assistants that can access databases, run code, or manipulate files while maintaining their reasoning abilities throughout a conversation. Tools consist of two key components: 1. **Underlying code**: This flexible, open-ended component can be anything from simple calculations to complex integrations. 1. **Description**: This precise element communicates the tool's purpose and appropriate use cases to the agent, so it knows when and how to use the tool. As of initial release, we have just a few important tool nodes like a Calculator and DateTime, but we've also included what can easily become the most adaptive tool of them all: Agents *as* Tools. ## Converting Agents to Tools Griptape Nodes allows you to turn entire agents into tools. This allows a primary agent to delegate specific tasks to specialized agents, creating a hierarchical system where expertise is distributed. When agents can "stay in their lane" and focus on specific domains, they perform more reliably—just like people who can concentrate on one task rather than juggling many simultaneously. This crazy-powerful concept is part of Griptape Nodes via the **AgentToTool** converter node. While fully-formed tools typically handle both components (underlying code, description) under-the-hood, agents-as-tools require you to provide the description part yourself. This is where you establish the boundaries and expectations for how that agent should function as a tool. The description you write helps the orchestrator agent understand when and how to use your specialized agent-as-tool. ## The ToolList node The ToolList functions similarly to the RuleSetList discussed earlier, but for tools instead of RuleSets. It serves as a collector that gathers multiple tools into a single collection, which can then connect to parameters accepting tool lists. Just as the RuleSetList organizes RuleSets, the ToolList provides a streamlined way to pass multiple tools to an agent. This becomes especially valuable when your agent needs access to several specialized tools. This organizational pattern is consistent throughout Griptape Nodes - "list" nodes act as waypoints, collecting multiple items of the same type before sending them to components that work with collections. Optional Playtime Just as you might have bypassed the RuleSetList earlier for practice, you can do the same with the ToolList. Simply disconnect it from the orchestrator agent, click "add item to tools" to create a new port, and connect your tool directly. This change won't affect how your workflow functions. ## The Orchestrator The central component of this workflow is the orchestrator agent: 1. Locate the orchestrator agent 1. Notice it has its own rule set: ``` You are creating a prompt for an image generation engine. You have access to topic experts in their respective fields. Work with the experts to get the results you need. You facilitate communication between them. If they ask for feedback, you can provide it. Ask the image generation specialist for the final prompt. Output only the image generation prompt. Do not wrap it in markdown context. ``` 1. The orchestrator's prompt is actually very simple: "Use all the tools at your disposal to create a spectacular image generation prompt about a skateboarding lion." ## How the Workflow Functions The entire system operates through this process: 1. The orchestrator receives a relatively simple prompt 1. It has access to all specialized tools (converted agents) 1. The orchestrator can call upon: - The Cinematographer for framing and composition guidance - The Color Theorist for color palette recommendations - The Detail Enthusiast for intricate details to include - The Image Generation Specialist for formatting the final prompt 1. The final output connects to the GenerateImage node ## Running the Workflow Let's execute the workflow to see the photography team in action: 1. Run the workflow 1. Observe as the orchestrator calls upon different specialized tools (it is a quick flash, but you can see them all "tagged in") 1. The final output is collected into a sophisticated image prompt 1. This prompt is then used to generate the image ## Summary In this tutorial, we covered: - Learning about what rule sets are and what they do - Learning about tools - Seeing how we can convert agents *into* tools - Seeing what a "team" of specialized AI experts looks like - Coordinating multiple agents through an orchestrator - Generating high-quality image prompts through team collaboration These advanced techniques showcase the full power of Griptape Nodes for creating complex, collaborative AI systems. Thank you for completing this series of tutorials. We're excited to see what you'll build with these powerful tools! If you're more in the mood to keep going to something more advanced, please continue on to our "I'm A Pro" series (\*Coming Soon!) # Desktop App # App Settings Griptape Nodes Desktop has its own settings screen, separate from the engine and editor settings you reach from the editor's **Settings** menu. App Settings covers what the application itself owns: how it closes, which theme it wears, the workspace it hands to the engine, your license, log files, and how the app updates itself. ## Opening App Settings Click your name in the top-right corner of the window, then choose **App Settings**. If you activated with a license key instead of signing in with a Griptape account, the button reads **Interactive license** (or whatever your license type is) rather than your name. Click **Back to editor** in the top-left corner when you're done. Most changes save as soon as you make them; the places where they don't are called out below. Note App Settings is not the same as the editor's **Settings** menu. That menu configures the engine and the editor — canvas theme, autosave, API keys and secrets, MCP servers, library settings — and is documented in the [editor overview](https://docs.griptapenodes.com/guides/editor/#settings). Only Griptape Nodes Desktop has App Settings; if you run the engine yourself and use the editor in a browser, see [Configuration](https://docs.griptapenodes.com/guides/configuration/index.md) instead. ## General **Confirm before closing** shows a confirmation dialog when you quit. Quitting stops the engine, which cancels anything still running, so leaving this on is a good idea if you run long workflows. ## Appearance **Theme** sets the application chrome to **Light**, **Dark**, or **System** (follow your operating system's setting). **Show system monitor** adds a strip below the main navigation with live CPU, memory, and GPU utilization, updated every second. Useful when you're running local models and want to see whether you're out of headroom. ## Workspace & Libraries **Workspace Directory** is where Griptape Nodes stores your [project files](https://docs.griptapenodes.com/glossary/#project-files) and [generated assets](https://docs.griptapenodes.com/glossary/#generated-assets). Use **Browse** to pick a different folder. Changing it doesn't move your existing files, so point it at a folder you can find again. See [Workspace](https://docs.griptapenodes.com/guides/projects/workspace/index.md) for how the engine uses it. **Import existing configuration** opens the **Import Configuration** dialog, which looks in the usual places for a Griptape Nodes configuration from an engine you installed and set up yourself. Pick the one you want under **Select configuration to import** and the app adopts its settings, including its workspace directory, so you don't have to re-enter them. If nothing turns up, use **Browse for config file** or **Scan home directory**. **Additional Libraries** installs optional node libraries alongside the built-in ones: - **Install Griptape Nodes Diffusers Library** — media generation with Diffusers. It wants a powerful machine, and some models need an NVIDIA GPU with CUDA. - **Install Griptape Cloud Library** — nodes for Griptape Cloud services. Both are also installable later from **Manage → Library Management**; see [Libraries](https://docs.griptapenodes.com/guides/libraries/index.md). Nothing in this section takes effect until you click **Apply Changes**, which stops the engine, reconfigures it, and starts it again. Save your workflow first. ## Engine Note This section only appears if you activated with a license key, which puts the app in direct engine mode. If you signed in with a Griptape account, you won't see it — restarting and managing the engine lives on the Engine page instead. **Direct Engine WebSocket URL** is the address the local engine binds to and the app dials when it talks to the engine directly. The default, `ws://127.0.0.1:18125`, is a loopback socket that only your machine can reach. Change it only if your engine listens somewhere else, then restart the engine to apply it. ## License If your organization runs Griptape Nodes on license keys, this section is where you manage them. Each activated license shows as a card with its organization, license ID, type, and expiry date, plus an **Active** badge when it's the one in use. Use the pencil icon to give it a friendlier name, or **Remove** to deactivate it on this machine. Making a different license the active one restarts the engine. **Griptape Server Endpoint** points the engine at an on-premises Griptape server for cloud and license requests instead of the default `https://cloud.griptape.ai`. Restart the engine after changing it. To add another key, paste it into the box and click **Add & Activate**. For the full activation walkthrough, see [Using the Admin Server](https://docs.griptapenodes.com/enterprise/using_the_admin_server/index.md). Administrators issuing the keys should start with the [Admin Dashboard](https://docs.griptapenodes.com/enterprise/admin_dashboard/index.md). ## Logging and Diagnostics Turn these on before reproducing a problem you want to report. - **Write engine logs to file** — saves engine output to a log file. Files rotate at 10MB. With this on, you can also export engine logs from the Engine page. - **Write application logs to file** — saves application and editor logs, also rotating at 10MB. **Export** asks whether you want the current session or a specific time range, then writes the logs out to a file you can attach to a bug report. - **Log Retention** — how long to keep log files before deleting them automatically. Pick a number and a unit (**Days**, **Months**, or **Years**); the default is 7 days. Check **Don't delete log files** to keep them forever. See [Troubleshooting](https://docs.griptapenodes.com/troubleshooting/index.md) for what to look for once you have the logs. ## Updates **Current Version** is the app version you're running, and **Check for Updates** looks for a newer one right now instead of waiting for the next launch. **Update Behavior** controls what happens when an update is found: | Setting | What it does | | --------------------- | ----------------------------------------------------------------------------- | | **Auto-Update** | Downloads and installs updates automatically on startup. | | **Prompt for Update** | Shows a notification banner when an update is available. This is the default. | | **Silence Updates** | Doesn't check for or notify you about updates. | **Show release notes after updates** displays a summary of what changed after the app updates itself. ### Release channels **Release Channel** picks which builds the app updates to. | Channel | What you get | | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | | **Stable** | Tested releases. This is the default and what you want unless you have a reason to be on Nightly. | | **Nightly** | A fresh build published every day, bundling pre-release engine and editor builds along with the app. Versions look like `0.22.0-nightly.20260611.123`. | Nightly builds go out once a day, and only the 14 most recent are kept, so a nightly install left alone for longer than a couple of weeks will jump several versions when it next updates. Warning Nightly builds are cut from the latest development work rather than a tested release. They can contain bugs, half-finished features, and changes that get reverted. Don't use Nightly for production work, and save your workflows often if you do run it. Switching channels takes effect at the next update check, whether that's the next time you launch the app, the check that runs every few hours in the background, or the moment you click **Check for Updates**. You don't need to reinstall, and switching back to **Stable** works even though the stable version number is lower than the nightly you were on. Your workspace, workflows, and settings are untouched either way. If you install the engine yourself instead of using the app, the equivalent is the pre-release engine channel described in the [FAQ](https://docs.griptapenodes.com/faq/#how-can-i-test-out-unreleased-features). ## Griptape Cloud API Key The API key the app provisioned for you, hidden by default. **Show** reveals it and **Copy** puts it on your clipboard — handy when you want to use the same key with a manually installed engine or a script. Treat it like a password. This section only appears if the app has a key for you, so you won't see it if you activated with a license key instead of signing in. ## App Data Locations The directories where the app keeps its own data, separate from your workspace. **Open** reveals a folder in Finder, Explorer, or your file manager. Log files are not in here; export those from [Logging and Diagnostics](#logging-and-diagnostics) or, for engine logs, the Engine page. ## Environment Information A snapshot of exactly what you're running: app version and build, the bundled editor version, the Python interpreter and its installed packages, the Griptape Nodes engine version and bundle directory, and your platform details. **Refresh** re-reads it. **Copy** puts the whole report on your clipboard and **Export...** saves it to a file. Attach one of those to any bug report you file. # Editor # The Editor The editor is the visual workspace where you build, run, and inspect Griptape Nodes workflows. It's a canvas: you drag nodes onto it, wire them together, and run the result. Everything else in the window — the header, the menu bar, the two sidebars — exists to support that canvas: naming and running your workflow, finding nodes to add, and inspecting what a node is doing once it runs. This page is a tour of each region. If you're looking for a specific task instead, the [Where to go next](#where-to-go-next) section links to focused guides for nodes, groups, running workflows, media editors, and library management. ## The canvas The canvas is where your workflow lives. Nodes are boxes with inputs and outputs; connections between them are the lines you draw from one node's output to another's input. Everything you build ends up here. A few ways to move around: - **Pan**: hold `Space` and drag (this works even while your cursor is over a node), or drag with the middle mouse button. - **Zoom**: scroll or pinch, use the zoom slider in the top-left corner (if enabled in your settings), or hold `Z` and drag left/right for a Photoshop-style scrubby zoom. - **Select**: drag on empty canvas to draw a selection box around several nodes, or Shift-click individual nodes to add or remove them from the current selection. A **minimap** in the corner (toggle it in your editor settings if it's hidden) shows the whole graph at a glance and lets you jump around by clicking or dragging inside it. The controls cluster in the bottom-left gives you zoom in/out/fit-view buttons and a **Toggle Clean Mode** button that hides the decorative Griptape logo watermark — useful when you're taking a screenshot of your own workflow. Double-clicking empty canvas opens the **Add Node** menu at your cursor. See [Working with Nodes](https://docs.griptapenodes.com/guides/editor/working_with_nodes/index.md) for everything you can do with a node once it's placed, and [Keyboard Shortcuts](https://docs.griptapenodes.com/guides/editor/keyboard_shortcuts/index.md) for the full list of canvas shortcuts. ## The header The header runs across the top of the window and is split into three clusters. On the **left**, a sidebar-toggle button sits next to the [menu bar](#the-menu-bar), followed by your workflow's name. The name shows a trailing `*` whenever you have unsaved changes, and the file name (`.py`) appears underneath once the workflow has been saved at least once. An unsaved, never-saved workflow shows just the in-progress name with no file name line. In the **center**, the run controls: | Button | What it does | | --------------------- | -------------------------------------------------------------- | | **Run Workflow** | Runs the entire workflow from its start. | | **Run To Selected** | Runs up through the single selected node. | | **Run From Selected** | Starts execution at the single selected node and runs forward. | | **Cancel Run** | Stops a workflow that's currently running. | **Run To Selected** and **Run From Selected** are only enabled when exactly one node is selected. See [Running Workflows](https://docs.griptapenodes.com/guides/editor/running_workflows/index.md) for what "runnable" means and how partial runs behave. On the **right**, an engine picker (for switching which running engine the editor talks to), an error history dropdown, and a **Publish Workflow** button that opens the publish dialog. ## The menu bar The menu bar sits in the header's left cluster, next to the sidebar toggle. It has three menus. ### File Create, open, save, and manage the current workflow file: - **New** / **Open...** — start a new workflow or browse to an existing one. - **Rename...** — rename the workflow (disabled for Griptape-provided templates; use Save As to make an editable copy instead). - **Save** / **Save As...** / **Save As New Version** — Save As New Version writes a new versioned file (`my_workflow_v002.py`, and so on) instead of overwriting; it only appears once the workflow has been saved at least once. - **AutoSave** — toggle automatic saving on or off, with a shortcut into its settings. - **Refresh Libraries** — pick up library changes without restarting the engine. - **Report Issue** — file a bug report. - **Exit** — leave the editor. - **Delete ``** — permanently delete the current workflow file. ### Manage Jumps to the management surfaces for the resources a workflow depends on: - **Model Management** — models available to nodes that need them. - **Library Management** — install, update, and configure node libraries. See [Libraries](https://docs.griptapenodes.com/guides/libraries/index.md) for the full guide. - **Engine Management** — the engines the editor can connect to. - **Project Management** — the projects available to switch between. See [Managing Projects in the GUI](https://docs.griptapenodes.com/guides/projects/gui_guide/index.md). ### Settings Everything configurable, grouped under a **Settings** submenu (All Settings, Agent Settings, Editor Settings, Theme Settings, Engine Settings, File System, Libraries, Library Settings, MCP Servers, and API Keys & Secrets), plus three actions below it: **Copy Path to Settings** (copies the settings file's path to your clipboard), **Show Settings Folder** (opens it in Finder/Explorer/your file manager), and **Reset Settings to Default**. These configure the engine and the editor. If you're running Griptape Nodes Desktop, the application's own settings — theme, license, log files, and update channel — live in [App Settings](https://docs.griptapenodes.com/guides/desktop/app_settings/index.md) instead, reached from the account menu in the top-right corner of the window. ## The left sidebar The left sidebar is where you find nodes and libraries to add to your workflow. A **Favorites** section sits above the tabs — star a node to pin it here so you don't have to hunt for it every time — followed by a search box that filters whichever tab is active. Two tabs live below the search box: - **Nodes** — every node available to you, organized by category, from both built-in and installed libraries. - **Libraries** — the same nodes, organized by which library they come from instead of by category. This is a browsing view; to install, update, or remove a library itself, use **Manage → Library Management** (see [Libraries](https://docs.griptapenodes.com/guides/libraries/index.md)). Drag a node from either tab onto the canvas to add it to your workflow. Click the sidebar-toggle button in the header (or its shortcut) to collapse the sidebar down to a narrow icon rail, or close it entirely. ## The right sidebar The right sidebar holds a set of tabbed panels, titled **Sidebar Panels**. A dropdown next to its close button (the icon with three dots) lets you show or hide any panel — each entry shows an eye icon so you can see which panels are currently visible. Hiding a panel here doesn't discard anything; its tab returns the next time you re-enable it. The four panels: - **Chat** — talk to an agent thread directly inside the editor, with a picker for model/provider and any MCP servers you've wired up. - **Code** — the workflow's generated Python, in a syntax-highlighted editor you can search and run from. - **Logs** — the execution log stream, filterable by severity (Debug, Info, Warning, Error, Critical) and searchable. - **Properties** — the parameters of whichever node (or nodes) you currently have selected on the canvas. This is usually where you'll spend most of your time once a workflow is built, since it's how you configure each node's inputs. If the sidebar is closed, a floating button near the top-right corner of the canvas reopens it. ## Where to go next - [Keyboard Shortcuts](https://docs.griptapenodes.com/guides/editor/keyboard_shortcuts/index.md) — every shortcut in the editor, grouped by what you're trying to do. - [Working with Nodes](https://docs.griptapenodes.com/guides/editor/working_with_nodes/index.md) — adding, wiring, renaming, locking, and duplicating nodes. - [Node Groups](https://docs.griptapenodes.com/guides/editor/node_groups/index.md) — grouping nodes and arranging your graph. - [Running Workflows](https://docs.griptapenodes.com/guides/editor/running_workflows/index.md) — the difference between running a workflow, running to a node, and running from a node. - [Media Viewers and Editors](https://docs.griptapenodes.com/guides/editor/media_editors/index.md) — the built-in viewers and editors for images, video, and other media parameters. - [Managing Models and Libraries](https://docs.griptapenodes.com/guides/editor/managing_models_and_libraries/index.md) — the Model Management and Library Management surfaces in more depth. # Keyboard Shortcuts A reference for every shortcut in the editor, grouped by what you're trying to do. macOS and Windows/Linux shortcuts are listed in separate columns where the modifier keys differ. Typing is never interrupted Most single-key canvas shortcuts (like `R` or `N`) are ignored while you're typing in a text field, a node's parameter, or the Code panel, so ordinary letters always go where you're typing instead of triggering a shortcut. For a tour of the regions these shortcuts act on, see [The Editor](https://docs.griptapenodes.com/guides/editor/index.md). ## Adding & editing nodes | Action | macOS | Windows / Linux | | ---------------------------------------------------------- | ------------------- | ------------------- | | Add a node at the cursor (canvas only) | Tab | Tab | | Add a node at the cursor, or select all if not over canvas | Shift+A | Shift+A | | Open the Add Node menu at the cursor | Double-click canvas | Double-click canvas | | Create a Note node at the cursor | N | N | | Rename the selected node(s) | R | R | | Duplicate the selected node(s) | Cmd+D | Ctrl+D | | Lock / unlock the selected node(s) | L | L | | Delete the selected node(s) or connection | Delete / Backspace | Delete / Backspace | ## Selection | Action | macOS | Windows / Linux | | ----------------------------------------------------- | -------------------- | -------------------- | | Select all nodes | Cmd+A | Ctrl+A | | Clear the current selection | Escape | Escape | | Add or remove a node from the selection | Shift-click a node | Shift-click a node | | Draw a selection box around several nodes | Drag on empty canvas | Drag on empty canvas | | Move the selection to the nearest node in a direction | Arrow keys | Arrow keys | ## Clipboard | Action | macOS | Windows / Linux | | ------------------------------------------------------ | ----- | --------------- | | Copy the selected node(s) | Cmd+C | Ctrl+C | | Paste nodes, or an image/media file from the clipboard | Cmd+V | Ctrl+V | ## Navigation & view | Action | macOS | Windows / Linux | | ------------------------------------------------------------------ | ------------------- | ------------------- | | Frame the selected node(s), or the whole graph if nothing selected | F | F | | Fit a selected group to its nodes (otherwise frames the selection) | Shift+F | Shift+F | | Pan the canvas, even while over a node | Hold Space + drag | Hold Space + drag | | Pan the canvas | Middle-click + drag | Middle-click + drag | | Scrubby zoom — drag right to zoom in, left to zoom out | Hold Z + drag | Hold Z + drag | | Zoom in | Cmd+= | Alt+= | | Zoom out | Cmd+- | Alt+- | | Zoom in / out | Scroll or pinch | Scroll or pinch | ## Groups & layout | Action | macOS | Windows / Linux | | ------------------------------------------------ | ----------- | --------------- | | Create a group from the selection (default type) | Cmd+G | Ctrl+G | | Create a group, always choosing the type | Shift+Cmd+G | Shift+Ctrl+G | | Auto-arrange the graph | Shift+G | Shift+G | See [Node Groups](https://docs.griptapenodes.com/guides/editor/node_groups/index.md) for what the group types mean and how auto-arrange lays out your graph. ## Connections | Action | macOS | Windows / Linux | | -------------------------------------------------------------- | -------- | --------------- | | Toggle display of parameters that already have a connection | Shift+H | Shift+H | | Insert a dot (reroute) node on the hovered/selected connection | I | I | | Show a connection's action buttons while hovering over it | Hold Cmd | Hold Ctrl | ## Workflow / file | Action | macOS | Windows / Linux | | --------------------------------- | --------------- | ---------------- | | Save | Cmd+S | Ctrl+S | | Save As | Shift+Cmd+S | Shift+Ctrl+S | | Save As New Version | Option+Shift+S | Alt+Shift+S | | Open Workflow | Cmd+O | Ctrl+O | | New Workflow | Shift+Cmd+O | Shift+Ctrl+O | | Run to the selected node | Cmd+Enter | Ctrl+Enter | | Run the whole workflow | Shift+Cmd+Enter | Shift+Ctrl+Enter | | Download the workflow as an image | Cmd+I | Ctrl+I | | Toggle the right sidebar | Cmd+B | Ctrl+B | | Toggle the left sidebar | Shift+Cmd+B | Shift+Ctrl+B | | Show this shortcuts reference | Shift+? | Shift+? | # Working with Nodes This page covers the everyday mechanics of building a workflow on the canvas: adding nodes, selecting and moving them, renaming, duplicating, locking, deleting, wiring connections between them, and editing their parameters. For running a workflow once it's built, see [Running Workflows](https://docs.griptapenodes.com/guides/editor/running_workflows/index.md). For grouping nodes together, see [Node Groups](https://docs.griptapenodes.com/guides/editor/node_groups/index.md). ## Adding nodes There are three ways to open the **Add Node** menu: - Press `Tab` while the cursor is over the canvas. - Press `Shift+A` while the cursor is over the canvas. (`Shift+A` with the cursor off the canvas selects all nodes instead — see [Selecting and moving](#selecting-and-moving).) - Right-click empty canvas and choose **Add Node**. The menu opens at your cursor and drops the new node there once you pick one. It's a searchable, categorized list — type to filter by name, description, or tag, or browse the category tree. A **Favorites** section (star a node to add it) and a **Recent** section (your last few added node types) sit above the category list when you have either. You can also drag a node type from the **Nodes** tab in the left sidebar straight onto the canvas. ## Selecting and moving Click a node to select it. `Cmd/Ctrl+A` selects every node in the flow; `Escape` clears the selection. Drag a box across empty canvas to select everything inside it. Shift-click (or Cmd/Ctrl-click) additional nodes to build a multi-selection. With more than one node selected, dragging any of them moves the whole selection together, and a floating toolbar appears above the selection with actions that apply to all of them at once — Copy, Duplicate, Lock, Reset, Delete, and Create Group, depending on what's configured in **Settings → Editor → Button Customization**. The arrow keys move the selection to the nearest node in that direction, which is a fast way to step through a chain of connected nodes without touching the mouse. `F` frames the current selection (or the whole graph, if nothing is selected); `Shift+G` auto-arranges the graph into a tidy layout. ## Renaming Every node's display name doubles as its unique identifier in the workflow, so renaming a node also updates every reference to it. To rename a single node, select it and press `R`, or click the small pencil icon that appears next to its name on hover, or choose **Rename** from its right-click menu. Type the new name and press `Enter` (or click away) to commit it, or `Escape` to cancel. To rename several nodes at once, select them and press `R`, or right-click one of them and choose **Rename**. This opens the **Rename nodes** dialog, which takes a single base name and applies it across the whole selection: the first node gets the base name exactly, and the rest get `Base_1`, `Base_2`, and so on. ## Duplicating, copying, and pasting - **Duplicate** (`Cmd/Ctrl+D`, or the Duplicate action on the node or the multi-node toolbar) creates an independent copy of the selected node(s) right next to the originals, with no connections to anything. - **Copy** (`Cmd/Ctrl+C`) serializes the selected node(s) — including their parameter values — to the system clipboard. - **Paste** (`Cmd/Ctrl+V`) creates new nodes from whatever you last copied, dropped near your cursor. Copy/paste is also how you move nodes between two open workflow tabs: copy in one, switch tabs, paste in the other. ## Locking Locking a node protects it from accidental changes: a locked node can't be run, deleted, have its parameters edited, or have parameters added to it until you unlock it again. Toggle a node's lock state with `L` (works on a multi-selection too), the lock icon in its header, or **Lock**/**Unlock** in its right-click menu. Note nodes can't be locked — the lock option doesn't apply to them. ## Deleting Select one or more nodes and press `Delete` (or `Backspace` on some keyboards), or use **Delete Node** from the right-click menu or the toolbar. Deleting a node removes any connections it had; deleting a [dot node](#dot-and-reroute-nodes) that sits in the middle of a connection instead reconnects the two ends directly, so the rest of the wire survives. Deleting a group only removes the group wrapper — its child nodes stay on the canvas, ungrouped. See [Node Groups](https://docs.griptapenodes.com/guides/editor/node_groups/#ungrouping) for more. ## Connecting parameters Every parameter that can act as an input or output gets a small handle on the left (input) or right (output) edge of its row. Drag from one handle to another to create a connection; drag from empty canvas off a handle to open the Add Node menu pre-wired to that handle, which is a quick way to add the *next* node in a chain. The editor only lets you complete a connection between compatible types — as you drag, incompatible handles elsewhere on the canvas dim out so you can see where the connection can land. Most parameters accept more than one type (an image input that also accepts a URL string, for example), and the specific accepted types are baked into each parameter by its node. To remove a connection, click its wire to select it and press `Delete`, or hover the wire and use the connection button that appears on it (if **Show Connection Buttons on Hover** is enabled in **Settings → Editor → Node Settings**). ## Dot and reroute nodes A **dot node** is a zero-logic pass-through you can drop in the middle of an existing connection to bend its path around other nodes, or just to tidy up a busy canvas. With a connection hovered or selected, press `I` to insert a dot node right at that spot — the original connection is split into two, routed through the new dot node, with no change to the data that flows through it. Deleting a dot node reconnects its two neighbors directly, so removing one never breaks the flow. ## Notes A **note** is a free-floating, non-executing block of text you drop on the canvas to leave yourself or a collaborator context — what a section of the graph is for, a TODO, a warning about an upstream dependency. Press `N` with the cursor over the canvas to drop one at your cursor. Notes can't be locked, and don't participate in execution or in the multi-node toolbar's Run/Reset actions. ## The Properties panel Selecting a node opens its parameters in the **Properties** panel (right sidebar) alongside the parameter rows shown inline on the node itself — the same values, just in a dedicated, always-visible place that's easier to work in when a node has a lot of parameters or you've collapsed the node itself. Open the right sidebar that hosts it with `Cmd/Ctrl+B`, from the right-click menu's **Property Panel** entry, or from a node's header button if you've added it there. Editing a value in the Properties panel and editing the same parameter's inline row on the node update the same underlying value. ### Adding a custom parameter Some nodes let you add parameters of your own on top of the ones the node ships with. If a node supports it, you'll see an **Add parameter** row at the bottom of its parameter list (on the node itself and in the Properties panel), or **Add parameter** in its right-click menu. Opening it shows the **Add New Parameter** dialog, where you set: | Field | What it controls | | ------------------ | ---------------------------------------------------------------------------------------------------------- | | **Name** | The parameter's internal name. Must be unique on the node. | | **Display Name** | The label shown in the UI, if you want it to read differently from the internal name. | | **Type** | One of `Any`, `str`, `bool`, `int`, `float`, `dict`, `json`, or the media types `image`, `audio`, `video`. | | **Tooltip** | Help text shown on hover. | | **Input / Output** | Whether the parameter can be wired as an incoming connection, an outgoing connection, or both. | | **Default Value** | The value the parameter starts with before you set or connect anything. | Depending on the type you pick, more options appear — a multiline/Markdown toggle for text, a slider with min/max/step for numbers, or a fixed set of choices (with optional icons, subtitles, and a search box) for a dropdown. ### Configuring an existing custom parameter Once a custom parameter exists, right-click it (or its equivalent row in the Properties panel) and choose **Configure parameter** to reopen the same dialog pre-filled with its current settings. This option only appears for parameters you added yourself — parameters that ship with the node's definition aren't editable this way. ### Hiding connected parameters Turn on **Hide Connected Parameters** (`Shift+H`, or **Settings → Editor → Node Settings**) to collapse any parameter row whose input already has an incoming connection. It's a decluttering tool for busy workflows: once a value is coming from somewhere else on the canvas, you usually don't need to see its editable field taking up space too. Toggling it back off restores every row immediately. Keep specific parameters visible anyway **Ignore Hide Connected (Display Nodes)** and **Ignore Hide Connected (Save Nodes)**, next to the main toggle in Node Settings, exempt any parameter whose name contains "display" or "save" — useful for nodes where you want to keep an eye on an output path or preview even while its input is wired up. # Node Groups A **group** is a container node that holds other nodes. Every group visually organizes the nodes inside it, but some group types go further and take over how those nodes execute — running them together, or running them repeatedly. This page covers creating, configuring, and running groups. For working with ordinary nodes, see [Working with Nodes](https://docs.griptapenodes.com/guides/editor/working_with_nodes/index.md). ## Creating a group Select the nodes you want to group (or select nothing, for an empty group you can drop nodes into later), then: - Press `Cmd/Ctrl+G` to create a group immediately, using whichever group type you've marked as your default. - Press `Shift+Cmd/Ctrl+G`, or right-click and choose **Create Group**, to open the **group type picker** first. The group type picker lists every group type available from your installed libraries, searchable the same way the Add Node menu is. Check **Make default for `Cmd/Ctrl+G`** at the bottom before picking a type to skip the picker next time. Until you've set a default, `Cmd/Ctrl+G` opens the picker too. A new group is sized to wrap whatever was selected when you created it, with a bit of padding. Dropping a node onto an existing group's body adds it to that group; dragging a child node out removes it. ## Group types The group types that ship with the standard library fall into two families: | Type | Executes? | What it does | | ---------------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------- | | **Group** | No | Purely organizational. Bundles nodes visually with a description field; doesn't run them as a unit or affect execution order. | | **Subflow Node Group** | Yes | Runs every node inside it together, as a single unit, in parallel. | | **ForEach Group** | Yes | Runs its contents once per item in an input list or dictionary. | | **For Loop Group** | Yes | Runs its contents once per value in a numeric range (start/end/step). | | **Retry Group** | Yes | Re-runs its contents on failure, up to a configurable number of attempts. | A plain **Group** never has a **Run Group** button — it's not executable, so there's nothing for the engine to run beyond the nodes inside it running on their own. Every other type in the table is a **subflow group**: it creates a dedicated subflow to hold its children, and the group node itself becomes a single executable unit in the outer flow's graph. ### Subflow Node Group The baseline executable group. When the group runs, every node inside it runs together in its own subflow, and their outputs are collected back onto the group's own output parameters. Use it when you want to treat a cluster of nodes as one step in a larger flow — for readability, or because you want a single **Run Group** button instead of running each node individually. ### ForEach Group Iterates over a list or dictionary. Wire a list (or dict) into its **items** input; the group runs its child nodes once per item, exposing that item on **current_item** and the item's position on **index** for the child nodes to consume. A **new_item_to_add** input on the far side collects a value from each iteration into the group's **results** output list. **Execution Mode** (in the group's settings) chooses between running iterations **one at a time** or **all at once**. Only sequential mode supports the **Skip to Next Iteration** and **Break Out of Loop** control inputs — they're hidden automatically when you switch to all-at-once, since there's no "next iteration" to skip to once every iteration is already running concurrently. A **Testing Mode** toggle in settings lets you run just one chosen index while you're wiring up the loop body, instead of the whole list. ### For Loop Group Same shape as ForEach, but iterates over a numeric range instead of a list — **start**, **end**, **step**, and an **Include end value** toggle to control whether the range is inclusive. It shares the same **Execution Mode**, **results**, **skip**, and **break** machinery as ForEach. ### Retry Group Re-executes its contents when they fail. Wire the success path from your child nodes to the group's **Succeeded** control input, and the failure path to **Failed**. If **Failed** fires and attempts remain (**Max Iterations**, default 3), the whole group re-runs; if **Succeeded** fires, or attempts run out, execution continues downstream with **was_successful** reporting which happened. A **Raise on failure** toggle stops the workflow with an error when the retry budget runs out, instead of continuing with **was_successful** set to `false`. ## Wiring connections across the group boundary Every connection that crosses from outside a subflow group to a node inside it (or back out) is routed through a **wall parameter** — a parameter that lives on the group node itself, shown along its left edge (things flowing in) or right edge (things flowing out). You don't create these by hand: drag a connection from an inside node's handle to an outside node (or vice versa) and the wall parameter appears automatically, wired straight through. Behind the scenes each wall parameter is a proxy: the engine deletes the direct connection, adds a matching parameter on the group, and reconnects through it in two hops instead of one. If a node with wall connections gets dragged out of the group, those connections resolve back to a single direct connection and the now-unused wall parameter disappears; if two nodes inside the group each need to reach the same outside source, they share a single wall parameter rather than getting one each. A plain **Group** doesn't do any of this — it has no subflow to wall off, so connections just pass straight through its boundary. ## Nesting groups inside groups A group can hold another group, as deeply as you like. Drop a group onto another group's body, or select a group along with other nodes and press `Cmd/Ctrl+G`, and the inner group becomes a child of the outer one — carrying everything already inside it along with it. Nesting is how you break a large flow into readable layers: a ForEach Group whose body is itself a Subflow Node Group, say, so each iteration runs a named cluster of work instead of a sprawl of loose nodes. Everything a single group does applies at each level. Running the outer group runs the inner ones as part of it, so you don't need to run them separately. Collapsing, rolling up, and **Fit to nodes** each act on the group you invoke them on, leaving whatever is nested inside untouched. Connections nest the same way. A connection from outside the outer group to a node buried two levels down passes through a wall parameter on *each* group it crosses, so you can trace the value inward one boundary at a time. Connections that stay entirely within one group don't get wall parameters, no matter how deeply that group is nested — only crossing a boundary creates one. **Ungroup** peels off one layer at a time. Ungrouping the outer group leaves the inner group intact and drops it, still holding its children, onto the canvas where it was. Deleting a group does the same to whatever it held. The one arrangement the editor won't allow is a group that ends up inside itself — dropping a group into one of its own descendants is refused, since there would be no outermost group left to run. ## Collapsing and rolling up Click the chevron in a group's header to **collapse** it. A collapsed group hides its children and shrinks down to just its wall parameters (or, if it has none, a single row that says "Group collapsed"). This is the fastest way to shrink a large group down to its inputs and outputs without losing the ability to rewire it. **Roll up** — double-click the group's header, or use the roll-up button next to the collapse button, when **Node Roll-Up** is enabled in settings — does something similar without discarding the layout: the group shrinks to its header height while keeping its current width and its child positions intact, so expanding it again snaps right back to how you left it. ## Fit to nodes **Fit to nodes** (`Shift+F` with a single group selected, or the group's right-click menu / toolbar) resizes the group's box to snugly wrap whatever nodes are currently inside it, with a fixed padding on every side. Use it after adding or removing children by hand, when the group's box has drifted out of sync with what's actually inside it. ## Ungrouping **Ungroup**, from the group's right-click menu or its toolbar, removes the group wrapper and leaves its child nodes exactly where they were, converted from group-relative to absolute canvas positions. For a subflow group, this also tears down its dedicated subflow and restores every wall connection as a direct connection between the original nodes — nothing about the underlying wiring is lost, only the grouping. Deleting a group node outright (rather than ungrouping it) does the same thing to its children: they're detached and left on the canvas rather than deleted along with the group. ## Running a group Executable group types get a **Run Group** button in their toolbar (visible when the group is selected), alongside the same collapse and duplicate controls every group has. Running a group resolves it — and, transitively, whatever it depends on upstream — the same way running any single node does. A subflow group also carries an **Execution Environment** setting (group settings popover), which chooses where its nodes actually run. It defaults to **Local Execution** — inside the engine process, same as everything else on the canvas — with additional options appearing for any installed library that registers a way to publish and run workflows remotely. While a group is running, it shows the same **Running** / **Resolved** / **Unresolved** status pill in its header that ordinary nodes show. A plain **Group** never shows this pill, since it never runs as a unit. # Running Workflows This page covers everything that happens once your workflow is built and you want to actually run it: starting a run, stopping one, reading what's happening on the canvas while it runs, checking the logs and error history afterward, and saving your work. ## Running the whole workflow The **Run Workflow** button, in the center of the header, runs every node in the current workflow from the start. It's disabled while a workflow is already running, and while no workflow is open. | Shortcut | Action | | ------------------------------- | ------------ | | `Shift` + `Cmd/Ctrl` + `Return` | Run Workflow | ## Running a single node Two more buttons next to Run Workflow let you run less than the whole graph. The buttons require exactly one node to be selected. Run From Selected is disabled while the workflow is already running; Run To Selected is not — see [Running a node during a run](#running-a-node-during-a-run) below. - **Run To Selected** resolves the selected node — and everything upstream of it that isn't already resolved — without touching anything downstream. This is the usual way to iterate: change a parameter, run to the node you're looking at, check the result. - **Run From Selected** starts execution at the selected node and runs forward from there through the rest of the flow, the same way Run Workflow would, just starting partway through. | Shortcut | Action | | --------------------- | --------------- | | `Cmd/Ctrl` + `Return` | Run To Selected | Run From Selected has no keyboard shortcut; use the button. Unlike the buttons, the `Cmd/Ctrl` + `Return` shortcut also works with several nodes selected — it runs to each of them. Both single-node actions are also available from the node's right-click context menu, and from the multi-node toolbar when applicable. ## Running a node during a run You don't have to wait for a run to finish to try a node. **Run To Selected** stays available while the workflow is running, and the node joins the run that's already in progress instead of starting a new one. Anything upstream of it that isn't resolved yet is pulled into the same run and resolved first, exactly as it would be from a standing start. When the node actually begins depends on how much of the run is already in flight: - In **Parallel** execution mode it starts as soon as the run has a free slot, which may be immediately. The number of nodes that can run at once is the `max_nodes_in_parallel` setting (5 by default). - In **Sequential** execution mode one node runs at a time, so the node you played starts once the node currently running finishes. Both modes are described in [Configuration](https://docs.griptapenodes.com/guides/configuration/index.md). Two cases are worth knowing about: - **If the run is paused**, the node is added to it but does not start. It runs when you step or continue the run. - **If the node is already part of the run** — already finished, running, or queued behind its dependencies — playing it is refused rather than run twice, and the editor reports that the node is already executing. Cancel the run if you want to start it over. **Run Workflow** and **Run From Selected** still require an idle workflow; they stay disabled until the current run ends. ## Stopping a run While a workflow is running, the same button area shows **Cancel Run** in place of the run buttons. Click it to cancel the in-flight execution. The button shows **Cancelling…** while the cancellation request is in flight. Canceling a run is also how you get out of the **Cannot Save While Flow is Running** dialog (see [Saving](#saving) below) — that dialog offers a **Cancel Flow** button that does the same thing. ## Reading execution state on the canvas Every node shows a status pill in its header while it's involved in the current run: | Pill | Meaning | | ------------------------------ | ------------------------------------------------------ | | **Running** (orange, spinning) | The node is actively resolving right now. | | **Resolved** (green) | The node finished successfully. | | **Error** (red) | The node failed. Hover the pill for the error message. | | **Unresolved** (blue) | The node hasn't run yet in this pass. | A small asterisk next to a node's name also marks it as unresolved, independent of whether it's part of the current run. After a run ends, a node that errored keeps its red **Error** pill so you can still find it and inspect the message without having to remember which node failed. ## The Logs panel The **Execution Log** panel (right sidebar) collects everything the engine logs while your workflow runs — engine messages as well as anything a node itself logs, tagged with the node's name when available. What you can do from the panel: - **Log Level Visibility** — a dropdown that sets the engine's logging verbosity (`DEBUG`, `INFO`, `WARNING`, `ERROR`, `CRITICAL`). This changes what gets logged in the first place, not just what's displayed. - **Filter** — a row of level toggles (only the levels at or above the current verbosity are selectable) that lets you show or hide levels in the panel without changing the engine's verbosity. - **Download** (the download icon) — saves the currently filtered logs to a `.txt` file, one line per entry with a timestamp. - **Clear** (the eraser icon) — clears the log list. - The settings (gear) icon opens **Log Settings**, where you can change the maximum number of log entries the panel keeps in memory. The panel auto-scrolls to the newest entry as logs arrive. ## Error History The header also carries an **Error History** button (the triangle/circle icon with a count badge) that's independent of any single run — it accumulates errors and permission denials across your whole session, not just the workflow that's currently executing. Click it to open a dropdown listing each error with a relative timestamp and its source. Click an entry to see the full error in a modal. **Clear All** empties the list; the small × next to an individual entry removes just that one. ## Saving The workflow name in the header shows an asterisk (`*`) whenever you have unsaved changes, whether that's from editing the graph or from a workflow that's never been saved to disk at all. - **Save** (`Cmd/Ctrl` + `S`) saves in place. For a workflow that's never been saved, this opens the **Save Workflow** dialog so you can choose a name. - **Save As** (`Cmd/Ctrl` + `Shift` + `S`) opens the **Save Workflow As…** dialog and writes a new, separate copy under the name you give it. Griptape-provided templates always go through Save As — you can't overwrite the template itself. - **Save As New Version** (`Option/Alt` + `Shift` + `S`, on engines that support versioned saves) writes the current state as a new versioned file (`my_workflow_v002.py`, and so on) and switches you to that new version, leaving the previous version untouched on disk. While a workflow is running, all three save actions are blocked. Trying to save shows a **Cannot Save While Flow is Running** dialog explaining that you need to cancel or wait for the run to finish first, with a shortcut button to cancel the flow right from the dialog. A **Saving workflow…** overlay appears briefly at the bottom of the screen while a save is in progress. ### Auto-save **Settings → Editor Settings → Auto-Save Settings** lets you turn on automatic saving so you don't have to remember to hit `Cmd/Ctrl+S`. Once enabled you can set: - **Auto-Save Interval (seconds)** — how often the editor attempts an auto-save. - **Auto-Save Notifications** — whether a toast confirms each auto-save. Auto-save only fires when there's an open workflow with unsaved changes that already exists on disk, and it skips the attempt entirely while the workflow is running — the same running-workflow guard that blocks manual saves applies here too, just silently instead of via a dialog. ## Developer Mode **Settings → Editor Settings → Developer Mode** adds a couple of extras for understanding *why* the engine is evaluating your workflow the way it is, rather than just *whether* a node succeeded. With it enabled, every node gets a collapsible **Developer Mode** strip that lists its upstream data dependencies — the nodes actually feeding it data, not control-flow-only connections — each shown with its own resolution icon (✓ resolved, ↻ resolving, ✗ errored, ○ unresolved). Clicking a dependency chip pans the canvas to that node, which helps when you're tracing a stale value back to its source in a large graph. Developer Mode also adds a **Mark as unresolved** action to the node's right-click menu. Use it to force a node — and everything downstream of it — back to the unresolved state without changing anything about the node itself. That's the fastest way to make the engine re-run a node on the next Run Workflow or Run To Selected, even though none of its inputs actually changed (for example, after fixing something external the node depends on, like a file on disk). # Media Viewers and Editors Whenever a parameter carries an image, video, audio clip, 3D model, or Gaussian splat, the editor swaps in a viewer built for that media type instead of a plain text field. Some of these viewers are also editors — you can paint a mask, crop a frame, or composite layers without leaving the canvas. This page is a tour of each one: where it appears, how you open it, and what you can do once it's open. Which viewer you get is decided by the parameter's type: | Parameter type | Viewer | | -------------------------------------- | ------------------------------------------------------------- | | `ImageArtifact` / `ImageUrlArtifact` | [Image viewer](#viewing-an-image-and-its-right-click-actions) | | `VideoArtifact` / `VideoUrlArtifact` | [Video player](#playing-and-comparing-video) | | `AudioArtifact` / `AudioUrlArtifact` | [Audio player](#playing-audio) | | `ThreeDArtifact` / `ThreeDUrlArtifact` | [3D model viewer](#viewing-3d-models) | | `SplatArtifact` / `SplatUrlArtifact` | [Gaussian splat viewer](#viewing-gaussian-splats) | The comparison sliders (image and video) aren't separate types — a node opts a parameter into comparison display when it exposes two media values through one parameter. (Node authors: the full type-to-widget mapping lives in the [Parameter UI Reference](https://docs.griptapenodes.com/development/custom_nodes/parameter_ui_reference/index.md).) Which of the extra buttons appear on top of a viewer (crop, mask, capture, and so on) depends on options a node author set on that parameter. You don't configure this yourself — it's baked into the node — but it explains why the same image parameter might show a crop icon on one node and a mask icon on another. ## Viewing an image and its right-click actions Image parameters display through the standard image viewer: a thumbnail with the image's name, dimensions, and file size underneath it. Hovering reveals an expand button that opens the image full-screen in a lightbox; press **Escape** or click outside it to close. Right-click any image — on its thumbnail in a node, or on the enlarged version in the lightbox — to get a context menu with: - **Copy image** — copies the full-resolution image to your system clipboard as PNG (re-encoding it first if the source is a different format). - **Copy image URL** — copies the image's URL as text. - **Save image** — downloads the full-resolution image to disk. - **Make thumbnail** — sets this image as the workflow's header thumbnail, the one shown in the workflow browser. This updates the workflow's saved metadata immediately. ## Comparing two images with a slider When a node exposes a pair of images through a single comparison parameter, the editor renders them as one image with a draggable vertical divider: everything left of the divider is the first image, everything right of it is the second. Move your mouse across the comparison area to slide the divider. Hover to reveal an expand button that opens the same comparison full-screen, with each side labeled by its filename. Escape closes it. ## Cropping an image Some image parameters carry a crop button (a crop icon over the thumbnail) that opens the crop editor. Inside, drag the handles on the overlay to resize the crop region, or use: - **Reset All** — resets the crop to the full image. - **Aspect ratio presets** — Square, 16:9, 3:2, 4:3, 9:16, 2:3, 3:4. - **Zoom** — 10–300%, previewed as a pulsing dashed overlay. - **Rotation** — -180° to 180°, with an arrow indicator showing the up direction. - **Crop Details** — the current left/top/width/height in pixels. **Accept & Save** writes the crop as parameter values on the node (left, top, width, height, zoom, rotation) rather than baking it into a new image file — the node itself is responsible for applying the crop when it runs. **Cancel** discards your changes. ## Painting a mask Image parameters with mask editing enabled show a mask icon; clicking it opens the Paint Mask editor. You paint directly onto a canvas laid over the source image: - **Brush** paints white (reveal); **Erase** paints black (hide). - **Brush Size** and **Blur** sliders control stroke width and soft edges. `[` and `]` also adjust brush size without touching the sliders. - **Show Mask / Show Composite** toggles between the raw grayscale mask and the image with the mask applied as transparency. - **Invert Mask** swaps black and white across the whole mask. - **Reset Mask** restores the mask to the image's original alpha channel. - **Flood Fill** fills the entire mask with the current tool's color. - Hold **Alt** and drag to pan, or **Alt**+scroll to zoom; dedicated zoom buttons and a reset-view button sit above the canvas. What **Apply** saves depends on what the parameter already holds: if it's a plain image, painting edits that image's alpha channel and saves a new image with transparency; if the parameter is already paired with a separate mask, painting edits that mask instead and saves it as its own grayscale file, keeping the source image untouched. ## Image Bash: compositing images and paint layers Image Bash is the editor's layered compositing tool. Some image parameters carry a pencil icon that opens it directly; it's also reachable from parameters that store their state as structured layer data, via an **Edit** button that opens the same tool. Griptape's own nodes that pass images through Image Bash use it to build up a shot from multiple source images and painted layers, rather than to edit one image in place. Inside, each layer is either an **image layer** (one of your source images, which you can move, scale, and rotate with the on-canvas transform handles) or a **brush layer** (a paintable canvas you draw into). The sidebar lists every layer with a thumbnail, a visibility toggle, opacity, and drag-to-reorder handles; you can rename, delete, or duplicate any layer, and duplicate an image layer as a new paintable brush layer. Painting tools include four brush types — Pen, Soft, Crayon, and Spray — each with its own size, color, and opacity, plus extra controls for Crayon (fleck size, fleck density, spread, square ratio, density falloff) and Spray (spot size, spot density, spread radius). Undo/redo covers your last 50 brush strokes. You can also resize the canvas and change its background color from within the tool. Default brush and canvas settings live in the editor's Settings panel — search for "ImageBash" to find the card. Changing a default there only affects new sessions of the tool going forward. ## Playing and comparing video Video parameters use a frame-accurate player: step one frame at a time, play forward or backward, and jump between marked frames. Keys work the same as the on-screen controls: `←`/`→` step a frame, `j`/`k`/`l` play backward, pause, and play forward. If the node also exposes a frame-selection parameter, the timeline shows markers you can add (`.` adds the current frame), drag, or clear, and `n`/`m` jump to the previous/next marker. The bottom bar shows playback FPS (editable, with a button to snap back to the video's native rate), the current frame out of the total, mute, and volume. An expandable **Video Details** panel reports dimensions, file size, format, codec, aspect ratio, duration, and frame rate. **Enlarge** opens the player in a larger dialog; the **Fullscreen** button uses your browser's fullscreen mode. Video comparison parameters show two videos side by side with the same draggable divider as image comparison, kept in sync frame-for-frame, with a switch to choose which side's audio track plays. Right-click a video for **Copy video URL** and **Save video**. ## Playing audio Audio parameters render as a waveform with playback controls underneath: play/pause, elapsed/total time, mute, and a volume slider. Click and drag anywhere on the waveform to scrub to that position. ## Capturing from a webcam or microphone Image parameters with webcam capture enabled show a live camera preview in place of the usual thumbnail once you grant camera access; a camera button captures a still and uploads it, replacing the preview with the captured image. A capture button lets you retake it. Audio parameters with microphone capture enabled show a record button next to the waveform. Click it to start recording — it turns into a running timer — and click again to stop; the recording is encoded and uploaded automatically. A trash icon clears a captured recording so you can record again. Both require you to grant the browser camera or microphone permission; if access is denied, the component shows an error message instead of the capture controls. ## Viewing 3D models 3D model parameters open in an interactive viewer: drag to orbit, scroll to zoom, and right-click drag to pan. Supported formats are glTF/GLB (including Draco-compressed meshes), OBJ, FBX, DAE, 3DS, STL, and PLY; USDZ files load only if they're the plain-text USDA variant — binary USDC files exported by tools like Rodin or Apple QuickLook won't preview, though the file itself is still saved and downloadable. Formats that don't carry their own materials (STL, PLY, plain OBJ) get a neutral default material so they still read as solid geometry. Hover the viewer and click the expand icon to open the same model full-screen, with slow auto-rotation while it's open. Escape or the close button exits. ## Viewing Gaussian splats Splat parameters (`.splat` and `.ksplat` files) use the same orbit/zoom camera controls as the 3D model viewer, rendered through a Gaussian splatting renderer instead of a triangle mesh. Click the expand icon to open the splat full-screen; Escape or the close button exits. ## Loading a workflow from an image Griptape Nodes can embed a workflow's node graph inside the PNG it exports as a thumbnail. Drop one of those PNG files onto the canvas, and if the editor detects embedded workflow metadata, it asks what you want to do with it instead of just adding an image node: - **Add Image** — treat it like any other image and create a regular image-loading node from it. - **Add Nodes from Workflow** — add the nodes that generated the image to your current workflow, importing the referenced libraries, static files, and any workflows it depends on. Expand the node, library, and dependency lists in the dialog to preview what will be added before you commit. Drop several PNG files at once and you get a batch version of the same dialog: it tells you how many of the dropped images carry workflow metadata and applies your choice (add as images, or add nodes from workflow) to all of them at once. Images without embedded metadata are always added as plain image nodes, regardless of which option you pick. # Managing Models and Libraries Some nodes run AI models locally and need the model files on disk before they can run; others come from a library you haven't installed yet. Both are managed from the editor's **Manage** menu, in the **Model Management** and **Library Management** windows. This page covers both. For the concepts behind libraries — how installs are isolated from each other, Shared vs. Isolated execution, and what to do when something goes wrong — see [Libraries](https://docs.griptapenodes.com/guides/libraries/index.md). This page is about the two management windows themselves. ## Model Management Open **Manage → Model Management** to search for, download, and clean up models. Everything this window does is also available from the terminal via `gtn models` (search, download, list, delete, and download status) — see the [CLI reference](https://docs.griptapenodes.com/reference/command_line_interface/#models). ### Searching for a model Type into the search box to search Hugging Face for matching models as you type. Matching results appear in a dropdown below the box, showing each model's ID, description (if any), download count, likes, and a few of its tags; models you already have installed are marked **Installed**. Click a result to select it — the field then shows the model's size (once the engine has looked it up) and a link to open it on Hugging Face. If you already know the exact model ID, you can type it directly instead of searching and picking from the dropdown. ### Downloading a model With a model selected, click **Download**. The download starts in the background — you can keep working, close the window, or switch to Library Management, and the download keeps going. ### Tracking progress The **Downloads** section lists every active and recently finished download, each with a status badge: | Status | Meaning | | --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Downloading** | In progress. Shows a progress bar and how many GB are done out of the total, plus a percentage. | | **Completed** | Finished; the bar fills green. | | **Failed** | The download didn't finish. The error message is shown below the entry (including any Hugging Face access-request link, if that's the cause — see [Gated models](#gated-models-need-a-token) below). | Use the **All / Downloads / Models** chips above the list to narrow it to just active downloads or just installed models. ### Cancelling a download Click the trash icon on an entry that's still downloading to cancel it. A **Cancel Download** confirmation appears first — **Keep Downloading** backs out, **Cancel Download** stops it and removes the partial download record. ### Deleting a model The **Installed Models** section lists everything you've already downloaded, with its local path and size. Click the trash icon to remove one. A **Delete Model** dialog confirms first — deleting is permanent, removes the files from local storage, and any workflow that depends on that model will fail until you either re-download it or point the workflow at a different one. ### Gated models need a token Some models on Hugging Face are gated — you need to request access on the model's page, and you need a Hugging Face access token configured in Griptape Nodes before you can download them. If no token is configured, the Model Management window shows a banner at the top explaining how to fix that: create a token on Hugging Face, then add it as `HF_TOKEN` under **Settings → API Keys & Secrets**. The banner's link jumps you straight there. See [Setup for Nodes that use Hugging Face](https://docs.griptapenodes.com/guides/integrations/hugging_face/index.md) for the full walkthrough, including how to request access to a specific gated model. ## Library Management Open **Manage → Library Management** to install, update, and remove libraries. From the terminal, `gtn libraries download ` and `gtn libraries sync` cover installing and updating — see the [CLI reference](https://docs.griptapenodes.com/reference/command_line_interface/#libraries). The filter bar at the top lets you search installed libraries by name and narrow the list with the **All / Updates / Errors** chips — **Errors** is the fastest way to find a library that failed to load. Two icon buttons next to the chips let you **check all libraries for updates** or just **refresh** the list. Click any library's row to expand it and see its description, its Git remote and ref (for Git-installed libraries), and any problems the engine reported loading it. ### Installing a library Click **Add Library** to open the **Add Library** dialog. Paste a Git URL — for example a GitHub repository hosting a community library — and click **Install**. The editor first inspects the repository and shows you a confirmation with the library's name, description, version, and node count before actually cloning it, so you can back out if it's not what you expected. **Advanced Options** (the disclosure below the URL field) lets you set: - **Branch / Tag / Commit** — install a specific ref instead of the repository's default branch. - **Download Directory** — where the library gets cloned to, if you don't want the default libraries directory. Use the folder icon to browse for one. Not sure what to install? **Browse Community Libraries**, below the form, opens a curated directory of libraries you can copy a URL from and paste back into the field above. If the target directory already has something in it — a previous install of the same library, or unrelated files — you'll see a confirmation asking whether to overwrite it. Overwriting is destructive: it deletes what's there (including any uncommitted changes to a Git checkout) and replaces it with the new install. This is the same overwrite confirmation you'll see if you update a library that has local modifications. ### Updating a library Expand a library that has a Git remote and click **Check for Updates** to compare it against the remote. If an update is available, an **Update** button appears on its row (labeled with the target version when the engine knows it, e.g. **Update to 1.4.0**); click it to pull the update in. If the update is being held back — releases only become eligible once they've aged past a minimum, so you don't pull in something published minutes ago — the row shows how long until it's eligible instead of an Update button. You can also switch a library to a different branch, tag, or commit directly from its expanded row: click the current ref to edit it in place, then confirm. If a library's dependencies didn't install automatically, **Install Dependencies** on its expanded row retries just that step without re-cloning the library. ### Removing a library The Library Management window itself doesn't delete a library's files from disk — for that, plus toggling a library off without removing it, or choosing whether it runs Shared or Isolated, see [Toggling and removing libraries](https://docs.griptapenodes.com/guides/libraries/#toggling-and-removing-libraries) in the Libraries guide. # Agent # Agent The Agent is a built-in AI assistant with direct access to your workflow. Ask it questions, have it build nodes, run your workflow, or inspect what's on the canvas — all in plain conversation. ## Open the sidebar The sidebar lives on the right side of the Griptape Nodes canvas. Click the **chat bubble** tab (the first icon in the Sidebar Panels header) to open it. ## Choose a model At the top of the chat panel, click the **Model** dropdown to see all available models, grouped by provider: - The top group contains **Griptape Cloud** models (Claude, GPT, Gemini, DeepSeek, Llama, and more) — available by default, no setup needed - Any additional providers you've configured appear as their own labeled sections below - Use **Search models...** to filter by name - Click **Manage providers...** at the bottom to open [Providers](https://docs.griptapenodes.com/guides/agent/providers/index.md) ## Send a message Type in the **Write a message...** input at the bottom and press **Enter** or click the send button. You can also attach files using the paperclip icon. The agent streams its response in real time. When it takes an action — like reading your canvas or running your workflow — you'll see the step appear inline so you always know what it's doing. For example, you can ask it to: - **"What nodes are in my workflow?"** — it reads the canvas and tells you what's there - **"Add a Text Input node and connect it to the Agent"** — it creates and wires the nodes for you - **"Run my workflow and tell me what the output was"** — it executes your workflow and reports back - **"Change the prompt on my Agent node to..."** — it updates node parameters directly - **"Explain what this workflow does"** — it reads your workflow structure and gives you a plain-language summary How does the agent control my workflow? Griptape Nodes runs a built-in **MCP server** alongside the engine. MCP (Model Context Protocol) is an open standard that lets AI models call tools — in this case, tools for reading your canvas, creating nodes, running workflows, and more. The agent connects to this server automatically; there's nothing to configure. ## What's next - [Threads](https://docs.griptapenodes.com/guides/agent/threads/index.md) — how conversations are saved and where to find them - [Personalization](https://docs.griptapenodes.com/guides/agent/personalization/index.md) — customize the agent's tone and context - [Skills](https://docs.griptapenodes.com/guides/agent/skills/index.md) — give the agent extra domain knowledge - [External Tools (MCP Servers)](https://docs.griptapenodes.com/guides/agent/mcp_servers/index.md) — connect file access, web search, and other tools to the agent - [Providers](https://docs.griptapenodes.com/guides/agent/providers/index.md) — add Ollama, LM Studio, or a custom endpoint - [Using in Workflows](https://docs.griptapenodes.com/guides/agent/using_in_workflows/index.md) — the Agent node for automated flows # Threads Each conversation is a **thread**. Threads are named automatically with the date and time they were created. - Click **+ New** to start a fresh conversation - Previous threads are listed above the message input ## Where threads are stored Threads are saved to your local filesystem and persist across sessions. Each thread is stored as two files: | File | Contents | | ----------------------- | -------------------------------- | | `thread_{id}.json` | Full message history | | `thread_{id}.meta.json` | Title, timestamps, message count | The storage location follows the [XDG Base Directory](https://specifications.freedesktop.org/basedir-spec/latest/) convention. | Platform | Path | | ------------- | ---------------------------------------------------- | | macOS / Linux | `~/.local/share/griptape_nodes/threads/` | | Windows | `%USERPROFILE%\.local\share\griptape_nodes\threads\` | Griptape Nodes Desktop instead keeps threads in a `xdg_data_home/griptape_nodes/threads/` folder inside its own application data folder. Open **App Settings → App Data Locations** to find that folder on your machine. If a history file becomes corrupt, Griptape Nodes moves it aside automatically (renamed with a `.corrupt-` suffix) so your other threads are unaffected. # Using in Workflows The **Agent node** is how you bring AI into a workflow. Unlike the chat sidebar — which is interactive and conversational — the Agent node runs as part of an automated sequence, taking inputs from other nodes and passing outputs downstream. ## Key parameters - **provider** — the AI provider to use (matches the providers you configure in [Providers](https://docs.griptapenodes.com/guides/agent/providers/index.md)) - **prompt model** — the specific model from that provider - **rulesets** — connect a Ruleset node or TextInput to shape the agent's behavior; skills are not supported in workflows - **tools** — capabilities to give the agent - **output_schema** — optionally constrain the output to a specific JSON structure ## Full reference See the [Agent node reference](https://docs.griptapenodes.com/nodes/agents/create_agent/index.md) for the complete parameter list, output schema examples, and common issues. # Personalization You can customize how the agent talks to you and what context it has. Open **Settings → Agent Settings** and scroll down to the **Personalization** section. | Field | What it does | | --------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | | **Address me as** | The name or nickname the agent uses when addressing you | | **Tone** | The communication style (e.g., Concise, Formal, Casual) | | **My role** | Your job title or area of expertise — gives the agent context about your background | | **About me** | Free-text interests, background, or any context you want the agent to know | | **Additional instructions** | Extra rules appended to every response — use this to set a persona, define output format, or add domain-specific constraints | Together these fields build the agent's system prompt. The more context you provide, the more tailored the responses. # Skills Skills are markdown files that give the agent extra instructions or domain knowledge — loaded automatically at startup and reloaded on every run, so edits take effect without restarting the engine. Griptape Nodes ships with a built-in skill for building and running workflows. You can add your own alongside it. ## Where to put skills Create a `.agents/skills/` folder inside your workspace directory and drop skill files in there: ``` / └── .agents/ └── skills/ ├── my-skill.md └── another-skill.md ``` The default workspace directory is `GriptapeNodes/` inside wherever you launched the engine. You can find the exact path in **Settings → File System → Workspace Directory**. ## Skill file format Each skill is a markdown file with a YAML frontmatter block: ``` --- name: my-skill-name description: What this skill does and when the agent should use it. --- # Skill title Instructions, reference material, or domain knowledge the agent should apply when this skill is relevant. Write in plain English — the agent reads this as part of its context. ``` The `name` and `description` fields tell the agent what the skill is for and when to use it. The body can be as long or short as you need — code snippets, step-by-step instructions, reference tables, etc. ## Example: a house style guide ``` --- name: writing-style description: Apply our house style when drafting or editing text. --- # Writing Style Guide - Use sentence case for headings, not title case. - Prefer active voice. - Avoid jargon; explain technical terms on first use. - Maximum sentence length: 25 words. ``` ## Hot reload Tip Skills are picked up automatically — no engine restart needed. Drop a new `.md` file into `.agents/skills/`, and the very next message you send will use it. ## Related - [Agent Skills documentation](https://agentskills.io/home) — full reference for the skills format and capabilities # MCP Servers The **MCP servers** dropdown lets you attach one or more MCP servers to the agent for a conversation. This gives the agent access to external tools — files, web search, Blender, Maya, and more — depending on which servers you have configured. See [MCP Integration](https://docs.griptapenodes.com/guides/mcp/index.md) for how to set up MCP servers. # AI Providers Griptape Nodes supports multiple AI providers for the [Agent](https://docs.griptapenodes.com/guides/agent/index.md) and the **Agent node** in workflows. Griptape Cloud is provided by default. You can add local providers or any OpenAI-compatible endpoint as additional options. ## Available Providers | Provider | Type | API Key Required | | --------------------------------------------------------------------------------------------------- | ---------------------- | ---------------- | | [Griptape Cloud](https://docs.griptapenodes.com/guides/agent/providers/griptape_cloud/index.md) | Hosted proxy (default) | Griptape API key | | [Ollama](https://docs.griptapenodes.com/guides/agent/providers/ollama/index.md) | Local | No | | [LM Studio](https://docs.griptapenodes.com/guides/agent/providers/lm_studio/index.md) | Local | No | | [Custom (OpenAI-compatible)](https://docs.griptapenodes.com/guides/agent/providers/custom/index.md) | Hosted or local | Yes | ## Adding a Provider Open **Settings → Agent Settings**. In the **AI Providers** section, click **+ Add Provider**. Select the provider type you want to add and follow the configuration steps on its page (linked in the table above). The wizard walks you through three steps: choose type → configure → test. ## Managing Providers Each provider (other than Griptape Cloud) has three controls: | Control | Action | | -------------- | ------------------------------------------------------------------------- | | Toggle | Enable or disable. Disabled providers don't appear in the model dropdown. | | Edit (pencil) | Update name, base URL, API key, or default model | | Delete (trash) | Remove the provider | ## Using a Provider in the Chat Sidebar Once a provider is enabled, its models appear in the **Model** dropdown in the Agent, grouped by provider name. Click any model to switch to it immediately. See [Agent Sidebar](https://docs.griptapenodes.com/guides/agent/index.md) for more on the chat interface. ## Using a Provider in a Workflow The **Agent node** has a `provider` parameter. Set it to any enabled provider to use it for that node's AI calls. The `prompt model` parameter lets you pick the specific model. This lets different nodes in the same workflow use different providers. # Griptape Cloud Griptape Cloud is the default AI provider in Griptape Nodes. It routes requests through Griptape's hosted proxy, giving you access to a wide catalog of models under a single Griptape API key — no per-provider accounts or keys needed. ## Setup No setup is required. If you installed Griptape Nodes with your Griptape Cloud account, this provider is ready to use immediately. ## Available Models Griptape Cloud exposes models from multiple providers: - **Anthropic** — Claude Opus, Sonnet, Haiku - **Google** — Gemini Pro and Flash variants - **OpenAI** — GPT and o-series models - **Meta** — Llama models - **DeepSeek** — DeepSeek V3 and R1 The full list is visible in the **Model** dropdown in the [Agent](https://docs.griptapenodes.com/guides/agent/index.md). ## Related - [AI Providers overview](https://docs.griptapenodes.com/guides/agent/providers/index.md) - [Agent Sidebar](https://docs.griptapenodes.com/guides/agent/index.md) # Ollama (local) [Ollama](https://ollama.com) lets you run AI models locally on your own machine. No API key is required and no data leaves your computer. Griptape Nodes dynamically discovers which models you have installed. ## Prerequisites 1. Download and install Ollama from [ollama.com](https://ollama.com) 1. Pull at least one model. For example: ``` ollama pull llama3.2 ``` 1. Verify Ollama is running and the model is available: ``` ollama list ``` Ollama must be running whenever you want to use it in Griptape Nodes. ## Add the Provider 1. Open **Settings → Agent Settings** 1. Click **+ Add Provider** 1. Select **Ollama (local)** Fill in the configuration: - **Name** — leave as `Ollama` or enter a custom label - **Base URL** — defaults to `http://localhost:11434/v1`; click the refresh button to test the connection - When Ollama is running you'll see **Connected — N models available** in green - **Model (optional)** — choose a default model, or leave blank to select at chat time Click **Create Provider**. ## Test After creation the wizard shows a confirmation screen. Select a model, type a test message, and verify you get a response before clicking **Done**. ## Choosing Models Models you've pulled with `ollama pull` appear automatically in: - The **Model** dropdown in the [Agent](https://docs.griptapenodes.com/guides/agent/index.md), grouped under the Ollama provider name - The `prompt model` parameter on the **Agent node** when `provider` is set to Ollama To add more models, run `ollama pull ` in a terminal and they'll appear immediately. ## Related - [AI Providers overview](https://docs.griptapenodes.com/guides/agent/providers/index.md) - [LM Studio](https://docs.griptapenodes.com/guides/agent/providers/lm_studio/index.md) — another local provider option - [Local Models with Agents (MCP approach)](https://docs.griptapenodes.com/guides/mcp/advanced_local_models/index.md) — an alternative pattern using Ollama via MCP nodes in a workflow # LM Studio (local) [LM Studio](https://lmstudio.ai) is a desktop app for downloading and running AI models locally. No API key is required and no data leaves your computer. Griptape Nodes dynamically discovers which models you have loaded. ## Prerequisites 1. Download and install LM Studio from [lmstudio.ai](https://lmstudio.ai) 1. Inside LM Studio, download at least one model using the model browser 1. Start the **Local Server** inside LM Studio — Griptape Nodes connects to this server, so it must be running whenever you want to use LM Studio as a provider ## Add the Provider 1. Open **Settings → Agent Settings** 1. Click **+ Add Provider** 1. Select **LM Studio (local)** Fill in the configuration: - **Name** — leave as `LM Studio` or enter a custom label - **Base URL** — defaults to `http://localhost:1234/v1`; click the refresh button to test the connection - When the LM Studio server is running you'll see **Connected — N models available** in green - **Model (optional)** — choose a default model, or leave blank to select at chat time Click **Create Provider**. ## Test After creation the wizard shows a confirmation screen. Select a model, type a test message, and verify you get a response before clicking **Done**. ## Choosing Models Models loaded in LM Studio appear automatically in: - The **Model** dropdown in the [Agent](https://docs.griptapenodes.com/guides/agent/index.md), grouped under the LM Studio provider name - The `prompt model` parameter on the **Agent node** when `provider` is set to LM Studio To add more models, download them inside LM Studio and they'll appear immediately. ## Related - [AI Providers overview](https://docs.griptapenodes.com/guides/agent/providers/index.md) - [Ollama](https://docs.griptapenodes.com/guides/agent/providers/ollama/index.md) — another local provider option # Custom (OpenAI-compatible) Use this provider type to connect Griptape Nodes to any endpoint that implements the OpenAI Chat Completions API — whether that's a third-party service, a self-hosted model server, or a corporate proxy. ## Prerequisites You'll need: - The **base URL** of the endpoint (e.g., `https://api.openai.com/v1`) - An **API key** for that endpoint - The **model name** to use (e.g., `google/gemma-4-e4b`) — required because custom endpoints don't support automatic model discovery ## Add the Provider 1. Open **Settings → Agent Settings** 1. Click **+ Add Provider** 1. Select **Custom (OpenAI-compatible)** Fill in the configuration: - **Name** — a label to identify this provider in the model dropdown - **Icon** — choose an icon to represent this provider (optional) - **Base URL** — the root URL of the endpoint - **API Key Secret** — select an existing secret, or click **+** to create one: - **Secret Name** — the environment variable name (e.g., `OPENAI_API_KEY`) - **Value** — the actual key value After saving, select the new secret from the dropdown. - **Model** — enter the model name to use with this endpoint Click **Create Provider**. ## Test After creation the wizard shows a confirmation screen. Verify you get a response before clicking **Done**. ## Related - [AI Providers overview](https://docs.griptapenodes.com/guides/agent/providers/index.md) - [Ollama](https://docs.griptapenodes.com/guides/agent/providers/ollama/index.md) — local provider, no API key needed - [LM Studio](https://docs.griptapenodes.com/guides/agent/providers/lm_studio/index.md) — local provider, no API key needed # Configuration and libraries # Engine Configuration When running Griptape Nodes engine on your own machine, you are provided with utilities to manage configuration settings. Understanding how the configuration settings are loaded is important as you build out and manage more complicated projects or share projects with your team members. > During installation, `gtn init` was run automatically. > > Looking for a specific setting? The [Configuration Reference](https://docs.griptapenodes.com/reference/configuration_reference/index.md) lists every setting with its type, default, environment variable, and description, grouped by category. ## Editing Settings in the Editor The recommended way to change settings is the **Configuration Editor** built into the editor: 1. Open the **Settings** menu in the editor's header and choose **All Settings**. The same submenu also has direct entries (such as **Engine Settings** or **Library Settings**) that open the Configuration Editor on that category. 1. Pick a category in the left sidebar — **Editor Settings**, **Engine Settings**, **File System**, **Libraries**, **Library Settings**, **MCP Servers**, **API Keys & Secrets** — or use the search box at the top to filter settings by name. 1. Change the value. The Configuration Editor writes it to your configuration files for you. Some settings only take effect after the engine restarts — `static_server_base_url` is one example (see [Static File Server Configuration](#static-file-server-configuration)). The rest of this page describes what's underneath: the files and environment variables the Configuration Editor writes to, and how they're merged. Edit those directly only when you're automating setup, running headless, or sharing configuration with a team. ## Configuration Loading Griptape Nodes employs a specific search order to load settings from environment variables and configuration files. Understanding this process is key to managing your setup. 1. **Environment Variables (`.env`)** Environment variables are used to securely store sensitive secrets like API keys. Griptape Nodes automatically loads env files, making these secrets available to the application. - The primary `.env` file is loaded from the system-wide user configuration directory: `xdg_config_home() / "griptape_nodes" / ".env"` (commonly `~/.config/griptape_nodes/.env`). - This file is intended for secrets like `GT_CLOUD_API_KEY`, `OPENAI_API_KEY`. > You shouldn't interact with these files directly. Griptape Nodes manages your environment variables through its Settings dialog. 1. **Configuration Files (`griptape_nodes_config.json`)** Configuration files hold information important for Griptape Nodes operation, such as where to locate Node Libraries, as well as user preferences to customize the Griptape Nodes experience. - If no configuration files are found, Griptape Nodes will run using built-in default values. - Configuration files are always JSON (`griptape_nodes_config.json`). Settings are loaded from up to three such files plus a runtime override and environment variables, and merged in priority order: - **Load Order (lower numbers are loaded first; higher numbers override):** 1. **Built-in defaults** — values baked into the application. 1. **User config** — `~/.config/griptape_nodes/griptape_nodes_config.json`. Global settings for this machine. 1. **Project-adjacent config** — `/griptape_nodes_config.json`. Loaded when a project is set as active. Use this to distribute shared defaults alongside a project file. 1. **Workspace config** — `/griptape_nodes_config.json`. Loaded after the workspace is resolved. Use this for per-user overrides that take precedence over the shared project config. When the workspace directory is the same as the project directory, this file is the same as the project-adjacent config and is not loaded twice. 1. **Per-project workspace override** — When the active project's path matches a key in the `project_workspaces` mapping (in your user config), that workspace directory is applied. This sets only `workspace_directory`, overriding any value from the config files above. See [Workspace](https://docs.griptapenodes.com/guides/projects/workspace/#per-project-workspace-overrides) for details. 1. **Environment variables** — `GTN_CONFIG_*` prefix (highest priority). See below. - **Override Priority:** Settings in files loaded later override settings from files loaded earlier. 1. **Defaults and Merging** Griptape Nodes comes with built-in default settings for various options, including the default workspace directory. These defaults are used unless overridden by settings loaded from discovered configuration files. - Settings loaded from the first found configuration file override the built-in default values. - If no configuration file is found in any of the search paths, the application uses only the built-in defaults. - One key default is `workspace_directory`, which defaults to `/GriptapeNodes` if not specified in a loaded configuration file. 1. **Runtime Management (`ConfigManager`)** After initial settings are loaded, the `ConfigManager` handles runtime operations using the final resolved configuration, particularly the workspace directory. It's responsible for saving user-specific changes, like registered workflows, back to a configuration file within the workspace. - Once settings are loaded, the `ConfigManager` uses the final resolved `workspace_directory`. - Modifications made at runtime (e.g., registering custom workflows) are typically saved by the `ConfigManager` into a `griptape_nodes_config.json` file located within this resolved `workspace_directory`. ## Loading Examples Here are a few scenarios to illustrate how configuration files are located and loaded: **Scenario 1: Using Defaults** - You run `gtn init` and accept the default settings. - `gtn init` creates `~/.config/griptape_nodes/griptape_nodes_config.json` and `~/.config/griptape_nodes/.env`. It sets `workspace_directory` inside the `.json` file to point to `/GriptapeNodes`. - You later run `gtn` from `/home/user/my_project/`. - **File Structure:** ``` /home/user/ my_project/ <-- CWD when running 'gtn' GriptapeNodes/ <-- Default Workspace (may contain runtime saved config) my_flow.graph.json .config/ griptape_nodes/ .env # Loaded for environment variables griptape_nodes_config.json # Contains workspace_directory = /home/user/my_project/GriptapeNodes ``` - **Loading Process:** 1. Loads built-in defaults. 1. Loads `~/.config/griptape_nodes/griptape_nodes_config.json` (Found!), merging it over the defaults. 1. **Result:** The `workspace_directory` is set to `/home/user/my_project/GriptapeNodes`. Subsequent runtime changes managed by `ConfigManager` will be saved to `/home/user/my_project/GriptapeNodes/griptape_nodes_config.json`. **Scenario 2: Custom Workspace** - You run `gtn init --workspace-directory /data/gtn_work`. - `gtn init` creates `~/.config/griptape_nodes/griptape_nodes_config.json` (setting `workspace_directory = "/data/gtn_work"`) and `~/.config/griptape_nodes/.env`. - You might manually create `/data/gtn_work/griptape_nodes_config.json` to store workspace-specific settings. - You run `gtn` from `/home/user/some_dir/`. - **File Structure:** ``` /home/user/ some_dir/ <-- CWD when running 'gtn' .config/ griptape_nodes/ .env # Loaded for environment variables griptape_nodes_config.json # Contains workspace_directory = /data/gtn_work /data/ gtn_work/ <-- Custom Workspace griptape_nodes_config.json # Workspace config (also where runtime changes are saved) project_flows/ ``` - **Loading Process:** 1. Loads built-in defaults. 1. Loads `~/.config/griptape_nodes/griptape_nodes_config.json` (Found!), merging it over the defaults. This sets `workspace_directory` to `/data/gtn_work`. 1. Resolves the workspace and loads `/data/gtn_work/griptape_nodes_config.json` as the workspace config, merging it over the user config. 1. **Result:** The `workspace_directory` is `/data/gtn_work`. Settings in the workspace config override the user config for this workspace. Runtime changes are saved back to `/data/gtn_work/griptape_nodes_config.json`. **Scenario 3: No User Config (Built-in Defaults)** - You haven't run `gtn init`, or you deleted `~/.config/griptape_nodes/`. - You run `gtn` from `/home/user/my_project/` with no project active. - **File Structure:** ``` /home/user/ my_project/ <-- CWD when running 'gtn' GriptapeNodes/ <-- Default workspace location my_flow.graph.json ``` - **Loading Process:** 1. Loads built-in defaults. 1. Checks `~/.config/griptape_nodes/griptape_nodes_config.json` (Assume not found). 1. No project is active, so no project-adjacent config is loaded. 1. **Result:** The application runs on built-in defaults. `workspace_directory` falls back to its default of `/GriptapeNodes` = `/home/user/my_project/GriptapeNodes`, and runtime changes are saved to a `griptape_nodes_config.json` created inside that workspace. ## Environment Variable Overrides A top-level configuration value can be set or overridden using an environment variable with the `GTN_CONFIG_` prefix. The key is the config setting name in uppercase: ``` GTN_CONFIG_= ``` Environment variable overrides have the **highest priority** — they win over user config files, project-adjacent config files, the per-project workspace override, and built-in defaults. Examples: | Setting | Environment variable | | --------------------- | -------------------------------- | | `workspace_directory` | `GTN_CONFIG_WORKSPACE_DIRECTORY` | | `libraries_directory` | `GTN_CONFIG_LIBRARIES_DIRECTORY` | | `project_file` | `GTN_CONFIG_PROJECT_FILE` | | `log_level` | `GTN_CONFIG_LOG_LEVEL` | | `storage_backend` | `GTN_CONFIG_STORAGE_BACKEND` | This is useful for scripted environments, containers, and CI/CD pipelines where you want to inject configuration without modifying any config files: ``` GTN_CONFIG_PROJECT_FILE=/shared/studio-project.yml gtn ``` > **Only top-level settings can be set this way.** `GTN_CONFIG_*` variables map to a single top-level config key; they cannot reach nested settings (anything under `app_events`, `worker`, or `agent`, such as `libraries_to_register`). To change a nested setting, edit a `griptape_nodes_config.json` file. The [Configuration Reference](https://docs.griptapenodes.com/reference/configuration_reference/index.md) marks which settings have an environment variable. ### Recursive Discovery Depth (`discovery_max_depth`) When `projects_to_register`, `libraries_to_register`, or `workflows_to_register` points at a directory, the engine recursively scans it for the relevant files on startup (project files, library manifests, and workflow files respectively). The scan is depth-bounded so a pathologically deep tree (or a symlink loop) can't stall the boot sequence. The `discovery_max_depth` setting controls that cap; its default is **5** directory levels below the registered directory, which comfortably covers normal layouts. Being a normal setting, it can be set in any config file or overridden with the `GTN_CONFIG_DISCOVERY_MAX_DEPTH` environment variable: ``` GTN_CONFIG_DISCOVERY_MAX_DEPTH=20 gtn # scan deeper-nested layouts ``` A value of `0` scans only the top-level directory (no subdirectories). ## Workspace Directory During `gtn init`, you specify a Workspace Directory. This is the root for your projects, saved flows, and potentially project-specific settings. While `gtn init` might suggest `/GriptapeNodes` as a default, you can choose any location. Griptape Nodes uses the exact path you provide, which is then stored in the system `griptape_nodes_config.json`. It does **not** automatically search within a hardcoded `GriptapeNodes` subdirectory; it relies solely on the configured path. ### Separating Libraries from the Workspace By default, downloaded libraries live in a `libraries/` folder **inside** the workspace, because `libraries_directory` is a relative path resolved against `workspace_directory`. If your workspace is on a slow or remote drive (a network share, a mounted volume), keeping libraries there can degrade performance, since the engine reads library code from disk frequently. You can point `libraries_directory` at an **absolute** path on fast local storage while keeping the workspace (projects, workflows, generated assets) on the remote drive. When `libraries_directory` is absolute, it is used as-is and the workspace location is ignored for libraries: ``` { "workspace_directory": "/Volumes/team-share/GriptapeNodes", "libraries_directory": "/Users/me/.griptape-nodes-libraries" } ``` The same equivalently set via an environment variable: ``` GTN_CONFIG_LIBRARIES_DIRECTORY=/Users/me/.griptape-nodes-libraries gtn ``` The same relative-vs-absolute rule applies to `sandbox_library_directory` and `static_files_directory`, so you can independently relocate any of them onto local storage while the workspace stays remote. `libraries_directory` here is the machine-wide default. An individual project can override it with its own [`libraries_dir`](https://docs.griptapenodes.com/guides/projects/projects/#libraries-directory) field in the project file, which takes precedence over this config value and travels with the project (and is inherited by child projects). Use the config setting to relocate libraries for the whole engine; use the project field when a specific project or project tree needs its own shared library location. ## Variables the Engine Publishes Every variable above is one **you** set. The engine also publishes one variable **for you to read**, into its own environment at startup: | Variable | Value | | ---------------------------- | ----------------------------------------------------------------- | | `GTN_DEFAULT_LIBRARIES_ROOT` | Absolute path to the directory libraries install under by default | You do not set it. Its purpose is to let a project file point at wherever this engine keeps its libraries, without hardcoding a path per machine: ``` libraries_dir: "${GTN_DEFAULT_LIBRARIES_ROOT}/shared" ``` That resolves to the same location `griptape-nodes init` installs the standard library into, so a project can share the existing library tree instead of downloading its own copy. Because the value is always absolute, it is safe to use in `libraries_dir`, which anchors *relative* values to the project file's own directory rather than to the workspace. The published value reflects the settings that describe where this engine keeps libraries, including `libraries_directory` and `GTN_CONFIG_LIBRARIES_DIRECTORY`. It deliberately does **not** follow a project's own `libraries_dir`, because that is the field reading it. It is also computed once at startup, so a project-adjacent or workspace `griptape_nodes_config.json` that re-points `libraries_directory` when that project is opened is not reflected in it. Warning A project file that names a variable nothing has set is **refused at load time**, and the project will not open. So a project file using `${GTN_DEFAULT_LIBRARIES_ROOT}` requires an engine version that publishes it. ## Static File Server Configuration When running Griptape Nodes, a local static file server hosts media assets (images, videos, audio) generated by your workflows. The `static_server_base_url` setting controls what base URL is used when generating links to these files. By default, it uses `http://localhost:8124`, but you can override this when using tunnels, proxies, or deploying in containers. ### When to Override This Setting You'll need to configure `static_server_base_url` in these scenarios: - **Tunneling Services**: Using ngrok, cloudflare tunnels, or similar services to expose your local server - **Docker/Kubernetes**: Running in containers where the internal address differs from the external access point - **Reverse Proxies**: Running behind nginx, Apache, or other reverse proxies - **Remote Development**: Working on a remote machine and accessing the UI from your local browser - **Team Collaboration**: Sharing your running instance with team members who need to access generated media ### How to Configure The static server base URL is configured using the `static_server_base_url` setting in the Griptape Nodes UI Settings dialog. If not explicitly set, it defaults to `http://localhost:8124` (or respects `STATIC_SERVER_HOST` and `STATIC_SERVER_PORT` environment variables if they are set). After updating this setting, you'll need to restart the Griptape Nodes engine for the changes to take effect. ### Example Scenarios **Scenario 1: Local Development with ngrok** You're testing webhook integrations that need to access generated media files. 1. Start ngrok tunnel: `ngrok http 8124` 1. Copy the generated URL (e.g., `https://abc123.ngrok.app`) 1. Open Griptape Nodes UI Settings dialog 1. Update the `static_server_base_url` setting with your ngrok URL 1. Restart the Griptape Nodes engine: `gtn` Now when workflows generate media: - Local access: Works via the tunnel URL - External services: Can fetch media via the ngrok URL - CORS: Automatically configured for the tunnel URL # Libraries A **library** is a bundle of nodes you can use in the editor. Some ship with the engine, some you install from a Git URL, some you write yourself. This page is for the artist installing and using libraries — not the developer authoring them. (Library authors: see the [Custom Nodes guide](https://docs.griptapenodes.com/development/custom_nodes/index.md) and [Node Isolation with Workers](https://docs.griptapenodes.com/development/custom_nodes/node_isolation_with_workers/index.md).) If you're worried about installing two libraries that conflict, jump to [Coexistence guarantees](#coexistence-guarantees) — the short answer is that two libraries' Python dependencies cannot break each other, but two libraries with the same node name can be ambiguous and the engine will tell you about it. ## What you start with The engine supports a **Sandbox Library** — a scratchpad library for quickly developing your own custom nodes without authoring a full library. It isn't there until you set one up: under **Settings → Library → Sandbox Settings**, point **Sandbox Library Directory** at a folder that exists on disk. Once it does, the engine picks up `.py` node files from that directory, and what you see in the editor's Sandbox category depends on what's actually in it. During `gtn init`, you're offered the **Advanced Media Library** (diffusion, image generation, video). You can register it then or re-run `gtn init` later to add it. See the [FAQ](https://docs.griptapenodes.com/faq/#how-do-i-install-the-advanced-media-library-after-initial-setup) for that path. Other libraries — first-party or community — you install yourself through the editor. ## Installing a library (in the editor) The editor's **Libraries** panel is the primary install surface. Open it from the header's **Manage** menu → **Library Management**. Click **Add Library** (top right) to open the **Add Library** modal. Paste a Git URL (for example a GitHub repository hosting a community library) and click **Install**. The editor clones the repo, reads the library's `griptape_nodes_library.json` manifest, installs its dependencies, and registers it. **Advanced Options** in the modal lets you pick a specific branch, tag, or commit instead of the repository default. Not sure what to install? The **Browse Community Libraries** button at the bottom of the modal opens a curated list of libraries you can install. After a successful install, the new library appears in the panel's library list. Each entry shows: - The library's name and version. - The number of nodes it provides. - An **Open** action that opens the library's directory in your file manager. - An **Advanced** disclosure that shows the library's Git remote, ref (branch or tag), and current commit. If you ever need to report a bug, the commit shown there is the precise version to cite. You can filter the list by **All**, **Updates**, or **Errors** using the chips above it. **Errors** is your first stop when something is wrong with one of your libraries — it surfaces install failures, dependency-install failures, and load-time failures together. ## Updating libraries In the same **Libraries** panel, the icon buttons next to the filter chips let you: - **Check for updates** — scan all installed libraries for new versions. Anything with an update available shows up under the **Updates** filter. - **Refresh** — re-read the library list (helpful if you just installed something and want to confirm it took). For ambient update awareness, **Configuration Editor → Libraries** controls how aggressively the engine checks for library updates on its own. **Update Notifications** options: - **Enable sidebar notifications** — show a badge on the Libraries tab and per-library buttons when an update is available. - **Notification color / animation** — visual style for the badge. - **Check on startup** — scan for updates each time the engine loads. - **Check periodically** — repeating schedule (Never, Hourly, etc.). - **Check Now** — trigger an immediate scan. You don't have to configure any of this; the defaults are fine for most artists. ## Toggling and removing libraries The same **Configuration Editor → Libraries** view also shows **Library Registration → Libraries To Register**. Each entry in that list is a library the engine loads at startup. Three controls per entry: - The toggle (left) — flip off to keep the library on disk but stop loading it on engine start. - The **Shared / Isolated** dropdown (middle) — choose where the library runs (see below). - The trash icon (right) — remove the entry entirely. The clone on disk stays put; delete its directory manually if you want the disk space back. ### Shared vs. Isolated The dropdown picks the process a library runs in: - **Shared** — the library runs inside the main engine process, alongside the other shared libraries. - **Isolated** — the library runs in its own separate process, so its Python dependencies are walled off from every other library and a crash in it can't take the rest of the engine down. The dropdown shows the mode the engine will actually use: the library author's suggested mode, unless you override it here. Pick **Isolated** for a heavy library you want walled off, or **Shared** to keep it in-process. Some libraries are marked by their author as incompatible with isolation; for those the dropdown is **locked to Shared**. The dropdown only appears when your engine version supports it (0.86.0 and later). Changes take effect on the next library refresh. The **Add Library** button below lets you point the engine at a `griptape_nodes_library.json` you already have on disk (a library you cloned manually, or a library you're developing locally). ## Coexistence guarantees The point of installing two libraries side-by-side is that they can't break each other. Three layers control how well that holds: ### Python dependency isolation Every registered library gets its own **virtual environment** — an isolated set of Python packages that doesn't share state with the engine's own packages or with any other library's. Library A can pin `torch==2.4.1`; library B can pin `torch==2.0.0`. Both are installed into separate `.venv` directories, and each library uses its own when its nodes run. This holds whether a library runs **Shared** or **Isolated** (see [Process isolation](#process-isolation-the-isolated-mode)): the `.venv` lives on disk next to the library's manifest either way. The difference is only which process loads those packages — the main engine process for Shared libraries, the library's own process for Isolated ones. From the artist's perspective the dependency isolation is the same. **You can install incompatibly-pinned libraries together without pip resolution conflicts.** This is the most important guarantee on this page. ### Process isolation: the Isolated mode Running a library **Isolated** (in its own dedicated process, instead of inside the engine's main process) gives you: - **Fault tolerance.** If the library crashes, only that library goes down — the rest of the engine keeps running. - **Resource isolation.** Anything the library loads into memory (model weights, GPU memory, background threads) lives in the library's own process and can't degrade other libraries. Heavy ML libraries (diffusion, transformers, custom CUDA stacks) benefit most; lightweight libraries (simple HTTP / data nodes) usually run fine Shared. You control this per library with the **Shared / Isolated** dropdown described in [Toggling and removing libraries](#shared-vs-isolated). The library author sets the suggested starting mode and whether the library is allowed to run Isolated at all; your dropdown choice overrides the author's suggestion for any library that permits it. Library authors declare isolation compatibility and a suggested mode in their `griptape_nodes_library.json` (the `worker_mode_compatibility` and `suggested_worker_mode` declarations) — see [Node Isolation with Workers](https://docs.griptapenodes.com/development/custom_nodes/node_isolation_with_workers/index.md) for the schema. As an artist, the Shared / Isolated dropdown is the only surface you need. ### Node-name collisions: not solved If two libraries register a node class with the same name (e.g. both ship a `MyImageNode`), the engine accepts both. **The engine does not warn you at install time.** When you create that node: - If your workflow names the library explicitly, it works. - If it doesn't, the engine raises an error listing both libraries so you can disambiguate. This is the one coexistence concern the engine doesn't solve for you. If you suspect a collision, the safest fix is to remove the library you don't want from the **Libraries To Register** list. ## When something goes wrong ### "I installed the library but I don't see its nodes" Open the **Libraries** panel and switch the filter to **Errors**. That's the consolidated view of every library that failed to install, failed to install dependencies, or failed to load. Click into the offending library for the specific error message. Common causes: - The library pins a wheel (a pre-built Python package) that doesn't exist for your Python version or platform — for example a `torch` wheel for an unsupported CUDA version. - Your network blocked the install (corporate proxy, or no internet during the install step). - Disk full (the engine reports this explicitly). Fix the underlying issue, then re-trigger the install (re-paste the URL in the **Add Library** modal, or use the CLI alternative below with `--overwrite`). ### "A node looks broken or red in the editor" The engine couldn't construct that node — usually because its library failed to load. The editor swaps in a placeholder so your workflow file isn't corrupted. Check the **Errors** filter in the **Libraries** panel for the underlying load error; once you fix it, reopening the workflow uses the real node again. ### Looking for the raw error text The terminal window where the engine is running (the same window you launched the engine from) carries the verbose error log, including stack traces. Errors from libraries running Isolated appear there with a `Worker-` prefix. ## CLI alternatives If you'd rather use the command line — for automation, headless engines, or just preference — the editor's library actions have `gtn` equivalents: | Editor action | CLI equivalent | | ----------------------------------- | ---------------------------------- | | Add Library → Install | `gtn libraries download ` | | Check for updates → install pending | `gtn libraries sync` | | Sync over local edits | `gtn libraries sync --overwrite` | | Re-register Advanced Media Library | `gtn init`, answer `y` | See [Command Line Interface](https://docs.griptapenodes.com/reference/command_line_interface/index.md) for the full reference. ## Where libraries are stored on disk - **Config**: `~/.config/griptape_nodes/griptape_nodes_config.json` (or the platform equivalent — see [Engine Configuration](https://docs.griptapenodes.com/guides/configuration/index.md)). The `app_events.on_app_initialization_complete.libraries_to_register` list inside it is what the editor's Libraries To Register list edits. - **Clones / venvs**: in the directory the editor cloned the library into; the library's `.venv` lives next to its `griptape_nodes_library.json`. - **Sandbox library**: the sandbox directory is configured separately in your settings; defaults vary by platform. To pin a project to specific library versions (so activating it provisions and, if needed, overwrites libraries to match), see [Pinning engine and library versions](https://docs.griptapenodes.com/guides/projects/version_pinning/index.md). For library authors: see [Custom Nodes](https://docs.griptapenodes.com/development/custom_nodes/index.md) and [Node Isolation with Workers](https://docs.griptapenodes.com/development/custom_nodes/node_isolation_with_workers/index.md). # Project system # Project The project system controls how files are organized, named, and saved when you work in Griptape Nodes. Every time a node saves an image, copies an uploaded file, or downloads a URL, the project system decides where that file goes and what it's called. ## Why it exists Without a project system, every file save operation would require a hard-coded path. The project system replaces hard-coded paths with named templates called **macros** and named file-saving scenarios called **situations**. This lets you customize your entire project's file layout in one place, and every node that saves files automatically follows that layout. ## How the pieces fit together ``` workspace/ ← workspace directory (your root) griptape-nodes-project.yml ← optional: your customizations my_workflow/ ← workflow directory inputs/ ← default inputs directory outputs/ ← default outputs directory temp/ ← default temp directory .griptape-nodes-previews/ ← default previews directory ``` The conceptual hierarchy is: ``` workspace └── project template ├── situations (where and how to save files in each scenario) ├── directories (logical name → relative path mappings) ├── variables (project-owned variables, usable in nodes and macros) └── environment (custom key-value pairs for macro composition) ``` **Workspace** is the root directory for all work in this project. It is configured in your settings. **Project template** is the configuration file that governs this project. It is loaded at startup — first the system defaults, then any [parent projects](https://docs.griptapenodes.com/guides/projects/projects/#parent-projects) declared via `parent_project_path`, and finally the overrides in this project's `griptape-nodes-project.yml`. The project template is what lets you control where files land and how they're named. **Situations** are named scenarios for when a file is read or written. Each has a macro template that determines the file path and a policy that determines what happens when a file already exists. **Directories** are logical name-to-path mappings. Each directory has a short name (like `outputs`) that you use as a variable in macros. That name maps to a real path on disk — by default `outputs` maps to an `outputs` subfolder. Directory paths can be relative or absolute, and can themselves contain macro variables or environment variable references. **Variables** are project-owned named values (strings or integers) that participate in `{VAR}` substitution in node parameters and macros. They can be marked writable so artists can change them at runtime, with changes saved back to the project file. **Environment** is a bag of custom key-value pairs that can be referenced in macros. **Macros** are the template strings used in situations and directories to generate concrete file paths. ## Pages in this section - [Managing Projects (GUI)](https://docs.griptapenodes.com/guides/projects/gui_guide/index.md) — create, view, edit, switch, and remove projects from the GUI - [Workspace](https://docs.griptapenodes.com/guides/projects/workspace/index.md) — the root working context and how relative paths resolve - [Projects](https://docs.griptapenodes.com/guides/projects/projects/index.md) — the project file format and the merge model - [Version Pinning](https://docs.griptapenodes.com/guides/projects/version_pinning/index.md) — pin a project to an engine version and to specific library versions - [Macros](https://docs.griptapenodes.com/guides/projects/macros/index.md) — template syntax reference for generating file paths - [Sequences](https://docs.griptapenodes.com/guides/projects/sequences/index.md) — image-sequence patterns and how missing frames are handled - [Directories](https://docs.griptapenodes.com/guides/projects/directories/index.md) — logical name-to-path mappings - [Situations](https://docs.griptapenodes.com/guides/projects/situations/index.md) — named file-saving scenarios with policies - [Environment & Builtin Variables](https://docs.griptapenodes.com/guides/projects/environment/index.md) — custom variables and system-provided values - [Project Variables](https://docs.griptapenodes.com/guides/projects/variables/index.md) — project-owned variables with types, permissions, and runtime writes - [File Extension Directories](https://docs.griptapenodes.com/guides/projects/file_extension_directories/index.md) — route files to folders by extension - [Customization Guide](https://docs.griptapenodes.com/guides/projects/customization/index.md) — practical examples for common customizations # Managing Projects in the GUI A **project** is a saved setup that tells Griptape Nodes where your files go and which libraries and engine version to use. Switching to a project changes that context for everything you do: where rendered images and downloads are saved, which workflows show up, and which node libraries are available. This page walks through every project task you can do from the GUI: switching between projects, creating new ones, viewing and editing them, and removing them. For the concepts behind projects (the file format, macros, version pinning), see the [Project overview](https://docs.griptapenodes.com/guides/projects/index.md). > Everything on this page happens inside the **Project Management** window. Open it from the top menu bar: **Manage → Project Management**. ## The Project Management window The Project Management window opens on the **project list**. Projects are shown as a tree: a child project appears nested under its parent. The project you're currently in is marked with an **Active** badge. Two buttons in the top-right corner add projects to this list: - **Register Template…**: point at a project file that already exists on disk and add it to the list. - **Create New Template…**: make a brand-new project from scratch. Click any project in the list to open its detail view, where you can review and edit it. ## Switching projects Switching is the most common thing you'll do. You can switch from the Project Management window (click a project, then **Activate**), but the quickest way is the **project picker** in the workflow chooser. The picker shows the project you're currently in. Open it to search the list and pick another; a checkmark marks the active one. When you choose a different project, Griptape Nodes asks you to confirm. The dialog shows which project you're leaving and which you're moving to. > **Switching reloads the engine.** Your active workflow is closed first (you'll be prompted to save if you have unsaved changes), and the workflow list refreshes to show the new project's workflows. After switching, open a workflow from the new project to keep working. If the project you're switching to pins specific library versions, a **provisioning preview** appears first so you can review (and approve) any libraries that will be installed or changed before the switch happens. A progress indicator reads **Switching to …**, and a **Switched to** confirmation appears when it's done. ## Creating a project Click **Create New Template…** to open the **New Project Template** dialog. For most projects you only need to fill in a name; everything else has a sensible default. The subtitle at the top reads "Creating a new project template inside *Default Project*." That highlighted name is the **location**: a picker for which project's folder the new project is saved into. It defaults to your active project (or the Default Project). Click it to choose a different one. Fill in the basics: - **Project Name**: the human-readable name shown everywhere in the GUI. This is the only required field. - **Description**: an optional note describing what the project is for. - **Location**: where the project file (`griptape-nodes-project.yml`) is written. By default this is linked to the chosen location and shown as a read-only path that ends in `your-project-name/griptape-nodes-project.yml`, so it updates automatically as you type the name. To set a path by hand, click the path (or the folder button to browse); a **Reset** link relinks it to the default. ### Advanced Settings Most artists never need to open this. Expand **Advanced Settings** to reveal: - **Project ID**: a unique, permanent identifier for the project. It is generated automatically from the name plus a short random suffix, and you can leave it as-is. Click it to type your own (lowercase letters, numbers, and dashes; it must be unique). Once a project is created, its ID does not change. - **Inherits settings from**: the parent project. A child project starts from its parent's setup and only stores the things you change. This is linked to the location by default, so choosing a location also sets the parent; pick **Default Project** here if you don't want inheritance. See [Projects](https://docs.griptapenodes.com/guides/projects/projects/#parent-projects) for how inheritance works. - **Workspace directory**: where the new project keeps its workflows and generated files. A new project defaults to `./` — its own folder — so it is self-contained. Change it to point elsewhere, or clear it to inherit the workspace from the parent project. (Shown only for current-schema projects; see [Schema versions](https://docs.griptapenodes.com/guides/projects/projects/#schema-versions).) Click **Create**. A progress panel walks through saving the project, registering it with the engine, activating it, and finishing up. Creating a project makes it the active project, so the engine reloads (the same reload that happens on any switch); if you have an unsaved workflow open, you'll be prompted to save first. > If the location or parent project pins specific library versions, the same [provisioning preview](#switching-projects) shown when switching appears here so you can review and approve those libraries before the new project activates. A **Project created** confirmation appears when it's ready. ## Viewing and editing a project Click a project in the list to open its detail view. The header shows the project's name, its file path (with a button to copy it), and badges for **Active** and **Unsaved changes**. Use the pencil icon next to the name to edit the **name** and **description** inline. The **Inherits from** dropdown sets (or clears) the parent project. The detail view also surfaces the project's editable sections: ### Workspace directory The **Workspace dir** field sets where this project's work is rooted: the folder that relative paths, outputs, and downloads resolve against. It is the highest-priority way to choose a workspace, overriding per-user and global settings. Leave it blank and the project uses the workspace the engine works out from your settings; when blank, the field shows that **calculated** path as grey placeholder text, so you can see where the project's work will land without setting anything. Type a path to pin the workspace explicitly, or use the folder button to browse for one. Like Directories, it has a per-platform toggle to give the workspace different paths on Linux, macOS, and Windows. For the full reference, see [Workspace directory](https://docs.griptapenodes.com/guides/projects/projects/#workspace-directory) and [Workspace](https://docs.griptapenodes.com/guides/projects/workspace/index.md). ### Libraries directory The **Libraries dir** field sets where this project installs and resolves its libraries, separate from the workspace. Use it so related projects can share one library install location instead of each re-downloading its own copy. Leave it blank and the project inherits its parent's libraries directory, falling back to a `libraries` folder inside the workspace when nothing is declared up the chain. A new top-level project is pre-filled with `./libraries` so its child projects inherit that shared location by default; child projects are created blank. Type a path to pin a specific location, or use the folder button to browse for one. Like the workspace field, it has a per-platform toggle for different paths on Linux, macOS, and Windows. For the full reference, see [Libraries directory](https://docs.griptapenodes.com/guides/projects/projects/#libraries-directory). ### Directories **Directories** map a short logical name (like `outputs`) to a real folder path. Workflows and nodes refer to the name, and the project decides where it actually points. Use **Add Directory** to create one, and the per-platform toggle on a row to give a directory different paths on Linux, macOS, and Windows. For the full reference, see [Directories](https://docs.griptapenodes.com/guides/projects/directories/index.md). ### File Extension Directories **File Extension Directories** route files into folders based on their extension (for example, `png → images`, `mp4 → videos`). Expand the section to add or edit mappings. For the full reference, see [File Extension Directories](https://docs.griptapenodes.com/guides/projects/file_extension_directories/index.md). ### Environment **Environment** is a set of custom key-value variables this project provides. You can reference them elsewhere in the project. Use **Add Variable** to add one. For the full reference, see [Environment & Builtin Variables](https://docs.griptapenodes.com/guides/projects/environment/index.md). ### Situations **Situations** are named file-saving scenarios (for example, saving a node's output, or downloading a URL). Each has a path template (a macro) and a policy for what to do if a file already exists. Use **Add Situation** to define one. For the full reference, see [Situations](https://docs.griptapenodes.com/guides/projects/situations/index.md). ### Saving, validating, and reloading A toolbar across the top of the detail view holds the actions for the project you're editing: - **Validate**: check the project for problems without saving. Issues are listed with the field and a description; a clean check reports **Template is valid**. - **Reload from Disk**: discard your in-window edits and reload the file as it is saved on disk. - **Save Changes**: write your edits back to the project file. (Enabled only when there are unsaved changes.) - **Upgrade schema**: appears only when the project is on an older schema version than the engine's current one. See [Upgrading a project's schema](#upgrading-a-projects-schema) below. - **Back to Projects**: return to the list. If you have unsaved changes, you'll be asked to confirm before discarding them. If a project pins specific library versions, a **Library Pins** panel shows what those pins are. ### Upgrading a project's schema When a project was created against an older major schema version than the engine ships (for example a `0.x` project on an engine whose current schema is `1.x`), an **Upgrade schema** button appears in the toolbar. Upgrading re-bases the project on the current defaults: the settings you explicitly customized are kept, and everything you left at the old defaults adopts the new ones. This is a **breaking** change — it can move where the project resolves its workspace, libraries, and saved files — so it is never automatic. A confirmation dialog spells out the consequences before anything is written; you can cancel. (Staying on an older version is fully supported; upgrade only when you want the new behavior.) The button is disabled while you have unsaved changes — save or reload first, since the upgrade re-reads the project from disk. ## Registering an existing project If a project file already exists on disk (for example, one shared by your team), use **Register Template…** in the project list, browse to its `griptape-nodes-project.yml`, and it's added to your list. From there you can activate, view, or edit it like any other project. ## Removing a project To remove a project from your list, open it and click the trash icon (or use the trash icon on its row in the list). Confirm in the **Remove Template** dialog. > **Removing only unregisters the project.** The project file on disk is **not** deleted, so you can register it again later with **Register Template…**. ## Exporting and importing projects You can package a project into a single `.zip` file and move it to another machine, hand it to a teammate, or keep it as a backup. The package bundles the project file along with the libraries it depends on, so the project works on the other end without manual setup. > **Secret values never travel in a package.** Things like API keys are referenced by name only. After importing, you set those secrets yourself in **Settings**; the import dialog lists exactly which ones you need. ### Exporting a project In the project list, each project has a small package icon on its row. Click it, choose a destination folder, and Griptape Nodes writes `.zip` there. A confirmation appears with the path, and if the project relies on any secrets, it also lists the secret key names whoever imports the package will need to provide. ### Importing a project Click **Import Project…** in the top-right of the project list and pick a `.zip` package. Before anything is extracted, an **Import project** dialog shows you what's inside: - **Package**: the project's original name. - **New project name** (optional): rename the imported copy. Leave it blank to keep the original name. Renaming is handy for branching or duplicating a project. - **Libraries**: each bundled library, tagged **bundled** (a full copy travels in the package) or **referenced** (re-downloaded on import). - **Secrets to set**: any secret keys this project needs that aren't already set in your environment. Set these in **Settings** before running the project. Click **Choose location & import**, pick the folder to extract into, and the project is registered in your list. A confirmation shows where it landed (and reminds you of any secrets to set). The imported project is added to your list but is **not** activated automatically; switch to it when you're ready. ## The Default Project Griptape Nodes always includes a built-in **Default Project**. It's read-only: you can view it, but you can't edit or save it. When you open it, a banner explains this and offers a shortcut to **Create a custom project** so you can define your own directories, situations, and variables. The Default Project is also the project you're in when no other project is active. ## Seeing a project's workflows The workflow chooser only shows the workflows that belong to the project you're currently in, so switching projects also switches which workflows you see. ## Where to go next - [Project overview](https://docs.griptapenodes.com/guides/projects/index.md): how the pieces fit together - [Workspace](https://docs.griptapenodes.com/guides/projects/workspace/index.md): the root working context and how relative paths resolve - [Projects](https://docs.griptapenodes.com/guides/projects/projects/index.md): the project file format, parent projects, and the merge model - [Version Pinning](https://docs.griptapenodes.com/guides/projects/version_pinning/index.md): pin a project to an engine version and to specific library versions - [Macros](https://docs.griptapenodes.com/guides/projects/macros/index.md): the template syntax used in situations and directories - [Directories](https://docs.griptapenodes.com/guides/projects/directories/index.md), [Situations](https://docs.griptapenodes.com/guides/projects/situations/index.md), [File Extension Directories](https://docs.griptapenodes.com/guides/projects/file_extension_directories/index.md), [Environment & Builtin Variables](https://docs.griptapenodes.com/guides/projects/environment/index.md) # Workspace The workspace is the root directory for all your work. It is the starting point from which relative file paths are resolved. ## Config files Engine configuration is stored in files named `griptape_nodes_config.json`. Three locations are meaningful: | File | Purpose | | ----------------------------------------------------- | -------------------------------------------------------------------------------- | | `~/.config/griptape_nodes/griptape_nodes_config.json` | User config — global settings for this machine | | `/griptape_nodes_config.json` | Project-adjacent config — shared defaults distributed alongside the project file | | `/griptape_nodes_config.json` | Workspace config — per-user overrides for the active project | ## Config resolution order Settings are resolved in this order (later entries win): 1. Built-in defaults 1. User config (`~/.config/griptape_nodes/griptape_nodes_config.json`) 1. Project-adjacent config (`/griptape_nodes_config.json`) 1. Workspace config (`/griptape_nodes_config.json`) 1. Environment variable (`GTN_CONFIG_*`) When the workspace directory is the same as the project directory (self-contained project), layers 3 and 4 point to the same file — it is loaded only once. ## Workspace resolution When a project is loaded, its workspace directory is decided by the following sources, **highest priority first** — the first source that supplies a value wins: 1. The project's own `workspace_dir` field (see [Project-declared workspace](#project-declared-workspace-workspace_dir) below, and the [`workspace_dir` field](https://docs.griptapenodes.com/guides/projects/projects/#workspace-directory) reference) 1. `project_workspaces` entry in user config (see [Per-project workspace overrides](#per-project-workspace-overrides) below) 1. Environment variable `GTN_CONFIG_WORKSPACE_DIRECTORY` 1. Project-adjacent config `workspace_directory` key 1. The nearest ancestor's resolved workspace, walking the explicit [parent-project chain](https://docs.griptapenodes.com/guides/projects/projects/#parent-projects) 1. The global `workspace_directory` from user config, else the directory containing the project file (auto-default) When no project file is involved, the workspace comes from the global `workspace_directory` (or the built-in `GriptapeNodes/` default beneath the engine's working directory). ## Per-project workspace overrides The `project_workspaces` setting in your user config maps project file paths to workspace directory overrides. Use this when a shared project needs to resolve to a different local workspace on each machine. ``` { "project_workspaces": { "//NAS/Projects/ProjectA/griptape-nodes-project.yml": "/Users/collin/ProjectA/", "//NAS/Projects/ProjectB/griptape-nodes-project.yml": "/Users/collin/ProjectB/" } } ``` Keys are resolved absolute paths to project files. When a project is loaded, if its resolved path matches a key, the corresponding value is used as the workspace directory. ## Project-declared workspace (`workspace_dir`) A project can name its own workspace directory in the project file via the `workspace_dir` field. This is the **highest-priority** workspace source: it beats the per-user `project_workspaces` mapping, the `GTN_CONFIG_WORKSPACE_DIRECTORY` env var, the project-adjacent config, parent inheritance, and the global default. Use it when a project should always resolve to a fixed workspace, regardless of each user's machine-level config. Unlike `project_workspaces` (which every user must set in their own config), `workspace_dir` travels inside the project file, so the project carries its workspace with it. When you create a **v1** project, the creation UI writes `workspace_dir: "./"` into it, so by default a v1 project is self-contained — its workspace is its own folder. Clearing the field (or omitting it) returns the project to inheriting the workspace from a parent or the global default. See [Schema versions](https://docs.griptapenodes.com/guides/projects/projects/#schema-versions). The value may be either a single path or a per-platform mapping: ``` # A single path: absolute, or relative to this project file's directory. workspace_dir: "./workspace" # Or a per-platform mapping; the active platform's key is used, falling back to `default`. workspace_dir: darwin: "/Volumes/fast/ProjectA" windows: "D:/ProjectA" default: "./workspace" ``` A relative path resolves against the directory containing the project file (the same way `parent_project_path` resolves), so a project that uses `workspace_dir: "./workspace"` stays portable: move or copy the project folder and the workspace follows. The target directory does not need to contain a `griptape_nodes_config.json` — an empty directory is valid. See the [`workspace_dir` field reference](https://docs.griptapenodes.com/guides/projects/projects/#workspace-directory) for the full schema, per-platform fallback rules, and how it behaves with parent projects. ## Example scenarios ### Scenario 1: Solo developer, no project file No project file, no project-adjacent or workspace config. Workspace comes from user config or the built-in default. ``` ~/.config/griptape_nodes/ griptape_nodes_config.json <- workspace_directory: ~/GriptapeNodes ~/GriptapeNodes/ <- workspace griptape_nodes_config.json <- optional workspace-level overrides ``` ### Scenario 2: Self-contained portable project Project and config live in the same directory. The workspace auto-defaults to the project directory. Move the folder to a thumb drive or a different machine — no changes needed. ``` /My_Indie_Short/ griptape-nodes-project.yml griptape_nodes_config.json <- project-adjacent and workspace config (same file) inputs/ outputs/ ``` ### Scenario 3: Shared project, per-user workspaces A project file lives on a shared network drive. Each user maps it to their own local workspace via `project_workspaces` in their user config. Each user's workspace can have its own `griptape_nodes_config.json` for personal overrides. ``` //NAS/Projects/ProjectA/ griptape-nodes-project.yml griptape_nodes_config.json <- shared studio defaults (e.g. model preferences) ``` Collin's user config: ``` { "project_workspaces": { "//NAS/Projects/ProjectA/griptape-nodes-project.yml": "/Users/collin/ProjectA/" } } ``` James' user config: ``` { "project_workspaces": { "//NAS/Projects/ProjectA/griptape-nodes-project.yml": "C:\\Projects\\ProjectA\\" } } ``` Each user can place a `griptape_nodes_config.json` in their local workspace directory for personal overrides that take precedence over the shared project-adjacent config. ### Scenario 4: Studio-mandated workspace The project-adjacent config sets a shared workspace. Artists without a `project_workspaces` entry all get the studio default. A workspace config on the shared drive can hold additional shared settings. ``` //NAS/Projects/ProjectA/ griptape-nodes-project.yml griptape_nodes_config.json <- workspace_directory: //NAS/Workspaces/ProjectA/ //NAS/Workspaces/ProjectA/ griptape_nodes_config.json <- shared workspace-level settings ``` Render farm machines can override the workspace via `GTN_CONFIG_WORKSPACE_DIRECTORY` without touching any shared files. ### Scenario 5: User overrides shared engine config The studio ships `log_level: "WARNING"` in the project-adjacent config. A developer wants `DEBUG` locally. They put the override in their workspace config — workspace config (layer 4) beats project-adjacent (layer 3). ``` /Users/dev/ProjectA/ griptape_nodes_config.json <- {"log_level": "DEBUG"} ``` ### Scenario 6: Multiple projects, different workspaces `project_workspaces` maps each shared project to a distinct local workspace. Switching active projects switches workspace and reloads the workspace config. ``` { "project_workspaces": { "//NAS/ProjectA/griptape-nodes-project.yml": "/Users/dev/ProjectA/", "//NAS/ProjectB/griptape-nodes-project.yml": "/Users/dev/ProjectB/" } } ``` ### Scenario 7: Workspace auto-discovery Griptape Nodes looks for `griptape-nodes-project.yml` in the workspace directory on startup. If found, the project is loaded automatically. This is the same as scenario 2 — workspace and project directory are the same, so the single `griptape_nodes_config.json` serves as both project-adjacent and workspace config. ### Scenario 8: Project pins its own workspace A project that should always resolve to a fixed workspace declares it directly with `workspace_dir`, so no per-user `project_workspaces` entry is needed. A relative value keeps the project portable across machines. ``` # griptape-nodes-project.yml name: "ProjectA" workspace_dir: "./workspace" ``` ``` /ProjectA/ griptape-nodes-project.yml <- workspace_dir: ./workspace workspace/ <- resolved workspace (created on demand) ``` Because `workspace_dir` is the highest-priority source, this wins over any `project_workspaces` mapping or env var. Move the `ProjectA/` folder anywhere and the workspace still resolves to its `workspace/` subdirectory. ## How paths resolve Relative file paths in your workflows resolve against the **workspace directory**. If your workspace is `/Users/you/workspace/` and a situation macro resolves to `outputs/render_001.png`, the final absolute path is `/Users/you/workspace/outputs/render_001.png`. Relative paths written in the project file resolve against the folder that holds the project YAML, which is how the `workspace_dir: ./workspace` example above finds its workspace. Libraries are the exception: they install and resolve under the workspace-relative `libraries` directory by default, but a project can relocate them (and share them across a project tree) with the [`libraries_dir`](https://docs.griptapenodes.com/guides/projects/projects/#libraries-directory) field, independent of the workspace. The **project base directory** (the folder containing `griptape-nodes-project.yml`) is exposed as the `{project_dir}` builtin variable but is not used as the resolution base for relative paths. It is used as a fallback when the path manager maps an absolute path back to a macro form and the path falls inside the project folder but outside any named directory. ## Workspace and the project file When Griptape Nodes starts, it looks for `griptape-nodes-project.yml` in your workspace directory. If found, the file is merged on top of the system defaults to produce the active project template. If not found, system defaults are used. See [Projects](https://docs.griptapenodes.com/guides/projects/projects/index.md) for details on the project file and merge model. ## Summary | Setting | Description | | -------------------------------------------- | --------------------------------------------------- | | `workspace_dir` | Project-declared workspace; highest-priority source | | `workspace_directory` | The root directory for your work | | `project_workspaces` | Per-project workspace overrides in user config | | `griptape-nodes-project.yml` | Optional project template file | | `/griptape_nodes_config.json` | Optional shared config distributed with the project | | `/griptape_nodes_config.json` | Optional per-user workspace config | | `GTN_CONFIG_WORKSPACE_DIRECTORY` | Environment variable override (highest priority) | # Projects A project template is a configuration that defines how files are organized and saved. It is the combination of [situations](https://docs.griptapenodes.com/guides/projects/situations/index.md), [directories](https://docs.griptapenodes.com/guides/projects/directories/index.md), and [environment variables](https://docs.griptapenodes.com/guides/projects/environment/index.md) that every node consults when it needs to save a file. By default, Griptape Nodes ships with a built-in project template that handles the common cases — saving images, audio, and other outputs to an `outputs` folder in your workspace. A project file lets you change any of that: redirect where a particular kind of file is saved, add a new named location (like `renders/4k`), or inject environment variables specific to your project. You only write down the things you want to change; everything else continues to use the [default situations](https://docs.griptapenodes.com/guides/projects/situations/#default-situations) and [default directories](https://docs.griptapenodes.com/guides/projects/directories/#default-directories). ## The project file Your workspace-level customizations live in a file named: ``` griptape-nodes-project.yml ``` Place this file in your workspace directory. It is optional — if absent, the [system defaults](#the-system-defaults) apply. ## Project file structure ``` project_template_schema_version: "1.0.0" name: "My Project" description: "Optional description" directories: outputs: path_macro: "renders" # override the outputs directory path renders_4k: # add a new directory path_macro: "renders/4k" situations: save_node_output: # override an existing situation macro: "{outputs}/{workflow_name?:_}{file_name_base}{_index?:03}.{file_extension}" archive_render: # add a new situation macro: "{renders_4k}/{workflow_name?:_}{file_name_base}.{file_extension}" policy: on_collision: overwrite create_dirs: true environment: PROJECT_CODENAME: "aurora" file_extension_directories: png: "images" mp4: "{outputs}/videos" ``` ### Fields reference | Field | Required | Description | | --------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `project_template_schema_version` | Yes | The project's schema version. Its **major** selects the defaults baseline (see [Schema versions](#schema-versions)); new projects are created at the latest (`"1.0.0"`). Older majors (e.g. `"0.5.1"`) still load against their own baseline. | | `name` | Yes | Human-readable name for this project | | `description` | No | Optional description | | `parent_project_path` | No | Path to a parent project YAML; the parent's merged template becomes the base for this one. See [Parent projects](#parent-projects). | | `workspace_dir` | No | Directory this project uses as its workspace; the highest-priority workspace source. A string (absolute or relative to this YAML) or a per-platform mapping. Not inherited from a parent. See [Workspace directory](#workspace-directory). | | `libraries_dir` | No | Directory this project installs and resolves its libraries under. A string (absolute or relative to this YAML) or a per-platform mapping. **Inherited** down the parent chain (unlike `workspace_dir`), so children can share a parent's libraries. See [Libraries directory](#libraries-directory). | | `situations` | No | Dict of situation overrides and additions | | `directories` | No | Dict of directory overrides and additions | | `environment` | No | Dict of custom key-value variables | | `file_extension_directories` | No | Extension-to-folder routing; see [File Extension Directories](https://docs.griptapenodes.com/guides/projects/file_extension_directories/index.md) | ### Situation fields Each entry under `situations` is keyed by situation name. You can provide any subset of these fields: | Field | Required for new | Description | | --------------------- | ---------------- | -------------------------------------------------- | | `macro` | Yes | Macro template string for the file path | | `policy` | Yes | Must include both `on_collision` and `create_dirs` | | `policy.on_collision` | Yes | One of: `create_new`, `overwrite`, `fail` | | `policy.create_dirs` | Yes | `true` to create missing directories automatically | | `fallback` | No | Name of another situation to use if this one fails | | `description` | No | Human-readable description | When *modifying* an existing situation, you only need to provide the fields you want to change. When *adding* a new situation, `macro` and `policy` are required. ### Directory fields Each entry under `directories` is keyed by the logical name: | Field | Required for new | Description | | ------------ | ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `path_macro` | Yes | Path string (may contain macros or environment variable references), or a per-platform mapping. See [Directories](https://docs.griptapenodes.com/guides/projects/directories/#per-platform-paths). | ## The merge model The project system uses a layered model: 1. **System defaults** — a complete, built-in project template that ships with Griptape Nodes. It defines all default situations and directories and is always loaded first. 1. **Parent chain** (optional) — when a project declares `parent_project_path`, the parent's merged template is loaded recursively and replaces the system defaults as this project's base. See [Parent projects](#parent-projects). 1. **This project's overlay** — the contents of this `griptape-nodes-project.yml`. It is merged *on top of* whatever base resolved above. The merge behavior is additive and field-level: - Situations and directories from the overlay are merged into the base. An overlay situation with the same name as a base situation changes only the fields you specify (e.g., just the macro, or just the policy). An overlay situation with a new name is added alongside the base. - Environment entries in the overlay override entries with the same key in the base. New keys are added. - `file_extension_directories` entries merge per-key the same way as environment entries. A `null` value tombstones the base entry. - The `name` field is always taken from the overlay (required). You never need to repeat inherited values. Your project file only needs to contain the things you want to change. ## Parent projects A project can declare another project as its parent. The parent's fully merged template (after the parent has resolved its *own* parent chain) becomes the base, and this project's overlay is applied on top. This lets you keep a shared base configuration in one place — a team-wide directory layout, a fixed set of environment variables, a custom situation — and let derived projects inherit from it instead of restating every field. ``` project_template_schema_version: "1.0.0" name: "Marketing Renders" parent_project_path: "../team-base/griptape-nodes-project.yml" # Only the diffs against the parent need to be listed. directories: outputs: path_macro: "renders/marketing" ``` ### Path forms `parent_project_path` accepts two forms: | Form | Example | Notes | | -------- | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------ | | Relative | `../team-base/griptape-nodes-project.yml` | Preferred. Resolved against the directory of *this* project's YAML file, so the link survives moves between machines and OSes. | | Absolute | `/Users/alice/projects/team-base/griptape-nodes-project.yml` | Bakes in a per-machine path; works locally but does not travel. | No macros are expanded inside `parent_project_path`. Tokens like `{workspace_dir}`, `{project_dir}`, and `{outputs}` are not substituted here. Macros belong in `path_macro` and `situations.*.macro` fields. `~` and environment variables *are* expanded — see [Path values](#path-values-in-workspace_dir-libraries_dir-and-parent_project_path). ### Inheritance and tombstones The parent chain is resolved before merge. A grandchild → child → parent → defaults chain composes the same way the workspace overlay composes onto defaults: each level's fields override or extend the level beneath it. To **drop** an inherited entry rather than override it, set the value to `null`: ``` directories: scratch: null # remove the parent's `scratch` directory entirely ``` Setting `parent_project_path: null` explicitly clears an inherited link, falling the project back to the system defaults as its base. Omitting the field entirely inherits the parent's link (which is rarely what you want — typically you set `parent_project_path` on each child explicitly). A project whose base *is* the system defaults should leave `parent_project_path` out entirely — absence already means "system defaults are the base," so there is nothing to point at. Only set the field when the parent is a *different* project file. This matters for sharing: writing a parent path into a project saved to shared or synced storage bakes in a machine-specific link that will not resolve on another person's machine, so a default-derived project should carry no path at all. ### Cycles and missing parents The engine refuses to load a project whose parent chain contains a cycle. A direct self-reference, A → B → A, and longer cycles are all caught and reported as a validation error on the child being loaded. A parent that cannot be read or parsed surfaces as a validation error on the child as well. The child is not silently downgraded to the system defaults — the load fails so the problem is visible. ### Sharing across machines A workspace zipped on one machine and unpacked on another will keep parent links intact as long as the parent is referenced with a relative path and travels alongside the child inside the workspace. Absolute paths break across machines (different home directories, different drive letters); the relative form is what makes parent chains portable. ## Path values in `workspace_dir`, `libraries_dir`, and `parent_project_path` These three fields all accept a path, and all three read it the same way, in this order: 1. **`~` and environment variables expand first.** `~` becomes your home directory, and `${NAME}` (or `%NAME%` on Windows) is replaced with that variable's value. 1. **A path that is still relative** after expansion resolves against the directory of the project file that declared it, so the value travels with the project. 1. The result is normalized, so two spellings of the same folder mean the same folder. Expansion happening *first* is what makes this work: `${STUDIO_LIBS}/griptape-libraries` is an absolute path once `STUDIO_LIBS` is filled in, and is treated as one rather than being tacked onto the project folder. ``` libraries_dir: "${STUDIO_LIBS}/griptape-libraries" # absolute once STUDIO_LIBS is set workspace_dir: "~/Projects/marketing" # your home directory ``` **Which variables count.** Only the environment the engine was launched with — what your shell, launcher, or studio pipeline exported before Griptape Nodes started. A project's own `environment:` block is deliberately **not** consulted here: those variables are applied when the project is *activated*, which happens after these fields have already been resolved. If a project's own environment could change what its workspace and libraries folders mean, a field's meaning would depend on which project happened to be open at the time. **`{macro}` tokens are not expanded.** `{workspace_dir}`, `{project_dir}`, `{outputs}`, and any other macro token is rejected rather than substituted — the engine reports the field as unresolvable instead of creating a folder literally named `{outputs}`. Macros belong in `path_macro` and `situations.*.macro` fields, which resolve against runtime state these fields cannot depend on. **One variable is always available: `${GTN_DEFAULT_LIBRARIES_ROOT}`.** The engine publishes it at startup, so unlike `${STUDIO_LIBS}` above it needs no setup on any machine. It holds the absolute directory libraries install under by default — the same place `griptape-nodes init` puts the standard library — which makes it the portable way to point a project at this engine's existing library tree instead of downloading a second copy: ``` libraries_dir: "${GTN_DEFAULT_LIBRARIES_ROOT}/shared" ``` It does not follow a project's own `libraries_dir`, since that is the field reading it. See [Variables the engine publishes](https://docs.griptapenodes.com/guides/configuration/#variables-the-engine-publishes). Three things worth knowing before you rely on a variable: - `%NAME%` expands **only on Windows**. For a value that has to work everywhere, use `${NAME}` or the per-platform mapping form. - A bare `$NAME` (no braces) is **not** a variable here. It is indistinguishable from a real folder name such as `$Recycle.Bin`, so it stays literal — always write `${NAME}`. - A variable only makes a path portable if every machine defines it. A relative path needs no such agreement, so prefer relative when the target travels with the project. ### A path that cannot be resolved is an error Leaving one of these fields **out** is always fine — the project simply falls back to the next source (a parent's value, the config, the global default). But a field that *is* written and cannot produce a path means the project is asking for something this machine cannot give it, and the engine will not quietly substitute somewhere else. **The project fails to load**, reports [`UNUSABLE`](#validation-status), and its validation problems name the field, the line, and the reason. Any project that lists it as a parent fails too, with a problem pointing at its `parent_project_path`. Three things trip this: - `${STUDIO_LIBS}/libraries` when `STUDIO_LIBS` is not set — no literal `${STUDIO_LIBS}` folder is created. - `{outputs}/libraries` or any other `{macro}` token. - A per-platform mapping with no entry for the machine you are on and no `default`. That last one is the case worth planning for, and `default` is the fix. A mapping that names only `windows` says nothing about what a teammate on Linux should get, so the honest answer on their machine is a failed load rather than a guess: ``` libraries_dir: windows: "D:/shared-libraries" # the fast local volume, Windows only default: "./libraries" # everyone else: beside the project ``` The failure is deliberately loud, because the alternative is worse: a shared project pinning `${STUDIO_LIBS}` would otherwise load for every teammate who has not set that variable and silently install a second copy of every library somewhere they never chose. Set the variable, add a `default`, or remove the field. ## Workspace directory The optional `workspace_dir` field names the directory this project uses as its [workspace](https://docs.griptapenodes.com/guides/projects/workspace/index.md). When set, it is the **highest-priority** workspace source — it overrides the per-user `project_workspaces` mapping, the `GTN_CONFIG_WORKSPACE_DIRECTORY` env var, the project-adjacent config, parent inheritance, and the global default. When you create a **v1** project, the creation UI writes `workspace_dir: "./"` into it, which makes it self-contained: the workspace resolves to the project's own folder. It is still an ordinary, optional field — clear it (or set it to `null`) to fall back to inheriting the workspace from a parent or the global default. **v0** projects are not given a default `workspace_dir`. (The engine's default template itself leaves `workspace_dir` unset for every major; the `./` is supplied at creation, not inherited from the default.) The value takes one of two forms: ``` # A string: absolute, or relative to this project file's directory. workspace_dir: "./workspace" # Or a per-platform mapping with optional linux / darwin / windows / default keys. workspace_dir: darwin: "/Volumes/fast/ProjectA" windows: "D:/ProjectA" default: "./workspace" ``` A relative path resolves against the directory of *this* project's YAML file — the same rule `parent_project_path` uses — so a relative value travels with the project across machines and operating systems. `~` and environment variables are expanded (see [Path values](#path-values-in-workspace_dir-libraries_dir-and-parent_project_path)). The raw value is stored verbatim and only resolved to an absolute path at workspace-resolution time; it is never absolutized on save. The target directory does not need to contain a `griptape_nodes_config.json`; an empty directory is valid. For the per-platform form, the engine picks the entry matching the current platform. When that platform's key is unset it falls back to `default`. If neither the platform key nor `default` is set, the project does not load on that platform — see [A path that cannot be resolved is an error](#a-path-that-cannot-be-resolved-is-an-error). Otherwise the resolved value feeds [Workspace resolution](https://docs.griptapenodes.com/guides/projects/workspace/#workspace-resolution) as its highest-priority source. Unlike most fields, `workspace_dir` is **not inherited** from a parent project. It describes this project's own workspace and is taken from this project's overlay alone — a child does not adopt its parent's `workspace_dir`. (Cross-project workspace inheritance is handled separately by the resolution ladder's parent-chain step, not by the merge model.) Setting `workspace_dir: null` or omitting the field both leave the project with no declared workspace. ## Libraries directory The optional `libraries_dir` field names the directory this project installs and resolves its libraries under, decoupled from the workspace. Use it so a group of related projects can share one library install location — a library is downloaded once and every project that points at the same directory reuses it instead of re-downloading. The value takes one of two forms: ``` # A string: absolute, or relative to this project file's directory. libraries_dir: "./libraries" # Or a per-platform mapping with optional linux / darwin / windows / default keys. libraries_dir: darwin: "/Volumes/fast/shared-libraries" windows: "D:/shared-libraries" default: "./libraries" ``` A relative path resolves against the directory of *this* project's YAML file — the same rule `parent_project_path` and `workspace_dir` use — so a relative value travels with the project across machines. `~` and environment variables are expanded (see [Path values](#path-values-in-workspace_dir-libraries_dir-and-parent_project_path)). The raw value is stored verbatim and only resolved to an absolute path at resolution time; it is never absolutized on save. For the per-platform form, the engine picks the entry matching the current platform, falling back to `default`; with neither present the project does not load on that platform (see [A path that cannot be resolved is an error](#a-path-that-cannot-be-resolved-is-an-error)). ### Inheritance and resolution `libraries_dir` resolves by the following order, **highest priority first**: 1. The project's **own** `libraries_dir` field. 1. The nearest ancestor that declares a `libraries_dir`, walking the [parent-project chain](#parent-projects) — resolved against **that ancestor's** directory. This is what lets a child adopt its parent's library tree. 1. No declared value anywhere in the chain: libraries fall back to the workspace-relative `libraries_directory` config value (default `libraries`, i.e. `/libraries`). See [Configuration](https://docs.griptapenodes.com/guides/configuration/#separating-libraries-from-the-workspace). This is the **key difference from `workspace_dir`**: `workspace_dir` is never inherited from a parent, but `libraries_dir` **is**. A child that declares no `libraries_dir` of its own adopts the nearest ancestor that declares one (only an *explicit* value is inheritable — a parent that leaves the field unset contributes nothing, and the walk continues past it). This is why the creation UI writes `libraries_dir: "./libraries"` into a **top-level** project (whose parent is the system defaults) but leaves it unset on a child: the top-level project declares an explicit, inheritable library root, and each child under it shares that root by default. A child can still declare its own `libraries_dir` to opt out, or set `libraries_dir: null` to tombstone an inherited value and fall back to the workspace-relative default. Like `workspace_dir`, `libraries_dir` is **not merge-inherited**: it is taken from this project's overlay alone and a child does not pick up the base's value through the [merge model](#the-merge-model). Cross-project inheritance is handled by the resolution order above (the parent-chain walk), not by merge. ### Seeing where the paths ended up Because the answer can come from any of the three places above, you do not have to work it out yourself. `ListProjectTemplatesRequest` reports each project's effective `workspace_dir` and `libraries_root` as absolute paths, resolved exactly the way activating the project resolves them — including inheritance from a parent, so a parent and its children show the same libraries root. Both are reported for every project that loaded from a file, and are `null` only for an entry with no backing project file (the system defaults) or one that failed to load. They are never `null` because resolution gave up: a project whose declared paths cannot be resolved does not load at all. ## Validation status When a project file is loaded, it receives one of four statuses: | Status | Meaning | | ---------- | -------------------------------------------------------------------------------------- | | `GOOD` | Loaded and fully valid | | `FLAWED` | Loaded but has warnings (e.g., behind the latest version for its major) — still usable | | `UNUSABLE` | Errors prevent the template from being used | | `MISSING` | The project file was not found | When a project file has `UNUSABLE` or `MISSING` status, Griptape Nodes falls back to the system defaults. You can inspect validation problems (with field paths and line numbers) to diagnose issues. ## The system defaults The system defaults define these situations and directories out of the box. See [Situations](https://docs.griptapenodes.com/guides/projects/situations/index.md) and [Directories](https://docs.griptapenodes.com/guides/projects/directories/index.md) for full details. ## Schema versions `project_template_schema_version` is read as a [semantic version](https://semver.org); its **major** component selects which defaults baseline the project merges onto. This lets the defaults change in a breaking way under a new major while existing projects keep the behavior they were written against: - **v0 (`0.x`)** — the legacy baseline. File destinations are workspace-root-relative and files are not routed into per-type subfolders by default. - **v1 (`1.x`)** — the current baseline. File destinations are workflow-relative, output files route through [file-extension directories](https://docs.griptapenodes.com/guides/projects/file_extension_directories/index.md), and a new project is **self-contained by default** — the creation UI writes [`workspace_dir`](#workspace-directory): `./` into a new v1 project so it resolves its workspace to its own folder (the field is an ordinary, removable override, not baked into the default template). A new **top-level** v1 project also gets [`libraries_dir`](#libraries-directory): `./libraries` so its child projects can inherit and share its library tree. New projects are created at `1.0.0`. A project always merges onto the baseline for **its own** major, so opening an older-major project never silently shifts its layout. ### Saving and version roll-forward Saving a project rolls its version forward to the latest **within its own major** (an additive change — e.g. a `0.4.0` project becomes `0.5.2` on save). Saving never crosses a major: a v0 project is never silently turned into v1, because that would change where its files land. A project behind the latest version for its major loads with a `FLAWED` warning (still fully usable). ### Upgrading across a major Moving a project to a new major is **explicit and opt-in** (in the GUI, the "Upgrade schema" action in [Manage Projects](https://docs.griptapenodes.com/guides/projects/gui_guide/index.md)). Upgrading re-bases the project on the new major's defaults: only the values you explicitly overrode are kept; everything else adopts the new defaults. This is a **breaking** change — the project's effective workspace, library, and file locations can move — which is why it never happens automatically. # Directories Directories are logical name-to-path mappings. Instead of hard-coding a path like `outputs/renders` everywhere, you give it a name (`outputs`) and use that name in macros. If you later change where outputs should go, you update the directory definition in one place. ## Default directories The system defaults define six directories: | Name | Default path | Description | | --------------------------- | ---------------------------- | --------------------------------------------------------------------- | | `inputs` | `inputs` | Files brought into the project — uploads, copies, and downloads. | | `outputs` | `outputs` | Files generated by nodes during workflow execution. | | `temp` | `temp` | Temporary scratch files; safe to delete between runs. | | `griptape-nodes-previews` | `.griptape-nodes-previews` | Generated preview/thumbnail artifacts; mirrors source-file hierarchy. | | `griptape-nodes-metadata` | `.griptape-nodes-metadata` | Sidecar metadata for project files; mirrors source-file hierarchy. | | `griptape-nodes-thumbnails` | `.griptape-nodes-thumbnails` | Workflow thumbnail images shown in the UI. | All default paths are relative and resolve against the project base directory. ## Using directory names in macros Directory names are available as variables in any macro. When a macro is resolved, the project system automatically substitutes the directory name with its configured path: ``` Template: {outputs}/{file_name_base}.{file_extension} ↓ Resolved: outputs/my_image.png ``` You do not supply directory values yourself — they come from the directory definitions. Directory names are **reserved**: if you try to pass a variable with the same name as a directory, the system will reject it with an error to prevent ambiguity. ## Customizing directory paths Override a directory's path in your `griptape-nodes-project.yml`: ``` project_template_schema_version: "0.1.0" name: "My Project" directories: outputs: path_macro: "renders/final" ``` Now `{outputs}` in any macro resolves to `renders/final` instead of `outputs`. ## Directory descriptions Each directory definition can carry an optional `description` — a human-readable explanation of what the directory is for and how it's meant to be used. Descriptions are surfaced in tools that display project directories (such as the Project Management screen in the GUI) and make hand-edited project YAMLs easier to understand. ``` directories: outputs: path_macro: "renders/final" description: "Final renders ready for delivery to clients." ``` `description` is optional and defaults to `null`. To clear an inherited description from a base or parent template, set it to `null` in your overlay: ``` directories: outputs: description: null ``` ## Adding new directories Add a directory that doesn't exist in the defaults: ``` directories: deliverables: path_macro: "client_deliverables" ``` Once added, `{deliverables}` is available in any macro. ## Directory paths with macros The `path_macro` field supports tilde (`~`) expansion, macro syntax, and environment variable references: ``` directories: downloads: path_macro: "~/Downloads" ``` This maps the `downloads` directory to the current user's Downloads folder, regardless of the machine it runs on. ``` directories: outputs: path_macro: "$OUTPUT_BASE/renders" ``` If `$OUTPUT_BASE` is set in your environment (or in the project's `environment` section), it will be substituted when the path is resolved. You can also reference builtin variables in directory paths: ``` directories: outputs: path_macro: "{workflow_dir}/renders" ``` This makes the `outputs` directory relative to the current workflow's location rather than the project base directory. ## Per-platform paths A directory's `path_macro` can be a single string (used everywhere) or a per-platform mapping when the right path differs across operating systems. Use this when a workspace is shared between collaborators on Linux, macOS, and Windows and the directory needs a different absolute location on each. ``` directories: scratch: path_macro: linux: "/mnt/fast-scratch" darwin: "/Volumes/scratch" windows: "D:/scratch" default: "{workspace_dir}/scratch" ``` At resolution time, the engine picks the entry matching the current platform (`linux`, `darwin`, or `windows`). If the active platform's key is unset, it falls back to `default`. At least one of the four keys must be supplied; otherwise the project fails validation. ``` directories: models: path_macro: darwin: "~/Library/Caches/models" default: "{workspace_dir}/.models" ``` Per-platform values support the same syntax as the string form — tilde expansion, environment variables, and macros all work the same way per platform. The mapping is **atomic** during overlay merges: a child template that supplies a per-platform mapping fully replaces the parent's `path_macro`, rather than merging key-by-key. To keep one platform's value while overriding another, restate every key you want kept. ## Reserved names Directory names are reserved across the entire variable namespace. You cannot use a directory name as a user-supplied variable in a macro call — the system will return an error if you try. This protects against accidentally overriding a directory path through a variable name collision. The builtin variables (`project_dir`, `workspace_dir`, `workflow_name`, `workflow_dir`, `static_files_dir`) are also reserved and cannot be overridden. See [Environment & Builtin Variables](https://docs.griptapenodes.com/guides/projects/environment/index.md). # Macros A macro is a template string that generates a file path by substituting named variables. Macros are used in situation templates and directory definitions. Note This page is about **file-path macros** used by the project system. For named values you create and read inside a workflow — including `{name}` substitution in text parameters — see [Workflow Variables](https://docs.griptapenodes.com/guides/variables/index.md). Before diving into the full syntax, here are two examples that show what macros look like in practice: ``` Template: {outputs}/{node_name?:_}{file_name_base}{_index?:03}.{file_extension} With all variables: outputs="outputs", node_name="ImageGen", file_name_base="render", _index=2, file_extension="png" → outputs/ImageGen_render002.png With optional variables omitted: outputs="outputs", file_name_base="render", file_extension="png" → outputs/render.png ``` `{outputs}` is a directory name that the project system supplies automatically. `{node_name?:_}` is optional — when present, its value is followed by `_`; when absent, the block disappears entirely. `{_index?:03}` is optional and zero-padded to three digits when present. ## Variable syntax reference ### Required variable ``` {variable_name} ``` The variable must be provided. If it is missing when the macro is resolved, resolution fails with an error. ### Optional variable ``` {variable_name?} ``` The `?` marks the variable as optional. If the variable is not provided, the `{}` block — and any format spec — is omitted entirely from the output. The rest of the macro continues normally. **Trailing form.** The `?` can also appear at the end of the last format spec — `{shot:upper?}` is equivalent to `{shot?:upper}`. Both spellings mark the variable optional. Applies to plain variables and sequence shorthand alike: `{###:upper?}` is the same as `{###?:upper}`. Quote the separator (`{shot:'lower?'}`) to keep the `?` as a literal character. ### Separator format ``` {variable_name:separator} ``` Appends `separator` after the variable's value. Any text that is not a recognized keyword (see transformations below) and not a numeric padding is treated as a separator. This is most useful for building path prefixes that disappear cleanly when the variable is absent. For example, `{node_name?:_}` adds `node_name_` before a filename when the node name is known, but produces nothing at all when it isn't: ``` {node_name?:_}{file_name_base} node_name="ImageGen", file_name_base="render" → ImageGen_render node_name not provided, file_name_base="render" → render ``` Path separators work the same way — `{sub_dirs?:/}` adds a subdirectory prefix only when sub-directories are specified: ``` {outputs}/{sub_dirs?:/}{file_name_base}.{file_extension} sub_dirs="lighting/pass_a", file_name_base="render", file_extension="exr" → outputs/lighting/pass_a/render.exr sub_dirs not provided, file_name_base="render", file_extension="exr" → outputs/render.exr ``` ### Leading separator ``` {variable_name:^prefix} ``` Mirror of the [separator format](#separator-format), but the text is **prepended** to the variable's value rather than appended. Marked by a `^` at the start of the format-spec text; anything after the `^` is the literal prefix payload. Renders iff the variable emits — an unbound optional variable takes its leading separator with it into oblivion. ``` {file_name_base}{version?:^_v}.{file_extension} file_name_base="render", version=3, file_extension="png" → render_v3.png file_name_base="render", version not provided → render.png ``` The load-bearing pattern: pair with a [sequence slot](#sequence-slot) to get a version suffix that comes and goes with the sequence: ``` render{###?:^_v}.png first save (slot omitted) → render.png second save (slot fires) → render_v001.png third save → render_v002.png ``` Works outside filenames too — the trailing separator is not just for path prefixes and neither is the leading one: ``` Hello, {name?}!{intro?:^ Nice to meet you.} name="Alice", intro="y" → Hello, Alice! Nice to meet you.y name="Alice", intro absent → Hello, Alice! name absent, intro absent → Hello, ! ``` **Composition rules** - A variable can carry at most **one** leading separator. - The leading separator is applied **after** every other format spec on the same variable, regardless of where you write it in the template. `{shot:03:^_v}` and `{shot:^_v:03}` both render `shot=5` as `_v005` — the parser normalizes the leading spec to the tail of the list so ordering never mangles the prefix. **Related grammar errors** | Error | Cause | | ----------------------------- | ------------------------------------------ | | `EMPTY_LEADING_SEPARATOR` | `:^` with no payload after the caret | | `MULTIPLE_LEADING_SEPARATORS` | Two `:^`-marked specs on the same variable | **Limitation.** A `^` at the start of a format spec is now reserved as the leading-separator discriminator, so a literal `^_v` prefix isn't spellable today. If a real use case surfaces, a future escape mechanism (e.g. `\^` or a `'^'`-quoted form) will fill it in. ### Numeric padding ``` {variable_name:03} ``` Zero-pads the value to the specified width. The variable must hold an integer value. ``` {_index:03} with _index = 5 → "005" {_index:04} with _index = 12 → "0012" ``` Used for auto-incrementing filenames under the `create_new` collision policy. Numeric padding (`:NN`) on a single unresolved variable is the opt-in: the first save lands at index `1` (or omitted, for the optional form), and subsequent saves walk forward against the same template — the padding format is preserved across the whole sequence. - **Optional form** `{_index?:03}` — absent on the first save, then `_001`, `_002`, … on collision (padded width preserved). - **Required form** `{_index:03}` — present from the first save: `_001`, `_002`, `_003`, … with consistent zero-padded width across the whole sequence. ``` Template: {file_name_base}_v{_index:03}.{file_extension} Save #1 → render_v001.png Save #2 → render_v002.png Save #3 → render_v003.png ``` The variable name does not need to be `_index`; any single unresolved required variable with `:NN` padding will be auto-allocated. Without padding, an unresolved required variable is treated as a missing binding (a configuration error) and the save fails — this prevents `{shot}` from silently being filled with `1, 2, 3, …` when the user forgot to wire it up. ### Sequence slot (`{###}`) ``` {#} → 1-digit minimum (1, 2, ..., 9, 10, 11, ...) {###} → 3-digit minimum (001, 002, ..., 999, 1000, ...) {####} → 4-digit minimum (0001, 0002, ..., 9999, 10000, ...) {##?} → 2-digit minimum, optional (omitted on first save; 01, 02, … on collision) ``` A run of `#` characters **inside `{}` braces** is the explicit syntax for a sequence slot. Each `#` contributes one digit to the **minimum** render width. Values below `10 ^ width` are zero-padded to that width; values at or above it render at their natural width (no truncation). This matches the universal `###` convention from ffmpeg (`%03d`), Houdini (`$F4`), Nuke (`####`), and Python's `:03` format spec. A trailing `?` inside the braces (e.g. `{##?}`) marks the slot **optional** — the same rule as for any other variable. Optional slots are omitted on the first save and only fill in on collision. ``` Template: {file_name_base}_v{###}.{file_extension} Save #1 → render_v001.png Save #2 → render_v002.png ... Save #999 → render_v999.png Save #1000 → render_v1000.png (overflow: 4 digits, not truncated) ``` ``` Template (optional): {file_name_base}{##?}.{file_extension} Save #1 → render.png (slot omitted) Save #2 → render01.png (slot fills in on collision) Save #3 → render02.png ``` Use `{###}` whenever you want a system-allocated sequence index. It says "this slot is what `create_new` should advance on collision" without leaning on the numeric-padding heuristic described above, so a macro author who genuinely needs a user-bound `{shot:03}` variable can write that without ambiguity. **Why the `{}` wrapping.** Macro templates often appear in places where bare `#` chars have other meanings — Markdown headers, comments, shell scripts. Wrapping the sigil inside `{}` keeps the sequence-slot syntax inside the same delimiters that already mark "this is a macro variable," so authors don't need escaping rules for stray `#` chars in static text. **One sequence slot per macro.** A template with two `{###}` blocks (e.g. `{###}_take_{##}.png`) is rejected at parse time — the system has no way to know which slot to auto-allocate. Compose the second number as an explicit `{var}` if you need it. **Relationship to `{_index:NN}`.** Internally `{###}` desugars to a variable named `_index` carrying a sequence-format marker. The legacy `{_index:03}` / `{_index?:03}` syntax continues to work and is still treated as a sequence slot for backward compatibility, but `{###}` is the recommended form going forward. Future versions may retire the `{_index:NN}` shorthand once project templates have migrated; see [issue #4902](https://github.com/griptape-ai/griptape-nodes-engine/issues/4902). ### Unresolved sequence slots A required `{###}` slot has no value until the write path allocates one. Any code that resolves a macro *before* that allocation happens — a node previewing where its output will land, a UI classifying user input as absolute-vs-relative — has to tell the resolver what to do about the empty slot. `GetPathForMacroRequest` exposes the choice as `unresolved_sequence_slot_behavior`, whose values live in the `UnresolvedSequenceSlotBehavior` enum: | Behavior | Renders as | When to use | | ------------------------- | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `FAIL` *(default)* | `MISSING_REQUIRED_VARIABLES` failure | The **write path** — the failure is the signal `on_write_file_request` uses to seed the first index and retry on collision. Nothing else should override the default. | | `RENDER_SEQUENCE_PATTERN` | `###` (or `####`, matching source width) | **Presentation only.** Renders the slot as its bare hash glyphs (the universal ffmpeg / Houdini / Nuke convention) so the resulting path reads as its on-disk shape. Never open, write, or hand this string to any I/O primitive — the pattern is not a valid filesystem path. | | `START_AT_ZERO` | `000` | Previewing 0-indexed sequences before the first save. | | `START_AT_ONE` | `001` | Previewing "what would my first save land at" — matches the write-path seed, so the preview lines up with the real save when the destination is empty. | Optional slots (`{###?}`) are unaffected — they're already omitted when unbound, so the flag only takes effect on required slots. **Rule of thumb.** If your code is about to open a file, do not pass a flag; let the write path do its thing. If your code is about to show a string to a user, use `RENDER_SEQUENCE_PATTERN`. `START_AT_ZERO` / `START_AT_ONE` are narrow tools for previewing an actual first save. ### String transformations | Format spec | Description | Example result | | ------------------ | ------------------------------------------------ | ------------------- | | `:lower` | All lowercase | `"my autumn shoot"` | | `:upper` | All uppercase | `"MY AUTUMN SHOOT"` | | `:title` | Title Case | `"My Autumn Shoot"` | | `:snake` | snake_case | `"my_autumn_shoot"` | | `:pascal` | PascalCase | `"MyAutumnShoot"` | | `:camel` | camelCase | `"myAutumnShoot"` | | `:screaming_snake` | SCREAMING_SNAKE_CASE | `"MY_AUTUMN_SHOOT"` | | `:slug` | Slug (spaces→hyphens, non-alphanumeric stripped) | `"my-autumn-shoot"` | | `:dot` | dot.case | `"my.autumn.shoot"` | | `:abbrev` | First letter of each word | `"MAS"` | | `:trim` | Strip leading/trailing whitespace | `"My Autumn Shoot"` | For example, if `workflow_name` is `"My Autumn Shoot"`: ``` {workflow_name:lower} → "my autumn shoot" {workflow_name:upper} → "MY AUTUMN SHOOT" {workflow_name:title} → "My Autumn Shoot" {workflow_name:snake} → "my_autumn_shoot" {workflow_name:pascal} → "MyAutumnShoot" {workflow_name:camel} → "myAutumnShoot" {workflow_name:screaming_snake} → "MY_AUTUMN_SHOOT" {workflow_name:slug} → "my-autumn-shoot" {workflow_name:dot} → "my.autumn.shoot" {workflow_name:abbrev} → "MAS" ``` `:snake`, `:pascal`, `:camel`, `:dot`, and `:screaming_snake` also handle camelCase and PascalCase input correctly by splitting on case transitions, so `{varName:snake}` → `"var_name"` works as expected. `:trim` is most useful as a pre-processing step before another transformation, e.g. `{name:trim:snake}` strips surrounding whitespace and then converts to snake_case. ### Default value ``` {variable_name|default_value} ``` If the variable is not provided, `default_value` is used instead. ``` {workflow_name|untitled} → uses "untitled" if workflow_name is not provided ``` ### Chaining format specs Multiple format specs are separated by `:` and applied left to right. If a separator is used, it must come first: ``` {variable_name:_:lower} → lowercase value with underscore appended {variable_name:lower:slug} → lowercase, then slug ``` ### Quoted separators If your separator text matches a keyword like `lower` or `upper`, wrap it in single quotes to treat it as a literal separator: ``` {variable_name:'lower'} → appends the text "lower" as a separator ``` ## Resolution When a macro is resolved, directory names and builtin variables are supplied automatically by the project system. You only need to provide the variables specific to your operation (like `file_name_base` and `file_extension`). For example, resolving the `save_node_output` situation macro: ``` Template: {outputs}/{sub_dirs?:/}{node_name?:_}{file_name_base}{_index?:03}.{file_extension} Automatic: outputs → resolved from the "outputs" directory definition → "outputs" Provided: node_name="StyleTransfer", file_name_base="portrait", _index=3, file_extension="png" Result: outputs/StyleTransfer_portrait003.png ``` Directory names (like `outputs`) are automatically resolved to their configured paths. See [Directories](https://docs.griptapenodes.com/guides/projects/directories/index.md). Builtin variables (like `workflow_name`, `project_dir`) are also supplied automatically. See [Environment & Builtin Variables](https://docs.griptapenodes.com/guides/projects/environment/index.md). ## Reverse matching The macro system can also work in reverse: given an actual path and a macro template, it can extract the values of the variables. This is used when the system needs to identify whether a file belongs to a known project directory and what metadata is encoded in its name — e.g. the `create_versioned_workflow` save path re-reads an existing file's name to figure out which `_index` to bump. The public API is `ParsedMacro.extract_variables(path, known_variables, secrets_manager)` (or `matches(...)` for a boolean check). Basic example: ``` Template: {outputs}/{node_name?:_}{file_name_base}{_index?:03}.{file_extension} Path: outputs/StyleTransfer_portrait003.png Extracted: outputs="outputs", node_name="StyleTransfer", file_name_base="portrait", _index=3, file_extension="png" ``` ### How extraction decides where each variable ends For a template like `{a}/{b}/{c}.{ext}` the extractor walks left to right. Each variable's value ends at the next **anchor** — a fixed piece of text that must appear in the path. Two kinds of anchors exist: - **Next-segment anchor.** The text of the following static segment (e.g. `.png`), or the leading-separator prefix of the following variable (e.g. `_v` inside `{###?:^_v}`). - **Self-anchor.** The variable's own trailing separator (e.g. the `_` in `{node_name?:_}`). The extractor uses whichever produces the tightest, self-consistent split. When both are available, the search direction depends on what follows: if the next segment is a static, take the LATEST self-anchor before the static (tightest right-edge split); if the next segment is another variable, take the FIRST self-anchor (leave room for the following variable to consume its share). This is what makes `{a?:_}{b?:_}file.png` on `first_second_file.png` split into `a=first, b=second` rather than `a=first_second, b=""`. ### Optional variables (`?`) — how ambiguity is resolved An optional variable may or may not have emitted at the time the path was written. Reverse-matching enumerates **all 2ᵏ combinations** of "each optional emitted vs. omitted" (where *k* is the number of optional-unbound variables in the template), extracts each combination, and validates via forward round-trip: the extracted values are re-resolved through the template and the result must match the input path byte-for-byte. Combinations are tried in **popcount-descending** order — the richest interpretation (most optionals emitted, most information recovered) is preferred over lossier readings. The first combination whose round-trip succeeds wins. Worked example — the canonical case: ``` Template: {workspace_dir}/{sub_dirs?:/}{file_name_base}{###?:^_v}.{file_extension} Known: workspace_dir="/ws", file_extension="py" Path: /ws/my_flow_v001.py Attempt 1 (both optionals emitted): sub_dirs="my_flow", file_name_base="", _index=1 → resolves to "/ws/my_flow/_v001.py" — MISS. Attempt 2 (sub_dirs on, _index off): sub_dirs="my_flow_v001", file_name_base="" → resolves to "/ws/my_flow_v001/.py" — MISS. Attempt 3 (sub_dirs off, _index on): file_name_base="my_flow", _index=1 → resolves to "/ws/my_flow_v001.py" — MATCH ✓ ``` The 4th combination is not attempted once the 3rd wins. **Consequences worth knowing:** - An emitted optional that captures the empty string is rejected before the round-trip runs — capturing an empty value contradicts the "the slot emitted" assumption. - Round-trip validation catches greedy misreads: an extraction that resolves to a different string than the input path can never win. This is why the extractor's greedy anchor choices are safe — a wrong choice fails to round-trip and the next combination is tried. - When multiple combinations round-trip, the highest-popcount one wins. Ties within a popcount are broken by mask value (ascending), which is deterministic but has no semantic meaning — see "Building unambiguous templates" below. ### Building unambiguous templates Two variables with no fixed text between them are grammatically ambiguous when both are unbound — the grammar has no way to say which side of the ambiguous boundary a character belongs to. Reverse-matching will still return *some* valid answer (any reading that round-trips is legal), but which specific reading isn't predictable across templates. **Design guidance:** - Put a **static separator** (or a distinctive leading-separator prefix) between adjacent variables when you want reliable reverse-matching. `{name}_{version}` is unambiguous; `{name}{version}` isn't. - The **sequence-slot form with a leading separator** — `{###?:^_v}` — is the recommended pattern for versioned filenames. The `_v` prefix is a distinctive anchor that lets the extractor find the version boundary regardless of what comes before it. - Pre-supply values you know via `known_variables`. Every known variable removes one dimension from the 2ᵏ search and eliminates one source of ambiguity. ### Format specs on the reverse path Not every format spec has a lossless inverse. When a spec can't be reversed unambiguously the extractor returns the raw string and lets the caller decide what to do with it. | Format spec | Reversal behavior | | ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `:03` (numeric padding) | Parsed as an integer (`"005"` → `5`). A non-numeric value fails the spec and disqualifies that extraction attempt. | | `{###}` / `{###?}` (sequence slot) | Same as numeric padding — parses to `int`. When bound, the value is available as `_index`. | | `:_` (trailing separator) | Strips the trailing separator if present. Idempotent when absent. | | `:^_v` (leading separator) | Strips the prefix if present. Idempotent when absent. When followed by a preceding variable's extraction, the prefix also serves as an anchor. | | `:lower` / `:upper` / `:title` / … | Case transformations return the extracted substring as-is; the original casing is not recoverable. Callers that need round-trip fidelity should avoid these on match keys. | | `:slug` / `:snake` / `:pascal` / … | Same — one-way transforms return the raw extracted value. | | `\|default_value` | Defaults are a forward-only construct. On the reverse path the variable is either extracted from the path or left unbound; the default text is never re-injected. | ### Practical limits - Reverse-matching caps the number of optional-unbound variables at 5 (2⁵ = 32 combinations). Templates with 6 or more optional-unbound variables raise `MacroParseFailureReason.TOO_MANY_OPTIONAL_VARIABLES`. Real-world templates in the tree top out at 3 optionals; the cap exists to prevent runaway work on pathological grammars. - The cap counts only optionals that aren't bound by `known_variables`. Pre-binding an optional removes it from the search space, so a template with 6 optional variables can still reverse-match if all 6 are supplied by the caller. - Path matching is **byte-exact**. Cross-platform callers should normalize path separators to forward slashes before calling `extract_variables` — the built-in project-directory match handler does this automatically for auto-resolved directory builtins. ## Syntax errors The macro parser reports syntax errors with a position number to help you find the problem: - Unclosed brace: `{variable_name` (no closing `}`) - Unmatched closing brace: `variable}name` - Nested braces: `{outer{inner}}` - Empty variable: `{}` # Situations A situation is a named file-saving scenario. It defines: - **Where** the file goes (via a macro template) - **How** to handle the case when a file already exists there (via a collision policy) - **What to do** if saving fails (via an optional fallback situation) When a node needs to save a file, it names the situation it's in (for example, `save_node_output`) and the project system resolves the path using that situation's macro and applies the policy. ## Collision policies | Policy | Behavior | | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `create_new` | Increment a counter in the filename until a non-colliding name is found. The macro can include `{_index?:NN}` (optional — absent on the first save, indexed on collision) or `{_index:NN}` (required — indexed from the first save). If neither is present, the system appends `_1`, `_2`, … to the resolved filename on collision. | | `overwrite` | Replace the existing file without asking. | | `fail` | Stop and report an error if the file already exists. | The `create_dirs` field controls whether intermediate parent directories are created automatically (`true`, like `mkdir -p`) or whether a missing parent directory causes an error (`false`). ## Fallbacks A situation can name a fallback situation. If the primary situation cannot resolve its macro (for example, because required variables are missing), the system tries the fallback. The default `save_file` situation is a minimal fallback used by most other situations. ## Default situations ### `save_file` ``` macro: {file_name_base}{_index?:03}.{file_extension} policy: create_new, create_dirs: true ``` Generic file save at the project root (or wherever the caller's path context puts it). This is the fallback for most other situations. The `{_index?:03}` variable is zero-padded and optional — omitted on the first save, then `001`, `002`, … on collision (padded width preserved across the sequence). ### `copy_external_file` ``` macro: {inputs}/{node_name?:_}{parameter_name?:_}{file_name_base}{_index?:03}.{file_extension} policy: create_new, create_dirs: true fallback: save_file ``` Used when a user copies or drags an external file into the project. The file is placed in the `inputs` directory. The node name and parameter name are prepended as optional prefixes to help identify the file's origin. **Example:** ``` node_name="LoadImage", parameter_name="source", file_name_base="photo", file_extension="jpg" → inputs/LoadImage_source_photo.jpg node_name not provided, file_name_base="photo", file_extension="jpg" → inputs/photo.jpg ``` ### `download_url` ``` macro: {inputs}/{sanitized_url} policy: overwrite, create_dirs: true fallback: save_file ``` Used when a node downloads a file from a URL. The URL is sanitized into a safe filename. Files downloaded from the same URL are overwritten rather than duplicated. ### `save_node_output` ``` macro: {outputs}/{sub_dirs?:/}{node_name?:_}{file_name_base}{_index?:03}.{file_extension} policy: create_new, create_dirs: true fallback: save_file ``` Used when a node generates and saves output. Files go into the `outputs` directory. Optional sub-directories (`{sub_dirs?:/}`) allow nesting within outputs. The node name is an optional prefix. **Example:** ``` outputs="outputs", node_name="ImageGen", file_name_base="render", _index=1, file_extension="png" → outputs/ImageGen_render001.png sub_dirs="lighting/pass_a", node_name="ImageGen", file_name_base="render", file_extension="exr" → outputs/lighting/pass_a/ImageGen_render.exr ``` ### `save_preview` ``` macro: {previews}/{drive_volume_mount?:/}{source_relative_path?:/}{source_file_name}.{preview_format} policy: overwrite, create_dirs: true fallback: save_file ``` Used to generate preview thumbnails. Previews mirror the directory hierarchy of the source file so that each source file has exactly one preview. Previews are overwritten rather than versioned. The `previews` directory defaults to `.griptape-nodes-previews` (a hidden folder). ### `save_static_file` ``` macro: {workflow_dir?:/}{static_files_dir}/{file_name_base}.{file_extension} policy: overwrite, create_dirs: true fallback: save_file ``` Used by the static files manager to save static assets. Files go into the `static_files_dir` subdirectory of the current workflow's directory. These files are overwritten when regenerated. ### `save_temp_file` ``` macro: {temp}/{node_name?:_}{file_name_base}{_index?:03}.{file_extension} policy: overwrite, create_dirs: true fallback: save_file ``` Used when a node needs to write an intermediate or scratch file during processing (for example, a temporary EXR written between color-space conversion steps). Files go into the `temp` directory and should be deleted by the node after use. ### `save_workflow` ``` macro: {workspace_dir}/{sub_dirs?:/}{file_name_base}.{file_extension} policy: overwrite, create_dirs: true fallback: save_file ``` Used when a workflow is saved. The workflow file goes into the workspace root, preserving any sub-directory hierarchy via the optional `{sub_dirs?:/}` prefix. Saving a workflow overwrites the existing file rather than versioning it; to produce a numbered sequence of saves instead, see [`create_versioned_workflow`](#create_versioned_workflow) below. **Example:** ``` workspace_dir="/projects/demo", file_name_base="my_workflow", file_extension="py" → /projects/demo/my_workflow.py sub_dirs="archived", file_name_base="my_workflow", file_extension="py" → /projects/demo/archived/my_workflow.py ``` ### `create_versioned_workflow` ``` macro: {workspace_dir}/{sub_dirs?:/}{file_name_base}_v{_index:03}.{file_extension} policy: create_new, create_dirs: true fallback: save_file ``` Used when a workflow is saved with the versioned-save intent. Every save produces a new file with the next padded index in the sequence — `my_workflow_v001.py`, `my_workflow_v002.py`, … — so users can keep snapshots without overwriting earlier work. The version-bump is **macro-driven**: when a versioned save runs, the engine reverse-matches the previous save's path against this situation's macro and extracts every variable the macro defines, including the bound padded slot. The next save reuses those variables verbatim, and the collision walk advances the padded index past any existing files. Because nothing about the version suffix is hardcoded, customizing the macro (e.g. swapping `_v{_index:03}` for `.{_index:04}`) still works — the new pattern becomes the contract for both forward saves and reverse-matches. > **Tip:** Custom projects can switch the auto-index slot to the more explicit `_v{###}` syntax (see [Sequence slot (`{###}`)](https://docs.griptapenodes.com/guides/projects/macros/#sequence-slot)). It behaves identically to `{_index:03}` for the default 3-digit case and overflows naturally past `999` instead of staying zero-padded. This situation is selected at the API layer by passing `create_versioned=True` on `SaveWorkflowRequest`; the UI exposes it as a separate menu item (e.g. "Save New Version"). See [Macros — Numeric padding](https://docs.griptapenodes.com/guides/projects/macros/#numeric-padding) for the auto-index contract. > **Note**: Customizing `save_workflow` to use `create_new` directly (instead of using `create_versioned_workflow` + the flag) emits a warning at save time. The configuration still works — the first save lands at `_v001` — but every subsequent save hits the in-place overwrite branch and writes back to `_v001` rather than advancing to `_v002`. Use `create_versioned_workflow` for true versioning. **Example:** ``` workspace_dir="/projects/demo", file_name_base="my_workflow", file_extension="py" First save → /projects/demo/my_workflow_v001.py Second save → /projects/demo/my_workflow_v002.py Third save → /projects/demo/my_workflow_v003.py ``` ## How nodes use situations A node's situation is chosen by whoever wrote the node, not by you. Nodes that save files use a `ProjectFileParameter`, and the situation name is baked in when that parameter is built. There is **no situation field on the node face**. The node shows a filename parameter (often called **Output File**), and the situation sits behind it. The filename parameter *is* the `ProjectFileParameter`. Whatever you type there becomes the `file_name_base` and `file_extension` variables, and the situation's macro decides where the file actually lands. So typing `render.png` into a node using `save_node_output` produces `outputs/MyNode_render.png`, not `render.png` in the project root. The node supplies the filename pieces; the project system supplies everything else (directory paths, built-in variables). Nearly every generation and save node uses `save_node_output`. The other situations are used by the parts of the system they name: dragging a file in uses `copy_external_file`, a URL download uses `download_url`, thumbnails use `save_preview`, and saving a workflow uses `save_workflow`. ### Finding out which situation a node uses Hover the node's filename parameter. Its tooltip names the situation, for example: ``` Output filename (uses 'save_node_output' situation template) ``` There is no panel that lists every node and its situation, so the tooltip is the reliable way to check a specific node. In a custom node's source, it is the `situation=` argument passed to `ProjectFileParameter`. ### Overriding a situation on one node To change where a single node writes without editing your project file, click the **cog** button on the node's filename parameter. This creates a **File Output Settings** node wired into that parameter, pre-filled with the node's situation and current filename. The File Output Settings node exposes what the situation had hidden, and each field can be changed: - **Situation**: pick any situation, including custom ones from your project file. Changing it reloads the macro and collision policy below. - **Macro**: the path template, editable for this one connection. - **If File Exists**: the collision policy (Increment Version, Overwrite Existing, or Abort / Error). - **Auto Create Path**: whether missing parent directories are created. Anything you set here applies only to the node it is connected to. To change where *every* node writes, edit the situation in your project file instead. ## Adding custom situations See [Customization Guide](https://docs.griptapenodes.com/guides/projects/customization/index.md) for examples. Redefining a default situation such as `save_node_output` in your project file changes the destination for every node that uses it, with no node edits required. Adding a *new* situation only takes effect where something selects it: either a custom node passing `situation=` or a **File Output Settings** node pointed at it. # Environment & Builtin Variables ## Environment The `environment` section of a project file holds custom key-value pairs. These values are available for use in macros and directory `path_macro` fields. ``` environment: RENDER_STYLE: "realistic" CLIENT_CODE: "ACME" ``` ### Overlay behavior Environment entries from your project file are merged on top of the system defaults. If a key exists in the defaults, your value replaces it. New keys are added. ### Referencing other variables from an environment value Environment values are macros themselves. A value can reference a builtin, a directory, another project environment variable, or a shell environment variable by using `{NAME}`: Assume the shell that launched Griptape Nodes has `SHARED_DRIVE=/mnt/renders` exported. Then: ``` directories: outputs: # Reference a shell env var directly — no need to declare it under `environment:` path_macro: "{SHARED_DRIVE}/outputs" environment: CLIENT_CODE: "ACME" # Reference a builtin PROJECT_RENDERS: "{project_dir}/renders" # Compose a shell env var, a project env var, and literal text CLIENT_RENDERS: "{SHARED_DRIVE}/{CLIENT_CODE}/renders" ``` References are resolved recursively, following the priority order in [Variable priority](#variable-priority). Cycles (for example, `A: "{B}"` and `B: "{A}"`) are detected and surfaced as macro resolution errors. #### Legacy `$VAR` syntax For backwards compatibility, an environment value that is **exactly** `$NAME` (no surrounding text, no suffix, no other macros) is expanded using the operating-system environment **when the value is consumed by a macro**: ``` environment: OUTPUT_ROOT: "$RENDER_FARM_SHARE" # works in macros: {OUTPUT_ROOT} -> /mnt/renders ``` Important limitations — the `$VAR` form is narrow and has known gaps: - **Whole value only.** Anything appended (for example `"$SHARED_DRIVE/outputs"`) or any adjacent text is **not** expanded and is treated as a literal string. Macro resolution will fail looking up the entire trailing string as a secret name. - **Macro-only, not process.** `$VAR` is only expanded at macro resolution time. It is **not** expanded when the value is written to `os.environ` — subprocesses and nodes calling `os.environ.get("OUTPUT_ROOT")` will see the literal string `"$RENDER_FARM_SHARE"`, not the expanded value. - **Cannot be composed.** A `$`-prefixed value cannot reference another project env var, a builtin, or a directory. Prefer the `{NAME}` form for all new projects — it composes cleanly, expands consistently in macros **and** `os.environ`, and works in both `environment` values and directory `path_macro` fields. ## Builtin variables Builtin variables are automatically available in all macros. You do not define them — the system provides their values at runtime. They cannot be overridden. | Variable | Type | Description | | ------------------ | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `project_dir` | directory | Absolute path to the project base directory (the folder containing `griptape-nodes-project.yml`, or the workspace directory when no project file is present) | | `workspace_dir` | directory | Absolute path to the workspace directory (defaults to the project directory when no explicit workspace is configured; see [Workspace](https://docs.griptapenodes.com/guides/projects/workspace/#config-resolution-order)) | | `workflow_name` | string | Name of the currently running workflow | | `workflow_dir` | directory | Absolute path to the directory containing the current workflow file, or — for a workflow that has not been saved yet — the folder it was created in, when the editor supplied one | | `static_files_dir` | string | Name of the static files subdirectory (from settings, defaults to `staticfiles`) | ### How builtins are resolved Builtins are resolved at the moment a macro is evaluated — not when the project file is loaded. This means: - `workflow_name` and `workflow_dir` reflect whichever workflow is currently executing - `project_dir` reflects the actual path of the loaded project file - `workspace_dir` reflects the project directory when no explicit workspace is configured, otherwise the value from the project-adjacent config or environment variable If a builtin variable is required but cannot be resolved (for example, `workflow_name` when no workflow is running), macro resolution fails with an error. If the variable is optional (marked with `?`), the block is silently omitted instead. ### Builtin variables in situation macros The `save_static_file` situation uses `workflow_dir` and `static_files_dir`: ``` {workflow_dir?:/}{static_files_dir}/{file_name_base}.{file_extension} ``` If `workflow_dir` is available, the static files go into a subdirectory of the workflow folder. If not, the `{workflow_dir?:/}` block is omitted and the path becomes workspace-relative. ### Unsaved workflows A workflow that has never been saved has no file, so there is no directory to derive `workflow_dir` from. When you create a workflow while browsing a folder, the editor tells the engine which folder that was, and `workflow_dir` answers with it until the workflow is saved. Files you generate before the first save land in that folder rather than at the workspace root. Once the workflow is saved, `workflow_dir` switches to the directory of the saved file — which may be somewhere else, if you saved it to a different folder. The files themselves stay where they were written, but any stored references built on `{workflow_dir}` now resolve into the new folder, so outputs generated before the save may appear missing from the node even though the bytes are on disk. If the editor did not supply a folder, `workflow_dir` stays unavailable until the first save, and `{workflow_dir?:/}` is omitted as described above. ## Variable priority When a macro is resolved, variables are supplied from these sources in priority order: 1. **Builtin variables** — always win; cannot be overridden by any other source 1. **Directory names** — resolved from the project's directory definitions; cannot be overridden by caller-supplied variables 1. **Caller-supplied variables** — values passed by the node or operation requesting path resolution 1. **Derived variables** — computed from the variables above plus project state, and injected before the situation macro is resolved. A derived variable abstains when the caller has already supplied its value, so caller-supplied entries still win. 1. **Project variables** — values from the project's `variables:` block (see [Project Variables](https://docs.griptapenodes.com/guides/projects/variables/index.md)). Only string and integer values participate. 1. **Project environment variables** — values from the project's `environment:` block. Recursively resolved, so a project env value can reference builtins, directory names, other project env vars, or shell env vars. 1. **Shell environment variables** — final fallback. Any variable set in the shell that launched Griptape Nodes (including `HOME`, `USER`, or anything the user exported) can be referenced with `{NAME}` in a macro. A project env var of the same name always wins. Reserved names (builtins, directories) silently win over the shell, since shells have many incidental variables that shouldn't shadow project state. If a caller tries to supply a value for a builtin or directory name that differs from the system value, the resolution fails with a `RESERVED_NAME_COLLISION` error. ### Derived variables | Variable | Derived from | Source of truth | | -------------------------- | ------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------- | | `file_extension_directory` | `file_extension` plus the project's `file_extension_directories` mapping | See [File Extension Directories](https://docs.griptapenodes.com/guides/projects/file_extension_directories/index.md) | # Project Variables The `variables` section of a project file declares named values that belong to the project. They join the same `{VAR}` substitution system as workflow variables and can be used in node parameter values and in macros. ``` variables: shot_code: value: sc042 frame_start: value: 1001 type: int facility: value: mtl permission: read_only ``` ## Fields | Field | Required | Default | Meaning | | ------------ | -------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `value` | yes | — | The variable's value. Must be a string or an integer, matching the declared `type`. | | `type` | no | `str` | `str` or `int`. The value must agree with it — declaring `type: int` with a quoted string is a load error. | | `permission` | no | `read_write` | `read_write` variables can be changed at runtime (and the change is saved back to this file). `read_only` variables can only be changed by editing the project file. `write_only` is accepted but not yet fully enforced — see below. | Booleans and floats are not supported as variable values. Only string and integer values can substitute into a `{VAR}` token. `write_only` is not yet read-restricted A `write_only` variable accepts runtime writes like `read_write`, but the engine does not yet hide its value on reads — today it behaves like `read_write` everywhere a value is displayed or substituted. **Do not use it to hold secrets yet.** Read restriction is planned as part of upcoming secrets support; when that lands, `write_only` values will stop appearing in reads, pickers, and `{VAR}` substitution. ## How project variables resolve Variables are looked up in layers, with closer layers winning on a name conflict: 1. **Workflow (flow) variables** — variables created in the workflow editor 1. **Project variables** — this section, plus the project's builtins and directories 1. **Global variables** Within the project layer, **builtins and directory names always win** over a `variables:` entry of the same name. Declaring a variable named `workspace_dir` or the name of one of your directories produces a load warning, and that variable can never resolve — pick a different name. Builtin and directory names are also **reserved**: the engine refuses to create or rename any workflow or global variable to one of those names. ## Runtime writes and persistence A `read_write` project variable can be changed while the engine runs — from the variable panel or by a node that sets variables. Each successful change is immediately saved back to the project file, so it survives restarts. Writes are checked against the declared type: setting a string value on an `int` variable is refused with an error rather than silently coerced. `read_only` variables refuse all runtime writes. To change one, edit the project file and reload the project. ## Project variables in macros Project variables participate in path macro resolution (for example in a directory `path_macro` or a situation macro), below caller-supplied values and above the project `environment:` section: 1. Builtin variables 1. Directory names 1. Caller-supplied variables 1. **Project variables** (this section) 1. Project environment variables 1. Shell environment variables See [Environment & Builtin Variables](https://docs.griptapenodes.com/guides/projects/environment/#variable-priority) for the full priority discussion. ## Inheritance When a project declares a parent (`parent_project_path` / `parent_project_id`), variables merge per-entry: - A child entry with the same name **replaces** the parent's entry entirely. - A child entry set to `null` **removes** the inherited variable: ``` # child project.yml variables: shot_code: value: sc099 # overrides the parent's shot_code facility: null # removes the inherited facility variable ``` - Parent entries the child doesn't mention are inherited unchanged. Deleting an inherited variable at runtime writes the `null` tombstone into the child's file, so the removal also survives reloads. ## Choosing between `variables` and `environment` Both are project-scoped key-value sections; they serve different consumers: - **`variables`** entries are first-class engine variables: they appear in the variable panel and pickers, carry a type and a permission, can be changed at runtime, and participate in `{VAR}` substitution inside node parameters. - **`environment`** entries exist for macro composition (paths, codes) and OS environment export. They are strings only, are not runtime-writable, and don't appear as variables in the editor. Rule of thumb: if an artist should see or change it, make it a variable; if it's plumbing for path macros, keep it in `environment`. # File Extension Directories `file_extension_directories` is a project-template mapping from file extension to a folder fragment used to route files by type. When a situation macro references the derived variable `{file_extension_directory}`, the project system looks up the file's extension in this table and substitutes the associated value. Typical use: keep images, videos, audio, and documents in separate subfolders under `outputs/` without writing a separate situation for each type. ## Quick example ``` project_template_schema_version: "0.3.0" name: "My Project" file_extension_directories: png: "images" jpg: "images" mp4: "videos" wav: "audio" situations: save_node_output: macro: "{outputs}/{file_extension_directory?:/}{node_name?:_}{file_name_base}{_index?:03}.{file_extension}" ``` With this configuration: ``` file_extension="png" → outputs/images/Node_render.png file_extension="mp4" → outputs/videos/Node_render.mp4 file_extension="xyz" → outputs/Node_render.xyz (unmapped: slot collapses) ``` The `?:/` on `{file_extension_directory?:/}` makes the slot optional and adds a trailing `/` when the value is present — so unmapped extensions land at the situation's directory root instead of failing. ## Two value forms A value can be either a **plain name** or a **macro**. ### Plain name ``` file_extension_directories: png: "images" ``` The string is used verbatim. No resolution happens. This is the common case and has zero overhead. ### Macro value ``` file_extension_directories: mp4: "{outputs}/videos" wav: "{workspace_dir}/shared/audio" ``` A value containing `{...}` is resolved against the project's builtins, directory definitions, and any caller-supplied context (like `node_name`) before being substituted into the situation macro. Macro values are what let a single `file_extension_directories` table reroute certain types to entirely different roots — e.g. video to a share drive — without writing a separate situation per type. ### What a macro value can reference | Source | Example | Available? | | ----------------------- | -------------------------------------------------------------------------------------------- | ---------------------------------------- | | Builtin variables | `{workspace_dir}`, `{workflow_dir}`, `{project_dir}`, `{project_name}`, `{static_files_dir}` | Yes | | Directory definitions | `{outputs}`, `{inputs}`, `{temp}`, any custom directory | Yes | | Caller-supplied context | `{node_name}`, `{parameter_name}`, `{sub_dirs}`, `{_index}` | Yes | | Filename parts | `{file_name_base}`, `{file_extension}` | **No** — routing is not a filename layer | Filename parts are excluded intentionally: `file_extension_directories` is a *routing* layer that decides which folder a file lands in. Filenames belong to the situation macro's filename section. ## How resolution works `file_extension_directory` is a **derived variable**. It is not a builtin, and callers don't supply it directly. Instead, the project system runs a small derivation rule whenever the situation's macro template references it: 1. The caller names a situation and provides variables (including `file_extension`). 1. Before the situation macro is resolved, the derivation rule fires: - If `file_extension_directory` is already set by the caller, the rule abstains (caller wins). - Otherwise, the rule looks up `file_extension` (case-insensitive) in the current project's `file_extension_directories` table. - If the value is plain, it becomes the variable's value directly. - If the value is a macro, it is resolved to a concrete path string first. 1. The resulting value is injected into the variable bag and the situation macro is resolved as usual. If lookup fails (empty extension, no project loaded, unmapped extension, or resolution error) the variable is simply not set. Situation macros that use the optional form `{file_extension_directory?:/}` degrade cleanly to no folder prefix; macros using the required form `{file_extension_directory}` fail resolution, as they would for any missing required variable. ## Interaction with the situation macro There is no special-case engine logic that prepends or reparents the routing prefix. What you get is exactly what the situation macro template says you get. | Situation macro shape | Routing behavior | | ------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | | `{outputs}/{file_extension_directory?:/}{file_name_base}.{ext}` | Routing is a **subfolder** under `{outputs}`. Values must be relative. | | `{file_extension_directory?:/}{file_name_base}.{ext}` | Routing dictates the **root**. Values may be absolute to redirect away from `{outputs}` entirely. | | `{outputs}/{file_extension_directory?:/}...` with an absolute value | String concatenation — `outputs//Volumes/share/videos/foo.mp4` — not what you want. | Choose the situation-macro shape that matches the kind of routing you need. ## Overlay merge behavior `file_extension_directories` merges entry-by-entry, identically to `environment`: - Keys not present in the overlay are inherited from the base. - Keys present in the overlay override the base entry for that extension. - Keys with a `null` value in the overlay are tombstoned — the base entry is dropped. ``` # Inherit the base's image routing, send mp4 somewhere else, drop csv routing. file_extension_directories: mp4: "{workspace_dir}/shared/videos" csv: null ``` ## Caller override Any caller can pre-populate `file_extension_directory` in the variables bag. When it's already set, the derivation rule abstains and the caller's value is used as-is. This is how UI-level settings (like an explicit output-folder override) bypass the taxonomy while still participating in the same situation macro. ## What's in the defaults The system defaults ship with entries for common image, video, audio, text, and Python source extensions, routing each to `images`, `videos`, `audio`, `text`, and `python` subfolders respectively. You can override individual entries, add new ones, or tombstone any you don't want. # Sequences Griptape Nodes can read directories of numbered files as **sequences** — for example, a render output like `render.0001.exr`, `render.0002.exr`, … `render.0100.exr`, but also dialogue takes (`take_##.wav`), text chunks (`chapter_###.md`), or anything else where a numeric key in the filename groups items into an ordered set. You point a sequence-aware node at a *path or pattern* (a filename with a placeholder for the number, or just a literal file path), and the engine finds the matching items on disk, handles gaps, and gives you back a list of entries with their integer numbers and zero-padded string forms. This page explains the path/pattern syntax, the policies for handling missing items, and the conventions you should know before authoring one. ## Pattern syntax A sequence pattern looks like a filename with a token in place of the item number. Four token forms are supported: | Token | Width | Notes | | ------ | ----- | ------------------------------------------------------------- | | `####` | 4 | Each `#` is one digit. `##` = 2-digit, `####` = 4-digit, etc. | | `%04d` | 4 | C-style printf. `%04d` = 4-digit zero-padded. | | `@@@@` | 4 | Houdini/RV style. Same meaning as `####`. | | `$F4` | 4 | Houdini variable. Same meaning as `####`. | All four are equivalent; pick whichever matches your pipeline conventions. We recommend `####` or `%04d` for new templates — they're the most widely understood across DCC tools. A few examples: ``` render.####.exr item 5 → render.0005.exr render.%04d.png item 12 → render.0012.png take_##.wav item 7 → take_07.wav ``` The token always sits in the **filename**. Tokens inside directory components (e.g. `render/####/beauty.exr`) are not supported — keep the number in the filename portion only. ## When the path has no sequence token A path with no sequence token (`/work/photo.png`, `{inputs}/poster.png`, `render.0002.png`) is *ambiguous*. The artist might mean: 1. **the literal name of one specific file** — `render.0002.png` is the file I want; 1. **one frame of an implicit sequence** — fileseq sees the digits and groups every `render.NNNN.png` it finds into a single sequence; 1. **an oversight** — the artist forgot to type `####`, and the right answer is to fail loudly so they fix it. The engine asks the caller which interpretation to use via `NoTokenBehavior` (in `griptape_nodes.common.sequences`): | Value | What you get | | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `SINGLE_FILE` *(default)* | Treats the whole filename as a literal. A 1-item sequence (`first=last=1, padding=0`) when the file exists; an empty result when it doesn't. Sibling files in the same directory are ignored — `render.0002.png` returns just `render.0002.png` even if `0001..0005` are next to it. | | `EXPLORE_SEQUENCE` | Lets fileseq read digits in the filename as an implicit sequence token. `render.0002.png` becomes one frame of an inferred `render.####.png` sequence; the scan walks every matching sibling. Useful when a downstream tool gave you one filename but you want the whole take. | | `REJECT` | Fails fast with `INVALID_TEMPLATE` and a message asking the artist to add a token. Strict mode for pipelines that should never silently widen the artist's intent. | Defaulting to `SINGLE_FILE` keeps the path → sequence-of-one mapping that "I picked one file" intuitively expects. Workflows that want the implicit-grouping behavior opt in explicitly. ## Combining with macros Sequence paths work alongside the project's [macro language](https://docs.griptapenodes.com/guides/projects/macros/index.md). The macro head is resolved internally so the engine can read the right directory off disk, but the engine emits paths in the shape you supplied: ``` input : {inputs}/shot_a/render.####.exr output : Sequence(directory="{inputs}/shot_a", entries=[{path: "{inputs}/shot_a/render.0001.exr"}, {path: "{inputs}/shot_a/render.0002.exr"}, ...]) ``` This is what makes scan results portable. A workflow built on a machine where `{inputs}` resolves to `/Volumes/Renders` produces sequences whose paths still say `{inputs}` — re-opening that workflow on a machine where `{inputs}` resolves to `C:\renders` still works, because every consumer downstream resolves the macro fresh against its own project. Plain absolute paths (no `{...}` segments) round-trip identically — you put `/work/render.####.png` in, you get `/work/render.0001.png` back. **Relative paths.** A path with no leading `/` and no macro head — e.g. `shot_a/render.####.png` — is interpreted relative to the project's workspace directory. The engine prepends the workspace path before listing, so `shot_a/render.####.png` and `{workspace_dir}/shot_a/render.####.png` resolve identically. Use whichever form reads more naturally for the workflow. Macro variables inside `{...}` are completely separate from sequence tokens — they don't share syntax, and they're resolved at different stages. ## Width matching is strict The number of `#` characters (or the `%0Nd` width) declares the **exact** number of digits to match. If your pattern says `####`, the engine matches files with exactly 4 digits in the slot — `render.0001.exr` matches, but `render.001.exr` (3 digits) and `render.12345.exr` (5 digits) do not. This matches what Nuke does. If your sequence has numbers that overflow the declared padding (e.g., a 4-digit pattern but real numbers go above 9999), use a wider pattern (`#####`) to capture them. If a directory contains files with mixed padding widths — for example both `render.0001.png` and `render.001.png` — they'll be treated as **separate sequences**. The engine matches the one whose padding matches your declared template; the others are silently ignored. ## Missing-item policies Real sequences often have gaps — a render that crashed on frame 47, a sparse export that only saved every other take, a chapter that's still unwritten. When you scan a sequence, you choose a **policy** for how those gaps are handled: | Policy | What you get | | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ABORT` | Fail fast. Surfaces a failure carrying the offending item number on the first gap inside `[first, last]`. No Sequence is returned. | | `SPLIT` *(default)* | One sequence per **contiguous run** of present items. A sequence with items 1–5, 8–12, 15 produces three separate sequences. | | `SKIP` | One sequence containing only the present items. Gaps are absent from the output (but visible via the sequence's `missing_numbers` set). | | `FILL_NEAREST` | One sequence covering the full `[first, last]` range. Each missing item is filled with the path of the nearest **earlier** present item (or the nearest later one if no earlier neighbor exists). | Pick `ABORT` when a gap should fail the workflow loudly (e.g. a render that crashed must not silently advance). Pick `SPLIT` when you want to *preserve* the gap structure (each contiguous run is meaningful on its own). Pick `SKIP` when you want a single sparse sequence, or `FILL_NEAREST` for a single dense sequence, regardless of what's actually on disk. **Domain-specific gap rendering belongs to nodes, not the engine.** If you want a black-frame placeholder, a magenta/yellow checkerboard, a silent audio chunk, an empty text chunk, or anything else synthesized in place of a missing item, scan with `SKIP` and walk `missing_numbers` in your node — render whatever your domain calls for. The engine deliberately stops at "this number isn't on disk"; the node owns the rest. ## Subset clipping Sequence-aware nodes accept optional `start` and `end` bounds. When supplied, the scan is clipped to that range: - Items below `start` and above `end` are dropped from the output. - The original disk range is still reported via `discovered_first` / `discovered_last`, so you can see what existed before the clip. - Subset bounds outside the discovered range yield an empty result (Failure). ## What you get back `Sequence` is a Pydantic model — read fields by attribute (`seq.first`, `seq.entries[0].number`). Nodes that operate on sequences should declare their input as `type="Sequence"`; the engine validates the connection by name. Each `Sequence` carries: - **`first`** / **`last`** — the active range (after subset clipping). - **`discovered_first`** / **`discovered_last`** — what was actually on disk, ignoring any subset. - **`padding`** — the declared zero-padding width (e.g. 4 for `####`). - **`pattern`** — the canonical pattern (e.g. `render.####.exr`). - **`directory`** — the directory portion of the path you supplied, in the same shape (macro-form when you supplied a macro, plain absolute otherwise). - **`policy`** — which policy was applied. - **`entries`** — one `SequenceEntry` per item in the active range. Each has: - `number` — the integer key (e.g. 5). - `padded_number` — the zero-padded form (e.g. `0005`). - `path` — the file path as a string, in the same shape you supplied. A macro-form input round-trips with the macro head intact (`{inputs}/render.0005.exr`); a plain absolute input round-trips identically (`/work/render.0005.exr`). Under `FILL_NEAREST`, gap entries carry the nearest present neighbor's path; cross-check `entry.number in seq.present_numbers` to tell present from filled. - **`present_numbers`** — the set of numbers actually on disk inside `[first, last]`. - **`missing_numbers`** — derived from `present_numbers`: the numbers in the active range that aren't on disk (useful for diagnostics under any policy). ## Cases that are intentionally not supported A few cases are deliberately excluded: - **Negative numbers**: files like `render.-0005.exr` are filtered out at scan time. The dropped count is reported on the resulting sequence. - **Sequence tokens in directory components**: patterns like `render/####/beauty.exr` aren't matched. Put the number in the filename. - **Multi-token patterns**: templates with more than one sequence token (e.g. `v##_f####.exr`, `render.##.##.exr`) are rejected at scan time with a clear error. Use a single token per pattern. - **Time codes**: not yet supported. ## Where this comes from Sequence handling is built on [`fileseq`](https://github.com/justinfx/fileseq), the de facto Python library for VFX-style frame-range parsing. We use it as a parser and number-math library; all filesystem listings still flow through the engine's request bus, so the same workspace permissions, path normalization, and Windows-long-path handling that govern other file operations apply here too. fileseq itself uses "frame" terminology throughout — that's an implementation detail; the public API speaks "items" and "numbers" so the same code can serve any numbered-filename sequence, not just images. ## Public entry point: `ScanSequencesRequest` Scans are dispatched on the engine's event bus, not by importing a function. Send a `ScanSequencesRequest` (defined in `griptape_nodes.retained_mode.events.os_events`) and `await GriptapeNodes.ahandle_request(...)`; the handler resolves any project macros, runs the directory listing, and runs fileseq parsing in a worker thread, so a long-running scan over a deep directory doesn't block the event loop. The request takes a single `path` field. Examples: ``` # Macro-form pattern. The {inputs} head is preserved on every emitted path. ScanSequencesRequest(path="{inputs}/shot_a/render.####.exr") # Plain absolute pattern. Round-trips identically. ScanSequencesRequest(path="/work/render.####.png", policy=MissingItemPolicy.SKIP) # Token-less path. Default `no_token_behavior=SINGLE_FILE` returns a one-item # sequence containing exactly that file (or empty if it doesn't exist). ScanSequencesRequest(path="/work/photo.png") # Token-less path, but treat it as one frame of an implicit sequence and # walk every matching sibling. ScanSequencesRequest( path="/work/render.0002.png", no_token_behavior=NoTokenBehavior.EXPLORE_SEQUENCE, ) # Strict mode: fail with INVALID_TEMPLATE if the path has no token. ScanSequencesRequest( path="/work/render.0002.png", no_token_behavior=NoTokenBehavior.REJECT, ) # Active range subset. ScanSequencesRequest(path="{inputs}/render.####.exr", start_number=10, end_number=50) ``` Success returns `ScanSequencesResultSuccess` carrying: - **`sequences: list[Sequence]`** — the inferred sequences, post-policy. Every `Sequence.directory` and `entry.path` keeps the same path shape you supplied (macro-form in, macro-form out). - **`has_entries: bool`** — true iff at least one Sequence has at least one entry. A scan that ran cleanly but found nothing is *success with `has_entries=False`*, not failure — callers that need to fail-fast on empty results check `has_entries` themselves. - **`directory_had_matching_files: bool`** — true iff the directory listing produced at least one file whose basename + extension matched the target shape (i.e. the prefilter accepted something). Combined with `has_entries`, it tells you *why* a scan came up empty: false means the path is wrong or the basename/extension doesn't match anything; true with `has_entries=False` means the files are there but the padding doesn't line up *or* the active subset clipped them all out. - **`discovered_first: int | None`** / **`discovered_last: int | None`** — the on-disk range of inferred numbers *before* subset clipping is applied. Populated whenever fileseq inferred at least one number from the directory; `None` if the listing yielded no padding-matching numbers. Lets the caller diagnose subset-clip cases without guessing — e.g. "asked for 90..100 but disk has 1..7" comes straight from these fields. These three diagnostic fields let consumers distinguish wrong-path / wrong-padding / wrong-range cases without inspecting `result_details` strings. Per-Sequence `discovered_first`/`discovered_last` (on each `Sequence` object) are still the right read when you have at least one sequence; the top-level fields are specifically for the empty-result diagnostic. Failure returns `ScanSequencesResultFailure` whose `failure_reason` is either a `SequenceScanFailureReason` (`INVALID_TEMPLATE`, `INVALID_BOUNDS`, `ABORTED_AT_GAP`) or an OS-layer `FileIOFailureReason`. Failures are reserved for cases where the scan couldn't proceed: - `INVALID_TEMPLATE` — the path string couldn't be parsed (bad macro syntax, multi-token pattern, missing filename component, unresolvable macro variables, etc.). Also surfaces when the path has zero sequence tokens and `no_token_behavior` is `REJECT`. - `INVALID_BOUNDS` — `start_number < 0` or `end_number < start_number`. - `ABORTED_AT_GAP` — the `ABORT` policy hit at least one gap inside the active range. The failure populates `missing_item_numbers: list[int]` with every offending integer key, sorted ascending — UI consumers can show the artist *all* the missing slots in one pass instead of fixing them one re-run at a time. - A `FileIOFailureReason` value — the inner directory listing failed (directory not found, permission denied, etc.). These surface via `FileIOFailureReason`, not folded into empty success. ### Node-level: `fail_on_empty_result` The standard library's `ScanSequenceNode` and `ScanSplitSequenceNode` both expose a top-level `fail_on_empty_result: bool = True` parameter. When true (the default), an empty scan result routes the node through its Failure control-flow edge with a diagnostic-aware status message built from the fields above. When false, the node succeeds with empty outputs and a status noting the opt-out — for workflows that legitimately tolerate empty scans (e.g. a sweep that may find nothing). ### Node-level: *When there's no sequence marker* Both Scan nodes also expose the `NoTokenBehavior` choice as a friendly dropdown labelled **When there's no sequence marker (e.g., ###)**, inside the collapsed *Advanced Sequence Control* group. The three options map onto the engine enum: | Dropdown label | Engine value | | ---------------------------------- | ------------------ | | *Treat as a single file* (default) | `SINGLE_FILE` | | *Treat as part of a sequence* | `EXPLORE_SEQUENCE` | | *Fail unless a token is present* | `REJECT` | The same dropdown also drives the relative-bounds discovery probe inside the node (when *Start at* / *End at* is set to *Relative …*), so a literal-file path doesn't get explored just because the node is sniffing for offsets. Library and node code should never import the underlying scanner directly — the request bus is the only public path. # Pinning engine and library versions This guide is for administrators who distribute a project and need it to run against a known-good engine version and a known-good set of library versions. Pinning makes the project the source of truth: when a user activates it, the engine refuses to run under an incompatible version and provisions each pinned library to the version the project declares. Both pins live in the **project-adjacent config** — a `griptape_nodes_config.json` placed next to the project's `griptape-nodes-project.yml`. The project YAML itself carries no version data; the adjacent config does. See [Workspace](https://docs.griptapenodes.com/guides/projects/workspace/#config-files) for how this file layers on top of user config. ``` /MyProject/ griptape-nodes-project.yml <- the project (no version pins here) griptape_nodes_config.json <- engine + library pins live here ``` Distribute both files together. Because the adjacent config layers above the user's global config but below their workspace config, your pins apply to every user who activates the project without being baked into their machine-wide settings. ## Pin the engine version Set `requires_engine` to a [PEP 440 version specifier](https://peps.python.org/pep-0440/#version-specifiers). When the project is activated, the running engine's version must satisfy the specifier or activation is blocked. ``` { "app_events": { "on_app_initialization_complete": { "requires_engine": ">=0.80,<1.0" } } } ``` - The specifier is matched against the running engine's version. - A mismatch **blocks activation**: the project does not load and the user is told which version is required versus which is running. - Omit the key (or set it to `null`) to skip the engine check entirely. Use a bounded range (`>=0.80,<1.0`) rather than an open lower bound so the project does not silently activate under a future major engine release that may have changed behavior. ## Pin library versions `libraries_to_download` lists the libraries the engine provisions on the project's behalf. Each entry can be either a bare git URL string (today's behavior — clone from source, no version enforcement) or an object that adds a version pin: ``` { "app_events": { "on_app_initialization_complete": { "libraries_to_download": [ { "name": "Griptape Nodes Library", "version": "==0.79.0", "git_url": "griptape-ai/griptape-nodes-library-standard@v0.79.0" } ] } } } ``` | Field | Required | Meaning | | --------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `git_url` | Yes | Git source in `url@ref` form: a full URL or `user/repo` shorthand, with an optional `@branch\|tag\|commit` suffix. No `@ref` uses the repository's default branch. | | `version` | No | PEP 440 specifier the installed library must satisfy (e.g. `==0.79.0`, `>=1.2,<2`). Omit to pin by source only. | | `name` | No | The library's manifest `name`. When set, the installed copy is matched by name to decide whether a re-download is needed. | Pin both the `git_url` ref **and** the `version` to the same release (e.g. `@v0.79.0` and `==0.79.0`) so the source you clone and the version you enforce cannot drift apart. ### Only downloaded libraries are overwritten A library listed in `libraries_to_download` is the only kind the engine will **overwrite** to satisfy a pin. A library that is merely registered (listed in `libraries_to_register` by path) is loaded as-is and is **never** overwritten by project activation. If you want a project to be able to enforce a library version, the library must be in the download list, not just the register list. You do not need to also add the library to `libraries_to_register`: after a successful download the engine appends the resolved manifest path to the register list automatically, so the download-then-load chain works on its own. ### Where the downloads land `libraries_to_download` says **what** to install and at which version; [`libraries_dir`](https://docs.griptapenodes.com/guides/projects/projects/#libraries-directory) (a field in the project YAML) says **where** those libraries install and resolve. The two compose: each pinned download is provisioned into the project's resolved libraries directory. When a project declares no `libraries_dir` (and inherits none from a parent), downloads land in the workspace-relative `libraries` directory, preserving today's behavior. A shared `libraries_dir` across a project tree means a pinned library downloaded once by the parent is reused by every child rather than re-downloaded. ## What happens on activation When a user activates a pinned project, the engine compares each `libraries_to_download` entry against what is installed and plans one of: | Plan | When | Effect | | ------------- | ------------------------------------------------ | --------------------------------------------------------------------------- | | **SKIP** | The installed version already satisfies the pin | Nothing changes. | | **INSTALL** | The library is not installed yet | Clones the pinned source. Non-destructive. | | **OVERWRITE** | A different, non-satisfying version is installed | Deletes the local library directory and re-clones the pin. **Destructive.** | Before any destructive OVERWRITE runs, the editor shows a read-only **preview** of the full plan and waits for the user to approve it. A denial is a clean no-op: the prior project stays active and no library files change. The preview also reports an engine-version mismatch (`requires_engine`) and blocks approval when the running engine cannot satisfy the pin. ## Worked example A project that requires engine `>=0.80,<1.0` and pins the standard library to `0.79.0`: `/MyProject/griptape-nodes-project.yml` ``` "project_template_schema_version": "1.0.0" "name": "my-pinned-project" "description": "Runs on engine 0.80-0.x with the standard library pinned to 0.79.0." ``` `/MyProject/griptape_nodes_config.json` ``` { "app_events": { "on_app_initialization_complete": { "requires_engine": ">=0.80,<1.0", "libraries_to_download": [ { "name": "Griptape Nodes Library", "version": "==0.79.0", "git_url": "griptape-ai/griptape-nodes-library-standard@v0.79.0" } ] } } } ``` Activation behavior: 1. **Engine check** — if the running engine is outside `>=0.80,<1.0`, activation is blocked with a version-mismatch message. 1. **First activation (clean machine)** — the standard library is absent, so the plan is INSTALL: `v0.79.0` is cloned and registered. 1. **Re-activation, `0.79.0` already present** — the pin is satisfied, so the plan is SKIP. 1. **A different version is installed** (say a prior project left `0.78.0`) — `0.78.0` does not satisfy `==0.79.0`, so the plan is the destructive OVERWRITE: the preview modal shows it, and on approval the local library directory is deleted and `v0.79.0` is re-cloned. ## Notes and gotchas - **Bare strings still work.** An existing `"libraries_to_download": ["user/repo"]` list keeps cloning from source with no version enforcement. Only the object form enforces a `version`. - **Per-user overrides win.** A user's workspace config layers above the project-adjacent config (see [Workspace](https://docs.griptapenodes.com/guides/projects/workspace/#config-resolution-order)). A user can override your pins locally; the pins are defaults distributed with the project, not a lock. - **CLI alternative.** Administrators automating headless engines can clone a library with `griptape-nodes libraries download ` and update with `griptape-nodes libraries sync`. See [Libraries](https://docs.griptapenodes.com/guides/libraries/#cli-alternatives) and the [Command Line Interface](https://docs.griptapenodes.com/reference/command_line_interface/index.md) reference. The declarative config above is the portable way to ship the same pins with a project. # Customization Guide All customizations go in `griptape-nodes-project.yml` in your workspace directory. You only need to include the things you want to change — everything else inherits from the system defaults. ## Changing the outputs directory path Move all node outputs to a folder called `renders`: ``` project_template_schema_version: "0.1.0" name: "My Project" directories: outputs: path_macro: "renders" ``` All situations that use `{outputs}` will now save to the `renders` folder instead of `outputs`. ## Changing the inputs directory path ``` project_template_schema_version: "0.1.0" name: "My Project" directories: inputs: path_macro: "source_files" ``` ## Adding a custom directory Add a directory for client deliverables: ``` project_template_schema_version: "0.1.0" name: "My Project" directories: deliverables: path_macro: "client/final" ``` Now `{deliverables}` is available as a variable in any macro. ## Adding a custom situation Add a situation that saves final renders to the deliverables directory with overwrite policy: ``` project_template_schema_version: "0.1.0" name: "My Project" directories: deliverables: path_macro: "client/final" situations: save_deliverable: macro: "{deliverables}/{workflow_name?:_}{file_name_base}.{file_extension}" policy: on_collision: overwrite create_dirs: true description: "Final deliverable for client" ``` ## Modifying an existing situation Change `save_node_output` to always include the workflow name in the filename: ``` project_template_schema_version: "0.1.0" name: "My Project" situations: save_node_output: macro: "{outputs}/{workflow_name:_}{file_name_base}{_index?:03}.{file_extension}" ``` Only the `macro` field is replaced. The policy, fallback, and description are inherited from the default. ## Adding environment variables Define custom values for use in macros: ``` project_template_schema_version: "0.1.0" name: "My Project" environment: CLIENT_CODE: "ACME" SEASON: "S03" ``` Reference them in a directory path or situation macro: ``` directories: outputs: path_macro: "{CLIENT_CODE}/{SEASON}/renders" ``` ## Routing files by extension Use `file_extension_directories` to land files of different types in different subfolders without writing a separate situation for each type. See [File Extension Directories](https://docs.griptapenodes.com/guides/projects/file_extension_directories/index.md) for the full reference. ``` project_template_schema_version: "0.3.0" name: "My Project" file_extension_directories: png: "images" jpg: "images" mp4: "videos" wav: "audio" situations: save_node_output: macro: "{outputs}/{file_extension_directory?:/}{node_name?:_}{file_name_base}{_index?:03}.{file_extension}" ``` Extensions can also resolve to a macro. To route videos to a share drive while everything else stays under `outputs`, use the situation macro that lets the routing value dictate the root: ``` project_template_schema_version: "0.3.0" name: "My Project" file_extension_directories: png: "{outputs}/images" mp4: "{workspace_dir}/shared/videos" situations: save_node_output: macro: "{file_extension_directory?:/}{node_name?:_}{file_name_base}{_index?:03}.{file_extension}" ``` ## Referencing OS environment variables Pull values from the operating system environment: ``` project_template_schema_version: "0.1.0" name: "My Project" environment: SHARED_DRIVE: "$STUDIO_SHARED_STORAGE" directories: outputs: path_macro: "{SHARED_DRIVE}/renders" ``` If the OS environment variable `STUDIO_SHARED_STORAGE` is set to `/mnt/studio`, then `{outputs}` resolves to `/mnt/studio/renders`. ## Putting it all together A complete customized project file for a visual effects project: ``` project_template_schema_version: "0.1.0" name: "VFX Pipeline" description: "Customized layout for VFX production" environment: SHOW_CODE: "AURORA" directories: inputs: path_macro: "source" outputs: path_macro: "renders" plates: path_macro: "source/plates" deliverables: path_macro: "deliverables/{SHOW_CODE}" situations: save_node_output: macro: "{outputs}/{workflow_name?:_}{file_name_base}{_index?:03}.{file_extension}" save_deliverable: macro: "{deliverables}/{workflow_name?:_}{file_name_base}.{file_extension}" policy: on_collision: overwrite create_dirs: true fallback: save_file description: "Final deliverable" ``` # Workflows # Workflow Variables A **variable** is a named value that lives on a flow instead of on any one node. Create it once, then read or write it from anywhere that value needs to show up — including inline inside text fields, using `{variable_name}`. Not the same as macro path variables [Macros](https://docs.griptapenodes.com/guides/projects/macros/index.md) are the template syntax the project system uses to build **file paths** (`{outputs}/{file_name_base}.{file_extension}`). Workflow variables are user-created values you store and reuse across a workflow's node parameters. The two systems share the same `{name}` / `{name:spec}` syntax and format specs under the hood, and project macros (like `workspace_dir` and `workflow_name`) even show up alongside your own variables when you type `{` in a text field — but a macro path template describes how to build a filename, while a workflow variable is a value you define and change during a run. If you're building a save path, read [Macros](https://docs.griptapenodes.com/guides/projects/macros/index.md) instead. ## When to use a variable Say three prompt fields across your workflow all need the same client name. Wiring a connection from one source node to every field that needs it works, but it clutters the canvas and breaks if you ever want to type the value directly into a field instead of dragging a wire. A variable solves this without any connections: set it once, then reference `{project_name}` inside as many text parameters as you like. Change the variable's value and every field that references it picks up the new value the next time it runs. ## Creating a variable The **Variables** category in the node library has a small family of nodes for creating and managing variables: - **Create Variable** — creates a variable with a name, type, and initial value, or updates it if a variable with that name already exists in the current flow. Connect any output into its `value` input and the variable's type is inferred automatically from that connection. - **Set Variable** — sets a variable's value, creating the variable first if it doesn't exist yet. Its `variable_name` field is a dropdown of variables already in scope, plus a **Create new variable** option that reveals a name field when picked. - **Set Variables from Data** — turns a dict, a JSON/YAML string, or a list of key-value pairs into several variables in one step. Useful when you already have a JSON blob (parsed config, an API response) and want each key to become its own variable instead of wiring up one **Set Variable** node per key. - **Get Variable** — reads a variable's current value out as a normal output, for wiring into a node that doesn't support inline `{name}` substitution. - **Has Variable** — checks whether a variable exists, so you can branch on it (for example, only create a variable the first time a flow runs). **Create Variable** and **Set Variable** always create the variable in the node's own flow (flow-scoped, described below) — there's currently no node that creates a global variable directly. The **scope** setting on these nodes (under **Advanced**) controls where they *look* for an existing variable, not where a new one is created. These nodes treat the variable they touch as live state rather than a resolved-once value. **Create Variable** forces itself back to unresolved every time the workflow or the node runs, so it always re-applies its current `value`. **Get Variable**, **Set Variable**, and **Has Variable** check the variable's actual current value each time they're asked for their resolution state, and mark themselves unresolved if it no longer matches what they last read or wrote — so they pick up a change made elsewhere (another **Set Variable** node, a different flow) without needing a manual run. ## Scopes Every variable request — create, get, set, list — takes a **scope**, which controls where the lookup searches: | Scope | Behavior | | ------------------------ | ----------------------------------------------------------------------------------------------------------------------------- | | `hierarchical` (default) | Search the current flow, then each ancestor flow up to the root, then global variables. The first match wins. | | `current_flow_only` | Only the exact flow the node is in. Ignores parent flows and globals entirely. | | `global_only` | Only global variables — variables not owned by any flow. | | `all` | Every variable from every flow, primarily for enumeration (populating a picker or list) rather than resolving a single value. | `hierarchical` is what you want almost always: define `project_name` once in the top-level flow, and every sub-flow nested inside it can read `{project_name}` without redeclaring it. If a nested flow defines its own `project_name`, that nested definition **shadows** the parent's for everything inside it — the parent's value is untouched and reappears once you leave the nested flow. A shorter way to think about it: the definition closest to where you're reading wins, and flow-scoped variables win over global ones. ## Variable types A variable's `type` is the same type label you'd see on any node parameter — `str`, `int`, `float`, `bool`, `json`, an image type, and so on. **Create Variable** and **Set Variable** infer the type automatically from whatever you connect to their `value` input; you don't need to set it by hand unless you're leaving `value` unconnected and typing a literal directly into the node. Only variables holding a `str` or `int` value (not `bool`) can be substituted inline into a `{name}` token in a text field — see [Inline substitution](#inline-substitution-in-text-fields) below. Variables holding other types (JSON, images, lists) still work fine with **Get Variable** / **Set Variable**; they just can't be dropped into a text field as a bare token, since there's no sensible way to render an image inline as text. ## Setting and reading values during a run Outside of nodes, if you're scripting against the engine's request API directly, the relevant requests are `CreateVariableRequest`, `GetVariableValueRequest`, and `SetVariableValueRequest` — each takes a `name` and a `lookup_scope`. `SetVariableValueRequest` unresolves any already-resolved node downstream that references the variable, so changing a variable mid-editing correctly invalidates anything that was computed from its old value. For inline substitution, one more layer sits on top of the plain variable lookup: when a node runs, the engine resolves every `{name}` token in its parameter values against a merged set of **your workflow variables plus the project's read-only macro values** (`workspace_dir`, `workflow_name`, and any project template directories) — your own variables take priority if a name collides with one of those. That merge is what lets a text field reference `{workflow_name}` and `{project_name}` side by side even though one comes from the project and the other from a variable you created. ## Inline substitution in text fields Type `{` inside any text parameter that supports it and the editor pops up a variable picker listing every variable and project macro currently in scope, grouped by source: Pick one, or just type the name and close the brace yourself — `{project_name}` — and its value is substituted in at execution time. If the name doesn't resolve to anything in scope, the token is left in the text untouched (or, for an optional token, silently dropped — see below), so a typo doesn't silently produce a wrong value. You can turn this behavior off per workflow with the **Set Variable Substitution** node if you specifically want `{literal text like this}` to pass through untouched instead of being treated as a token. ### Format specs A token can carry format specs after a `:`, applied left to right, using the same syntax and the same underlying spec list documented in full in [Macros → String transformations](https://docs.griptapenodes.com/guides/projects/macros/#string-transformations): | Spec | Result | | ------------------ | --------------------------------- | | `:lower` | all lowercase | | `:upper` | ALL UPPERCASE | | `:title` | Title Case | | `:snake` | snake_case | | `:pascal` | PascalCase | | `:camel` | camelCase | | `:screaming_snake` | SCREAMING_SNAKE_CASE | | `:slug` | url-safe-slug | | `:dot` | dot.case | | `:abbrev` | first letter of each word | | `:trim` | strip leading/trailing whitespace | ``` {project_name} → "Autumn Campaign" {project_name:lower} → "autumn campaign" {project_name:slug} → "autumn-campaign" {project_name:snake:upper} → "AUTUMN_CAMPAIGN" ``` Two more pieces of syntax carry over from macros and work the same way here: - **Optional marker `?`** — `{project_name?}` renders as an empty string instead of leaving the literal token behind when the variable isn't found, so you can build sentences that gracefully drop a clause when a variable is unset. - **Default value `|default`** — `{project_name|untitled}` substitutes `untitled` if `project_name` isn't in scope. Numeric padding (`:03`), sequence slots (`{###}`), and leading separators (`:^prefix`) are also parsed by the same engine and technically valid inside a text field, but they exist for building filenames with auto-incrementing counters — see [Macros](https://docs.griptapenodes.com/guides/projects/macros/index.md) if you need that behavior. For ordinary text substitution, the transformations and optional/default specs above cover the common cases. ## Worked example: a project name across several prompts Say a workflow generates concept art and needs the same project name in several prompt fields. 1. Drop a **Create Variable** node near the top of the flow. Set `variable_name` to `project_name`, type `Autumn Campaign` directly into `value` (no connection needed for a literal), and let `variable_type` stay as the inferred `str`. 1. In your prompt-building node's text field, type: ``` A cinematic key art poster for {project_name}, dramatic lighting, wide shot ``` 1. In a second prompt field elsewhere in the flow: ``` Concept sketch, {project_name:slug} style guide, muted palette ``` 1. Run the flow once. Both prompts pick up "Autumn Campaign" (or whatever you typed), with the second one rendering `autumn-campaign` where the slug spec applies. To change the project for a new run, edit the value on the **Create Variable** node (or wire a different source into it) — every field referencing `{project_name}` updates the next time the flow executes, without rewiring anything. # Assets and Outputs When a node in your workflow generates an image, video, audio clip, or any other file, that file has to land somewhere on disk and be reachable by the editor so you can preview and download it. This page explains where those files go by default, how to change that, and how files get into a workflow in the first place (uploads, drag-and-drop). ## Where generated files go Nodes don't write to hard-coded locations. Every save goes through your **project**: the project defines a set of named directories (`inputs`, `outputs`, `temp`, and more) and a set of named file-saving scenarios called **situations** that decide which directory a given kind of file lands in and what it gets called. With the default project, that works out to: - A node's rendered output (an image, a rendered video, generated audio) is saved through the [`save_node_output`](https://docs.griptapenodes.com/guides/projects/situations/#save_node_output) situation, into the project's `outputs` directory. - A file you drag into the editor or copy from outside the project is saved through the [`copy_external_file`](https://docs.griptapenodes.com/guides/projects/situations/#copy_external_file) situation, into the `inputs` directory. - Scratch files a node writes during processing and cleans up afterward go into `temp`. - Thumbnails and preview images the editor generates for you go into hidden `.griptape-nodes-previews` / `.griptape-nodes-thumbnails` folders. None of these paths are baked into the engine — they come from the project, and a project file can point any of them somewhere else (a shared drive, a different subfolder, per-platform paths) or change what happens when a file with that name already exists. See [Directories](https://docs.griptapenodes.com/guides/projects/directories/index.md) for the full list of named directories, [Situations](https://docs.griptapenodes.com/guides/projects/situations/index.md) for the save rule each kind of file follows, and [Project](https://docs.griptapenodes.com/guides/projects/index.md) for how to customize all of it. ## How the editor previews and downloads files When the engine needs to show you a file or let you download it, it doesn't hand the browser a raw filesystem path — it creates a URL. For files inside your workspace, that's usually a direct URL to the local static file server (`http://localhost:8124/workspace/...` by default). For anything else — a path outside the workspace, a `file://` path, or a macro path like `{outputs}/render.png` — the engine mints a short-lived, presigned download URL on request. Either way, the mechanism is the same: the editor asks for a URL, the engine resolves it against wherever the file actually lives, and the browser uses that URL to fetch or download the file. You don't manage these URLs yourself; they're generated on demand and are not meant to be long-lived or shared outside your session. You may also see a `staticfiles` folder in your workspace (the exact name is set by the `static_files_directory` setting). It belongs to an older save path that most nodes no longer use — they save through the project system's directories described above instead — but files saved through the engine's static-file API still land there. ## Uploading files into a workflow Getting an external file (a reference photo, a source video, an audio clip) into a workflow works the same way in reverse: you drag a file onto a node, or use a node's file picker, and the editor uploads it into the project. Where it lands is decided by the project's [`copy_external_file`](https://docs.griptapenodes.com/guides/projects/situations/#copy_external_file) situation. With the default project, that's the `inputs` directory, grouped into a per-type subfolder (`images`, `videos`, `audio`, `text`) when the file's extension is recognized; if a file with the same name is already there, the new upload gets a numbered suffix (`photo_001.png`) instead of overwriting it. Under the hood this is a two-step handoff rather than the browser talking to your filesystem directly: the editor asks the engine for an upload URL, then PUTs the file's bytes to that URL. ## Related pages - [Directories](https://docs.griptapenodes.com/guides/projects/directories/index.md) — the full list of named directories (`inputs`, `outputs`, `temp`, and more) and how to customize their paths. - [Situations](https://docs.griptapenodes.com/guides/projects/situations/index.md) — the save rules (where a file goes, what happens on a name collision) behind every kind of file a node writes. - [Sequences](https://docs.griptapenodes.com/guides/projects/sequences/index.md) — reading a directory of numbered output files (`render.0001.exr`, `render.0002.exr`, …) back in as a single ordered set. - [Macros](https://docs.griptapenodes.com/guides/projects/macros/index.md) — the path-template syntax (`{outputs}/{file_name_base}.{file_extension}`) that situations use to build file paths, if you want to customize where something is saved. - [Configuration Reference](https://docs.griptapenodes.com/reference/configuration_reference/index.md) — the exact settings (`static_files_directory`, `workspace_directory`, and friends) and their defaults and environment variable overrides. # Workflow Versions **File → Save As New Version** (`Option/Alt` + `Shift` + `S`) writes the current state of your workflow to a new file instead of overwriting the one you have open, and switches you over to editing that new file. Save it again and you get another new file — each save produces the next one in the sequence, leaving every earlier version untouched on disk. ## What it produces Versioned saves add a padded `_v` suffix to the workflow's file name: saving `my_workflow.py` this way produces `my_workflow_v001.py`, then `my_workflow_v002.py`, and so on. The version number always advances; it never overwrites an existing version file. ## When to use it Use Save As New Version when you want a checkpoint you can go back to without losing your current progress — before a risky restructuring, before handing a workflow off, or just to keep a trail of snapshots as you iterate. Because each version is its own file, going back to an older version is just a matter of opening that file. ## How it differs from Save and Save As - **Save** overwrites the file you currently have open. No new file is created. - **Save As** writes a new, separate file under a name *you* choose, and switches you to editing it: a one-time copy. - **Save As New Version** writes a new file too, but the name is chosen for you (the next number in the sequence) and it's meant to be repeated — each subsequent save from that file keeps incrementing the same sequence. Autosave, when enabled, behaves like **Save**: it silently overwrites the currently open file on an interval, and never creates a versioned file on its own. See [Auto-save](https://docs.griptapenodes.com/guides/editor/running_workflows/#auto-save) for how to configure it. Not the same as engine or library version pinning Workflow versions are just numbered copies of a workflow *file* — they have nothing to do with which engine or library *versions* a project requires to run correctly. If you're looking to pin a project to a known-good engine version or a known-good set of library versions, see [Pinning engine and library versions](https://docs.griptapenodes.com/guides/projects/version_pinning/index.md) instead. # Publishing a workflow Publishing turns a saved workflow into a self-contained bundle — the workflow plus the libraries, Python dependencies, configuration, and files it needs — and delivers it somewhere it can run: a folder on your disk, a Structure on Griptape Cloud, or a gizmo inside Foundry Nuke. The component that packages and delivers the bundle is called a *publisher*, and each destination has its own. Every publisher follows the same lifecycle and discovers dependencies the same way. This page covers that shared behavior, then where each publisher differs. If your published workflow is missing a media file it loads (an image, audio clip, video, or text file), skip ahead to [Static files: making sure they get bundled](#static-files-making-sure-they-get-bundled). ## Publishers Publishing is built into the engine, but publishers come from node libraries. Any library can register one, so the list you see depends on which libraries are installed. Griptape ships several: | Publisher | Provided by | Where the workflow goes | | --------------------- | ---------------------- | ----------------------------------------------------------------------------------------- | | **Publish To Folder** | Griptape Nodes Library | A self-contained folder on your disk that you can run headlessly. | | **Griptape Cloud** | Griptape Cloud Library | A deployed Structure on Griptape Cloud that you can run and integrate remotely. | | **Publish To Nuke** | Foundry Nuke Library | A versioned `.gizmo` installed into a Foundry Nuke installation, runnable from Nuke's UI. | When you publish, you pick which publisher to use. Each publisher decides what options it needs from you — Publish To Folder asks for an output directory; Griptape Cloud reads its target from your configured cloud bucket and the workflow's Griptape Cloud Start Flow node; Publish To Nuke asks which Nuke installation and gizmo directory to install into, and whether to update an existing version or publish a new one. ## Before you publish The **Publish Workflow** button lives in the top toolbar, at the far right above the sidebar panels (next to the engine status indicator). It's a global editor action — the button and its location are the same no matter which library or publisher you end up choosing in the dialog. - **Your workflow must have been saved at least once** so it has a file on disk. Any unsaved changes are saved automatically when you publish, so you don't need to save again right before publishing — but a workflow that has never been saved has no file path and cannot be published until you save it once. - **Choose a publisher.** Pick the publisher for where you want the workflow to go (see the table above). If only one library provides a publisher, it is selected for you. - **Fill in the publisher's options.** The publish dialog shows whatever fields the selected publisher asks for — for example Publish To Folder's output directory. Fields are pre-filled from your last publish where possible. ## What happens when you publish Regardless of which publisher you pick, the lifecycle is the same. The engine saves any unsaved changes to your workflow's file, hands off to the selected publisher, and the publisher walks the workflow to discover and bundle everything it depends on before delivering it to its destination: ``` flowchart TD A[You click Publish] --> B[Engine saves unsaved changes to the workflow file] B --> C[Engine hands off to the selected publisher] C --> D[Publisher walks every node in the workflow] D --> E[Discovers dependencies:
libraries, pip packages, static files] E --> F[Bundles workflow + dependencies + config] F --> G{Destination} G -->|Publish To Folder| H[Self-contained folder on disk] G -->|Griptape Cloud| I[Deployed Structure in the cloud] G -->|Publish To Nuke| J[Versioned gizmo in Nuke] ``` As it works, the publisher reports progress with messages such as `Copying libraries...` or `Deploying workflow to Griptape Cloud...`, so you can watch the bundle come together. ## What ends up in the bundle Every publisher assembles the same core ingredients, because a workflow needs all of them to run anywhere: - The **workflow file** itself. - The **node libraries** the workflow references, including their transitive dependencies — the libraries those libraries depend on in turn, so nothing the workflow relies on indirectly is left out. - **Configuration** telling the engine which libraries to load. - An **`.env` file** containing your **entire** workspace environment, **all** of your configured secrets, and any of those secrets exported into the environment of the shell you publish from, written in plaintext. See the security warning below. - A **project template** so [directory macros](https://docs.griptapenodes.com/guides/projects/macros/index.md) and [situations](https://docs.griptapenodes.com/guides/projects/situations/index.md) resolve at runtime. - **Python dependencies**, pinned to the engine and library versions the workflow was built against. - A **Hugging Face model download step**, when the workflow uses such models. The bundle contains all of your secrets, in plaintext The packaged `.env` is **not** filtered to what the workflow uses. It merges your whole workspace `.env` with every secret configured in the Secrets Manager, in plaintext — including API keys for services this workflow never touches. It also picks up any of those secrets that are set as environment variables in the shell you publish from, and an exported value wins over the one in a file, the same way the engine itself resolves secrets. Only secret names the engine already knows about are read from the environment, never your whole environment. Anyone you hand a published folder to (or anyone on a machine where a gizmo is installed) gets all of those credentials. **Review the bundled `.env` and remove anything the workflow doesn't need before sharing a published bundle.** What differs is the *shape* of the delivered result: - **Publish To Folder** writes these into a folder on your disk, plus a `run.py` entrypoint and a `README.md`. The README documents installing dependencies (`uv sync`) and running the workflow (`uv run python run.py --help`). - **Griptape Cloud** zips these into a Structure package, uploads it, and creates or updates a Structure in your account. It can also create a webhook integration and generates a separate *executor* workflow you can use to invoke the deployed Structure. On success it returns a link to the Structure in the Griptape Cloud console. - **Publish To Nuke** installs these as a versioned `.gizmo` (plus a runner script) into the chosen Nuke installation's gizmo directory, and adds a Griptape submenu to Nuke's toolbar so you can run the workflow from inside Nuke. Publishing the same workflow again either updates the current version or adds a new one, and outputs are routed to land next to the Nuke script. ## How dependencies are discovered The publisher walks every node in your workflow and asks each one what it depends on. It aggregates three kinds of dependency across the whole workflow: - **Libraries** — the node libraries in use, by name and version. These are always collected, along with any libraries they depend on. - **Python (pip) dependencies** — the Python packages each referenced library declares in its manifest, pinned so the bundle reproduces the environment the workflow was built against. - **Static files** — media and data files a node reads from your project (images, audio, video, text, and so on). These are only bundled **if the node declares them** as dependencies. That last point is where publishing can surprise you, and it applies to every publisher equally. ## Static files: making sure they get bundled Referenced files can be missing from the bundle A static file is only included in the bundle if the node that uses it *declares* it as a dependency, and not every node does so. If a node loads a file but doesn't declare it, that file is **left out**, and the published workflow breaks when it tries to read a file that isn't there. This is true whichever publisher you use. The reliable way to guarantee a file is bundled is to route it through the **`SelectFromProject`** node, which ships with the [Griptape Nodes Library](https://github.com/griptape-ai/griptape-nodes-library-standard): 1. Add a `SelectFromProject` node and set its `selected_path` input to the file (or directory) you want to include. 1. Connect its `project_path` output into the node that consumes the file. `SelectFromProject` explicitly declares its `selected_path` as a static-file dependency, so the publisher always bundles that file. Feeding your file through it guarantees the file travels with the published workflow. Files inside your project stay portable When the selected file lives inside your project, `SelectFromProject` resolves it to a project-relative [macro](https://docs.griptapenodes.com/guides/projects/macros/index.md) path rather than an absolute path. That keeps the reference valid after the bundle is moved to another machine or deployed to the cloud. Files outside your project folder don't travel A file reached from outside the folders that travel with the bundle — an external drive or a network mount, whether referenced directly or through a [directory](https://docs.griptapenodes.com/guides/projects/directories/index.md) you pointed there — has no place inside the bundle, so it is left where it is. The published workflow still looks for it at that same absolute path, so it runs anywhere that path exists and fails anywhere it doesn't. That is what you want for shared storage a render farm already mounts. For a file that should travel, copy it into your project folder and reference the copy. Each file left out is recorded in the engine log as `will not be bundled because ...`, along with the reason. If a published workflow can't find a file it needs, that log line is the place to start; see [Exporting engine logs](https://docs.griptapenodes.com/troubleshooting/#exporting-engine-logs). **When do I need this?** Use `SelectFromProject` for any file loaded from your project that doesn't show up in the published bundle — for example an image, audio clip, video, or text file that a node reads but that goes missing after you publish. If you write nodes yourself, the durable fix is to have your node declare the files it uses so users never need the workaround. See [For library authors](#for-library-authors) below. ## For library authors Publishing is extensible: a node library can provide its own publisher targeting any destination, and its nodes can declare exactly which files they need. **Registering a publisher.** A library subclasses `AdvancedNodeLibrary` and, in `after_library_nodes_loaded`, registers a handler for `PublishWorkflowRequest` via `LibraryManager.on_register_event_handler(...)`. The registration also names the library's own start/end flow node types (and, optionally, a `get_publish_options` callback that supplies the dialog fields). The handler becomes a selectable publisher in the publish dialog. Several reference implementations exist: - **Publish To Folder** — the [Griptape Nodes Library](https://github.com/griptape-ai/griptape-nodes-library-standard) (`griptape_nodes_library_advanced.py`), which supplies publish options for the output directory and packages to a local folder. - **Griptape Cloud** — the [Griptape Cloud Library](https://github.com/griptape-ai/griptape-nodes-library-griptape-cloud) (`griptape_cloud_library_advanced.py`), which registers its own `GriptapeCloudStartFlow`/`GriptapeCloudEndFlow` node types and deploys to the cloud rather than to disk. - **Publish To Nuke** — the [Foundry Nuke Library](https://github.com/griptape-ai/griptape-nodes-library-nuke) (`nuke_library_advanced.py`), which registers `NukeStartFlow`/`NukeEndFlow` node types, offers a multi-field dialog (with dependent and versioning fields), reuses the shared packager for the common ingredients, and then does its own Nuke-specific installation. A publisher is free to deliver its bundle however it likes; the engine only requires it to handle `PublishWorkflowRequest` and return a result. Publishers that need to bundle the common ingredients can reuse the engine's `WorkflowPackager` (as the Folder and Nuke publishers do) rather than reimplementing that logic. **Declaring node dependencies.** The permanent fix for the static-files gap above is for each node to declare the files it uses, which benefits *every* publisher. Override `get_node_dependencies()` and add the file to `NodeDependencies.static_files`. When a node does this, publishers bundle its files automatically and the `SelectFromProject` workaround is unnecessary: ``` def get_node_dependencies(self) -> NodeDependencies | None: deps = super().get_node_dependencies() if deps is None: deps = NodeDependencies() value = self.get_parameter_value("path") if value and isinstance(value, str): deps.static_files.add(value) return deps ``` Always call `super().get_node_dependencies()` first so library and widget dependencies are preserved, then add your own. For a node that does this correctly, look at `SelectFromProject` in the [Griptape Nodes Library](https://github.com/griptape-ai/griptape-nodes-library-standard) (`griptape_nodes_library/files/select_from_project.py`) — it declares its `selected_path` as a static-file dependency, which is exactly why routing a file through it guarantees the file is bundled. The [custom node development docs](https://docs.griptapenodes.com/development/custom_nodes/index.md) cover node development in general. # MCP integration # Connect Your Agent to the World Think of MCP (Model Context Protocol) as a universal translator that lets your Agents talk to various apps and services. Instead of your Agent being stuck in its own little world, MCP lets it reach out and use real tools, access real data, and work with real applications. ## What Can Your Agent Do with MCP? Using MCP, Agents can now: - **Work with your files** - Read, write, and organize documents on your computer - **Use professional software** - Control 3D modeling tools like Blender and Maya - **Search the web** - Find information using search engines like Brave - **Send messages** - Post updates to Slack channels and communicate with your team - **Manage databases** - Store and retrieve information from databases - **Remember things** - Keep track of important information between conversations ## Why This Matters Before MCP, your Agent was like a smart assistant locked in a room with no windows. Now it's like giving that assistant keys to the entire building - it can go anywhere, use any tool, and help you with real work. **Real examples:** - Ask your Agent to "Rename all the models in my Blender scene to .." and it will actually connect to Blender and rename them - Tell it to "Find the latest news about AI" and it will search the web and bring back real results - Say "Send a project update to the team" and it will post to your Slack channel - Request "Organize my documents" and it will actually move and sort your files ## Connection Types There are two official ways your Agent can connect to other applications: 1. **[Local Apps](https://docs.griptapenodes.com/guides/mcp/connection_types/stdio/index.md)** - Connect to software on your computer **Examples**: [Fetch](https://docs.griptapenodes.com/guides/mcp/servers/fetch/index.md) (web content), [Filesystem](https://docs.griptapenodes.com/guides/mcp/servers/filesystem/index.md) (your files), [Time](https://docs.griptapenodes.com/guides/mcp/servers/time/index.md) (dates and schedules) **Best for**: Software you have installed on your computer 1. **[Web Services](https://docs.griptapenodes.com/guides/mcp/connection_types/streamable_http/index.md)** - Connect to web-based applications and services **Examples**: [Exa](https://docs.griptapenodes.com/guides/mcp/servers/exa/index.md) (web search), live data feeds, real-time updates **Best for**: Web-based applications, remote services, and real-time communication ### Additional Options - **[Server-Sent Events (SSE)](https://docs.griptapenodes.com/guides/mcp/connection_types/sse/index.md)** - ⚠️ Deprecated (now part of Streamable HTTP) - **[WebSocket](https://docs.griptapenodes.com/guides/mcp/connection_types/websocket/index.md)** - Custom transport (may be implemented by clients/servers) ## Ready to Get Started? 1. **[Getting Started Tutorial](https://docs.griptapenodes.com/guides/mcp/getting_started/index.md)** - Step-by-step tutorial using the Fetch MCP server 1. **[Using MCPTask with Agents](https://docs.griptapenodes.com/guides/mcp/mcp_task_agents/index.md)** - How to make your AI agents use these tools 1. **[MCP Server Rules](https://docs.griptapenodes.com/guides/mcp/rules/index.md)** - Guide agents with custom rules for each MCP server 1. **[Local Models with Agents](https://docs.griptapenodes.com/guides/mcp/advanced_local_models/index.md)** - Use local AI models for sensitive data processing 1. **[Example MCP Servers](https://docs.griptapenodes.com/guides/mcp/servers/index.md)** - Setup guides for some example servers ## Need More Information? - [Official MCP Documentation](https://modelcontextprotocol.io/docs/getting-started/intro) - Learn more about the technology behind MCP - [MCP Servers Repository](https://github.com/modelcontextprotocol/servers) - Find more apps you can connect - [MCP Specification](https://modelcontextprotocol.io/specification/2025-06-18) - Technical details for developers ## Ready to Connect Your First App? Start with our [Getting Started Guide](https://docs.griptapenodes.com/guides/mcp/getting_started/index.md) - it will walk you through connecting your first app in just a few minutes! # Getting Started with MCP: Fetch Server Tutorial This tutorial will walk you through setting up your first MCP server using the **[Fetch MCP Server](https://github.com/modelcontextprotocol/servers/tree/main/src/fetch)** - a simple web content fetching tool that's perfect for beginners. ## What We're Going to Build We'll set up the [Fetch MCP Server](https://github.com/modelcontextprotocol/servers/tree/main/src/fetch), which provides web content fetching capabilities. This server enables your AI agents to retrieve and process content from web pages, converting HTML to markdown for easier consumption. **Why Fetch?** - ✅ **Easy to set up** - Uses `uvx` for automatic installation - ✅ **No complex dependencies** - Just needs internet access - ✅ **Immediate results** - You can test it right away - ✅ **Real-world useful** - Perfect for research and content analysis ## How MCP Works in Griptape Nodes ``` graph LR A[Agent] --> B[MCPTask Node] B --> C[MCP Server] C --> D[External System/Tool] D --> E[Results/Data] E --> C C --> B B --> A ``` The MCPTask node acts as a bridge between your Griptape Nodes workflow and external MCP servers, enabling your AI agents to interact with external systems seamlessly. ## Step-by-Step Tutorial ### Step 1: Open MCP Server Settings 1. Open Griptape Nodes 1. Go to **Settings** → **MCP Servers** 1. If this is your first time opening the MCP Server Settings, you won't have any MCP servers configured yet. ### Step 2: Creating an MCP Server To create a new MCP Server you'll need to configure the appropriate MCP Server settings. 1. Click **+ New MCP Server** 1. Set the **Server Name/ID** to `fetch` 1. Set the **Connection Type** to `Local Process (stdio)` if it's not already set. 1. Look at the **Configuration (JSON)**. It will look something like: ``` { "transport": "stdio", "command": "", "args": [], "env": {}, "encoding": "utf-8", "encoding_error_handler": "strict" } ``` This is the information you'll need to fill out that tells the *Client* (Griptape Nodes) how to connect to the *Server*. Most MCP Server documentation will provide instructions on how to set something up for Claude or VS Code. The Fetch server is no different, we'll adapt it's instructions for our use. 1. Navigate to the [Fetch](https://github.com/modelcontextprotocol/servers/tree/main/src/fetch) MCP Server GitHub page 1. Scroll down until you see the [Configuration](https://github.com/modelcontextprotocol/servers/tree/main/src/fetch#configuration) section 1. In the **Configure for Claude.app** section, you'll see "Using `uvx`". Open this and take a look at the instructions: ``` { "mcpServers": { "fetch": { "command": "uvx", "args": ["mcp-server-fetch"] } } } ``` The parts we are interested in are the `command` and the `args`. This is what we'll use in our server. 1. Modify the **Configuration (JSON)** to look like: ``` { "transport": "stdio", "command": "uvx", "args": ["mcp-server-fetch"], "env": {}, "encoding": "utf-8", "encoding_error_handler": "strict" } ``` Notice we just modified the **command** and **args** settings to match what was in the docs. **Optional: Add Custom Rules** You can optionally add custom rules in the **Rules** text area to provide instructions for the AI agent when using this MCP server. These rules are automatically applied when the agent uses tools from this server. For example, you might enter: ``` Always validate URLs before fetching. Return errors in JSON format if a fetch fails. ``` Rules help guide the agent's behavior when interacting with this specific MCP server, ensuring consistent and appropriate usage. 1. Click **Create Server** ### Step 3: Manage Your Server Once created, you can see your server in the list. You can: - **Enable/Disable** servers using the toggle button - **Edit** a server by clicking the Edit button (you can add or modify rules here) - **Delete** servers using the delete button Close the settings when you're done. ### Step 4: Create an MCP Task 1. In your workflow editor, drag an **MCP Task** node from the **Agent** section 1. In the MCP Task node, select `fetch` from the **mcp_server_name** dropdown 1. If the server isn't showing up, click the **reload button** to refresh the list ### Step 5: Test Your Setup In the prompt field, ask a question about something from the internet. For example: ``` Explain MCP: https://www.anthropic.com/news/model-context-protocol ``` Then hit the **Run** button on the node. ### Step 6: See the Results The node will execute and return results like: ## Next Steps: Chaining Tasks Now that you have a working MCP server, you can: ### Chain Multiple Tasks - Connect another **MCP Task** node to your first one - The second task can ask for more information and will use the MCP server if needed - You can use different MCP servers in each task if required Notice in the second task, we're asking the agent about the official MCP site, but we don't *specifically* mention the actual url. That's because in the previous response the MCP server responded with it. The Agent remembers the conversation, and is able to iterate on it. ## What's Next? Now that you've successfully set up your first MCP server, explore: - **[Using MCPTask with Agents](https://docs.griptapenodes.com/guides/mcp/mcp_task_agents/index.md)** - Advanced integration patterns - **[Example MCP Servers](https://docs.griptapenodes.com/guides/mcp/servers/index.md)** - Setup guides for other useful servers - **[Connection Types](https://docs.griptapenodes.com/guides/mcp/#connection-types)** - Learn about different ways to connect to apps # Local Process (stdio) Connection The **stdio** connection type allows Griptape Nodes to communicate with MCP servers running as local processes using standard input/output streams. ## When to Use stdio - **Local Applications**: Running MCP servers on the same machine - **Command-Line Tools**: Interacting with CLI-based MCP servers - **Development**: Testing and development scenarios - **Simple Setup**: When you want minimal configuration overhead ## Example stdio MCP Servers Here are a couple of examples. There are of course many more MCP Servers you can set up, but we wanted to show a few that can help you get started. - **[Fetch](https://docs.griptapenodes.com/guides/mcp/servers/fetch/index.md)** - Web content fetching and processing - **[Filesystem](https://docs.griptapenodes.com/guides/mcp/servers/filesystem/index.md)** - File and directory operations ## Configuration ### Required Fields | Field | Type | Description | Example | | --------- | ------ | ------------------------------- | ----------------------------------------------- | | `command` | string | Command to start the MCP server | `"npx"`, `"python"`, `"uvx"` | | `args` | array | Arguments passed to the command | `["-y", "@modelcontextprotocol/server-memory"]` | ### Optional Fields | Field | Type | Description | Default | | ------------------------ | ------ | ----------------------- | ----------------- | | `env` | object | Environment variables | `{}` | | `cwd` | string | Working directory | Current directory | | `encoding` | string | Text encoding | `"utf-8"` | | `encoding_error_handler` | string | Error handling strategy | `"strict"` | ## Example Configurations ### Memory Server (Node.js) ``` { "name": "memory", "transport": "stdio", "command": "npx", "args": ["-y", "@modelcontextprotocol/server-memory"], "description": "Persistent memory storage for conversations" } ``` ### Filesystem Server ``` { "name": "filesystem", "transport": "stdio", "command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem", "/allowed/path"], "env": { "NODE_ENV": "production" }, "cwd": "/home/user/projects" } ``` ## Setup Steps ### 1. Install MCP Server ``` # For Node.js servers npm install -g @modelcontextprotocol/server-memory # For Python servers pip install mcp-server-git # or uvx mcp-server-git ``` ### 2. Configure in Griptape Nodes 1. Open Griptape Nodes settings 1. Navigate to MCP Server configuration 1. Add new server with stdio transport 1. Fill in command and arguments 1. Save the connection ### 3. Use in Workflow 1. Add MCPTask node to your flow 1. Select your configured stdio server 1. Enter your prompt 1. Execute the workflow ## Advantages - **Low Latency**: Direct process communication - **Simple Setup**: Minimal configuration required - **Local Control**: Full control over the server process - **Resource Efficient**: No network overhead ## Limitations - **Local Only**: Cannot connect to remote servers - **Process Management**: Must handle server lifecycle - **Platform Dependent**: Command syntax varies by OS - **Single Connection**: One connection per server instance ## Troubleshooting ### Server Won't Start - Verify the command exists in PATH - Check file permissions - Ensure all dependencies are installed - Test the command manually in terminal ### Connection Timeout - Check if server is responding to stdio - Verify encoding settings - Look for server error messages - Check working directory permissions ### Permission Errors - Ensure proper file system permissions - Check if user has access to required directories - Verify environment variable access ## Best Practices 1. **Use Absolute Paths**: For commands and working directories 1. **Set Environment Variables**: For configuration and secrets 1. **Handle Errors Gracefully**: Implement proper error handling 1. **Monitor Resources**: Watch for memory leaks or high CPU usage 1. **Test Commands**: Verify server commands work before configuration ## Next Steps - [SSE Connection](https://docs.griptapenodes.com/guides/mcp/connection_types/sse/index.md) - HTTP-based streaming - [Streamable HTTP](https://docs.griptapenodes.com/guides/mcp/connection_types/streamable_http/index.md) - HTTP with streaming support - [WebSocket Connection](https://docs.griptapenodes.com/guides/mcp/connection_types/websocket/index.md) - Full-duplex communication # Server-Sent Events (SSE) Connection ⚠️ **Deprecated** - SSE as a standalone transport has been deprecated in favor of **Streamable HTTP**. ## What Happened to SSE? According to the [official MCP specification](https://modelcontextprotocol.io/specification/2025-06-18/basic/transports), SSE as a standalone transport was deprecated in protocol version 2024-11-05 and replaced by **Streamable HTTP**. ## Current MCP Transport Options The MCP specification now defines only **two standard transport mechanisms**: 1. **[stdio](https://docs.griptapenodes.com/guides/mcp/connection_types/stdio/index.md)** - Communication over standard in and standard out 1. **[Streamable HTTP](https://docs.griptapenodes.com/guides/mcp/connection_types/streamable_http/index.md)** - HTTP-based communication with optional SSE support ## What This Means - **SSE functionality is still available** - but as part of Streamable HTTP, not as a standalone transport - **Streamable HTTP can use SSE** - for server-to-client streaming when needed - **No standalone SSE servers** - SSE is now integrated into the HTTP transport ## Migration Path If you were planning to use SSE, consider: - **[Streamable HTTP](https://docs.griptapenodes.com/guides/mcp/connection_types/streamable_http/index.md)** - The modern replacement that includes SSE capabilities - **[Local Process (stdio)](https://docs.griptapenodes.com/guides/mcp/connection_types/stdio/index.md)** - For local server connections - **[WebSocket](https://docs.griptapenodes.com/guides/mcp/connection_types/websocket/index.md)** - For full-duplex real-time communication (custom transport) ## Next Steps - **[Streamable HTTP](https://docs.griptapenodes.com/guides/mcp/connection_types/streamable_http/index.md)** - The modern HTTP transport with SSE capabilities - **[Local Process (stdio)](https://docs.griptapenodes.com/guides/mcp/connection_types/stdio/index.md)** - Local server connections with many examples - **[Example MCP Servers](https://docs.griptapenodes.com/guides/mcp/servers/index.md)** - Available server examples # Streamable HTTP Connection The **streamable_http** connection type enables Griptape Nodes to communicate with MCP servers over HTTP with support for **bidirectional streaming** (client ↔ server) and real-time communication. ## When to Use Streamable HTTP - **Bidirectional Communication**: When both client and server need to send data - **Interactive Applications**: Real-time chat, collaborative editing, live collaboration - **HTTP Infrastructure**: Leveraging existing HTTP-based systems - **Custom Streaming**: When you need more control than SSE provides - **Session Management**: Applications requiring persistent session state ## Available Streamable HTTP MCP Servers - **[Exa](https://docs.griptapenodes.com/guides/mcp/servers/exa/index.md)** - Advanced web search and research capabilities ## Example Streamable HTTP MCP Server Configuration ### Chat Application Server ``` { "name": "chat_app", "transport": "streamable_http", "url": "https://api.chat-service.com/mcp/stream", "headers": { "Authorization": "Bearer chat-token" }, "timeout": 60, "sse_read_timeout": 120, "terminate_on_close": false, "description": "Real-time messaging and communication" } ``` ## Popular Streamable HTTP Use Cases - **Chat Applications** - Real-time messaging and communication - **Collaborative Editing** - Shared document editing (like Google Docs) - **Live Collaboration** - Team workspaces and shared whiteboards - **Interactive Dashboards** - Real-time data visualization and interaction - **Customer Support** - Live chat and support systems - **Online Gaming** - Turn-based and real-time multiplayer games ## Configuration ### Required Fields | Field | Type | Description | Example | | ----- | ------ | -------------------------------- | ------------------------------- | | `url` | string | HTTP endpoint for the MCP server | `"https://api.example.com/mcp"` | ### Optional Fields | Field | Type | Description | Default | | -------------------- | ------- | ------------------------------- | ------- | | `headers` | object | HTTP headers for authentication | `{}` | | `timeout` | number | Request timeout in seconds | `30` | | `sse_read_timeout` | number | SSE read timeout in seconds | `60` | | `terminate_on_close` | boolean | Terminate session on close | `true` | ## Example Configurations ### Basic Streamable HTTP ``` { "name": "streamable_api", "transport": "streamable_http", "url": "https://api.example.com/mcp/stream", "description": "HTTP API with bidirectional streaming (client ↔ server)" } ``` ### Authenticated Streamable HTTP ``` { "name": "auth_streamable", "transport": "streamable_http", "url": "https://api.example.com/mcp/stream", "headers": { "Authorization": "Bearer your-token-here", "Content-Type": "application/json", "Accept": "application/json" }, "timeout": 60, "sse_read_timeout": 120, "terminate_on_close": true } ``` ### Custom Configuration ``` { "name": "custom_streamable", "transport": "streamable_http", "url": "https://mcp.example.com/stream", "headers": { "X-API-Key": "your-api-key", "X-Client-Version": "1.0.0", "User-Agent": "GriptapeNodes/1.0" }, "timeout": 90, "sse_read_timeout": 300, "terminate_on_close": false } ``` ## Setup Steps ### 1. Deploy MCP Server Ensure your MCP server supports streamable HTTP: ``` # Example streamable HTTP endpoint @app.post("/mcp/stream") async def mcp_stream(request: Request): return StreamingResponse( process_mcp_stream(request), media_type="application/json", headers={"Cache-Control": "no-cache", "Connection": "keep-alive"}, ) ``` ### 2. Configure in Griptape Nodes 1. Open Griptape Nodes settings 1. Navigate to MCP Server configuration 1. Add new server with streamable_http transport 1. Enter the server URL 1. Configure authentication and timeouts 1. Test the connection ### 3. Use in Workflow 1. Add MCPTask node to your flow 1. Select your configured streamable HTTP server 1. Enter your prompt 1. Execute the workflow ## Advantages - **Bidirectional Streaming**: Full duplex communication (client ↔ server) - **HTTP Compatible**: Works with standard web infrastructure - **Real-Time Updates**: Live data streaming in both directions - **Session Management**: Built-in session handling - **Custom Implementation**: More control over streaming behavior - **Interactive Applications**: Perfect for real-time collaboration ## Limitations - **HTTP Overhead**: More overhead than direct connections - **Network Dependent**: Requires stable network connection - **Complexity**: More complex than simple HTTP requests - **Resource Usage**: Higher resource consumption - **Custom Implementation**: Requires more development work than SSE ## Streamable HTTP vs SSE | Feature | Streamable HTTP | SSE | | ------------------ | --------------------------------------- | ------------------------------------- | | **Direction** | Bidirectional (client ↔ server) | Unidirectional (server → client) | | **Protocol** | Custom HTTP streaming | Standardized (`text/event-stream`) | | **Use Case** | Interactive apps, real-time chat | Notifications, live feeds, monitoring | | **Implementation** | Custom client/server logic | Built-in browser support | | **Reconnection** | Manual implementation | Automatic reconnection | | **Example** | Chat application, collaborative editing | Stock ticker, news feed | ## Authentication ### Bearer Token ``` { "headers": { "Authorization": "Bearer your-jwt-token" } } ``` ### API Key ``` { "headers": { "X-API-Key": "your-api-key", "X-Client-ID": "griptape-nodes" } } ``` ### Custom Authentication ``` { "headers": { "X-Custom-Auth": "your-custom-token", "X-User-ID": "user123", "X-Session-ID": "session456" } } ``` ## Session Management ### Terminate on Close ``` { "terminate_on_close": true } ``` - Automatically terminates the session when connection closes - Useful for stateless operations - Default behavior ### Persistent Sessions ``` { "terminate_on_close": false } ``` - Maintains session state across connections - Useful for stateful operations - Requires server-side session management ## Troubleshooting ### Connection Issues - Verify server URL is accessible - Check network connectivity - Test with curl or Postman - Monitor server logs ### Timeout Problems - Increase timeout values - Check server response times - Monitor network latency - Optimize server performance ### Authentication Failures - Verify credentials are correct - Check token expiration - Ensure proper header format - Test authentication separately ### Streaming Issues - Verify server supports streaming - Check for proper content types - Monitor connection stability - Test with smaller payloads ## Best Practices 1. **Use HTTPS**: Always use secure connections 1. **Handle Reconnection**: Implement automatic reconnection 1. **Monitor Sessions**: Track session state and cleanup 1. **Optimize Timeouts**: Set appropriate timeout values 1. **Secure Credentials**: Store sensitive data securely ## Example Use Cases ### Interactive Chat ``` { "name": "chat_interactive", "transport": "streamable_http", "url": "https://chat.example.com/stream", "headers": { "Authorization": "Bearer chat-token" }, "terminate_on_close": false } ``` ### Real-Time Collaboration ``` { "name": "collaboration", "transport": "streamable_http", "url": "https://collab.example.com/stream", "headers": { "X-User-ID": "user123", "X-Workspace-ID": "workspace456" } } ``` ### Live Data Processing ``` { "name": "data_processor", "transport": "streamable_http", "url": "https://processor.example.com/stream", "timeout": 120, "sse_read_timeout": 600 } ``` ## Performance Considerations ### Timeout Configuration - **Short Timeouts**: For quick operations (30-60 seconds) - **Medium Timeouts**: For standard operations (60-120 seconds) - **Long Timeouts**: For complex operations (120+ seconds) ### Connection Pooling - Reuse connections when possible - Monitor connection limits - Implement proper cleanup - Handle connection failures gracefully ## Next Steps - [WebSocket Connection](https://docs.griptapenodes.com/guides/mcp/connection_types/websocket/index.md) - Full-duplex communication - [SSE Connection](https://docs.griptapenodes.com/guides/mcp/connection_types/sse/index.md) - Unidirectional streaming - [stdio Connection](https://docs.griptapenodes.com/guides/mcp/connection_types/stdio/index.md) - Local process communication # WebSocket Connection The **websocket** connection type enables Griptape Nodes to communicate with MCP servers using WebSocket protocol for full-duplex, real-time communication. > **Note**: WebSocket is not an official MCP transport type, but it can be implemented as a custom transport according to the [MCP specification](https://modelcontextprotocol.io/specification/2025-06-18/basic/transports). ## What WebSocket Is WebSocket is a communication protocol that provides full-duplex communication channels over a single TCP connection. Unlike HTTP, WebSocket allows both the client and server to send data at any time. ## Coming Soon WebSocket support for MCP servers is still emerging. While the connection type can be implemented as a custom transport, there are currently no widely available WebSocket-based MCP servers to demonstrate with. **For now, consider:** - **[Streamable HTTP](https://docs.griptapenodes.com/guides/mcp/connection_types/streamable_http/index.md)** for HTTP-based communication with real examples - **[Local Process (stdio)](https://docs.griptapenodes.com/guides/mcp/connection_types/stdio/index.md)** for local server connections with many examples ## Configuration When WebSocket MCP servers become available, they will use a configuration similar to: ``` { "transport": "websocket", "url": "ws://your-websocket-server.com/mcp", "headers": { "Authorization": "Bearer your-api-key" } } ``` ## Next Steps - **[Streamable HTTP](https://docs.griptapenodes.com/guides/mcp/connection_types/streamable_http/index.md)** - HTTP-based communication with real examples - **[Local Process (stdio)](https://docs.griptapenodes.com/guides/mcp/connection_types/stdio/index.md)** - Local server connections with many examples - **[Example MCP Servers](https://docs.griptapenodes.com/guides/mcp/servers/index.md)** - Available server examples # Using MCPTask with Agents The **MCPTask** node allows you to give your AI agents temporary access to external tools and data sources. This powerful feature lets you control exactly when and how your agents can interact with external systems. ## Key Concept: Temporary Access with Memory When you connect an agent to an MCPTask node: - ✅ **The agent gains access** to the MCP server's tools - ✅ **The agent can perform tasks** it couldn't do before - ✅ **The agent retains memory** of what it learned - ❌ **The agent loses access** to MCP tools after the task This gives you precise control over when agents can use external capabilities while preserving their learning. ## How MCPTask Works with Agents ``` graph LR A[Agent] --> B[MCPTask Node] subgraph MCP ["MCP System"] B --> C[MCP Server] C --> D[External System/Tool] D --> E[Results/Data] E --> C C --> B end B --> F[Agent with Memory] F --> G[Next Agent Task] ``` The MCPTask node acts as a bridge that temporarily extends your agent's capabilities, then returns an agent with the knowledge gained but without the external access. ## Step-by-Step Demonstration: Temporary Access with Memory Let's build a workflow that demonstrates how agents temporarily gain MCP capabilities and retain memory. We'll use the Fetch MCP server to show how an agent can access the internet, then continue working with that knowledge. ### Prerequisites - You have the **Fetch MCP server** set up (from the [Getting Started tutorial](https://docs.griptapenodes.com/guides/mcp/getting_started/index.md)) - You understand basic Griptape Nodes workflow concepts ### Step 1: Create Your First Agent 1. **Drag an Agent node** to your workflow editor 1. **Configure the agent**: - Set **prompt model** to `gpt-4o-mini` (or your preferred model) - Set **prompt** to: `"Look up information about https://griptapenodes.com"` - Leave **additional_context** empty for now 1. **Run the agent** - Notice it will respond that it cannot access the internet: > I'm unable to browse the internet or access real-time data. However, I can help you understand what kind of information you might find on a website like griptapenodes.com... This demonstrates the agent's limitations without MCP access. ### Step 2: Add MCPTask Node 1. **Drag an MCPTask node** to your workflow 1. **Connect the Agent output** to the MCPTask node's **agent** input 1. **Configure the MCPTask**: - Set **mcp_server_name** to `fetch` - Set **prompt** to: `"Can you look up the information now?"` 1. **Run the MCPTask** - The agent now has access to the Fetch server and can retrieve information: > [Using tool mcpFetch] Griptape Nodes is a platform designed to help creative professionals work with AI in a predictable, controllable way that fits their thinking process. It offers an intuitive drag-and-drop interface for creating advanced creative pipelines using graphs, nodes, and flowcharts... ### Step 3: Connect to Another Agent 1. **Drag another Agent node** to your workflow 1. **Connect the MCPTask's agent output** to the new Agent's **agent** input 1. **Configure the second agent**: - Set **prompt** to: `"Do you still have access?"` 1. **Run the second agent** - Notice it responds: > I don't have the ability to access or retrieve real-time information from the internet... The information I provided earlier is based on my training data... **Key observation**: The agent no longer has MCP access, but it remembers the information it learned during the MCPTask! ### Step 4: Test Memory Retention 1. **Add another Agent node** connected to the previous one 1. **Configure it** with: `"What did you learn about Griptape Nodes?"` 1. **Run it** - The agent will recall the information it learned: > Based on the information I learned earlier, Griptape Nodes is a platform designed to help creative professionals work with AI in a predictable, controllable way... This demonstrates that the agent retains knowledge even after losing MCP access. ## Advanced Agent Configuration ### Customizing Agent Behavior You can enhance your agents with additional capabilities: #### Rulesets Add rulesets to control how your agent behaves: - **Research Agent**: "Always cite sources when providing information" - **Creative Agent**: "Think outside the box and suggest innovative solutions" - **Analytical Agent**: "Provide detailed analysis with pros and cons" #### Tools Add additional tools to complement MCP capabilities: - **Calculator tools** for mathematical operations - **Date Time** for understanding dates #### Prompt Models Choose different models for different tasks: - **gpt-4o-mini**: Fast and cost-effective for simple tasks - **gpt-4o**: More capable for complex reasoning - **Claude Sonnet**: Excellent for analysis and writing ## Next Steps Now that you understand how agents work with MCPTask nodes, explore: - **[Local Models with Agents](https://docs.griptapenodes.com/guides/mcp/advanced_local_models/index.md)** - Use local AI models for sensitive data processing - **[Example MCP Servers](https://docs.griptapenodes.com/guides/mcp/servers/index.md)** - Set up additional servers for different capabilities - **[Connection Types](https://docs.griptapenodes.com/guides/mcp/#connection-types)** - Learn about different ways to connect to external systems # Local Models with Agents Using local models in the chat sidebar or Agent node? If you want to use Ollama or LM Studio as the AI provider for the **chat sidebar** or an **Agent node** directly, see [AI Providers](https://docs.griptapenodes.com/guides/agent/providers/index.md). That's the simpler path for most use cases. This tutorial covers a different pattern: routing a **local model through an MCP server** inside a workflow, which gives the model access to local files and tools while keeping all data on your machine. This advanced tutorial demonstrates how to use **local AI models** with MCP servers to handle sensitive data without sending it to external services. This is perfect for internal documents, private files, or any data you want to keep on your local machine. ## Why Use Local Models with MCP? ### Security Benefits - ✅ **No data leaves your machine** - Everything stays local - ✅ **No API costs** - Run models on your own hardware - ✅ **Complete privacy** - Perfect for sensitive documents - ✅ **Offline capability** - Works without internet connection ### Perfect Use Cases - **Internal documents** - Company files, contracts, sensitive reports - **Personal data** - Private notes, passwords, personal information - **Proprietary code** - Source code you don't want to share - **Local file management** - Organizing files on your computer ## What We're Building We'll create a workflow that: 1. **Sets up a [Filesystem MCP](https://github.com/modelcontextprotocol/servers/blob/main/src/filesystem/README.md) server** to access your local files 1. **Uses Ollama with a local model** (`qwen3:1.7b`) for AI processing 1. **Demonstrates secure file access** by reading a "secret" file 1. **Shows how sensitive data stays local** throughout the entire process ## Prerequisites - You understand basic MCP concepts (from [Getting Started](https://docs.griptapenodes.com/guides/mcp/getting_started/index.md)) - You have a Mac, Windows, or Linux computer - You're comfortable with basic file operations ## Step 1: Install Ollama ### Download and Install Ollama 1. **Go to [ollama.org](https://ollama.org)** 1. **Download Ollama** for your operating system 1. **Install Ollama** following the installation instructions 1. **Verify installation** by opening a terminal and running: ``` ollama --version ``` ### Download the Qwen3 Model 1. **Open a terminal** and run: ``` ollama pull qwen3:1.7b ``` 1. **Wait for download** - This may take a few minutes depending on your internet speed 1. **Verify the model** is available: ``` ollama list ``` > **Why Qwen3:1.7b?** This model is small enough to run on most computers while still supporting tool use, which is required for MCP integration. ## Step 2: Set Up Filesystem MCP Server The [Filesystem MCP Server](https://github.com/modelcontextprotocol/servers/blob/main/src/filesystem/README.md) provides secure access to your local file system, allowing AI agents to read, write, and manage files and directories. This server is perfect for local data processing because it only accesses directories you explicitly allow, ensuring your sensitive files remain protected. The server supports common file operations like listing directories, reading files, creating new files, and organizing your local storage - all while keeping everything on your machine. For detailed information about the filesystem server's capabilities and configuration options, see our [Filesystem Server documentation](https://docs.griptapenodes.com/guides/mcp/servers/filesystem/index.md). ### Create the MCP Server Configuration 1. **Open Griptape Nodes** and go to **Settings** → **MCP Servers** 1. **Click + New MCP Server** 1. **Configure the server**: - **Server Name/ID**: `filesystem` - **Connection Type**: `Local Process (stdio)` - **Configuration JSON**: ``` { "transport": "stdio", "command": "npx", "args": [ "-y", "@modelcontextprotocol/server-filesystem", "/Users/jason/Desktop", "/Users/jason/Downloads" ], "env": {}, "cwd": null, "encoding": "utf-8", "encoding_error_handler": "strict" } ``` > **Important**: Replace `/Users/jason/Desktop` and `/Users/jason/Downloads` with your actual desktop and downloads folder paths. The filesystem server can only access directories you explicitly allow. 1. **Click Create Server** ### Test the Filesystem Access 1. **Create a test file** on your desktop called `secret.txt` 1. **Add some content** to the file: ``` The password is: CAPYBARA ``` 1. **Save the file** ## Step 3: Build the Local AI Workflow ### Create the Ollama Prompt Configuration 1. **Drag an Ollama Prompt node** to your workflow 1. **Configure the model**: - **Model**: `qwen3:1.7b` - **Temperature**: `0.1` (for consistent results) - **Max Tokens**: `-1` (unlimited) - **Stream**: `True` - **Use Native Tools**: `True` (required for MCP) ### Create the Agent 1. **Drag an Agent node** to your workflow 1. **Connect the Ollama Prompt's `prompt_model_config` output** to the Agent's `prompt_model_config` input 1. **Configure the agent**: - **Prompt**: Leave empty for now - **Additional Context**: Leave empty for now ### Create the MCP Task 1. **Drag an MCPTask node** to your workflow 1. **Connect the Agent's `agent` output** to the MCPTask's `agent` input 1. **Configure the MCPTask**: - **MCP Server Name**: `filesystem` - **Prompt**: `"what's the password in secret.txt? it's in the desktop folder"` ## Step 4: Run the Secure Workflow ### Execute the Workflow 1. **Run the Ollama Prompt node** first to initialize the model 1. **Run the Agent node** to create the agent with local model access 1. **Run the MCPTask node** to execute the file access ### Expected Results The MCPTask should return something like: ``` Okay, the user asked for the password in secret.txt. After checking the allowed directories and listing the contents, the secret.txt file was found. Reading the file revealed the password "CAPYBARA". Since the user requested the password, the assistant needs to provide it. The previous steps were successful, so the final answer is the password. The password in secret.txt is **CAPYBARA**. ``` ## Why This is Secure ### Data Flow Analysis ``` graph LR A[Local Files] --> B[Filesystem MCP Server] B --> C[Local Ollama Model] C --> D[Your Computer Only] subgraph Security ["🔒 Everything Stays Local"] A B C D end ``` ### Security Benefits Demonstrated 1. **No External API Calls** - The AI model runs on your machine 1. **No Data Transmission** - Files are read locally, processed locally 1. **No Cloud Storage** - Nothing is sent to external servers 1. **Complete Control** - You control exactly what the AI can access ## Advanced Configuration Options ### Model Selection Choose models based on your needs: | Model | Size | Speed | Capabilities | Best For | | ------------ | ------ | ------ | ------------------ | ----------------- | | `qwen3:1.7b` | Small | Fast | Basic tool use | Simple tasks | | `qwen3:4b` | Medium | Medium | Better reasoning | Complex analysis | | `llama4` | Large | Slower | Advanced reasoning | Complex workflows | ### Filesystem Security Configure the filesystem server to only access specific directories: ``` { "args": [ "-y", "@modelcontextprotocol/server-filesystem", "/Users/jason/Documents/Work", "/Users/jason/Documents/Projects" ] } ``` ### Performance Optimization - **Allocate sufficient RAM** (8GB+ recommended for larger models) - **Close other applications** when running large models - **Use smaller models** for faster responses ## Troubleshooting ### Common Issues #### Model Not Found ``` # Check available models ollama list # Pull the model if missing ollama pull qwen3:1.7b ``` #### Filesystem Access Denied - Verify the directory paths in your MCP server configuration - Check that the directories exist and are accessible - Ensure you have read permissions for the directories #### Model Performance Issues - Try a smaller model like `qwen3:1.7b` - Close other applications to free up memory - Check your system resources (CPU, RAM usage) #### Tool Use Not Working - Ensure `use_native_tools` is set to `True` in Ollama Prompt - Verify the model supports tool use (Qwen3 models do) - Check that the MCP server is properly configured ## Next Steps Now that you've mastered local models with MCP servers, explore: - **[Example MCP Servers](https://docs.griptapenodes.com/guides/mcp/servers/index.md)** - Set up additional servers for different capabilities - **[Connection Types](https://docs.griptapenodes.com/guides/mcp/#connection-types)** - Explore different ways to connect to external systems # Connect External MCP Clients to Griptape Nodes Griptape Nodes runs its own MCP server so external agents (Claude Desktop, Claude Code, Cursor, VS Code, etc.) can drive the engine. This page is the inverse of the rest of this section: instead of Griptape Nodes consuming external MCP servers, here we expose Griptape Nodes itself as an MCP server. ## URL By default the engine listens on: ``` http://localhost:8125/mcp/ ``` The transport is **Streamable HTTP**. The trailing slash is recommended; the server redirects `/mcp` to `/mcp/` for clients that drop it. When the engine starts, it logs the actual bound address, for example: ``` INFO MCP server listening at http://127.0.0.1:8125/mcp/ ``` ## Overrides The host and port are controlled by environment variables: | Variable | Default | Description | | -------------------------- | ----------- | ------------------------------------------------------------------------ | | `GTN_MCP_SERVER_HOST` | `localhost` | Interface to bind. Use `127.0.0.1` to be explicit, or `0.0.0.0` for LAN. | | `GTN_MCP_SERVER_PORT` | `8125` | TCP port. Set to `0` to let the OS assign a free port. | | `GTN_MCP_SERVER_LOG_LEVEL` | `ERROR` | uvicorn log level for the MCP server. | If the configured port is already in use, the engine falls back to an OS-assigned port. Check the startup log to see the actual URL. Local-only by default The engine binds to `localhost`, which means only processes on the same machine can reach it. The MCP server has no authentication. Do not bind to `0.0.0.0` or expose the port to the network unless you fully trust everything that can reach it. ## Client configuration ### Claude Code Add to `~/.claude.json` (or use `claude mcp add`): ``` { "mcpServers": { "griptape-nodes": { "type": "streamable-http", "url": "http://localhost:8125/mcp/" } } } ``` ### Cursor Create `~/.cursor/mcp.json` for global access, or `.cursor/mcp.json` in a workspace: ``` { "mcpServers": { "griptape-nodes": { "url": "http://localhost:8125/mcp/" } } } ``` ### VS Code Create `.vscode/mcp.json` in your workspace, or open the user file via **MCP: Open User Configuration**: ``` { "servers": { "griptape-nodes": { "type": "http", "url": "http://localhost:8125/mcp/" } } } ``` Note that VS Code uses `servers` (not `mcpServers`) and `"type": "http"`. ### Claude Desktop Claude Desktop's `claude_desktop_config.json` only supports `stdio` servers. To connect to a remote/HTTP MCP server, either: - Use **Settings → Connectors → Add custom connector** in the app and paste `http://localhost:8125/mcp/`, or - Wrap the URL with [`mcp-remote`](https://www.npmjs.com/package/mcp-remote) in the config file: ``` { "mcpServers": { "griptape-nodes": { "command": "npx", "args": ["-y", "mcp-remote", "http://localhost:8125/mcp/"] } } } ``` ## Verifying the connection The simplest non-interactive check is the [MCP Inspector](https://github.com/modelcontextprotocol/inspector) CLI. The engine speaks Streamable HTTP, so pass `--transport http`: ``` npx -y @modelcontextprotocol/inspector --cli http://localhost:8125/mcp/ \ --transport http --method tools/list ``` Without `--transport http` the inspector defaults to SSE and you'll see `SSE error: Non-200 status code (400)`, which is the engine refusing an SSE-style GET without an MCP session. The inspector also has a browser UI: ``` npx -y @modelcontextprotocol/inspector ``` Paste `http://localhost:8125/mcp/` into the URL field and pick **Streamable HTTP** as the transport. If the connection fails with `TypeError: NetworkError when attempting to fetch resource`, it's almost always CORS: the engine's MCP server does not currently emit `Access-Control-Allow-Origin` headers, so cross-origin browser fetches are blocked. Use the CLI command above instead, or run the inspector with browser security relaxed. ## Install the workflow-construction skill The engine ships a [`griptape-nodes-workflows` skill](https://docs.griptapenodes.com/en/stable/skills/griptape-nodes-workflows/SKILL/) that teaches an agent how to drive the MCP tools described above (cold-start recipe, `EventRequestBatch`, common gotchas). Claude Code, Cursor, and VS Code natively load skills with the `name` + `description` frontmatter convention from [agentskills.io](https://agentskills.io), so installation is a directory drop. The published markdown lives at: ``` https://docs.griptapenodes.com/en/stable/skills/griptape-nodes-workflows/SKILL/index.md ``` Whichever scope you choose, the directory name **must** be `griptape-nodes-workflows` (it has to match the `name` field in the frontmatter) and the file **must** be named `SKILL.md`. ### Per-client install paths | Client | Project scope | User scope | | ----------------- | -------------------------------------------------- | ----------------------------------------------------- | | Claude Code | `.claude/skills/griptape-nodes-workflows/SKILL.md` | `~/.claude/skills/griptape-nodes-workflows/SKILL.md` | | Cursor | `.cursor/skills/griptape-nodes-workflows/SKILL.md` | `~/.cursor/skills/griptape-nodes-workflows/SKILL.md` | | VS Code (Copilot) | `.github/skills/griptape-nodes-workflows/SKILL.md` | `~/.copilot/skills/griptape-nodes-workflows/SKILL.md` | Cursor and VS Code also pick up `.agents/skills/` (project) and `~/.agents/skills/` (user), and VS Code additionally recognizes `.claude/skills/` / `~/.claude/skills/`. If you want one folder to serve multiple clients, drop the skill at `~/.agents/skills/griptape-nodes-workflows/SKILL.md`. ### Install in one command Adjust `DEST` per the table above: ``` DEST="$HOME/.claude/skills/griptape-nodes-workflows" mkdir -p "$DEST" \ && curl -fsSL https://docs.griptapenodes.com/en/stable/skills/griptape-nodes-workflows/SKILL/index.md \ -o "$DEST/SKILL.md" ``` Confirm it loaded by typing `/skills` in chat (Claude Code or VS Code) or opening the Skills tab in the customization menu (Cursor). # MCP Server Rules MCP server rules allow you to provide custom instructions to AI agents when they use tools from a specific MCP server. These rules are automatically applied as a ruleset whenever an agent uses that server's tools, helping ensure consistent and appropriate behavior. ## What Are Rules? Rules are text instructions that guide how the AI agent should interact with a particular MCP server. They're added to the MCP server configuration and automatically applied when: - Using the **MCPTask** node with that server - Using the **Agent** node with that server configured ## Why Use Rules? Rules help you: - **Guide Agent Behavior**: Provide specific instructions on how to use the server's tools - **Ensure Consistency**: Make sure the agent follows your preferred patterns - **Handle Edge Cases**: Instruct the agent on how to handle errors or special situations - **Optimize Usage**: Guide the agent to use the server's capabilities most effectively ## Adding Rules to an MCP Server ### When Creating a Server When creating a new MCP server, you can add rules in the **Rules** text area: 1. Go to **Settings** → **MCP Servers** 1. Click **+ New MCP Server** 1. Fill in the server configuration (name, connection type, etc.) 1. In the **Rules** text area, enter your custom rules 1. Click **Create Server** ### When Editing a Server 1. Go to **Settings** → **MCP Servers** 1. Click the **Edit** button on the server you want to modify 1. Modify the text in the **Rules** text area 1. Save your changes ## Example Rules ### For a Web Fetching Server ``` Always validate URLs before fetching. Check that URLs use HTTPS when possible. If a fetch fails, return a clear error message explaining what went wrong. ``` ### For a File System Server ``` Always check if a file exists before attempting to read it. Use absolute paths when possible. Never delete files without explicit user confirmation. ``` ### For a Search Server ``` Always verify search results are relevant before returning them. If no relevant results are found, suggest alternative search terms. Format results in a clear, readable structure. ``` ### For a Database Server ``` Always validate SQL queries before executing them. Never execute DROP or DELETE operations without explicit confirmation. Return query results in a structured format. ``` ## How Rules Work 1. **Storage**: Rules are stored as part of the MCP server configuration 1. **Application**: When an agent uses tools from that server, the rules are automatically added as a ruleset 1. **Scope**: Rules apply only when using that specific MCP server 1. **Format**: Rules are a single string - you can include multiple instructions separated by periods or newlines ## Best Practices ### Be Specific ✅ **Good**: "Always validate URLs before fetching. Check for HTTPS and return clear error messages." ❌ **Vague**: "Be careful with URLs." ### Focus on Behavior ✅ **Good**: "Return errors in JSON format with 'error' and 'message' fields." ❌ **Too Generic**: "Handle errors well." ### Keep It Concise ✅ **Good**: "Validate inputs before processing. Return structured JSON responses." ❌ **Too Long**: A paragraph explaining every possible scenario in detail. ### Test Your Rules After adding rules, test them with your MCP server to ensure they work as expected: 1. Create an MCPTask node using the server 1. Run a test prompt 1. Verify the agent follows the rules you specified ## Rules in Different Contexts ### MCPTask Node When using the MCPTask node, rules from the selected MCP server are automatically applied to the agent that processes the task. ### Agent Node When using the Agent node with MCP servers configured, rules from all enabled MCP servers are collected and applied to the agent. ## Troubleshooting ### Rules Not Being Applied - Verify the rules are entered in the Rules text area (not empty) - Check that the server is enabled - Ensure you're using the correct server name in your workflow ### Agent Not Following Rules - Make rules more specific and actionable - Test with simpler rules first - Check that the rules are appropriate for the server's capabilities ## Next Steps - **[Getting Started Tutorial](https://docs.griptapenodes.com/guides/mcp/getting_started/index.md)** - Learn how to set up your first MCP server - **[Connection Types](https://docs.griptapenodes.com/guides/mcp/#connection-types)** - Learn about different connection methods - **[Example Servers](https://docs.griptapenodes.com/guides/mcp/servers/index.md)** - See examples of configured MCP servers # Example MCP Servers This section provides setup guides and documentation for example MCP servers that work well with Griptape Nodes. These are just a few of the many MCP servers available in the community. ## Where to Find More MCP Servers The [official MCP servers repository](https://github.com/modelcontextprotocol/servers) contains a comprehensive collection of reference implementations and community-built servers. This repository showcases the versatility of MCP and demonstrates how it can give Large Language Models secure, controlled access to tools and data sources. ### Official Repository Features: - **Reference implementations** for common use cases - **Community contributions** from developers worldwide - **Multiple language support** (TypeScript, Python, JavaScript, etc.) - **Easy installation** with `npx`, `uvx`, and `pip` - **Comprehensive documentation** for each server ## Available Server Documentation ### Local Applications (stdio) These servers run locally on your machine and provide direct access to your software and files: - **[Blender](https://docs.griptapenodes.com/guides/mcp/servers/blender/index.md)** - 3D modeling, scene creation, and manipulation (3rd party) - **[Maya](https://docs.griptapenodes.com/guides/mcp/servers/maya/index.md)** - 3D modeling, animation, and scene creation with Autodesk Maya (3rd party) - **[Fetch](https://docs.griptapenodes.com/guides/mcp/servers/fetch/index.md)** - Web content fetching and processing - **[Filesystem](https://docs.griptapenodes.com/guides/mcp/servers/filesystem/index.md)** - File and directory operations - **[Time](https://docs.griptapenodes.com/guides/mcp/servers/time/index.md)** - Date and time operations ### Web Services (streamable_http) These servers connect to web-based applications and services: - **[Exa](https://docs.griptapenodes.com/guides/mcp/servers/exa/index.md)** - Advanced web search and research capabilities ## Need More Options? - **Browse the [official repository](https://github.com/modelcontextprotocol/servers)** for hundreds of additional servers - **Explore [MCP Servers](https://mcpservers.org/)** - A curated collection of MCP servers with featured and official options - **Check community directories** like MCPHub, MCP Servers Hub, and other curated lists - **Build your own** using the MCP SDKs available in multiple programming languages The MCP ecosystem is constantly growing, with new servers being added regularly by the community! # Blender MCP Server The **[Blender MCP Server](https://www.blender.org/lab/mcp-server/)** is the official MCP integration for [Blender](https://www.blender.org), developed and maintained by the Blender Lab team. It allows AI agents to directly interact with and control Blender for prompt-assisted 3D modeling, scene inspection, and manipulation. Prerequisites Before using the Blender MCP server, you must: 1. Have [Blender](https://www.blender.org) 5.1 or newer installed 1. Install the `uv` package manager ([installation instructions](https://docs.astral.sh/uv/getting-started/installation/)) 1. Clone the [Blender MCP repository](https://projects.blender.org/lab/blender_mcp) 1. Install the Blender MCP extension (see below) ## Installing the Blender MCP Extension The Blender MCP server is distributed as a Blender Extension through the official Blender Extensions platform. 1. Visit [blender.org/lab/mcp-server](https://www.blender.org/lab/mcp-server/#addon) and scroll down to the **Add-on** section 1. Choose one of the two installation methods: **Option A — Drag and Drop** Drag the **Drag and Drop into Blender** button onto an open Blender window. Drag and drop twice You must drag and drop **twice**: the first drop adds the Blender Lab repository, and the second drop installs the add-on itself. **Option B — Install from Disk** Click **download** on the page, then in Blender go to **Edit** → **Preferences** → **Get Extensions** → dropdown → **Install from Disk...** and select the downloaded file. 1. In Blender, go to **Edit** → **Preferences** → **Get Extensions** 1. Search for `mcp` — you should see the **MCP** extension listed as Available 1. Click **Install** ## Starting the MCP Server in Blender Once the extension is installed, you need to start the MCP server inside Blender before connecting from Griptape Nodes: 1. In Blender, go to **Edit** → **Preferences** → **Add-ons** 1. Search for `mcp` and expand the **MCP** extension preferences 1. Configure the **Host** and **Port** if needed (defaults: `localhost` / `9876`) 1. Optionally enable **Auto Start** so the server starts automatically with Blender 1. Click **Start MCP Server** The panel will show **Server is running** when the connection is ready. ## Cloning the MCP Server Code The Griptape Nodes MCP connection requires a local clone of the Blender MCP repository. Clone it to an easily discoverable location — for example, on a Mac you might use `$HOME/Documents/GitHub`: ``` cd $HOME/Documents/GitHub git clone https://projects.blender.org/lab/blender_mcp.git ``` You will reference the path to the `mcp/` subdirectory inside your clone in the Griptape Nodes configuration below. ## Installation in Griptape Nodes 1. **Open Griptape Nodes** and go to **Settings** → **MCP Servers** 1. **Click + New MCP Server** 1. **Configure the server**: - **Server Name/ID**: `blender` - **Connection Type**: `Local Process (stdio)` - **Configuration JSON**: ``` { "transport": "stdio", "command": "uv", "args": [ "--directory", "/path/to/blender_mcp/mcp", "run", "blender-mcp" ], "env": {}, "cwd": null, "encoding": "utf-8", "encoding_error_handler": "strict" } ``` 1. **Replace `/path/to/blender_mcp/mcp`** with the actual path to the `mcp/` subdirectory inside your local clone 1. **Click Create Server** Example path If you cloned the repository to `$HOME/Documents/GitHub/blender_mcp`, the `--directory` value would be: ``` /Users/yourname/Documents/GitHub/blender_mcp/mcp ``` ## Example Use Cases - "With the current open Blender file, suggest descriptive names for all data-blocks, and apply if approved" - "What is the object with the highest poly-count in this file? Ignore objects which are not linked to any scene" - "Create a sphere and place it above the cube" - "Make the lighting like a studio" - "Point the camera at the scene and make it isometric" - "Get information about the current scene and export it as JSON" ## Troubleshooting ### Common Issues - **Connection refused**: Ensure the MCP server is running in Blender (check that the extension preferences show **Server is running**) - **First command fails**: The first command after connecting sometimes doesn't go through. Try running the command again - **Wrong path**: Verify the `--directory` argument points to the `mcp/` subdirectory of your clone, not the repository root - **Timeout errors**: Try simplifying your requests or breaking them into smaller steps - **Code execution warnings**: If the server exposes code execution tools, always save your Blender work before using them ### Debug Tips 1. Confirm the extension is installed and enabled under **Edit** → **Preferences** → **Add-ons** 1. Check the Host and Port match on both sides (Blender extension and Griptape Nodes config) 1. Test with a simple query first (e.g., "what objects are in the scene?") 1. Restart both Blender and the MCP server if issues persist ## Resources - [Blender MCP Server](https://www.blender.org/lab/mcp-server/) - Official page and extension download - [Blender MCP Repository](https://projects.blender.org/lab/blender_mcp) - Source code and issue tracker - [Blender Python API](https://docs.blender.org/api/current/) - Reference for Blender scripting - [uv Installation](https://docs.astral.sh/uv/getting-started/installation/) - Package manager required to run the MCP server ## Security Considerations Code Execution Some MCP tools may execute code directly in Blender. This can be powerful but potentially dangerous: - **Always save your Blender work** before using code execution tools - Review generated code when possible - Use with caution in production environments # Exa MCP Server The **[Exa MCP Server](https://github.com/exa-labs/exa-mcp-server)** provides powerful web search and research capabilities through Exa AI's advanced search engine. It offers both local and remote deployment options, with specialized tools for code search, web research, and content extraction. ## Installation The easiest way to use Exa is through their hosted MCP server: 1. **Open Griptape Nodes** and go to **Settings** → **MCP Servers** 1. **Click + New MCP Server** 1. **Configure the server**: - **Server Name/ID**: `exa` - **Connection Type**: `Streamable HTTP` - **Configuration JSON**: ``` { "transport": "streamable_http", "url": "https://mcp.exa.ai/mcp", "headers": {}, "timeout": 30, "sse_read_timeout": 300, "terminate_on_close": true } ``` 1. **Click Create Server** ## Available Tools - **`get_code_context_exa`** - Search billions of GitHub repos, docs, and Stack Overflow for relevant code examples - **`web_search_exa`** - Real-time web searches with optimized results - **`crawling`** - Extract content from specific URLs - **`company_research`** - Comprehensive company information gathering - **`linkedin_search`** - Search LinkedIn for companies and people - **`deep_researcher_start`** - Start AI-powered research on complex topics - **`deep_researcher_check`** - Get comprehensive research reports ## Configuration Options You can enable specific tools by adding them to your configuration: ``` { "transport": "streamable_http", "url": "https://mcp.exa.ai/mcp", "enabled_tools": ["get_code_context_exa", "web_search_exa"] } ``` Available tool combinations: - **For Developers**: `["get_code_context_exa", "web_search_exa"]` - **For Researchers**: `["web_search_exa", "deep_researcher_start", "deep_researcher_check"]` - **For Business Intelligence**: `["company_research", "linkedin_search", "web_search_exa"]` - **All Tools**: `["get_code_context_exa", "web_search_exa", "company_research", "crawling", "linkedin_search", "deep_researcher_start", "deep_researcher_check"]` ## Troubleshooting ### Common Issues - **Connection Issues**: Test the remote server URL `https://mcp.exa.ai/mcp`, check your internet connection, verify firewall settings allow HTTPS connections - **Tool Not Available**: Ensure the tool is enabled in your configuration, check the tool name spelling, verify the tool is available in the current Exa service ### Debug Tips 1. Test with simple queries first 1. Check the Exa dashboard for usage and errors 1. Use the remote server for easier troubleshooting 1. Start with basic tools before using advanced features ## Resources - [Exa MCP Server](https://mcp.exa.ai/mcp) - Official server endpoint - [Exa AI](https://exa.ai) - Exa AI platform and documentation # Fetch MCP Server The **[Fetch MCP Server](https://github.com/modelcontextprotocol/servers/tree/main/src/fetch)** provides web content fetching capabilities, allowing AI agents to retrieve and process content from web pages. It converts HTML to markdown for easier consumption and is perfect for research, content analysis, and web scraping tasks. ## Installation 1. **Open Griptape Nodes** and go to **Settings** → **MCP Servers** 1. **Click + New MCP Server** 1. **Configure the server**: - **Server Name/ID**: `fetch` - **Connection Type**: `Local Process (stdio)` - **Configuration JSON**: ``` { "transport": "stdio", "command": "uvx", "args": ["mcp-server-fetch"], "env": {}, "encoding": "utf-8", "encoding_error_handler": "strict" } ``` 1. **Click Create Server** ## Available Tools - **`fetch`** - Fetch content from a web URL and convert to markdown ## Configuration Options You can customize the fetch server behavior with environment variables: ``` { "transport": "stdio", "command": "uvx", "args": ["mcp-server-fetch"], "env": { "FETCH_TIMEOUT": "30000", "FETCH_USER_AGENT": "MyApp/1.0" }, "encoding": "utf-8", "encoding_error_handler": "strict" } ``` Available environment variables: - **`FETCH_TIMEOUT`** - Request timeout in milliseconds (default: `30000`) - **`FETCH_USER_AGENT`** - User agent string for requests (default: `mcp-server-fetch`) - **`FETCH_MAX_SIZE`** - Maximum response size in bytes (default: `10485760` (10MB)) ## Troubleshooting ### Common Issues - **Server Not Responding**: Check your internet connection, verify the URL is accessible in a browser, try a different URL to test the server - **Content Not Loading**: Some websites block automated requests, try adding a custom user agent, check if the site requires authentication - **Timeout Errors**: Increase the `FETCH_TIMEOUT` value, try smaller, simpler pages first, check your network connection speed - **Invalid URLs**: Ensure URLs include the protocol (http:// or https://), check for typos in the URL, verify the website is accessible ## Resources - [Fetch MCP Server](https://github.com/modelcontextprotocol/servers/tree/main/src/fetch) - Official repository and documentation # Filesystem MCP Server The **[Filesystem MCP Server](https://github.com/modelcontextprotocol/servers/blob/main/src/filesystem/README.md)** enables AI agents to perform file and directory operations on your local machine. It provides secure, controlled access to specific directories for file management, content organization, and data processing tasks. ## Installation 1. **Open Griptape Nodes** and go to **Settings** → **MCP Servers** 1. **Click + New MCP Server** 1. **Configure the server**: - **Server Name/ID**: `filesystem` - **Connection Type**: `Local Process (stdio)` - **Configuration JSON**: ``` { "transport": "stdio", "command": "npx", "args": [ "-y", "@modelcontextprotocol/server-filesystem", "/path/to/allowed/directory"], "env": {}, "encoding": "utf-8", "encoding_error_handler": "strict" } ``` 1. **Click Create Server** ## Available Tools - **`read_file`** - Read file contents - **`write_file`** - Write content to files - **`list_directory`** - List directory contents - **`create_directory`** - Create new directories - **`search_files`** - Find files by name or pattern - **`move_file`** - Move or rename files - **`delete_file`** - Remove files ## Configuration Options To allow access to multiple directories, add them as separate arguments: ``` { "transport": "stdio", "command": "npx", "args": [ "-y", "@modelcontextprotocol/server-filesystem", "/Users/username/Desktop", "/Users/username/Downloads", "/Users/username/Documents" ], "env": {}, "encoding": "utf-8", "encoding_error_handler": "strict" } ``` ## Resources - [Filesystem MCP Server](https://github.com/modelcontextprotocol/servers/blob/main/src/filesystem/README.md) - Official repository and documentation - [Node.js File System API](https://nodejs.org/api/fs.html) - Reference for file operations # Maya MCP Server The **[Maya MCP Server](https://github.com/PatrickPalmer/MayaMCP)** enables AI agents to directly interact with and control Autodesk Maya through natural language using the Model Context Protocol (MCP). This integration allows for prompt-assisted 3D modeling, scene creation, and manipulation. This is a third-party server created by [Patrick Palmer](https://github.com/PatrickPalmer), and is not made by Autodesk Maya or Griptape. Third-Party Server This MCP server is not officially supported by Griptape or Autodesk Maya. Use at your own discretion and ensure you have proper backups of your work. Prerequisites Before using the Maya MCP server, you must: 1. Have [Autodesk Maya](https://www.autodesk.com/products/maya) 2023 or newer installed 1. Install Python 3.10 or greater 1. Download and set up the Maya MCP server from the [repository](https://github.com/PatrickPalmer/MayaMCP). See the [installation instructions](#1-download-and-setup-maya-mcp-server) for specific info. ## Installation ### 1. Download and Setup Maya MCP Server 1. Open up a terminal and navigate to the location on your machine where you'd like to download the repository. I would recommend placing the server in an easily discoverable location. For example, on my Mac I like to place it where I keep my GitHub repos: `$HOME/Documents/GitHub`. ``` cd $HOME/Documents/GitHub ``` 1. **Clone the repository**: ``` git clone https://github.com/PatrickPalmer/MayaMCP.git cd MayaMCP ``` 1. **Create a virtual environment**: ``` python -m venv .venv ``` 1. **Activate the virtual environment**: - **Windows**: `.venv\Scripts\activate.bat` - **Mac/Linux**: `source .venv\bin\activate` 1. **Install dependencies**: ``` pip install -r requirements.txt ``` ### 2. Open Maya and Enable Command Port 1. **Open Autodesk Maya** 1. **Enable the Command Port** - This is required for the MCP server to communicate with Maya. In Maya's Script Editor, run this Python code: ``` import maya.cmds as cmds def setup_maya_command_port(port=50007): """Setup Maya command port with error handling""" try: # First, try to close any existing command port on this port try: cmds.commandPort(name=f"localhost:{port}", close=True) print(f"Closed existing command port on localhost:{port}") except: # No existing port to close, that's fine pass # Enable the command port cmds.commandPort(name=f"localhost:{port}") print(f"Command Port successfully enabled on localhost:{port}") return True except Exception as e: print(f"Error setting up command port: {e}") return False # Run the setup if setup_maya_command_port(50007): print("Maya MCP server should now be able to connect!") else: print("Failed to setup command port. Check Maya's Command Port settings in Preferences.") ``` Command Port Required Every Session You must enable the Command Port every time you start Maya. The Command Port setting is not persistent between Maya sessions. Make It Easier: Save as Maya Script To make this process easier, you can save the command port setup as a Maya script: 1. **Save the following script as `enable_mcp_command_port.py`**: ``` import maya.cmds as cmds def enable_mcp_command_port(port=50007): """Setup Maya command port with error handling""" try: # First, try to close any existing command port on this port try: cmds.commandPort(name=f"localhost:{port}", close=True) print(f"Closed existing command port on localhost:{port}") except: # No existing port to close, that's fine pass # Enable the command port cmds.commandPort(name=f"localhost:{port}") print(f"Command Port successfully enabled on localhost:{port}") return True except Exception as e: print(f"Error setting up command port: {e}") return False ``` 1. **Test the script** in Maya's Script Editor: ``` import enable_mcp_command_port enable_mcp_command_port.enable_mcp_command_port() ``` 1. **Choose one of these options**: **Option A: Create a Shelf Button** - Drag the test code from step 2 to the shelf to create a button - Click the button whenever you need to enable the command port **Option B: Auto-Start with userSetup.py** - Find Maya's userScripts directory: - **Windows**: `%USERPROFILE%\Documents\maya\2025\scripts\` - **macOS**: `~/Library/Preferences/Autodesk/maya/2025/scripts/` - **Linux**: `~/maya/2025/scripts/` - Add this line to your existing `userSetup.py` file (or create one if it doesn't exist): ``` import enable_mcp_command_port enable_mcp_command_port.enable_mcp_command_port() ``` - Restart Maya - the command port will be enabled automatically Tip The Maya MCP server communicates with Maya through the Command Port. When the MCP server first attempts to communicate with Maya, you may get a popup within Maya asking for permission. Click **"Allow All"** to enable ongoing communication. ### 3. Configure Griptape Nodes 1. **Open Griptape Nodes** and go to **Settings** → **MCP Servers** 1. **Click + New MCP Server** 1. **Configure the server**: - **Server Name/ID**: `maya` - **Connection Type**: `Local Process (stdio)` - **Configuration JSON** (choose the appropriate example for your platform): **Windows**: ``` { "transport": "stdio", "command": "C:\\path\\to\\MayaMCP\\.venv\\Scripts\\python.exe", "args": [ "C:\\path\\to\\MayaMCP\\src\\maya_mcp_server.py" ], "cwd": null, "encoding": "utf-8", "encoding_error_handler": "strict" } ``` **macOS/Linux**: ``` { "transport": "stdio", "command": "/path/to/MayaMCP/.venv/bin/python", "args": [ "/path/to/MayaMCP/src/maya_mcp_server.py" ], "cwd": null, "encoding": "utf-8", "encoding_error_handler": "strict" } ``` Path Configuration Replace the example paths with the actual absolute path to your MayaMCP project directory. Use forward slashes (`/`) on macOS/Linux and backslashes (`\\`) on Windows. 1. **Click Create Server** ## Available Tools ### Basic Tools - **`list_objects_by_type`** - Get a list of objects in the scene, with optional filtering for cameras, lights, materials, or shapes - **`create_object`** - Create basic objects (cube, cone, sphere, cylinder, camera, lights) - **`get_object_attributes`** - Get a list of attributes on a Maya object - **`set_object_attribute`** - Set an object's attribute with a specific value - **`scene_new`** - Create a new scene in Maya - **`scene_open`** - Load a scene into Maya - **`scene_save`** - Save the current scene - **`select_object`** - Select an object in the scene - **`clear_selection_list`** - Clear the user selection list - **`viewport_focus`** - Center and fit the viewport to focus on an object ### Advanced Modeling Tools - **`create_advanced_model`** - Create complex 3D models (cars, trees, buildings, cups, chairs) with detailed parameters - **`mesh_operations`** - Perform modeling operations (extrude, bevel, subdivide, boolean, combine, bridge, split) - **`create_material`** - Create and assign materials (lambert, phong, wood, marble, chrome, glass, etc.) - **`create_curve`** - Generate NURBS curves (line, circle, spiral, helix, star, gear, etc.) - **`curve_modeling`** - Create geometry using curve-based modeling (extrude, loft, revolve, sweep, etc.) - **`organize_objects`** - Organize objects through grouping, parenting, layout, alignment, and distribution ## Configuration Options You can customize the Maya connection by modifying the configuration: ``` { "transport": "stdio", "command": "/path/to/MayaMCP/.venv/bin/python", "args": [ "/path/to/MayaMCP/src/maya_mcp_server.py" ], "cwd": "/path/to/MayaMCP", "encoding": "utf-8", "encoding_error_handler": "strict" } ``` ### Platform-Specific Paths **Windows**: ``` { "command": "C:\\path\\to\\MayaMCP\\.venv\\Scripts\\python.exe", "args": ["C:\\path\\to\\MayaMCP\\src\\maya_mcp_server.py"] } ``` **macOS/Linux**: ``` { "command": "/path/to/MayaMCP/.venv/bin/python", "args": ["/path/to/MayaMCP/src/maya_mcp_server.py"] } ``` ## Example Use Cases Here are some examples of what you can create with the Maya MCP server: - "Create a simple car model with 4 wheels and a sporty design" - "Build a tree with 3 branches and dense foliage" - "Create a building with windows and apply a brick material" - "Make a cup and apply a chrome material to it" - "Create a chair and position it in the scene" - "Generate a spiral curve and extrude it to create a spring" - "Create a gear-shaped curve for mechanical modeling" - "Group all the furniture objects together" - "Align all objects to the world origin" - "Create a new scene and save it as 'my_project.ma'" ## Advanced Features ### Custom Model Creation The `create_advanced_model` tool supports various model types with specific parameters: **Car Model**: ``` { "model_type": "car", "parameters": { "wheels": 4, "sporty": true, "convertible": false } } ``` **Tree Model**: ``` { "model_type": "tree", "parameters": { "branches": 3, "leaf_density": 0.8, "type": "pine" } } ``` ### Material Creation Create various material types with custom properties: **Chrome Material**: ``` { "material_type": "chrome", "color": [0.8, 0.8, 0.8], "parameters": { "reflectivity": 0.9 } } ``` **Wood Material**: ``` { "material_type": "wood", "color": [0.6, 0.4, 0.2], "parameters": { "veinSpread": 0.5, "veinColor": [0.3, 0.2, 0.1] } } ``` ## Troubleshooting ### Common Issues - **Connection Issues**: Ensure Maya is running and the Command Port is enabled, verify the MCP server is configured correctly in Griptape Nodes, check that the Python path points to the correct virtual environment - **Permission Denied**: When Maya first connects, click "Allow All" in the Maya popup to enable communication - **Path Issues**: Use absolute paths in the configuration, ensure the MayaMCP project path is correct - **Python Version**: Ensure you're using Python 3.10 or greater ### Debug Tips 1. Test with simple commands first (e.g., "list all objects in the scene") 1. Verify Maya is running and accessible 1. Check that the virtual environment is properly activated 1. Ensure all file paths in the configuration are absolute and correct 1. Restart both Maya and the MCP connection if issues persist ### Maya Command Port The Maya MCP server uses Maya's default Command Port for communication. This means: - No additional Maya plugins or addons are required - Communication happens through MEL scripting - Python code is executed within Maya's Python interpreter - Results are returned through the command port ## Security Considerations Arbitrary Code Execution The Maya MCP server executes Python code within Maya's environment. This can be powerful but potentially dangerous: - **Always save your Maya work** before using the MCP server - Review generated operations when possible - Use with caution in production environments - Be aware that complex operations could potentially affect your Maya scene Maya Command Port The server uses Maya's Command Port for communication: - This is a standard Maya feature for external communication - No additional security measures are implemented - Ensure your Maya installation is secure and up-to-date - Consider the implications of allowing external access to Maya ## Resources - [Maya MCP Server Repository](https://github.com/PatrickPalmer/MayaMCP) - Official repository and documentation - [Autodesk Maya](https://www.autodesk.com/products/maya) - Official Maya documentation - [Maya Python API](https://help.autodesk.com/view/MAYAUL/2023/ENU/?guid=Maya_SDK_Python_ref_index_html) - Reference for Maya scripting - [Model Context Protocol](https://modelcontextprotocol.io/) - MCP specification and documentation ## Developer Notes The Maya MCP server is designed to be easily extensible. New tools can be added by creating Python files in the `mayatools/thirdparty` directory. The server automatically discovers and registers new tools at runtime. Key design principles: - Tools run directly in Maya's Python environment - No MCP decorators needed in tool files - Functions are scoped to prevent namespace pollution - Results are returned through Maya's command port - Error handling is built into the communication layer # Time MCP Server The [**Time MCP Server**](https://github.com/modelcontextprotocol/servers/tree/main/src/time) provides date and time operations for AI agents, enabling them to work with temporal data, scheduling, and time-based calculations. It's perfect for any workflow that needs to handle dates, times, timezones, or scheduling. ## Installation 1. **Open Griptape Nodes** and go to **Settings** → **MCP Servers** 1. **Click + New MCP Server** 1. **Configure the server**: - **Server Name/ID**: `time` - **Connection Type**: `Local Process (stdio)` - **Configuration JSON**: ``` { "transport": "stdio", "command": "uvx", "args": ["mcp-server-time"], "env": {}, "cwd": null, "encoding": "utf-8", "encoding_error_handler": "strict" } ``` 1. **Click Create Server** ## Available Tools - **`get_current_time`** - Get the current date and time - **`parse_date`** - Parse date strings into structured format - **`format_date`** - Format dates into different string formats - **`add_time`** - Add time periods to dates - **`subtract_time`** - Subtract time periods from dates - **`compare_dates`** - Compare two dates - **`get_timezone_info`** - Get information about timezones - **`convert_timezone`** - Convert times between timezones ## Resources - [Time MCP Server](https://github.com/modelcontextprotocol/servers/tree/main/src/time) - Official repository and documentation - [JavaScript Date Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) - Reference for date operations - [Timezone Database](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) - Complete list of timezones # External services # How to get and use an OpenAI API Key While Griptape Nodes provides easy access to OpenAI models by default via your Griptape Cloud account and it's key (this is all handled automatically during install), you might want to connect directly to your own OpenAI account. This gives you full control over your usage, settings, and permissions. If that's what you'd like to do, getting your OpenAI API key is the simple first step. ## Account and API Key Creation Before you can get API keys for your OpenAI account, you'll need an OpenAI account. To begin, head to Info If you already have an account, go ahead and skip ahead to [Step 2](#2-create-an-api-key) ### 1. Create an OpenAI account 1. You can either go to the Log In (choose any option, they all create the same account), and then choose "sign up", or go directly to 1. Complete the registration process ### 2. Create an API Key 1. If you're not there already, go back to and from the **Log In** button in the top right, select the **API Platform** option 1. Go to the "Dashboard" area - look for it near the top of the page after logging in. 1. Navigate to the **API Keys** section - look for it on the left after opening the dashboard. Account Verification Required If you encounter issues during key creation, ensure you've **verified** your account which should have been part of the account creation the website guided you through. Complete the verification process before continuing. That should present you a screen, that towards the top right looks like this: 1. Click **Create New Secret Key**, which should take you to a model like this: 1. Set the key permissions to "Read-Only" (feel free to research other options, this is recommended merely for expedience here) 1. Click **Create Secret Key**. That will bring up a window with your new API key. Read and understand the messages there; this really is the only time you'll be able to see or copy this key. 1. Copy and securely store your API key Security Notice It is recommended to save this token in a password locker or secure notes app, so you can find it, but also keep it secure. Your access token is a personal identifier for requests made to OpenAI services. Never share it with anyone, and take precautions to avoid displaying it in screenshots, videos, or during screen-sharing sessions. Treat it like you would a credit card number. 1. Click **Done** to close the key window ## Add Your Secret Key to Griptape Nodes Settings Overview Now that you've set up your OpenAI account, you need to configure Griptape Nodes to use your secret key. This process is straightforward. ### 1. Open the Griptape Nodes Settings Menu 1. Launch Griptape Nodes 1. Look for the **Settings** menu located in the top menu bar (just to the right of File and Edit) 1. Click on **Settings** to open the configuration options ### 2. Add your OpenAI Secret Key in API Keys & Secrets 1. In the Configuration Editor, click on **API Keys and Secrets** on the left 1. Scroll down to the **OPENAI_API_KEY** field 1. Paste the secret key you just generated into this field 1. Close the Configuration Editor to automatically save your settings Setup Complete After completing these steps, you should have the ability to use models via your own account credentials. # How to get and use an xAI Grok API key Grok is a family of Large Language Models (LLMs) developed by xAI. You can access these models via the GrokPrompt config node. To make use of these, however, you'll need to have an XAI account, and generate an API key. It's worth noting as well, that xAI is a paid service, and to make use of it, you'll need to set up billing details on their website. ## Account and API Key Creation Before you can get API keys for your xAI account, you'll *need* a xAI account. To begin, head to Info If you already have an account, go ahead and skip ahead to [Step 2](#2-set-up-billing) ### 1. Create a xAI account 1. Click on the console login option, or navigate to 1. Complete the signup process, it is extremely straightforward. ### 2. Set Up Billing Billing Required Before using xAI models with Griptape Nodes, be aware that xAI *requires* billing information to be set up. Without this step, the models won't be available for use in your Nodes. If you attempt to run Nodes using xAI without completing this billing setup, your workflows will fail as the service will reject the account credentials. ### 3. Generate an API Key 1. Navigate to the API keys section; there are multiple links to take you there. 1. Click on "Create API Key" 1. Name your API key (suggested name: GriptapeNodes!) 1. Click **save** 1. Copy and securely store your API key Security Notice It is recommended to save this token in a password locker or secure notes app, so you can find it, but also keep it secure. Your access token is a personal identifier for requests made to OpenAI services. Never share it with anyone, and take precautions to avoid displaying it in screenshots, videos, or during screen-sharing sessions. Treat it like you would a credit card number. ## Add Your API Key to Griptape Nodes Settings Overview Now that you've set up your xAI account, you need to configure Griptape Nodes to use your API key. This process is straightforward. ### 1. Open the Griptape Nodes Settings Menu 1. Launch Griptape Nodes 1. Look for the **Settings** menu located in the top menu bar (just to the right of File and Edit) 1. Click on **Settings** to open the configuration options ### 2. Add your xAI API Key in API Keys & Secrets 1. In the Configuration Editor, click on **API Keys and Secrets** on the left 1. Scroll down to the **GROK_API_KEY** field 1. Paste the API key you just generated into this field 1. Close the Configuration Editor to automatically save your settings Setup Complete After completing these steps, you should have the ability to use models via your own Grok.ai account credentials. # Setup for Nodes that use Hugging Face ## Account and Token Creation This guide will walk you through setting up a Hugging Face account, creating an access token, and installing the required models for use with Griptape Nodes. Info If you already have an account skip ahead to [Step 2](#2-create-an-access-token) ### 1. Create a new account on Hugging Face 1. Go to 1. Click **Sign Up** in the top-right corner 1. Complete the verification step to prove you're not a robot ### 2. Create an Access Token 1. Access Your Account Settings 1. Log in to your Hugging Face account 1. Click on your profile icon in the top right corner 1. Select **Settings** from the dropdown menu (or go directly to [Settings](https://huggingface.co/settings/profile/)) Email Verification Required If you encounter issues during token creation, ensure you've verified your email address. Complete the verification process before continuing. 1. Navigate to **Access Tokens** in the settings menu 1. Click **Create new token** in the top right area 1. Select **Read** as the token type for the access you'll need 1. Give your token a descriptive name (GriptapeNodes, for example) 1. Click **Create Token**. That will bring up a window with you new token in it. Read and understand the messages there; this really is the only time you'll be able to see or copy this key. 1. Copy and securely store your token 1. Click **Done** to close the token window. Security Notice It is recommended to save this token in a password locker or secure notes app, so you can find it, but also keep it secure. Your access token is a personal identifier for requests made to Hugging Face services. Never share it with anyone, and take precautions to avoid displaying it in screenshots, videos, or during screen-sharing sessions. Treat it like you would a credit card number. ## Install Required Files Now that you have a token associated with your account, you can install the Hugging Face CLI (Command Line Interface) to interact with Hugging Face from the command line. ### 1. Install the Hugging Face CLI Open a terminal and run: ``` pip install -U "huggingface_hub[cli]" ``` For more information, visit the [official CLI documentation](https://huggingface.co/docs/huggingface_hub/main/en/guides/cli). ### 2. Login with Your Token In your terminal, authenticate with the access token you made earlier: ``` huggingface-cli login ``` You'll be prompted to enter your token. ### 3. Install Models as Required Each Hugging Face node is designed to work with specific models. While some nodes work with only one model, others are compatible with multiple options. The table below outlines which models can be used with each node: | Node | Compatible Model(s) | | ----------------- | -------------------------- | | SPAN Upscale | 4x-ClearRealityV1.pth | | Flux | FLUX.1-dev, FLUX.1-schnell | | Flux Post Upscale | FLUX.1-dev, FLUX.1-schnell | ## Model Installation Considerations You have two options for model installation. Your choice depends on your specific workflow requirements, available disk space, and internet connection speed. 1. **Selective Installation**: Install only the specific models needed for the nodes you plan to use. - **Advantages**: Reduced download time and disk space usage. - **Disadvantages**: Limited functionality until additional models are installed. 1. **Complete Installation**: Download all available models. - **Advantages**: Full access to all node capabilities without additional downloads. - **Disadvantages**: Longer initial download time and greater disk space requirements. Download Time These model downloads are quite large an may collectively take anywhere from 30 minutes to several hours to complete, depending on your internet connection speed. You can continue to the last step while downloads are ongoing. Download Location Models will be downloaded into your Hugging Face Hub Cache Directory. To see where this is, you can use the huggingface-cli, and look for HF_HUB_CACHE. Running this command in the terminal will produce a list with several entries ``` huggingface-cli env ``` Look for an entry that starts with `- HF_HUB_CACHE:` ``` - HF_HUB_CACHE: /Users/jason/.cache/huggingface/hub ``` #### For SPAN Upscale ``` huggingface-cli download skbhadra/ClearRealityV1 4x-ClearRealityV1.pth ``` #### For Flux and Flux Post Upscale ``` huggingface-cli download black-forest-labs/FLUX.1-schnell ``` #### For FLUX.1-dev ``` huggingface-cli download black-forest-labs/FLUX.1-dev ``` Download errors It is possible to encounter errors during download that start like this: ``` Cannot access gated repo for url https://huggingface.co... ``` The end of those errors will contain a link, though, with instructions to request access. Follow those instructions: Visit to ask for access. When that is successful you should see a message about being granted access, and you can try to download again. ## Add Your Token to Griptape Nodes settings Overview Now that you've set up your Hugging Face account and installed the required models, you need to configure Griptape Nodes to use your token. This process is straightforward. ### 1. Open the Griptape Nodes Settings Menu 1. Launch Griptape Nodes 1. Look for the **Settings** menu located in the top menu bar (just to the right of File and Edit) 1. Click on **Settings** to open the configuration options ### 2. Add your Hugging Face Token in API Keys & Secrets 1. In the Configuration Editor, locate **API Keys and Secrets** in the bottom left 1. Click to expand this section 1. Scroll down to the **HUGGINGFACE_HUB_ACCESS_TOKEN** field 1. Paste your previously created token into this field 1. Close the Configuration Editor to automatically save your settings Setup Complete After completing these steps, the Hugging Face Nodes should be ready to use in Griptape Nodes! # Scripting (retained mode) # Griptape Nodes Retained Mode Scripting "Retained Mode" provides a Python scripting interface for interacting with Griptape Nodes. This allows users to create, modify, and manage nodes, parameters, connections, and flows through a simplified Python API. > **Note:** The actual import command for RetainedMode is: > > ``` > from griptape_nodes.retained_mode import RetainedMode as cmd > ``` > > However, for convenience, in the script editor of the GUI, this import is already done for you automatically, so you can freely use `cmd.` directly. ## Working with Scripts There are two primary ways to develop and run scripts: 1. **Using the Script Editor** - Write scripts directly in the Griptape Nodes script editor and execute them immediately to modify or control your flows. 1. **Importing External Scripts** - Keep reusable script modules in external files, import them with Python's import system in the script editor, and run the imported functionality from there. The script editor is the primary entry point for all scripting operations. Scripts are useful for automating repetitive tasks: duplicating nodes with their properties and connections, exporting node values and importing them into other flows, programmatically creating entire flows with predefined nodes and connections, or performing bulk operations across many nodes in a loop. You can also combine Retained Mode with other Python libraries (pandas, numpy, requests, and so on) to extend what your scripts can do. The rest of this page is the API reference: flow management, node operations, parameter management, connections, and flow execution. ## Flow Operations ### create_flow Creates a new flow within the Griptape system. ``` cmd.create_flow(flow_name=None, parent_flow_name=None) ``` #### Arguments | Name | Argument Type | Required | | ---------------- | ------------- | -------- | | flow_name | string | ⚪ | | parent_flow_name | string | ⚪ | #### Return Value ResultPayload object with flow creation status #### Description Creates a new flow with the specified name. If parent_flow_name is provided, the new flow will be created as a child of the specified parent flow. ______________________________________________________________________ ### delete_flow ``` cmd.delete_flow(flow_name) ``` Deletes an existing flow. #### Arguments | Name | Argument Type | Required | | --------- | ------------- | -------- | | flow_name | string | 🟢 | #### Return Value ResultPayload object with flow deletion status #### Description Removes the specified flow from the system. ______________________________________________________________________ ### get_flows ``` cmd.get_flows(parent_flow_name=None) ``` Lists all flows within a parent flow. #### Arguments | Name | Argument Type | Required | | ---------------- | ------------- | -------- | | parent_flow_name | string | ⚪ | #### Return Value ResultPayload object containing a list of flows #### Description Returns all flows within the specified parent flow. If no parent_flow_name is provided, returns all top-level flows. ______________________________________________________________________ ### get_nodes_in_flow ``` cmd.get_nodes_in_flow(flow_name, node_types=None) ``` Lists all nodes within a flow, with an optional filter by Python class name. #### Arguments | Name | Argument Type | Required | | ---------- | ------------- | -------- | | flow_name | string | 🟢 | | node_types | list[string] | None | #### Return Value ResultPayload object containing a list of node names #### Description Returns all nodes within the specified flow. When `node_types` is provided, only nodes whose Python class name matches one of the entries are returned (e.g. `["StartFlow", "Agent"]`). Omit `node_types` or pass `None` to return all nodes. ______________________________________________________________________ ### run_flow ``` cmd.run_flow(flow_name) ``` Executes a flow. #### Arguments | Name | Argument Type | Required | | --------- | ------------- | -------- | | flow_name | string | 🟢 | #### Return Value ResultPayload object with flow execution status #### Description Starts the execution of the specified flow. ______________________________________________________________________ ### reset_flow ``` cmd.reset_flow(flow_name) ``` Resets a flow to its initial state. #### Arguments | Name | Argument Type | Required | | --------- | ------------- | -------- | | flow_name | string | 🟢 | #### Return Value ResultPayload object with flow reset status #### Description Unresolves all nodes in the flow, returning it to its initial state. ______________________________________________________________________ ### get_flow_state ``` cmd.get_flow_state(flow_name) ``` Returns the current state of a flow. #### Arguments | Name | Argument Type | Required | | --------- | ------------- | -------- | | flow_name | string | 🟢 | #### Return Value ResultPayload object containing flow state information #### Description Gets the current execution state of the specified flow. ______________________________________________________________________ ### cancel_flow ``` cmd.cancel_flow(flow_name) ``` Cancels the execution of a flow. #### Arguments | Name | Argument Type | Required | | --------- | ------------- | -------- | | flow_name | string | 🟢 | #### Return Value ResultPayload object with flow cancellation status #### Description Stops the execution of the specified flow. ______________________________________________________________________ ### single_step ``` cmd.single_step(flow_name) ``` Executes a single node step in a flow. #### Arguments | Name | Argument Type | Required | | --------- | ------------- | -------- | | flow_name | string | 🟢 | #### Return Value ResultPayload object with step execution status #### Description Executes a single node in the specified flow. ______________________________________________________________________ ### single_execution_step ``` cmd.single_execution_step(flow_name) ``` Executes a single execution step in a flow. #### Arguments | Name | Argument Type | Required | | --------- | ------------- | -------- | | flow_name | string | 🟢 | #### Return Value ResultPayload object with execution step status #### Description Executes a single execution step in the specified flow. ______________________________________________________________________ ### continue_flow ``` cmd.continue_flow(flow_name) ``` Continues the execution of a paused flow. #### Arguments | Name | Argument Type | Required | | --------- | ------------- | -------- | | flow_name | string | 🟢 | #### Return Value ResultPayload object with flow continuation status #### Description Continues the execution of a flow that was previously paused. ## Node Operations ### create_node ``` cmd.create_node(node_type, specific_library_name=None, node_name=None, parent_flow_name=None, metadata=None) ``` Creates a new node. #### Arguments | Name | Argument Type | Required | | --------------------- | ------------- | -------- | | node_type | string | 🟢 | | specific_library_name | string | ⚪ | | node_name | string | ⚪ | | parent_flow_name | string | ⚪ | | metadata | dict | ⚪ | #### Return Value Node name or ResultPayload object with node creation status #### Description Creates a new node of the specified type. Optional parameters allow specifying the library, node name, parent flow, and metadata. ______________________________________________________________________ ### delete_node ``` cmd.delete_node(node_name) ``` Deletes an existing node. #### Arguments | Name | Argument Type | Required | | --------- | ------------- | -------- | | node_name | string | 🟢 | #### Return Value ResultPayload object with node deletion status #### Description Removes the specified node from the system. ______________________________________________________________________ ### run_node ``` cmd.run_node(node_name) ``` Executes a single node. #### Arguments | Name | Argument Type | Required | | --------- | ------------- | -------- | | node_name | string | 🟢 | #### Return Value ResultPayload object with node execution status #### Description Resolves and executes the specified node. ______________________________________________________________________ ### get_resolution_state_for_node ``` cmd.get_resolution_state_for_node(node_name) ``` Returns the resolution state of a node. #### Arguments | Name | Argument Type | Required | | --------- | ------------- | -------- | | node_name | string | 🟢 | #### Return Value ResultPayload object containing node resolution state #### Description Gets the current resolution state of the specified node. ______________________________________________________________________ ### get_metadata_for_node ``` cmd.get_metadata_for_node(node_name) ``` Returns the metadata for a node. #### Arguments | Name | Argument Type | Required | | --------- | ------------- | -------- | | node_name | string | 🟢 | #### Return Value ResultPayload object containing node metadata #### Description Gets the metadata associated with the specified node. ______________________________________________________________________ ### set_metadata_for_node ``` cmd.set_metadata_for_node(node_name, metadata) ``` Sets the metadata for a node. #### Arguments | Name | Argument Type | Required | | --------- | ------------- | -------- | | node_name | string | 🟢 | | metadata | dict | 🟢 | #### Return Value ResultPayload object with metadata update status #### Description Sets the metadata for the specified node. ______________________________________________________________________ ### exists ``` cmd.exists(node) ``` Checks if a node exists. #### Arguments | Name | Argument Type | Required | | ---- | ------------- | -------- | | node | string | 🟢 | #### Return Value Boolean indicating whether the node exists #### Description Returns True if the specified node exists, False otherwise. ______________________________________________________________________ ### ls ``` cmd.ls(**kwargs) ``` Lists objects in the system. #### Arguments | Name | Argument Type | Required | | ---------- | ------------- | -------- | | \*\*kwargs | dict | ⚪ | #### Return Value List of object names matching the filter criteria #### Description Lists objects in the system, optionally filtered by the provided criteria. ## Parameter Operations ### list_params ``` cmd.list_params(node) ``` Lists all parameters on a node. #### Arguments | Name | Argument Type | Required | | ---- | ------------- | -------- | | node | string | 🟢 | #### Return Value List of parameter names #### Description Returns a list of all parameters on the specified node. ______________________________________________________________________ ### add_param ``` cmd.add_param( node_name, parameter_name, default_value, tooltip, type=None, input_types=None, output_type=None, edit=False, tooltip_as_input=None, tooltip_as_property=None, tooltip_as_output=None, ui_options=None, mode_allowed_input=True, mode_allowed_property=True, mode_allowed_output=True, **kwargs, ) ``` Adds a parameter to a node. #### Arguments | Name | Argument Type | Required | | --------------------- | ------------------ | -------- | | node_name | string | 🟢 | | parameter_name | string | 🟢 | | default_value | any | 🟢 | | tooltip | string or list | 🟢 | | type | string | ⚪ | | input_types | list of strings | ⚪ | | output_type | string | ⚪ | | edit | boolean | ⚪ | | tooltip_as_input | string or list | ⚪ | | tooltip_as_property | string or list | ⚪ | | tooltip_as_output | string or list | ⚪ | | ui_options | ParameterUIOptions | ⚪ | | mode_allowed_input | boolean | ⚪ | | mode_allowed_property | boolean | ⚪ | | mode_allowed_output | boolean | ⚪ | #### Return Value ResultPayload object with parameter addition status #### Description Adds a parameter to the specified node with the given configuration. If edit=True, modifies an existing parameter instead. ______________________________________________________________________ ### del_param ``` cmd.del_param(node_name, parameter_name) ``` Removes a parameter from a node. #### Arguments | Name | Argument Type | Required | | -------------- | ------------- | -------- | | node_name | string | 🟢 | | parameter_name | string | 🟢 | #### Return Value ResultPayload object with parameter removal status #### Description Removes the specified parameter from the node. ______________________________________________________________________ ### param_info ``` cmd.param_info(node, param) or cmd.param_info("node.param") ``` Gets information about a parameter. #### Arguments | Name | Argument Type | Required | | ----- | ------------- | -------- | | node | string | 🟢 | | param | string | 🟢 | #### Return Value ResultPayload object containing parameter details #### Description Returns detailed information about the specified parameter. Accepts either separate node and param arguments or a single "node.param" string. ______________________________________________________________________ ### get_value ``` cmd.get_value(node, param) or cmd.get_value("node.param") ``` Gets the value of a parameter. #### Arguments | Name | Argument Type | Required | | ----- | ------------- | -------- | | node | string | 🟢 | | param | string | 🟢 | #### Return Value The value of the parameter or a failure result #### Description Returns the current value of the specified parameter. Supports indexed access for container types (e.g., "node.param[0]"). ______________________________________________________________________ ### set_value ``` cmd.set_value(node, param, value) or cmd.set_value("node.param", value) ``` Sets the value of a parameter. #### Arguments | Name | Argument Type | Required | | ----- | ------------- | -------- | | node | string | 🟢 | | param | string | 🟢 | | value | any | 🟢 | #### Return Value ResultPayload object with value update status #### Description Sets the value of the specified parameter. Supports indexed access for container types (e.g., "node.param[0]"). ## Connection Operations ### connect ``` cmd.connect(source, destination) ``` Creates a connection between two parameters. #### Arguments | Name | Argument Type | Required | | ----------- | ------------- | -------- | | source | string | 🟢 | | destination | string | 🟢 | #### Return Value ResultPayload object with connection creation status #### Description Creates a connection from the source parameter to the destination parameter. Both arguments should be in the format "node.param". ______________________________________________________________________ ### exec_chain ``` cmd.exec_chain(*node_names) ``` Creates execution connections between a sequence of nodes. #### Arguments | Name | Argument Type | Required | | ------------ | ------------- | -------- | | \*node_names | string(s) | 🟢 | #### Return Value Dictionary with results of each connection attempt #### Description Creates exec_out -> exec_in connections between a sequence of nodes, effectively chaining them for execution in sequence. ______________________________________________________________________ ### delete_connection ``` cmd.delete_connection(source_node_name, source_param_name, target_node_name, target_param_name) ``` Deletes a connection between parameters. #### Arguments | Name | Argument Type | Required | | ----------------- | ------------- | -------- | | source_node_name | string | 🟢 | | source_param_name | string | 🟢 | | target_node_name | string | 🟢 | | target_param_name | string | 🟢 | #### Return Value ResultPayload object with connection deletion status #### Description Removes the connection between the specified source and target parameters. ______________________________________________________________________ ### get_connections_for_node ``` cmd.get_connections_for_node(node_name) ``` Lists all connections for a node. #### Arguments | Name | Argument Type | Required | | --------- | ------------- | -------- | | node_name | string | 🟢 | #### Return Value ResultPayload object containing connection information #### Description Returns all connections involving the specified node, both incoming and outgoing. ## Library Operations ### get_available_libraries ``` cmd.get_available_libraries() ``` Lists all available node libraries. #### Arguments None #### Return Value ResultPayload object containing a list of library names #### Description Returns all registered node libraries in the system. ______________________________________________________________________ ### get_node_types_in_library ``` cmd.get_node_types_in_library(library_name) ``` Lists all node types in a library. #### Arguments | Name | Argument Type | Required | | ------------ | ------------- | -------- | | library_name | string | 🟢 | #### Return Value ResultPayload object containing a list of node type names #### Description Returns all node types available in the specified library. ______________________________________________________________________ ### get_node_metadata_from_library ``` cmd.get_node_metadata_from_library(library_name, node_type_name) ``` Gets metadata for a node type. #### Arguments | Name | Argument Type | Required | | -------------- | ------------- | -------- | | library_name | string | 🟢 | | node_type_name | string | 🟢 | #### Return Value ResultPayload object containing node type metadata #### Description Returns the metadata for the specified node type in the given library. ## Config Operations ### get_config_value ``` cmd.get_config_value(category_and_key) ``` Gets a configuration value. #### Arguments | Name | Argument Type | Required | | ---------------- | ------------- | -------- | | category_and_key | string | 🟢 | #### Return Value ResultPayload object containing the configuration value #### Description Returns the value for the specified configuration key. ______________________________________________________________________ ### set_config_value ``` cmd.set_config_value(category_and_key, value) ``` Sets a configuration value. #### Arguments | Name | Argument Type | Required | | ---------------- | ------------- | -------- | | category_and_key | string | 🟢 | | value | any | 🟢 | #### Return Value ResultPayload object with configuration update status #### Description Sets the value for the specified configuration key. ______________________________________________________________________ ### get_config_category ``` cmd.get_config_category(category=None) ``` Gets all configuration values in a category. #### Arguments | Name | Argument Type | Required | | -------- | ------------- | -------- | | category | string | ⚪ | #### Return Value ResultPayload object containing category configuration values #### Description Returns all configuration values in the specified category. If no category is provided, returns all configuration values. ______________________________________________________________________ ### set_config_category ``` cmd.set_config_category(category=None, contents={}) ``` Sets configuration values for a category. #### Arguments | Name | Argument Type | Required | | -------- | ------------- | -------- | | category | string | ⚪ | | contents | dict | ⚪ | #### Return Value ResultPayload object with category configuration update status #### Description Sets all configuration values for the specified category with the provided contents. ## Utility Operations ### run_arbitrary_python ``` cmd.run_arbitrary_python(python_str) ``` Executes arbitrary Python code. #### Arguments | Name | Argument Type | Required | | ---------- | ------------- | -------- | | python_str | string | 🟢 | #### Return Value ResultPayload object with execution status and results #### Description Executes the provided Python code string in the Griptape environment. ## Examples ### Creating a Flow and Nodes ``` # Create a new flow flow = cmd.create_flow(flow_name="MyFlow") # Create two nodes in the flow node1 = cmd.create_node(node_type="CreateText", node_name="MyText", parent_flow_name="MyFlow") node2 = cmd.create_node(node_type="RunAgent", node_name="MyAgent", parent_flow_name="MyFlow") ``` ### Setting Parameter Values and Creating Connections ``` # Set a parameter value cmd.set_value("MyText.text", "This is a sample text to summarize.") # Connect two nodes cmd.connect("MyText.text", "MyAgent.prompt") ``` ### Running a Flow ``` # Run the flow cmd.run_flow("MyFlow") # Get the result summary = cmd.get_value("MyAgent.output") print(summary) # ``` ### Working with Parameters ``` # Add a new parameter to a node cmd.add_param( node_name="MyAgent", parameter_name="sunglasses", default_value=100, tooltip="The coolness of sunglasses", type=["int"], ) # Set the value of the new parameter cmd.set_value("MyAgent.sunglasses", 50) # Change the parameter cmd.add_param(node_name="MyAgent", parameter_name="sunglasses", tooltip="The coolness of sunglasses if halved", edit=1) ``` ### Listing and Querying ``` # List all nodes in a flow nodes = cmd.get_nodes_in_flow("MyFlow") print(nodes) # ["MyText","MyAgent"] # List all parameters on a node params = cmd.list_params("MyAgent") print(params) # ["agent", "prompt_driver", "tools", "rulesets", "prompt", "prompt_context", "output"] # Check if a node exists if cmd.exists("MyText"): print("MyText node exists") # True ``` # Custom nodes # Developing Nodes This section provides comprehensive documentation for developers building custom nodes for Griptape Nodes. For AI Assistants & Coding Agents All documentation in this section is available as post-processed markdown for AI coding assistants. The site exposes a full machine-readable surface; see [For Agents](https://docs.griptapenodes.com/for_agents/index.md) for the index. - **Getting Started**: [Markdown](https://docs.griptapenodes.com/en/stable/development/custom_nodes/getting_started/index.md) - **Overview** (this page): [Markdown](https://docs.griptapenodes.com/en/stable/development/custom_nodes/index.md) - **Example Code**: [View Python Example](https://raw.githubusercontent.com/griptape-ai/griptape-nodes/main/docs/development/custom_nodes/example_control_node.py) **Usage:** Point your AI assistant to these URLs with instructions like: `"Read this node development guide: [URL] and help me build a custom node"` ## Introduction Griptape Nodes are modular workflow components that enable users to build complex AI workflows through visual programming. This section covers both fundamental concepts and advanced patterns for creating robust, user-friendly nodes. If you're new to developing nodes, start with the [Getting Started Guide](https://docs.griptapenodes.com/development/custom_nodes/getting_started/index.md). It provides a beginner-friendly introduction to the node development ecosystem and walks you through building your first node. Nodes inherit from BaseNode subclasses: - **DataNode**: For data processing tasks - **ControlNode**: For flow control with exec_in/out - **StartNode**: For workflow initialization - **EndNode**: For workflow termination ## Core Concepts ### Base Classes - **DataNode**: Processes data without execution flow control. Use for nodes that transform or pass through data synchronously — they process immediately when their inputs are satisfied. - **ControlNode**: Manages execution flow with exec_in/exec_out connections. Use for nodes that make external API calls or perform long-running operations — override `async def aprocess()` for async work, or yield blocking work to a background thread with `AsyncResult`. If your node calls an API and polls for results, it must be a ControlNode. - **StartNode**: Entry points for workflows - **EndNode**: Terminal points for workflows ### Parameters Define inputs, outputs, and properties via the Parameter class. Parameters support: - Type validation - UI customization - Connection constraints - Default values - Traits (Options, Slider, Button, ColorPicker) See [Parameters](https://docs.griptapenodes.com/development/custom_nodes/parameters/index.md) for the full reference. ### Process Method The `process()` method contains core logic. Set outputs in `self.parameter_output_values`. For asynchronous work, override `async def aprocess()` instead — see [Execution and Lifecycle](https://docs.griptapenodes.com/development/custom_nodes/execution_and_lifecycle/index.md). ### Node States - **UNRESOLVED**: Initial state - **RESOLVING**: Currently processing - **RESOLVED**: Processing complete ### Connections Managed via lifecycle callbacks for validation and handling. See [Execution and Lifecycle](https://docs.griptapenodes.com/development/custom_nodes/execution_and_lifecycle/index.md). ### Events Use `on_griptape_event` for reacting to workflow events. ## Setting Up 1. Install griptape-nodes 1. Use virtual environments for isolation 1. Structure projects with simple folder hierarchies 1. Import from `griptape_nodes.exe_types.*` and `griptape_nodes_library.utils.*` ## Creating a Node ### Basic Node Structure ``` from typing import Any from griptape_nodes.exe_types.core_types import Parameter, ParameterMode from griptape_nodes.exe_types.node_types import DataNode class MyNode(DataNode): def __init__(self, **kwargs) -> None: super().__init__(**kwargs) self.category = "Category" self.description = "Description" self.add_parameter(Parameter(name="input", input_types=["str"], type="str", tooltip="Input parameter")) self.add_parameter(Parameter(name="output", output_type="str", tooltip="Output parameter")) def process(self) -> None: val = self.get_parameter_value("input").upper() self.parameter_output_values["output"] = val ``` ## Documentation Structure - **[Getting Started](https://docs.griptapenodes.com/development/custom_nodes/getting_started/index.md)** — Beginner-friendly walkthrough of your first node - **[Parameters](https://docs.griptapenodes.com/development/custom_nodes/parameters/index.md)** — Parameter attributes, traits, helper classes, containers, and advanced parameter patterns - **[Parameter UI Reference](https://docs.griptapenodes.com/development/custom_nodes/parameter_ui_reference/index.md)** — Parameter type to widget mapping, supported `ui_options` keys, and traits - **[Execution and Lifecycle](https://docs.griptapenodes.com/development/custom_nodes/execution_and_lifecycle/index.md)** — Lifecycle callbacks and asynchronous API integration patterns - **[Working with the Project System](https://docs.griptapenodes.com/development/custom_nodes/project_system/index.md)** — Saving files through situations, macros, and `ProjectFileParameter` - **[Best Practices and Error Handling](https://docs.griptapenodes.com/development/custom_nodes/error_handling/index.md)** — Secrets, imports, parameter payload size, validation, error handling, and logging - **[Authoring Libraries](https://docs.griptapenodes.com/development/custom_nodes/authoring_libraries/index.md)** — Library manifests, declarations, dependency management, and contributing to the standard library - **[Advanced Libraries](https://docs.griptapenodes.com/development/custom_nodes/advanced_libraries/index.md)** — `AdvancedNodeLibrary` lifecycle hooks, library-owned request handlers, and registering node types without listing them in the manifest - **[Custom Widgets](https://docs.griptapenodes.com/development/custom_nodes/custom_widgets/index.md)** — Custom JavaScript widget components and the widget testbed - **[Patterns and Examples](https://docs.griptapenodes.com/development/custom_nodes/examples/index.md)** — Advanced patterns from production nodes and quick-reference material - **[Node Isolation with Workers](https://docs.griptapenodes.com/development/custom_nodes/node_isolation_with_workers/index.md)** — Running a library isolated in a worker subprocess - **[Strict Mode Reference](https://docs.griptapenodes.com/development/custom_nodes/strict_mode/index.md)** — Strict-mode rules that identify isolation incompatibilities - **[Example Control Node](https://docs.griptapenodes.com/development/custom_nodes/example_control_node.py)** — A complete working example demonstrating best practices for building control nodes ## Start from the Template Repository The fastest way to a production-ready node library is the official template repository: [Griptape Nodes Library Template](https://github.com/griptape-ai/griptape-nodes-library-template/) ([readme](https://github.com/griptape-ai/griptape-nodes-library-template/blob/main/README.md)) It provides the necessary boilerplate, testing framework, and documentation patterns. The workflow: 1. **Use the template repository** - Create your own repository from the GitHub template 1. **Set up your environment** - Clone the repo to your Griptape Nodes workspace directory 1. **Configure your library** - Rename directories and update package information in `pyproject.toml` 1. **Create your nodes** - Define node classes (either ControlNode or DataNode) with appropriate parameters 1. **Implement your logic** - Code the required `process()` method and any additional functionality 1. **Configure library metadata** - Set up your library.json file with nodes and category information 1. **Register with the engine** - Add your library to Griptape Nodes through the settings interface 1. **Test and use** - Create flows using your custom nodes in the Griptape Nodes interface To understand how to design your nodes, explore the patterns used in the standard library: [node reference](https://docs.griptapenodes.com/nodes/overview/index.md). ## Quick Links - **Custom Scripts**: [Retained Mode Scripting](https://docs.griptapenodes.com/development/retained_mode/index.md) - Automate and script the engine with Python # Getting Started with Node Development For AI Assistants & Coding Agents This guide is available as post-processed markdown for AI coding assistants. The site exposes a full machine-readable surface; see [For Agents](https://docs.griptapenodes.com/for_agents/index.md) for the index. - **Getting Started** (this page): [Markdown](https://docs.griptapenodes.com/en/stable/development/custom_nodes/getting_started/index.md) - **Overview**: [Markdown](https://docs.griptapenodes.com/en/stable/development/custom_nodes/index.md) - **Example Code**: [View Python Example](https://raw.githubusercontent.com/griptape-ai/griptape-nodes/main/docs/development/custom_nodes/example_control_node.py) **Usage:** Point your AI assistant to these URLs with instructions like: `"Read this node development guide: [URL] and help me build a custom node"` This page is for developers who are **new to the Griptape Nodes ecosystem** and want to build custom nodes with confidence. It’s a beginner-friendly “front door” to the deeper, exhaustive technical material in the rest of this section — see the [Overview](https://docs.griptapenodes.com/development/custom_nodes/index.md) for the full map of reference pages. ## What you’ll build (mentally) before you write code At a high level: - A **Node** is a Python class that defines **parameters** (inputs/outputs/properties) and a `process()` method. - A **Workflow (Flow)** is a graph of nodes connected by parameters. - Parameters are both: - **UI elements** (what a user sees/edits/connects), and - **type-checked connection points** (what can connect to what). ### Choose the right base node type - **`DataNode`**: use when your node processes data and doesn’t need to branch execution. - **`ControlNode`**: use when your node needs explicit execution flow (control in/out). - **`SuccessFailureNode`**: use when you want separate control-flow outputs for success vs failure. - **Iterative loop nodes**: the engine’s loop primitives are built on `BaseIterativeStartNode` / `BaseIterativeEndNode`. If you’re unsure, start with a `DataNode` and only graduate to `ControlNode`/loop nodes when you need it. ## Quick start (recommended path) If you’ve never built a Griptape Node before, this is the fastest path to a working node: - Start from the [library template repository](https://github.com/griptape-ai/griptape-nodes-library-template/) (see the [overview](https://docs.griptapenodes.com/development/custom_nodes/#start-from-the-template-repository)) - Build a single `DataNode` first (no control flow). - Prefer the built-in `Parameter*` helper constructs for common types. - Validate inputs with `validate_before_node_run()`. - Add secrets through `GriptapeNodes.SecretsManager()` when needed. ## Your first node (minimal example) This is the smallest useful node you can build: read a string, transform it, emit a string. ``` from griptape_nodes.exe_types.core_types import Parameter, ParameterMode from griptape_nodes.exe_types.node_types import DataNode class UppercaseText(DataNode): def __init__(self, **kwargs) -> None: # Always call the parent constructor so the engine can initialize # the node’s internal state and register the node context. super().__init__(**kwargs) # add_parameter(...) registers a Parameter with the node. # Parameters define: # - what users can configure (PROPERTY mode), # - what can be connected from other nodes (INPUT mode), # - what can be connected to other nodes (OUTPUT mode). self.add_parameter( Parameter( name="text", # A parameter's "type" is its primary data type in the engine. # It influences UI defaults and connection type checking. type="str", # input_types controls what types can connect INTO this parameter. # You can allow multiple incoming types if you need flexible wiring. input_types=["str"], # default_value is used when nothing is connected and the user # hasn't set a value in the UI. default_value="Hello Griptape Nodes", allowed_modes={ParameterMode.INPUT, ParameterMode.PROPERTY}, tooltip="Input text", ) ) self.add_parameter( Parameter( name="uppercased", type="str", output_type="str", allowed_modes={ParameterMode.OUTPUT}, tooltip="Uppercased output", ) ) def process(self) -> None: # process() is called when the node executes in a flow. # Read inputs via get_parameter_value(...) and write outputs via parameter_output_values. text = self.get_parameter_value("text") or "" self.parameter_output_values["uppercased"] = text.upper() ``` ### Test as you go - After adding/editing a node, run it in a simple flow and verify: - the parameter UI looks correct (inputs, properties, outputs) - output values update in the UI - validation errors are actionable ## Parameters: the practical model Every parameter can be used in three “modes”: - **Input**: accepts a connection from another node - **Output**: provides a connection to another node - **Property**: user-configurable value in the node UI ### Use `Parameter*` helpers for common cases The core engine ships parameter helper constructs under `griptape_nodes.exe_types.param_types.*` such as: - `ParameterString`, `ParameterInt`, `ParameterFloat`, `ParameterBool` - `ParameterJson`, `ParameterDict`, `ParameterRange` - `ParameterImage`, `ParameterAudio`, `ParameterVideo`, `Parameter3D` - `ParameterButton` These helpers are useful because they: - hard-set the intended `type` / `output_type` and common `ui_options` - often support `accept_any=True` to convert values safely - expose several UI options as Python properties for runtime updates If you need a quick reference, see the [Parameter helper constructs](https://docs.griptapenodes.com/development/custom_nodes/parameters/#parameter-helper-constructs-parameterstring-parameterint) section of the Parameters reference. ### Containers: `ParameterList` and `ParameterDictionary` - **`ParameterList`**: use when you want “many of the same thing” in a node UI. - Retrieval: `get_parameter_list_value()` flattens nested iterables. - Note: the current implementation drops falsey items (e.g. `0`, `False`). Preserve those by using `get_parameter_value()` and flattening manually. - **`ParameterDictionary`**: use when you want ordered key/value entries in the UI. ### Traits: UI behaviors and validation Traits are attached to parameters to add UI and behavior. Common traits you’ll use: - `Options(...)`: dropdowns (choices are stored in `ui_options` for serialization stability) - `Slider(min_val, max_val)`: slider UI + range validation - `FileSystemPicker(...)`: file/directory picking UI (with filters and workspace constraints) Example: a numeric slider parameter: ``` from griptape_nodes.exe_types.core_types import Parameter, ParameterMode from griptape_nodes.traits.slider import Slider self.add_parameter( Parameter( name="temperature", type="float", default_value=0.7, tooltip="Sampling temperature (higher = more random)", # This can be a pure PROPERTY, or INPUT+PROPERTY if you want to allow wiring. allowed_modes={ParameterMode.INPUT, ParameterMode.PROPERTY}, # Common pattern in the codebase: attach traits inline via the `traits` argument. traits={Slider(min_val=0.0, max_val=2.0)}, ) ) ``` ## Validation, error handling, and user experience For newcomers, a good default is: - Use `validate_before_node_run()` for parameter validation - Fail early with actionable messages (tell the user what to connect or set) - If the node can fail but you want the workflow to continue, use `SuccessFailureNode` and route failure explicitly Examples you can reference in the docs: - [Start Flow](https://docs.griptapenodes.com/nodes/execution/start_flow/index.md) and [End Flow](https://docs.griptapenodes.com/nodes/execution/end_flow/index.md) for control-flow concepts and status reporting. ## Common gotchas - **`get_parameter_list_value()` drops falsey items**: if your list can contain `0` or `False`, use `get_parameter_value()` and flatten manually. - **`ui_options` conflicts**: if you pass both `hide=...` and `ui_options={"hide": ...}`, the `ui_options` value wins. - **Secrets**: do not hardcode API keys. Use `GriptapeNodes.SecretsManager().get_secret(...)`. ## Secrets and configuration When a node needs an API key or other secret: - Register secrets in the library configuration (`griptape_nodes_library.json`) - Read secrets via `GriptapeNodes.SecretsManager().get_secret("NAME")` ## Where to look for real examples - **Standard library nodes**: `libraries/griptape_nodes_library/griptape_nodes_library/` - **Engine internals** (advanced): `src/griptape_nodes/` ## Next steps - Read the deeper technical references: [Parameters](https://docs.griptapenodes.com/development/custom_nodes/parameters/index.md), [Execution and Lifecycle](https://docs.griptapenodes.com/development/custom_nodes/execution_and_lifecycle/index.md), and the rest of the pages in this section - Browse a few nodes in the standard library and copy patterns that match your use case # Parameters Parameters define the inputs, outputs, and properties of a node. This page covers every `Parameter` attribute, the traits system, the parameter helper classes, containers, and advanced patterns for dynamic parameter surfaces. For the mapping from parameter types to editor widgets, supported `ui_options` keys, and traits, see the [Parameter UI Reference](https://docs.griptapenodes.com/development/custom_nodes/parameter_ui_reference/index.md). ## Parameter Attributes All Parameter attributes: - **name**: str, unique identifier, no whitespace - **tooltip**: str or list[dict] for UI help text - **default_value**: Any default value - **type**: str (e.g., "str", "list[str]", ParameterTypeBuiltin.STR.value) - **input_types**: list[str] for incoming connection types - **output_type**: str for outgoing connection type - **allowed_modes**: set[ParameterMode] {INPUT, OUTPUT, PROPERTY} - **ui_options**: dict for UI customization - **converters**: list\[Callable\[[Any], Any\]\] for value transformation - **validators**: list\[Callable\[[Parameter, Any], None\]\] for validation - **hide/hide_label/hide_property**: common UI flags (also available via `ui_options`; `ui_options` wins on conflict) - **allow_input/allow_property/allow_output**: convenience flags for configuring modes (ignored if `allowed_modes` is explicitly set) - **settable**: bool (default True) - False for computed/output parameters - **serializable**: bool (default True) - set False for non-serializable values (drivers, file handles, etc.) - **user_defined**: bool (default False) - **private**: bool (default False) - hide from general user editing (library/internal use) - **exclude_from_metadata**: bool (default False) - exclude this parameter's value from plaintext metadata outputs (sidecar JSON and embedded PNG text chunks). The parameter name is still recorded in `parameters_omitted` so the omission is auditable. Use this for parameters that hold sensitive values such as passwords or user-supplied credentials. - **parent_container_name**: str|None — assigns this parameter as a child of a `ParameterContainer` (i.e. a `ParameterList` or `ParameterDictionary`). Used for list-like ownership. - **parent_element_name**: str|None — nests this parameter under a `ParameterGroup` (a UI grouping element). Used for visual grouping in the node UI. ## Traits Add functionality via `add_trait()`: - **Options**: `Options(choices=list[str] | list[tuple[str, Any]], show_search: bool = True, search_filter: str = "", allow_custom: bool = False)` - **Slider**: `Slider(min_val: float, max_val: float)` - **Button**: `Button(label: str = "", variant=..., size=..., button_link=... | on_click=..., get_button_state=...)` - **ColorPicker**: `ColorPicker(format="hex")` - **FileSystemPicker**: `FileSystemPicker(...)` (file/directory selection UI) For the full list of traits, the widgets they render, and the `ui_options` keys they manage, see the [Parameter UI Reference](https://docs.griptapenodes.com/development/custom_nodes/parameter_ui_reference/index.md). ## Parameter helper constructs (`ParameterString`, `ParameterInt`, ...) Griptape Nodes includes a set of convenience Parameter subclasses under `griptape_nodes.exe_types.param_types.*`. They exist to make common parameter patterns **simple, consistent, and runtime-mutable** (many expose UI options as Python properties). ### Quick reference table | Helper | Enforced `type` / `output_type` | Default `input_types` behavior | Key UI convenience args | Notes | | ----------------- | --------------------------------------------- | ------------------------------------------------------- | ---------------------------------------------------------------------------------- | --------------------------------------------------------------------- | | `ParameterString` | `"str"` / `"str"` | `accept_any=True` → `["any"]` with converter to `str` | `markdown`, `multiline`, `placeholder_text`, `is_full_width` | `type`, `output_type`, and `input_types` constructor args are ignored | | `ParameterBool` | `"bool"` / `"bool"` | `accept_any=True` → `["any"]` with converter to `bool` | `on_label`, `off_label` | Converts common strings like `"true"/"false"`, `"yes"/"no"` | | `ParameterInt` | `"int"` / `"int"` | `accept_any=True` → `["any"]` with converter to `int` | `step`, `slider`, `min_val`, `max_val`, `validate_min_max` | Adds constraint traits (Clamp/MinMax/Slider) based on args | | `ParameterFloat` | `"float"` / `"float"` | `accept_any=True` → `["any"]` with converter to `float` | `step`, `slider`, `min_val`, `max_val`, `validate_min_max` | Adds constraint traits (Clamp/MinMax/Slider) based on args | | `ParameterDict` | `"dict"` / `"dict"` | `accept_any=True` → `["any"]` with converter to `dict` | (none) | Uses `griptape_nodes.utils.dict_utils.to_dict()` for conversion | | `ParameterJson` | `"json"` / `"json"` | `accept_any=True` → `["any"]` with converter to JSON | `button`, `button_label`, `button_icon` | Uses `json_repair.repair_json()` for robust string → JSON | | `ParameterRange` | `"list"` / `"list"` | `accept_any=True` → `["any"]` with converter to `list` | `range_slider` + `min_val/max_val/step`, labels | Range slider is only meaningful when the value is a 2-number list | | `ParameterImage` | `"ImageUrlArtifact"` / `"ImageUrlArtifact"` | `accept_any=True` → `["any"]` (no conversion) | `clickable_file_browser`, `webcam_capture_image`, `edit_mask`, `pulse_on_run` | Mostly UI convenience; add converters if you need type coercion | | `ParameterAudio` | `"AudioUrlArtifact"` / `"AudioUrlArtifact"` | `accept_any=True` → `["any"]` (no conversion) | `clickable_file_browser`, `microphone_capture_audio`, `edit_audio`, `pulse_on_run` | Mostly UI convenience; add converters if you need type coercion | | `ParameterVideo` | `"VideoUrlArtifact"` / `"VideoUrlArtifact"` | `accept_any=True` → `["any"]` (no conversion) | `clickable_file_browser`, `webcam_capture_video`, `edit_video`, `pulse_on_run` | Mostly UI convenience; add converters if you need type coercion | | `Parameter3D` | `"ThreeDUrlArtifact"` / `"ThreeDUrlArtifact"` | `accept_any=True` → `["any"]` (no conversion) | `clickable_file_browser`, `expander`, `pulse_on_run` | Mostly UI convenience; add converters if you need type coercion | | `ParameterButton` | `"button"` / `"str"` | `["str", "any"]` | `label`, `variant`, `size`, `icon`, `state`, `href` / `on_click` | **Label is display text; `default_value` is stored value** | ### Shared behavior across helpers - All helpers forward the standard `Parameter` constructor knobs (`allowed_modes` or `allow_input/allow_property/allow_output`, `hide/hide_label/hide_property`, `settable`, `serializable`, etc.). - Many helpers default `accept_any=True`. When enabled, the helper typically sets `input_types=["any"]` and prepends a converter (e.g. `ParameterString` converts any input to `str`). Turn this off if you want strict typing. - If you provide both an explicit convenience parameter (e.g. `hide=True`) and the same key in `ui_options` (e.g. `ui_options={"hide": False}`), **`ui_options` wins** and Griptape Nodes will warn about the conflict. ### Detailed helper notes #### `ParameterString` - Enforces `type="str"` and `output_type="str"`. - `accept_any=True` converts `None` → `""` and otherwise uses `str(value)`. - UI convenience: `markdown`, `multiline`, `placeholder_text`, `is_full_width` (all are runtime-settable properties). #### `ParameterBool` - Enforces `type="bool"` and `output_type="bool"`. - `accept_any=True` converts common string representations (e.g. `"true"`, `"yes"`, `"on"`, `"1"`) to `True` and (`"false"`, `"no"`, `"off"`, `"0"`) to `False`. - UI convenience: `on_label`, `off_label` (runtime-settable properties). #### `ParameterInt` / `ParameterFloat` (via `ParameterNumber`) - Enforces numeric `type` / `output_type` and can prepend a converter when `accept_any=True`. - `step`: stored in `ui_options["step"]` and validated (value must be a multiple of the current step). - `slider`, `min_val`, `max_val`, `validate_min_max`: adds one of these constraint traits based on priority: - `Slider(min_val, max_val)` if `slider=True` - `MinMax(min_val, max_val)` if `validate_min_max=True` - `Clamp(min_val, max_val)` if `min_val` and `max_val` are provided #### `ParameterJson` - Enforces `type="json"` and `output_type="json"`. - `accept_any=True` attempts to repair/parse JSON strings using `json_repair.repair_json()` (and will also attempt to stringify non-string inputs). - UI convenience: optional editor button (`button`, `button_label`, `button_icon`). #### `ParameterDict` - Enforces `type="dict"` and `output_type="dict"`. - `accept_any=True` uses `to_dict(...)` to coerce common inputs into a dict. #### `ParameterRange` - Enforces `type="list"` and `output_type="list"`. - `accept_any=True` coerces `None` → `[]`, list → list, and any other value → `[value]`. - UI convenience: `range_slider` (a nested `ui_options["range_slider"]` object) with `min_val/max_val/step` and label visibility options. - The range slider UI is only applicable when the value is a list of exactly two numeric values. #### `ParameterImage` (Recommended for Image Parameters) **Always use `ParameterImage` instead of generic `Parameter` for image inputs/outputs.** It provides: - Automatic `type="ImageUrlArtifact"` and `output_type="ImageUrlArtifact"` - Built-in UI options for file browser, webcam capture, and mask editing - Consistent behavior across all image-handling nodes This also keeps parameter values small: `ImageUrlArtifact` holds a short URL string, while the generic alternative `ImageArtifact` inlines the image's entire byte payload into WebSocket traffic, and into saved workflows unless the parameter is declared `serializable=False` — see [Parameter Payload Size](https://docs.griptapenodes.com/development/custom_nodes/error_handling/#parameter-payload-size). **Basic Usage:** ``` from griptape_nodes.exe_types.param_types.parameter_image import ParameterImage # Input image parameter self.add_parameter( ParameterImage( name="input_image", tooltip="Input image for processing", allow_output=False, # Input only ) ) # Output image parameter self.add_parameter( ParameterImage( name="output_image", tooltip="Generated image result", allow_input=False, # Output only allow_property=False, ) ) ``` **Available UI Options:** - `clickable_file_browser`: Enable file browser for image selection - `webcam_capture_image`: Enable webcam capture - `edit_mask`: Enable mask editing overlay - `pulse_on_run`: Visual feedback when image updates **Dynamic Visibility Pattern:** For parameters that should only appear for certain model types: ``` def __init__(self, **kwargs) -> None: super().__init__(**kwargs) # Add image parameter (hidden by default) self.add_parameter( ParameterImage( name="input_image", tooltip="Input image for image-to-image generation", allow_output=False, ) ) # Initialize visibility based on default model self._initialize_parameter_visibility() def _initialize_parameter_visibility(self) -> None: """Initialize parameter visibility based on default model.""" model = self.get_parameter_value("model") or "default" if model in ["model-with-image-support", "another-model"]: self.show_parameter_by_name("input_image") else: self.hide_parameter_by_name("input_image") def after_value_set(self, parameter: Parameter, value: Any) -> None: """Update visibility when model changes.""" if parameter.name == "model": if value in ["model-with-image-support", "another-model"]: self.show_parameter_by_name("input_image") else: self.hide_parameter_by_name("input_image") self.set_parameter_value("input_image", None) # Clear when hiding return super().after_value_set(parameter, value) ``` **Why Use `ParameterImage` Over Generic `Parameter`:** | Aspect | Generic `Parameter` | `ParameterImage` | | ----------- | --------------------------------- | ------------------------------------------- | | Type safety | Manual `type`/`input_types` setup | Automatic artifact types | | UI features | Manual `ui_options` configuration | Built-in file browser, webcam, mask editing | | Consistency | Varies by implementation | Standardized across nodes | | Maintenance | More boilerplate code | Less code, cleaner | **Legacy Pattern (Avoid):** ``` # ❌ Don't do this - use ParameterImage instead self.add_parameter( Parameter( name="input_image", input_types=["ImageArtifact", "ImageUrlArtifact", "str"], type="ImageArtifact", default_value=None, tooltip="Input image", allowed_modes={ParameterMode.INPUT, ParameterMode.PROPERTY}, ui_options={"display_name": "Input Image"}, ) ) ``` **Why `ParameterImage` is better:** - **Small parameter values:** Declares `ImageUrlArtifact`, not `ImageArtifact` — see [Parameter Payload Size](https://docs.griptapenodes.com/development/custom_nodes/error_handling/#parameter-payload-size) - **Standardized type conversion logic:** Handles ImageArtifact, ImageUrlArtifact, and string inputs consistently - **Built-in UI features:** File browser, webcam capture, mask editing - **Less boilerplate:** Automatically configures types and options - **Robust error handling:** Gracefully handles various input formats (URLs, file paths, data URIs) **Recommended Pattern:** ``` # ✅ Use ParameterImage for cleaner, more maintainable code self.add_parameter( ParameterImage( name="input_image", tooltip="Input image", allow_output=False, ) ) ``` `ParameterImage` standardizes how your node handles different image input types, reducing conversion errors and improving reliability. #### `ParameterAudio` / `ParameterVideo` / `Parameter3D` - Enforce their corresponding `*UrlArtifact` `type` / `output_type` (e.g., `AudioUrlArtifact`, `VideoUrlArtifact`, `ThreeDUrlArtifact`). - These helpers primarily provide UI options (file browser / capture / editing / expanders). If you need coercion from e.g. `str` → artifact, supply `converters` and/or handle it in your node's `before_value_set()` / `process()` logic. - Follow the same patterns as `ParameterImage` for these media types. - **Audio:** `ParameterAudio` matters for payload size the same way `ParameterImage` does — `AudioArtifact` (the generic alternative) inlines its entire byte payload into WebSocket traffic, and into saved workflows unless the parameter is declared `serializable=False`; `AudioUrlArtifact` holds only a short URL. See [Parameter Payload Size](https://docs.griptapenodes.com/development/custom_nodes/error_handling/#parameter-payload-size). - **Video:** there is no raw-bytes `VideoArtifact` class, so that name carries no payload-size risk. Declare `VideoUrlArtifact` in new nodes — it's the type actually produced, and connection type-checking matches type names exactly, so `"VideoArtifact"` and `"VideoUrlArtifact"` are *not* interchangeable. Some libraries list both in `input_types` to accept either spelling. - **3D:** `ThreeDArtifact` holds raw bytes too, so prefer `ThreeDUrlArtifact` (which `Parameter3D` enforces). See [Parameter Payload Size](https://docs.griptapenodes.com/development/custom_nodes/error_handling/#parameter-payload-size). #### `ParameterButton` Buttons provide interactive UI elements that trigger actions when clicked, such as updating parameters, performing calculations, or navigating between states. **Basic Properties:** - Enforces `type="button"` and `output_type="str"` - By default, it's a **property-only** UI element (`allow_property=True`, `allow_input=False`, `allow_output=False`) - Accepts either `href="..."` (simple link) or `on_click=...` (custom callback) parameters - `label` is the display text shown on the button - `icon` adds a visual icon (optional) - `icon_position` controls icon placement ("left" or "right", defaults to "left") **Important:** The `on_click` handler and `href` are passed **directly to `ParameterButton`** as parameters, not via the `Button` trait. **Implementation Pattern:** Buttons must be wrapped in a `ParameterButtonGroup` container: ``` from griptape_nodes.exe_types.core_types import ParameterButtonGroup from griptape_nodes.exe_types.param_types.parameter_button import ParameterButton from griptape_nodes.traits.button import Button, ButtonDetailsMessagePayload class MyNode(DataNode): def __init__(self, **kwargs) -> None: super().__init__(**kwargs) # Create button group with context manager with ParameterButtonGroup(name="my_button_group") as button_group: ParameterButton( name="update_button", label="Update Date/Time", icon="calendar", on_click=self._handle_button_click, # Pass on_click directly ) self.add_node_element(button_group) # Add parameter that will be updated by button self.add_parameter( Parameter( name="display_value", tooltip="Value updated by button", type=ParameterTypeBuiltin.STR.value, allowed_modes={ParameterMode.PROPERTY}, ui_options={ "display_name": "Display Value", "readonly": True, # Prevent manual editing }, default_value="Click button to update", ) ) def _handle_button_click( self, button: Button, button_payload: ButtonDetailsMessagePayload, ) -> None: """Button click handler. Args: button: The Button trait instance button_payload: Contains click event details """ # Update parameter value new_value = "Updated at " + datetime.now().strftime("%H:%M:%S") self.set_parameter_value("display_value", new_value) ``` **Multiple Buttons in a Group:** ``` with ParameterButtonGroup(name="navigation_buttons") as nav_buttons: ParameterButton( name="previous", label="Previous", icon="arrow-left", on_click=self._previous_item, # Pass on_click directly ) ParameterButton( name="next", label="Next", icon="arrow-right", icon_position="right", on_click=self._next_item, # Pass on_click directly ) self.add_node_element(nav_buttons) ``` **Common Use Cases:** 1. **Update Display Values** ``` def _update_datetime(self, button: Button, button_payload: ButtonDetailsMessagePayload) -> None: """Update datetime display when button is clicked.""" current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") self.set_parameter_value("datetime_display", current_time) ``` 1. **Navigate Through Items** ``` def _next_image(self, button: Button, button_payload: ButtonDetailsMessagePayload) -> None: """Increment index and update display.""" current_index = self.get_parameter_value("image_index") self.set_parameter_value("image_index", current_index + 1) self._update_display() ``` 1. **Trigger Calculations** ``` def _calculate(self, button: Button, button_payload: ButtonDetailsMessagePayload) -> None: """Perform calculation and update result parameter.""" input_value = self.get_parameter_value("input") result = self._perform_complex_calculation(input_value) self.set_parameter_value("result", result) ``` 1. **Reset to Defaults** ``` def _reset(self, button: Button, button_payload: ButtonDetailsMessagePayload) -> None: """Reset parameters to default values.""" self.set_parameter_value("counter", 0) self.set_parameter_value("display", "") ``` **Link Buttons (Alternative to `on_click`):** For simple navigation to external URLs: ``` ParameterButton( name="docs_link", label="View Documentation", icon="external-link", href="https://docs.griptape.ai", # Pass href directly ) ``` **Best Practices:** - Use descriptive button labels that clearly indicate the action - Choose appropriate icons that match the action (e.g., "calendar" for date/time, "arrow-left"/"arrow-right" for navigation) - Keep button handlers simple and focused on a single action - Use read-only parameters for values that should only be updated by buttons - Avoid triggering expensive operations directly in button handlers (consider using flags that `process()` checks instead) - Group related buttons together in a single `ParameterButtonGroup` **Common Patterns:** | Pattern | Button Action | Updated Parameter Type | Use Case | | -------------- | ---------------------------------- | --------------------------- | -------------------------------- | | Update Display | Updates a read-only text parameter | `PROPERTY` (readonly) | Show current time, status, count | | Navigation | Increments/decrements an index | Hidden `PROPERTY` parameter | Image carousel, list browsing | | Toggle State | Switches between states | `PROPERTY` parameter | Enable/disable features | | Trigger Action | Sets a flag checked by `process()` | Hidden `PROPERTY` parameter | Refresh data, recalculate | **Complete Example:** See `example_control_node.py` and `image_carousel.py` for working implementations that demonstrate: - Button creation with icons - Button group usage - Updating read-only parameters - Handler method signatures - Navigation patterns - Locale-appropriate datetime formatting ## Containers - **ParameterList**: A container parameter that owns multiple child `Parameter` items (use `get_parameter_list_value()` to flatten values) - **ParameterDictionary**: A container parameter that owns ordered key/value pairs (distinct from `ParameterDict`, which is a `dict`-typed value parameter helper) - **ParameterGroup**: For UI grouping **Container semantics (important):** - Container parameters are represented as `ParameterContainer` objects in the engine. They are **always truthy**, even when empty (they override `__bool__()` to avoid bugs with stale cached values). - `ParameterList` supports several UI convenience options (e.g. `collapsed`, grid display, and column count) that are merged into `ui_options` at runtime. - `ParameterDictionary` is an ordered collection of key/value pair children (internally represented as a list to preserve order). **`parent_container_name` vs `parent_element_name` — critical distinction:** Parameters have two separate parent-pointer attributes that serve different purposes: | Attribute | Points to | Purpose | | ----------------------- | ------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `parent_container_name` | `ParameterContainer` (`ParameterList`, `ParameterDictionary`) | **Ownership.** The parameter is a child of a list/dictionary container. The engine uses this for `add_parameter()`, child cleanup, value aggregation, and serialization/reload. | | `parent_element_name` | `ParameterGroup` | **UI grouping.** The parameter is visually nested under a collapsible group in the node UI. The engine uses this for `add_parameter()` placement, `_remove_existing_*()` lookups, and serialization/reload. | **Do NOT confuse them.** If you use `parent_container_name` when you should be using `parent_element_name` (or vice versa), the parameter will: 1. Appear at the node root instead of inside the intended group/container 1. Not be cleaned up between runs (e.g. stale outputs persist) 1. Fail to restore after save/reload — the reload handler looks for a `ParameterContainer` or `ParameterGroup` by the name you specified, and if the type doesn't match, the parameter is silently dropped before its saved values are applied **Rule of thumb:** - Putting a parameter inside a **`ParameterList`** or **`ParameterDictionary`**? → use `parent_container_name` - Putting a parameter inside a **`ParameterGroup`** for visual organization? → use `parent_element_name` ``` # ✅ CORRECT: Nesting under a ParameterGroup for UI grouping param = ParameterImage( name="cell_0_0", parent_element_name=self._grid_cells_group.name, # ParameterGroup ... ) # ❌ WRONG: Using parent_container_name for a ParameterGroup param = ParameterImage( name="cell_0_0", parent_container_name=self._grid_cells_group.name, # BUG: this is a ParameterGroup, not a ParameterContainer ... ) ``` ## ParameterList Pattern For parameters accepting multiple inputs of the same type: ``` self.add_parameter( ParameterList( name="tools", input_types=["Tool", "list[Tool]"], default_value=[], tooltip="Connect individual tools or a list of tools", allowed_modes={ParameterMode.INPUT}, ) ) # Retrieve in process method tools = self.get_parameter_list_value("tools") # Always returns a list for tool in tools: # Process each tool ``` **Benefits:** - Multiple connection points in UI - Automatic aggregation of inputs - Flexible workflow design - Follows Griptape design patterns **Important behavior note:** `get_parameter_list_value()` flattens nested iterables and **drops falsey items** (e.g. `0`, `False`, `""`, empty dicts/lists). If you need to preserve falsey values, use `get_parameter_value()` and handle flattening yourself. ## Common Parameter Patterns ### Search Input with Placeholder ``` Parameter( name="search_query", input_types=["str"], type="str", tooltip="Search term to find models", allowed_modes={ParameterMode.INPUT, ParameterMode.PROPERTY}, ui_options={"placeholder_text": "e.g., llama, bert, stable-diffusion"}, ) ``` ### Full-Width List Output ``` Parameter( name="results", output_type="list[dict]", type="list[dict]", tooltip="Search results with full information", allowed_modes={ParameterMode.OUTPUT}, ui_options={"is_full_width": True}, ) ``` ### Multiline Text Input ``` Parameter( name="prompt", input_types=["str"], type="str", tooltip="Description of desired output", allowed_modes={ParameterMode.INPUT, ParameterMode.PROPERTY}, ui_options={"multiline": True, "placeholder_text": "Describe what you want..."}, ) ``` ### File Path with Browser To let the user pick a file or directory from disk, add the `FileSystemPicker` trait to a `str` parameter. The parameter's value is the chosen path: ``` from griptape_nodes.traits.file_system_picker import FileSystemPicker Parameter( name="config_path", type="str", tooltip="Path to the configuration file", allowed_modes={ParameterMode.INPUT, ParameterMode.PROPERTY}, traits={ FileSystemPicker( allow_files=True, allow_directories=False, file_extensions=[".json", ".yaml"], ) }, ) ``` `FileSystemPicker` defaults to directories only, so pass `allow_files=True` when you want a file picker. It also supports `multiple`, `workspace_only`, size limits, and include/exclude patterns — see [Parameter UI Reference](https://docs.griptapenodes.com/development/custom_nodes/parameter_ui_reference/#traits). Media parameters work differently. `ParameterImage`, `ParameterAudio`, `ParameterVideo`, and `Parameter3D` each provide their own click-to-browse upload in the media viewer (`clickable_file_browser`, on by default), and the picked file is uploaded and handed to your node as a `*UrlArtifact` rather than a path string. Reach for those helpers for media instead of a `str` path — see [`ParameterImage`](#parameterimage-recommended-for-image-parameters) above. ## Advanced Parameter Patterns ### Dynamic Parameter Visibility Use `after_value_set()` callback to create context-aware UIs: ``` def after_value_set(self, parameter: Parameter, value: Any) -> None: """Update parameter visibility based on model selection.""" if parameter.name == "model": if value == "text-to-image": self.hide_parameter_by_name("input_image") self.show_parameter_by_name("prompt") elif value == "image-to-image": self.show_parameter_by_name("input_image") self.show_parameter_by_name("prompt") return super().after_value_set(parameter, value) ``` ### Dynamic Options Updates Update parameter choices at runtime: ``` from griptape_nodes.traits.options import Options def _update_option_choices(self, param_name: str, choices: list, default_value: str): """Update Options trait choices dynamically.""" param = self.get_parameter_by_name(param_name) if not param: return # Traits are stored as child elements on the Parameter # (most commonly, you'll be updating an Options trait) for trait in param.find_elements_by_type(Options): trait.choices = choices break self.set_parameter_value(param_name, default_value) ``` ### Dynamic Parameter Schemas with ParameterTransitionComponent Some nodes expose a dropdown — a model picker, a mode selector, an operation chooser — where each choice implies a different set of input and output parameters. The naive approach (clear every parameter, rebuild from scratch) destroys every connection the user set up. `ParameterTransitionComponent` solves this by computing the diff between the current parameter surface and the one the new choice needs, then acting one of four ways per name: - **Preserve** — the name exists on both sides with identical signatures. Nothing is touched. - **Replace** — the name exists on both sides but the signature changed (types, modes, or both). The component captures the existing incoming and outgoing connections, removes the old parameter, adds a new one with the new signature, and then re-dispatches each captured connection through `CreateConnectionRequest`. The platform's connection handler validates type compatibility, so connections that are still valid under the new signature come back; connections that are no longer valid are dropped. Because connection re-creation goes through the same path as a fresh connection, values re-flow through incoming edges the normal way. - **Remove** — the name no longer exists in the desired schema. Removed via `RemoveParameterFromNodeRequest`, which cleans up all connections. - **Add** — the name is new. Added via the caller-supplied factory. One consequence of always-replace: a replaced parameter's stored *property* value is lost (the new Parameter starts empty). Connections and their re-flowed values are what survive. For nodes that don't want this behavior for certain same-name-same-signature cases, declaring identical signatures keeps the parameter in the preserve bucket. #### Walkthrough — Multi-model image generation (hypothetical) > **Note on this example.** The `MultiModelImageGenerator` node used below is **hypothetical** — it does not exist in Griptape today, and this walkthrough is not a description of how any shipped node works. We use a hypothetical consolidated node here because it's the kind of node an author might build *on top of* `ParameterTransitionComponent`, and it makes the "switching between shapes" story concrete for an artist audience. > > It illustrates the mechanics of type narrowing, not a recommendation about parameter types. `reference_image` is typed `ImageArtifact` below purely because it makes the SDXL-vs-SD3 signature change easy to follow. A real node would declare `ImageUrlArtifact` — see [Parameter Payload Size](https://docs.griptapenodes.com/development/custom_nodes/error_handling/#parameter-payload-size). **The hypothetical node.** `MultiModelImageGenerator` has a **Model** dropdown offering **FLUX**, **SDXL**, and **Stable Diffusion 3**, plus a "— none —" option for an unconfigured starting state. Every model shares some parameters; each has its own extras. Here's the parameter surface per model: | Parameter | FLUX | SDXL | SD3 | | ----------------- | ----- | ------------- | ----------------- | | `prompt` | str | str | str | | `width` | int | int | int | | `height` | int | int | int | | `seed` | int | int | int | | `guidance` | float | — | — | | `steps` | — | int | — | | `cfg_scale` | — | float | — | | `reference_image` | — | **str (URL)** | **ImageArtifact** | | `negative_prompt` | — | — | str | Notice `reference_image` appears on both SDXL and SD3 but with different types — SDXL's driver accepts a URL string, SD3's accepts a Griptape-native `ImageArtifact`. Real image-to-image APIs diverge exactly this way: some want a URL, some want uploaded bytes, some want a native artifact object. **The user session.** An artist opens a workflow. The `MultiModelImageGenerator` node starts with no model selected — its parameter surface is empty apart from the Model dropdown itself. **Step 1 — First selection (FLUX).** The artist picks **FLUX**. Every FLUX parameter (`prompt`, `width`, `height`, `seed`, `guidance`) appears. They connect a `TextPrompt` node to `prompt`, connect a `ResolutionPicker` node to `width` and `height`, type `42` into `seed`, leave `guidance` at its default. They render. *Under the hood:* the component sees an empty "current" set and a full "desired" set for FLUX. Every FLUX parameter lands in `to_add`. Nothing in `to_remove` or `to_preserve`. **Step 2 — Comparing models (FLUX → SDXL).** The artist wants to see how SDXL handles the same prompt. They switch the dropdown to **SDXL**. - `prompt`, `width`, `height` → `to_preserve`. The connections to `TextPrompt` and `ResolutionPicker` are untouched. - `seed` → `to_preserve`. Same name, both `int`. Value `42` survives. - `guidance` → `to_remove`. SDXL doesn't have it. - `steps`, `cfg_scale`, `reference_image` → `to_add`. The SDXL-specific parameters appear, ready for the artist to tune. The artist tweaks `steps`, connects an `ImageURL` node to `reference_image`, and renders. Every connection they set up in Step 1 is still live. They didn't have to redo anything. **Step 3 — Signature change (SDXL → SD3).** Curious about SD3, they switch the dropdown again. - `prompt`, `width`, `height`, `seed` → `to_preserve`. Signatures unchanged, so nothing is touched. - `reference_image` → `to_replace`. Both models use the name `reference_image`, but SDXL's accepts a URL string and SD3's accepts an `ImageArtifact`. The component captures the existing connection from `ImageURL`, removes the old parameter, adds the new `ImageArtifact`-typed one, and then re-dispatches the captured connection through `CreateConnectionRequest`. The connection handler sees the source provides `str` but the new parameter accepts only `ImageArtifact` and rejects it. Net result: `reference_image` exists with the new signature, no connection, ready for the artist to connect an artifact source (e.g., a `LoadImage` node). - `steps`, `cfg_scale` → `to_remove`. SD3 doesn't use them. - `negative_prompt` → `to_add`. SD3's distinguishing parameter appears, empty and ready for input. Type narrowing behaves exactly as an artist would expect. If `reference_image` had previously accepted `[ImageArtifact, str]` with a connection providing `ImageArtifact`, and the new schema narrowed it to `[ImageArtifact]`, the replace path captures the connection, rebuilds the parameter, and re-dispatches the connection — the platform's connection handler sees `ImageArtifact` is still valid and the edge comes back automatically. Only connections that are no longer type-compatible get dropped. **Step 4 — Clearing the node (SD3 → no selection).** The artist decides this node isn't working out and selects "— none —" from the dropdown. Everything the node was managing goes to `to_remove`. `prompt`, `width`, `height`, `seed`, `reference_image`, `negative_prompt` — all gone. The parameter surface returns to just the Model dropdown. The artist can start fresh or pick a different approach. #### What the node author writes ``` from griptape_nodes.exe_types.param_components.parameter_transition_component import ( ParameterTransitionComponent, TransitionParameter, ) # In MultiModelImageGenerator.__init__: self._model_params = ParameterTransitionComponent( self, manages_parameter=lambda p: p.name in self._MODEL_PARAM_NAMES, ) # In the model-dropdown change handler: desired = self._build_transition_parameters_for_model(selected_model) # author's domain logic self._model_params.transition_to(desired) ``` `_build_transition_parameters_for_model` is the author's — the component doesn't care how the list is computed (from a dataclass, a config file, a registry, hand-rolled). It only cares about the resulting `list[TransitionParameter]`. The author also decides how `manages_parameter` scopes the component's view of the node's parameters (here: a known set of model-parameter names, excluding the Model dropdown itself and any other ambient parameters the node owns). #### API reference **Constructor:** ``` ParameterTransitionComponent( node: BaseNode, *, manages_parameter: Callable[[Parameter], bool], ) ``` - `node` — the node instance that owns the parameter surface. - `manages_parameter` — predicate identifying which of the node's parameters this component manages. Everything outside the predicate is left alone. **`TransitionParameter` fields:** - `name: str` — parameter name, must be unique within a single `transition_to` call. - `allowed_modes: frozenset[ParameterMode]` — which modes the parameter supports (`INPUT`, `PROPERTY`, `OUTPUT`). Must match the existing Parameter's `allowed_modes` for preservation. - `input_types: frozenset[str]` — the set of types the parameter accepts as input, matching what `Parameter.input_types` will return after creation. The property applies fallbacks when no input types are declared on the underlying request, so populate this to reflect the *effective* signature (e.g., for an output-only parameter built with `output_type="X"`, use `frozenset({"X"})` because `Parameter.input_types` falls back to `[output_type]`). - `output_type: str` — the type the parameter emits, matching what `Parameter.output_type` will return after creation. For an input-only parameter built with `input_types=[X, Y, Z]`, use `X` because the property falls back to the first input type. - `add_request_factory: Callable[[], AddParameterToNodeRequest]` — a zero-arg callable the component invokes only for parameters that actually need adding. Typically a `functools.partial` wrapping the author's builder method. **`TransitionPlan` fields:** - `to_preserve: frozenset[str]` — parameters left completely untouched (name + signature both match). Connections and stored values are unaffected because nothing was dispatched. - `to_replace: frozenset[str]` — same-named parameters whose signature differed. The component captured their existing connections, removed the old Parameter, added a new one, and re-dispatched each connection through `CreateConnectionRequest` (the platform validates type compatibility and rejects ones that are no longer valid). Stored property values on replaced parameters are lost; connection values re-flow normally. - `to_remove: frozenset[str]` — parameters removed via `RemoveParameterFromNodeRequest`. - `to_add: frozenset[str]` — parameters added via `AddParameterToNodeRequest`. **Signature-match check.** Two same-named parameters are considered a match when *all three* of `allowed_modes`, `input_types`, and `output_type` match exactly. The component reads these off the existing Parameter's public properties (the same accessors used elsewhere for connection type checks). Any difference — a widened input-type list, a dropped mode, a changed output type — puts the parameter in the replace bucket. This is identity equality over the effective schema, not directional type-compatibility: two parameters that happen to be assignment-compatible but were declared differently represent different schema intent. #### When to reach for this - A single node's parameter surface depends on a **discrete user choice** (model picker, mode selector, operation chooser). - Users are expected to switch between choices during normal workflow authoring and should not lose their connections each time. #### When NOT to reach for this - You want to **hide or show** parameters based on a value — use the [Dynamic Parameter Visibility](#dynamic-parameter-visibility) pattern with `hide_parameter_by_name` / `show_parameter_by_name`. - You only need to **update a dropdown's options** — use the [Dynamic Options Updates](#dynamic-options-updates) pattern. - The parameter set is fixed; only values change. #### Working with ParameterGroups The component manages individual parameters by name. If your dynamic surface uses groups, pass `parent_element_name` in your `add_request_factory` so newly-added parameters land in the right group; group creation and cleanup are handled by the caller. ### Advanced ParameterList Usage Include both individual and list types for maximum flexibility. `ImageArtifact` entries here are for accepting connections from older workflows only — declare new list parameters against `ImageUrlArtifact` (see [Parameter Payload Size](https://docs.griptapenodes.com/development/custom_nodes/error_handling/#parameter-payload-size)): ``` self.add_parameter( ParameterList( name="images", input_types=[ "ImageUrlArtifact", "ImageArtifact", # legacy input compatibility only "str", "list", "list[ImageUrlArtifact]", "list[ImageArtifact]", # legacy input compatibility only ], default_value=[], tooltip="Input images (up to 10 images total)", allowed_modes={ParameterMode.INPUT}, ui_options={"expander": True, "display_name": "Input Images"}, ) ) ``` ### Controlling Parameter Order in the UI Parameters appear in the UI in the order they are added via `add_parameter()`. This matters for user experience - related parameters should be grouped logically. **Problem**: Base classes like `BaseImageProcessor` automatically add parameters (e.g., `input_image`) in their `__init__`, which may not be the order you want. **Solution**: Extend `SuccessFailureNode` directly instead of `BaseImageProcessor` to gain full control over parameter ordering: ``` from griptape_nodes.exe_types.node_types import SuccessFailureNode from griptape_nodes.exe_types.param_types.parameter_image import ParameterImage class ColorMatch(SuccessFailureNode): """Transfer colors from a reference image to a target image.""" def __init__(self, name: str, metadata: dict[Any, Any] | None = None) -> None: super().__init__(name, metadata) # Reference image FIRST - the source of the color palette self.add_parameter( ParameterImage( name="reference_image", tooltip="Reference image - the source of the color palette to transfer", ui_options={"clickable_file_browser": True, "expander": True}, ) ) # Target image SECOND - the image to modify self.add_parameter( ParameterImage( name="target_image", tooltip="Target image - the image to apply the color transfer to", ui_options={"clickable_file_browser": True, "expander": True}, ) ) # Additional parameters in desired order... ``` **When to Use This Pattern**: - Two-image nodes where the semantic order matters (reference → target) - Nodes requiring specific parameter groupings not provided by base classes - When base class parameter order conflicts with your UX goals **Trade-off**: You lose helper methods from specialized base classes, but gain complete control over the node's UI structure. ### Two-Image Processing Node Pattern For nodes that process two images together (blending, color matching, compositing), use this pattern: ``` from typing import Any, ClassVar from PIL import Image from griptape.artifacts import ImageUrlArtifact from griptape_nodes.exe_types.core_types import Parameter from griptape_nodes.exe_types.node_types import SuccessFailureNode from griptape_nodes.exe_types.param_types.parameter_image import ParameterImage from griptape_nodes_library.utils.image_utils import ( dict_to_image_url_artifact, load_pil_from_url, save_pil_image_with_named_filename, ) from griptape_nodes_library.utils.file_utils import generate_filename class TwoImageProcessor(SuccessFailureNode): """Base pattern for nodes processing two images.""" CATEGORY: ClassVar[str] = "image" def __init__(self, name: str, metadata: dict[Any, Any] | None = None) -> None: super().__init__(name, metadata) # First image input self.add_parameter( ParameterImage( name="image_a", tooltip="First input image", ui_options={"clickable_file_browser": True, "expander": True}, ) ) # Second image input self.add_parameter( ParameterImage( name="image_b", tooltip="Second input image", ui_options={"clickable_file_browser": True, "expander": True}, ) ) # Output image self.add_parameter( ParameterImage( name="output_image", tooltip="Processed result", allowed_modes={ParameterMode.OUTPUT}, ) ) def _get_image_artifact(self, param_name: str) -> ImageUrlArtifact | None: """Convert parameter value to ImageUrlArtifact.""" value = self.get_parameter_value(param_name) if value is None: return None if isinstance(value, dict): return dict_to_image_url_artifact(value) return value def _process_images(self) -> None: """Process both images and set output.""" image_a_artifact = self._get_image_artifact("image_a") image_b_artifact = self._get_image_artifact("image_b") if not image_a_artifact or not image_b_artifact: return # Load as PIL images pil_a = load_pil_from_url(image_a_artifact.value) pil_b = load_pil_from_url(image_b_artifact.value) # Process images (override in subclass) result_pil = self._do_processing(pil_a, pil_b) # Save result filename = generate_filename(self.name, suffix="processed") output_artifact = save_pil_image_with_named_filename(result_pil, filename) self.parameter_output_values["output_image"] = output_artifact def _do_processing(self, image_a: Image.Image, image_b: Image.Image) -> Image.Image: """Override this method with actual processing logic.""" raise NotImplementedError def after_value_set(self, parameter: Parameter, value: Any) -> None: """Trigger live preview when both images are available.""" if parameter.name in ("image_a", "image_b"): image_a = self.get_parameter_value("image_a") image_b = self.get_parameter_value("image_b") if image_a and image_b: self._process_images() return super().after_value_set(parameter, value) def process(self) -> None: """Main processing entry point.""" self._process_images() ``` **Key Utilities Used**: - `dict_to_image_url_artifact()`: Converts dict representation to artifact - `load_pil_from_url()`: Loads PIL Image from URL (including localhost) - `save_pil_image_with_named_filename()`: Saves PIL Image and returns artifact - `generate_filename()`: Creates consistent filenames with node name # Parameter UI Reference This page maps what you declare on a `Parameter` in Python to what the editor renders for it. Three things determine a parameter's UI: 1. **The parameter's `type`** selects the widget: `str` gets a text field, `bool` gets a toggle, `ImageUrlArtifact` gets the image viewer, and so on. 1. **`ui_options`** tweaks that widget: hide it, stretch it full-width, make a text field multiline, add a webcam capture button. 1. **Traits** bundle UI and behavior together: `Slider` renders a slider *and* validates the range, `Options` renders a dropdown *and* constrains the value to its choices. Under the hood, a trait writes its own keys into `ui_options` — traits are the supported way to get those keys right. Prefer the [parameter helper classes](https://docs.griptapenodes.com/development/custom_nodes/parameters/#parameter-helper-constructs-parameterstring-parameterint) (`ParameterString`, `ParameterImage`, ...) and traits when one exists for what you want; reach for raw `ui_options` keys only for the presentation tweaks listed here. Undocumented keys are internal The editor reads more `ui_options` keys than are listed on this page. Anything not listed here (or emitted by a trait) is editor-internal and may change or disappear without notice. ## How the widget is chosen For each parameter, the editor picks a widget in this order: 1. If `ui_options` carries `widget` and `library` (set by the [`Widget` trait](#traits)), the editor loads that custom widget from the library. 1. Otherwise, the parameter's `type` selects a built-in widget from the table below. A `list[...]` type (any element type) selects the list widget. 1. A type with no mapping gets no inline widget at all — the parameter still shows its label and connection handles, but there's nothing to edit in place. ## Type-to-widget mapping | `type` | Widget | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | | `str` | Single-line text field (multiline, Markdown, and picker variants via `ui_options` and traits below) | | `int`, `float` | Number input (slider via the `Slider` trait; step size via `step`) | | `bool` | Toggle switch | | `json`, `JsonArtifact` | JSON viewer/editor | | `python`, `yaml` | Syntax-highlighted code editor | | `html`, `xml` | Code editor in HTML/XML mode | | `dict` | Key-value editor; also hosts the image/video comparison sliders (see [dict options](#dict)) | | `list`, `list[...]` | List editor rendering one item widget per element | | `button` | Clickable button (configure with the `Button` trait) | | `Status` | Status/message block | | `UrlArtifact` | URL display | | `ImageUrlArtifact` / `ImageArtifact`, `VideoUrlArtifact` / `VideoArtifact`, `AudioUrlArtifact` / `AudioArtifact`, `ThreeDUrlArtifact` / `ThreeDArtifact`, `SplatUrlArtifact` / `SplatArtifact` | The media viewers and editors — see [Media Viewers and Editors](https://docs.griptapenodes.com/guides/editor/media_editors/index.md) for what each one does | | anything else | No inline widget; label and connection handles only | `GLTFArtifact` / `GLTFUrlArtifact` still render (as the 3D viewer) for backward compatibility; use the `ThreeD` types in new nodes. ## Common `ui_options` (any type) | Key | Effect | | --------------------------- | ----------------------------------------------------------------------------------------------------- | | `hide` | Hide the parameter entirely (label, widget, and handles). | | `hide_label` | Hide the name label, keep the widget. | | `hide_property` | Hide the inline widget, keep the label and connection handles. | | `display_name` | Label text shown in the UI, when it should differ from the parameter's name. | | `is_full_width` | Stretch the widget across the node's full width. | | `parameter_render_location` | Where the parameter renders relative to its siblings: `"top"`, `"in-order"` (default), or `"bottom"`. | ## Per-type `ui_options` ### `str` | Key | Effect | | ------------------ | ------------------------------------------------------------------------ | | `multiline` | Multi-line text area instead of a single-line field. | | `markdown` | Render/edit the text as Markdown. | | `placeholder_text` | Placeholder shown while the field is empty (also works on code editors). | ### `int` / `float` | Key | Effect | | -------------- | -------------------------------------------------------------------------------------------------------- | | `step` | Increment used by the input's stepper; the engine also validates values against it. | | `progress_bar` | Render the value as a progress bar instead of an editable input (for values a node reports, like 0–100). | For a bounded slider, use the `Slider` trait rather than writing `ui_options["slider"]` by hand — the trait also validates the range. ### Image types | Key | Effect | | ------------------------ | ------------------------------------------------------------------------------------ | | `clickable_file_browser` | Clicking the empty parameter opens a file picker; dropped/picked files are uploaded. | | `expander` | Let the viewer expand/collapse. | | `crop` / `crop_image` | Show the crop button that opens the crop editor. | | `edit_mask` | Show the mask button that opens the Paint Mask editor. | | `edit_excalidraw` | Show the edit button that opens Image Bash. | | `webcam_capture_image` | Replace the thumbnail with a live webcam preview and capture button. | | `aspect_ratio` | Fix the display area to an aspect ratio (e.g. `"16:9"`). | | `object_fit` | How the image fills its frame (CSS object-fit values like `"contain"` / `"cover"`). | | `hide_details` | Hide the name/dimensions/file-size line under the thumbnail. | | `pulse_on_run` | Pulse the viewer while the node is executing (also works on audio). | ### Audio types | Key | Effect | | -------------------------- | ------------------------------------------------------- | | `clickable_file_browser` | Click-to-browse upload, same as images. | | `microphone_capture_audio` | Show a record button that captures from the microphone. | | `pulse_on_run` | Pulse the player while the node is executing. | ### `dict` | Key | Effect | | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | | `compare` | Render the dict (`{"input_image_1": ..., "input_image_2": ...}`) as the image comparison slider. Pair with `CompareImagesTrait` to validate the shape. | | `video_compare` | Render the dict as the side-by-side video comparison player. | ### `json` | Key | Effect | | ------- | -------------------------------------------------- | | `modal` | Open the JSON editor in a modal instead of inline. | ### `list` | Key | Effect | | ----------- | ----------------------------------------------- | | `collapsed` | Start the list collapsed. | | `columns` | Lay items out in a grid with this many columns. | `ParameterList` sets the common list options for you — see [ParameterList Pattern](https://docs.griptapenodes.com/development/custom_nodes/parameters/#parameterlist-pattern). ## Traits Traits live in `griptape_nodes.traits` and are attached with `add_trait()` or `traits={...}` on the parameter. Each row lists what the trait renders and, where relevant, the `ui_options` keys it manages — set the trait rather than the keys. | Trait | Typical types | What it does | `ui_options` it writes | | -------------------- | ------------------------- | -------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------- | | `Options` | `str`, any | Dropdown constrained to fixed choices, with optional search. `allow_custom=True` makes it a typeahead instead. | `simple_dropdown`, `show_search`, `search_filter`, `allow_custom` | | `MultiOptions` | `list` | Multi-select dropdown. | `multi_options` | | `Slider` | `int`, `float` | Slider between `min_val` and `max_val`; out-of-range values fail validation. | `slider` | | `Clamp` | `int`, `float`, sequences | Clamps the value into range on assignment. No UI of its own. | — | | `Button` | `button` | Configures a button's label, variant, size, and click behavior. | `button_label`, `variant`, `size`, `state`, `full_width` | | `ColorPicker` | `str` | Color swatch that opens a color picker; validates the format (`"hex"`, etc.). | `color_picker` | | `FileSystemPicker` | `str` | Browse button that opens a file/directory picker with filtering options. | `fileSystemPicker` | | `NumbersSelector` | `dict` | Min/max/step numeric range selector. | `numbers_selector` | | `CompareImagesTrait` | `dict` | Validates the two-image dict shape used by the comparison slider (pair with `ui_options["compare"]`). | — | | `Widget` | any | Replaces the built-in widget with a custom widget shipped by a library. | `widget`, `library` | See the [Traits section of the Parameters reference](https://docs.griptapenodes.com/development/custom_nodes/parameters/#traits) for constructor signatures and examples. ### When the choices are only hints By default `Options` treats `choices` as the whole set of valid values: the parameter renders as a dropdown, a value outside the list is snapped back to the first choice, and assigning one directly fails validation. Pass `allow_custom=True` when the list is a convenience rather than the full set. The parameter renders as a text field that offers matching choices as the user types, and stores whatever they type: ``` Parameter( name="model", type="str", tooltip="Pick a suggested model or type your own id", traits={Options(choices=SUGGESTED_MODELS, allow_custom=True)}, default_value=SUGGESTED_MODELS[0], ) ``` The flag drops the converter and the validator, so `choices` can be replaced at run time without any risk of invalidating a value the node already holds. Reach for it when a value the list cannot know about is still legitimate: a model id the provider added after the node shipped, or a user's own fine-tune. Stay with the default when an unrecognized value would fail at run time, since a dropdown that rejects bad input up front beats a node that errors mid-flow. # Execution and Lifecycle This page covers the callbacks a node can override to participate in the workflow lifecycle, and the patterns for long-running asynchronous work such as external API integration with polling. ## Lifecycle Callbacks All callbacks are overridable: - **allow_incoming_connection**, **allow_outgoing_connection**: Return bool for connection validation - **after_incoming_connection**, **after_outgoing_connection**: Handle post-connection logic - **after_incoming_connection_removed**, **after_outgoing_connection_removed**: Handle disconnection - **before_value_set**: Return modified value before setting - **after_value_set**: React to parameter value changes - **validate_before_workflow_run**, **validate_before_node_run**: Return list[Exception]|None - **on_griptape_event**: Handle workflow events - **initialize_spotlight**: Setup spotlight functionality - **get_next_control_output**: Return Parameter|None for control flow ### Helper Methods - `hide_parameter_by_name()`, `show_parameter_by_name()` - `append_value_to_parameter()` - `publish_update_to_parameter()` - `show_message_by_name()`, `hide_message_by_name()`, `get_message_by_name_or_element_id()` ## Asynchronous API Integration Nodes have two ways to perform long-running work without stalling the engine: 1. **Override `async def aprocess()` (preferred).** The engine awaits it on its event loop, so genuinely asynchronous integrations (async HTTP clients, `await asyncio.sleep()` polling) run concurrently with the rest of the engine. 1. **Override `process()` and `yield` callables (`AsyncResult`).** Each yielded callable is executed **synchronously on a background thread** — the engine stays responsive, but the work itself is still blocking, sequential code. If you are writing a new integration, reach for `aprocess()` and async I/O. Use the `yield` pattern when your integration is built on synchronous libraries (`requests`, blocking SDKs) that you don't want to rewrite. ### Async Processing with `aprocess()` (Preferred) ``` import asyncio import httpx from griptape_nodes.exe_types.node_types import ControlNode POLLING_INTERVAL = 10 # seconds (use API-recommended value) MAX_POLLING_ATTEMPTS = 60 # 10 minutes max class MyAsyncNode(ControlNode): async def aprocess(self) -> None: """Process the request asynchronously.""" try: # Set safe defaults self._set_safe_defaults() # Validate API key api_key = self._validate_api_key() async with httpx.AsyncClient(timeout=60) as client: # Submit task task_id = await self._submit_task(client, api_key) # Poll for completion result = await self._poll_for_completion(client, task_id, api_key) # Process result self.parameter_output_values["output"] = result except Exception as e: self._set_safe_defaults() self._log(f"Processing failed: {e}") raise RuntimeError(f"{self.name}: {e}") from e async def _submit_task(self, client: httpx.AsyncClient, api_key: str) -> str: response = await client.post( "https://api.example.com/v1/tasks", json=self._build_payload(), headers={"Authorization": f"Bearer {api_key}"}, ) response.raise_for_status() return response.json()["task_id"] async def _poll_for_completion(self, client: httpx.AsyncClient, task_id: str, api_key: str) -> str: for attempt in range(MAX_POLLING_ATTEMPTS): await asyncio.sleep(POLLING_INTERVAL) # never time.sleep() in aprocess response = await client.get( "https://api.example.com/v1/query/task", params={"task_id": task_id}, headers={"Authorization": f"Bearer {api_key}"}, ) response.raise_for_status() status_data = response.json() if status_data["status"] == "Success": return status_data["result"] if status_data["status"] == "Fail": error_msg = status_data.get("error_message", "Unknown error") raise RuntimeError(f"Task failed: {error_msg}") # Continue polling for "Processing", "Pending", etc. raise RuntimeError(f"Task did not complete within {MAX_POLLING_ATTEMPTS * POLLING_INTERVAL} seconds") ``` **Key Points:** - Override `async def aprocess()` instead of `process()` — the engine awaits it directly - Use async I/O throughout: `httpx.AsyncClient` for requests, `await asyncio.sleep()` for polling delays - Blocking calls (`requests`, `time.sleep()`) inside `aprocess()` stall the engine's event loop — if you must call a blocking function, wrap it with `await asyncio.to_thread(blocking_fn)` - The base class's default `aprocess()` wraps `process()`, so nodes only need to override one of the two ### Blocking Work on a Background Thread (`process()` + `yield`) For integrations built on synchronous libraries, override `process()` and yield a callable. The engine runs each yielded callable synchronously on a background thread and resumes the generator with its return value — the engine stays responsive, but this does **not** make the work itself asynchronous: ``` from griptape_nodes.exe_types.node_types import ControlNode, AsyncResult class MyBlockingNode(ControlNode): def process(self) -> AsyncResult | None: """Yield the blocking work to a background thread.""" yield lambda: self._process() def _process(self) -> None: """Main processing method (runs synchronously on a background thread).""" try: # Set safe defaults self._set_safe_defaults() # Validate API key api_key = self._validate_api_key() # Submit task task_id = self._submit_task(api_key) # Poll for completion result = self._poll_for_completion(task_id, api_key) # Process result self.parameter_output_values["output"] = result except Exception as e: self._set_safe_defaults() self._log(f"Processing failed: {e}") raise RuntimeError(f"{self.name}: {str(e)}") from e ``` **Key Points:** - `process()` returns `AsyncResult | None` and yields a callable - Each yielded callable runs synchronously on a background thread; the generator resumes with its return value - Fine for existing synchronous code (`requests`, blocking SDKs), but prefer `aprocess()` for new integrations ### Polling Pattern for Long-Running Tasks When integrating with APIs that use asynchronous task processing (video generation, model training, etc.), implement a three-step pattern. The examples below use synchronous `requests` calls, suited to the background-thread pattern above; in `aprocess()`, use `httpx.AsyncClient` and `await asyncio.sleep()` instead, as shown earlier. #### Step 1: Task Submission ``` def _submit_task(self, params: dict[str, Any], headers: dict[str, str]) -> dict[str, Any]: """Submit task and return response with task_id.""" payload = self._build_payload(params) response = requests.post(self.API_BASE_URL, json=payload, headers=headers, timeout=DEFAULT_TIMEOUT) response.raise_for_status() response_data = response.json() task_id = response_data.get("task_id") return response_data ``` #### Step 2: Status Polling ``` POLLING_INTERVAL = 10 # seconds (use API-recommended value) MAX_POLLING_ATTEMPTS = 60 # 10 minutes max def _poll_for_completion(self, task_id: str, headers: dict[str, str]) -> str | None: """Poll API for task completion and return result identifier.""" query_url = "https://api.example.com/v1/query/task" for attempt in range(MAX_POLLING_ATTEMPTS): time.sleep(POLLING_INTERVAL) # Wait before each poll response = requests.get( query_url, headers=headers, params={"task_id": task_id}, # Use query params, not path timeout=DEFAULT_TIMEOUT, ) response.raise_for_status() status_data = response.json() status = status_data.get("status") self._log(f"Polling attempt {attempt + 1}: Status = {status}") if status == "Success": file_id = status_data.get("file_id") return file_id elif status == "Fail": error_msg = status_data.get("error_message", "Unknown error") raise RuntimeError(f"Task failed: {error_msg}") # Continue polling for "Processing", "Pending", etc. raise RuntimeError(f"Task did not complete within {MAX_POLLING_ATTEMPTS * POLLING_INTERVAL} seconds") ``` #### Step 3: Result Retrieval ``` def _retrieve_result(self, file_id: str, headers: dict[str, str]) -> str: """Retrieve download URL from result identifier.""" retrieve_url = "https://api.example.com/v1/files/retrieve" response = requests.get(retrieve_url, headers=headers, params={"file_id": file_id}, timeout=DEFAULT_TIMEOUT) response.raise_for_status() response_data = response.json() download_url = response_data.get("file", {}).get("download_url") return download_url ``` **Key Considerations:** - Always use API-recommended polling intervals (typically 5-10 seconds) - Set reasonable maximum attempts to prevent infinite loops - Use query parameters, not path parameters, for task_id (verify with API docs) - Handle all status states: Success, Fail, Processing, Pending - Log polling attempts for debugging - Set safe defaults on failure ### Dynamic Endpoint Selection Based on Inputs When a node can operate in multiple modes depending on which inputs are connected (e.g., image-to-video when images are provided, text-to-video when they are not), select the API endpoint dynamically in the process method rather than hardcoding a single URL: ``` IMAGE2VIDEO_URL = "https://api.example.com/v1/videos/image2video" TEXT2VIDEO_URL = "https://api.example.com/v1/videos/text2video" def _process(self): image_data = self._get_image_data("start_frame") has_images = image_data is not None if has_images: api_url = IMAGE2VIDEO_URL else: api_url = TEXT2VIDEO_URL payload = self._build_payload() if image_data: payload["image"] = image_data response = requests.post(api_url, headers=headers, json=payload, timeout=30) # ... polling uses the same api_url for status checks poll_url = f"{api_url}/{task_id}" ``` This avoids requiring image inputs when the user wants text-only generation, and ensures the correct API endpoint is called for each mode. The polling URL should use the same base endpoint. ### Image Artifact Conversion to Base64 The `ImageArtifact` branch below exists to keep working with values that may still arrive from older saved workflows or upstream nodes that haven't been updated. New parameters should declare `ImageUrlArtifact` (via `ParameterImage`) rather than `ImageArtifact` — see [Parameter Payload Size](https://docs.griptapenodes.com/development/custom_nodes/error_handling/#parameter-payload-size). **CRITICAL: Localhost URL Handling** When sending images to external APIs, ImageUrlArtifact URLs from static storage are localhost and inaccessible to external services. Always detect and convert localhost URLs to base64: ``` import base64 def _get_image_data(self, image_artifact: ImageArtifact | ImageUrlArtifact) -> str: """Convert image artifact to URL or base64 data URI.""" # ImageUrlArtifact - check if localhost or public URL if isinstance(image_artifact, ImageUrlArtifact): url = image_artifact.value # Localhost URLs must be converted to base64 for external APIs if url.startswith(("http://localhost", "http://127.0.0.1", "https://localhost", "https://127.0.0.1")): self._log(f"Converting localhost URL to base64: {url[:100]}...") response = requests.get(url, timeout=30) response.raise_for_status() image_bytes = response.content # Detect MIME type from headers mime_type = response.headers.get("content-type", "image/jpeg") if not mime_type.startswith("image/"): mime_type = "image/jpeg" base64_data = base64.b64encode(image_bytes).decode("utf-8") return f"data:{mime_type};base64,{base64_data}" # Public URLs can be passed through self._log(f"Using public URL: {url[:100]}...") return url # ImageArtifact - use .base64 property (preferred method) if isinstance(image_artifact, ImageArtifact): # PREFERRED: Use built-in properties if hasattr(image_artifact, "base64") and hasattr(image_artifact, "mime_type"): base64_data = image_artifact.base64 # Raw base64 (no prefix) mime_type = image_artifact.mime_type # e.g., 'image/jpeg' # Check if already has data URI prefix if base64_data.startswith("data:"): self._log("Using ImageArtifact.base64 (already has data URI)") return base64_data # Add data URI prefix self._log(f"Using ImageArtifact.base64 with mime_type: {mime_type}") return f"data:{mime_type};base64,{base64_data}" # FALLBACK: Manual byte extraction self._log("Falling back to manual base64 encoding") if hasattr(image_artifact, "value") and hasattr(image_artifact.value, "read"): image_artifact.value.seek(0) image_bytes = image_artifact.value.read() elif hasattr(image_artifact, "data"): if isinstance(image_artifact.data, bytes): image_bytes = image_artifact.data elif hasattr(image_artifact.data, "read"): image_artifact.data.seek(0) image_bytes = image_artifact.data.read() else: raise ValueError("Unsupported ImageArtifact format") else: raise ValueError("Unsupported ImageArtifact format") # Detect MIME type with PIL mime_type = "image/jpeg" try: from PIL import Image from io import BytesIO img = Image.open(BytesIO(image_bytes)) format_to_mime = {"JPEG": "image/jpeg", "PNG": "image/png", "WEBP": "image/webp"} mime_type = format_to_mime.get(img.format, "image/jpeg") except Exception: pass base64_data = base64.b64encode(image_bytes).decode("utf-8") return f"data:{mime_type};base64,{base64_data}" raise ValueError("Unsupported artifact type") ``` **Key Points:** 1. **Always detect localhost URLs** - External APIs cannot access them 1. **Use ImageArtifact.base64 property** - The proper Griptape way (returns raw base64) 1. **Use ImageArtifact.mime_type property** - Automatic MIME type detection 1. **Log which path is used** - Essential for debugging 1. **Download localhost files** - Convert to base64 before sending to API **Parameter Definition:** ``` Parameter( name="image_input", input_types=["ImageUrlArtifact", "ImageArtifact"], # ImageArtifact: legacy input compatibility only type="ImageUrlArtifact", tooltip="Image input (file or URL)", ui_options={"clickable_file_browser": True}, # Enable file browser ) ``` ### Multi-Image Input Validation When nodes accept multiple image parameters, use a reusable validation method with clear parameter identification. As above, the `ImageArtifact` branch is legacy-input handling, not a reason to declare new parameters against `ImageArtifact`: ``` def _validate_image(self, image_artifact: ImageArtifact | ImageUrlArtifact, param_name: str) -> list[Exception]: """Validate image with parameter name in error messages.""" exceptions = [] if isinstance(image_artifact, ImageArtifact): # Get image bytes if hasattr(image_artifact, "value") and hasattr(image_artifact.value, "read"): image_artifact.value.seek(0) image_bytes = image_artifact.value.read() image_artifact.value.seek(0) else: return exceptions # Validate size size_mb = len(image_bytes) / (1024 * 1024) if size_mb >= 20: exceptions.append(ValueError(f"{self.name}: {param_name} size must be < 20MB (current: {size_mb:.1f}MB)")) # Validate format and dimensions try: from PIL import Image from io import BytesIO img = Image.open(BytesIO(image_bytes)) if img.format not in ["JPEG", "PNG", "WEBP"]: exceptions.append( ValueError(f"{self.name}: {param_name} format must be JPG, PNG, or WebP (current: {img.format})") ) width, height = img.size short_edge = min(width, height) if short_edge <= 300: exceptions.append( ValueError(f"{self.name}: {param_name} short edge must be > 300px (current: {short_edge}px)") ) except ImportError: self._log("PIL not available for validation") except Exception as e: self._log(f"Error validating {param_name}: {e}") return exceptions def validate_before_node_run(self) -> list[Exception] | None: """Validate all image parameters.""" exceptions = [] # Validate each image parameter independently first_frame = self.get_parameter_value("first_frame_image") if first_frame: exceptions.extend(self._validate_image(first_frame, "first_frame_image")) last_frame = self.get_parameter_value("last_frame_image") if last_frame: exceptions.extend(self._validate_image(last_frame, "last_frame_image")) return exceptions if exceptions else None ``` **Benefits:** - Clear error messages identifying which image parameter has issues - Reusable validation logic across multiple image inputs - Independent validation for each parameter - Actionable feedback for users ### Model-Dependent Parameter Management When different models support different parameter combinations: ``` def after_value_set(self, parameter: Parameter, value: Any) -> None: """Handle model-dependent parameter visibility and options.""" if parameter.name == "model": if value == "AdvancedModel": # Show model-specific parameters self.show_parameter_by_name("advanced_option") # Update dropdown choices dynamically resolution_param = self.get_parameter_by_name("resolution") if resolution_param: for child in resolution_param.children: if hasattr(child, "choices"): child.choices = ADVANCED_MODEL_RESOLUTIONS break else: # Hide and reset for other models self.hide_parameter_by_name("advanced_option") # Update to standard choices resolution_param = self.get_parameter_by_name("resolution") if resolution_param: for child in resolution_param.children: if hasattr(child, "choices"): child.choices = STANDARD_RESOLUTIONS break self.set_parameter_value("resolution", "720P") return super().after_value_set(parameter, value) ``` **Model-Specific Validation:** ``` def validate_before_node_run(self) -> list[Exception] | None: """Validate model-specific parameter combinations.""" exceptions = [] model = self.get_parameter_value("model") duration = self.get_parameter_value("duration") resolution = self.get_parameter_value("resolution") # Example: 10s only for specific model/resolution if duration == 10: if model != "AdvancedModel": exceptions.append(ValueError(f"{self.name}: 10s duration only supported by AdvancedModel")) elif resolution == "4K": exceptions.append(ValueError(f"{self.name}: 10s duration not supported with 4K resolution")) # Model-specific parameter requirements if model in ["ModelB", "ModelC"]: required_param = self.get_parameter_value("required_for_model_b_c") if not required_param: exceptions.append(ValueError(f"{self.name}: Parameter required for {model}")) return exceptions if exceptions else None ``` ### Deprecated Model Migration and User Notification When a model provider deprecates endpoints (e.g., preview models replaced by GA equivalents), nodes should automatically migrate saved workflows while informing the user. This pattern uses three components working together: 1. A `DEPRECATED_MODELS` dictionary mapping old model names to their replacements 1. A hidden `ParameterMessage` element that acts as a dismissable info banner 1. The `before_value_set` lifecycle hook to intercept and replace deprecated values before they are applied **Step 1: Define the deprecation map and current models** ``` from griptape_nodes.exe_types.core_types import Parameter, ParameterMessage from griptape_nodes.traits.button import Button MODELS = [ "veo-3.1-generate-001", "veo-3.1-fast-generate-001", ] # Mapping of deprecated model names to their replacements. # When a saved workflow references one of these, the node auto-migrates. DEPRECATED_MODELS: dict[str, str] = { "veo-3.1-generate-preview": "veo-3.1-generate-001", "veo-3.1-fast-generate-preview": "veo-3.1-fast-generate-001", "veo-3.0-generate-001": "veo-3.1-generate-001", "veo-2.0-generate-001": "veo-3.1-generate-001", } ``` **Step 2: Add a hidden ParameterMessage in `__init__`** Place this after the model parameter so it appears near the model selector in the UI. The `hide=True` keeps it invisible until needed. The `Button` trait with `on_click` gives the user a "Dismiss" button. ``` def __init__(self, **kwargs): super().__init__(**kwargs) # ... model parameter added above ... # Hidden deprecation notice — shown when a deprecated model is detected self.add_node_element( ParameterMessage( name="model_deprecation_notice", title="Model Deprecation Notice", variant="info", value="", traits={ Button( full_width=True, on_click=lambda _, __: self.hide_message_by_name("model_deprecation_notice"), ) }, button_text="Dismiss", hide=True, ) ) ``` **Step 3: Implement `before_value_set` to intercept deprecated models** `before_value_set` fires before the parameter's value is applied. This is the right place to swap a deprecated model for its replacement, because `after_value_set` (and any logic that depends on the model value) will see the replacement. ``` def before_value_set(self, parameter: Parameter, value: Any) -> Any: """Auto-migrate deprecated models and show a deprecation notice.""" if parameter.name == "model" and value in DEPRECATED_MODELS: replacement = DEPRECATED_MODELS[value] message = self.get_message_by_name_or_element_id("model_deprecation_notice") if message is not None: message.value = ( f"The '{value}' model has been deprecated. " f"The model has been updated to '{replacement}'. " "Please save your workflow to apply this change." ) self.show_message_by_name("model_deprecation_notice") value = replacement return super().before_value_set(parameter, value) ``` **Step 4: Hide the notice when the user selects a valid model** In `after_value_set`, dismiss the banner when the current model is not deprecated. This handles the case where the user manually selects a different model after the migration. ``` def after_value_set(self, parameter: Parameter, value: Any) -> None: if parameter.name == "model": # ... model-specific logic (update duration choices, etc.) ... if value not in DEPRECATED_MODELS: self.hide_message_by_name("model_deprecation_notice") return super().after_value_set(parameter, value) ``` **How it works end-to-end:** 1. A user opens a workflow saved with `"veo-3.1-generate-preview"`. 1. The framework calls `before_value_set` with the saved value. 1. The hook detects it in `DEPRECATED_MODELS`, swaps it to `"veo-3.1-generate-001"`, and shows the info banner. 1. `after_value_set` fires with the replacement value — model-dependent UI updates (duration choices, parameter visibility, etc.) work correctly because they see the valid GA model. 1. The user sees the banner: *"The 'veo-3.1-generate-preview' model has been deprecated. The model has been updated to 'veo-3.1-generate-001'. Please save your workflow to apply this change."* 1. The user can dismiss the banner or it hides automatically on the next valid model selection. **Key API methods used:** | Method | Purpose | | ---------------------------------------------- | ------------------------------------------------- | | `self.add_node_element(ParameterMessage(...))` | Adds the message element to the node | | `self.get_message_by_name_or_element_id(name)` | Retrieves the message element to update its value | | `self.show_message_by_name(name)` | Makes the hidden message visible | | `self.hide_message_by_name(name)` | Hides the message again | **Reference implementations:** - `GriptapeCloudPrompt` in `griptape_nodes_library/config/prompt/griptape_cloud_prompt.py` (standard library) - `VeoVideoGenerator`, `VeoImageToVideoGenerator`, `VeoTextToVideoWithRef` in the `griptape-nodes-library-googleai` external library ### Enhanced Debug Logging for API Integration For nodes that integrate with external APIs, implement comprehensive debug logging to quickly diagnose issues: ``` # Task Submission - Log full response def _submit_task(self, params: dict, headers: dict) -> dict: response = requests.post(API_URL, json=payload, headers=headers) response.raise_for_status() response_data = response.json() self._log(f"Task submission response: {json.dumps(response_data, indent=2)}") return response_data # Payload Sizes - Log data sizes before sending def _log_request(self, payload: dict) -> None: if "first_frame_image" in payload: img_len = len(payload.get("first_frame_image", "")) self._log(f"first_frame_image data length: {img_len} chars (~{img_len / 1024:.1f}KB)") if "last_frame_image" in payload: img_len = len(payload.get("last_frame_image", "")) self._log(f"last_frame_image data length: {img_len} chars (~{img_len / 1024:.1f}KB)") # Error Responses - Log full API error details def _poll_for_completion(self, task_id: str, headers: dict) -> str: status_data = response.json() status = status_data.get("status") if status == "Fail": # Log complete error response for debugging self._log(f"Full API error response: {json.dumps(status_data, indent=2)}") error_msg = status_data.get("error_message", "Unknown error") raise RuntimeError(f"Task failed: {error_msg}") # Processing Paths - Log which code path is executed def _get_image_data(self, image_artifact) -> str: if isinstance(image_artifact, ImageUrlArtifact): if url.startswith("http://localhost"): self._log(f"Converting localhost URL to base64: {url[:100]}...") else: self._log(f"Using public URL: {url[:100]}...") elif isinstance(image_artifact, ImageArtifact): if hasattr(image_artifact, "base64"): self._log(f"Using ImageArtifact.base64 with mime_type: {mime_type}") else: self._log("Falling back to manual base64 encoding") ``` **What to Log:** - **Full API responses** (submission, polling, retrieval) - **Payload sizes** (especially for base64 data) - **Processing paths** (which code branches execute) - **Model/parameter combinations** being used - **Error details** (full error response from API) **Benefits:** - Quickly identify where failures occur - Understand what data is being sent - Track which code paths execute - Get exact API error messages and codes - Debug without reproducing issues ### API Documentation Verification **Critical Best Practice:** Always verify API specifications directly from documentation. **Common Pitfalls to Avoid:** 1. **Model Names**: Check exact capitalization (`MiniMax-Hailuo-02` not `video-01`) 1. **Endpoints**: Verify exact URLs (`/v1/query/video_generation` not `/v1/video_generation/{id}`) 1. **Parameters**: Check query params vs path params 1. **Response Structure**: Verify exact field names (`file_id` vs `file_list`) 1. **Polling Intervals**: Use API-recommended values **Example: Correct vs Incorrect Polling:** ``` # ✅ CORRECT: Query parameter response = requests.get("https://api.example.com/v1/query/task", params={"task_id": task_id}) # ❌ INCORRECT: Path parameter (unless API specifies this) response = requests.get(f"https://api.example.com/v1/query/task/{task_id}") ``` **When Documentation is Inaccessible:** - Explicitly state inability to access web pages (e.g., JavaScript-heavy docs) - Request user to provide relevant documentation sections - Never assume or infer API patterns without verification - Update implementation when code samples are provided # Working with the Project System The **project system** is Griptape Nodes' centralized file management framework that handles file organization, naming, and saving across all workflows. It eliminates hard-coded file paths and provides a consistent, configurable approach to file operations. This page covers the node-developer side: how a node saves files through the project system. ## Concepts The concepts behind the project system — the workspace, project templates, situations, macros, directories, and environment variables — are documented in the [Project system guides](https://docs.griptapenodes.com/guides/projects/index.md): - [Overview](https://docs.griptapenodes.com/guides/projects/index.md) — how the pieces fit together - [Situations](https://docs.griptapenodes.com/guides/projects/situations/index.md) — named file-saving scenarios, collision policies, and the full table of default situations - [Macros](https://docs.griptapenodes.com/guides/projects/macros/index.md) — the template syntax used to build file paths - [Directories](https://docs.griptapenodes.com/guides/projects/directories/index.md) — logical name-to-path mappings referenced in macros - [Customization Guide](https://docs.griptapenodes.com/guides/projects/customization/index.md) — how users override paths and situations via `griptape-nodes-project.yml` The short version for node authors: a **situation** names a file-saving scenario (e.g. `save_node_output`), its **macro** template (e.g. `{outputs}/{node_name?:_}{file_name_base}{_index?:03}.{file_extension}`) generates the concrete path, and users can customize all of it without any changes to your node's code. ## Using the Project System in Nodes There are two main patterns for working with project files in nodes: ### Pattern 1: ProjectFileParameter (Recommended for Node Outputs) Use `ProjectFileParameter` when your node has a configurable output file parameter that users might want to customize. ``` from griptape_nodes.exe_types.param_components.project_file_parameter import ProjectFileParameter from griptape_nodes.exe_types.core_types import Parameter, ParameterMode from griptape.artifacts.video_url_artifact import VideoUrlArtifact class MyVideoNode(ControlNode): def __init__(self, **kwargs) -> None: super().__init__(**kwargs) # Add regular output parameter self.add_parameter( Parameter( name="output_video", output_type="VideoUrlArtifact", tooltip="Generated video", allowed_modes={ParameterMode.OUTPUT}, ) ) # Add project file parameter for output file configuration. # `situation` declares which situation this node saves under. It defaults # to "save_node_output", but pass it explicitly so the choice is visible. self._output_video_file = ProjectFileParameter( node=self, name="output_video_file", default_filename="output_video.mp4", situation="save_node_output", ) self._output_video_file.add_parameter() def process(self) -> None: # ... generate video_bytes ... # Use build_file() to get a ProjectFileDestination dest = self._output_video_file.build_file() saved = dest.write_bytes(video_bytes) # Set the output parameter with the saved location self.parameter_output_values["output_video"] = VideoUrlArtifact(saved.location) ``` **Key Points:** - `ProjectFileParameter` creates a UI parameter that users can configure - Call `build_file()` to get a `ProjectFileDestination` instance - Use `write_bytes()` to save the file - Access the saved file's URL/path via `saved.location` **How the situation is chosen:** The `situation` argument is the only place a node declares its situation. It is a constructor argument, not a parameter on the node: users never see or edit a situation field on the node face. It defaults to `save_node_output`, so omitting it is valid, but pass it explicitly so readers of your node can tell which situation is in play. The UI parameter this creates (conventionally named `output_file`) holds a filename, not a path. `build_file()` splits that filename into `file_name_base` and `file_extension`, then resolves it through the situation's macro, so the parameter's value is only one part of the final path. A user typing `render.png` on a `save_node_output` node gets `outputs/MyNode_render.png`. The situation name is not displayed on the parameter's value, but it does appear in the tooltip that `add_parameter()` generates (`Output filename (uses 'save_node_output' situation template)`), which is how a user discovers it. Users override the situation per-node via the cog button on the parameter, which spawns a `FileOutputSettings` node pre-filled with your declared situation. When that node is connected, `build_file()` returns *its* `FileDestination` and your `situation` argument is bypassed. Choose the situation that honestly describes your node's operation and let users redirect from there. **❌ Common Mistake: Not Capturing write_bytes() Return Value** ``` # WRONG - Don't do this: dest = self._output_video_file.build_file() dest.write_bytes(video_bytes) # ❌ Return value not captured artifact = VideoUrlArtifact(dest.location) # Using dest, not saved file # This will fail with: "Failed because missing required variables: file_extension, file_name_base" ``` **Why it fails:** Macro variables like `{file_extension}` and `{file_name_base}` are resolved when `write_bytes()` saves the file and returns the saved file object, not by `build_file()`. Using `dest.location` before writing causes macro resolution errors. ``` # CORRECT: dest = self._output_video_file.build_file() saved = dest.write_bytes(video_bytes) # ✅ Capture return value artifact = VideoUrlArtifact(saved.location) # Use saved file's resolved location ``` The `saved` object contains the fully resolved file path with all macros filled in. ### Pattern 2: ProjectFileDestination Directly (For Utility Functions) Use `ProjectFileDestination.from_situation()` directly in utility functions or when you don't need user configuration. ``` from griptape_nodes.files.project_file import ProjectFileDestination from griptape.artifacts.video_url_artifact import VideoUrlArtifact def frames_to_video_artifact(frames: list, fps: int = 30, video_format: str = "mp4") -> VideoUrlArtifact: """Convert a list of frames to a VideoUrlArtifact.""" # ... process frames into video_bytes ... # Save using project file system dest = ProjectFileDestination.from_situation(filename=f"video.{video_format}", situation="save_node_output") saved = dest.write_bytes(video_bytes) return VideoUrlArtifact(saved.location) ``` **Key Points:** - Use `from_situation()` to create a destination with a named situation - The `filename` parameter is the base filename (will be transformed by the situation's macro) - The situation (e.g., "save_node_output") determines the final path and collision behavior ## Migration from StaticFilesManager ### Old Pattern (Deprecated) ``` from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes import uuid def old_save_video(video_bytes: bytes) -> VideoUrlArtifact: filename = f"{uuid.uuid4()}.mp4" url = GriptapeNodes.StaticFilesManager().save_static_file(video_bytes, filename) return VideoUrlArtifact(url) ``` ### New Pattern ``` from griptape_nodes.files.project_file import ProjectFileDestination def new_save_video(video_bytes: bytes) -> VideoUrlArtifact: dest = ProjectFileDestination.from_situation(filename="video.mp4", situation="save_node_output") saved = dest.write_bytes(video_bytes) return VideoUrlArtifact(saved.location) ``` **Migration Benefits:** - No more UUID generation required - Consistent file organization across all nodes - User-configurable file paths via project templates - Better file tracking and management - Automatic handling of name collisions ## Common Situations and When to Use Them - **`save_node_output`**: Primary situation for files generated by nodes (images, videos, audio, etc.) - **`copy_external_file`**: When importing/copying files from external sources - **`download_url`**: When downloading files from URLs - **`save_preview`**: For generating thumbnail or preview images - **`save_static_file`**: For static assets that don't change between runs See [Situations](https://docs.griptapenodes.com/guides/projects/situations/#default-situations) for the full table of default situations, their macros, and collision policies. Users can override any of these (paths, macros, collision policies) in two ways, and your node needs no code changes to respect either: - **Project-wide**: redefining a situation in `griptape-nodes-project.yml` changes it for every node using it. See the [Customization Guide](https://docs.griptapenodes.com/guides/projects/customization/index.md). - **Per-node**: the cog button on your `ProjectFileParameter` connects a `FileOutputSettings` node that overrides the situation, macro, and collision policy for that node alone. ## Best Practices 1. **Always use the project system** for saving files - never use hard-coded paths 1. **Choose the right pattern**: Use `ProjectFileParameter` for user-configurable outputs, `ProjectFileDestination` for utility functions 1. **Use semantic situations**: Pick the situation that best describes your operation 1. **Let macros handle naming**: Don't generate UUIDs or timestamps yourself - let the situation's macro and collision policy handle it 1. **Handle temporary files properly**: Use Python's `tempfile` for intermediate processing, only save final results via the project system 1. **Clean up temporary files**: Always clean up temporary files after copying to the project system ## Example: Complete Video Processing Node ``` import tempfile from pathlib import Path from typing import Any from griptape.artifacts.video_url_artifact import VideoUrlArtifact from griptape_nodes.exe_types.core_types import Parameter, ParameterMode from griptape_nodes.exe_types.node_types import ControlNode, AsyncResult from griptape_nodes.exe_types.param_components.project_file_parameter import ProjectFileParameter from griptape_nodes.files.file import File class ProcessVideo(ControlNode): def __init__(self, **kwargs) -> None: super().__init__(**kwargs) self.add_parameter( Parameter( name="input_video", input_types=["VideoUrlArtifact"], type="VideoUrlArtifact", tooltip="Input video to process", ) ) self.add_parameter( Parameter( name="output_video", output_type="VideoUrlArtifact", tooltip="Processed video", allowed_modes={ParameterMode.OUTPUT}, ) ) # Add project file parameter for output self._output_video_file = ProjectFileParameter( node=self, name="output_video_file", default_filename="processed_video.mp4", situation="save_node_output", ) self._output_video_file.add_parameter() def process(self) -> AsyncResult: yield lambda: self._process() def _process(self) -> None: # Get input video input_artifact = self.get_parameter_value("input_video") input_bytes = File(input_artifact.value).read_bytes() # Process in temporary location with tempfile.NamedTemporaryFile(suffix=".mp4", delete=False) as temp_file: temp_path = Path(temp_file.name) try: # Write input to temp file temp_path.write_bytes(input_bytes) # ... perform processing on temp_path ... # Read processed result output_bytes = temp_path.read_bytes() # Save using project system dest = self._output_video_file.build_file() saved = dest.write_bytes(output_bytes) # Set output self.parameter_output_values["output_video"] = VideoUrlArtifact(saved.location) finally: # Clean up temporary file if temp_path.exists(): temp_path.unlink() ``` # Best Practices and Error Handling General best practices for production-quality nodes: secrets, imports, code quality, error handling, validation, and logging. ## Best Practices ### Core Principles - **Descriptive names and tooltips** - **Robust error handling with validators** - **Single responsibility per node** - **Use `SecretsManager` for API keys and secrets** - **Import all dependencies at module level** - **Idempotent process methods** ### Secrets Management Use `GriptapeNodes.SecretsManager()` to access API keys and secrets: ``` from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes class MyNode(DataNode): SERVICE_NAME = "MyService" API_KEY_NAME = "MY_SERVICE_API_KEY" def _validate_api_key(self) -> str: api_key = GriptapeNodes.SecretsManager().get_secret(self.API_KEY_NAME) if not api_key: raise ValueError(f"Missing {self.API_KEY_NAME}") return api_key ``` **Key Points:** - Import `GriptapeNodes` at module level, not inside functions - Use `SecretsManager().get_secret()` to retrieve secrets - Define `API_KEY_NAME` as a class constant for consistency - Always validate that the secret exists before using it ### Parameter Payload Size A parameter value can end up fully embedded in two places: - **Saved workflow files.** The workflow serializer writes unique parameter values inline into the saved `.py` file, with no size limit — whatever Python state the value holds gets written out whole. - **WebSocket events.** Parameter values traveling in request/response events are serialized and sent to every connected client (the editor UI, the MCP server). Neither path checks the size of the value first, so by default a large value bloats both the saved workflow file and the traffic to every connected client. Store large binary data (images, audio, video, 3D assets, model weights) by reference — a file path or URL — rather than inlining the bytes, wherever the node's underlying API allows it. `Parameter(serializable=False)` (see [Parameter Attributes](https://docs.griptapenodes.com/development/custom_nodes/parameters/#parameter-attributes)) covers **only the first path**. It keeps a value out of saved workflow files — the right choice for values that should never persist, such as drivers, file handles, and large transient buffers — but it has no effect on the second: the value is still sent to every connected client. There is no per-parameter opt-out of the WebSocket path, so keeping the value small is the only lever you have over it. Keep parameter values small `griptape.artifacts.BlobArtifact` stores raw bytes, and `ImageArtifact` / `AudioArtifact` both subclass it — so a node using one of these as a parameter type sends the entire byte payload to every connected client, and writes it into saved workflows unless the parameter is declared `serializable=False`. Use `ImageUrlArtifact` / `AudioUrlArtifact` instead (the `ParameterImage` / `ParameterAudio` helper classes enforce them — see [Parameters](https://docs.griptapenodes.com/development/custom_nodes/parameters/#parameterimage-recommended-for-image-parameters)), which hold only a short URL string no matter how large the file they point to is. Raw bytes aren't confined to `BlobArtifact` and its subclasses — `ThreeDArtifact` holds them too (though does not send its bytes over the WebSocket). Judge a parameter by the size of the value it will actually hold, not by whether its type name looks safe. ### Import Best Practices **Always import dependencies at module level, not inside functions:** ❌ **Bad** - Conditional/lazy imports: ``` def _get_image_data(self, image_artifact): try: from PIL import Image # Don't do this from io import BytesIO img = Image.open(BytesIO(image_bytes)) ``` ✅ **Good** - Module-level imports: ``` # At top of file from PIL import Image from io import BytesIO def _get_image_data(self, image_artifact): img = Image.open(BytesIO(image_bytes)) ``` **Why?** - Makes dependencies clear and visible - Avoids redundant imports throughout the file - Follows Python best practices (PEP 8) - Easier to catch missing dependencies early - Better IDE support and code completion **Exception**: Only use conditional imports for truly optional dependencies that may not be installed: ``` def process(self) -> None: try: from huggingface_hub import HfApi except ImportError: error_msg = "huggingface_hub library not installed" self.parameter_output_values["output"] = None raise ImportError(error_msg) ``` ### Import Organization Organize imports in standard order with blank lines between groups: ``` # Standard library imports import base64 import logging from typing import Any # Third-party imports import requests from PIL import Image # Local/Griptape imports from griptape_nodes.exe_types.core_types import Parameter, ParameterMode from griptape_nodes.exe_types.node_types import DataNode from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes ``` ### Type Checking for Third-Party Libraries When importing third-party libraries, you may encounter type checking errors. Use the appropriate `type: ignore` comment based on the situation: #### Scenario 1: Library Installed but Missing Type Stubs For libraries that are installed but lack type annotations (like `sklearn`, `ultralytics`, `supervision`): ``` # ✅ Library exists but has no type stubs from sklearn.cluster import KMeans # type: ignore[import-untyped] from ultralytics import YOLO # type: ignore[import-untyped] from supervision import Detections # type: ignore[import-untyped] ``` #### Scenario 2: Library Not Installed in CI Type Checking Environment For libraries that are runtime dependencies but not installed in the CI type checking environment (like `color-matcher`, specialized processing libraries): ``` # ✅ Library not installed in type checking environment from color_matcher import ColorMatcher # type: ignore[reportMissingImports] from color_matcher.normalizations import norm_img_to_uint8 # type: ignore[reportMissingImports] ``` #### When to Use Which | Error Type | Comment | Use When | | ---------------------- | -------------------------------------- | -------------------------------- | | `import-untyped` | `# type: ignore[import-untyped]` | Library installed, no type stubs | | `reportMissingImports` | `# type: ignore[reportMissingImports]` | Library not in CI environment | **General guidance:** - `import-untyped` is preferred when both work - it's more precise - `reportMissingImports` is necessary when the library isn't available during type checking - Check CI logs to determine which error you're actually getting ### Function Parameter Management Keep function argument counts low (under 6) by using dataclasses: ❌ **Bad** - Too many parameters: ``` def process_bbox(self, x: int, y: int, width: int, height: int, dilation_percent: float, img_width: int, img_height: int): # Process bounding box ``` ✅ **Good** - Use dataclass: ``` from dataclasses import dataclass @dataclass class BoundingBox: x: int y: int width: int height: int dilation_percent: float img_width: int img_height: int def process_bbox(self, bbox: BoundingBox): # Process bounding box using bbox.x, bbox.y, etc. ``` **Benefits:** - Improved readability - Type safety - Easier to maintain - Self-documenting code ### Code Quality **Additional linting best practices:** - Remove trailing whitespace from all lines (including blank lines) - Use consistent indentation (spaces only, no tabs) - Keep lines under 120 characters when possible - Use descriptive variable names - Avoid adding unnecessary Python packaging scaffolding. Create `__init__.py` files only when you actually want a package (or need them for your chosen packaging approach). #### Pre-commit checks (required) Before committing in `griptape-nodes`, run formatting and checks and fix any errors: ``` make format make check/lint make check/types ``` #### Node docs + navigation When adding a new node to the core library, also add node reference documentation: - Create a docs page at: `docs/nodes//.md` - Add it to `mkdocs.yml` under: `nav -> Nodes Reference -> ` #### Common gotchas - Repo-wide lint/type checks can surface issues in **untracked** files too. Avoid leaving untracked folders/files in the repo (for example, copied scratch folders) when running checks or preparing a PR. - If ruff flags function complexity (e.g., `C901`, `PLR0912`), prefer refactoring into smaller helpers over suppressing. - **`parent_container_name` ≠ `parent_element_name`**: These two `Parameter` attributes look similar but serve completely different purposes. `parent_container_name` is for `ParameterContainer` (list/dictionary ownership), `parent_element_name` is for `ParameterGroup` (UI grouping). Mixing them up causes parameters to land at the node root, skip cleanup between runs, and silently vanish on save/reload. See the [Containers](https://docs.griptapenodes.com/development/custom_nodes/parameters/#containers) section for the full distinction. ## Production Error Handling ### Comprehensive Validation Use `validate_before_node_run()` for complex validation: ``` def validate_before_node_run(self) -> list[Exception] | None: """Validate parameters before running the node.""" exceptions = [] model = self.get_parameter_value("model") if model == "advanced": images = self.get_parameter_list_value("images") or [] if len(images) > MAX_IMAGES: exceptions.append(ValueError(f"{self.name}: Maximum {MAX_IMAGES} images allowed, got {len(images)}")) return exceptions if exceptions else None ``` ### Connection Validation Patterns For complex nodes with multiple connection requirements: ``` def _validate_iterative_connections(self) -> list[Exception]: """Validate that all required connections are properly established.""" errors = [] node_type = self._get_base_node_type_name() # Check if exec_out has outgoing connections if not _outgoing_connection_exists(self.name, self.exec_out.name): errors.append( Exception( f"{self.name}: Missing required connection from 'On Each Item'. " f"REQUIRED ACTION: Connect {node_type} Start to interior loop nodes. " "The start node must connect to other nodes to execute the loop body." ) ) # Check if loop has outgoing connection to End if self.end_node is None: errors.append( Exception( f"{self.name}: Missing required tethering connection. " f"REQUIRED ACTION: Connect {node_type} Start 'Loop End Node' to {node_type} End 'Loop Start Node'. " "This establishes the explicit relationship between start and end nodes." ) ) return errors ``` **Best Practice**: Provide detailed, actionable error messages that tell users exactly what connections are missing and how to fix them. ### Safe Defaults Pattern Always set safe defaults before raising exceptions: ``` def _set_safe_defaults(self) -> None: """Set safe default values for all outputs.""" self.parameter_output_values["result"] = None self.parameter_output_values["status"] = "error" self.parameter_output_values["count"] = 0 def process(self) -> None: try: # Processing logic result = process_data() self.parameter_output_values["result"] = result except Exception as e: self._set_safe_defaults() raise RuntimeError(f"Processing failed: {str(e)}") from e ``` ### URL Construction Use `urllib.parse.urljoin()` for safe URL building: ``` from urllib.parse import urljoin import os def __init__(self, **kwargs): super().__init__(**kwargs) # Safe URL construction base = os.getenv("API_BASE_URL", "https://api.example.com") base_slash = base if base.endswith("/") else base + "/" api_base = urljoin(base_slash, "api/") self._endpoint = urljoin(api_base, "v1/process/") ``` ## Logging Best Practices ### Safe Logging Pattern Prevent logging failures from breaking execution: ``` from contextlib import suppress import logging logger = logging.getLogger(__name__) def _log(self, message: str) -> None: """Safe logging with exception suppression.""" with suppress(Exception): logger.info(message) ``` ### Request Sanitization Sanitize sensitive data in logs: ``` from copy import deepcopy import json PROMPT_TRUNCATE_LENGTH = 100 def _log_request(self, payload: dict[str, Any]) -> None: """Log request with sanitized sensitive data.""" with suppress(Exception): sanitized_payload = deepcopy(payload) # Truncate long prompts prompt = sanitized_payload.get("prompt", "") if len(prompt) > PROMPT_TRUNCATE_LENGTH: sanitized_payload["prompt"] = prompt[:PROMPT_TRUNCATE_LENGTH] + "..." # Redact base64 image data if "image" in sanitized_payload: image_data = sanitized_payload["image"] if isinstance(image_data, str) and image_data.startswith("data:image/"): parts = image_data.split(",", 1) header = parts[0] if parts else "data:image/" b64_len = len(parts[1]) if len(parts) > 1 else 0 sanitized_payload["image"] = f"{header}," self._log(f"Request: {json.dumps(sanitized_payload, indent=2)}") ``` # Authoring Libraries Nodes are distributed as libraries. This page covers the library manifest (`griptape_nodes_library.json`), declarations, dependency management, documentation conventions, and the process for contributing nodes to the standard library. ## Creating Node Libraries Bundle nodes into libraries for sharing. Create `griptape_nodes_library.json`: ``` { "name": "Library Name", "library_schema_version": "0.11.0", "settings": [ { "description": "API keys required by nodes in this library", "category": "app_events.on_app_initialization_complete", "contents": { "secrets_to_register": ["MY_SERVICE_API_KEY", "MY_OTHER_API_KEY"] } } ], "metadata": { "author": "Author Name", "description": "Library description", "library_version": "1.0.0", "engine_version": "0.55.0", "tags": ["AI", "Image Processing"], "dependencies": { "pip_dependencies": ["pillow", "requests"], "pip_install_flags": ["--upgrade"] }, "declarations": [ { "type": "lifecycle_stage", "stage": "STABLE" }, { "type": "model_catalog", "providers": { "anthropic": { "display_name": "Anthropic", "terms_url": "https://www.anthropic.com/legal/commercial-terms", "models": { "claude_opus_byok": { "display_name": "Claude Opus 4 (BYOK)", "family": "Claude 4", "provider_model_id": "claude-opus-4", "key_support": "REQUIRES_CUSTOMER_KEY" } } } } } ] }, "widgets": [ { "name": "MyWidget", "path": "widgets/MyWidget.js", "description": "Custom UI component for the node" } ], "categories": [ { "image": { "title": "Image Processing", "description": "Image manipulation nodes", "color": "border-purple-500", "icon": "Image" } } ], "nodes": [ { "class_name": "MyImageNode", "file_path": "image/my_image_node.py", "metadata": { "category": "image", "description": "Process images with AI", "display_name": "AI Image Processor", "icon": "image", "group": "processing", "declarations": [ { "type": "model_usage", "model_ids": ["claude_opus_byok"] } ] } } ], "workflow_nodes": [ { "node_type": "UpscaleAndTag", "workflow_path": "workflows/upscale_and_tag.py", "metadata": { "category": "image", "description": "Upscales an image and tags it", "display_name": "Upscale and Tag" } } ], "workflows": ["workflows/example_workflow.py"], "is_default_library": false } ``` ### Library Structure - **settings**: Register secrets/API keys used by library nodes - Use `secrets_to_register` array to declare required secrets - Category should be `app_events.on_app_initialization_complete` - Secrets are accessed via `GriptapeNodes.SecretsManager().get_secret()` - **metadata.dependencies**: PIP packages installed on library load - **metadata.declarations** / per-node **metadata.declarations**: typed identity properties (lifecycle stage, arbitrary Python execution) and a library-level model catalog plus per-node references into it. See [Library and Node Declarations](#library-and-node-declarations) below. - **widgets**: Register custom JS widget components (see [Custom Widgets](https://docs.griptapenodes.com/development/custom_nodes/custom_widgets/index.md)) - **categories**: Group nodes in UI with colors and icons - **nodes**: List node classes, file paths, and metadata - **advanced_library_path**: Optional Python file declaring an `AdvancedNodeLibrary` subclass, for libraries that need to run code at load and unload, own a request type, or register node types the manifest does not list (see [Advanced Libraries](https://docs.griptapenodes.com/development/custom_nodes/advanced_libraries/index.md)) - **workflow_nodes**: Nodes generated from saved workflow files instead of Python classes. See [Nodes From Workflow Files](#nodes-from-workflow-files) below. - **workflows**: Template workflow files **Important:** The `secrets_to_register` array tells the system which secrets your library needs. Users will be prompted to configure these secrets through the UI or environment variables. Use flat directory structures. The engine automatically registers and loads libraries. ### Nodes From Workflow Files A node does not have to be a Python class. Point a `workflow_nodes` entry at a saved workflow and the engine generates the node type for you: ``` "workflow_nodes": [ { "node_type": "UpscaleAndTag", "workflow_path": "workflows/upscale_and_tag.py", "metadata": { "category": "image", "description": "Upscales an image and tags it", "display_name": "Upscale and Tag" } } ] ``` | Field | Meaning | | --------------- | ---------------------------------------------------------------------------------------------- | | `node_type` | The name the node is registered under. This is what appears in saved workflows. | | `workflow_path` | Path to the saved workflow `.py`, relative to `griptape_nodes_library.json`. | | `metadata` | The same node metadata block the `nodes` array uses: category, description, display name, etc. | **The workflow needs a Start Flow node and an End Flow node.** Those are what define the node's parameters: - Every non-control parameter on a **Start Flow** node becomes an **input** on the node. - Every non-control parameter on an **End Flow** node becomes an **output** on the node. - The node supplies its own Flow In and Flow Out, so the workflow's control parameters are not surfaced. - The End Flow node's built-in **Status** parameters (`was_successful`, `result_details`) are not surfaced either. They report on the End Flow node's own run, not on anything you exposed. A parameter of your own with one of those names is only dropped if you also put it in a parameter group named `Status`. A parameter name used by exactly one Start Flow or End Flow node keeps its bare name. If two Start Flow nodes both expose `prompt`, both parameters are qualified with their node name instead (`Start_Flow.prompt`, `Start_Flow_2.prompt`) so neither is lost. Spaces in the node name become underscores, because parameter names cannot contain whitespace. A name that appears on both a Start Flow node and an End Flow node becomes a single parameter that is both an input and an output. When the node runs, the engine loads the workflow as a child subflow of the node's own flow, copies the node's input values onto the Start Flow nodes, runs it, and copies the End Flow values back out as the node's outputs. The subflow is reused for later runs in the same session and is never written into a saved workflow, so your users' files carry the node, not a copy of the workflow behind it. **Save the workflow from the editor before shipping it.** The parameter shape is read out of the metadata header at the top of the workflow file, which the editor writes on save. A workflow with no saved shape (no Start Flow and End Flow nodes, or a hand-written file with no header) is reported as a library problem and its node is not registered. The workflow is also registered as a workflow in its own right, so it shows up in the editor's workflow picker. To keep a workflow that only exists to back a node out of that list, set `is_internal = true` in its metadata header. ### Library and Node Declarations Declarations attach typed metadata to a library or to an individual node. Each entry in a `declarations` array is an object with a `type` discriminator that selects a declaration class. Today's vocabulary covers a lifecycle-stage property, a library-level model catalog with per-node references, and an arbitrary-Python-execution property; future engine releases add more declaration types under the same field. Both `metadata.declarations` (library-level) and per-node `metadata.declarations` accept a list. Order in the list does not matter. The field defaults to `[]`, so libraries on older schema versions (`0.6.0`, `0.4.0`, `0.1.0`) load unchanged. #### `lifecycle_stage` Lifecycle stage for a library or for a specific node. Values: | Value | Meaning | | ------------ | ------------------------------------------------------------------- | | `STABLE` | Mature; intended for production use. | | `BETA` | Feature-complete but still hardening. | | `ALPHA` | Early implementation; expect breaking changes. | | `LABS` | Exploratory; may be removed. | | `DEPRECATED` | Slated for removal; existing usage should migrate to a replacement. | Semantics: - **Library-level absence is intentionally distinct from `STABLE`.** A library with no `lifecycle_stage` declaration is "unstated" — consumers should surface that explicitly (``) rather than silently assume `STABLE`. - **Node-level absence means "inherit the library's stage."** A node-level `lifecycle_stage` overrides the library's value. #### `model_catalog` A library-level declaration of the third-party models nodes in the library can use, organized as a `provider → model` registry. Identifiers at both levels are dict keys (the key *is* the stable handle used by node references and admin policies); each entry carries a `display_name` for UI plus optional `terms_url` and `notes`. Each `Model` additionally declares `key_support` (required), an optional `family` grouping tag, and an optional upstream `provider_model_id`. The `key_support` value tells admins what kind of API key authorizes the call: | Value | Meaning | | --------------------------------------- | ----------------------------------------------------------------------------------- | | `REQUIRES_CUSTOMER_KEY` | Customer-supplied API key only. | | `SUPPORTS_CUSTOMER_KEY_OR_GRIPTAPE_KEY` | Customer key or Griptape-provided key both work. | | `REQUIRES_GRIPTAPE_KEY` | Griptape-provided key only. | | `NO_KEY_REQUIRED` | The model runs locally or otherwise needs no API key (e.g. an Ollama-hosted model). | `notes` (available on both the provider and the model) is free-form author guidance rendered alongside the entry. Use it for caveats that don't fit other fields, like "BYOK requires injecting a provider-specific prompt driver." ``` { "type": "model_catalog", "providers": { "anthropic": { "display_name": "Anthropic", "terms_url": "https://www.anthropic.com/legal/commercial-terms", "models": { "claude_opus_byok": { "display_name": "Claude Opus 4 (BYOK)", "family": "Claude 4", "provider_model_id": "claude-opus-4", "key_support": "REQUIRES_CUSTOMER_KEY" }, "claude_opus_griptape": { "display_name": "Claude Opus 4 (Griptape Key)", "family": "Claude 4", "provider_model_id": "claude-opus-4", "key_support": "REQUIRES_GRIPTAPE_KEY" } } }, "kling": { "display_name": "Kling", "terms_url": "https://app.klingai.com/global/about/terms", "models": { "kling_v2": { "display_name": "Kling v2", "provider_model_id": "kling-v2-master", "key_support": "REQUIRES_GRIPTAPE_KEY" } } }, "ollama": { "display_name": "Ollama", "key_support": "NO_KEY_REQUIRED", "notes": "Local runtime; models enumerated at runtime, none declared here." } } } ``` A few rules worth knowing: - **`family` is just a tag.** It clusters related models for display (e.g. the two Claude 4 entries above). It is not a container and is not part of a model's identity, so providers without meaningful families simply omit it. - **`key_support` lives on the model by default.** Every `Model` declares its own value. The same upstream model with two different key requirements becomes two models under two distinct dict keys (see `claude_opus_byok` and `claude_opus_griptape` above). A `ModelProvider` also accepts an optional `key_support` used only when it declares no models at all (e.g. a local-runtime provider like Ollama where `key_support=NO_KEY_REQUIRED` is the only meaningful signal). - **Model IDs must be unique across the entire library.** Pydantic enforces uniqueness within each provider's `models` dict for free; collisions across providers are caught at library-load time as `DuplicateModelIdProblem`. - **At most one `model_catalog` per library.** Declaring two is rejected at validation time; merge their providers into one. A node's model-selection dropdown stores the provider's own model id (`kling-v2-master`, not `Kling v2`), and `ModelAccessComponent` resolves it to the catalog key that the permission layer gates on. Storing a display name instead means the selection cannot be resolved, so it cannot be checked against license policy. If a dropdown historically stored something else, migrate those saved values with the component's `deprecated_values` mapping rather than renaming the catalog entry to match them: ``` self._model_access = ModelAccessComponent( node=self, parameter=model_param, model_choices=["kling-v2-master", "kling-v2-1-master"], default_model="kling-v2-master", deprecated_values={"Kling v2": "kling-v2-master", "Kling v2.1": "kling-v2-1-master"}, ) ``` Each key is accepted wherever a value is assigned, including workflow load, and migrated to its mapped choice. Keys are never offered in the dropdown, so retiring a model means moving it from `model_choices` into `deprecated_values` pointed at its replacement. Every mapped value must be one of `model_choices`, and no key may already be a choice; either mistake raises when the node is constructed. If the model you are retiring was also the `default_model`, point `default_model` at its replacement in the same edit -- `default_model` must always name a current choice, never a retired key, and the component raises if it does not. ##### `model_usage` A node references one or more catalog models by their dict keys. Use this when the node binds to a specific, named set of models. Each entry must resolve to a model somewhere in the catalog at library-load time; unresolved references surface as `UnresolvedModelUsageReferenceProblem`. ``` { "type": "model_usage", "model_ids": ["claude_opus_byok", "kling_v2"] } ``` ##### `model_provider_usage` A node references one or more entire providers. Use this when a node dynamically enumerates every model a provider offers at runtime. Each entry must resolve to a provider declared in the catalog; unresolved references surface as `UnresolvedModelProviderUsageReferenceProblem`. ``` { "type": "model_provider_usage", "provider_ids": ["anthropic", "ollama"] } ``` The two usage declarations are independent. A node can carry any combination — for instance, "every model this provider offers, plus these two specific models from another provider." #### `arbitrary_python_execution` Declares that a node executes arbitrary Python code supplied at runtime (for example, an artist-authored script). Node-level only. This is a security-relevant identity fact: consumers (UI) can warn an artist before the node runs. Absence of this declaration means the node does not execute arbitrary Python. | Field | Meaning | | --------------------------- | ------------------------------------------------------------ | | `executes_arbitrary_python` | `true` when the node runs unvetted, runtime-supplied Python. | ``` "declarations": [ { "type": "arbitrary_python_execution", "executes_arbitrary_python": true } ] ``` #### Combining declarations A node can carry any combination of declarations. For example, a Labs node that uses two models: ``` "metadata": { "category": "labs", "description": "Labs node demonstrating multiple declarations.", "display_name": "Labs Node", "declarations": [ { "type": "lifecycle_stage", "stage": "LABS" }, { "type": "model_usage", "model_ids": ["claude_opus_byok", "kling_v2"] } ] } ``` New declaration types added in future engine releases land additively under this same `declarations` field without a schema-version bump. ## Library Structure with uv Dependency Management **Modern Approach**: Use `uv` for fast, reproducible dependency management following the Minimax library pattern. ### Directory Structure ``` library-name/ ├── pyproject.toml # uv configuration ├── uv.lock # Lock file (generated) ├── LICENSE # License file ├── README.md # Documentation ├── CHANGELOG.md # Version history ├── .gitignore # Ignore rules └── library_name/ ├── griptape_nodes_library.json # Library metadata └── node_file.py ``` ### pyproject.toml Configuration ``` [project] name = "library-name" version = "1.0.0" description = "Description of your library" authors = [ {name = "Your Name", email = "email@example.com"} ] readme = "README.md" requires-python = ">=3.12" dependencies = [ "griptape-nodes-engine", "requests", # Add other dependencies ] [tool.uv.sources] griptape-nodes-engine = { git = "https://github.com/griptape-ai/griptape-nodes", rev="latest"} [tool.hatch.build.targets.wheel] packages = ["library_name"] [build-system] requires = ["hatchling"] build-backend = "hatchling.build" ``` ### Library Configuration (inside subdirectory) Place `griptape_nodes_library.json` inside the library subdirectory: ``` { "name": "Library Name", "library_schema_version": "0.1.0", "settings": [ { "description": "API keys required by nodes", "category": "app_events.on_app_initialization_complete", "contents": { "secrets_to_register": ["API_KEY_NAME"] } } ], "nodes": [ { "class_name": "NodeClassName", "file_path": "node_file.py", // Relative to library subdirectory "metadata": { "category": "category_name", "description": "Node description", "display_name": "Node Display Name" } } ] } ``` ### Installation Instructions in README Provide both uv (recommended) and pip (fallback) installation methods: ```` ## Installation ### Option 1: Using uv (Recommended) 1. Clone or download this library 2. Install dependencies: ```bash cd library-name uv sync ```` 3. Place in Griptape Nodes libraries directory ### Option 2: Automatic Installation 1. Place folder in libraries directory 1. Dependencies install automatically via pip ``` ### Generate Lock File ``` cd library-name uv sync ``` **Benefits:** - Fast installation (Rust-based) - Reproducible builds via lock file - Direct GitHub integration for griptape-nodes - Backward compatible with pip installation ## Documentation Patterns for Node Libraries ### Comprehensive README Structure ``` # Library Name Brief description and key features. ## Features - Bullet list of main capabilities - Include model options - Highlight unique features ## Installation ### Option 1: Using uv (Recommended) Steps for uv installation ### Option 2: Automatic Installation Steps for pip installation ## Getting Started ### Simple Mode (Recommended for First-Time Users) Minimal example with explanations ### Custom Mode (Advanced) Advanced example showing all features ## Parameters ### Basic Parameters Table with Name, Type, Description ### Advanced Parameters (Hidden by Default) Table with Name, Type, Default, Description ### Output Parameters Table with outputs ## Model Comparison Table comparing models: | Model | Max Duration | Quality | Speed | Character Limits | ## Character Limits Clear tables showing limits by model/mode ## API Rate Limits Document: - Concurrency limits - Generation time expectations - File retention policies ## Example Workflows 3-5 complete examples covering common use cases ## Error Handling Common errors and solutions ## Troubleshooting FAQ-style troubleshooting guide ## API Reference Link to official API docs ## Best Practices Tips for optimal usage ## Support Where to get help ## Version History Link to CHANGELOG ``` ### Troubleshooting Entries Worth Documenting Two errors come up often enough that a library README's troubleshooting section should cover them: #### Error: "Missing required variables: file_extension, file_name_base" **Full Error:** ``` ERROR: Attempted to resolve macro path. Failed because missing required variables: file_extension, file_name_base ERROR: Attempted to create download URL. Failed with file_path='{outputs}/{node_name?:\_}{file_name_base}{\_index?:03}.{file_extension}' ``` **Cause:** Not capturing the return value from `write_bytes()`. Using `dest.location` instead of `saved.location`. **Incorrect Code:** ``` dest = self.\_output_file.build_file() dest.write_bytes(video_bytes) # ❌ Return value not captured artifact = VideoUrlArtifact(dest.location) # Using dest, not saved file ``` **Solution:** ``` dest = self.\_output_file.build_file() saved = dest.write_bytes(video_bytes) # ✅ Capture the saved file artifact = VideoUrlArtifact(saved.location) # Use saved file's resolved location ``` **Explanation:** Macro variables are populated when `write_bytes()` actually saves the file. The `saved` object returned by `write_bytes()` contains the fully resolved path. #### Error: Type Conversion Issues with Image Parameters **Symptom:** Image parameters don't handle different input types consistently, or errors occur when passing URLs, file paths, or artifacts between nodes. **Problem:** Using generic `Parameter` with manual type configuration doesn't standardize type conversion logic: ``` # ❌ Inconsistent type handling Parameter( name="image", input_types=["ImageArtifact", "ImageUrlArtifact", "str"], type="ImageArtifact", ) ``` **Solution:** Use `ParameterImage` for standardized type conversion: ``` # ✅ Standardized type handling ParameterImage( name="image", tooltip="Input image", allow_output=False, ) ``` **Benefits:** - Declares `ImageUrlArtifact` instead of `ImageArtifact`, keeping the parameter's value small — see [Parameter Payload Size](https://docs.griptapenodes.com/development/custom_nodes/error_handling/#parameter-payload-size) - Handles ImageArtifact, ImageUrlArtifact, and strings consistently - Built-in support for URLs, file paths, and data URIs - Graceful error handling for various input formats - Reduces type conversion errors in complex workflows ### Model Comparison Table Always include a comparison table for services with multiple models: ``` | Model | Max Duration | Quality | Speed | Character Limits | | ----- | ------------ | -------- | ------- | ------------------------- | | V5 | 4 min | Superior | Fastest | Prompt: 5000, Style: 1000 | | V4_5 | 8 min | High | Fast | Prompt: 5000, Style: 1000 | | V4 | 4 min | Best | Medium | Prompt: 3000, Style: 200 | ``` ## Contributing to the Standard Library When adding nodes to the core `griptape_nodes_library` (as opposed to creating a standalone library), follow this process: ### 1. Create a Feature Branch ``` cd griptape-nodes git checkout -b feature/add-color-match-node ``` ### 2. Add the Node File Place your node in the appropriate category subdirectory: ``` libraries/griptape_nodes_library/griptape_nodes_library/ ├── image/ │ ├── color_match.py # New node file │ ├── load_image.py │ └── save_image.py ├── text/ ├── audio/ └── ... ``` ### 3. Update griptape_nodes_library.json Make three updates to `libraries/griptape_nodes_library/griptape_nodes_library.json`: #### a. Increment the library version ``` { "metadata": { "library_version": "0.59.0" // Was 0.58.0 } } ``` #### b. Add any new pip dependencies ``` { "metadata": { "dependencies": { "pip_dependencies": [ "existing-dep", "color-matcher" // New dependency ] } } } ``` #### c. Add the node entry ``` { "nodes": [ { "class_name": "ColorMatch", "file_path": "griptape_nodes_library/image/color_match.py", "metadata": { "category": "image", "description": "Transfer color characteristics from a reference image to a target image", "display_name": "Color Match", "icon": "palette", "group": "edit" } } ] } ``` ### 4. Add Documentation Create a documentation page at `docs/nodes//.md`: ``` # Color Match Transfer color characteristics from a reference image to a target image. ## What It Does Applies the color palette from a reference image to a target image... ## Parameters ### Inputs | Parameter | Type | Description | | --------------- | ---------------- | --------------------------- | | reference_image | ImageUrlArtifact | Source of the color palette | | target_image | ImageUrlArtifact | Image to apply colors to | ### Outputs | Parameter | Type | Description | | ------------ | ---------------- | -------------------- | | output_image | ImageUrlArtifact | Color-matched result | ## Example Usage 1. Connect a reference image with desired colors 1. Connect the target image to transform 1. Run the node ## Technical Details Uses the color-matcher library with histogram matching... ``` ### 5. Update mkdocs.yml Navigation Add your doc page to the navigation in `mkdocs.yml`: ``` nav: - Nodes Reference: - Image: - Load Image: nodes/image/load_image.md - Save Image: nodes/image/save_image.md - Color Match: nodes/image/color_match.md # New entry ``` ### 6. Run Quality Checks Before committing, run formatting and checks: ``` make format # Auto-format code make check/lint # Check for linting issues make check/types # Check for type errors ``` Fix any issues that arise before proceeding. ### 7. Commit and Create PR ``` git add . git commit -m "feat(image): add ColorMatch node for color transfer" git push -u origin HEAD gh pr create --title "Add ColorMatch node" --body "## Summary - Adds ColorMatch node for transferring colors between images - Uses color-matcher library - Includes documentation ## Test plan - [ ] Load two images - [ ] Run color match - [ ] Verify output has reference colors" ```` ### Standard Library vs External Library | Aspect | Standard Library | External Library | | --- | --- | --- | | Location | `griptape-nodes` repo | Separate repo | | Installation | Included by default | User installs | | Review | Requires PR approval | Self-published | | Dependencies | Added to core `griptape_nodes_library.json` | Own `griptape_nodes_library.json` | | Versioning | Follows core library version | Independent versioning | | Docs | Added to main docs site | README in library | **When to contribute to standard library:** - Node has broad utility for many users - No proprietary/paid API dependencies - Stable, well-tested implementation - Follows all code quality standards **When to create external library:** - Niche use case - Requires paid API keys - Experimental/rapidly changing - Want independent release cycle``` ```` # Advanced Libraries Most libraries are fully described by their `griptape_nodes_library.json` manifest: the engine reads it, imports the node modules it names, and registers those classes. An **advanced library** is an optional Python class that lets your library run code at five points in its own lifecycle: before its nodes load, after they load, before it is unregistered, when the engine collects request handlers, and when it collects post-dispatch hooks. This page is the author's reference for that class. For the manifest itself (metadata, categories, declarations, dependency management) see [Authoring Libraries](https://docs.griptapenodes.com/development/custom_nodes/authoring_libraries/index.md). ## Should I write one? **You don't need one if** your library is a fixed set of node classes in files you can list in the manifest. That is the common case and it needs no Python beyond the nodes. **Write one if** you need to: - **Acquire or release process-wide resources** at library load and unload: a GPU context, a Python binding to a native SDK, a background thread, a connection pool. - **Register node types the manifest doesn't list**, because the set is data-driven or generated. See [Registering node types without listing them](#registering-node-types-without-listing-them-in-the-manifest). - **Serve a request type** your library owns, so other libraries and nodes can call into it. See [`get_request_handlers`](#get_request_handlers). - **React after the engine handles a request** it already owns, such as running your own step every time a workflow is saved. See [`get_post_dispatch_hooks`](#get_post_dispatch_hooks). - **Register a competing provider** for an engine request type, such as a workflow publisher. See [Publishing](https://docs.griptapenodes.com/guides/publishing/index.md). ## Wiring one up Point the manifest at a Python file with `advanced_library_path`, relative to the manifest: ``` { "name": "My Library", "advanced_library_path": "advanced_library.py", "nodes": [] } ``` Then subclass `AdvancedNodeLibrary` in that file and override only the hooks you need. Every hook has a default no-op implementation: ``` from griptape_nodes.node_library.advanced_node_library import AdvancedNodeLibrary class MyLibrary(AdvancedNodeLibrary): def after_library_nodes_loaded(self, library_data, library) -> None: print(f"Loaded {len(library.get_registered_nodes())} nodes") ``` Three rules govern how the engine finds and builds your class. All three fail the whole library if broken, so they are worth knowing: - **The class must be defined in that file.** The engine scans the module for an `AdvancedNodeLibrary` subclass whose `__module__` matches the module it just imported. A subclass *imported* into the file is skipped. If you want the implementation to live in a package, subclass it in the file the manifest names. - **The first match wins.** The engine takes the first qualifying subclass it finds in module order and stops. Define exactly one to avoid ambiguity. - **`__init__` must take no required arguments.** The engine instantiates your class with no arguments. Derive whatever state you need in `__init__` or in the hooks. If the module fails to import, contains no qualifying subclass, or cannot be instantiated, the library is marked `UNUSABLE`, registration fails, and the editor shows an `AdvancedLibraryLoadFailureProblem` carrying the underlying error. ## The load sequence Understanding where each hook sits is the difference between a hook that works and one that silently does nothing. Registering a library runs, in order: | Step | What the engine does | | ---- | ------------------------------------------------------------------- | | 1 | Parses and validates `griptape_nodes_library.json` | | 2 | Adds the library directory and its venv site-packages to `sys.path` | | 3 | Imports your advanced library module and instantiates your class | | 4 | Registers the `Library` in the `LibraryRegistry` | | 5 | Persists any library settings the manifest declares | | 6 | **Calls `before_library_nodes_loaded`** | | 7 | Iterates `library_data.nodes`, registering each node type | | 8 | Registers the manifest's widgets | | 9 | **Calls `after_library_nodes_loaded`** | | 10 | **Calls `get_request_handlers`** and registers what it returns | | 11 | **Calls `get_post_dispatch_hooks`** and registers what it returns | | 12 | Computes library fitness and marks the library `LOADED` | Two consequences worth internalizing: - Your class exists (step 3) before the library is registered (step 4), so `__init__` cannot look itself up in the `LibraryRegistry`. - Step 7 reads `library_data.nodes` *after* step 6 has run, which is what makes dynamic registration possible. Unregistering runs in the reverse spirit: | Step | What the engine does | | ---- | ------------------------------------------------------------------------------- | | 1 | **Calls `before_library_unregistered`** | | 2 | Removes the library's app event listeners, pre-dispatch and post-dispatch hooks | | 3 | Removes the request handlers it registered | | 4 | Unregisters the library's widgets | | 5 | Removes the library from the `LibraryRegistry` | ## The hooks ### `before_library_nodes_loaded` ``` def before_library_nodes_loaded(self, library_data: LibrarySchema, library: Library) -> None: ... ``` Runs after your library is registered but before any node type is. Use it to set up prerequisites that node imports depend on, or to add node definitions to `library_data.nodes`. `library_data` is the live `LibrarySchema` the `Library` holds, not a copy. Mutating it here changes what the engine loads in step 7 and what it reports afterwards. If this raises, the engine records a `BeforeLibraryCallbackProblem` and **continues loading**. The library ends up `FLAWED` rather than failed, so a broken hook produces a library whose nodes work but whose setup did not run. Do not rely on it having succeeded. ### `after_library_nodes_loaded` ``` def after_library_nodes_loaded(self, library_data: LibrarySchema, library: Library) -> None: ... ``` Runs once every node type in the manifest is registered. At this point `library.get_registered_nodes()` returns the full list, so this is the place for work that needs to see the finished library. This is also where you register competing-provider event handlers via `LibraryManager.on_register_event_handler()`, which is how a library advertises itself as a workflow publisher. Errors are handled the same way as the before hook: an `AfterLibraryCallbackProblem` is recorded and loading continues. ### `before_library_unregistered` ``` def before_library_unregistered(self, library_data: LibrarySchema, library: Library) -> None: ... ``` Runs before the engine tears anything down, so your listeners and handlers are still registered while it executes. Use it to release what you acquired at load: native bindings, GPU contexts, background threads, connection pools. Errors here are **logged and swallowed**. Unregistration continues regardless, so a failing teardown cannot wedge the engine. The flip side is that a resource you fail to release is leaked silently. ### `get_request_handlers` ``` def get_request_handlers(self) -> list[tuple[type[RequestPayload], Callable]]: ... ``` Returns `(request_type, handler)` pairs the engine registers on your behalf, and deregisters automatically when your library unloads. Both sync and async handlers work. This is how a library exposes a service its own nodes, other libraries, and external clients can all call. There are three pieces to build. See [the worked example](#example-a-library-that-serves-a-request-type) for all of them in context. **1. Define the payloads.** A request type and at least one result type, each a dataclass registered with `PayloadRegistry` so it can be resolved by name over the WebSocket and MCP surfaces: ``` @dataclass @PayloadRegistry.register class ConvertColorspaceRequest(RequestPayload): color: tuple[float, float, float] source: str target: str @dataclass @PayloadRegistry.register class ConvertColorspaceResultSuccess(WorkflowNotAlteredMixin, ResultPayloadSuccess): color: tuple[float, float, float] @dataclass @PayloadRegistry.register class ConvertColorspaceResultFailure(WorkflowNotAlteredMixin, ResultPayloadFailure): pass ``` Put them in their own module that both your advanced library and your nodes import. The library directory is on `sys.path` by the time either loads, so a plain `import colorspace_events` resolves, and both files get the same module object and therefore the same payload classes. Give that module a distinctive name: every library directory lands on the same `sys.path`, so `events.py` risks resolving to another library's file. **2. Return the pair from the hook.** ``` def get_request_handlers(self) -> list[tuple[type[RequestPayload], Callable]]: return [(ConvertColorspaceRequest, self._handle_convert_colorspace)] ``` Annotate the return with a bare `Callable`. The base class declares `Callable[[RequestPayload], ResultPayload]`, and a handler annotated with your concrete request type is not assignable to that, because parameter types are contravariant. Keeping the handler's own annotation precise is worth more than matching the base signature exactly. **3. Dispatch it.** Any caller in the orchestrator process sends the request through the normal bus and gets your result back: ``` result = GriptapeNodes.handle_request(ConvertColorspaceRequest(color=(1.0, 0.0, 0.0), source="rgb", target="hsv")) if result.failed(): msg = f"Attempted to convert a color in '{self.name}'. Failed because {result.result_details}" raise RuntimeError(msg) success = cast("ConvertColorspaceResultSuccess", result) ``` Two rules for callers: - **Dispatch from `process`, never from `__init__`.** A node constructor that sends a request trips the `reentrant-bus-in-init` strict-mode rule, and it can deadlock against handlers that await engine startup. See [Strict Mode Reference](https://docs.griptapenodes.com/development/custom_nodes/strict_mode/index.md). - **Always handle failure.** The providing library might not be installed, might have failed to load, or might have been unloaded. In those cases the request has no handler at all, and the engine returns a generic failure result rather than your library's failure type. Check `result.failed()` before narrowing to your success type. Constraints on the mechanism: - **Your library must own the request type.** Define the `RequestPayload` subclass in your own package. - **One handler per request type, engine-wide.** Registering a type that already has a handler raises, surfaced as a `RequestHandlerRegistrationProblem`. For request types where several libraries compete and the caller picks one by name, use `LibraryManager.on_register_event_handler()` in `after_library_nodes_loaded` instead. - **Orchestrator only.** A library running isolated in a worker subprocess registers its handlers in that worker, where the orchestrator cannot reach them. Requests fail with "No manager found". The engine flags this combination with a `RequestHandlersWorkerIncompatibleProblem` at load. See [Node Isolation with Workers](https://docs.griptapenodes.com/development/custom_nodes/node_isolation_with_workers/index.md). Other code can discover what a loaded library exposes with `library.get_registered_request_handler_types()`, then inspect each type with `dataclasses.fields()` and `typing.get_type_hints()`. ### `get_post_dispatch_hooks` ``` def get_post_dispatch_hooks(self) -> list[tuple[type[RequestPayload], Callable]]: ... ``` Returns `(request_type, callback)` pairs the engine registers on your behalf, and deregisters when your library unloads. After the engine's own handler for that request type produces a result, your callback is invoked with `(request, result)`. This is how a library runs its own step when something happens in the engine — appending to an audit log, posting to a chat channel, kicking off an export — for a request type it does not own. It is the mirror image of [`get_request_handlers`](#get_request_handlers), and the difference is ownership: | | `get_request_handlers` | `get_post_dispatch_hooks` | | ----------------------- | ------------------------------ | --------------------------------------------------- | | Claims the request type | Yes — one handler engine-wide | No — any number of libraries may hook the same type | | When it runs | *Instead of* an engine handler | *After* the engine's handler produced a result | | Can change the outcome | It **is** the outcome | No, notification only | That is why you can hook `SaveWorkflowRequest`, which `WorkflowManager` already owns and which `get_request_handlers` would fail to claim. Both sync and async callbacks work. Return the pairs from the hook: ``` def get_post_dispatch_hooks(self): return [(SaveWorkflowRequest, self._on_workflow_saved)] async def _on_workflow_saved(self, request: RequestPayload, result: ResultPayload) -> None: if not isinstance(result, SaveWorkflowResultSuccess): return await self._append_audit_line(result.file_path) ``` Constraints on the mechanism: - **Notification only.** The return value is ignored, and the callback cannot alter the result or fail the operation. One that raises is logged and otherwise ignored, and it does not stop other hooks on the same request. - **Both outcomes.** The callback fires for successes and failures alike, including a failure synthesized from an exception that escaped the handler. Branch on the result type to filter, as the example above does. - **Usually detached, sometimes not.** Whenever the engine has a live event loop the hook is scheduled as a detached task, so the result reaches the editor without waiting for it. Some paths have no such loop — CLI commands, bootstrap workflow runs, worker threads — and there the hook runs inline and **blocks the caller until it returns**. Keep hooks quick, or move slow work off-process, if they may fire on those paths. - **Exact type matching.** A hook registered for a request type fires for that exact type only, never for its subclasses. - **Read-only arguments.** Do not mutate the request or the result; both are still referenced by the result event the engine is about to serialize. Fields marked `omit_from_result` have already been cleared on the request you receive, so a hook is not a way to read them. - **Do not issue engine requests from a hook.** The engine's operation-depth and node-execution state is process-wide, so a request sent from a hook can perturb an operation that is still in flight. Do external work — HTTP, file writes — instead. - **Orchestrator only.** Hooks are registered on the event manager of whichever process loads the library, so a worker-mode library's hooks never see requests the orchestrator handled. The engine flags that combination with a `PostDispatchHooksWorkerIncompatibleProblem` at load. See [Node Isolation with Workers](https://docs.griptapenodes.com/development/custom_nodes/node_isolation_with_workers/index.md). - **Not durable.** Hooks still in flight when the process exits are abandoned. Do not use them where delivery has to be guaranteed. A malformed pair — a callback that is not callable, or a key that is not a request type — is reported as a `PostDispatchHookRegistrationProblem` and stops the rest of the list from registering, leaving the library `FLAWED`. Pairs registered before the bad one stay active, and are still removed when the library unloads. ## Registering node types without listing them in the manifest If your node set is data-driven, generated, or simply large enough that hand-maintaining the manifest is a chore, you can leave `"nodes": []` in the manifest and synthesize the definitions in `before_library_nodes_loaded`. This works because of step 6 and step 7 in [the load sequence](#the-load-sequence): the hook runs first, `library_data` is the same object the engine reads next, and `library_data.nodes` is an ordinary list. ``` class MyLibrary(AdvancedNodeLibrary): def before_library_nodes_loaded(self, library_data, library) -> None: library_data.nodes.extend( NodeDefinition( class_name=spec["class_name"], file_path="generated_nodes.py", metadata=NodeMetadata( category="dynamic", description=spec["description"], display_name=spec["display_name"], ), ) for spec in load_specs() ) ``` Nothing the editor reads comes from the manifest's `nodes` list. `ListNodeTypesInLibrary` and `GetAllInfoForLibrary` both read the in-memory `Library`, so synthesized node types appear in the node palette exactly like declared ones. Categories are still read from the manifest, so declare every category you intend to synthesize into. Nothing validates a node's category against the declared keys, so a mismatch fails quietly: the node type registers fine, but the editor has no category to file it under. Definitions added this way are indistinguishable from hand-written ones, which means they inherit the loader's behavior: lazy module loading, one memoized import per file even when many classes share it, stable-namespace aliasing so saved workflows can unpickle values your classes define, per-node problem reporting, and correct fitness. ### Where the classes come from The engine resolves a node type by importing the file in `NodeDefinition.file_path` and calling `getattr(module, class_name)`. A module-level `__getattr__` ([PEP 562](https://peps.python.org/pep-0562/)) satisfies that, so one file can back every node type in the library without a single `class` statement: ``` def __getattr__(name: str) -> type[DataNode]: spec = find_spec(name) if spec is None: msg = f"module {__name__!r} has no attribute {name!r}" raise AttributeError(msg) node_class = build_node_class(spec) globals()[name] = node_class # cache: next lookup skips __getattr__ return node_class ``` Cache the built class in module globals. Two lookups of the same node type must return the same object, because the engine caches the resolved class, `isinstance` checks compare against it, and pickles reference it. Set `__module__` explicitly when you build a class `type(name, bases, namespace)` does not give you the current module for free. With no `__module__` key in the namespace, class creation reads `__name__` from the calling frame's globals. Because `BaseNode` subclasses carry `ABCMeta`, that frame is inside the standard library's `abc` module, and your class ends up claiming `__module__ == "abc"`. Nothing complains at load time. The failure appears later: unpickling a saved workflow imports `__module__` and looks up `__qualname__` on it, so any workflow carrying a value your class defines will fail to reopen. Pass both explicitly: ``` return type( spec["class_name"], (DataNode,), { "__init__": __init__, "process": process, "__module__": __name__, "__qualname__": spec["class_name"], }, ) ``` ### Why not register classes directly? `Library.register_new_node_type()` and `Library.register_lazy_node_type()` are public, and calling them from `after_library_nodes_loaded` also registers working node types. Prefer synthesizing definitions anyway, for two reasons: - **Fitness.** The engine decides whether a library loaded successfully from the manifest-driven loop in step 7. A library with `"nodes": []` that registers everything in the after hook is scored `UNUSABLE` and its registration is reported as a failure, even though the node types registered fine. - **Stable namespaces.** The loader also registers a pending stable-module loader so `griptape_nodes.node_libraries..` resolves when a saved workflow reopens. Registering a class directly skips that, and you own the problem. ### Limitations - **Isolated (worker) libraries are not supported.** When a library runs in a worker subprocess, the orchestrator rebuilds stub classes from schemas the worker sends back, and it resolves each stub's metadata from the manifest's `nodes` list. Node types that exist only in the worker's registry are dropped with a warning. Keep dynamically registered libraries in the orchestrator, or list their nodes in the manifest. - **Declaration validation only sees the manifest.** Validation of `model_usage` and `model_provider_usage` references runs against the manifest read from disk, so those declarations on synthesized nodes are never checked. A bad model reference fails at runtime instead of at load. - **Node name collisions still apply.** Synthesized class names go through the same cross-library collision check as declared ones, and generated names collide just as easily. Prefix them. ## Examples Two complete, working libraries. Copy either folder into your workspace's `libraries` directory, register it through the editor's library settings, and restart the engine. ### Example: a library that serves a request type A library that owns `ConvertColorspaceRequest`, serves it from its advanced library, and consumes it from its own node: - [`griptape_nodes_library.json`](https://docs.griptapenodes.com/development/custom_nodes/example_request_handler_library/griptape_nodes_library.json): manifest declaring one node and the advanced library - [`colorspace_events.py`](https://docs.griptapenodes.com/development/custom_nodes/example_request_handler_library/colorspace_events.py): the request and result payloads, registered with `PayloadRegistry` - [`advanced_library.py`](https://docs.griptapenodes.com/development/custom_nodes/example_request_handler_library/advanced_library.py): returns the handler from `get_request_handlers` and implements it - [`nodes.py`](https://docs.griptapenodes.com/development/custom_nodes/example_request_handler_library/nodes.py): a node that dispatches the request from `process()` and handles failure Add a **Convert Colorspace** node, set `color` to `[0, 0.5, 1]` with `source` `rgb` and `target` `hsv`, and run it. Any other library in the same engine can now send `ConvertColorspaceRequest` and get the same answer. ### Example: a library that registers node types dynamically A library that registers four node types from a JSON file while declaring none in its manifest: - [`griptape_nodes_library.json`](https://docs.griptapenodes.com/development/custom_nodes/example_dynamic_library/griptape_nodes_library.json): manifest with `"nodes": []` and one declared category - [`node_specs.json`](https://docs.griptapenodes.com/development/custom_nodes/example_dynamic_library/node_specs.json): the data the node set is derived from - [`advanced_library.py`](https://docs.griptapenodes.com/development/custom_nodes/example_dynamic_library/advanced_library.py): synthesizes one `NodeDefinition` per spec in `before_library_nodes_loaded` - [`generated_nodes.py`](https://docs.griptapenodes.com/development/custom_nodes/example_dynamic_library/generated_nodes.py): module `__getattr__` that builds each `DataNode` subclass on demand You should see a **Dynamic** category with four nodes. Add an entry to `node_specs.json` reusing an existing `operator` value, restart, and a fifth node appears with no change to the manifest or the Python. # Custom Widgets Nodes can use custom JavaScript widget components to provide rich, interactive UI beyond the standard parameter controls. Widgets are standalone `.js` files that render into a container element and communicate value changes back to the framework via a callback. ## Widget Architecture A custom widget involves three pieces: 1. **Widget JS file** (`widgets/MyWidget.js`) — the UI component 1. **Node Python file** — references the widget via the `Widget` trait on a parameter 1. **Library JSON** (`griptape_nodes_library.json`) — registers the widget so the framework can find it ``` library_name/ ├── griptape_nodes_library.json ├── my_node.py └── widgets/ └── MyWidget.js ``` ## Registering a Widget Add a `"widgets"` array to `griptape_nodes_library.json`: ``` { "name": "My Library", "widgets": [ { "name": "MyWidget", "path": "widgets/MyWidget.js", "description": "Description of the widget" } ], "nodes": [ ... ] } ``` The `name` must match the name used in the `Widget` trait on the Python side, and the `library` argument must match the `"name"` field at the top level of the JSON. ## Attaching a Widget to a Parameter Use the `Widget` trait to bind a parameter to your custom widget. The parameter's value is passed to the widget as `props.value`, and changes flow back through `props.onChange`: ``` from griptape_nodes.exe_types.core_types import Parameter, ParameterMode from griptape_nodes.traits.widget import Widget self.add_parameter( Parameter( name="my_data", input_types=["list"], type="list", output_type="list", default_value=[], tooltip="Data managed by custom widget", allowed_modes={ParameterMode.PROPERTY, ParameterMode.OUTPUT}, traits={Widget(name="MyWidget", library="My Library")}, ) ) ``` ## Widget JS Function Signature Widgets are ES module default exports. The function receives a container DOM element and a props object, and must return a cleanup function: ``` export default function MyWidget(container, props) { const { value, onChange, disabled, height } = props; // Build your UI inside `container` // Call `onChange(newValue)` when the user changes data // Respect `disabled` to prevent interaction when appropriate // Return a cleanup function return () => { // Remove event listeners, dispose resources }; } ``` **Props:** | Prop | Type | Description | | ---------- | ---------- | ------------------------------------------------ | | `value` | `any` | Current parameter value (matches Python default) | | `onChange` | `function` | Callback to send updated value to the framework | | `disabled` | `boolean` | Whether the widget should be read-only | | `height` | `number` | Suggested height in pixels (may be 0 or absent) | ## Critical Patterns and Pitfalls ### Emit Changes Sparingly — Not on Every Keystroke Calling `onChange` triggers framework state updates that steal focus from the active element. For text inputs, this means the textarea loses focus after every keystroke, making typing impossible. This is not specific to custom widgets — the built-in `TextComponent` in the Griptape Nodes editor uses the same pattern: - **Local state** (your internal data array, counters, border colors) updates immediately on every `input` event. - **`onChange`** is called only on `blur` — when the user clicks away or tabs out of the field. - **Discrete controls** (buttons, steppers, drag-end) call `onChange` immediately since they don't hold focus. ``` // Local state updates on every keystroke — UI stays responsive textarea.addEventListener("input", (e) => { localData[index].text = e.target.value; // Update character counters, border colors, etc. here }); // Emit to framework only when the user leaves the field textarea.addEventListener("blur", () => { localData[index].text = textarea.value; onChange(structuredClone(localData)); }); // Discrete controls (buttons, steppers) can emit immediately button.addEventListener("pointerdown", (e) => { e.stopPropagation(); localData[index].value++; onChange(structuredClone(localData)); render(); }); ``` > **Why not `requestAnimationFrame` to restore focus?** Attempting to call `onChange` on every keystroke and then restore focus via `requestAnimationFrame` does not reliably work — the framework's React rendering cycle can complete asynchronously, and the focus restoration races with it. ### Prevent Node Drag Interference Node canvases handle drag events for panning and node movement. Interactive elements inside your widget must stop event propagation and use the `nodrag` / `nowheel` CSS classes: ``` // On the outermost wrapper const wrapper = document.createElement("div"); wrapper.className = "my-widget nodrag nowheel"; // On interactive child elements (textareas, sliders, etc.) textarea.addEventListener("pointerdown", (e) => e.stopPropagation()); textarea.addEventListener("mousedown", (e) => e.stopPropagation()); ``` ### Prevent Keyboard Shortcut Interference The node editor binds keyboard shortcuts at the canvas level — for example, pressing `Delete` deletes the selected node. When a text input inside your widget has focus, these shortcuts still fire because keyboard events bubble up. Stop propagation on `keydown` to isolate your text inputs: ``` textarea.addEventListener("keydown", (e) => e.stopPropagation()); ``` This prevents the Delete key from deleting the node while the user is editing text, and stops other canvas-level shortcuts (copy, paste, undo at the node level) from interfering with normal text editing. ### Override `user-select: none` for Text Inputs Widget wrappers typically set `user-select: none` to prevent accidental text selection during drag operations. This cascades into child elements and blocks textarea editing. Override it explicitly: ``` textarea { user-select: text; -webkit-user-select: text; } ``` ### Clone Values Before Emitting Always pass a fresh copy to `onChange` — not a reference to your internal state. Otherwise the framework and your widget share the same object, leading to subtle bugs: ``` onChange(localData.map((item) => ({ ...item }))); ``` ### Clean Up Document-Level Listeners If you attach listeners to `document` (e.g., for drag-and-drop or click-outside-to-close), remove them in the cleanup function: ``` document.addEventListener("pointerdown", onDocumentClick, true); return () => { document.removeEventListener("pointerdown", onDocumentClick, true); }; ``` ### Assign Stable IDs to List Items When building widgets that manage reorderable lists (e.g., drag-and-drop shot editors), give each item a unique ID that is decoupled from array index. Without stable IDs, item attributes such as text field contents can be lost during drag-and-drop reordering because the widget re-renders from scratch and identity was tied to position. ``` let nextItemId = 1; function assignId(item) { if (!item.id) { item.id = `item-${nextItemId++}`; } else { const num = parseInt(item.id.replace("item-", ""), 10); if (!isNaN(num) && num >= nextItemId) { nextItemId = num + 1; } } return item; } // On initialization — preserve existing IDs from saved data let items = value.map((v) => assignId({ ...v })); // When adding new items items.push(assignId({ name: "New Item", text: "" })); ``` The ID persists through reorders, re-renders, and round-trips via `onChange`. The display name (e.g., "Shot1", "Shot2") can be renumbered based on visual position while the `id` remains stable. ### Handle the `disabled` Attribute Correctly in DOM Helpers If you write a DOM helper function that creates elements from an attributes object, be careful with the `disabled` attribute. Using `setAttribute("disabled", false)` does **not** remove the disabled state — the presence of the attribute in any form disables the element. Use the property instead: ``` if (key === "disabled") { element.disabled = !!val; } ``` ### Show Drop Indicators at End of List When implementing drag-and-drop reordering, the drop target indicator (e.g., a blue border) must also appear when dragging past the last item in the list. A common approach: when the drag position is below all items, show a `border-bottom` on the last item instead of a `border-top` on a nonexistent next item: ``` if (dragOverIndex === items.length && item.index === lastIndex) { item.el.style.borderBottom = "2px solid #4a9eff"; } else if (item.index === dragOverIndex) { item.el.style.borderTop = "2px solid #4a9eff"; } ``` ### Enforce Aggregate Constraints (Min/Max Totals) When list items have numeric values that must sum to within a range (e.g., total duration 3–15 seconds), enforce the constraint in both directions: - **Ceiling:** Disable the increase stepper and "add" button when the total would exceed the maximum. - **Floor:** Disable the decrease stepper when reducing any item would bring the total below the minimum. - **Auto-compensate on delete:** When removing an item would drop the total below the minimum, increase the last remaining item's value to make up the difference. ``` const MIN_TOTAL = 3; const MAX_TOTAL = 15; // Disable decrease if total would go below minimum const wouldGoBelow = totalValue() - 1 < MIN_TOTAL; const canDecrease = !disabled && item.value > MIN_VALUE && !wouldGoBelow; // On delete — auto-compensate to maintain minimum total trash.addEventListener("pointerdown", (e) => { e.stopPropagation(); if (items.length <= 1) return; items.splice(index, 1); const total = totalValue(); if (total < MIN_TOTAL) { const lastItem = items[items.length - 1]; lastItem.value += MIN_TOTAL - total; } emitChange(); render(); }); ``` Show both bounds in the status bar so users understand the valid range: `"8s (3–15s)"`. Highlight in red when outside bounds. ## Example: List-Based Editor Widget A common pattern is a widget that manages a list of structured items with add, delete, reorder, and inline editing. Key implementation details: - **Stable IDs:** Assign a unique `id` to each item that survives reordering and round-trips through `onChange`. - **Drag-and-drop reordering:** Attach `pointerdown` on drag handles, create a floating clone for visual feedback, track the insertion point via `pointermove`, and finalize the reorder on `pointerup`. Call `onChange` only after the drop. Show drop indicators at both middle and end-of-list positions. - **Stepper controls:** For constrained numeric values (e.g., duration 1–15s), use ▲/▼ stepper buttons instead of dropdown menus. Disable buttons when they would violate constraints (min/max per item, min/max total across all items). - **Aggregate constraints:** Enforce both minimum and maximum totals across all items. Auto-compensate on delete to maintain the floor (see [Enforce Aggregate Constraints](#enforce-aggregate-constraints-minmax-totals)). - **Validation constraints:** Enforce limits (max items, max total values, max text length) by disabling the add button and stepper arrows rather than silently ignoring input. - **Status feedback:** Show a small status bar with current counts vs. limits (e.g., `"3 / 6 shots"`, `"8s (3–15s)"`) so users understand the valid range and why controls may be disabled. - **Text input isolation:** Stop propagation on `pointerdown`, `mousedown`, and `keydown` to prevent node drag and keyboard shortcut interference. Emit `onChange` only on `blur`. ``` # Python side — list parameter with widget self.add_parameter( Parameter( name="items", input_types=["list"], type="list", output_type="list", default_value=[{"name": "Item1", "duration": 2, "description": ""}], allowed_modes={ParameterMode.PROPERTY, ParameterMode.OUTPUT}, traits={Widget(name="MyListEditor", library="My Library")}, ) ) ``` ``` // JS side — skeleton for a list editor widget export default function MyListEditor(container, props) { const { value, onChange, disabled } = props; // Stable ID assignment let nextId = 1; function assignId(item) { if (!item.id) item.id = `item-${nextId++}`; return item; } let items = Array.isArray(value) ? value.map((v) => assignId({ ...v })) : [assignId({ name: "Item1", duration: 2, description: "" })]; function emitChange() { if (!disabled && onChange) { onChange(items.map((item) => ({ ...item }))); } } function render() { container.innerHTML = ""; const wrapper = document.createElement("div"); wrapper.className = "nodrag nowheel"; items.forEach((item, index) => { // ... build item row with drag handle, stepper, textarea, trash ... // Text input — local update on input, emit on blur textarea.addEventListener("input", (e) => { items[index].description = e.target.value; }); textarea.addEventListener("blur", () => { items[index].description = textarea.value; emitChange(); }); // Isolate text input from node-level events textarea.addEventListener("pointerdown", (e) => e.stopPropagation()); textarea.addEventListener("mousedown", (e) => e.stopPropagation()); textarea.addEventListener("keydown", (e) => e.stopPropagation()); }); container.appendChild(wrapper); } render(); return () => { /* cleanup document-level listeners */ }; } ``` ## Widget Testbed The **widget-testbed** is a standalone React + Vite application for testing and developing custom widget components outside the full Griptape Nodes environment. It provides a lightweight, hot-reloading development environment where you can iterate quickly on widget UI and behavior. ### Purpose Custom widgets for Griptape Nodes are imperative JavaScript functions that manage their own DOM and state. The widget-testbed allows you to: - **Rapid prototyping**: Test widget behavior with instant hot-reload during development - **Isolated testing**: Work on widget UI/UX without launching the full Griptape Nodes application - **State management verification**: Test complex state transitions and user interactions - **Cross-widget development**: Easily switch between testing different widgets - **Debug UI issues**: Inspect the widget's rendered output and state in a clean environment ### When to Use Use the widget-testbed when: - Creating a new custom widget component from scratch - Debugging widget behavior issues (focus loss, drag-and-drop, event handling) - Testing widget state management and `onChange` callback patterns - Verifying widget appearance and layout without node editor interference - Developing widgets that manage complex internal state (lists, editors, multi-step forms) ### File Structure ``` widget-testbed/ ├── index.html # Entry HTML with minimal styling ├── package.json # Dependencies (React 19, Vite 6) ├── vite.config.js # Vite configuration with React plugin ├── src/ │ ├── main.jsx # React app entry point │ ├── App.jsx # Main test harness with controls │ └── WidgetHost.jsx # React wrapper for imperative widgets └── node_modules/ # Dependencies ``` ### Key Components #### WidgetHost.jsx The `WidgetHost` component is a React wrapper that hosts imperative widget functions using the same `(container, props)` signature as Griptape Nodes widgets. It handles the lifecycle of mounting, updating, and unmounting widgets while preventing unnecessary re-renders. **Key features:** - **Imperative widget support**: Calls your widget function with a container element and props - **Smart re-mounting**: Only re-mounts the widget when value changes externally (e.g., Reset button) - **onChange differentiation**: Tracks whether changes originated from the widget or parent - **Cleanup management**: Properly calls widget cleanup functions on unmount or re-mount **Props:** | Prop | Type | Description | | ---------- | ---------- | ----------------------------------------------------------- | | `widgetFn` | `function` | The widget function to render (container, props) => cleanup | | `value` | `any` | Current widget value | | `onChange` | `function` | Callback when widget emits changes | | `disabled` | `boolean` | Whether widget should be read-only (default: false) | | `height` | `number` | Suggested height in pixels (default: 0) | **Implementation pattern:** ``` import WidgetHost from "./WidgetHost"; import MyWidget from "../../path/to/widgets/MyWidget.js"; export default function App() { const [value, setValue] = useState(initialValue); const [disabled, setDisabled] = useState(false); return ( ); } ``` #### App.jsx The main test harness that provides: - **Widget mounting**: Imports and renders the widget via `WidgetHost` - **State controls**: Checkbox to toggle disabled state - **Debug panel**: JSON view of current widget state (toggle with checkbox) - **Reset functionality**: Button to reset widget to initial state - **Visual layout**: Clean, dark-themed UI matching Griptape Nodes aesthetic ### Testing a Widget **1. Install dependencies:** ``` cd widget-testbed npm install ``` **2. Update App.jsx to import your widget:** ``` import MyWidget from "../../my-library/widgets/MyWidget.js"; const INITIAL_VALUE = { /* your initial state */ }; export default function App() { const [value, setValue] = useState(INITIAL_VALUE); const [disabled, setDisabled] = useState(false); const [showDebug, setShowDebug] = useState(true); return (

MyWidget Testbed

{showDebug && (
          {JSON.stringify(value, null, 2)}
        
)}
); } ``` **3. Start the development server:** ``` npm run dev ``` **4. Open in browser:** Navigate to `http://localhost:5173` (or the port shown in terminal). ### Development Workflow **Typical development cycle:** 1. **Write widget code**: Create or modify your widget `.js` file 1. **Update testbed**: Import the widget in `App.jsx` 1. **Run dev server**: `npm run dev` for hot-reload 1. **Test interactions**: Click, type, drag, and interact with the widget 1. **Verify state**: Check the JSON debug panel to see state changes 1. **Test edge cases**: Use Reset button and Disabled toggle to test edge cases 1. **Iterate**: Make changes to widget code and see updates instantly **Common testing scenarios:** - **Focus management**: Type in text fields, ensure focus isn't lost on `onChange` - **Drag-and-drop**: Test reordering, ensure item identity is preserved - **State transitions**: Add/remove items, verify correct state updates - **Disabled mode**: Toggle disabled, ensure widget becomes read-only - **External state changes**: Use Reset button to verify widget handles external updates - **Event propagation**: Ensure clicks/drags don't interfere with parent (use `nodrag` class) - **Keyboard shortcuts**: Test that Delete, Ctrl+C, etc. don't trigger node-level actions ### WidgetHost Pattern Details The `WidgetHost` component solves a critical problem: **preventing unnecessary widget re-mounts when the widget itself triggers `onChange`**. Without this, the widget would be destroyed and recreated on every keystroke, losing focus and internal state. **How it works:** 1. **Flag-based change tracking**: `isWidgetChangeRef` tracks whether the current change originated from the widget 1. **Conditional re-mount**: Widget is only re-mounted when `value` changes externally (not from `onChange`) 1. **Stable onChange callback**: Uses `useCallback` to prevent unnecessary effect triggers 1. **Cleanup on unmount**: Calls widget's cleanup function when widget is destroyed or value changes externally **Key implementation:** ``` const isWidgetChangeRef = useRef(false); const stableOnChange = useCallback( (newValue) => { isWidgetChangeRef.current = true; // Mark as widget-originated change onChange?.(newValue); }, [onChange], ); useEffect(() => { if (isWidgetChangeRef.current) { isWidgetChangeRef.current = false; // Clear flag and skip re-mount return; } // External value change: re-mount widget const cleanup = widgetFn(container, { value, onChange: stableOnChange, disabled, height }); return cleanup; }, [widgetFn, value, disabled, height, stableOnChange]); ``` This pattern ensures the widget maintains its internal DOM and state across `onChange` calls, preventing focus loss and other re-mount issues. ### Best Practices **When using the widget-testbed:** - **Match production props**: Use the same prop names (`value`, `onChange`, `disabled`, `height`) as Griptape Nodes - **Test disabled state**: Always verify your widget respects the `disabled` prop - **Verify cleanup**: Check that your widget's cleanup function properly removes event listeners - **Test edge cases**: Use the Reset button to test how your widget handles external value changes - **Inspect state**: Keep the JSON debug panel visible to understand state flow - **Test keyboard events**: Ensure `stopPropagation` on `keydown` prevents node-level shortcuts - **Test mouse events**: Ensure `stopPropagation` on `pointerdown`/`mousedown` prevents node dragging - **Verify cloning**: Check that `onChange` receives cloned data, not references to internal state **Don't:** - Don't commit `widget-testbed/` to your library repository (it's a development tool) - Don't test production-specific features (node connections, workflow execution) - Don't assume testbed behavior matches production exactly (always final-test in Griptape Nodes) ### Example: MultiShotEditor Testbed The current testbed configuration tests the `MultiShotEditor` widget from the Kling library: ``` import MultiShotEditor from "../../kling/widgets/MultiShotEditor.js"; const INITIAL_SHOTS = [{ name: "Shot1", duration: 2, description: "" }]; export default function App() { const [shots, setShots] = useState(INITIAL_SHOTS); // ... controls and debug UI ... return ( ); } ``` This demonstrates the testbed pattern for a complex widget managing an array of shot objects with drag-and-drop reordering, add/remove functionality, and multiple text inputs. # Patterns and Examples A collection of advanced patterns and reference material: API integration approaches, UI/UX patterns from production nodes, flexible artifact processing, and quick-reference tables for imports, utilities, and types. ## Advanced Topics ### REST API vs SDK Integration **Problem**: Python SDKs often lag behind REST APIs in supporting new features. Parameters available in REST API documentation may not be exposed in SDK libraries. **When to Use REST API Directly**: - SDK missing documented API features (e.g., `image_config` for Gemini) - Need immediate access to new API parameters - SDK has bugs or limitations - Want lighter dependencies **REST API Implementation Pattern**: ``` import base64 import requests from google.oauth2 import service_account from google.auth.transport.requests import Request # Authentication credentials = service_account.Credentials.from_service_account_file( service_account_file, scopes=["https://www.googleapis.com/auth/cloud-platform"] ) def _get_access_token(self, credentials) -> str: """Get access token from credentials.""" if not credentials.valid: credentials.refresh(Request()) return credentials.token # Build JSON payload matching REST API spec payload = { "contents": { "role": "USER", "parts": [ {"text": prompt}, {"inline_data": {"mime_type": "image/jpeg", "data": base64.b64encode(image_bytes).decode("utf-8")}}, ], }, "generation_config": { "temperature": 1.0, "topP": 0.95, "candidateCount": 1, "response_modalities": ["TEXT", "IMAGE"], "image_config": { # Feature not in SDK! "aspect_ratio": "16:9" }, }, } # Make authenticated request access_token = self._get_access_token(credentials) headers = {"Authorization": f"Bearer {access_token}", "Content-Type": "application/json"} api_endpoint = f"https://{location}-aiplatform.googleapis.com/v1/projects/{project_id}/locations/{location}/publishers/google/models/{model}:generateContent" response = requests.post(api_endpoint, headers=headers, json=payload, timeout=120) response.raise_for_status() response_data = response.json() # Parse JSON response (handle both camelCase and snake_case) candidates = response_data.get("candidates", []) for cand in candidates: parts_list = cand.get("content", {}).get("parts", []) for part in parts_list: if "inlineData" in part or "inline_data" in part: inline_data = part.get("inlineData") or part.get("inline_data", {}) mime = inline_data.get("mimeType") or inline_data.get("mime_type") data_b64 = inline_data.get("data", "") data_bytes = base64.b64decode(data_b64) ``` **Key Considerations**: 1. **Dependencies**: Use `google-auth` instead of full SDK (`google-cloud-aiplatform`, `google-genai`) 1. **Regional Availability**: Some models only work in specific regions (e.g., `us-central1`), not `global` 1. **Model Names**: Check for `-preview` suffix differences between preview and stable models 1. **Authentication Scopes**: Use `https://www.googleapis.com/auth/cloud-platform` for Vertex AI 1. **Response Format**: Handle both camelCase (API) and snake_case (some SDKs) field names 1. **Base64 Encoding**: REST API expects base64-encoded strings for binary data 1. **Error Handling**: Parse JSON error responses for detailed error messages **Trade-offs**: - ✅ Immediate access to all API features - ✅ Lighter dependencies - ✅ Full control over requests - ❌ More implementation work - ❌ Must handle auth/tokens manually - ❌ Need to track API changes yourself ### Complex Type Management Systems For nodes that need sophisticated type negotiation between multiple parameters: ``` class IfElse(BaseNode): def __init__(self, name: str, metadata: dict[Any, Any] | None = None) -> None: super().__init__(name, metadata) # Sophisticated connection tracking for type management self._possibility_space: list[str] = [] # Types acceptable to output target self._locked_type: str | None = None # Specific type locked by input self._connected_inputs: set[str] = set() # Track input connections self._output_connected: bool = False # Track output connections def _update_parameter_types(self) -> None: """Update all parameter types based on current state.""" if self._locked_type: # Locked to specific type - everything uses that type self.output_if_true.input_types = [self._locked_type] self.output_if_false.input_types = [self._locked_type] self.output.output_type = self._locked_type elif self._possibility_space: # Flexible within possibility space self.output_if_true.input_types = self._possibility_space.copy() self.output_if_false.input_types = self._possibility_space.copy() self.output.output_type = ParameterTypeBuiltin.ALL.value else: # Default state - accept any type self.output_if_true.input_types = ["any"] self.output_if_false.input_types = ["any"] self.output.output_type = ParameterTypeBuiltin.ALL.value ``` **Best Practice**: Use sophisticated type management for nodes that route data between multiple inputs and outputs. ### Agentic Nodes Inherit from ControlNode for agent management: ``` from griptape.structures import Agent from griptape_nodes.exe_types.node_types import ControlNode class MyAgentNode(ControlNode): def __init__(self, **kwargs): super().__init__(**kwargs) self.add_parameter(Parameter(name="agent_in", input_types=["Agent"], type="Agent")) self.add_parameter(Parameter(name="agent_out", output_type="Agent")) def process(self) -> None: agent_state = self.get_parameter_value("agent_in") agent = Agent.from_dict(agent_state) if agent_state else Agent() # Process with agent self.parameter_output_values["agent_out"] = agent.to_dict() ``` ### Abstract Base Classes for Node Families Create abstract base classes for related nodes to share common functionality: ``` from abc import abstractmethod from typing import Any from griptape_nodes.exe_types.base_iterative_nodes import BaseIterativeStartNode class BaseCustomIterativeStartNode(BaseIterativeStartNode): """Base class for a family of custom iterative start nodes.""" @abstractmethod def _get_compatible_end_classes(self) -> set[type]: """Return the set of End node classes that this Start node can connect to.""" @abstractmethod def _get_parameter_group_name(self) -> str: """Return the name for the parameter group containing iteration data.""" @abstractmethod def _get_exec_out_display_name(self) -> str: """Return the display name for the exec_out parameter.""" @abstractmethod def _get_exec_out_tooltip(self) -> str: """Return the tooltip for the exec_out parameter.""" @abstractmethod def _get_iteration_items(self) -> list[Any]: """Get the list of items to iterate over.""" @abstractmethod def is_loop_finished(self) -> bool: """Return True if the loop has completed all iterations.""" ``` **Best Practice**: Use abstract base classes to share common logic across node families while enforcing implementation of specific methods. ### Caching Use ClassVar for shared resources: ``` from typing import ClassVar, Any class CachedModelNode(DataNode): _cache: ClassVar[dict[str, Any]] = {} def get_model(self, model_id: str) -> Any: if model_id not in self._cache: self._cache[model_id] = load_model(model_id) return self._cache[model_id] ``` ### Hub Integration (e.g., HuggingFace) ``` # Gated model detection is_gated = getattr(model, "gated", False) model_dict["gated"] = is_gated # Status updates for gated models if getattr(model_info, "gated", False): self.publish_update_to_parameter("status", "🔒 GATED MODEL - May require approval") ``` ### ParameterMessage for External Links and Status Updates ``` from griptape_nodes.exe_types.core_types import ParameterMessage # External link example ParameterMessage( name="model_card_link", title="Model Card", variant="info", value="View model documentation", button_link=f"https://huggingface.co/{model_id}", button_text="View on HuggingFace", ) # Dynamic status message example class MyIterativeNode(BaseIterativeStartNode): def __init__(self, name: str, metadata: dict[Any, Any] | None = None) -> None: super().__init__(name, metadata) # Status message parameter for real-time updates self.status_message = ParameterMessage( name="status_message", variant="info", value="", ) self.add_node_element(self.status_message) def _update_status_message(self, status_type: str = "normal") -> None: """Update status message based on current state.""" if self._total_iterations == 0: status = "Completed 0 (of 0)" elif status_type == "break": status = f"Stopped at {self._current_iteration_count} (of {self._total_iterations}) - Break" elif self.is_loop_finished(): status = f"Completed {self._total_iterations} (of {self._total_iterations})" else: status = f"Processing {self._current_iteration_count} (of {self._total_iterations})" self.status_message.value = status ``` **Best Practice**: Use ParameterMessage for static external links, dynamic status updates, and deprecation notices. For a full walkthrough of the deprecation notice pattern (hidden message + `before_value_set` auto-migration), see [Deprecated Model Migration and User Notification](https://docs.griptapenodes.com/development/custom_nodes/execution_and_lifecycle/#deprecated-model-migration-and-user-notification). ## Modern UI/UX Patterns ### UI Options The supported `ui_options` keys, the widget each parameter type renders, and the traits that manage them are documented authoritatively in the [Parameter UI Reference](https://docs.griptapenodes.com/development/custom_nodes/parameter_ui_reference/index.md). Keys not listed there are editor-internal and may change without notice. #### Hidden Parameters Best Practice Use `"hide": True` to hide parameters from the UI (for advanced/expert settings): ``` num_images_param = Parameter( name="num_images", input_types=["int"], type="int", default_value=1, tooltip="Number of images to generate (1-9)", allowed_modes={ParameterMode.INPUT, ParameterMode.PROPERTY}, ui_options={ "display_name": "Number of Images", "hide": True, }, ) num_images_param.add_trait(Slider(min_val=1, max_val=9)) self.add_parameter(num_images_param) ``` A legacy `"hidden": True` key also exists in a few older nodes; prefer `"hide": True`. **Common Use Cases for Hidden Parameters:** - Advanced/expert configuration options - Internal control signals - Debug parameters - Optional advanced features - Parameters that should only be set programmatically ### Success/Failure Node Pattern For nodes that can succeed or fail, inherit from SuccessFailureNode: ``` from griptape_nodes.exe_types.node_types import SuccessFailureNode class LoadImage(SuccessFailureNode): def __init__(self, **kwargs) -> None: super().__init__(**kwargs) # Add status parameters using the helper method self._create_status_parameters( result_details_tooltip="Details about the image loading operation result", result_details_placeholder="Details on the load attempt will be presented here.", ) def process(self) -> None: # Reset execution state at start self._clear_execution_status() # Clear output values to prevent stale data on errors self.parameter_output_values["image"] = None try: # Processing logic here result = load_image() self.parameter_output_values["image"] = result # Success case success_details = f"Image loaded successfully from {source}" self._set_status_results(was_successful=True, result_details=f"SUCCESS: {success_details}") except Exception as e: error_details = f"Failed to load image: {e}" self._set_status_results(was_successful=False, result_details=f"FAILURE: {error_details}") self._handle_failure_exception(e) ``` **Best Practice**: Use SuccessFailureNode for operations that can fail and need to report status to users. ### Parameter Initialization Initialize parameter visibility on node creation: ``` def _initialize_parameter_visibility(self) -> None: """Initialize parameter visibility based on default values.""" default_model = self.get_parameter_value("model") or "default" if default_model == "text-only": self.hide_parameter_by_name("image_input") else: self.show_parameter_by_name("image_input") ``` ### Artifact Path Tethering Pattern For nodes that work with files, use the artifact tethering pattern to keep path and artifact parameters synchronized: ``` from griptape_nodes_library.utils.artifact_path_tethering import ( ArtifactPathTethering, ArtifactTetheringConfig, ) class LoadImage(SuccessFailureNode): def __init__(self, **kwargs) -> None: super().__init__(**kwargs) # Configuration for artifact tethering self._tethering_config = ArtifactTetheringConfig( dict_to_artifact_func=dict_to_image_url_artifact, extract_url_func=self._extract_url_from_image_value, supported_extensions=self.SUPPORTED_EXTENSIONS, default_extension="png", url_content_type_prefix="image/", ) # Create artifact parameter self.image_parameter = Parameter( name="image", input_types=["ImageUrlArtifact", "ImageArtifact", "str"], type="ImageUrlArtifact", output_type="ImageUrlArtifact", ui_options={"clickable_file_browser": True}, ) # Create path parameter using tethering utility self.path_parameter = ArtifactPathTethering.create_path_parameter( name="path", config=self._tethering_config, display_name="File Path or URL", ) # Tethering helper keeps parameters in sync self._tethering = ArtifactPathTethering( node=self, artifact_parameter=self.image_parameter, path_parameter=self.path_parameter, config=self._tethering_config, ) def after_value_set(self, parameter: Parameter, value: Any) -> None: # Delegate tethering logic to helper self._tethering.on_after_value_set(parameter, value) return super().after_value_set(parameter, value) ``` **Best Practice**: Use artifact tethering for seamless file/URL parameter synchronization. ## Two-Mode UI Pattern (Simple + Custom) **Use Case**: Create beginner-friendly nodes while offering advanced control for power users. **Example**: Music/video generation APIs often have "simple description" mode and "detailed control" mode. ### Implementation Pattern ``` class GenerativeNode(DataNode): def __init__(self, **kwargs) -> None: super().__init__(**kwargs) # Mode selector mode_param = Parameter( name="custom_mode", input_types=["bool"], type="bool", default_value=False, tooltip="Custom Mode: Full control. Simple Mode: Auto-generate from prompt.", allowed_modes={ParameterMode.INPUT, ParameterMode.PROPERTY}, ui_options={"display_name": "Custom Mode"}, ) self.add_parameter(mode_param) # Prompt (meaning changes by mode) prompt_param = Parameter( name="prompt", input_types=["str"], type="str", default_value="", tooltip=[ {"type": "text", "text": "Custom Mode: Exact lyrics/script"}, {"type": "text", "text": "Simple Mode: General description"}, ], allowed_modes={ParameterMode.INPUT, ParameterMode.PROPERTY}, ui_options={"multiline": True, "display_name": "Prompt"}, ) self.add_parameter(prompt_param) # Advanced parameters (custom mode only) style_param = Parameter( name="style", input_types=["str"], type="str", default_value="", tooltip="Style/genre (Custom Mode only)", allowed_modes={ParameterMode.INPUT, ParameterMode.PROPERTY}, ui_options={"hide": True}, # Hidden by default ) self.add_parameter(style_param) title_param = Parameter( name="title", input_types=["str"], type="str", default_value="", tooltip="Title (Custom Mode only)", allowed_modes={ParameterMode.INPUT, ParameterMode.PROPERTY}, ui_options={"hide": True}, # Hidden by default ) self.add_parameter(title_param) # Initialize visibility self._initialize_parameter_visibility() def _initialize_parameter_visibility(self) -> None: """Initialize parameter visibility based on default mode.""" custom_mode = self.get_parameter_value("custom_mode") or False if custom_mode: self.show_parameter_by_name("style") self.show_parameter_by_name("title") else: self.hide_parameter_by_name("style") self.hide_parameter_by_name("title") def after_value_set(self, parameter: Parameter, value: Any) -> None: """Update UI based on mode selection.""" if parameter.name == "custom_mode": if value: self.show_parameter_by_name("style") self.show_parameter_by_name("title") else: self.hide_parameter_by_name("style") self.hide_parameter_by_name("title") return super().after_value_set(parameter, value) def validate_before_node_run(self) -> list[Exception] | None: """Validate based on selected mode.""" exceptions = [] custom_mode = self.get_parameter_value("custom_mode") if custom_mode: # Custom mode requires style and title style = self.get_parameter_value("style") or "" title = self.get_parameter_value("title") or "" if not style.strip(): exceptions.append(ValueError(f"{self.name}: Style required in Custom Mode")) if not title.strip(): exceptions.append(ValueError(f"{self.name}: Title required in Custom Mode")) else: # Simple mode just needs prompt prompt = self.get_parameter_value("prompt") or "" if not prompt.strip(): exceptions.append(ValueError(f"{self.name}: Prompt required in Simple Mode")) return exceptions if exceptions else None ``` **Best Practice for First-Time Users**: Default to Simple Mode with recommendation in documentation: ``` ### Getting Started #### Simple Mode (Recommended for First-Time Users) 1. Leave "Custom Mode" unchecked 2. Enter a description: "A calm piano melody" 3. Run! #### Custom Mode (Advanced) 1. Check "Custom Mode" 2. Fill in style, title, and detailed prompt 3. Fine-tune advanced parameters ``` ## Music/Audio Generation API Patterns ### Character Limits by Model Many generation APIs have model-specific character limits. Store limits as class constants: ``` class MusicGenerationNode(DataNode): # Prompt length limits by model PROMPT_LIMITS_CUSTOM = { "V3_5": 3000, "V4": 3000, "V4_5": 5000, "V5": 5000, } PROMPT_LIMIT_SIMPLE = 500 # Style length limits by model STYLE_LIMITS = { "V3_5": 200, "V4": 200, "V4_5": 1000, "V5": 1000, } TITLE_LIMIT = 80 def validate_before_node_run(self) -> list[Exception] | None: """Validate with model-specific limits.""" exceptions = [] model = self.get_parameter_value("model") custom_mode = self.get_parameter_value("custom_mode") if custom_mode: prompt = self.get_parameter_value("prompt") or "" prompt_limit = self.PROMPT_LIMITS_CUSTOM.get(model, 3000) if len(prompt) > prompt_limit: exceptions.append( ValueError( f"{self.name}: Prompt exceeds {prompt_limit} character limit for {model} " f"(current: {len(prompt)} characters)" ) ) style = self.get_parameter_value("style") or "" style_limit = self.STYLE_LIMITS.get(model, 200) if len(style) > style_limit: exceptions.append( ValueError( f"{self.name}: Style exceeds {style_limit} character limit for {model} " f"(current: {len(style)} characters)" ) ) return exceptions if exceptions else None ``` ### Model Selection with Detailed Tooltips Use list of dict tooltip format for model comparison: ``` model_param = Parameter( name="model", input_types=["str"], type="str", default_value="V5", tooltip=[ {"type": "text", "text": "Model version for generation:"}, {"type": "text", "text": "• V5: Superior quality, fastest (4 min max)"}, {"type": "text", "text": "• V4_5PLUS: Richest sound, up to 8 min"}, {"type": "text", "text": "• V4_5: Superior blending, up to 8 min"}, {"type": "text", "text": "• V4: Best quality, refined structure (4 min)"}, {"type": "text", "text": "• V3_5: Creative diversity (4 min)"}, ], allowed_modes={ParameterMode.INPUT, ParameterMode.PROPERTY}, ) model_param.add_trait(Options(choices=["V5", "V4_5PLUS", "V4_5", "V4", "V3_5"])) ``` ### Dual Track Output Pattern APIs that generate multiple variations: ``` # Output parameter for multiple tracks music_urls_param = Parameter( name="music_urls", output_type="list[str]", type="list[str]", tooltip="Download URLs for generated tracks (2 variations)", allowed_modes={ParameterMode.OUTPUT}, settable=False, ui_options={"is_full_width": True, "display_name": "Music URLs"}, ) self.add_parameter(music_urls_param) def process(self) -> None: # ... generation logic ... urls = self._extract_music_urls(response_data) self.parameter_output_values["music_urls"] = urls # Build detailed result result_lines = [ f"✓ Generated {len(urls)} track variation(s)", "", "Music URLs:", ] for i, url in enumerate(urls, 1): result_lines.append(f"{i}. {url}") self.parameter_output_values["result_details"] = "\n".join(result_lines) ``` ### Status Updates During Long Operations Update status parameter in real-time during polling: ``` def _poll_for_completion(self, task_id: str, api_key: str) -> dict[str, Any]: """Poll API with real-time status updates.""" for attempt in range(self.MAX_POLLING_ATTEMPTS): time.sleep(self.POLLING_INTERVAL) # Update status parameter with progress status_msg = f"Generating... ({attempt + 1}/{self.MAX_POLLING_ATTEMPTS})" self.set_parameter_value("status", status_msg) response = requests.get(query_url, headers=headers, params={"ids": task_id}) # ... check completion ... ``` **Best Practice**: Always provide progress feedback for operations longer than 10 seconds. ## Flexible Artifact Processing The patterns below handle values that may still arrive as `ImageArtifact` from older workflows or upstream nodes — they're runtime input handling, not a template for declaring new parameters. New parameters should declare `ImageUrlArtifact` (see [Parameter Payload Size](https://docs.griptapenodes.com/development/custom_nodes/error_handling/#parameter-payload-size)). ### Duck Typing for Artifacts Handle multiple artifact formats gracefully: ``` def _extract_image_value(self, image_input: Any) -> str | None: """Extract string value from various image input types.""" if isinstance(image_input, str): return image_input try: # ImageUrlArtifact: .value holds URL string if hasattr(image_input, "value"): value = getattr(image_input, "value", None) if isinstance(value, str): return value # ImageArtifact: .base64 holds raw or data-URI if hasattr(image_input, "base64"): b64 = getattr(image_input, "base64", None) if isinstance(b64, str) and b64: return b64 except Exception as e: self._log(f"Failed to extract image value: {e}") return None ``` ### Image Format Conversion for External APIs **Problem**: External APIs often have strict format requirements (e.g., JPEG, PNG, WebP only), but cameras may save images in unsupported formats like MPO (Multi Picture Object) for 3D/burst photos. **Solution**: Automatically detect and convert unsupported formats: ``` # Import at top of file from PIL import Image from io import BytesIO def _get_image_data(self, image_artifact: ImageArtifact | ImageUrlArtifact) -> str: """Convert image to API-compatible format.""" # ... extract image_bytes ... try: img = Image.open(BytesIO(image_bytes)) # Convert unsupported formats (MPO, TIFF, BMP, etc.) to JPEG if img.format not in ["JPEG", "PNG", "WEBP"]: self._log(f"Converting {img.format} to JPEG for API compatibility") # Convert to RGB if needed (for formats like MPO) if img.mode not in ["RGB", "L"]: img = img.convert("RGB") # Save as JPEG to bytes output = BytesIO() img.save(output, format="JPEG", quality=95) image_bytes = output.getvalue() mime_type = "image/jpeg" else: format_to_mime = {"JPEG": "image/jpeg", "PNG": "image/png", "WEBP": "image/webp"} mime_type = format_to_mime.get(img.format, "image/jpeg") except Exception as e: self._log(f"Could not detect image format: {e}") mime_type = "image/jpeg" # Encode as base64 data URI base64_data = base64.b64encode(image_bytes).decode("utf-8") return f"data:{mime_type};base64,{base64_data}" ``` **Key Points**: - Apply conversion at all image input points (ImageArtifact, ImageUrlArtifact, localhost URLs) - Use high quality (95%) to preserve image fidelity - Handle color mode conversion (MPO often uses non-RGB modes) - Log conversions for debugging - Gracefully fall back to JPEG if detection fails ### Utility Function Patterns Create reusable utility functions for common operations: ``` # Connection checking utilities def _outgoing_connection_exists(source_node: str, source_param: str) -> bool: """Check if a source node/parameter has any outgoing connections.""" from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes connections = GriptapeNodes.FlowManager().get_connections() source_connections = connections.outgoing_index.get(source_node) if source_connections is None: return False param_connections = source_connections.get(source_param) return bool(param_connections) if param_connections else False def _incoming_connection_exists(target_node: str, target_param: str) -> bool: """Check if a target node/parameter has any incoming connections.""" from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes connections = GriptapeNodes.FlowManager().get_connections() target_connections = connections.incoming_index.get(target_node) if target_connections is None: return False param_connections = target_connections.get(target_param) return bool(param_connections) if param_connections else False ``` **Best Practice**: Create utility functions for common operations like connection checking, validation, and data processing. ### Flexible Image Processing ``` def _image_to_bytes(self, image_artifact) -> bytes: """Convert various image artifact types to bytes.""" if not image_artifact: raise ValueError("No input image provided") try: # Handle dictionary format (serialized artifacts) if isinstance(image_artifact, dict): image_url_artifact = self._dict_to_image_url_artifact(image_artifact) image_bytes = image_url_artifact.to_bytes() # Handle artifact objects directly elif isinstance(image_artifact, (ImageArtifact, ImageUrlArtifact)): image_bytes = image_artifact.to_bytes() else: # Try generic to_bytes method image_bytes = image_artifact.to_bytes() # Verify we have valid image data if not image_bytes or len(image_bytes) < 100: raise ValueError("Image data is empty or too small") return image_bytes except Exception as e: raise ValueError(f"Failed to extract image data: {str(e)}") ``` ## Appendix ### Imports ``` # Core imports from griptape_nodes.exe_types.core_types import ( Parameter, ParameterList, ParameterMode, ParameterTypeBuiltin, ParameterGroup, ParameterMessage, ControlParameterInput, ControlParameterOutput, ) from griptape_nodes.exe_types.node_types import DataNode, ControlNode, BaseNode, SuccessFailureNode, StartNode, EndNode from griptape_nodes.exe_types.base_iterative_nodes import BaseIterativeStartNode, BaseIterativeEndNode from griptape_nodes.traits.options import Options from griptape_nodes.traits.slider import Slider from griptape_nodes.traits.color_picker import ColorPicker from griptape_nodes.traits.file_system_picker import FileSystemPicker # Artifacts from griptape.artifacts import ImageArtifact, ImageUrlArtifact, TextArtifact # Utilities from griptape_nodes_library.utils.artifact_path_tethering import ArtifactPathTethering, ArtifactTetheringConfig from griptape_nodes_library.utils.image_utils import ( dict_to_image_url_artifact, load_pil_from_url, save_pil_image_with_named_filename, ) from griptape_nodes_library.utils.file_utils import generate_filename ``` ### Utility Function Reference #### Image Utilities (`griptape_nodes_library.utils.image_utils`) | Function | Purpose | Returns | | --------------------------------------------------- | ---------------------------------------------------- | ------------------ | | `dict_to_image_url_artifact(d)` | Convert dict representation to ImageUrlArtifact | `ImageUrlArtifact` | | `load_pil_from_url(url)` | Load PIL Image from URL (handles localhost) | `PIL.Image.Image` | | `save_pil_image_with_named_filename(img, filename)` | Save PIL Image using project system (see note below) | `ImageUrlArtifact` | **Example usage:** ``` from griptape_nodes_library.utils.image_utils import ( dict_to_image_url_artifact, load_pil_from_url, save_pil_image_with_named_filename, ) # Convert parameter value to artifact value = self.get_parameter_value("image") if isinstance(value, dict): artifact = dict_to_image_url_artifact(value) else: artifact = value # Load as PIL for processing pil_image = load_pil_from_url(artifact.value) # Process image... processed = pil_image.filter(...) # Save and get output artifact output_artifact = save_pil_image_with_named_filename(processed, "result.png") self.parameter_output_values["output"] = output_artifact ``` #### File Utilities (`griptape_nodes_library.utils.file_utils`) | Function | Purpose | Returns | | ------------------------------------------- | -------------------------- | ------- | | `generate_filename(node_name, suffix, ext)` | Create consistent filename | `str` | **Example usage:** ``` from griptape_nodes_library.utils.file_utils import generate_filename # Generate filename like "ColorMatch_processed_abc123.png" filename = generate_filename(self.name, suffix="processed", ext="png") ``` #### Project System (`griptape_nodes.files.project_file`) | Class/Function | Purpose | Returns | | ----------------------------------------- | ------------------------------------------------ | ------------------------ | | `ProjectFileDestination.from_situation()` | Create file destination with named situation | `ProjectFileDestination` | | `ProjectFileParameter` | Parameter component for configurable file output | - | **Example usage:** ``` from griptape_nodes.files.project_file import ProjectFileDestination # Save file using project system dest = ProjectFileDestination.from_situation(filename="output.mp4", situation="save_node_output") saved = dest.write_bytes(file_bytes) artifact = VideoUrlArtifact(saved.location) ``` **Note:** The utility functions `save_pil_image_with_named_filename()` and similar helpers use the project system internally. For new code, prefer using `ProjectFileParameter` or `ProjectFileDestination` directly to have full control over file handling. See [Working with the Project System](https://docs.griptapenodes.com/development/custom_nodes/project_system/index.md) for comprehensive documentation. ### Advanced Parameter Types - **ControlParameterInput/Output**: For execution flow control - **ParameterGroup**: For organizing related parameters with collapsible UI - **ParameterMessage**: For status updates and external links - **ParameterList**: For accepting multiple inputs of the same type ### Enumerations - **NodeResolutionState**: UNRESOLVED, RESOLVING, RESOLVED - **ParameterMode**: INPUT, OUTPUT, PROPERTY - **ParameterTypeBuiltin**: STR("str"), BOOL("bool"), INT("int"), FLOAT("float"), ANY("any"), NONE("none"), CONTROL_TYPE("parametercontroltype"), ALL("all") ### Advanced Node Types - **BaseNode**: Most basic node type for custom implementations - **DataNode**: For data processing without execution flow - **ControlNode**: For nodes that manage execution flow - **SuccessFailureNode**: For operations that can succeed or fail - **BaseIterativeStartNode/BaseIterativeEndNode**: For iterative operations - **AsyncResult**: Generator type for yielding blocking work to a background thread (prefer overriding `async def aprocess()` for genuinely async work) ### Custom Artifacts Inherit from BaseArtifact and override methods as needed: ``` from griptape.artifacts import BaseArtifact class CustomArtifact(BaseArtifact): def __init__(self, value: Any, **kwargs): super().__init__(value, **kwargs) def to_text(self) -> str: return str(self.value) ``` ### Advanced Lifecycle Methods #### Spotlight Control For conditional dependency resolution: ``` def initialize_spotlight(self) -> None: """Custom spotlight initialization - only include evaluate parameter initially.""" evaluate_param = self.get_parameter_by_name("evaluate") if evaluate_param and ParameterMode.INPUT in evaluate_param.get_mode(): self.current_spotlight_parameter = evaluate_param def advance_parameter(self) -> bool: """Custom parameter advancement with conditional dependency resolution.""" if self.current_spotlight_parameter is None: return False # Special handling for conditional parameters if self.current_spotlight_parameter is self.evaluate: try: evaluation_result = self.check_evaluation() next_param = self.output_if_true if evaluation_result else self.output_if_false if ParameterMode.INPUT in next_param.get_mode(): self.current_spotlight_parameter.next = next_param next_param.prev = self.current_spotlight_parameter self.current_spotlight_parameter = next_param return True except Exception: self.current_spotlight_parameter = None return False return super().advance_parameter() ``` #### Control Flow Management ``` def get_next_control_output(self) -> Parameter | None: """Return the appropriate control output based on evaluation.""" if "evaluate" not in self.parameter_output_values: self.stop_flow = True return None if self.parameter_output_values["evaluate"]: return self.get_parameter_by_name("Then") return self.get_parameter_by_name("Else") ``` # Node Isolation with Workers This page is the operational guide for running your library **isolated**: in a dedicated Python subprocess so your library's pinned dependencies (`torch`, `transformers`, `diffusers`) cannot collide with another library's. Artists pick this with the **Shared / Isolated** dropdown in the editor (see [Libraries](https://docs.griptapenodes.com/guides/libraries/#shared-vs-isolated)); under the hood, an isolated library runs on a **worker** subprocess. This page is the author's side of that mechanism. For the rule catalog that catches isolation mistakes, see [Strict Mode Reference](https://docs.griptapenodes.com/development/custom_nodes/strict_mode/index.md). ## Vocabulary A few terms used throughout this page: - **Orchestrator** — the main Griptape Nodes Python process. It owns the flow graph, connections, parameter registry, config, and secrets. The editor talks to the orchestrator directly. - **Worker subprocess** — a separate Python process that runs your library's nodes. Each library that opts into worker mode gets its own. Workers communicate with the orchestrator over a WebSocket connection (the **bus**). - **`process` and `aprocess`** — your node's execution method. Implement `process(self) -> ...` as you do today; the framework wraps it as `async def aprocess(self) -> None` so it can run on the worker's event loop. The strict-mode rules describe behavior "from inside aprocess," but in practice that means "from inside the `process` method you wrote." - **Schema probe** — a one-time pass at library load where the worker instantiates each registered node class once to discover its parameter layout. This runs your `__init__` before any execute request arrives. ## Should I opt in? **Opt in if** your library pins specific versions of heavy ML packages (`torch`, `transformers`, `diffusers`, `accelerate`, `peft`, `controlnet-aux`, custom CUDA wheels) and you want to coexist with other libraries that pin different versions. Worker mode is the mechanism for cross-library dependency isolation. **Stay opted out if** your library only uses lightweight, broadly compatible packages (the standard library, `pydantic`, `griptape` itself, common HTTP / YAML / JSON tooling). Running in the orchestrator process avoids the cross-process serialization tax described below. If unsure, the safer default is to opt in — the cost is real but small, and it future-proofs against a downstream user installing a heavier library next to yours. ## How to opt in Worker hosting is described by two declarations on your library's `metadata.declarations` in `griptape-nodes-library.json`. They live side by side because they answer two different questions: - **`worker_mode_compatibility`** — whether the library is *compatible* with worker hosting. One field, `compatibility`: - `COMPATIBLE`: the library can run in either the orchestrator process or a dedicated worker subprocess. - `INCOMPATIBLE`: the library only works in the orchestrator process and must never be hosted on a worker. - **`suggested_worker_mode`** — where the library *launches* when nothing else overrides. One field, `mode`: `ORCHESTRATOR` or `WORKER`. Omit the declaration to take the engine default (today: orchestrator). The editor exposes a per-library **Shared / Isolated** dropdown (Shared = orchestrator, Isolated = worker) that lets users flip a `COMPATIBLE` library between modes; this declaration is the author's suggested starting point for that dropdown. Omitting both declarations is equivalent to declaring `worker_mode_compatibility` with `compatibility=COMPATIBLE` and no `suggested_worker_mode` — the library is capable of worker mode but launches in the orchestrator until something asks for the flip. Declaring `worker_mode_compatibility` with `compatibility=INCOMPATIBLE` and a `suggested_worker_mode` of `WORKER` is contradictory; library metadata validation rejects that combination. ``` { "name": "My Library", "library_schema_version": "0.10.0", "metadata": { "author": "", "description": "", "library_version": "0.1.0", "engine_version": "0.85.0", "tags": ["AI", "Custom"], "declarations": [ { "type": "worker_mode_compatibility", "compatibility": "COMPATIBLE" }, { "type": "suggested_worker_mode", "mode": "WORKER" } ], "dependencies": { "pip_dependencies": [ "torch==2.4.1", "transformers==4.45.2" ], "pip_install_flags": [ "--extra-index-url", "https://download.pytorch.org/whl/cu121" ] } }, "categories": [], "nodes": [] } ``` Worker mode only delivers specific environment control if you pin specific wheels. A loose `torch>=2.0` resolves to whatever pip finds, which drifts between developers' machines and users' machines. Pin `torch==2.4.1`, not `torch>=2.0`. `pip_install_flags` is the escape hatch for index URLs and other arguments your install legitimately needs. The schemas: [`WorkerModeCompatibility`](https://github.com/griptape-ai/griptape-nodes/blob/main/src/griptape_nodes/node_library/library_declarations.py) and [`SuggestedWorkerMode`](https://github.com/griptape-ai/griptape-nodes/blob/main/src/griptape_nodes/node_library/library_declarations.py) in `library_declarations.py`, and [`Dependencies`](https://github.com/griptape-ai/griptape-nodes/blob/main/src/griptape_nodes/node_library/library_registry.py#L39) in `library_registry.py`. ## What you give up Cross-process serialization tax. When your worker-side node calls back into orchestrator-owned state (flow graph, connections, parameter registry, config, secrets) **during a node execute**, that request is forwarded over the WebSocket bus. Each call is a network round-trip, and the returned view is **stale-by-call** — by the time the worker reads it, the orchestrator may have moved on. Two practical implications: - **Pass data into nodes via parameters; don't fetch flow state during execution.** Reading connection or peer-node state from inside `process` (or from `before_value_set` / `after_value_set`, which run during input hydration on the same scope) works but is expensive and surfaces as the [`worker-reach-into-orchestrator`](https://docs.griptapenodes.com/development/custom_nodes/strict_mode/index.md) warning. - **Intentional writes are sanctioned**, not penalized. Emit the corresponding request (`SetParameterValueRequest`, `AddParameterToNodeRequest`, `RemoveParameterFromNodeRequest`, etc.) and the engine handles the round-trip correctly. The strict- mode rule's remediation explicitly flags writes as fine to ignore. Requests issued **outside** node execution (during library load or bootstrap) are not forwarded — the worker is not connected to the orchestrator at that point. Bus calls from `__init__` reentrantly hit the worker's own event loop, which is why `__init__` has its own strict-mode rule (next section). ## Lifecycle changes you need to know ### `__init__` runs during library load The worker subprocess instantiates each registered node class once at startup to extract a parameter schema for the orchestrator. Three implications: - **No I/O in `__init__`.** Network calls, auth checks, disk reads, database connections all block library load. The schema probe has a finite timeout, and a class whose `__init__` raises or times out is **silently dropped from the exported library** with no rule fired. Move I/O into `process` or a lifecycle hook that runs after construction. - **No event-bus calls in `__init__`.** Reentering the bus during the schema probe deadlocks the worker. The [`reentrant-bus-in-init`](https://docs.griptapenodes.com/development/custom_nodes/strict_mode/index.md) correctness rule fails the class on this; because it is a correctness-class violation, the class is also dropped from the library schema. - **Parameters declared in `__init__` are the normal pattern.** `self.add_parameter(...)` is fine here — the schema probe is the one place a node is "supposed to" define its parameter list. ### Each `ExecuteNodeRequest` constructs a fresh node The worker materializes a transient node from request metadata, runs `process`, and discards it. **Your node holds no in-memory state between calls.** The supported patterns for moving values: - **Inputs** arrive in `self.parameter_values` at the start of each execute, hydrated from the orchestrator's authoritative copy. Read them inside `process`; do not assume the values from a prior call are still present. - **Outputs** go in `self.parameter_output_values`. The framework ships these back to the orchestrator after `process` returns. Set `self.parameter_output_values["my_param"] = value` inside `process`. - **Cross-call state that must persist** belongs in the orchestrator. Issue a `SetParameterValueRequest` from inside `process` to update an authoritative value; on the next execute the new value will hydrate into `self.parameter_values`. Do not rely on `self.parameter_values[k] = v` mid-execute as a way to carry state forward — that mutation does not propagate. What does **not** work: setting `self.foo = ...` and expecting it to survive. The next execute gets a fresh node instance. ### Mutating the parameter list during execute does not propagate `self.add_parameter(...)` and `self.remove_parameter_element(...)` called from inside `process` (or `aprocess`) apply only to the transient worker-side node. The orchestrator's authoritative copy never sees the change. To mutate parameters during execution, route through the request bus: - `AddParameterToNodeRequest` to add a parameter - `RemoveParameterFromNodeRequest` to remove one Issue the request via `GriptapeNodes.handle_request(...)` from inside `process`. The handler-side path propagates the change back to the orchestrator. Worker subprocesses pick up the new parameter list on the next execute. The [`parameter-mutation-during-aprocess`](https://docs.griptapenodes.com/development/custom_nodes/strict_mode/index.md) rule fires on direct in-execute mutations and tells you which one to use. **`before_value_set` / `after_value_set` do not fire on editor changes for isolated libraries.** These hooks run inside the worker subprocess, on the temporary copy of your node built for each execute. The orchestrator holds only a stub copy of your node class — parameters, no code — so it never calls your overrides when a user edits a value in the editor. Transforming an incoming value still works: the hooks run as inputs are applied, just before `process`. But a parameter-list change made inside them is discarded with the temporary node. It skips the [`parameter-mutation-during-aprocess`](https://docs.griptapenodes.com/development/custom_nodes/strict_mode/index.md) rule, and it does not propagate either. The practical consequence: hooks that adjust the parameter list in response to user input (for example, showing or hiding fields when a dropdown changes) only work in Shared mode. For an isolated library, define the full parameter list statically in `__init__`. Overriding a value hook fires the [`value-hooks-execute-only-on-worker`](https://docs.griptapenodes.com/development/custom_nodes/strict_mode/index.md) warning at library load. ### Connection hooks never fire under isolation `after_incoming_connection`, `after_outgoing_connection`, the `allow_*` validators, and the `*_removed` variants are invoked on the orchestrator when connections change — connections are orchestrator-owned state, and the worker is not consulted. For a worker-hosted library those calls land on the stub class, so an override you write never runs, silently. Overriding a connection hook fires the [`connection-hooks-inert-on-worker`](https://docs.griptapenodes.com/development/custom_nodes/strict_mode/index.md) warning at library load. If your node needs to react to wiring (the dynamic-parameter pattern), run the library in Shared mode. ### Parameter `converters`, `validators`, and `traits` do not cross to the orchestrator When the schema probe exports your library, only the scalar-shaped fields of each `Parameter` (name, type, default, tooltip, allowed modes) are serialized for the orchestrator's stub copy of the class. Custom `converters`, `validators`, and `traits` you attached to a `Parameter` are **not** carried across — they live in the worker's process and run only when the worker executes the node. The orchestrator stub still accepts user input on those parameters and ships values to the worker, but the orchestrator-side UI cannot re-run your `converters` / `validators` / `traits` to massage or reject values before they leave the editor. Authors see this as a [`parameter-behaviors-dropped-in-schema`](https://docs.griptapenodes.com/development/custom_nodes/strict_mode/index.md) warning at library load. Two workable patterns: - **Move the validation or transform into `process`.** The worker re-runs it on the actual value. The cost is that the editor cannot show the user a validation failure inline; they only see it when the node executes. - **Accept the divergence as orchestrator-only UI sugar.** If the converter is purely a display nicety (e.g., title-casing a string), losing it on the orchestrator is harmless. ## Configuration, secrets, and the current project propagate automatically The orchestrator broadcasts `ReloadConfigRequest` and `RefreshSecretsRequest` to every registered worker after a successful config or secret mutation. Each worker re-reads the shared on-disk files and updates its in-memory view. You do not need to wire this up; it happens automatically. The active **project** propagates the same way. A worker boots like the orchestrator: it re-derives the current project from the same shared on-disk config, so a freshly spawned worker lands on the orchestrator's project for free. After startup, switching projects in the orchestrator pushes the new project to every running worker so that environment variables, directory macros, and situation/path macros resolve against the same project in both processes. Two cases are worth knowing: - A switch that changes library configuration restarts the worker, which then re-derives the project on boot. - A "shallow" switch (same workspace and library config, where only the environment, directories, or situations differ) does **not** restart the worker. The orchestrator broadcasts the switch and the worker adopts the new project in place, ahead of any queued node execution, so it never runs against a stale project. You do not need to wire any of this up. A worker never persists the project choice back to the shared config; the orchestrator is the single source of truth. A subtle point: operator-set OS environment variables (e.g., a container-injected `OPENAI_API_KEY`) are preserved across **refresh broadcasts** and **explicit deletes**. The worker's refresh re-reads the `.env` file but does not overwrite an operator-set value, and deleting a secret from the file does not pop a colliding OS env var. `set_secret(...)` is the one path that does override an OS-set value, because it represents the user's stated intent. The engine logs a `WARNING` so the asymmetry is visible. ## "Is my library isolation-ready?" checklist - [ ] `worker_mode_compatibility` declared in `metadata.declarations` with `compatibility: COMPATIBLE` (or omit the declaration entirely -- absence is treated as `COMPATIBLE`), plus `suggested_worker_mode` with `mode: WORKER` (or omit `suggested_worker_mode` if you want the library to launch in the orchestrator by default and let users opt in via the GUI) - [ ] `__init__` does no I/O and issues no event-bus requests - [ ] No `add_parameter` / `remove_parameter_element` from inside `process`; use `AddParameterToNodeRequest` / `RemoveParameterFromNodeRequest` via `GriptapeNodes.handle_request(...)` instead - [ ] Cross-node / flow state passed in via parameters, not fetched from inside `process` - [ ] No connection hooks (`after_incoming_connection` and siblings) overridden -- they never fire for a worker-hosted library - [ ] `before_value_set` / `after_value_set` used only for value transformation, not editor-time reactivity or parameter-list mutation - [ ] Custom `converters` / `validators` / `traits` either re-run inside `process` or accepted as orchestrator-only UI sugar - [ ] `pip_dependencies` pinned to specific versions - [ ] `pip_install_flags` set if your install needs a custom index URL or other arguments ## Strict mode is your safety net Run your library locally with the engine and watch the engine's console output for `strict-mode` lines during a normal node execute. Worker output appears in the same terminal you launched the engine from, prefixed with `Worker-` so you can tell it apart from orchestrator output. Look for both **WARNING** and **ERROR** entries. The six rules and their actual severities: | Rule | Orchestrator | Worker | Notes | | ----------------------------------------------------------------------------------------------------------------------- | ------------ | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [`reentrant-bus-in-init`](https://docs.griptapenodes.com/development/custom_nodes/strict_mode/index.md) | ERROR | ERROR | Correctness rule. The class is dropped from the library schema. | | [`parameter-behaviors-dropped-in-schema`](https://docs.griptapenodes.com/development/custom_nodes/strict_mode/index.md) | WARNING | WARNING | Fires during library load when a `Parameter` carries `converters` / `validators` / `traits` that the worker schema cannot serialize. Does not escalate. | | [`connection-hooks-inert-on-worker`](https://docs.griptapenodes.com/development/custom_nodes/strict_mode/index.md) | WARNING | WARNING | Fires during library load when a node class overrides a connection lifecycle hook. Those hooks run on the orchestrator against the stub, so the override never runs. Does not escalate. | | [`value-hooks-execute-only-on-worker`](https://docs.griptapenodes.com/development/custom_nodes/strict_mode/index.md) | WARNING | WARNING | Fires during library load when a node class overrides `before_value_set` / `after_value_set`. Value transformation still works; editor-time reactivity and parameter-list mutation do not. Does not escalate. | | [`parameter-mutation-during-aprocess`](https://docs.griptapenodes.com/development/custom_nodes/strict_mode/index.md) | WARNING | ERROR | Promotes the node's result to a failure on the worker. | | [`worker-reach-into-orchestrator`](https://docs.griptapenodes.com/development/custom_nodes/strict_mode/index.md) | n/a | WARNING | Fires anywhere during node execution including hydration. Does not escalate; intentional writes are explicitly fine to ignore. | If a strict-mode line fires, the rule's remediation message names exactly which guideline above was violated and how to fix it. A worker log free of strict-mode WARNING and ERROR entries (apart from the explicitly-fine-to-ignore writes flagged by `worker-reach-into-orchestrator`) is the bar for "isolation-ready." # Strict Mode Reference > For when and how to run your library isolated in a worker subprocess in the first place, see [Node Isolation with Workers](https://docs.griptapenodes.com/development/custom_nodes/node_isolation_with_workers/index.md). This page is the rule catalog that catches isolation incompatibilities. > > Throughout this page, "`aprocess`" refers to the framework's async wrapper around the `process` method you implemented on your node. When a rule says "during `aprocess`," it means "during the node's execute, including from inside the `process` method you wrote." Strict mode is a runtime contract for what node code is allowed to do across the orchestrator and worker subprocess. When a node violates the contract, the framework records a named violation and routes it to the node's result payload so the author sees a remediation message in the editor instead of a silent no-op, deadlock, or a stack trace that names the wrong layer. Strict mode is always on. There is no config flag, env var, or runtime toggle. Severity is picked per-rule: correctness rules fail execution; ergonomics rules emit a warning. ## How it surfaces Violations attach to the `ResultDetails` on the outgoing `ResultPayload`. In the editor, the node's output panel shows the rule id, severity, and remediation. On the worker side, correctness violations elevate a successful `ExecuteNodeResultSuccess` to an `ExecuteNodeResultFailure`; ergonomics violations stay non-fatal. Violations are also logged through the `griptape_nodes.strict_mode` logger. Set it to `WARNING` or lower to see every violation in the console. ## Rule catalog Each rule is either a **correctness** rule (fails on both orchestrator and worker) or an **ergonomics** rule (warns on orchestrator, escalates to a failure on the worker unless the rule opts out). ### Correctness rules (fail execution) #### `reentrant-bus-in-init` A node issued an event-bus request from inside its `__init__`. The worker library probe runs `__init__` to extract a schema; re-entering the bus there deadlocks the worker. **Remediation**: move the call into `aprocess` (or a lifecycle hook that runs after construction). ### Ergonomics rules (warnings) #### `parameter-behaviors-dropped-in-schema` A `Parameter` attached `converters`, `validators`, or `traits` that are not captured in the worker schema. The orchestrator stub cannot re-run those behaviors, so UI-side behavior diverges from worker-side execution. **Remediation**: re-run the converter / validator logic inside `process` so the worker still applies it to the actual value, or accept the divergence as orchestrator-only UI sugar. Note that moving the logic into `process` loses the inline editor-side validation feedback — the user only sees a failure when the node executes. #### `parameter-mutation-during-aprocess` A node called `add_parameter` or `remove_parameter_element` during `aprocess`. On the worker, these mutations apply to the transient node instance and do not sync back to the orchestrator. Hydration-time mutations made from `before_value_set` / `after_value_set` (the standard dynamic-parameter pattern) do **not** trip this rule. **Remediation**: emit an `AddParameterToNodeRequest` or `RemoveParameterFromNodeRequest` so the mutation propagates to the authoritative orchestrator-side node. #### `worker-reach-into-orchestrator` A node running on a worker issued a request whose authoritative state lives on the orchestrator (flow graph, connections, parameter registry, config, secrets). The request is forwarded across the WebSocket bus; each call is a network round-trip and the returned view is stale-by-call. The rule fires for forwarded requests issued anywhere during a node's execution -- including from `before_value_set` / `after_value_set` during hydration -- not only from `aprocess`. **Remediation**: if this is an intentional write (e.g. publishing a parameter value), ignore the warning. If this is a read of flow / connection state, consider whether the data could be passed in via parameters instead of fetched per-call. #### `connection-hooks-inert-on-worker` A node class in a worker-hosted library overrides one or more connection lifecycle hooks (`allow_incoming_connection`, `before_incoming_connection`, `after_incoming_connection`, their outgoing counterparts, or the `_removed` variants). Connections are orchestrator-owned state, so these hooks are invoked on the orchestrator -- where a worker-hosted library is represented by a synthesized stub class that does not carry the override. The author's code never runs, silently. Fires once per node class at library load, during the worker's schema probe. Hooks implemented by engine-owned bases and components are not flagged; the rule targets code the library author can change. **Remediation**: dynamic parameters driven by connections are not supported for isolated libraries. Run the library in Shared mode, or remove the override. #### `value-hooks-execute-only-on-worker` A node class in a worker-hosted library overrides `before_value_set` / `after_value_set`. For an isolated library these hooks fire only during execute-time input hydration, on the transient worker-side node: transforming an incoming value still works, but the hooks do not run when a value changes in the editor, and any parameter-list mutation they make is discarded with the transient node. Fires once per node class at library load, during the worker's schema probe. Hooks implemented by engine-owned bases and components are not flagged. **Remediation**: keep value hooks to value transformation. If the node needs editor-time reactivity (adjusting parameters as values change), run the library in Shared mode. # Reference # Griptape Nodes Command Line Interface (CLI) If you're new to command-line interfaces (CLIs), a CLI is a text-based way to interact with software by typing commands instead of clicking buttons in a UI. Griptape Nodes provides a CLI with the `griptape-nodes` (or `gtn`) command. This enables you to interact with Griptape Nodes in the terminal. `griptape-nodes` (or its shorthand alias `gtn`) is a command-line tool specifically designed to launch and manage the Griptape Nodes Engine installation on your computer. This tool handles tasks like initializing your workspace, managing configuration settings, and starting the engine that powers the web-based Griptape Nodes editor. The actual creation and editing of workflows happens in the web interface that opens when you run the engine. ## Basic Usage ``` griptape-nodes [options] [COMMAND] ``` If no command is specified, the tool defaults to the `engine` command. ## Commands ### `engine` (Default Command) Run the Griptape Nodes engine. ``` griptape-nodes engine ``` This will start the Griptape Nodes engine and open the web interface at https://nodes.griptape.ai. ### `init` Initialize a new workspace for Griptape Nodes: sets your API key, workspace directory, storage backend, and (optionally) extra libraries. Running it again re-runs the same prompts so you can change any of these later. ``` griptape-nodes init [options] ``` Options: - `--api-key` - Directly specify your Griptape API key without being prompted - `--workspace-directory` - Directly specify your workspace directory without being prompted - `--storage-backend` - Set the storage backend (`local` or `gtc`) without being prompted - `--bucket-name` - Name for the bucket (existing or new) to use when `--storage-backend gtc` is set - `--register-diffusers-library` / `--no-register-diffusers-library` - Install (or skip) the Griptape Nodes Diffusers Library - `--register-griptape-cloud-library` / `--no-register-griptape-cloud-library` - Install (or skip) the Griptape Cloud Library - `--no-interactive` - Run init without any prompts, using only the flags and defaults provided - `--hf-token` - Set the Hugging Face token used for downloading gated models - `--config key=value` - Set an arbitrary configuration value; repeat the flag to set several (e.g. `--config log_level=DEBUG --config workspace_directory=/tmp`) - `--secret key=value` - Set an arbitrary secret value; repeat the flag to set several (e.g. `--secret MY_API_KEY=abc123`) ### `config` Manage your Griptape Nodes configuration. ``` griptape-nodes config SUBCOMMAND ``` Subcommands: - `show [config_path]` - Show the current configuration. With no argument, prints the whole merged configuration as JSON; with a dotted path (e.g. `workspace_directory`), prints just that value - `list` - List all configuration files that contribute to your configuration, in order of precedence - `reset` - Reset your configuration to default values For the full list of settings you can read or set this way, see the [Configuration Reference](https://docs.griptapenodes.com/reference/configuration_reference/index.md). ### `self` Manage the CLI installation itself. ``` griptape-nodes self SUBCOMMAND ``` Subcommands: - `uninstall` - Uninstall the CLI, removing its configuration and data directories and the installed executable - `version` - Display the current version of the CLI - `info` - Print a system information report for debugging: engine version and install source, platform and Python details, configuration paths, the full merged configuration, and every registered library with its version. Useful to paste into a bug report ### `libraries` Manage local libraries. For the full guide on installing and managing libraries, see [Libraries](https://docs.griptapenodes.com/guides/libraries/index.md). ``` griptape-nodes libraries SUBCOMMAND ``` Subcommands: - `sync` - Update every registered library to its latest version - `--overwrite` - Discard any uncommitted local changes in a library's clone before updating it - `download ` - Clone a library from Git and register it - `--branch` - Branch, tag, or commit to check out - `--target-dir` - Name of the directory to clone into - `--download-dir` - Parent directory the library is cloned under - `--overwrite` - Overwrite the library's directory if it already exists ### `models` Manage AI models downloaded from the Hugging Face Hub — the same models nodes like local diffusion or LLM nodes pull from when you run a workflow. ``` griptape-nodes models SUBCOMMAND ``` Subcommands: - `download ` - Download a model from the Hugging Face Hub (e.g. `microsoft/DialoGPT-medium`) - `--local-dir` - Local directory to download the model into - `--revision` - Git revision to download (defaults to `main`) - `list` - List all model files currently in your local cache, with their size on disk - `delete ` - Delete a model's files from your local cache - `search [query]` - Search for models on the Hugging Face Hub - `--task` - Filter results by task type (e.g. `text-generation`) - `--limit` - Maximum number of results to return (defaults to 20, capped at 100) - `--sort` - Field to sort results by (defaults to `downloads`) - `--direction` - Sort direction (defaults to `desc`) - `downloads status [model_id]` - Show download progress/status for one model, or for every tracked model if none is given - `downloads list` - List every tracked model download and its status - `downloads delete ` - Delete the download-status tracking record for a model (does not delete the model's files; use `models delete` for that) ### `doctor` Run health checks against your Griptape Nodes installation and print a pass/fail table (for example, whether the engine's WebSocket connection is reachable). Exits with a nonzero status if any check fails, so it's safe to use in scripts. ``` griptape-nodes doctor ``` ## Configuration Griptape Nodes stores its configuration in the following locations. - Configuration directory: `~/.config/griptape_nodes` (macOS/Linux) or `%USERPROFILE%\.config\griptape_nodes` (Windows) - Data directory: `~/.local/share/griptape_nodes` (macOS/Linux) or `%USERPROFILE%\.local\share\griptape_nodes` (Windows) - Configuration file: `griptape_nodes_config.json` in the configuration directory - Environment file: `.env` in the configuration directory Griptape Nodes Desktop keeps these two directories inside its own application data folder so they don't collide with a manually installed engine. See [Uninstalling Griptape Nodes](https://docs.griptapenodes.com/uninstalling/#griptape-nodes-desktop) for those paths. ## Workflow Typical usage flow: 1. Run `griptape-nodes init` to set up your workspace and API key 1. Run `griptape-nodes` to start the engine 1. Use the web interface to create and manage your workflows # Configuration Reference Every Griptape Nodes engine setting, grouped by category. Each setting can be placed in any `griptape_nodes_config.json` file (see [Engine Configuration](https://docs.griptapenodes.com/guides/configuration/index.md) for the load order). Settings with a `GTN_CONFIG_*` env var can also be overridden from the environment; nested settings must be edited in a config file. ## File System Directories and file paths for the application | Setting | Type | Default | Environment variable | Description | | -------------------------------- | ------- | ------------------------------------------- | ------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `workspace_directory` | string | `/GriptapeNodes` | `GTN_CONFIG_WORKSPACE_DIRECTORY` | Root directory for projects, workflows, and generated assets. Defaults to a GriptapeNodes folder under the current working directory. The other File System paths (libraries_directory, static_files_directory, sandbox_library_directory, synced_workflows_directory) are interpreted relative to this directory unless they are set to absolute paths. | | `static_files_directory` | string | `"staticfiles"` | `GTN_CONFIG_STATIC_FILES_DIRECTORY` | Path to the static files directory, relative to the workspace directory. | | `sandbox_library_directory` | string | `"sandbox_library"` | `GTN_CONFIG_SANDBOX_LIBRARY_DIRECTORY` | Path to the sandbox library directory (useful while developing nodes). Relative paths are interpreted relative to the workspace directory. Absolute paths are used as-is. | | `libraries_directory` | string | `"libraries"` | `GTN_CONFIG_LIBRARIES_DIRECTORY` | Path to directory for downloaded libraries. All griptape_nodes_library.json files found recursively will be auto-discovered on startup. Relative paths are interpreted relative to the workspace directory. Absolute paths are used as-is. A project may override this location via the project-template `libraries_dir` field (inheritable down the parent-project chain), which takes precedence over this value so a child project can share its parent's library install location. | | `ffmpeg_directory` | string | `""` | `GTN_CONFIG_FFMPEG_DIRECTORY` | Absolute path to the directory holding the ffmpeg/ffprobe binaries the engine downloads on first use. Unlike the other directory settings, this is never interpreted relative to the workspace: the ffmpeg cache belongs to the machine, not to a workspace, so it is shared across every workspace and project. A relative value is ignored with a warning. Empty (the default) means `/griptape_nodes/ffmpeg`. To supply your own binaries instead of downloading, point this at a directory containing `bin//` holding ffmpeg, ffprobe, and an empty `installed.crumb` file - static-ffmpeg treats that marker as proof of a completed install, and re-downloads over the binaries whenever it is missing. | | `synced_workflows_directory` | string | `"synced_workflows"` | `GTN_CONFIG_SYNCED_WORKFLOWS_DIRECTORY` | Path to the synced workflows directory, relative to the workspace directory. | | `enable_workspace_file_watching` | boolean | `true` | `GTN_CONFIG_ENABLE_WORKSPACE_FILE_WATCHING` | Enable file watching for synced workflows directory | ## Application Events Configuration for application lifecycle events | Setting | Type | Default | Environment variable | Description | | ------------ | ------ | --------------- | ------------------------------ | ------------------------------------------------------------- | | `app_events` | object | (nested object) | n/a (nested; edit config file) | Nested settings; edit the sub-keys directly in a config file. | ## Execution Workflow execution and processing settings | Setting | Type | Default | Environment variable | Description | | ------------------------- | ------------------------------------------------------ | --------------- | ------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `log_level` | one of `CRITICAL`, `ERROR`, `WARNING`, `INFO`, `DEBUG` | `"INFO"` | `GTN_CONFIG_LOG_LEVEL` | Logging verbosity for the engine. One of CRITICAL, ERROR, WARNING, INFO, or DEBUG, from least to most verbose. | | `workflow_execution_mode` | one of `sequential`, `parallel` | `"sequential"` | `GTN_CONFIG_WORKFLOW_EXECUTION_MODE` | Workflow execution mode for node processing. SEQUENTIAL mode uses ParallelResolutionMachine with max_nodes_in_parallel=1 to execute nodes one at a time. PARALLEL mode uses the configured max_nodes_in_parallel value. | | `max_nodes_in_parallel` | integer | `5` | `GTN_CONFIG_MAX_NODES_IN_PARALLEL` | Maximum number of nodes executing at a time for parallel execution. | | `worker` | object | (nested object) | n/a (nested; edit config file) | Nested settings; edit the sub-keys directly in a config file. | ## Storage Data storage and persistence configuration | Setting | Type | Default | Environment variable | Description | | ------------------------------- | --------------------- | --------- | ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `storage_backend` | one of `local`, `gtc` | `"local"` | `GTN_CONFIG_STORAGE_BACKEND` | Backend used to persist workflow data and generated assets. 'local' stores files on the local filesystem under the workspace; 'gtc' uses Griptape Cloud storage. | | `auto_inject_workflow_metadata` | boolean | `true` | `GTN_CONFIG_AUTO_INJECT_WORKFLOW_METADATA` | Automatically inject workflow metadata into saved files with supported formats | | `thread_storage_backend` | `"local"` (constant) | `"local"` | `GTN_CONFIG_THREAD_STORAGE_BACKEND` | Storage backend for conversation threads. Only 'local' (filesystem) is supported; Griptape Cloud support was removed in the Pydantic AI migration. | ## System Requirements System resource requirements and limits | Setting | Type | Default | Environment variable | Description | | --------------------------------- | ------- | ------- | -------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `minimum_disk_space_gb_libraries` | number | `10.0` | `GTN_CONFIG_MINIMUM_DISK_SPACE_GB_LIBRARIES` | Minimum disk space in GB required for library installation and virtual environment operations | | `minimum_disk_space_gb_workflows` | number | `1.0` | `GTN_CONFIG_MINIMUM_DISK_SPACE_GB_WORKFLOWS` | Minimum disk space in GB required for saving workflows | | `discovery_max_depth` | integer | `5` | `GTN_CONFIG_DISCOVERY_MAX_DEPTH` | Maximum directory depth the engine walks when a registered entry points at a directory to recursively discover files (e.g. project files under projects_to_register). Bounds boot-time scans against pathologically deep trees and symlink loops. 0 scans only the top-level directory; each nested level adds 1. | ## MCP Servers Model Context Protocol server configurations | Setting | Type | Default | Environment variable | Description | | ------------- | ----- | ------- | ------------------------------ | ---------------------------------------------------- | | `mcp_servers` | array | `[]` | n/a (nested; edit config file) | List of Model Context Protocol server configurations | ## Static Server Static file server configuration for serving media assets | Setting | Type | Default | Environment variable | Description | | ------------------------ | ------ | ------- | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `static_server_base_url` | string | `null` | `GTN_CONFIG_STATIC_SERVER_BASE_URL` | Base URL for the static server. Leave unset to derive it from the server's host/port (including the OS-assigned port when the configured port is unavailable). Set this only to override the derived URL, e.g. when fronting the server with a tunnel (ngrok, cloudflare) or reverse proxy. | ## Artifacts Settings for artifact providers and preview generation | Setting | Type | Default | Environment variable | Description | | ----------- | ------ | ------- | ------------------------------ | ------------------------------------------------------------------- | | `artifacts` | object | `{}` | n/a (nested; edit config file) | Control how previews are generated for images and other media files | ## Projects Project template configurations and registrations | Setting | Type | Default | Environment variable | Description | | -------------------- | ------ | ------- | ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `project_file` | string | `null` | `GTN_CONFIG_PROJECT_FILE` | Path to the project file (griptape-nodes-project.yml) to load initially when the engine starts. When set, overrides the default location of /griptape-nodes-project.yml. If the specified path does not exist, falls back to the workspace default. The sentinel value '' means the engine deliberately stays on system defaults and suppresses the workspace-default fallback (so a workspace griptape-nodes-project.yml is not auto-discovered); this is what the engine persists when it is intentionally on system defaults. | | `project_workspaces` | object | `{}` | n/a (nested; edit config file) | Mapping of project identifiers to workspace directory overrides. A key may be either a project ID or a project file path: it is first matched against loaded project IDs, and if none match, treated as a project file path. When a project is loaded, if it matches a key here, the corresponding value is used as the workspace directory instead of the project-adjacent config or auto-default. | ## Agent Agent behavior and system prompt | Setting | Type | Default | Environment variable | Description | | ------- | ------ | --------------- | ------------------------------ | ------------------------------------------------------------- | | `agent` | object | (nested object) | n/a (nested; edit config file) | Nested settings; edit the sub-keys directly in a config file. | ## Libraries Settings for library management and dependency installation | Setting | Type | Default | Environment variable | Description | | --------- | ------ | --------------- | ------------------------------ | ------------------------------------------------------------- | | `library` | object | (nested object) | n/a (nested; edit config file) | Nested settings; edit the sub-keys directly in a config file. | # Enterprise # Admin Dashboard The Admin Dashboard is the administration interface built into the Griptape Nodes editor. Organization owners use it to issue and manage license keys, organize users into access groups, and control exactly what each user can do with permission templates — which libraries and nodes they can load, which models they can call, and which projects they can open. ## Who can use it The Admin Dashboard manages **organizations you own**, and it talks to Griptape Cloud — so it is only available when you are **signed in with a Griptape Cloud account**. In the desktop application this means the dashboard appears only for users who signed in through **Login or Sign-Up**, not for seats activated with a license key. License activation (see [Using the Admin Server](https://docs.griptapenodes.com/enterprise/using_the_admin_server/index.md)) creates a session with no cloud account behind it: those users are the seats being managed, and the **Admin Dashboard** menu entry never appears for them. The entry is also hidden when your active organization is one you do not own. If you own more than one organization, an organization switcher in the dashboard's top bar lets you move between them; you can never switch into an organization you do not own. ## Opening the Admin Dashboard In the desktop application, open the profile menu in the top-right corner (the button showing your name) and choose **Admin Dashboard**. The dashboard opens in its own window, separate from the editor, so you can administer licenses side by side with your work. Choosing **Admin Dashboard** again focuses the existing window. Close the window when you are done — the editor is unaffected. Also available in the web editor The same dashboard is available in the Griptape Nodes web editor: open the user menu in the bottom-left corner and choose **Admin Dashboard**. There it replaces the editor view, and the **Back to Editor** link in the top bar returns you to your workflow. The dashboard has: - A **top bar** with a **Back to Editor** link, the active organization (or an organization switcher), and the signed-in user. - A **sidebar** with the three sections: **License Keys**, **Access Groups**, and **Permission Editor**. - A **Dashboard Settings** menu pinned to the bottom of the sidebar, with links to the onboarding guide and this documentation. ### The welcome dialog On your first visit, a welcome dialog summarizes your organization at a glance — assigned licenses, permission templates, and active sessions — and offers three "Get Started" shortcuts that map to the three sidebar sections: 1. **Create a license key for each user.** Generate a unique key per seat and send it to the user; they paste it into Griptape to activate their access. 1. **Create sets of license keys as access groups.** Group keys by department or project, then assign permission templates to the whole group. 1. **Customise permissions per project.** Build permission templates that control access studio-wide or per project. Check **Don't show this again** to dismiss it permanently; you can bring it back at any time via **Dashboard Settings → Onboarding Guide**. ## License Keys The **License Keys** section is the home page of the dashboard. It is where you issue seats: one license key per user, delivered to them as a token they paste into the desktop application (see [Using the Admin Server](https://docs.griptapenodes.com/enterprise/using_the_admin_server/index.md) for the user-side activation flow). ### The stats bar Four tiles across the top summarize the organization: | Tile | Shows | Click action | | ------------------------ | ---------------------------------------------- | ------------------------- | | **Assigned Licenses** | Total license keys, plus how many are revoked. | — | | **Permission Templates** | Total templates, plus how many are assigned. | — | | **Sessions** | Active sessions, plus how many are available. | Opens the Sessions modal. | | **API Keys** | Total API keys, plus how many are active. | Opens the API Keys modal. | ### The license table Each row is one license key with: - **Name** — usually the user or seat it belongs to. - **License Type** — `Interactive` (a person using the editor) or `Headless` (automated/unattended use). - **Status** — `Active`, `Revoked`, or `Expired`. - **Access Groups** — an inline multi-select for the groups this key belongs to. With multiple rows selected, changing the value applies to every selected row. - **Permissions** — a popover listing every permission template that applies to the key, both directly assigned and inherited through access groups (inherited entries show the source group). Templates can be removed from here. - **Actions** — Edit, Reissue token, Revoke, and Delete. The toolbar above the table provides search, a status filter, and the **+ Create License Key** button. Column headers sort; columns are resizable. ### Creating license keys Click **+ Create License Key**. The dialog accepts: - **License Name(s)** — one or more names. Adding several names creates a batch of keys in one step, one per name. - **License Type** — `Interactive` or `Headless`. This **cannot be changed** after creation. - **Expiration Date** — required; must be between 1 and 730 days from now. - **Access Groups** — optionally add the new key(s) to existing groups. When creation succeeds, a dialog reveals each license token **exactly once**. Copy the token(s) and deliver them to the users — the token cannot be retrieved again later. If you lose a token, use **Reissue** on the license to generate a new one. ### Managing license keys - **Edit** — rename the key and change its access group membership. - **Reissue token** — generates a fresh token for the same license (for example, when the original was lost). The new token is shown once, like at creation. - **Revoke** — releases the key's existing sessions and prevents the token from allocating new sessions. Only active keys can be revoked or reissued. - **Delete** — permanently removes the key. This cannot be undone. ### Sessions Click the **Sessions** tile to open the Sessions modal. It lists your organization's sessions with the session ID, the license key that holds it, status, expiry, and creation time, filterable by status. From here you can: - **Release** a session, with a configurable grace period, freeing the seat for another user. - **Delete** a session record. ### API Keys Click the **API Keys** tile to open the API Keys modal, which manages your organization's Griptape Cloud API keys (used, for example, to operate an [Admin Server](https://docs.griptapenodes.com/enterprise/admin_server/index.md)). You can create a key — its value is revealed once, so copy it immediately — and delete keys that are no longer needed. ## Access Groups Access groups collect license keys into sets — typically a department, a team, or a show — so permissions can be managed for the whole set at once. A permission template assigned to a group applies to every license key in it. Each row is one group with: - **Access Group Name** - **License Keys** — an inline multi-select of the keys in the group. - **Permissions** — an inline multi-select of the permission templates assigned to the group, covering both your own templates and Griptape-managed ones (marked with a Managed badge). Create a group with **+ Add New Access Group**, giving it a name and optionally its initial license keys and permission templates. Groups can be edited or deleted from their row; deleting a group does not delete its license keys. ## Permission Editor The Permission Editor is where you author **permission templates**: named policies that decide what a license key is allowed to do. Templates are assigned to access groups or directly to individual license keys; a key's effective permissions are the combination of everything assigned to it, and an explicit deny always wins over an allow. ### Template list The left panel lists templates in two tabs: - **Admin** — templates authored by your organization's admins. Fully editable. - **Griptape (read-only)** — templates authored and maintained by Griptape. You can attach them to access groups or license keys and audit their contents, but not modify them. Search filters both tabs. **+ Create Template** opens the creation dialog. ### Creating a template The creation dialog asks for: - **Template Name** - **Authoring mode**: - **Permission Builder** — configure permissions with the visual builder described below. This is the recommended mode. - **Raw Cedar** — write the policy by hand in a [Cedar](https://www.cedarpolicy.com/) editor after creation. The builder is not available for raw Cedar templates. - **Scope** (builder mode only) — **Studio-wide** or **Project-scoped** (see below). ### The permission builder Selecting a builder template opens its detail panel, which is organized top to bottom: #### Scope A template is either: - **Studio-wide** — applies across the entire studio, everywhere the license is used. - **Project-scoped** — linked to a specific project template on an engine; it overrides the studio defaults only within that project. The scope row also selects an **Engine**. Connecting an engine loads its manifest — the engine's installed libraries, project templates, models, and model providers — which populates the pickers throughout the builder, so you choose real resources by name instead of typing identifiers. Project-scoped templates additionally pick the **Linked project** from the engine's project templates. #### Default access The template's base posture, which decides what happens to anything you do not explicitly configure: - **Exploration (Allow all)** — everything is allowed by default; you deny specific things. Suited to look-development and R&D. - **Production (Deny All)** — everything is denied by default; you allow specific things. A lockdown posture for shows in production. Explicit per-permission choices keep their meaning if you later switch the posture. #### Assign to Attach the template to **access groups** and/or directly to individual **license keys**. Assignments through a group reach every key in the group. #### Permissions The permission list is a catalog of capabilities grouped by category. Each row can be set to **Allow** or **Deny**, or left on **Default** to follow the posture, and most rows can be **scoped to specific resources** (for example, deny only certain libraries rather than all of them): | Category | Capability | What it controls | Scopeable to | | ------------- | --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | ------------------ | | **Libraries** | Use libraries | Loading node libraries. | Specific libraries | | **Libraries** | Use libraries by lifecycle stage | Loading libraries at specific lifecycle stages (`STABLE`, `BETA`, `ALPHA`, `LABS`, `DEPRECATED`). | Lifecycle stages | | **Nodes** | Use nodes by lifecycle stage | Loading and instantiating nodes at specific lifecycle stages. | Lifecycle stages | | **Nodes** | Use nodes that run arbitrary code | Nodes that execute arbitrary Python or Cypher. | — | | **Projects** | Use projects | Loading and activating projects. | Specific projects | | **Models** | Use model providers | Every model under a provider. Denied models are filtered out of model pickers and blocked from invocation; nodes stay creatable. | Specific providers | | **Models** | Use model families | Models in specific families (e.g. Claude 4, GPT-4). Same picker filtering and invocation blocking as providers. | Specific families | | **Models** | Use specific models | Individual models by id. Same picker filtering and invocation blocking. | Specific models | When a template's settings collide with another template or access group that applies to the same license keys — for example, this template allows a capability that an assigned group's template denies — the affected rows are flagged with a conflict warning naming the conflicting templates and keys. Remember: a deny always wins. #### Compiled Cedar Under the hood the builder compiles your choices into [Cedar](https://www.cedarpolicy.com/) policy statements, which are what the engine actually enforces. The **Compiled Cedar Policy** section (collapsed by default) shows the Cedar from the last save, for auditing. ### Raw Cedar templates Templates created in **Raw Cedar** mode skip the builder and expose a Cedar editor with syntax validation — a template with syntax errors cannot be saved. A read-only statement summary above the editor breaks the policy down per statement, and the same **Assign to** controls apply. New raw templates start with `permit(principal, action, resource);` (allow everything), mirroring the builder's exploration default. ### Griptape-managed templates Selecting a template on the **Griptape (read-only)** tab shows a read-only detail view: its description, a per-statement breakdown of the permissions it grants, and its full Cedar source. Managed templates cannot be edited or deleted — the only actions are attaching them to (or detaching them from) access groups and license keys. ## How it fits together A typical rollout looks like: 1. **Create permission templates** in the Permission Editor — for example, an "Exploration" template for R&D and a locked-down "Production" template that only allows approved libraries and model providers. 1. **Create access groups** for each team or show, and assign each group the appropriate templates. 1. **Create a license key per user**, adding each key to the right access groups, and send each user their token. 1. Users **activate** their seats by pasting the token into the desktop application (through your [Admin Server](https://docs.griptapenodes.com/enterprise/admin_server/index.md) for on-premises deployments). License-activated users get the editor only — the Admin Dashboard never appears for them. 1. Monitor and manage seats from the License Keys section — watch active **sessions**, **release** stuck ones, **reissue** lost tokens, and **revoke** keys when someone leaves. # Admin Server The Admin Server runs inside your network and acts as the single host that talks to Griptape Cloud on behalf of your Griptape Nodes Application instances. Your instances point at the Admin Server instead of `cloud.griptape.ai` directly, and it forwards each request upstream. This lets you keep individual application instances off the public internet while they continue to license, run sessions, and use Griptape Cloud features. ## Why use it A studio that deploys Griptape Nodes on-premises usually does not want every application instance reaching the public internet on its own. The Admin Server gives you one place to manage that connection: - **Lock instances down.** Application instances talk only to the Admin Server, so they never need direct internet egress. - **One egress point.** Instead of opening internet access for each instance, you allow a single host through your firewall — one rule to manage and one place to audit outbound traffic. - **Central configuration.** You configure where Griptape Cloud lives and, optionally, which Cloud paths are allowed to leave your network — in one spot rather than per instance. If your application instances can already reach `cloud.griptape.ai` directly and that is acceptable for your environment, you do not need the Admin Server. For a diagram of where the Admin Server sits and what traffic flows through it, see [Architecture](https://docs.griptapenodes.com/architecture/#on-premises-configuration). ## Getting the Admin Server The Admin Server is provided to enterprise customers. [Contact Foundry](https://www.foundry.com/products/griptape/request-demo) to obtain it for your deployment. ## Capabilities - **Forwards Cloud requests.** It forwards your applications' Griptape Cloud requests to the configured upstream (`https://cloud.griptape.ai` by default) and preserves the caller's `Authorization` header, so licensing and sessions keep working through it. Griptape Cloud remains the authority on authentication and authorization. - **Validates at startup.** It validates the operator's Griptape Cloud API key once at startup, so a misconfigured deployment fails fast at boot rather than breaking later. - **Egress filtering.** You can optionally choose exactly which Cloud paths may leave your network (see [`forwarding`](#forwarding)). - **Health endpoint.** A local `GET /health` endpoint returns `{"status":"ok"}` for liveness and readiness probes. - **Structured logging.** Each request is logged once at completion (method, path, status, latency, client IP, bytes written) in JSON or text for auditing. ## Configuration The Admin Server reads its settings from a `config.yaml` file. Configuration is resolved in this order, with later sources overriding earlier ones: 1. Built-in defaults 1. The `config.yaml` file (the default path; it can be pointed elsewhere at startup) 1. Environment variables Which version this describes This page documents Admin Server 0.3.0 and later — run `./server -version` to check which one you have. On earlier versions `read_timeout` and `write_timeout` default to `30s`, which cuts streamed replies off mid-reply; see the [warning below](#server) for what to set. A complete `config.yaml` with the default values looks like this: ``` server: host: "0.0.0.0" port: 8080 read_header_timeout: "10s" write_stall_timeout: "60s" idle_timeout: "120s" shutdown_timeout: "10s" upstream: base_url: "https://cloud.griptape.ai" timeout: "120s" # Name of the environment variable that holds the Griptape Cloud API key. # The key value is never stored here — only the variable name. api_key_env: "GT_CLOUD_API_KEY" logging: level: "info" # debug | info | warn | error format: "json" # json | text forwarding: mode: "allow_all" # allow_all | allow | deny rules: [] ``` ### server | Key | Default | Description | | --------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `host` | `0.0.0.0` | Address the server listens on. | | `port` | `8080` | Port the server listens on. | | `read_header_timeout` | `10s` | Deadline for a request's headers to arrive (a duration string, e.g. `10s`). | | `write_stall_timeout` | `60s` | How long a single write to a client may block before the connection is dropped. Re-armed before every write, so it does not cap how long a response may take. `0` disables it. | | `idle_timeout` | `120s` | How long an idle keep-alive connection is held open between requests. | | `shutdown_timeout` | `10s` | Deadline for in-flight requests to finish during a graceful shutdown. | | `read_timeout` | `0` (off) | **Deprecated.** A deadline on reading the *whole* request, so it caps how long an upload may take. Use `read_header_timeout`. | | `write_timeout` | `0` (off) | **Deprecated.** A deadline on writing the *whole* response — any value above `0` cuts streamed responses off mid-reply. Use `write_stall_timeout`. | `write_timeout` breaks streamed responses Earlier versions of this page and of the example config shipped `read_timeout: "30s"` and `write_timeout: "30s"`. If your `config.yaml` contains either line, **set it to `"0"`**: ``` server: read_timeout: "0" write_timeout: "0" ``` Setting `"0"` is correct on every version. *Deleting* the lines only works on 0.3.0 and later, where the defaults are already `0` — on an earlier build a deleted line falls back to that build's `30s` default and replies keep getting cut off. Once every deployment is on 0.3.0 or later you can drop the lines entirely. `write_timeout` is a deadline on the entire response, measured from the moment the request arrives — not an idle timeout. Agent replies stream one token at a time and routinely take longer than 30 seconds, so the server cuts them off mid-reply once the deadline passes. In Griptape Nodes that looks like a chat response that stops mid-sentence, and in the application log like: ``` httpx.RemoteProtocolError: peer closed connection without sending complete message body ``` `write_stall_timeout` replaces it: it limits how long a single write may block, so a stalled client is still dropped while a healthy stream runs as long as it needs to. You do not have to edit the file to test this — the environment variable wins over `config.yaml`: ``` export SERVER_WRITE_TIMEOUT=0 # then restart the Admin Server ``` ### upstream | Key | Default | Description | | ------------- | --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `base_url` | `https://cloud.griptape.ai` | The Griptape Cloud root the server forwards to. | | `timeout` | `120s` | How long to wait for Griptape Cloud to *begin* responding. It never limits how long a response may take, so streamed replies are unaffected. A request that is **not** streamed sends nothing until the whole answer is ready, which is why this default is generous — raise it if long generations return `502 Bad Gateway`. | | `api_key_env` | `GT_CLOUD_API_KEY` | The **name** of the environment variable that holds the Griptape Cloud API key. | The Admin Server requires a Griptape Cloud API key, which it validates at startup to confirm the operator owns a Griptape organization. The key is **not** used on the request path — your applications still send their own `Authorization` header, which is forwarded untouched. The key value is never stored in `config.yaml`. Instead, `api_key_env` names the environment variable to read it from (default `GT_CLOUD_API_KEY`), and you set the key in the environment: ``` export GT_CLOUD_API_KEY="gt-..." ``` To use a different variable name, set `api_key_env` and export the key under that name. Never commit the API key Keep the API key in the environment, not in `config.yaml`. The config file only names the environment variable to read the key from, so the key itself never needs to live in a file you might check into version control. ### logging | Key | Default | Description | | -------- | ------- | --------------------------------------------------- | | `level` | `info` | Log verbosity: `debug`, `info`, `warn`, or `error`. | | `format` | `json` | Log output format: `json` or `text`. | The Admin Server writes logs to standard output and standard error — it does not write a log file. If you need one, redirect when you start it (`./server -config config.yaml > admin-server.log 2>&1`), or let your container runtime or service manager collect the streams. Set `format: "text"` if you will be reading the log by eye rather than feeding it to a log collector. Three lines are worth knowing when diagnosing a request that ended early: | Line | Meaning | | ------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `starting server` | Lists every timeout actually in effect, with `config.yaml` and environment overrides already resolved. Check this before anything else. | | `response stream aborted before completion` | A response could not be finished. Includes `bytes_out` (how far it got) and `request_id`. Logged as a warning because the usual cause is a user closing a tab mid-reply; the other cause is a write deadline elapsing. | | `panic recovered` | A bug in the Admin Server. The caller received a `500`. Worth reporting. | ### forwarding The `forwarding` block controls which Cloud paths may egress through the Admin Server. This is an egress control for your network, layered on top of the upstream's own access control. | Key | Default | Description | | ------- | ----------- | ----------------------------------------------------------------------------------------------- | | `mode` | `allow_all` | `allow_all`, `allow`, or `deny` (see below). | | `rules` | *(empty)* | Paths to allow or deny. Each rule is an absolute path; a trailing `/*` makes it a prefix match. | The three modes: - **`allow_all`** (default) — forward every path; `rules` is ignored. - **`deny`** — forward everything **except** matching paths. Best for carving out a few exceptions. - **`allow`** — forward **only** matching paths. A lockdown posture with an explicit, minimal surface. Each rule is an absolute path. A trailing `/*` makes it a prefix match (`/api/proxy/*` matches `/api/proxy` and anything under it); otherwise the match is exact. A path that is not permitted is answered locally with `403 {"error":"path not permitted"}` and never reaches the upstream. For example, to keep the Griptape Cloud model proxy in-network while forwarding everything else: ``` forwarding: mode: "deny" rules: - "/api/proxy/*" ``` For the most restricted posture, use `allow` mode and list only the routes the application needs at runtime. Nothing else egresses. These are the required routes — the Admin Server refuses to start if `allow` mode omits any of them: ``` forwarding: mode: "allow" rules: - "/api/sessions/*" # session allocation and lifecycle, including /api/sessions/{id} - "/api/session-renew" # keep a session alive - "/api/session-release" # end a session - "/api/users" # fetched on startup and on every heartbeat - "/api/organizations" # fetched on startup and on every heartbeat ``` This is the minimal set the product cannot run without. Add further rules only for Cloud features you want to permit (for example, `/api/proxy/*` for the model proxy). Egress control, not authentication Forwarding rules decide which paths may leave your network — they are not authentication; Griptape Cloud remains the authority on who may call what. The core routes the application needs at runtime (session lifecycle, user, and organization routes) can never be blocked: if your configuration would block them, the Admin Server refuses to start and names the offending routes. This means you can tighten egress without accidentally breaking the product. ## Environment variable overrides Every setting can be overridden with an environment variable, which takes precedence over `config.yaml`: | Variable | Default | Description | | ---------------------------- | --------------------------- | ----------------------------------------------------------------------------- | | `GT_CLOUD_API_KEY` | *(required)* | The Griptape Cloud API key (or the variable named by `upstream.api_key_env`). | | `SERVER_HOST` | `0.0.0.0` | Listen address. | | `SERVER_PORT` | `8080` | Listen port. | | `SERVER_READ_HEADER_TIMEOUT` | `10s` | Request-header deadline. | | `SERVER_WRITE_STALL_TIMEOUT` | `60s` | Per-write deadline for a client that stops reading. | | `SERVER_IDLE_TIMEOUT` | `120s` | Idle keep-alive connection deadline. | | `SERVER_SHUTDOWN_TIMEOUT` | `10s` | Graceful shutdown deadline. | | `SERVER_READ_TIMEOUT` | `0` (off) | **Deprecated.** Whole-request read deadline; caps upload duration. | | `SERVER_WRITE_TIMEOUT` | `0` (off) | **Deprecated.** Whole-response write deadline; cuts off streamed replies. | | `UPSTREAM_BASE_URL` | `https://cloud.griptape.ai` | Upstream Griptape Cloud root. | | `UPSTREAM_TIMEOUT` | `120s` | Upstream response-header timeout. | | `UPSTREAM_API_KEY_ENV` | `GT_CLOUD_API_KEY` | Name of the env var holding the API key. | | `LOG_LEVEL` | `info` | Log verbosity. | | `LOG_FORMAT` | `json` | Log output format. | | `FORWARDING_MODE` | `allow_all` | `allow_all`, `allow`, or `deny`. | | `FORWARDING_RULES` | *(empty)* | Comma-separated list of paths to allow or deny. | Duration values must carry a unit — `30s`, `2m` — and `0` disables a timeout. A value with no unit (`SERVER_READ_TIMEOUT=30`) makes the Admin Server refuse to start, rather than quietly running with a different timeout than you asked for. ## Running it 1. Set the Griptape Cloud API key in the environment: ``` export GT_CLOUD_API_KEY="gt-..." ``` 1. Provide a `config.yaml` (see [Configuration](#configuration)) and start the Admin Server. By default it listens on `0.0.0.0:8080` and forwards to `https://cloud.griptape.ai`. 1. Point your Griptape Nodes Application instances at the Admin Server's address instead of `cloud.griptape.ai`. If the API key is missing or invalid, or the upstream cannot be reached, the Admin Server logs the reason and exits without serving — so configuration problems surface at startup. ## Troubleshooting - **Chat replies stop mid-sentence**, or the application logs `peer closed connection without sending complete message body`. A streamed reply was cut off. Almost always `write_timeout` is set in your `config.yaml` — see the [warning above](#server). Set it to `"0"` (or `export SERVER_WRITE_TIMEOUT=0`, which overrides the file) and restart. The Admin Server's own log confirms it. The `starting server` line reports every timeout it is using, so check `write_timeout` there first; a `response stream aborted before completion` warning shows the server ended the response and how many bytes it had sent. If `write_timeout=0s` and replies are still truncated, the cut is happening somewhere else — check for a load balancer, ingress controller, or TLS terminator in front of the Admin Server, each of which has its own response timeout. - **`502 Bad Gateway` on a long generation that isn't streamed.** `upstream.timeout` limits how long Griptape Cloud may take to *begin* responding, and a non-streamed generation sends nothing until the whole answer is ready. Raise it. - **Large uploads fail partway through.** `read_timeout` limits the total time a request body may take to arrive, which caps upload size in practice. It is off by default on 0.3.0 and later; if your `config.yaml` sets it, set it to `"0"`. - **The server won't start.** Confirm `GT_CLOUD_API_KEY` is set and valid; the Admin Server validates it at startup and fails closed if it cannot. A startup error naming an environment variable (`invalid SERVER_WRITE_TIMEOUT: "30" is not a duration`) means a duration is missing its unit — use `30s`. - **`502` on every request.** The Admin Server cannot reach the upstream. Check network egress to `upstream.base_url`, DNS resolution, and TLS interception by corporate proxies. - **`403 {"error":"path not permitted"}`.** A `forwarding` rule is blocking that path. Adjust `forwarding.mode` / `forwarding.rules` if the path should egress. # Using the Admin Server Once your [Admin Server](https://docs.griptapenodes.com/enterprise/admin_server/index.md) is up and running, users connect to it from the Griptape Nodes desktop application by activating with a license instead of logging in through Griptape Cloud. This page walks through that flow from the user's perspective. ## Before you start Each user needs: - **The Griptape Nodes desktop application** installed. - **A license key.** These are issued to your organization; users should request one from their organization admin. - **The Admin Server address.** The URL where your Admin Server is reachable on your network, for example `http://admin.internal.example.com:8080`. Your Admin Server operator can provide this. ## Activate with a license ### 1. Open the license form Launch the desktop application. On the login screen, instead of signing in with Griptape Cloud, click **Activate with a License**. ### 2. Paste your license key In the **License Key** field, paste the license key you received from your organization admin. ### 3. Point at the Admin Server In the **Griptape Server Endpoint** field, replace the default Griptape Cloud address (`https://cloud.griptape.ai`) with your Admin Server's address: ``` http://admin.internal.example.com:8080 ``` This is the key step for an on-premises deployment: with the endpoint set, the application sends all of its Griptape Cloud traffic through the Admin Server instead of reaching the public internet directly. ### 4. Activate Click **Activate License**. The application validates the license and launches the engine as normal, taking you to the editor. From this point on, everything — licensing, sessions, and any Cloud features your organization has permitted — flows through the Admin Server. ## Reusing a saved license Licenses you have activated before are remembered. If you log out (or the license is deactivated), the login screen shows a **Saved licenses** section after you click **Activate with a License**. Click **Use** next to a saved license to re-activate it without pasting the key again. The configured server endpoint is also remembered, so you only need to enter the Admin Server address once. ## Troubleshooting - **Activation fails immediately.** Check that the license key was pasted completely and has not expired. If the key looks right, confirm the **Griptape Server Endpoint** is correct and that the machine can reach it — try opening `http:///health` in a browser; a healthy server responds with `{"status":"ok"}`. - **Activation worked before but fails now.** The license may have expired. Ask your organization admin for a new one. - **Some features return errors after activating.** If the Admin Server is configured with [forwarding rules](https://docs.griptapenodes.com/enterprise/admin_server/#forwarding), paths that are not permitted are rejected with `403 {"error":"path not permitted"}`. Ask your Admin Server operator whether the feature's routes are allowed to egress. - **Everything returns errors.** If every request fails, the Admin Server itself may be unable to reach Griptape Cloud. This is an operator-side issue — see the [Admin Server troubleshooting section](https://docs.griptapenodes.com/enterprise/admin_server/#troubleshooting). # Agent skills # Griptape Nodes Workflow Construction Guide This skill covers the full cold-start cycle (build → wire → run → read) against the engine's MCP server, plus the idioms and gotchas discovered from running real workflows. ## Mental Model - **Workflow**: Top-level namespace. Only ONE can be active at a time. Reset with `ClearAllObjectStateRequest`. - **Flow**: The canvas inside a workflow. A workflow has exactly one top-level "canvas" flow. Sub-flows are possible but rarely needed for scratch work. - **Node**: A unit of work with parameters (inputs, outputs, properties). - **Connection**: An edge between two parameters. Two kinds: - **Data flow**: A typed parameter on one node → a typed parameter on another. The engine derives execution order from data dependencies, so data connections alone are usually sufficient. - **Control flow**: `exec_out` → `exec_in`. Only needed when two nodes must run in a specific order but don't share data (e.g. side-effecting steps, branching). Skip these by default. - **Current Context**: A stack (workflow → flow → node). Most requests default to "current" when a name is omitted. ## What the MCP server actually exposes Every MCP tool corresponds 1:1 to a `RequestPayload` class registered in `SUPPORTED_REQUEST_EVENTS` (`src/griptape_nodes/servers/mcp.py`). The server name is prefixed onto each tool, so the request `CreateNodeRequest` is reachable as `griptape_nodes_CreateNodeRequest`. A few consequences worth remembering up front: - **There are no plural variants of individual requests.** `CreateNodesRequest`, `CreateConnectionsRequest`, etc. do not exist. To create N nodes, send N `CreateNodeRequest` calls — or wrap them in a single `EventRequestBatch` call (see below). - **`CreateNodeRequest` does not take parameter values.** Setting a parameter is always a separate `SetParameterValueRequest` after the node exists. There is no `parameter_values` / `inputs` shortcut on create. - **`EventRequestBatch` is the only fan-out primitive.** It is a synthetic tool (no matching `RequestPayload` class) that ships an ordered list of inner requests in one transport frame. Reach for it whenever you already know the shape of a build phase. See "EventRequestBatch: collapse a build phase into one round trip" below. ## Survey the Workspace First Before building anything, find out what is actually loaded in this engine. The set of registered libraries and the node types they expose is the catalog every later step draws from, so spend the round trips up front instead of guessing names that may not exist (or that exist in a different library than you expect). ### Find the workspace directory on disk The MCP surface does not expose `GetConfigValueRequest`, so resolve the workspace path by reading the user config file directly. On macOS / Linux it lives at: ``` ~/.config/griptape_nodes/griptape_nodes_config.json ``` The keys you care about are: - `workspace_directory`: absolute (or `~`-prefixed) workspace root. The sandbox library lives inside this directory. - `app_events.on_app_initialization_complete.libraries_to_register`: list of locally registered `griptape_nodes_library.json` paths. Entries can be either a string or an object `{"path": "...", "enabled": true}`. Reading these tells you where each library's source lives if you need to look at an existing node's Python module before writing a similar sandbox node. The sandbox subdirectory key (`sandbox_library_directory`) is optional and defaults to `sandbox_library`. The absolute sandbox path is therefore `/` (e.g. `~/Projects/.../GriptapeNodes/sandbox_library`). Do this once per session and remember the paths; they don't change mid-run. ### Survey the registered libraries via MCP ``` A. griptape_nodes_ListRegisteredLibrariesRequest() → list of library names currently loaded (e.g. "Griptape Nodes Library", "Sandbox Library"). If a library you expect is missing, no `DescribeNodeType` call against its node types will resolve. B. griptape_nodes_ListNodeTypesInLibraryRequest(library="") → call once per library you might pull from. The returned node-type names are the exact strings to pass to `CreateNodeRequest.node_type` and `DescribeNodeTypeRequest.node_type`. C. (optional) griptape_nodes_ListCategoriesInLibraryRequest(library="") → useful when you only care about a slice of a large library (e.g. only image nodes) and want to scope the next step. ``` Do this once per session, then reuse the catalog. Skip the survey only when you already know (from a prior call in the same session) which library provides each node type you intend to use. ## EventRequestBatch: collapse a build phase into one round trip `EventRequestBatch` (MCP tool name: `griptape_nodes_EventRequestBatch`) wraps an ordered list of inner requests in a single transport frame. Use it whenever the shape of the build phase is already decided — typical pattern is N `CreateNodeRequest` - M `SetParameterValueRequest` + K `CreateConnectionRequest` + one `AutoLayoutFlowRequest`, all in one round trip. Shape: ``` { "requests": [ {"request_type": "CreateNodeRequest", "request": {"node_type": "TextInput", "node_name": "TextInput_1"}}, {"request_type": "SetParameterValueRequest", "request": {"node_name": "TextInput_1", "parameter_name": "text", "value": "..."}} ], "timeout_ms": 60000 } ``` Behavior: - **Sequential dispatch.** The engine awaits inner requests in submission order (`for inner in batch.requests: await _dispatch_event_request(inner)`), so a `CreateNodeRequest` followed by a `SetParameterValueRequest` on that same node is safe inside one batch. - **Pre-flight validation.** Every inner request is constructed against its `RequestPayload` class before anything goes on the wire. An unknown `request_type`, a `request` that is not a JSON object, or unknown kwargs in the inner payload all reject the entire batch up front with a `TypeError` / `ValueError`. - **Per-slot failure isolation.** Once the batch is dispatched, a failure in one slot does not abort siblings. Failed slots come back as `{"ok": false, "details": "..."}` in the result array; successful slots come back as the same flattened object a single tool call would return. Walk every slot before assuming the batch succeeded. - **No nesting.** `EventRequestBatch` is intentionally absent from `SUPPORTED_REQUEST_EVENTS` and the inner `request_type` enum, so a batch cannot contain another batch. - **Default timeout scales with size.** `timeout_ms` defaults to `30000 × len(requests)` clamped at `300000` ms (5 min). Pass an explicit override when the last slot is `StartFlowRequest(wait_for_completion=True)` or any other long-running call; otherwise the synchronous run can eat the budget meant for the rest of the batch. `bool` is rejected explicitly so `True` cannot silently become 1ms. Return shape: a JSON array of trimmed slot responses in submission order. Each slot looks identical to the response that single-tool dispatch would have returned for that `request_type`. ``` [ {"ok": true, "node_name": "TextInput_1", "...": "..."}, {"ok": true, "finalized_value": "...", "...": "..."} ] ``` ### Critical idiom: pre-name nodes you reference later in the same batch Inside one batch you cannot read `CreateNodeResultSuccess.node_name` from an earlier slot before composing a later one — every entry is fixed when the batch is submitted. So either: - Pass an explicit `node_name` on every `CreateNodeRequest` and reuse those names verbatim in later `SetParameterValueRequest` / `CreateConnectionRequest` entries, or - Split the build across two batches: one that creates nodes (read the assigned names back from the result array), then a second that sets parameters and wires them up. The one-batch + explicit names path is almost always shorter and is what the batched recipe below uses. ## Canonical Cold-Start Recipe For a typical 3-node linear pipeline (`TextInput → Agent → DisplayText`), once the workspace survey above has confirmed the relevant library exposes those node types: ``` 1. griptape_nodes_EnsureWorkflowAndFlowRequest() → returns workflow_name, flow_name, created_workflow, created_flow. Idempotent: if both pieces are already in context, reuses them. 2. griptape_nodes_DescribeNodeTypeRequest(node_type="TextInput") 3. griptape_nodes_DescribeNodeTypeRequest(node_type="Agent") 4. griptape_nodes_DescribeNodeTypeRequest(node_type="DisplayText") → get exact parameter names/types/modes. Look for: - data input(s) (mode_allowed_input == true) - data output(s) (mode_allowed_output == true) - control params (type == "parametercontroltype"): exec_in / exec_out are usually skippable (see Mental Model) 5. griptape_nodes_CreateNodeRequest(node_type="TextInput") 6. griptape_nodes_CreateNodeRequest(node_type="Agent") 7. griptape_nodes_CreateNodeRequest(node_type="DisplayText") → each returns a flat `node_name` (e.g. "TextInput_1", "Agent_1"). Read it from the response, do not assume a naming convention. The default name comes from `metadata.display_name` and may include spaces (e.g. "Text Input_1"). Pass an explicit `node_name` if you want a stable handle. 8. griptape_nodes_SetParameterValueRequest( node_name="TextInput_1", parameter_name="text", value="...") → there is no `parameter_values` shortcut on CreateNode; set every non-default parameter with its own SetParameterValue call. 9. griptape_nodes_CreateConnectionRequest( source_node_name="TextInput_1", source_parameter_name="text", target_node_name="Agent_1", target_parameter_name="prompt") 10. griptape_nodes_CreateConnectionRequest( source_node_name="Agent_1", source_parameter_name="output", target_node_name="DisplayText_1", target_parameter_name="text") → data connections only; the engine orders execution from data dependencies. 11. griptape_nodes_AutoLayoutFlowRequest() → required after any multi-node build. Without it, every node lands at (0, 0) and the canvas shows them stacked on top of each other. Topologically sorts the graph and assigns column-and-row positions. Omit `flow_name` to lay out the current-context flow. 12. griptape_nodes_StartFlowRequest(wait_for_completion=True, completion_timeout_ms=60000) → omit flow_name; the handler uses the current-context flow. wait_for_completion blocks until the flow resolves or times out. 13. griptape_nodes_GetParameterValueRequest(node_name="DisplayText_1", parameter_name="text") → the terminal node's output. ``` Thirteen MCP calls: 1 ensure + 3 describe + 3 create + 1 set + 2 connect + 1 layout + 1 run + 1 read. Wider graphs scale linearly: every node adds one CreateNode plus its SetParameterValue calls; every edge adds one CreateConnection. ### Batched variant (4 round trips) The build phase (steps 5-11 above) has fixed shape, so it collapses into one `EventRequestBatch` call. The describe phase still benefits from being a separate batch because its results inform the build payload, and `StartFlowRequest` is usually kept out of the build batch so its long timeout does not gate the rest: ``` 1. EnsureWorkflowAndFlowRequest (1 call) 2. EventRequestBatch([ (1 call, runs sequentially) DescribeNodeTypeRequest("TextInput"), DescribeNodeTypeRequest("Agent"), DescribeNodeTypeRequest("DisplayText"), ]) 3. EventRequestBatch([ (1 call, runs sequentially) CreateNodeRequest(node_type="TextInput", node_name="TextInput_1"), CreateNodeRequest(node_type="Agent", node_name="Agent_1"), CreateNodeRequest(node_type="DisplayText", node_name="DisplayText_1"), SetParameterValueRequest(node_name="TextInput_1", parameter_name="text", value="..."), CreateConnectionRequest(source_node_name="TextInput_1", source_parameter_name="text", target_node_name="Agent_1", target_parameter_name="prompt"), CreateConnectionRequest(source_node_name="Agent_1", source_parameter_name="output", target_node_name="DisplayText_1", target_parameter_name="text"), AutoLayoutFlowRequest(), ]) 4. StartFlowRequest(wait_for_completion=True, completion_timeout_ms=60000) + GetParameterValueRequest("DisplayText_1", "text") (2 calls) ``` Four round trips instead of thirteen. Walk the result arrays after each batch and verify every slot returned `ok: true` before moving on; per-slot failures don't abort the rest of the batch, so a typo in slot 4 still lets slots 5 and 6 run against stale state. ## Key Idioms - **Survey the workspace before picking node types.** Read the JSON config at `~/.config/griptape_nodes/griptape_nodes_config.json` to learn where the sandbox directory and registered library JSONs live, then run `ListRegisteredLibrariesRequest` and `ListNodeTypesInLibraryRequest` for the libraries you care about before reaching for `DescribeNodeType`. The catalog tells you which node types actually exist in this engine and which library owns each one, which is the input both `DescribeNodeTypeRequest.library` and `CreateNodeRequest.specific_library_name` expect when the same name lives in more than one library. - **Discover before wiring.** Always call `DescribeNodeType` for each node type you intend to use before guessing parameter names. The cost is 3-5 calls up front but saves many round trips fighting typos and assumed-wrong names. - **Batch with `EventRequestBatch` once the shape is known.** Anything past the discovery phase usually has fixed shape (N creates + M sets + K connects + a layout). Wrap them in one `EventRequestBatch` call and pre-name every node you reference later in the same batch. Inspect every slot of the result array; per-slot failures do not abort siblings. Keep `StartFlowRequest` out of the build batch unless you raise `timeout_ms` to cover the synchronous run. - **Wire data, not control.** The engine derives execution order from data dependencies, so `exec_out` → `exec_in` connections are usually noise. Only add them when two nodes must run in a specific order but don't exchange data. - **Always run AutoLayout after a multi-node build.** Without it nodes land at (0, 0) and stack on top of each other. `AutoLayoutFlowRequest` is one round trip and idempotent; treat it as the closing step of any build phase. - **Use `wait_for_completion=True` on `StartFlowRequest`.** For workflows that touch LLMs, image generators, or long I/O, set `completion_timeout_ms` generously (60000+ ms). Otherwise the call returns the instant the flow is kicked off and you have to poll `GetNodeResolutionStateRequest` yourself. - **Omit `flow_name` on `StartFlowRequest`** when you just finished building a single flow. The handler defaults to the current-context flow. - **Read the response, don't assume names.** `CreateNodeResultSuccess.node_name` is the authoritative handle for every later call. Pass it verbatim into `SetParameterValueRequest`, `CreateConnectionRequest`, and `GetParameterValueRequest`. ## Response Shape Every MCP tool returns a trimmed object (the engine envelope is unwrapped server-side in `_trim_response`): ``` { "ok": true, "details": "", "altered_workflow_state": true|false, "...": "...payload fields..." } ``` `ok` reflects whether the engine produced a `*ResultSuccess` payload. The remaining fields are flattened from the inner result class, so for example `CreateNodeResultSuccess.node_name` is reachable as `response["node_name"]`, `EnsureWorkflowAndFlowResultSuccess` exposes `workflow_name`, `flow_name`, `created_workflow`, `created_flow` at the top level, and `AutoLayoutFlowResultSuccess` exposes `flow_name` and `positioned_nodes`. Failures surface as MCP tool errors with the engine's `result_details` message attached. `EventRequestBatch` returns a JSON **array** instead of a single object. Each slot mirrors the single-call shape above, so a batched build phase looks like `[{"ok": true, "node_name": "TextInput_1", ...}, {"ok": true, ...}, ...]`. Failed slots come back as `{"ok": false, "details": "..."}` in place; the rest of the array still executes. ## Gotchas ### DescribeNodeType may be partial `DescribeNodeType` probes by instantiating the node class. For node types whose `__init__` performs I/O (network, auth, disk), instantiation may fail. When that happens the response still succeeds but returns: - Full library-level `metadata` (category, description, display_name, etc.) - `parameters: []` (empty — instantiation failed before parameters were declared) - A WARNING-level entry in `details` naming the cause You still know what the node does, but you cannot see its parameter schema from MCP alone. Consider falling back to a different node type, or describe on a system where credentials are present. ### CreateNode can silently produce an ErrorProxyNode When a node fails to instantiate and `create_error_proxy_on_failure=True` (the default), the engine substitutes an `ErrorProxyNode` and reports success. Inspect the response's `node_type` (it will be the proxy class) or the `details` string if a later step fails mysteriously. If you need strict failure semantics, set `create_error_proxy_on_failure=False` on the request. ### Default node names contain spaces The engine names nodes after `metadata.display_name`, which is often human-readable with spaces (e.g. `"Text Input_1"`, `"Display Text_1"`). That works for every API, but it's easy to typo. Either pass an explicit `node_name` per request, or always read the returned `node_name` from `CreateNodeResultSuccess` and reuse it verbatim. ### Connection request field names are long `CreateConnectionRequest` uses the unabbreviated names `source_node_name`, `source_parameter_name`, `target_node_name`, `target_parameter_name`. There are no short aliases (`source`, `source_param`, etc.). A typo here surfaces as an opaque validation error from pydantic, not a friendly "unknown field" message. ### Only one workflow in context at a time `SetWorkflowContextRequest` refuses if a workflow is already in context. To swap, `ClearAllObjectStateRequest(i_know_what_im_doing=True)` first — this wipes EVERYTHING (nodes, flows, connections, workflow). There is no softer reset today. ### Agents cannot be interrupted mid-run There is no pause/cancel for a running flow today. Use `completion_timeout_ms` to bound the wait; if the timeout fires, `StartFlowRequest` returns a failure but the flow keeps running in the engine until it finishes or errors. A subsequent `StartFlowRequest` will fail with "Flow is already running" until it does. ## Tool Cheat Sheet | Goal | Tool | | --------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | | Bootstrap a workflow + flow from cold | `EnsureWorkflowAndFlowRequest` | | Fan N requests out in one round trip | `EventRequestBatch` (synthetic; pre-name nodes that later slots reference) | | Discover libraries / node types | `ListRegisteredLibrariesRequest`, `ListNodeTypesInLibraryRequest`, `ListCategoriesInLibraryRequest` | | Inspect a node type's parameters | `DescribeNodeTypeRequest` | | Create a node | `CreateNodeRequest` | | Wire a single edge | `CreateConnectionRequest` | | Lay out the canvas after a multi-node build | `AutoLayoutFlowRequest` | | Move a single node to an explicit position | `SetNodeMetadataRequest` (set `metadata.position`) | | Set a parameter value | `SetParameterValueRequest` | | Read a parameter value | `GetParameterValueRequest` | | Inspect a parameter's schema/details on a live node | `GetParameterDetailsRequest`, `ListParametersOnNodeRequest` | | Run synchronously | `StartFlowRequest(wait_for_completion=True, completion_timeout_ms=...)` | | Run from a specific node | `StartFlowFromNodeRequest` | | Resolve a single node without firing the control flow | `ResolveNodeRequest` | | Execute a single node directly | `ExecuteNodeRequest` | | Rename a node or flow | `RenameObjectRequest(allow_next_closest_name_available=True)` | | Lock or unlock a node | `SetLockNodeStateRequest` | | Reset a node's parameters to defaults | `ResetNodeToDefaultsRequest` | | Inspect state | `ListNodesInFlowRequest`, `ListConnectionsForNodeRequest`, `GetNodeResolutionStateRequest`, `GetNodeMetadataRequest`, `GetConnectionsForParameterRequest` | | Find nodes by Python class (e.g. StartFlow, Agent) | `ListNodesInFlowRequest(node_types=["StartFlow", "Agent"])` — returns only nodes whose class name matches; omit to get all nodes | | Register a sandbox node type from Python source already on disk | `RegisterSandboxNodeFromSourceRequest` (see Custom nodes below) | | Reset everything | `ClearAllObjectStateRequest(i_know_what_im_doing=True)` | ## Custom nodes If the task involves writing a new node type via `RegisterSandboxNodeFromSourceRequest`, read the [node development overview](https://docs.griptapenodes.com/en/stable/development/custom_nodes/index.md), the [parameters reference](https://docs.griptapenodes.com/en/stable/development/custom_nodes/parameters/index.md), and the [execution and lifecycle guide](https://docs.griptapenodes.com/en/stable/development/custom_nodes/execution_and_lifecycle/index.md) **before** drafting source. These pages document the engine-side conventions a sandbox class must follow: - `BaseNode` subclassing and the `process` / `aprocess` contract - `Parameter` declaration, modes (`mode_allowed_input` / `..._property` / `..._output`), traits - `ParameterString` / `ParameterImage` / etc. helpers (preferred over hand-rolled `Parameter`) - `ParameterGroup` / `ParameterList` containers - Connection rules and node states `RegisterSandboxNodeFromSourceRequest` only **registers** Python source already on disk inside the sandbox library directory; it never writes the file itself. The agent is responsible for placing the `.py` file under `/` (via its own filesystem tool, e.g. pi's `write`) before issuing the request. The imported source then runs inside the engine process with no isolation, so matching the conventions up front is faster than iterating on registration failures. For pure workflow-driving tasks (build → wire → run → read) the guides are overkill — stick to this skill. ## Example: One-Shot Haiku Pipeline Goal: run an `Agent` on a one-line prompt and read the output. 1. `EnsureWorkflowAndFlowRequest()` 1. `DescribeNodeTypeRequest(node_type="TextInput")` → text output parameter is `text` 1. `DescribeNodeTypeRequest(node_type="Agent")` → input `prompt`; output `output` 1. `DescribeNodeTypeRequest(node_type="DisplayText")` → input `text` 1. `CreateNodeRequest(node_type="TextInput")` → read assigned `node_name` 1. `CreateNodeRequest(node_type="Agent")` → read assigned `node_name` 1. `CreateNodeRequest(node_type="DisplayText")` → read assigned `node_name` 1. `SetParameterValueRequest(node_name="TextInput_1", parameter_name="text", value="Write a haiku about clouds.")` 1. `CreateConnectionRequest(TextInput_1.text → Agent_1.prompt)` 1. `CreateConnectionRequest(Agent_1.output → DisplayText_1.text)` 1. `AutoLayoutFlowRequest()` → arrange the 3 nodes across columns 1. `StartFlowRequest(wait_for_completion=True, completion_timeout_ms=60000)` 1. `GetParameterValueRequest(node_name="DisplayText_1", parameter_name="text")` Total: 13 MCP calls from empty engine to rendered output. ### Same pipeline, batched (4 round trips) 1. `EnsureWorkflowAndFlowRequest()` 1. `EventRequestBatch([DescribeNodeTypeRequest × 3])` 1. `EventRequestBatch([CreateNodeRequest × 3 (with explicit node_name), SetParameterValueRequest, CreateConnectionRequest × 2, AutoLayoutFlowRequest])` 1. `StartFlowRequest(wait_for_completion=True, completion_timeout_ms=60000)` then `GetParameterValueRequest("DisplayText_1", "text")` The build batch in step 3 only works because every `CreateNodeRequest` carries an explicit `node_name`; the later `SetParameterValueRequest` and `CreateConnectionRequest` slots reference those names directly instead of waiting on the per-create response. ## Further reading For anything beyond the build → wire → run → read scope of this skill, start at [`/for_agents/`](https://docs.griptapenodes.com/en/stable/for_agents/index.md). It is the canonical entry point to the engine's machine-readable doc surface — it explains the trade-offs between `/llms.txt` (curated index), `/llms-full.txt` (full corpus in one fetch), and per-page `.md` files, and lists the highest-value pages for grounding an agent in the engine's actual API. # Node libraries # Node Libraries This section documents **node libraries** — installable bundles of nodes that extend Griptape Nodes. The [Standard Library](https://docs.griptapenodes.com/nodes/overview/index.md) ships installed by default; every other library lives in its own Git repository and is installed through the editor's **Libraries** panel or the `gtn` CLI. For how installation, updating, dependency isolation, and Shared/Isolated modes work, see the [Libraries guide](https://docs.griptapenodes.com/guides/libraries/index.md). ## Documented libraries | Library | Nodes | What it's for | Requirements | | ------------------------------------------------------------------------------------------ | ----- | ---------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------- | | [Standard Library](https://docs.griptapenodes.com/nodes/overview/index.md) | 284 | The default nodes — agents, text, images, video, audio, lists, dicts, JSON, execution flow, and more | None (installed by default) | | [Advanced Media Library](https://docs.griptapenodes.com/libraries/advanced_media/index.md) | 28 | Local media generation and manipulation — diffusion pipelines, image/video aux processing, LoRAs, face detection | GPU recommended; Hugging Face account for model downloads | | [OpenColorIO](https://docs.griptapenodes.com/libraries/opencolorio/index.md) | 2 | Professional color management using OCIO configs — the industry-standard system for film, VFX, and animation pipelines | An OCIO config (`$OCIO` or explicit path) | | [OpenEXR](https://docs.griptapenodes.com/libraries/openexr/index.md) | 4 | Load, inspect, display, and save OpenEXR files for VFX and HDR image workflows | None (optionally pairs with OpenColorIO) | | [Nuke](https://docs.griptapenodes.com/libraries/nuke/index.md) | 3 | Run `.nk` scripts headlessly from the canvas and publish workflows as versioned Nuke gizmos | A local Foundry Nuke installation and license | | [Diffusers](https://docs.griptapenodes.com/libraries/diffusers/index.md) | 19 | Modular 🧨 Diffusers pipelines — build media generation workflows from individual, connectable diffusion stages | GPU (CUDA or MPS) | This is not an exhaustive list — many more first-party and community libraries are available in the [Griptape Nodes directory](https://github.com/griptape-ai/griptape-nodes-directory). You can also browse them from the editor via the **Browse Community Libraries** button in the **Add Library** modal. ## Installing a library The Standard Library is registered automatically, and the Advanced Media Library is offered during `gtn init`. Every other library on this page installs the same way: 1. In the editor, open **Manage → Library Management**. 1. Click **Add Library** and paste the library's Git URL (listed on each library's overview page). 1. Click **Install**. The engine clones the repository, installs the library's Python dependencies into an isolated virtual environment, and registers its nodes. Or from the command line: ``` gtn libraries download ``` Each library's dependencies are isolated from the engine and from every other library, so installing any combination of these libraries is safe. See [Coexistence guarantees](https://docs.griptapenodes.com/guides/libraries/#coexistence-guarantees) for details. ## How these docs are organized Each library gets: - An **overview page** — what the library does, how to install it, any prerequisites (environment variables, hardware, accounts), and its settings. - A **reference page per node** — a consistent format with a TL;DR, the node's typical position in a workflow, and inputs/outputs/parameters tables. # Standard Library This is the node reference for the **Standard Library** (`Griptape Nodes Library`) — the default nodes that ship with Griptape Nodes. It focuses on individual nodes, their specific capabilities, and how they can interact with each other, explaining: - Core functionality and purpose - Configuration options and parameters - Input/output behaviors # Diffusion Pipelines You need to perform setup steps to use Hugging Face Diffusion Pipeline nodes [This guide](https://docs.griptapenodes.com/guides/integrations/hugging_face/index.md) will walk you through setting up a Hugging Face account, creating an access token, and installing the required models to make this node fully functional. ## What are they? The Diffusion Pipeline system consists of two complementary nodes that work together to provide efficient image generation: - **Diffusion Pipeline Builder**: Builds and caches 🤗 Diffusers Pipelines for reuse across multiple execution nodes - **Generate Image (Diffusion Pipeline)**: Generates images using the cached pipelines This modular approach allows you to configure a pipeline once and reuse it multiple times, improving performance and resource efficiency. The system supports a wide range of providers and models through dynamic parameters. ## Supported Providers The Diffusion Pipeline Builder supports multiple AI model providers: - **Flux** - High-quality text-to-image generation - **Qwen** - Multimodal capabilities - **Stable Diffusion** - Popular open-source diffusion models - **Allegro** - Video generation capabilities - **Amused** - Efficient masked image modeling - **AudioLDM** - Audio generation from text - **WAN** - Specialized image generation - **Wuerstchen** - Efficient diffusion architecture - **Custom** - Support for custom pipeline configurations and self-provided models ## When would I use it? Use these nodes when you need to: - Generate images from textual descriptions with various model architectures - Leverage advanced image generation models for creative projects - Experiment with different providers and model configurations - Optimize performance by reusing cached pipelines across multiple generations - Work with specialized models for audio, video, or multimodal generation ## How to use it ### Basic Setup The Diffusion Pipeline system uses a two-node workflow: 1. **Configure the Builder**: - Add a "Diffusion Pipeline Builder" node to your workflow - Select your desired provider (Flux, Stable Diffusion, etc.) - Configure provider-specific parameters (model, LoRAs, optimizations) - Run the builder to cache the pipeline 1. **Generate Images**: - Add a "Generate Image (Diffusion Pipeline)" node - Connect the pipeline output from the builder to the runtime node - Configure generation parameters (prompt, dimensions, steps, etc.) - Run the runtime node to generate images ### Pipeline Builder Parameters The builder node has dynamic parameters that change based on the selected provider: - **provider**: Select from supported providers (Flux, Stable Diffusion, etc.) - **Provider-specific parameters**: Model selection, LoRA configurations, optimization settings - **pipeline**: Output connection containing the cached pipeline configuration ### Runtime Parameters The runtime node parameters are dynamically generated based on the connected pipeline: - **pipeline**: Input connection from the builder node - **Dynamic generation parameters**: Prompts, dimensions, inference steps, guidance scales - **output_image**: The generated image as an ImageArtifact - **seed**: An integer seed for random number generation - **logs**: Detailed logs of the generation process Dynamic Parameters Both nodes use dynamic parameters that automatically adjust based on your selections. The available parameters will change when you select different providers or connect different pipelines. ### Advanced Features - **Pipeline Caching**: Built pipelines are cached using configuration hashes for efficient reuse - **LoRA Support**: Load and configure LoRA adapters for model customization - **Optimization Options**: Enable various optimizations for better performance - **Real-time Previews**: Optional intermediate image previews during generation (may slow inference) - **Connection Preservation**: Runtime node preserves parameter connections when pipeline changes ## Manual Memory Settings The Diffusion Pipeline Builder ships with `memory_optimization_strategy` set to **Manual** by default. Manual mode is the default because the Automatic strategy is conservative — it enables every optimization needed to fit the model, which bottlenecks powerful GPUs and slows generation on capable hardware. The tradeoff is that manual mode exposes a row of toggles that assume some familiarity with [🤗 Diffusers memory optimization concepts](https://huggingface.co/docs/diffusers/main/en/optimization/memory). This section explains each manual-mode parameter: what it does, the tradeoff it makes, and a heuristic for when to enable it. ### `attention_slicing` - **What it does**: Computes the attention operation in sequential slices instead of all at once, lowering peak VRAM during attention. - **Tradeoff**: Lower memory at the cost of speed (often 5–20% slower). - **When to enable**: When you hit out-of-memory errors during generation, particularly on GPUs with less than 8 GB VRAM, on Apple Silicon (MPS) with less than 64 GB unified memory, or on CPU. Leave off when you have headroom. ### `vae_slicing` - **What it does**: Decodes the VAE latent in batched slices rather than as a single tensor. - **Tradeoff**: Lower peak memory during the VAE decode step, with negligible speed cost. - **When to enable**: When generating multiple images in a single batch, or when decoding at higher resolutions exhausts VRAM in the final step. Cheap to leave on; effectively free for batch sizes of 1. ### `transformer_layerwise_casting` - **What it does**: Stores the transformer (or UNet) weights in fp8 (`float8_e4m3fn`) and upcasts each layer to bfloat16 only while it is computing. - **Tradeoff**: Roughly halves transformer weight memory vs. bfloat16, with a small speed cost from per-layer casting and a small quality hit on some models. - **When to enable**: When the model fits in VRAM only after weight compression, but you don't want a full quantization pass. Skip if the pipeline is pre-quantized or doesn't support layerwise casting — the node logs a notice and ignores the toggle in those cases. ### `cpu_offload_strategy` - **What it does**: Moves pipeline components between CPU RAM and GPU VRAM to reduce GPU memory residency. - **Choices**: - **None** — All components stay on the GPU. Fastest, requires the most VRAM. - **Model** — One full submodel (e.g. text encoder, transformer, VAE) is on the GPU at a time; others live in CPU RAM and swap in as needed. Moderate VRAM savings, modest speed penalty. - **Sequential** — Even finer-grained: individual `nn.Module` layers are streamed to GPU on demand. Largest VRAM savings, largest speed penalty (often several times slower). - **When to use which**: - **None** if the pipeline already fits with room to spare. - **Model** if you're a few GB short of fitting everything resident. - **Sequential** as a last resort to run a model that wouldn't otherwise load. ### `quantization_mode` - **What it does**: Quantizes pipeline weights via `optimum-quanto` to `fp8`, `int8`, or `int4` before inference. - **Tradeoff**: Significant memory savings (`int4` ≈ 1/4 the size of bfloat16) at increasing risk of quality loss as the bit width drops. The first run also pays a one-time quantization cost. - **When to enable**: When even with offloading the model won't fit, or when you want to free VRAM for other work (larger batch, longer context, additional LoRAs). `fp8` is usually a safe starting point; drop to `int8`/`int4` only if needed. ### If you run out of memory Escalate one knob at a time in this order: enable `vae_slicing` → enable `attention_slicing` → switch `cpu_offload_strategy` to `Model` → enable `transformer_layerwise_casting` → step `quantization_mode` down (`fp8` → `int8` → `int4`) → switch `cpu_offload_strategy` to `Sequential`. ### When in doubt: switch to Automatic Automatic mode runs the pipeline through a memory-aware decision tree (see `_automatic_optimize_diffusion_pipeline` in `pipeline_utils.py`) that enables only the optimizations needed to fit the model on the detected device. It's slower than a hand-tuned manual configuration on capable hardware, but it's a safe fallback when you don't want to hand-pick settings. For a deeper treatment of the underlying concepts, see the upstream [🤗 Diffusers memory optimization guide](https://huggingface.co/docs/diffusers/main/en/optimization/memory). ## Performance Optimization - **Reuse Pipelines**: Build once, generate many times by connecting multiple runtime nodes to one builder - **Cache Management**: Pipelines are automatically cached and reused across workflow runs - **Memory Management**: Configure optimization settings in the builder for your hardware - **Preview Settings**: Disable intermediate previews for faster generation ## Common Issues - **Missing API Key**: Ensure the Hugging Face API token is set as `HUGGINGFACE_HUB_ACCESS_TOKEN`; instructions for that are in [this guide](https://docs.griptapenodes.com/guides/integrations/hugging_face/index.md) - **Pipeline Not Found**: If you see cache errors, ensure the builder node has been executed successfully - **Memory Constraints**: Large models or high-resolution generation may require significant GPU memory - **Provider Compatibility**: Ensure your selected model is compatible with the chosen pipeline type # YOLOv8 Face Detection You need to perform setup steps to use Hugging Face models [This guide](https://docs.griptapenodes.com/guides/integrations/hugging_face/index.md) will walk you through setting up a Hugging Face account, creating an access token, and installing the required models to make this node fully functional. ## What is it? YOLOv8 Face Detection is a computer vision node that detects human faces in images using the YOLOv8 (You Only Look Once version 8) object detection model from 🤗 Hugging Face. The node processes images and returns bounding box coordinates for each detected face along with confidence scores. The implementation uses the `arnabdhar/YOLOv8-Face-Detection` model, which is specifically trained for face detection tasks and provides fast, accurate results suitable for real-time applications. ## When would I use it? Use this node when you need to: - Detect faces in images for face recognition pipelines - Crop or extract face regions from photos - Count the number of people in an image - Filter images based on face presence or absence - Create face-focused compositions or effects - Process images for privacy protection (face blurring/masking) - Build automated photo organization systems - Implement face-based access control systems ## How to use it ### Basic Setup 1. **Add the Node**: - Add a "YOLOv8 Face Detection" node to your workflow - The node will automatically download the model on first use (requires Hugging Face access) 1. **Connect Input**: - Connect an `ImageArtifact` or `ImageUrlArtifact` to the `input_image` parameter - This can come from file loaders, image generation nodes, or other image processing nodes 1. **Configure Parameters**: - Set the `confidence_threshold` (0.0-1.0) to filter detections - Optionally set `dilation` (0-100%) to expand the detected bounding boxes 1. **Run Detection**: - Execute the node to detect faces in the input image - The `detected_faces` output will contain a list of face detections ### Parameters #### Input Parameters - **input_image** (required) - Type: `ImageArtifact` or `ImageUrlArtifact` - The image to analyze for face detection - **confidence_threshold** - Type: `float` (0.0-1.0) - Default: `0.5` - Minimum confidence score for a detection to be included in the results - Higher values = fewer but more confident detections - Lower values = more detections but may include false positives - **dilation** - Type: `float` (0.0-100.0) - Default: `0.0` - Percentage to expand bounding boxes while keeping them centered - Useful for including more context around detected faces - Example: `10.0` expands the box by 10% in all directions #### Output Parameters - **detected_faces** - Type: `list` - A list of detected faces, each containing: - `x`: Top-left x-coordinate of the bounding box - `y`: Top-left y-coordinate of the bounding box - `width`: Width of the bounding box - `height`: Height of the bounding box - `confidence`: Detection confidence score (0.0-1.0) - **logs** - Type: `string` - Detailed logs of the detection process including: - Model loading status - Detection parameters - Number of faces detected ### Output Format Each detected face is represented as a dictionary: ``` { "x": 150, "y": 200, "width": 300, "height": 350, "confidence": 0.95 } ``` The bounding box coordinates are in pixels relative to the input image dimensions, with the origin (0,0) at the top-left corner. ### Example Workflows #### Basic Face Detection 1. Load an image using "File to Bytes" or similar node 1. Connect to YOLOv8 Face Detection's `input_image` 1. Set `confidence_threshold` to `0.5` for balanced detection 1. The `detected_faces` output contains all face locations and confidence scores #### Face Cropping Pipeline 1. Detect faces using YOLOv8 Face Detection 1. Set `dilation` to `10.0` to include some background around faces 1. Connect `detected_faces` to a "Crop Image" node 1. Extract individual face images for further processing #### High-Confidence Detection Only 1. Set `confidence_threshold` to `0.8` or higher 1. This filters out uncertain detections 1. Ideal for applications requiring high precision ### Advanced Features - **Automatic Model Caching**: Downloaded models are cached locally for faster subsequent runs - **Boundary Clamping**: Dilated bounding boxes are automatically clamped to image boundaries - **Centered Dilation**: Box expansion maintains the center point of the original detection - **Batch-Compatible**: Process multiple images by connecting to loop structures ## Performance Considerations - **First Run**: The initial execution downloads the model (~6MB), which may take a few moments - **Subsequent Runs**: Cached models load almost instantly - **Image Size**: Larger images take longer to process but may detect more distant faces - **Detection Speed**: YOLOv8 is optimized for real-time performance, typically processing images in milliseconds - **Memory Usage**: Model requires ~50MB of RAM when loaded ## Common Issues - **Missing API Key**: Ensure the Hugging Face API token is set as `HF_TOKEN`; instructions for that are in [this guide](https://docs.griptapenodes.com/guides/integrations/hugging_face/index.md) - **Model Not Found**: If you see "model not available" warnings, click the provided link to open the Model Manager and download the model - **No Faces Detected**: Try lowering the `confidence_threshold` if you expect faces but none are detected - **Too Many False Positives**: Increase the `confidence_threshold` to filter out low-confidence detections - **Bounding Box Issues**: If boxes seem too tight, increase the `dilation` parameter to add padding ## Technical Details - **Model**: YOLOv8 Face Detection from Hugging Face (`arnabdhar/YOLOv8-Face-Detection`) - **Architecture**: YOLOv8 object detection framework specialized for face detection - **Dependencies**: `ultralytics>=8.0.0`, `supervision>=0.20.0` - **Output Format**: Standard bounding box format (x, y, width, height) with confidence scores - **Processing**: Single-pass detection, optimized for speed and accuracy # Agent ## What is it? The Agent node lets you configure an AI Agent with customizable capabilities like tools and rulesets. This node can create an Agent for immediate use given it's own prompt, or can be passed to other nodes' "agent" inputs. ## When would I use it? Use this node when you want to: - Create a configurable AI Agent from scratch - Set up an Agent with specific tools and rulesets - Prepare an Agent that can be reused across your workflow - Get immediate responses from your agent using a custom prompt ## How to use it ### Basic Setup 1. Add the Agent to your workflow 1. Configure the agent's capabilities (tools and rulesets) ### Parameters - **agent**: An existing Agent configuration (optional). If specified, it will use the existing Agent when prompting. - **provider**: The AI provider to use (e.g., Griptape Cloud, Ollama, LM Studio, or a custom endpoint). See [AI Providers](https://docs.griptapenodes.com/guides/agent/providers/index.md) for setup instructions. - **prompt model**: The specific model from the selected provider. - **prompt**: The instructions or question you want to ask the Agent. - **additional_context**: String or key-value pairs providing additional context to the Agent. - **tools**: Capabilities you want to give your Agent. - **rulesets**: Rules that tell your Agent what it can and cannot do. - **output_schema**: A JSON Schema template that defines the exact format you want the Agent's response to follow (optional). ### Outputs - **output**: The text response from your agent (if a prompt was provided) - **agent**: The configured agent object, which can be connected to other nodes ## Example Imagine you want to create an Agent that can write haikus based on prompt_context: 1. Add a KeyValuePair 1. Set the "key" to "topic" and "value" to "swimming" 1. Add an Agent 1. Set the Agent "prompt" to "Write me a haiku about {{topic}}" 1. Connect the KeyValuePair dictionary output to the Agent "prompt_context" input 1. Run the workflow 1. The Agent "output" will contain a haiku about swimming! ## Using Output Schemas ### What is an Output Schema? Think of an output schema as a form you're asking the AI to fill out. Instead of getting a free-form text response, you can specify exactly what pieces of information you want and how they should be organized. For example, instead of asking "Tell me about this product" and getting a paragraph of text, you can ask for: - A product name (text) - A price (number) - Whether it's in stock (yes/no) - A list of features (multiple text items) The AI will then respond with structured data that matches exactly what you asked for. ### Why Use an Output Schema? Use output schemas when you need: - **Consistent formats**: Every response follows the same structure, making it easier to process - **Specific data types**: Guarantee you get numbers where you need numbers, lists where you need lists, etc. - **Easier automation**: Structured data is much easier to connect to other nodes in your workflow - **Validation**: The AI must provide all required fields in the correct format ### How to Create an Output Schema 1. Add a **JSON Input** node to your workflow 1. Add a JSON Schema that defines the output that you want (you can use online tools to help create this) ### Output Schema Example Let's say you want to extract information about a restaurant from a review: 1. Create Schema Fields: - Field "restaurant_name" (type: string) - Field "rating" (type: integer) - Field "price_range" (type: string) - Field "cuisine_type" (type: string) - Field "recommended_dishes" (type: list, list_type: string) 1. Connect all fields to a Create Schema node 1. Connect the schema to your Agent's output_schema input 1. Set your Agent's prompt: "Extract restaurant information from this review: [review text]" 1. The Agent will now respond with structured data instead of plain text, containing exactly those fields 1. The generated schema will look like this: ``` { "type": "object", "properties": { "restaurant_name": { "type": "string" }, "rating": { "type": "integer" }, "price_range": { "type": "string" }, "cuisine_type": { "type": "string" }, "recommended_dishes": { "type": "array", "items": { "type": "string" } } }, "required": ["restaurant_name", "rating", "price_range", "cuisine_type", "recommended_dishes"] } ``` ### What Changes When Using a Schema? - **Output type**: The Agent's output changes from plain text to structured data (JSON format) - **Validation**: If the AI cannot provide data in the requested format, it will try again or return an error ## Important Notes - If you don't provide a prompt, the node will create the agent without running it and the output will contain exactly "Agent Created" - The node supports both streaming and non-streaming prompt drivers - Tools and rulesets can be provided as individual items or as lists - The additional_context parameter allows you to provide additional_context to the agent as a string or dictionary of key/value pairs - By default the node uses Griptape Cloud, which requires a valid `GT_CLOUD_API_KEY`. You can switch to a local or custom provider using the **provider** parameter — see [AI Providers](https://docs.griptapenodes.com/guides/agent/providers/index.md). - When you pass an Agent from one node to another using the agent input/output pins, the conversation memory is maintained, which means: - The Agent "remembers" previous interactions in the same flow - Context from previous prompts influences how the Agent interprets new prompts - You can build multi-turn conversations across multiple nodes - The Agent can reference information provided in earlier steps of your workflow - Don't know how to create a JSON Schema for the output_schema parameter? Use an online tool like [JSON Schema Builder](https://transform.tools/json-to-json-schema) to create one based on your desired output structure. Or just give an Agent an example of the output you want and have it generate the schema for you! ## Controlling Agent Behavior in Workflows To shape how an Agent behaves in a workflow, use **Rulesets** (or a plain **TextInput** connected to the `Behaviors (Rulesets)` input). These let you define what the Agent can and cannot do, set its persona, or constrain its output style. Skills are not supported in the Agent node The `.agents/skills` folder used by the [chat sidebar](https://docs.griptapenodes.com/guides/agent/skills/index.md) does not apply to Agent nodes in workflows. Use a **Ruleset node** or a **TextInput** connected to the Agent's `Behaviors (Rulesets)` input to control agentic behavior instead. ## Common Issues - **No provider configured**: If no provider is set, the node uses Griptape Cloud by default, which requires a valid `GT_CLOUD_API_KEY`. See [AI Providers](https://docs.griptapenodes.com/guides/agent/providers/index.md) to add Ollama, LM Studio, or a custom endpoint. - **Streaming Issues**: If using a streaming prompt driver, ensure your flow supports handling streamed outputs. # LoadAudio ## What is it? The LoadAudio node is a simple building block that lets you bring an audio file into your workflow. Think of it as picking up an audio recording so you can use it in your project. ## When would I use it? Use this node when you want to: - Use an audio file that was created by another node - Pass an audio file to other nodes in your workflow - Process an audio file as part of your project ## How to use it ### Basic Setup 1. Add the LoadAudio node to your workflow 1. Connect it to a source of audio (like the Transcribe Audio node) ### Parameters - **audio**: The audio to load (this can be connected to an output from another node) ### Outputs - **audio**: The loaded audio that can be used by other nodes in your flow ## Example Imagine you've recorded some audio and have it saved on your computer, and you'd like to use it elsewhere: 1. Create a LoadAudio node 1. Click on the load audio button, and choose your audio file from disk 1. The LoadAudio will make the audio available to use in the rest of your workflow ## Important Notes - The LoadAudio simply passes the audio through - it doesn't change the audio itself - You can click the file browser icon to select an audio file from your computer - The audio can be played by clicking the play button # Microphone ## What is it? The Microphone node allows you to capture audio directly from your computer's microphone. It's a simple way to record audio input that can be used in your workflow. ## When would I use it? Use this node when you want to: - Record audio directly from your microphone - Capture voice input for transcription - Create audio recordings as part of your workflow - Provide real-time audio input to other nodes ## How to use it ### Basic Setup 1. Add the Microphone node to your workflow 1. Click the microphone icon to start recording 1. Speak or provide audio input 1. Stop recording when finished ### Parameters - **audio**: The captured audio output (AudioUrlArtifact) ### Outputs - **audio**: The recorded audio that can be used by other nodes in your flow ## Example A simple workflow to record and transcribe audio: 1. Add a Microphone node to your workflow 1. Record your audio input 1. Connect the "audio" output to a TranscribeAudio node 1. The transcription will be available in the TranscribeAudio node's output ## Important Notes - The node requires microphone permissions in your browser - Audio is captured in real-time - The quality of the recording depends on your microphone and system settings - Whatever format your browser records in, the capture is saved to a file and output as an AudioUrlArtifact ## Common Issues - **No Microphone Access**: Ensure your browser has permission to access your microphone - **Poor Audio Quality**: Check your microphone settings and system audio configuration - **Recording Not Starting**: Make sure no other application is using the microphone # TranscribeAudio ## What is it? The TranscribeAudio node uses OpenAI's models to convert audio into text. It supports multiple transcription models and can work with both direct model configuration and agent-based transcription. ## When would I use it? Use this node when you want to: - Convert speech to text - Transcribe audio recordings - Extract text from audio files - Process voice input into written form - Create transcripts of audio content ## How to use it ### Basic Setup 1. Add the TranscribeAudio node to your workflow 1. Connect an audio source to the "audio" input 1. Choose a transcription model or connect an agent 1. Run the node to generate the transcription ### Parameters - **agent**: An optional existing agent configuration to use for transcription - **model**: The transcription model to use (defaults to "gpt-4o-mini-transcribe") - **audio**: The audio file to transcribe (required) - **output**: The transcribed text output ### Outputs - **output**: The transcribed text from the audio - **agent**: The agent object used for transcription, which can be connected to other nodes ## Example A complete workflow for recording and transcribing audio: 1. Add a Microphone node to capture audio 1. Connect the Microphone's "audio" output to the TranscribeAudio node's "audio" input 1. Select your preferred transcription model 1. Run the workflow 1. The transcribed text will be available in the "output" parameter ## Important Notes - The node requires a valid OpenAI API key set up in your environment as `OPENAI_API_KEY` - Available models include: - gpt-4o-mini-transcribe - gpt-4o-transcribe - whisper-1 - You can provide your own agent configuration for more customized behavior - The quality of transcription depends on the audio quality and the selected model ## Common Issues - **Missing API Key**: Ensure your OpenAI API key is properly set up as the environment variable - **No Audio Provided**: Make sure you've connected a valid audio source to the "audio" input - **Poor Transcription Quality**: Try using a different model or improving the audio quality - **Processing Errors**: Very long audio files or poor audio quality might result in less accurate transcriptions # AnthropicPrompt ## What is it? The AnthropicPrompt node sets up a connection to Anthropic's AI models (like Claude). ## When would I use it? Use this node when you want to: - Use Anthropic's chat models in your workflow - Customize how your agents interact with Anthropic models - Control specific settings for Anthropic model responses ## How to use it ### Basic Setup 1. Add an AnthropicPrompt node to your workflow 1. Connect its output to nodes that can use Anthopic prompt models like Claude (lfor instance, an Agent!) ### Parameters - **model**: The model to use (default is "claude-3-7-sonnet-latest") - **stream**: Whether to receive responses as they're generated (true) or all at once (false) - **temperature**: Controls randomness in responses (higher values = more creative, lower = more focused) - **max_attempts_on_fail**: How many times to retry if there's an error - **use_native_tools**: Whether to use Anthropic's built-in tools - **max_tokens**: Maximum length of responses - **top_p**: Controls diversity of outputs (similar to temperature) - **top_k**: Controls focus on most likely tokens ### Outputs - **prompt_model_config**: The configured Anthropic driver that other nodes can use ## Example To create an agent that uses Anthropic models with specific settings: 1. Add an AnthropicPrompt to your workflow 1. Connect the "driver" output to an Agent's "prompt_driver" input 1. Now that agent will use Claude with your custom settings Things to try: 1. Try changing the "model" from "claude-3-5-sonnet-latest" 1. Set "temperature" to 0.7 (for more creative responses) 1. Set "max_tokens" to 2000 (for longer responses) ## Important Notes - You need a valid Anthropic API key set up in your environment as `ANTHROPIC_API_KEY` - The default model is "claude-3-5-sonnet-latest" - The node checks if your API key is valid during setup ## Common Issues - **Missing API Key**: Make sure your Anthropic API key is properly set up in app settings - **Connection Errors**: Check your internet connection and API key validity # CoherePrompt ## What is it? The CoherePrompt node sets up a connection to Cohere's AI models. ## When would I use it? Use this node when you want to: - Use Cohere's AI models in your workflow - Take advantage of Cohere's specific capabilities ## How to use it ### Basic Setup 1. Add the CoherePrompt node to your workflow 1. Connect its output to nodes that can use prompt drivers (like Agent) ### Parameters - **model**: The Cohere model to use (default is "command-r-plus") - **max_attempts_on_fail**: How many times to retry if there's an error - **use_native_tools**: Whether to use Cohere's built-in tools - **max_tokens**: Maximum length of responses - **p**: Controls diversity of outputs (similar to temperature) - **k**: Controls focus on most likely tokens - **temperature**: Controls randomness in responses (higher values = more creative, lower = more focused) - **stream**: Whether to receive responses as they're generated (true) or all at once (false) ### Outputs - **prompt_model_config**: The configured Cohere driver that other nodes can use ## Example If you want to create an agent that uses Cohere: 1. Add a CoherePrompt node to your workflow 1. Connect the "driver" output to an Agent's "prompt_driver" input Try: 1. Set "model" to something besides the default 1. Set "max_tokens" to 1000 (for moderate length responses) ## Important Notes - You need a valid Cohere API key set up in your environment as `COHERE_API_KEY` - The default model is "command-r-plus" - The node checks if your API key is valid during setup ## Common Issues - **Missing API Key**: Make sure your Cohere API key is properly set up - **Connection Errors**: Check your internet connection and API key validity # GriptapeCloudImage ## What is it? The GriptapeCloudImage node sets up a connection to Griptape Cloud's image generation service. ## When would I use it? Use this node when you want to: - Generate images using the Griptape Cloud service (you won't have to go register with OpenAI and get an api key) - Create images with DALL-E 3 through Griptape's platform ## How to use it ### Basic Setup 1. Add a GriptapeCloudImage node to your workflow 1. Connect its driver output to nodes that need to generate images (like GenerateImage) ### Parameters - **model**: The model to use (default is "gpt-image-1-mini") - **image_size**: The size of images to generate (default is "1024x1024") - **style**: natural or vivid. Natural creates photorealistic images with natural lighting and textures, while vivid creates images with enhanced colors, contrast, and more dramatic compositions - **quality**: Select the quality for image generation. Standard or HD. ### Outputs - **image_model_config**: The configured Griptape Cloud image model configuration that other nodes can use ## Example Imagine you want to create images using Griptape Cloud: 1. Add a GriptapeCloudImage node to your workflow 1. Set "size" to "1024x1792" for vertical images 1. Connect the "image_model_config" output to a GenerateImage's "image_model_config" input 1. Now that node will generate images using Griptape Cloud with your settings ## Important Notes - You need a valid Griptape API key set up in your environment as `GT_CLOUD_API_KEY` (this should be automagic with your being in Griptape Nodes at all!) - The node will automatically adjust image sizes based on model choice - It should be noted, this is the default image_model_config for the GenerateImage node, and in fact should have no effect if plugged into that node. # GriptapeCloudPrompt ## What is it? The GriptapeCloudPrompt node sets up a connection to Griptape Cloud's AI services. ## When would I use it? Use this node when you want to: - Use various AI models through the Griptape Cloud service (you won't have to go register with OpenAI and get an api key) - Customize which AI model for anything that takes a prompt driver ## How to use it ### Basic Setup 1. Add a GriptapeCloudPrompt node to your workflow 1. Connect its driver output to nodes that need to use AI models (like an Agent) ### Parameters - **model**: The AI model to use (default is "gpt-4o") - **stream**: Whether to receive responses as they're generated (true) or all at once (false) - **temperature**: Controls randomness in responses (higher values = more creative, lower = more focused) - **max_attempts_on_fail**: How many times to retry if there's an error - **use_native_tools**: Whether to use the model's built-in tools - **max_tokens**: Maximum length of responses - **top_p**: Controls diversity of outputs (converted to top_p internally) ### Outputs - **prompt_model_config**: The configured Griptape Cloud driver that other nodes can use ## Example Imagine you want to create an agent that uses GPT-4o through Griptape Cloud: 1. Add a GriptapeCloudPrompt to your workflow 1. Set "model" to "gpt-4o" 1. Connect the "driver" output to an Agent's "prompt_driver" input 1. Now that agent will use Griptape Cloud with your custom settings Try these things: 1. Set "temperature" to 0.7 (for more creative responses) 1. Set "stream" to true or false (to see responses as they're generated, or just at once when done) ## Important Notes - You need a valid Griptape API key set up in your environment as `GT_CLOUD_API_KEY` - The default model is "gpt-4o" - The min_p parameter is converted to top_p internally (top_p = 1 - min_p) ## Common Issues - **Missing API Key**: Make sure your Griptape API key is properly set up - **Connection Errors**: Check your internet connection and API key validity # GrokImage ## What is it? The GrokImage node sets up a connection to Grok's image generation service (DALL-E). ## When would I use it? Use this node when you want to: - Generate images using Grok's DALL-E models - Create visual content from text descriptions - Connect image generation capabilities to your workflow ## How to use it ### Basic Setup 1. Add an GrokImage node to your workflow 1. Connect its driver output to nodes that can generate images (like GenerateImage) ### Parameters - **model**: The model to use (default is "grok-2-image-1212") ### Outputs - **image_model_config**: The configured Grok image model configuration that other nodes can use ## Example Imagine you want to create images using Grok: 1. Add an GrokImage node to your workflow 1. Configure any available settings 1. Connect the "image_model_config" output to a GenerateImage's "image_model_config" input ## Important Notes - You need a valid Grok API key set up in your environment as `GROK_API_KEY`, available from: https://console.x.ai - This node is a simple wrapper around Grok's image generation capabilities ## Common Issues - **Missing API Key**: Make sure your Grok API key is properly set up - **Connection Errors**: Check your internet connection and API key validity - **Generation Limits**: Be aware of Grok's rate limits and usage quotas # GrokPrompt Billing Required for xAI API Usage The GrokPrompt node requires an xAI account and billing information to be set up before xAI API keys will work. Without completing the billing setup, any nodes using xAI will fail, even with a valid API key. See [this guide](https://docs.griptapenodes.com/guides/integrations/grok/index.md) for instructions on setting up a xAI account with billing. ## What is it? The GrokPrompt node sets up a connection to xAI's Grok models. ## When would I use it? Use this node when you want to: - Use Grok's chat models in your workflow - Customize how your agents interact with Grok models - Control specific settings for Grok model responses ## How to use it ### Basic Setup 1. Add a GrokPrompt node to your workflow 1. Connect its output to nodes that can use Grok prompt models (for instance, an Agent!) ### Parameters - **model**: The model to use. Default is "grok-3-beta", choices are "grok-3-beta", "grok-3-fast-beta", "grok-3-mini-beta", "grok-3-mini-fast-beta", "grok-2-vision-1212" - **top_p**: Controls diversity of outputs (default: 0.9) - **stream**: Whether to receive responses as they're generated (true) or all at once (false) - **temperature**: Controls randomness in responses (higher values = more creative, lower = more focused) - **max_attempts_on_fail**: How many times to retry if there's an error - **use_native_tools**: Whether to use Anthropic's built-in tools - **max_tokens**: Maximum length of responses ### Outputs - **prompt_model_config**: The configured Grok driver that other nodes can use ## Example To create an agent that uses Grok models with specific settings: 1. Add a GrokPrompt to your workflow 1. Connect the **prompt_model_config** output to an Agent's **prompt_model_config** input 1. Now that agent will use Grok with your custom settings Things to try: 1. Try changing the "model" from "grok-3-beta" to "grok-3-mini-beta" 1. Set "top_p" to 0.7 (for more focused responses) ## Important Notes - You need a valid Grok API key set up in your environment as `GROK_API_KEY` - Unlike some other drivers, Grok doesn't support parameters like 'seed' and 'top_k' - Grok is a paid service, and you must have billing set up on the Grok.ai website before your API key will work # Groq Prompt Driver The Groq Prompt Driver node allows you to configure and utilize Groq's language models within the Griptape Nodes framework. This node provides access to Groq's high-performance inference engine and various language models. ## Configuration ### API Key To use the Groq Prompt Driver, you need to provide a Groq API key. You can obtain an API key from the [Groq Console](https://console.groq.com/keys). ### Available Models The Groq Prompt Driver supports the following models: #### Production Models - [gemma2-9b-it](https://huggingface.co/google/gemma-2-9b-it) - Google's Gemma 2 9B model - [meta-llama/llama-guard-4-12b](https://console.groq.com/docs/model/llama-guard-4-12b) - Meta's Llama Guard model for content moderation - [llama-3.3-70b-versatile](https://console.groq.com/docs/model/llama-3.3-70b-versatile) - Meta's versatile 70B parameter model - [llama-3.1-8b-instant](https://console.groq.com/docs/model/llama-3.1-8b-instant) - Meta's fast 8B parameter model - [llama3-70b-8192](https://console.groq.com/docs/model/llama3-70b-8192) - Meta's 70B parameter model with 8K context - [llama3-8b-8192](https://console.groq.com/docs/model/llama3-8b-8192) - Meta's 8B parameter model with 8K context - [meta-llama/llama-4-scout-17b-16e-instruct](https://console.groq.com/docs/model/llama-4-scout-17b-16e-instruct) - Meta's vision model with 16K context, compatible with the Image Description node - [meta-llama/llama-4-maverick-17b-128e-instruct](https://console.groq.com/docs/model/llama-4-maverick-17b-128e-instruct) - Meta's vision model with 128K context, compatible with the Image Description node #### Preview Models - [allam-2-7b](https://ai.azure.com/explore/models/ALLaM-2-7b-instruct/version/2/registry/azureml) - Saudi Data and AI Authority's 7B parameter model - [deepseek-r1-distill-llama-70b](https://console.groq.com/docs/model/deepseek-r1-distill-llama-70b) - DeepSeek's distilled 70B parameter model ### Parameters The Groq Prompt Driver supports the following configuration parameters: | Parameter | Type | Default | Description | | ------------ | ------- | ------------ | ---------------------------------------------------- | | model | string | gemma2-9b-it | The Groq model to use for text generation | | temperature | float | 0.7 | Controls randomness in the output (0.0 to 1.0) | | top_p | float | 0.9 | Controls diversity via nucleus sampling (0.0 to 1.0) | | max_tokens | integer | 2048 | Maximum number of tokens to generate | | stream | boolean | false | Whether to stream the response | | max_attempts | integer | 3 | Maximum number of retry attempts for failed requests | ## Usage 1. Add the Groq Prompt Driver node to your workflow 1. Make sure to get an API key here: [Groq API Keys](https://console.groq.com/keys) 1. Configure your `GROQ_API_KEY` in the Griptape Configuration settings 1. Select your desired model from the available options 1. Adjust the generation parameters as needed 1. Connect the node to other nodes in your workflow ## Notes - The Groq Prompt Driver uses the OpenAI-compatible API endpoint at `https://api.groq.com/openai/v1` - Preview models are intended for evaluation purposes and may be discontinued at short notice - Production models are recommended for production environments - The node automatically handles API key validation before workflow execution # OpenAiImage ## What is it? The OpenAiImage node sets up a connection to OpenAI's image generation service (DALL-E). ## When would I use it? Use this node when you want to: - Generate images using OpenAI's DALL-E models - Create visual content from text descriptions - Connect image generation capabilities to your workflow ## How to use it ### Basic Setup 1. Add an OpenAiImage node to your workflow 1. Connect its driver output to nodes that can generate images (like GenerateImage) ### Parameters - **model**: The model to use (default is "dall-e-3") - **image_size**: The size of images to generate (default is "1024x1024") - **style**: natural or vivid. Natural creates photorealistic images with natural lighting and textures, while vivid creates images with enhanced colors, contrast, and more dramatic compositions - **quality**: Select the quality for image generation. Standard or HD. - **background**: Select the background for image generation - **moderation**: Standard or strict, regarding filtering for harmful or offensice content - **output_format**: png, jpeg ### Outputs - **image_model_config**: The configured OpenAI image model configuration that other nodes can use ## Example Imagine you want to create images using OpenAI's DALL-E: 1. Add an OpenAiImage node to your workflow 1. Configure any available settings 1. Connect the "image_model_config" output to a GenerateImage's "image_model_config" input ## Important Notes - You need a valid OpenAI API key set up in your environment as `OPENAI_API_KEY` - This node is a simple wrapper around OpenAI's image generation capabilities - The specific DALL-E model used will depend on what's configured in the underlying driver ## Common Issues - **Missing API Key**: Make sure your OpenAI API key is properly set up - **Connection Errors**: Check your internet connection and API key validity - **Generation Limits**: Be aware of OpenAI's rate limits and usage quotas # OpenAiPrompt ## What is it? The OpenAiPrompt node sets up a direct connection to OpenAI's chat models like GPT-4o. ## When would I use it? Use this node when you want to: - Use OpenAI's models in your workflow - Customize how your agents interact with models like GPT-4o - Control specific settings for OpenAI's responses ## How to use it ### Basic Setup 1. Add the OpenAiPrompt to your workflow 1. Connect its driver output to nodes that need to use OpenAI (like Agent) ### Parameters - **model**: The OpenAI model to use (default is "gpt-4o") - **stream**: Whether to receive responses as they're generated (true) or all at once (false) - **temperature**: Controls randomness in responses (higher values = more creative, lower = more focused) - **use_native_tools**: Whether to use OpenAI's built-in tools - **max_tokens**: Maximum length of responses - **max_attempts_on_fail**: How many times to retry if there's an error - **top_p**: Controls diversity of outputs (converted to top_p internally) ### Outputs - **prompt_model_config**: The configured OpenAI driver that other nodes can use ## Example Imagine you want to create an agent that uses GPT-4o with specific settings: 1. Add an OpenAiPrompt node to your workflow 1. Connect the "driver" output to an Agent's "prompt_driver" input Things to try: 1. Set "model" to anything other than "gpt-4o" 1. Set "temperature" to 0.2 (for more focused, deterministic responses) 1. Set "max_tokens" to 2000 (for longer responses) ## Important Notes - You need a valid OpenAI API key set up in your environment as `OPENAI_API_KEY` - The default model is "gpt-4o" - The min_p parameter is converted to top_p internally (top_p = 1 - min_p) - Unlike some other drivers, OpenAI doesn't support the top_k parameter # To JSON Converts incoming value to JSON data using json-repair. ## Description The To JSON node converts various data types to JSON format using `json-repair` for robust handling of malformed JSON strings. It provides intelligent conversion for different input types and includes automatic repair capabilities. ## Parameters ### Input Parameters | Parameter | Type | Description | Default | | --------- | ---- | --------------------------- | ------- | | `from` | any | The data to convert to JSON | `{}` | ### Output Parameters | Parameter | Type | Description | | --------- | ---- | -------------------------- | | `output` | json | The converted data as JSON | ## Features - **JSON Repair Integration**: Uses `json_repair.repair_json()` to handle malformed JSON strings - **Multiple Input Type Support**: Handles different input types intelligently - **Error Handling**: Graceful fallbacks when repair or parsing fails - **Robust Conversion**: Can handle various input formats and convert them to proper JSON data ## Examples ### Dictionary to JSON ``` # Input: {"name": "John", "age": 30, "active": True} # Output: {"name": "John", "age": 30, "active": true} ``` ### Malformed JSON String Repair ``` # Input: '{"name": "John", age: 30, "city": "New York"}' # Missing quotes around age # Output: {"name": "John", "age": 30, "city": "New York"} # Repaired JSON ``` ### Regular JSON String ``` # Input: '{"user": {"name": "Alice", "active": true}}' # Output: {"user": {"name": "Alice", "active": true}} ``` ### List to JSON ``` # Input: [1, 2, 3, "four", {"nested": "value"}] # Output: [1, 2, 3, "four", {"nested": "value"}] ``` ### Other Data Types ``` # Input: "simple string" # Output: "simple string" # Input: 42 # Output: 42 # Input: True # Output: true ``` ## Input Type Handling ### Dictionary Input - **Behavior**: Uses as-is if already a dict - **Example**: `{"key": "value"}` → `{"key": "value"}` ### String Input - **Behavior**: Attempts to repair malformed JSON, falls back to regular JSON parsing - **Example**: `'{"name": "John", age: 30}'` → `{"name": "John", "age": 30}` ### Other Types - **Behavior**: Converts to string first, then attempts repair, with fallback to empty dict - **Example**: `[1, 2, 3]` → `[1, 2, 3]` ## Use Cases - **Data Standardization**: Convert various data formats to JSON - **API Integration**: Prepare data for JSON-based APIs - **Data Cleaning**: Repair and standardize malformed JSON data - **Workflow Integration**: Convert different data types to JSON format for processing - **Configuration Processing**: Handle configuration data in various formats ## Related Nodes - [JSON Input](https://docs.griptapenodes.com/nodes/json/json_input/index.md) - Create JSON data from inputs - [JSON Extract Value](https://docs.griptapenodes.com/nodes/json/json_extract_value/index.md) - Extract values from JSON - [JSON Replace](https://docs.griptapenodes.com/nodes/json/json_replace/index.md) - Replace values in JSON - [Display JSON](https://docs.griptapenodes.com/nodes/json/display_json/index.md) - Display and format JSON data # Dictionary ## What is it? The Dictionary node lets you create a dictionary (a collection of key-value pairs) by providing separate lists of keys and values. It's a simple way to organize related data with named values that can be used throughout your workflow. ## When would I use it? Use this node when you want to: - Organize related data with named values - Create structured data to pass to other nodes - Build configuration settings for other components - Prepare data in a format that's easy to access by key - Combine multiple values into a single organized structure ## How to use it ### Basic Setup 1. Add the Dictionary node to your workflow 1. Set up your lists of keys and values 1. Connect the dictionary output to other nodes that need structured data ### Parameters - **keys**: A list of strings that will be used as dictionary keys - **values**: A list of values (strings, numbers, booleans, etc.) that correspond to each key ### Outputs - **dict**: The constructed dictionary containing all key-value pairs ## Example Imagine you want to create a configuration dictionary for a user profile: 1. Add a Dictionary node to your workflow 1. Set the "keys" parameter to: ``` ["name", "age", "premium_member", "interests"] ``` 1. Set the "values" parameter to: ``` ["Jane Smith", 32, true, ["hiking", "photography", "coding"]] ``` 1. The output dictionary will be: ``` {"name": "Jane Smith", "age": 32, "premium_member": true, "interests": ["hiking", "photography", "coding"]} ``` 1. Connect this dictionary to other nodes that need user profile information ## Important Notes - Keys are automatically converted to strings - If you provide more keys than values, the extra keys will be assigned `None` values - Empty or `None` keys are skipped unless it's the only key and has a value - You can provide single values instead of lists, and they'll be converted to single-item lists - The node works with various value types including strings, numbers, booleans, and nested lists - The dictionary format is compatible with nodes that accept dictionary inputs ## Common Issues - **Mismatched Lists**: If your keys and values lists have different lengths, some keys may have `None` values or some values may be ignored - **Key Conversion**: All keys are converted to strings, which may cause unexpected behavior if you're using complex objects as keys - **Empty Keys**: Empty strings or `None` values used as keys may be skipped depending on their values # DisplayDictionary Node ## What is it? The `DisplayDictionary` node is a utility node that displays the contents of a dictionary in your workflow. It provides a visual representation of dictionary data, making it easier to inspect and debug dictionary values during workflow execution. ## When would I use it? Use the DisplayDictionary node when: - You need to inspect the contents of a dictionary in your workflow - You want to visualize complex data structures for debugging purposes - You need to monitor how dictionary values change throughout your workflow - You're working with JSON-like data and need to see its structure clearly ## How to use it ### Basic Setup 1. Add the DisplayDictionary node to your workflow 1. Connect any node that outputs a dictionary to the "dictionary" input of this node 1. The dictionary content will be displayed in the node's interface ### Parameters - **dictionary**: The dictionary to be displayed (input/output parameter) - **dictionary_display**: A string representation of the dictionary content (property parameter, displayed in the UI) ### Outputs - **dictionary**: The same dictionary that was provided as input (passed through) ## Example Imagine you're working with a workflow that processes user data: 1. Add a DisplayDictionary node to your workflow 1. Connect the output of a node that produces user data (like a database query or API response) to the "dictionary" input 1. The DisplayDictionary node will show the user data structure in a readable format 1. You can continue your workflow by connecting the "dictionary" output to other nodes that need this data ## Important Notes - The DisplayDictionary node doesn't modify the dictionary data, it simply displays it - The multiline text area in the node's UI makes it easier to read complex nested dictionaries - This node is particularly useful for debugging and understanding data flow in your workflows ## Common Issues - None # KeyValuePair Node ## What is it? The `KeyValuePair` node creates a simple dictionary containing a single key-value pair. It takes a key and a value as inputs and outputs a dictionary with that single mapping. ## When would I use it? Use the KeyValuePair node when: - You need to create a simple dictionary with a single key-value association - You want to dynamically generate configuration parameters - You're building data structures piece by piece - You need to transform two separate values into a dictionary format - You're preparing data for nodes that require dictionary inputs ## How to use it ### Basic Setup 1. Add the KeyValuePair node to your workflow 1. Set the "key" parameter to your desired dictionary key 1. Set the "value" parameter to the value you want associated with that key 1. Connect the "dictionary" output to other nodes that require dictionary input ### Parameters - **key**: The string that will be used as the dictionary key (string) - **value**: The string that will be associated with the key (string, supports multiline text) ### Outputs - **dictionary**: A dictionary containing the single key-value pair ## Example Imagine you're creating a workflow that needs to set configuration options: 1. Add a KeyValuePair node to your workflow 1. Set the "key" parameter to "max_tokens" 1. Set the "value" parameter to "1024" 1. Connect the "dictionary" output to a node that requires configuration parameters The output will be a dictionary: `{"max_tokens": "1024"}` This dictionary can then be used by other nodes in your workflow that need this configuration parameter. ## Important Notes - Both the key and value are treated as strings in this node - The value parameter supports multiline text for longer content - If you need to create a dictionary with multiple key-value pairs, you can use multiple KeyValuePair nodes and combine their outputs with a merge node # MergeKeyValuePair Node ## What is it? The `MergeKeyValuePair` node merges multiple dictionaries into a single dictionary. It takes up to four input dictionaries and combines them into one unified output dictionary. ## When would I use it? Use the MergeKeyValuePair node when: - You need to combine multiple dictionaries into a single dictionary - You're working with separate configuration settings that need to be unified - You want to aggregate data from different sources into one structure - You're building complex parameter sets from individual components - You need to consolidate related key-value pairs from different parts of your workflow ## How to use it ### Basic Setup 1. Add the MergeKeyValuePair node to your workflow 1. Connect up to four dictionary outputs from other nodes to the inputs of this node 1. The node will combine all input dictionaries into a single output dictionary ### Parameters - **key_value_pair_1**: First dictionary to merge (dictionary input) - **key_value_pair_2**: Second dictionary to merge (dictionary input) - **key_value_pair_3**: Third dictionary to merge (dictionary input) - **key_value_pair_4**: Fourth dictionary to merge (dictionary input) ### Outputs - **output**: A single dictionary containing all key-value pairs from the input dictionaries ## Example Imagine you're building a workflow that needs to combine configuration settings from different sources: 1. Add a MergeKeyValuePair node to your workflow 1. Connect a dictionary with database settings to "key_value_pair_1" (e.g., {"host": "localhost", "port": 5432}) 1. Connect a dictionary with authentication settings to "key_value_pair_2" (e.g., {"username": "admin", "password": "secure123"}) 1. Connect a dictionary with application settings to "key_value_pair_3" (e.g., {"app_name": "MyApp", "debug": true}) 1. The output will be a single dictionary containing all these settings: {"host": "localhost", "port": 5432, "username": "admin", "password": "secure123", "app_name": "MyApp", "debug": true} ## Important Notes - If the same key appears in multiple input dictionaries, the value from the later dictionary will overwrite earlier values - The node ignores any inputs that are not dictionaries or are None - You don't need to connect all four inputs - the node works with any number of inputs from one to four - The order of inputs matters when there are key conflicts # EndFlowNode ## What is it? The EndFlowNode is a simple building block that marks the end of your workflow. Think of it as a stop sign that tells the system "the flow ends here." ## When would I use it? Use this node when you want to: - Clearly mark where your workflow ends - Create a visual endpoint for your flow - Ensure your workflow has a proper ending point ## How to use it ### Basic Setup 1. Add the EndFlowNode to your workflow 1. Connect it to the end of your flow ### Parameters - **control**: This is an input connection point for the flow (you connect other nodes to this) ### Outputs - **None** - this node doesn't output anything, it just marks the end of the flow ## Example Imagine you have a workflow that generates and saves text: 1. Create a flow with several nodes (like an agent that generates text and a node that saves it) 1. Add an EndFlowNode at the end of your sequence 1. Connect the last active node in your flow to the EndFlowNode ## Important Notes - The EndFlowNode doesn't actually do anything - it's just a marker for readability - You can have multiple EndFlowNodes in complex workflows with different branches - Using EndFlowNodes makes your workflows easier to understand ## Common Issues - **Flow Continues Past EndFlowNode**: Make sure you're not connecting anything after the EndFlowNode - **Flow Doesn't Reach EndFlowNode**: Check your connections to ensure the flow can reach the end # Reroute ## What is it? The Reroute node is a utility node that acts as a connection point for rerouting data between different parts of your workflow. It dynamically adjusts the allowed types of its parameters based on the connections established, making it easier to organize complex workflows. In short, it's just a way to shape your connections between nodes. ## When would I use it? Use the Reroute node when: - You need to organize complex workflows with many crossing connections - You want to improve the visual clarity of your workflow - You're working with connections that need to span across distant parts of your workflow - You need to bundle multiple connections together - You want to create a cleaner, more maintainable workflow layout ## How to use it ### Basic Setup 1. Add a Reroute node to your workflow 1. Connect the source node output to the Reroute node's input 1. Connect the Reroute node's output to the target node's input 1. The node will automatically adapt to pass through the correct data types ### Parameters - **passThru**: A parameter that can function as both input and output, dynamically adapting its allowed types based on connections ### Outputs - **passThru**: The data passed through the node without modification ## Example Add a Reroute node between two distant nodes in your workflow to improve organization. For instance, connect an image output from a node at the top of your workflow to a Reroute node, then connect that Reroute node's output to an image processing node at the bottom of your workflow. ## Important Notes - The Reroute node does not modify the data passing through it - It automatically adjusts its parameter types based on the connections made - It can help improve workflow organization and readability - Multiple Reroute nodes can be chained together for complex routing needs # StartFlow ## What is it? The StartFlow is a special building block that marks the beginning of your workflow. Think of it as the "Go" sign that tells the system where to start running your flow. ## When would I use it? Use this node when you want to: - Create a clear starting point for your workflow - Begin a sequence of connected nodes - Define the entry point for your flow ## How to use it ### Basic Setup 1. Add the StartFlow to your workflow 1. Connect it to the first action node in your flow ### Parameters ### Outputs - **exec_out** - use this pin to pass control to the next node in the flow ## Example Imagine you're creating a workflow that generates and saves text: 1. Create a StartFlow node 1. Connect the top, white "exec chain" pins to an Agent that will generate text 1. Connect that to a SaveText to save the generated text 1. Connect that to an EndFlowNode to complete the flow The StartFlow tells the system "start here and follow the exec chain in order." ## Important Notes - Every workflow needs exactly one StartFlow - The StartFlow doesn't take any inputs - it's just a starting point - You can only have one StartFlow per contiguous workflow ## Common Issues - **Flow Doesn't Run**: Make sure your StartFlow is properly connected to the next node - **Multiple Start Points**: Ensure you only have one StartFlow in your workflow # AddBoundingBoxes ## What is it? The AddBoundingBoxes node draws bounding boxes on images from coordinate dictionaries. It's designed to visualize object detection results by overlaying colored rectangles with optional labels on your images. Perfect for displaying YOLO, face detection, or any other detection system outputs. ## When would I use it? Use this node when you want to: - Visualize object detection results from YOLO, SSD, or other detection models - Display face detection bounding boxes with confidence scores - Annotate images with coordinate-based regions of interest - Create visual feedback for computer vision workflows - Debug and verify detection model outputs - Combine multiple detection results on a single image ## How to use it ### Basic Setup 1. Add the AddBoundingBoxes node to your workflow 1. Connect an image source to the "input_image" input 1. Connect bounding box data (dict or list of dicts) to the "bounding_boxes" input 1. Optionally customize colors, labels, and thickness 1. The annotated image will be available at the "output" parameter ### Parameters #### Required Inputs - **input_image**: The image to draw bounding boxes on (ImageUrlArtifact or ImageArtifact) - **bounding_boxes**: Single dict or list of dicts containing box coordinates - Each dict must have: `x`, `y`, `width`, `height` (integers or convertible strings) - Can include additional keys for label templates (e.g., `confidence`, `class`, etc.) - Example: `{"x": 100, "y": 50, "width": 200, "height": 150, "confidence": 0.95}` #### Styling Parameters - **box_color** (hex color, default: "#FF0000"): Color of the bounding box outlines - Uses ColorPicker for easy color selection - Supports hex format (e.g., "#FF0000" for red, "#00FF00" for green) - **line_thickness** (1-10, default: 2): Thickness of the bounding box lines in pixels - Lower values for subtle annotations - Higher values for bold, prominent boxes #### Label Parameters - **show_labels** (boolean, default: True): Toggle label visibility - Set to True to show labels above bounding boxes - Set to False to hide labels and show only boxes - **label_key** (string template): Template for bounding box labels - Default: `"{x}, {y}, width: {width}, height: {height}"` - Use `{key}` syntax to insert values from bounding box dicts - Example: `"Class: {class}, Conf: {confidence}"` - Example: `"{x}, {y} - Size: {width}x{height}"` - Keys not in the dict will remain as `{key}` in the output ### Outputs - **output**: The image with bounding boxes and labels drawn ## Example A typical object detection visualization workflow: 1. Load an image using LoadImage 1. Run object detection (e.g., using YOLO or custom detection) 1. Format detection results as dictionaries: ``` [ {"x": 100, "y": 50, "width": 200, "height": 150, "class": "person", "confidence": 0.95}, {"x": 350, "y": 120, "width": 180, "height": 200, "class": "car", "confidence": 0.87} ] ``` 1. Connect the image to AddBoundingBoxes "input_image" 1. Connect the detection results to "bounding_boxes" 1. Set label template: `"Class: {class}, Conf: {confidence}"` 1. Choose box color: "#00FF00" (green) 1. Set line thickness: 3 for visibility 1. Connect the "output" to DisplayImage to view the annotated result ## Important Notes - **Coordinate System**: Bounding boxes use (x, y) as the top-left corner, with width and height extending right and down - **String Conversion**: The node automatically converts string coordinate values to integers if possible - Example: `{"x": "100", "y": "50"}` will work correctly - **Label Positioning**: Labels are positioned with smart spacing: - Default: Above the box with a gap equal to half the label height - If near the top of the image: Inside the box at the top - **Label Size**: Font size automatically scales to 4% of the image height for proportional labeling - **Validation**: The node validates all inputs before processing: - Coordinates must be non-negative (x ≥ 0, y ≥ 0) - Dimensions must be positive (width > 0, height > 0) - Clear error messages guide you to correct any issues - **RGBA Support**: The node preserves transparency in RGBA images ## Common Issues - **Missing Required Keys**: Ensure each bounding box dict has `x`, `y`, `width`, `height` - Error message will specify which keys are missing - **String Coordinates**: If your data has string coordinates, the node will convert them automatically - If conversion fails, you'll get a clear error message - **Negative Coordinates**: Bounding boxes must have non-negative coordinates - Check that x ≥ 0 and y ≥ 0 - **Zero or Negative Dimensions**: Width and height must be greater than zero - Verify width > 0 and height > 0 - **Labels Not Showing**: Check that: - `show_labels` is set to True - `label_key` has a valid template - The keys in your template exist in your bounding box dicts - **Label Text Wrong**: Make sure the `{key}` names in your template match the keys in your bounding box dictionaries ## Technical Details The node performs the following operations: 1. **Input Validation**: - Validates bounding box format (dict or list) - Checks for required keys (x, y, width, height) - Converts string values to integers if needed - Validates coordinate ranges 1. **Color Parsing**: Converts hex color codes to RGB tuples for drawing 1. **Font Loading**: Dynamically loads a font sized at 4% of image height (configurable via `LABEL_HEIGHT_PERCENT` constant) 1. **Box Drawing**: For each bounding box: - Calculates rectangle corners from x, y, width, height - Draws rectangle outline with specified color and thickness 1. **Label Rendering** (if enabled): - Processes template string by replacing `{key}` patterns with values - Calculates text dimensions for proper positioning - Draws black background rectangle for text visibility - Draws white text on the background 1. **Smart Positioning**: Labels are positioned above boxes with proportional spacing, or inside boxes when near the top of the image The node uses PIL (Pillow) ImageDraw for all rendering operations, ensuring high-quality output compatible with standard image formats. # AddTextToExistingImage ## What is it? The AddTextToExistingImage node overlays text onto an existing image. It supports text/background colors (with alpha), alignment controls, and simple template placeholders like `{key}` that are expanded from a separate dictionary input. ## When would I use it? Use this node when you want to: - Add titles, captions, or labels onto an image - Stamp metadata (like filenames, timestamps, or IDs) onto images - Create annotated image outputs for downstream steps - Render dynamic text using `{key}` placeholders sourced from a dictionary ## How to use it ### Basic Setup 1. Add the AddTextToExistingImage node to your workflow 1. Connect an image source to **input_image** 1. Enter the text template in **text** 1. (Optional) Connect a dictionary to **template_values** to expand `{key}` placeholders 1. Adjust alignment, colors, border, and font size 1. Run the node to produce **output** ### Parameters #### Required Inputs - **input_image**: The image to render text onto (ImageUrlArtifact / ImageArtifact / dict) #### Text Inputs - **text** (string): The text template to render - Supports placeholders like `{key_name}` - Placeholder expansion is applied only to what’s rendered on the image; the `text` output parameter remains the original template - **template_values** (dict, optional): Values used to expand placeholders in `text` - If a placeholder key is not present, it will be left as-is in the rendered text - Missing keys are also reported in `result_details` when the node runs #### Styling Parameters - **text_color** (hexa, default: `#ffffffff`): Text color including alpha - **text_background** (hexa, default: `#000000ff`): Background rectangle color behind the text including alpha - **text_vertical_alignment** (top | center | bottom, default: top): Vertical alignment of the text block - **text_horizontal_alignment** (left | center | right, default: left): Horizontal alignment of the text block - **margin** (int, default: 10): Margin inset from image edges for text placement - **font_size** (int, default: 36): Font size used for rendering ### Outputs - **output**: The updated image (ImageUrlArtifact) - **text**: The original text template string (not the expanded render string) - **was_successful**: Indicates whether the node succeeded - **result_details**: Success/failure details, including missing placeholder key messages ## Example A typical “stamp metadata” workflow: 1. Load an image using LoadImage 1. Add AddTextToExistingImage 1. Set **text** to: `"Photo: {name} | #{index}"` 1. Provide **template_values**: ``` {"name": "Portrait", "index": 7} ``` 1. Set `text_background` to a semi-transparent black like `#00000080` 1. Set `text_color` to white `#ffffffff` 1. Run the node and connect **output** to DisplayImage ## Important Notes - **Missing keys**: If `{key}` is not present in `template_values`, the placeholder stays in the rendered text and a message like `key: key not found in dictionary input` is appended to `result_details`. - **Alpha support**: Both text and background colors support transparency via hexa (`#RRGGBBAA`). - **Execution**: The node uploads and outputs the final image on `process()`. # AdjustImageEQ ## What is it? The AdjustImageEQ node allows you to fine-tune your images with precise controls for brightness, contrast, saturation, and gamma correction. It's like having a professional photo editing tool built into your workflow, giving you complete control over the visual appearance of your images. ## When would I use it? Use this node when you want to: - Enhance image brightness and contrast for better visibility - Adjust color saturation to make colors more vibrant or muted - Apply gamma correction to fix exposure issues - Fine-tune images before passing them to other processing nodes - Create consistent lighting across multiple images - Prepare images for specific output requirements ## How to use it ### Basic Setup 1. Add the AdjustImageEQ node to your workflow 1. Connect an image source to the "input_image" input 1. Adjust the sliders to fine-tune your image 1. The processed image will be available at the "output" parameter ### Parameters #### Adjustment Settings - **brightness** (0.0-3.0, default: 1.0): Controls overall image brightness - Values < 1.0 make the image darker - Values > 1.0 make the image brighter - 1.0 = no change - **contrast** (0.0-3.0, default: 1.0): Controls the difference between light and dark areas - Values < 1.0 reduce contrast (more gray) - Values > 1.0 increase contrast (more dramatic) - 1.0 = no change - **saturation** (0.0-3.0, default: 1.0): Controls color intensity - Values < 1.0 reduce saturation (more grayscale) - Values > 1.0 increase saturation (more vibrant colors) - 1.0 = no change - **gamma** (0.1-10.0, default: 1.0): Controls midtone brightness - Values < 1.0 brighten midtones - Values > 1.0 darken midtones - 1.0 = no change ### Outputs - **output**: The adjusted image with your EQ settings applied ## Example A typical image enhancement workflow: 1. Load or generate an image using LoadImage or GenerateImage 1. Connect that image to the AdjustImageEQ node's "input_image" parameter 1. Adjust the sliders to achieve your desired look: - Increase brightness to 1.2 for a brighter image - Increase contrast to 1.1 for more dramatic shadows - Increase saturation to 1.3 for more vibrant colors - Adjust gamma to 0.9 to brighten midtones 1. Connect the "output" to DisplayImage to view the result ## Important Notes - **Live Preview**: Changes are applied in real-time as you move the sliders - **RGBA Support**: The node preserves transparency in RGBA images - **High Quality**: Uses PIL's ImageEnhance for professional-quality adjustments - **Non-destructive**: Original image data is preserved in the workflow ## Common Issues - **No Changes Visible**: Make sure you're moving the sliders away from their default values (1.0) - **Over-processed Look**: Be careful with extreme values - subtle adjustments often work best - **Color Shifts**: Gamma adjustments can affect color balance, so monitor the overall result ## Technical Details The node uses PIL's ImageEnhance module for high-quality image adjustments: - **Brightness**: Multiplies pixel values by the brightness factor - **Contrast**: Applies contrast enhancement using PIL's contrast algorithm - **Saturation**: Uses PIL's color enhancement for saturation control - **Gamma**: Applies gamma correction using lookup tables for precise control # AdjustImageLevels ## What is it? The AdjustImageLevels node provides professional-level image adjustment capabilities similar to Photoshop's levels tool. It allows you to control input levels (shadows, midtones, highlights) and output levels, giving you precise control over the tonal range and contrast of your images. ## When would I use it? Use this node when you want to: - Fine-tune the tonal range of your images - Adjust shadows and highlights independently - Control midtone brightness with gamma correction - Create high-contrast or low-contrast effects - Fix exposure issues in images - Match the tonal characteristics of multiple images - Prepare images for specific output requirements ## How to use it ### Basic Setup 1. Add the AdjustImageLevels node to your workflow 1. Connect an image source to the "input_image" input 1. Adjust the input and output level sliders 1. The processed image will be available at the "output" parameter ### Parameters #### Input Levels - **shadows** (0-255, default: 0): Input shadows level - Pixels below this value will be mapped to output shadows - Higher values clip more shadow detail - **midtones** (0.1-10.0, default: 1.0): Midtones adjustment (gamma) - Values < 1.0 brighten midtones - Values > 1.0 darken midtones - 1.0 = no change - **highlights** (0-255, default: 255): Input highlights level - Pixels above this value will be mapped to output highlights - Lower values clip more highlight detail #### Output Levels - **output_shadows** (0-255, default: 0): Output shadows level - Input shadows will be mapped to this value - Higher values brighten the overall image - **output_highlights** (0-255, default: 255): Output highlights level - Input highlights will be mapped to this value - Lower values darken the overall image ### Outputs - **output**: The adjusted image with your levels settings applied ## Example A typical levels adjustment workflow: 1. Load an image using LoadImage 1. Connect that image to the AdjustImageLevels node's "input_image" parameter 1. Adjust the levels to improve the image: - Set shadows to 20 to preserve more shadow detail - Set midtones to 0.8 to brighten midtones - Set highlights to 240 to preserve more highlight detail - Set output_shadows to 10 to slightly brighten shadows - Set output_highlights to 245 to slightly darken highlights 1. Connect the "output" to DisplayImage to view the result ## Important Notes - **Live Preview**: Changes are applied in real-time as you move the sliders - **RGBA Support**: The node preserves transparency in RGBA images - **Professional Quality**: Uses lookup tables for precise level adjustments - **Validation**: The node ensures shadows < highlights for proper operation ## Common Issues - **No Changes Visible**: Make sure you're adjusting the sliders away from their default values - **Over-processed Look**: Be careful with extreme values - subtle adjustments often work best - **Clipping**: Setting shadows too high or highlights too low can cause detail loss ## Technical Details The node applies a three-step process: 1. **Input Levels**: Clips and maps the input range based on shadows and highlights 1. **Gamma Correction**: Applies midtone adjustment using power-law transformation 1. **Output Levels**: Maps the processed values to the output range This provides the same functionality as professional photo editing software's levels adjustment tool. # ApplyMask ## What is it? The ApplyMask node applies a mask to an input image to create transparency effects. It takes an image and a mask, then uses a specified channel from the mask as the alpha channel for the final image. You can choose which channel to use (red, green, blue, or alpha) depending on where your mask data is stored. This allows you to selectively make parts of an image transparent based on the mask. ## When would I use it? Use this node when you want to: - Apply transparency effects to images using masks - Create selective transparency for image compositing - Use masks created by other nodes (DisplayMask, PaintMask, InvertMask) to modify image transparency - Create cutouts or remove backgrounds from images - Apply complex transparency effects in image processing workflows - Control which channel of a mask image to use for transparency ## How to use it ### Basic Setup 1. Add the ApplyMask node to your workflow 1. Connect an image source to the "input_image" input 1. Connect a mask source to the "input_mask" input 1. Select the appropriate channel in the "channel" parameter 1. The masked image will be available at the "output" parameter ### Parameters - **input_image**: The image to apply the mask to (can be connected from other image nodes) - **input_mask**: The mask to apply (can be connected from mask nodes like DisplayMask, PaintMask, or InvertMask) - **channel**: Which channel to use from the mask image: - **red**: Use the red channel (default, compatible with PaintMask output) - **green**: Use the green channel - **blue**: Use the blue channel - **alpha**: Use the alpha channel (when available) ### Outputs - **output**: The final image with the mask applied (transparency based on the mask) ## Example A common workflow pattern: 1. Generate or load an image using nodes like GenerateImage or LoadImage 1. Create a mask using nodes like DisplayMask or PaintMask 1. Connect the image to the ApplyMask node's "input_image" parameter 1. Connect the mask to the ApplyMask node's "input_mask" parameter 1. Select the appropriate channel (usually "red" for PaintMask output, "alpha" for RGBA masks) 1. The ApplyMask node will apply the mask to create transparency effects 1. Connect the "output" to DisplayImage to view the final result ## Important Notes - **Channel Selection**: Choose the channel that contains your mask data: - **PaintMask output**: Usually use "red" channel - **RGBA masks with alpha data**: Use "alpha" channel - **Custom masks**: Try different channels to see which works best - **Mask Processing**: The mask is automatically resized to match the input image dimensions - **RGBA Output**: The final image is converted to RGBA format to support transparency - **Real-time Processing**: The node processes immediately when both inputs are available - **Output Format**: The masked image is saved as a PNG file to preserve transparency ## Common Issues - **No Output**: Check that both "input_image" and "input_mask" are properly connected - **Unexpected Transparency**: Remember that white areas in the mask become opaque, black areas become transparent - **Wrong Channel**: If the mask isn't applying correctly, try a different channel (red, green, blue, or alpha) - **Mask Size Mismatch**: The mask is automatically resized to match the input image, but this may affect quality ## Technical Details The node processes the mask and image through several steps: - **Image Loading**: Loads both the input image and mask from their URLs - **Channel Extraction**: Extracts the specified channel from the mask image: - RGB images: Can extract red, green, or blue channels - RGBA images: Can extract red, green, blue, or alpha channels - L (grayscale) images: Uses the grayscale channel directly - LA images: Can extract alpha or grayscale channels - **Resizing**: Resizes the extracted channel to match the input image dimensions - **Alpha Application**: Applies the channel as the alpha channel to the input image using `putalpha()` - **Output Generation**: Saves the result as a new RGBA image artifact The node handles different mask formats: - **RGB**: Can extract red, green, or blue channels (alpha falls back to red) - **RGBA**: Can extract red, green, blue, or alpha channels - **L**: Uses grayscale channel directly (channel parameter ignored) - **LA**: Can extract alpha or grayscale channels - **Other formats**: Raises an error for unsupported modes # BloomEffect ## What is it? The BloomEffect node applies a beautiful bloom or glow effect to your images, creating dreamy, ethereal results. It simulates the way bright light sources create a soft, luminous halo around them, adding a magical quality to your images. ## When would I use it? Use this node when you want to: - Create dreamy, ethereal image effects - Add a soft glow around bright areas - Enhance the magical quality of fantasy or sci-fi images - Create romantic or atmospheric lighting effects - Add visual interest to portraits or landscapes - Simulate lens flare or light bloom effects - Enhance the mood and atmosphere of your images ## How to use it ### Basic Setup 1. Add the BloomEffect node to your workflow 1. Connect an image source to the "input_image" input 1. Adjust the bloom amount and radius sliders 1. The processed image will be available at the "output" parameter ### Parameters #### Bloom Settings - **bloom_amount** (0.0-2.0, default: 0.5): Controls the intensity of the bloom effect - 0.0 = no bloom effect - Higher values create more intense glow - 2.0 = maximum bloom intensity - **bloom_radius** (1-20, default: 5): Controls the size of the bloom effect - Lower values create tighter, more focused glow - Higher values create larger, more diffuse glow - Larger radius values take longer to process ### Outputs - **output**: The image with the bloom effect applied ## Example A typical bloom effect workflow: 1. Load or generate an image using LoadImage or GenerateImage 1. Connect that image to the BloomEffect node's "input_image" parameter 1. Adjust the bloom settings: - Set bloom_amount to 0.8 for a moderate glow effect - Set bloom_radius to 8 for a medium-sized bloom 1. Connect the "output" to DisplayImage to view the result ## Important Notes - **Live Preview**: Changes are applied in real-time as you move the sliders - **Processing Time**: Larger radius values take longer to process - **Best Results**: Works best on images with bright areas or light sources - **RGBA Support**: The node preserves transparency in RGBA images ## Common Issues - **No Visible Effect**: Make sure bloom_amount is greater than 0 - **Too Subtle**: Increase bloom_amount for more visible effect - **Too Strong**: Decrease bloom_amount for more subtle effect - **Slow Processing**: Reduce bloom_radius for faster processing ## Technical Details The bloom effect is created by: 1. **Brightness Detection**: Identifying bright areas in the image 1. **Gaussian Blur**: Applying blur to create the glow effect 1. **Blending**: Combining the original image with the blurred version 1. **Intensity Control**: Scaling the effect based on bloom_amount This creates a realistic bloom effect similar to what you'd see in professional photography or film. # ColorMatch ## What is it? The ColorMatch node transfers color characteristics from a reference image to a target image using various color matching algorithms. This is useful for automatic color grading, matching the look and feel between images, correcting lighting inconsistencies, and creating consistent visual styles across multiple images. ## When would I use it? Use this node when you want to: - Match colors between two images for consistency - Apply color grading from one image to another - Correct lighting inconsistencies between shots - Create a unified color palette across multiple images - Transfer the mood or atmosphere of a reference image - Color-match stop-motion or video frames - Apply a color style from a reference photo ## How to use it ### Basic Setup 1. Add the ColorMatch node to your workflow 1. Connect the reference image to "reference_image" (the image with the color palette you want to copy) 1. Connect the target image to "target_image" (the image you want to modify) 1. Select a color transfer method 1. Adjust the strength parameter as needed 1. The color-matched image will be available at the "output" parameter ### Parameters #### Image Inputs - **reference_image**: The reference image providing the color palette to transfer - **target_image**: The target image to apply color transfer to (will be modified) #### Color Match Settings - **method**: The color transfer algorithm to use - **mkl**: Monge-Kantorovich Linearization (fast, good quality, default) - **hm**: Histogram Matching (fast, moderate quality) - **reinhard**: Reinhard et al. color transfer (fast, good quality) - **mvgd**: Multi-Variate Gaussian Distribution (medium speed, good quality) - **hm-mvgd-hm**: Compound method (slower, best quality) - **hm-mkl-hm**: Alternative compound method (slower, best quality) - **strength** (0.0-10.0, default: 1.0): Controls the intensity of the color transfer - 0.0 = no change (original image) - 1.0 = full color transfer - Values > 1.0 exaggerate the effect ### Outputs - **output**: The color-matched image with transferred color characteristics ## Example A typical color matching workflow: 1. Load two images using LoadImage nodes 1. Connect the image you want to modify to "input_image" 1. Connect the reference image (with the desired colors) to "reference_image" 1. Select the "mkl" method for a good balance of speed and quality 1. Set strength to 1.0 for full color transfer 1. Connect the "output" to DisplayImage to view the result 1. Adjust strength if the effect is too strong or too subtle ### Use Cases #### Photo Color Grading Match the color palette of a professionally graded photo to your own images: 1. Load your photo as the target image 1. Load the reference photo with the desired color grading 1. Use "reinhard" or "mkl" method 1. Adjust strength to taste (0.7-1.0 for subtle, 1.0-1.5 for dramatic) #### Stop-Motion Correction Correct lighting inconsistencies between stop-motion frames: 1. Load a frame with correct lighting as the reference 1. Load frames that need correction as target images 1. Use "mkl" method for fast processing 1. Set strength to 1.0 for accurate matching #### Batch Style Transfer Apply a consistent color style across multiple images: 1. Load your style reference image 1. Process each target image through the ColorMatch node 1. Use "hm-mvgd-hm" for highest quality results ## Important Notes - **Live Preview**: Changes are applied in real-time as you adjust parameters - **RGB Processing**: Images are converted to RGB for processing - **Preserves Dimensions**: Output image maintains the same dimensions as the target image - **Algorithm Differences**: Different methods produce subtly different results - experiment to find the best one for your use case ## Common Issues - **No Change Visible**: Make sure strength is greater than 0 - **Effect Too Strong**: Reduce the strength value below 1.0 - **Unexpected Colors**: Try a different method - "reinhard" often produces more natural results - **Slow Processing**: Use "mkl" or "hm" for faster results; avoid compound methods for large images ## Technical Details The node uses the color-matcher library to perform color transfer. The available algorithms are: - **MKL (Monge-Kantorovich Linearization)**: Uses optimal transport theory to match color distributions - **HM (Histogram Matching)**: Matches the histogram of each color channel independently - **Reinhard**: Classic color transfer method based on decorrelated color spaces - **MVGD (Multi-Variate Gaussian Distribution)**: Matches the mean and covariance of color distributions - **Compound Methods**: Combine multiple algorithms for improved results The strength parameter uses linear interpolation: `result = target + strength * (matched - target)` # CombineMasks ## What is it? The CombineMasks node merges a list of mask images into a single consolidated mask by taking the **maximum value per pixel** (a union operation). This is useful when a segmentation node produces multiple masks and you want one mask you can apply downstream. ## When would I use it? Use this node when you want to: - Combine multiple segmentation masks into one mask - Create a single “subject mask” from many detected objects - Simplify workflows that expect one mask input ## How to use it ### Basic Setup 1. Add the CombineMasks node to your workflow 1. Provide one or more masks to the `masks` input (as a list) 1. Use the `output_mask` output as the consolidated mask ### Parameters - **masks**: A list of mask images to combine ### Outputs - **output_mask**: The consolidated mask (PNG) ## Important Notes - **Union logic**: For each pixel, the output mask uses the maximum value across all input masks. - **Size requirement**: All input masks must be the **same dimensions**. If sizes differ, the node will fail validation with a clear error. - **Alpha handling**: If an input mask is RGBA/LA, the node uses the **alpha channel** as the mask; otherwise it converts the image to grayscale. # CompareImages ## What is it? The CompareImages node allows you to compare two images side by side, making it easy to see differences, evaluate changes, or compare different versions of the same image. It's perfect for A/B testing, quality control, or analyzing the effects of different image processing operations. ## When would I use it? Use this node when you want to: - Compare before and after versions of processed images - A/B test different image generation prompts - Evaluate the quality of different image processing settings - Compare different versions of the same image - Analyze the effects of image adjustments - Quality control for image processing workflows - Present multiple image options for review ## How to use it ### Basic Setup 1. Add the CompareImages node to your workflow 1. Connect two image sources to the "image1" and "image2" inputs 1. The comparison will be displayed showing both images side by side ### Parameters - **image1**: The first image to compare (left side) - **image2**: The second image to compare (right side) ### Outputs - **comparison**: A side-by-side comparison of both images ## Example A typical comparison workflow: 1. Load an original image using LoadImage 1. Process the image with AdjustImageEQ or another processing node 1. Connect the original image to CompareImages "image1" input 1. Connect the processed image to CompareImages "image2" input 1. The node will display both images side by side for easy comparison ## Important Notes - **Side-by-Side Display**: Images are displayed next to each other for easy comparison - **Automatic Scaling**: Images are automatically scaled to fit the comparison view - **Real-time Updates**: The comparison updates when either input image changes - **High Quality**: Maintains image quality for accurate comparison ## Common Issues - **No Images Showing**: Make sure both image1 and image2 inputs are connected - **Images Too Small**: The node automatically scales images to fit the display - **Layout Issues**: The side-by-side layout is automatic and optimized for comparison ## Technical Details The node creates a composite image that displays both input images side by side, making it easy to: - Compare image quality - See processing effects - Evaluate different versions - Make informed decisions about image processing This is essential for any workflow that involves image processing or generation where you need to evaluate results. # CreateColorBars ## What is it? The CreateColorBars node generates standard color bar test patterns used for video calibration, display testing, and color accuracy verification. It supports over 40 different test patterns including SMPTE, EBU, ARIB standards, as well as various calibration patterns like PLUGE, zone plates, and chroma ramps. ## When would I use it? Use this node when you want to: - Calibrate video displays and monitors - Test color accuracy and brightness levels - Generate test patterns for video production workflows - Verify display settings and color space handling - Create reference images for color grading - Test video encoding and decoding pipelines - Generate patterns for display quality assessment ## How to use it ### Basic Setup 1. Add the CreateColorBars node to your workflow 1. Select the type of color bars you want to generate from the "bar_type" dropdown 1. Set the width and height for your test pattern (default: 1920x1080) 1. The generated color bars image will be available at the "image" output ### Parameters #### Main Settings - **bar_type**: The type of color bars to generate (string, default: "SMPTE 219-100 Bars") Available options include: - **SMPTE Patterns**: SMPTE 219-100 Bars, SMPTE 75% Bars, SMPTE Bars, SMPTE 219+i Bars - **Full Field Patterns**: 100% Full Field Bars, 75% Full Field Bars, 75% Bars Over Red - **International Standards**: EBU Bars (European), ARIB 28-100, ARIB 28-75, ARIB 28+i (Japanese) - **HD Patterns**: HD Color Bars - **Solid Colors**: Full Field White, Blue, Cyan, Green, Magenta, Red, Yellow - **Test Patterns**: Zone Plate, Tartan Bars, Multi Burst, Bowtie - **Grayscale Ramps**: Stair 5 Step, Stair 5 Step Vert, Stair 10 Step, Stair 10 Step Vert - **Gradient Patterns**: Y Ramp Up, Y Ramp Down, Vertical Ramp - **Chroma Patterns**: Legal Chroma Ramp, Full Chroma Ramp, Chroma Ramp - **Calibration Patterns**: Pluge (with advanced options), Pathological EG, Pathological PLL - **Special Patterns**: AV Delay Pattern 1, AV Delay Pattern 2, Bouncing Box - **width**: Width of the color bars image in pixels (integer, default: 1920) - **height**: Height of the color bars image in pixels (integer, default: 1080) #### PLUGE-Specific Parameters These parameters are only visible when "Pluge" is selected as the bar_type: - **pluge_ire_setup**: IRE setup type for PLUGE pattern calibration (string, default: "NTSC 7.5 IRE") - **NTSC 7.5 IRE**: Standard NTSC setup level (default) - **PAL 0 IRE**: PAL standard with 0 IRE black level - **RGB Full Range**: RGB full range (0-255) calibration - **pluge_bar_count**: Number of PLUGE bars to display (integer, default: 3, range: 2-5) - 2 bars: Black level and just above black - 3 bars: Super black, black level, and just above black (standard) - 4-5 bars: Includes intermediate values between black levels - **pluge_orientation**: Orientation of PLUGE bars (string, default: "vertical") - **vertical**: Bars arranged vertically (default) - **horizontal**: Bars arranged horizontally ### Outputs - **image**: The generated color bars image as an ImageUrlArtifact ## Example ### Basic Color Bars Generation 1. Add a CreateColorBars node to your workflow 1. Select "SMPTE 75% Bars" from the bar_type dropdown 1. Set width to 1920 and height to 1080 1. Connect the "image" output to a DisplayImage node to view the result 1. Optionally connect to SaveImage to save the test pattern ### PLUGE Calibration Pattern 1. Add a CreateColorBars node 1. Select "Pluge" from the bar_type dropdown 1. The PLUGE-specific parameters will automatically appear 1. Configure the calibration: - Set pluge_ire_setup to match your video standard (e.g., "NTSC 7.5 IRE" for NTSC, "PAL 0 IRE" for PAL) - Set pluge_bar_count to 3 for standard calibration - Choose vertical or horizontal orientation 1. Connect the output to DisplayImage to view the calibration pattern ### Custom Resolution Test Pattern 1. Add a CreateColorBars node 1. Select your desired pattern type (e.g., "HD Color Bars") 1. Set custom dimensions: - width: 3840 (4K width) - height: 2160 (4K height) 1. The pattern will be generated at your specified resolution ## Important Notes - **Live Preview**: The image is automatically regenerated when you change bar_type, width, height, or PLUGE parameters - **SMPTE Standards**: SMPTE 75% Bars and SMPTE Bars use 75% intensity levels per SMPTE ECR 1-1978 standard for SDTV - **IRE Values**: PLUGE patterns use IRE (Institute of Radio Engineers) units for precise black level calibration - **Real-time Updates**: Changes to parameters trigger immediate regeneration of the color bars - **PNG Format**: All generated images are saved as PNG files for lossless quality ## Common Use Cases ### Video Production - Generate SMPTE color bars for broadcast standards compliance - Create reference patterns for color grading workflows - Test video encoding pipelines with standard test patterns ### Display Calibration - Use PLUGE patterns to calibrate black levels and brightness - Generate zone plates to test display sharpness and focus - Create grayscale ramps to verify gamma curves ### Quality Testing - Use pathological patterns to test video processing equipment - Generate chroma ramps to verify color space handling - Create multi-burst patterns to test frequency response ## Technical Details - **Image Format**: PNG (lossless) - **Color Space**: RGB - **Default Resolution**: 1920x1080 (Full HD) - **Standards Supported**: SMPTE, EBU, ARIB - **IRE Conversion**: IRE values are converted to RGB using the formula: RGB = (IRE / 100) * 255 The node generates industry-standard test patterns suitable for professional video production, broadcast, and display calibration workflows. # GenerateImage ## What is it? The GenerateImage node generates images based on text prompts using Griptape Cloud services. It integrates with Griptape Cloud Image Generation Driver and Prompt Driver to transform textual descriptions into high-quality images. ## When would I use it? Use the GenerateImage node when: - You need to generate images dynamically in your workflow - You want to create visual content based on text descriptions - You need to visualize concepts described in natural language ## How to use it ### Basic Setup 1. Add a GenerateImage node to your workflow 1. Provide a text prompt describing the image you want to generate 1. Run your workflow! ### Parameters - **agent**: The agent responsible for handling prompt and image generation tasks (Agent or dict) - **image_generation_driver**: The driver used for image generation (defaults to None) - **prompt**: Text description of the image to generate (string) - **enhance_prompt**: Whether to enhance the prompt for better image quality (boolean, defaults to True) ### Outputs - **output**: The generated image as an ImageUrlArtifact ## Example A simple workflow to generate and save an image: 1. Add a GenerateImage node to your workflow 1. Connect an Agent node into the agent parameter 1. Set the prompt to "A serene mountain landscape at sunset with a lake reflecting the orange sky" 1. Connect the output to a SaveImage node to store the generated image ## Important Notes - You must set the `GT_CLOUD_API_KEY` environment variable for authentication with Griptape Cloud - The node uses 'dall-e-3' as the default model and 'hd' as the default quality - Enhanced prompts can improve image quality but may interpret your prompt differently than expected # CropImage ## What is it? The CropImage node provides precise control over image cropping, zooming, and rotation. It allows you to select specific areas of an image, apply zoom effects, and rotate images to achieve the perfect composition for your workflow. ## When would I use it? Use this node when you want to: - Crop images to specific dimensions or aspect ratios - Focus on particular areas of an image - Apply zoom effects to magnify details - Rotate images to correct orientation - Create consistent image sizes across your workflow - Remove unwanted areas from images - Prepare images for specific output requirements ## How to use it ### Basic Setup 1. Add the CropImage node to your workflow 1. Connect an image source to the "input_image" input 1. Set the crop coordinates and dimensions 1. Optionally adjust zoom and rotation 1. The processed image will be available at the "output" parameter ### Parameters #### Crop Settings - **left** (0+, default: 0): Left edge of the crop area in pixels - **top** (0+, default: 0): Top edge of the crop area in pixels - **width** (1+, default: 100): Width of the crop area in pixels - **height** (1+, default: 100): Height of the crop area in pixels #### Transform Settings - **zoom** (1.0+, default: 1.0): Zoom factor for the crop area - 1.0 = no zoom - Values > 1.0 zoom in (magnify) - Values < 1.0 zoom out (shrink) - **rotate** (degrees, default: 0): Rotation angle in degrees - Positive values rotate clockwise - Negative values rotate counter-clockwise ### Outputs - **output**: The cropped and transformed image ## Example A typical cropping workflow: 1. Load an image using LoadImage 1. Connect that image to the CropImage node's "input_image" parameter 1. Set the crop area: - Set left to 100 to start cropping 100 pixels from the left - Set top to 50 to start cropping 50 pixels from the top - Set width to 400 to crop 400 pixels wide - Set height to 300 to crop 300 pixels tall 1. Optionally add effects: - Set zoom to 1.2 to magnify the cropped area by 20% - Set rotate to 15 to rotate the image 15 degrees clockwise 1. Connect the "output" to DisplayImage to view the result ## Important Notes - **Live Preview**: Changes are applied in real-time as you adjust parameters - **Boundary Checking**: The node ensures crop areas stay within image bounds - **High Quality**: Uses high-quality resampling for zoom and rotation - **RGBA Support**: The node preserves transparency in RGBA images ## Common Issues - **Empty Crop Area**: Make sure width and height are greater than 0 - **Crop Outside Image**: Ensure left + width and top + height don't exceed image dimensions - **Unexpected Results**: Check that your crop coordinates are within the image bounds ## Technical Details The node performs the following operations: 1. **Crop**: Extracts the specified rectangular area from the image 1. **Zoom**: Scales the cropped area using high-quality resampling 1. **Rotate**: Rotates the image around its center point 1. **Boundary Management**: Ensures all operations stay within valid image bounds This provides professional-quality image cropping and transformation capabilities. # DescribeImage ## What is it? The DescribeImage node uses AI to generate textual descriptions of an image you provide it. It leverages vision-capable models to analyze image content and produce detailed descriptions based on your specific prompting instructions. ## When would I use it? Use this node when you want to: - Generate textual descriptions of images - Extract information from visual content - Create alt text for accessibility purposes - Analyze the content of photographs, diagrams, or other visual media - Convert visual information into text format for further processing ## How to use it ### Basic Setup 1. Add the DescribeImage node to your workflow 1. Connect an image source to the "image" input 1. Optionally provide specific instructions in the "prompt" field 1. Run the node to generate a description of the image Be Specific About What You Want When creating your prompt, clearly state the elements you are interested in. For example: - For color information, try prompts like "Describe the color palette, the color of light and shadow, the saturation and value" - For character details, try "Describe the character's gender, age, clothing, posture, demeanor, and actions" The more precise you are with your prompts, the more likely it is you'll get the kind of output you desire. ### Parameters - **agent**: An optional existing agent configuration to use for image description - **model**: Select a vision-capable prompt model (or connect a Prompt Model Config). Ignored when **agent** is connected. - **image**: The image you would like to describe (required) - **prompt**: Instructions for how you want the image described (defaults to "Describe the image") - **description_only**: If enabled, returns only the description (no conversation) - **output_schema**: Optional JSON Schema for structured output. Accepts either a JSON schema object or a JSON string. When connected, **output** switches to JSON. ### Outputs - **output**: The image description. Returns **text** by default, or **JSON** when **output_schema** is provided. - **agent**: The agent object used for the description, which can be connected to other nodes ## Example Imagine you want to get a detailed description of a landscape photograph: 1. Add a DescribeImage node to your workflow 1. Connect an image output from another node to the "image" input 1. In the "prompt" field, type: "Describe this landscape in detail, including colors, features, and mood" 1. Run the node 1. The "output" will contain a detailed description of the landscape image ### Structured Output Example (JSON) If you want the output in a specific JSON shape: 1. Add a `CreateAgentSchema` node and provide an example JSON object (the shape you want back) 1. Connect `CreateAgentSchema.schema` to `DescribeImage.output_schema` 1. Run `DescribeImage` 1. The `output` will be JSON matching your schema (or the run will fail validation) ## Important Notes - The node requires a valid Griptape API key set up in your environment as `GT_CLOUD_API_KEY` - By default, the node uses the `gpt-5.2` model through the Griptape Cloud API - If no prompt is provided, the default "Describe the image" will be used - If no image is provided, the output will be "No image provided" - You can provide your own agent configuration for more customized behavior - The quality of descriptions depends on the clarity and content of the provided image ## Common Issues - **Missing API Key**: Ensure your Griptape API key is properly set up as the environment variable - **No Image Provided**: Make sure you've connected a valid image to the "image" input - **Poor Description Quality**: Try refining your prompt to be more specific about what aspects of the image you want described - **Processing Errors**: Very complex images or unusual content might result in less accurate descriptions # DisplayChannel ## What is it? The DisplayChannel node creates a mask from an input image by extracting a specific color channel (red, green, blue, or alpha). This node is similar to DisplayMask but defaults to extracting the red channel instead of the alpha channel. A mask is a grayscale image where white areas represent high values and black areas represent low values for the selected channel. ## When would I use it? Use this node when you want to: - Extract specific color channels (red, green, blue) as an image - Analyze the intensity of individual color channels in an image - Create masks for image editing workflows based on color information - Visualize how different color channels contribute to an image - Generate color-based masks for use in other image processing nodes - Debug color channel issues in your image pipeline - Focus on red channel analysis (since it's the default) ## How to use it ### Basic Setup 1. Add the DisplayChannel node to your workflow 1. Connect an image source to the "input_image" input 1. Select the desired channel from the "channel" dropdown (defaults to "red") 1. The generated channel image will be available at the "output" parameter ### Parameters - **input_image**: The image to create a mask from (can be connected from other image nodes) - **channel**: The channel to extract as a mask (red, green, blue, or alpha) - defaults to "red" ### Outputs - **output**: A grayscale mask image created from the selected channel of the input image ## Example A common workflow pattern: 1. Generate or load an image using nodes like GenerateImage or LoadImage 1. Connect that image to the DisplayChannel node's "input_image" parameter 1. The node will default to extracting the red channel, or you can select a different channel 1. The DisplayChannel node will extract the selected channel and create a visible mask 1. Connect the "output" to DisplayImage to view the mask, or to other mask processing nodes ## Channel Options - **red**: Extracts the red color channel (default) - useful for isolating red elements in an image - **green**: Extracts the green color channel - useful for isolating green elements in an image - **blue**: Extracts the blue color channel - useful for isolating blue elements in an image - **alpha**: Extracts the transparency channel - useful for seeing which parts of an image are transparent ## Important Notes - **Default Channel**: Unlike DisplayMask, this node defaults to the "red" channel instead of "alpha" - **Channel Extraction**: The node extracts the specified channel from the input image to create the mask - **Grayscale Output**: The resulting mask is a grayscale image where white represents high values and black represents low values for the selected channel - **Real-time Processing**: The node processes the image immediately when a connection is made or when the input value or channel selection changes - **Output Format**: The mask is saved as a PNG file to preserve quality - **Image Mode Support**: Supports RGB, RGBA, grayscale (L), and grayscale+alpha (LA) image modes ## Common Issues - **No Output**: Check that your image source is properly connected to the "input_image" parameter - **Solid Black Mask**: This may indicate that the selected channel has no variation or low values - **Solid White Mask**: This may indicate that the selected channel has high values throughout - **Unsupported Mode Error**: The image format may not support the selected channel (e.g., alpha channel in RGB images) ## Technical Details The node uses channel extraction logic to: - Load the input image from its URL - Extract the specified channel (red, green, blue, or alpha) - Convert it to a grayscale mask - Save the result as a new image artifact For RGB images, alpha channel selection will fallback to the red channel since RGB doesn't have an alpha channel. ## Difference from DisplayMask The main difference between DisplayChannel and DisplayMask is the default channel selection: - **DisplayChannel**: Defaults to "red" channel - **DisplayMask**: Defaults to "alpha" channel This makes DisplayChannel more suitable for color analysis workflows, while DisplayMask is better for transparency-based workflows. # DisplayImage ## What is it? The DisplayImage node displays an image and provides its dimensions. It's a simple viewer that shows an image in your workflow while also extracting useful information like width and height. ## When would I use it? Use this node when you want to: - Display an image in your workflow for visual feedback - Get the dimensions (width and height) of an image - Pass an image through your workflow while viewing it - Debug your workflow by seeing what image is being processed ## How to use it ### Basic Setup 1. Add the DisplayImage node to your workflow 1. Connect an image source to the "image" input 1. The image will be displayed and its dimensions will be available as outputs (note: They are hidden by default. You can display them in the Properties panel on the right.) ### Parameters - **image**: The image to display ### Outputs - **image**: The same image that was input, passed through for use by other nodes - **width**: The width of the image in pixels (integer) - **height**: The height of the image in pixels (integer) ## Example A common workflow pattern: 1. Connect an image source (like GenerateImage or LoadImage) to the DisplayImage node 1. View the image in the DisplayImage node to confirm it's what you expected 1. Use the width and height outputs if you need the image dimensions for other nodes 1. Connect the image output to other nodes that need the image ## Important Notes - The DisplayImage node passes the image through unchanged - it's purely for display and dimension extraction - For ImageUrlArtifact inputs, the node will fetch the image from the URL to determine dimensions - For ImageArtifact inputs, dimensions are read directly from the artifact's properties - The width and height outputs are hidden from the UI by default, but can be enabled in the Properties Panel. ## Common Issues - **Image not displaying**: Check that your image source is properly connected and producing valid image data - **Dimensions showing as 0**: This may indicate an issue with the image data or connection - **Slow performance with URLs**: Large images from URLs may take time to fetch and process for dimension calculation # DisplayImageGrid ## What is it? The DisplayImageGrid node allows you to display multiple images in a grid or masonry layout with customizable styling options, preset output sizes, and flexible alignment controls. It's perfect for creating image galleries, showcasing multiple results, or presenting image collections in an organized, visually appealing format with professional output quality. ## When would I use it? Use this node when you want to: - Create image galleries or portfolios - Display multiple image generation results - Showcase different versions of processed images - Create image collections or albums - Present multiple image options for review - Build image-based presentations - Organize images in a grid layout - Export grids at standard video resolutions (4K, 1440p, 1080p, 720p) ## How to use it ### Basic Setup 1. Add the DisplayImageGrid node to your workflow 1. Connect multiple image sources to the "images" input 1. Choose your layout style and customize the appearance 1. Select output size mode (custom or preset) 1. The grid will be displayed with all your images ### Parameters #### Image Input - **images**: List of images to display in the grid #### Layout Settings - **layout_style**: The arrangement style for the images - **grid**: Regular grid layout with equal-sized cells - **masonry**: Pinterest-style layout with varying heights - **grid_justification** (grid layout only): Horizontal alignment of images in incomplete rows - **left**: Align images to the left (default) - **center**: Center images horizontally - **right**: Align images to the right - **columns** (1-10, default: 4): Number of columns in the grid - **spacing** (0-100, default: 10): Space between images in pixels #### Styling Options - **crop_to_fit** (boolean, default: true): Crop images to fit perfectly within the grid cells for clean borders - **transparent_bg** (boolean, default: false): Use transparent background instead of solid color - **background_color** (hex color, default: #000000): Background color for the grid (visible by default) - **border_radius** (0-500, default: 8): Corner radius for rounded corners (0 for square) #### Output Settings - **output_image_size**: Choose between custom width or preset sizes - **custom**: Use a custom width value - **preset**: Use standard video resolutions - **output_image_width** (default: 1200): Maximum width in pixels (shown when custom is selected) - **output_preset**: Standard video resolutions (shown when preset is selected) - **4K (3840x2160)**: Ultra HD resolution - **1440p (2560x1440)**: Quad HD resolution - **1080p (1920x1080)**: Full HD resolution - **720p (1280x720)**: HD resolution - **output_format**: Output file format - **png**: Best quality, supports transparency - **jpeg**: Smaller file size, no transparency - **webp**: Modern format with good compression ### Outputs - **output**: The formatted image grid as an ImageUrlArtifact ## Example A typical image grid workflow: 1. Generate multiple images using GenerateImage nodes 1. Connect all images to the DisplayImageGrid node's "images" parameter 1. Set the layout settings: - Select "grid" layout_style for uniform tiles - Set grid_justification to "center" to center incomplete rows - Set columns to 4 for a 4-column grid - Set spacing to 10 for comfortable spacing between images 1. Customize the appearance: - Set background_color to "#000000" (black) for a dark background - Set border_radius to 8 for rounded corners - Enable crop_to_fit for clean borders 1. Choose output size: - Select "preset" for output_image_size - Choose "1080p (1920x1080)" for Full HD output 1. The grid will display all your images in an organized, attractive layout at exactly 1920×1080 pixels ## Important Notes - **Flexible Layout**: Choose between grid and masonry layouts - **Preset Sizes**: Export at standard video resolutions (4K, 1440p, 1080p, 720p) - **Smart Scaling**: When using presets, the grid scales proportionally to fit without distortion - **Justification Control**: Align incomplete rows left, center, or right (grid layout only) - **Background Control**: Choose between solid colors or transparent backgrounds - **Customizable Styling**: Full control over colors, spacing, and appearance - **Multiple Images**: Can handle any number of input images - **No Cropping**: When using presets, images are scaled to fit (not cropped), with padding as needed ## Common Issues - **No Images Showing**: Make sure you've connected images to the "images" parameter - **Layout Issues**: Adjust columns and spacing for better appearance - **Images Too Small**: Increase the overall grid size or reduce the number of columns - **Preset Output Different Than Expected**: Preset mode scales the grid to fit within the exact dimensions while maintaining aspect ratio ## Technical Details The node creates a responsive grid layout that: - Automatically arranges images in the specified layout style - Maintains aspect ratios while fitting images to the grid - Provides consistent spacing and styling - Supports both regular grid and masonry layouts - When using presets, creates a canvas at exact dimensions and scales the grid proportionally - Applies justification for horizontal positioning in grid layout - Centers the grid vertically with background padding when needed This is perfect for creating professional-looking image galleries and collections at standard output resolutions. # DisplayMask ## What is it? The DisplayMask node creates a mask from an input image by extracting a specific channel (red, green, blue, or alpha). A mask is a grayscale image where white areas represent fully opaque regions and black areas represent fully transparent regions. This node is useful for visualizing and working with different color channels in images. ## When would I use it? Use this node when you want to: - Extract transparency information from an image as a visible mask - Extract specific color channels (red, green, blue) as masks - Create masks for image editing workflows - Visualize different channels of an image - Generate masks for use in other image processing nodes - Debug transparency or color channel issues in your image pipeline ## How to use it ### Basic Setup 1. Add the DisplayMask node to your workflow 1. Connect an image source to the "input_image" input 1. Select the desired channel from the "channel" dropdown 1. The generated mask will be available at the "output_mask" parameter ### Parameters - **input_image**: The image to create a mask from (can be connected from other image nodes) - **channel**: The channel to extract as a mask (red, green, blue, or alpha) ### Outputs - **output_mask**: A grayscale mask image created from the selected channel of the input image ## Example A common workflow pattern: 1. Generate or load an image using nodes like GenerateImage or LoadImage 1. Connect that image to the DisplayMask node's "input_image" parameter 1. Select the desired channel (e.g., "alpha" for transparency, "red" for red channel) 1. The DisplayMask node will extract the selected channel and create a visible mask 1. Connect the "output_mask" to DisplayImage to view the mask, or to other mask processing nodes ## Channel Options - **alpha**: Extracts the transparency channel (default) - useful for seeing which parts of an image are transparent - **red**: Extracts the red color channel - useful for isolating red elements in an image - **green**: Extracts the green color channel - useful for isolating green elements in an image - **blue**: Extracts the blue color channel - useful for isolating blue elements in an image ## Important Notes - **Channel Extraction**: The node extracts the specified channel from the input image to create the mask - **Grayscale Output**: The resulting mask is a grayscale image where white represents high values and black represents low values for the selected channel - **Real-time Processing**: The node processes the image immediately when a connection is made or when the input value or channel selection changes - **Output Format**: The mask is saved as a PNG file to preserve quality - **Image Mode Support**: Supports RGB, RGBA, grayscale (L), and grayscale+alpha (LA) image modes ## Common Issues - **No Output**: Check that your image source is properly connected to the "input_image" parameter - **Solid Black Mask**: This may indicate that the selected channel has no variation or low values - **Solid White Mask**: This may indicate that the selected channel has high values throughout - **Unsupported Mode Error**: The image format may not support the selected channel (e.g., alpha channel in RGB images) ## Technical Details The node uses channel extraction logic to: - Load the input image from its URL - Extract the specified channel (red, green, blue, or alpha) - Convert it to a grayscale mask - Save the result as a new image artifact For RGB images, alpha channel selection will fallback to the red channel since RGB doesn't have an alpha channel. # ExtendCanvas ## What is it? The ExtendCanvas node allows you to extend the canvas around an image to fit target aspect ratios or custom dimensions. It's perfect for adding padding, creating specific aspect ratios, or preparing images for different output formats without cropping the original content. ## When would I use it? Use this node when you want to: - Add padding or margins around images - Create specific aspect ratios (16:9, 4:3, square, etc.) - Prepare images for different social media formats - Add space for text overlays or graphics - Create consistent image dimensions across a workflow - Prepare images for print layouts - Add borders or frames around images ## How to use it ### Basic Setup 1. Add the ExtendCanvas node to your workflow 1. Connect an image source to the "input_image" input 1. Set the target aspect ratio or custom dimensions 1. The extended image will be available at the "output" parameter ### Parameters #### Canvas Settings - **target_aspect_ratio**: The desired aspect ratio for the output - **16:9**: Widescreen format - **4:3**: Standard format - **1:1**: Square format - **3:2**: Photo format - **21:9**: Ultra-wide format - **Custom**: Set custom dimensions - **custom_width** (pixels): Custom width when using "Custom" aspect ratio - **custom_height** (pixels): Custom height when using "Custom" aspect ratio #### Extension Settings - **extension_method**: How to extend the canvas - **center**: Center the original image on the new canvas - **top_left**: Position the original image at the top-left - **top_right**: Position the original image at the top-right - **bottom_left**: Position the original image at the bottom-left - **bottom_right**: Position the original image at the bottom-right - **background_color** (hex color, default: #ffffff): Color for the extended areas - **background_opacity** (0.0-1.0, default: 1.0): Opacity of the background color ### Outputs - **output**: The image with extended canvas ## Example A typical canvas extension workflow: 1. Load an image using LoadImage 1. Connect that image to the ExtendCanvas node's "input_image" parameter 1. Set the canvas settings: - Select "16:9" target_aspect_ratio for widescreen format - Select "center" extension_method to center the image - Set background_color to "#000000" for a black background 1. Connect the "output" to DisplayImage to view the result ## Important Notes - **Non-destructive**: The original image content is never cropped or modified - **Flexible Positioning**: Choose where to position the original image on the new canvas - **Aspect Ratio Control**: Create any aspect ratio or use preset formats - **RGBA Support**: The node preserves transparency in RGBA images ## Common Issues - **Image Not Centered**: Check the extension_method setting - **Wrong Aspect Ratio**: Verify your target_aspect_ratio selection - **Background Not Visible**: Make sure background_opacity is greater than 0 ## Technical Details The node performs the following operations: 1. **Canvas Creation**: Creates a new canvas with the target dimensions 1. **Background Fill**: Fills the canvas with the specified background color 1. **Image Positioning**: Places the original image at the specified position 1. **Output Generation**: Saves the final extended image This provides a powerful way to prepare images for specific output requirements without losing any original content. # ExtractKeyColors ## What is it? The ExtractKeyColors node analyzes images and extracts the most prominent colors using either KMeans clustering or Median Cut (MMCQ) algorithms. It automatically orders colors by their frequency in the image and creates dynamic color picker parameters for each extracted color, making it perfect for color palette generation, brand analysis, and design workflows. ## When would I use it? Use this node when you want to: - Generate color palettes from images for design projects - Analyze dominant colors in logos and marketing materials - Extract brand colors from images - Create color schemes based on image content - Build color-based filters and effects - Generate color data for visualization projects - Analyze color distribution in photographs - Create design systems from visual references ## How to use it ### Basic Setup 1. Add the ExtractKeyColors node to your workflow 1. Connect an image source to the "input_image" input 1. Set the number of colors you want to extract 1. The extracted colors will be available as dynamic color picker parameters (color_1, color_2, etc.) ### Parameters #### Image Input - **input_image**: The image to analyze (can be connected from other image nodes) - Supports common formats: JPG, PNG, GIF, BMP, TIFF, ICO, WEBP - Accepts ImageArtifact or ImageUrlArtifact - File browser integration for easy image selection #### Color Settings - **num_colors** (2-12, default: 3): Number of colors to extract - Slider interface for easy selection - Colors are ordered by prominence (most frequent first) - Each extracted color becomes a separate output parameter - **algorithm** (default: kmeans): Color extraction algorithm to use - **kmeans**: Uses KMeans clustering for accurate color grouping - **median_cut**: Uses Modified Median Cut Quantization (MMCQ) for balanced color space division - Dropdown menu for easy selection ### Outputs Dynamic color picker parameters are created for each extracted color: - **color_1, color_2, etc.**: Hexadecimal color values (#RRGGBB) - Each parameter includes an interactive color picker UI - Colors are ordered by frequency (most dominant first) - Available in the Properties panel - Can be connected to other nodes for color-based workflows ## Example A typical color extraction workflow: 1. Load or generate an image using LoadImage or GenerateImage 1. Connect that image to the ExtractKeyColors node's "input_image" parameter 1. Set num_colors to 5 to extract the top 5 dominant colors 1. The node will analyze the image and create 5 color parameters: - color_1: The most prominent color (highest frequency) - color_2: The second most prominent color - color_3: The third most prominent color - And so on... 1. View the extracted colors in the Properties panel 1. Connect the color outputs to other nodes for color-based processing ## Important Notes - **Algorithm Choice**: Choose between KMeans (accurate clustering) and Median Cut (balanced color space) - **Automatic Sorting**: Colors are automatically ordered by their frequency in the image - **Color Diversity**: Both algorithms ensure extracted colors are distinct and representative - **Dynamic Parameters**: The node creates only the number of color parameters you request - **Real-time Processing**: Colors are extracted immediately when the image changes - **Hex Format**: All colors are provided as hexadecimal values (#RRGGBB) - **Compatibility**: Works with Pillow 11+ and NumPy 1.20+ without compatibility issues ## Common Issues - **Too Few Colors**: Increase the num_colors setting to extract more colors - **Similar Colors**: The KMeans algorithm ensures color diversity, but very similar images may produce similar colors - **Processing Time**: Larger images may take longer to process - **No Colors Extracted**: Check that your image source is properly connected and contains valid image data ## Technical Details The node uses a sophisticated color extraction process: 1. **Image Conversion**: Converts input image to PIL Image format and ensures RGB color space 1. **Algorithm Selection**: Choose between two proven color extraction algorithms: - **KMeans**: Uses scikit-learn's KMeans clustering to identify dominant color clusters - Groups similar pixels into clusters based on perceptual similarity - Identifies representative colors for each cluster - Orders colors by cluster size (pixel count) - **Median Cut (MMCQ)**: Modified Median Cut Quantization algorithm - Iteratively divides color space into buckets - Always splits the bucket with the largest color range - Stops at exactly the requested number of colors - Orders colors by bucket size (pixel count) 1. **Frequency Analysis**: Each extracted color includes its frequency/prominence in the image 1. **Automatic Ordering**: Colors are automatically sorted by frequency (most prominent first) 1. **Color Diversity**: Both algorithms ensure extracted colors are distinct and representative 1. **Dynamic UI Creation**: Generates color picker parameters for each extracted color ### Algorithm Benefits - **KMeans Algorithm**: - Accurate color clustering based on perceptual similarity - Excellent for images with distinct color regions - Handles gradients and transitions well - **Median Cut Algorithm**: - Balanced color space representation - Classic quantization approach - Good for images with uniform color distribution - Faster than KMeans for large images - **Shared Benefits**: - Native Python implementations using stable libraries (sklearn, numpy) - Compatible with Pillow 11+ and NumPy 1.20+ - Efficient processing for images of various sizes - Built-in frequency data for each color This provides a professional-grade solution for color extraction using modern, actively-maintained libraries, making it ideal for both creative and technical workflows. # FlipImage ## What is it? The FlipImage node flips images horizontally, vertically, or in both directions. It's perfect for creating mirror effects, correcting orientation issues, or creating symmetrical compositions from existing images. ## When would I use it? Use this node when you want to: - Create mirror effects or reflections - Correct images that are oriented incorrectly - Create symmetrical compositions - Generate variations of existing images - Flip images for artistic or design purposes - Create left/right or up/down variations of the same image ## How to use it ### Basic Setup 1. Add the FlipImage node to your workflow 1. Connect an image source to the "input_image" input 1. Choose your flip direction 1. The flipped image will be available at the "output" parameter ### Parameters #### Flip Settings - **direction**: The direction to flip the image - **horizontal**: Flip left to right (mirror effect) - **vertical**: Flip top to bottom (upside down) - **both**: Flip both horizontally and vertically (180° rotation) ### Outputs - **output**: The flipped image - **was_successful**: Indicates whether the flip operation succeeded - **result_details**: Detailed information about the flip operation ## Example A typical flipping workflow: 1. Load an image using LoadImage 1. Connect that image to the FlipImage node's "input_image" parameter 1. Set the flip direction: - Select "horizontal" to create a mirror image - Select "vertical" to flip upside down - Select "both" to rotate 180 degrees 1. Connect the "output" to DisplayImage to view the result ## Important Notes - **Live Preview**: Changes are applied in real-time as you adjust parameters - **Format Preservation**: The node preserves the original image format and quality - **RGBA Support**: The node preserves transparency in RGBA images - **Success/Failure Tracking**: The node provides detailed status information about the operation ## Common Issues - **No Output**: Check that your image source is properly connected to the "input_image" parameter - **Unexpected Results**: Remember that "horizontal" flips left-to-right, "vertical" flips top-to-bottom - **Both Direction**: "both" applies both horizontal and vertical flips, effectively rotating the image 180 degrees ## Technical Details The node uses PIL's image operations: - **Horizontal Flip**: Uses `ImageOps.mirror()` for left-to-right flipping - **Vertical Flip**: Uses `ImageOps.flip()` for top-to-bottom flipping - **Both Directions**: Applies both operations sequentially This provides reliable image flipping capabilities with proper error handling and status reporting. # Gaussian Edge Fade ## Overview The **Gaussian Edge Fade** node applies a Gaussian-blurred alpha channel fade to image edges for smooth compositing. This creates smooth transitions at the edges of images, making them ideal for overlaying on other images or backgrounds without harsh borders. ## Use Cases - **Image Compositing**: Seamlessly blend images together with smooth edge transitions - **Vignette Effects**: Create professional vignette-style edge fading - **Overlay Preparation**: Prepare images for overlay on varied backgrounds - **Logo Processing**: Add edge fading to logos with transparent backgrounds while preserving cutouts - **Photo Editing**: Create organic, natural-looking edge fades with rounded corners ## Parameters ### Input Parameters | Parameter | Type | Description | | ----------------- | -------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **input_image** | ImageArtifact / ImageUrlArtifact | The input image to apply edge fade to | | **fade_mode** | String | How to measure fade distance: "percentage" (relative to image size) or "pixels" (absolute distance). Default: "percentage" | | **fade_distance** | Integer | Distance from edge to fade. In percentage mode: 5 = 5% of image dimension. In pixels mode: 5 = 5 pixels. Default: 5 | | **blur_radius** | Integer | Gaussian blur radius for smooth edge transition (0-100). Higher values create softer fades. Default: 10 | | **fade_curve** | Float | Controls transparency transition shape (0.5-4.0). 1.0 = linear, >1.0 = more transparent near edges (aggressive), \<1.0 = less transparent near edges (gentle). Default: 2.0 | | **edge_shape** | String | Shape of the fade: "square" (straight edges) or "rounded" (curved corners). Default: "square" | | **replace_mask** | Boolean | If False, combines edge fade with existing alpha channel. If True, replaces existing alpha entirely. Default: False | | **apply_top** | Boolean | Apply fade to top edge. Default: True | | **apply_bottom** | Boolean | Apply fade to bottom edge. Default: True | | **apply_left** | Boolean | Apply fade to left edge. Default: True | | **apply_right** | Boolean | Apply fade to right edge. Default: True | ### Output Parameters | Parameter | Type | Description | | ---------------- | ---------------- | ------------------------------------------------ | | **output_image** | ImageUrlArtifact | The processed image with alpha channel edge fade | ## Key Features ### Fade Modes **Percentage Mode**: Resolution-independent fading that scales with image size. A 5% fade looks consistent across images of any size. **Pixels Mode**: Fixed-size fading with precise pixel control. A 50-pixel fade is the same absolute distance regardless of image size. ### Fade Curve The fade curve parameter uses a power function to control how transparency transitions from edge to center: - **0.5-0.9**: Gentle curve - edges stay MORE opaque with quick transition to full opacity - **1.0**: Linear - even transition from transparent to opaque - **2.0 (Default)**: Aggressive - edges stay MORE transparent with gradual transition - **3.0-4.0**: Very aggressive - extreme transparency at edges, very gradual fade ### Edge Shapes **Square**: Each edge fades independently, creating straight transitions. Best for traditional vignettes and letterbox effects. **Rounded**: Fade follows curved contours using distance field calculations, creating smooth organic corner transitions. Best for modern UI designs and natural photo compositing. ### Alpha Channel Handling **Replace Mask = False (Default)**: Preserves existing transparency and combines it with the edge fade using multiplicative blending. Perfect for logos with transparent backgrounds. **Replace Mask = True**: Ignores existing alpha channel and applies only the new edge fade. Useful for images with unwanted transparency. ## Example Usage ### Basic Vignette Effect 1. Connect an image to **input_image** 1. Set **fade_mode** to "percentage" 1. Set **fade_distance** to 10 (10%) 1. Set **blur_radius** to 15 1. Set **fade_curve** to 2.0 1. Keep all **apply\_**\* edges set to True 1. Run the node Result: Image with soft, even fade on all edges. ### Letterbox Effect 1. Connect an image to **input_image** 1. Set **fade_mode** to "pixels" 1. Set **fade_distance** to 50 1. Set **blur_radius** to 20 1. Set **apply_top** and **apply_bottom** to True 1. Set **apply_left** and **apply_right** to False 1. Run the node Result: Horizontal fades only, suitable for widescreen overlays. ### Rounded Corner Composite 1. Connect an image to **input_image** 1. Set **edge_shape** to "rounded" 1. Set **fade_mode** to "percentage" 1. Set **fade_distance** to 8 1. Set **blur_radius** to 12 1. Set **fade_curve** to 2.5 (aggressive) 1. Run the node Result: Image with smooth rounded corners perfect for modern UI compositing. ### Preserve Logo Transparency 1. Connect a logo PNG with transparent background to **input_image** 1. Set **replace_mask** to False 1. Set **fade_distance** to 5 1. Set **blur_radius** to 10 1. Run the node Result: Logo keeps its transparent cutouts while adding smooth edge fade. ## Tips - **For natural compositing**: Use fade_curve values between 2.0-3.0 for more transparency at edges - **For resolution-independent effects**: Use percentage mode with consistent values across different image sizes - **For rounded corners**: Combine edge_shape "rounded" with all edges enabled for organic oval shapes - **For selective fading**: Disable specific edges to create directional fades - **For text overlays**: Use gentle fade_curve (0.7-0.9) to keep edges more visible ## Technical Notes - Output format is RGBA PNG with alpha channel preserved - Supports any input image format that PIL can process - Uses NumPy for efficient gradient calculations - Applies Gaussian blur using PIL's ImageFilter for smooth transitions - Multiplicative alpha blending when combining with existing alpha channels - Automatically clamps fade distance to prevent artifacts when exceeding image dimensions # ImageBash ## What is it? The ImageBash node allows you to create composite images by combining multiple images into a single canvas. It's perfect for creating collages, image grids, or combining multiple images into one cohesive composition with customizable canvas sizes and background colors. ## When would I use it? Use this node when you want to: - Create image collages or grids - Combine multiple images into a single composition - Create image mosaics or patterns - Build image galleries or portfolios - Combine related images for comparison - Create social media image layouts - Build image-based presentations ## How to use it ### Basic Setup 1. Add the ImageBash node to your workflow 1. Connect multiple image sources to the "input_images" input 1. Set the canvas size and background color 1. The composite image will be available at the "output_image" parameter ### Parameters #### Canvas Settings - **canvas_size**: The size of the output canvas - **Custom**: Set custom width and height - **HD**: 1280x720 - **Full HD**: 1920x1080 - **4K**: 3840x2160 - **Square**: 1024x1024 - **Instagram Post**: 1080x1080 - **Instagram Story**: 1080x1920 - **width** (pixels, default: 1920): Custom canvas width (when canvas_size is "Custom") - **height** (pixels, default: 1080): Custom canvas height (when canvas_size is "Custom") - **background_color** (hex color, default: #ffffff): Background color for empty areas #### Image Input - **input_images**: List of images to combine (can be connected from multiple image sources) ### Outputs - **output_image**: The composite image with all input images combined ## Example A typical image bashing workflow: 1. Load multiple images using LoadImage nodes 1. Connect all images to the ImageBash node's "input_images" parameter 1. Set the canvas settings: - Select "Full HD" for a 1920x1080 canvas - Set background_color to "#f0f0f0" for a light gray background 1. The node will automatically arrange the images on the canvas 1. Connect the "output_image" to DisplayImage to view the result ## Important Notes - **Automatic Layout**: The node automatically arranges images on the canvas - **Flexible Canvas**: Choose from preset sizes or set custom dimensions - **Background Control**: Set any background color for empty areas - **Multiple Images**: Can handle any number of input images ## Common Issues - **No Images Showing**: Make sure you've connected images to the "input_images" parameter - **Images Too Small**: Increase canvas size or check image dimensions - **Wrong Background**: Adjust the background_color parameter to your preference ## Technical Details The node performs the following operations: 1. **Canvas Creation**: Creates a new canvas with the specified size and background color 1. **Image Arrangement**: Automatically positions input images on the canvas 1. **Compositing**: Combines all images into a single composite image 1. **Output Generation**: Saves the final composite as a high-quality image This provides a powerful way to create complex image compositions from multiple sources. # ImageBlendCompositor ## What is it? The ImageBlendCompositor node allows you to blend two images together using various blend modes, similar to layers in photo editing software. It provides precise control over how images are combined, including positioning, opacity, and advanced compositing options. ## When would I use it? Use this node when you want to: - Combine two images using different blend modes - Create composite images with multiple layers - Apply creative blending effects - Overlay images with precise positioning - Create double exposure effects - Blend textures or patterns onto images - Create artistic compositions ## How to use it ### Basic Setup 1. Add the ImageBlendCompositor node to your workflow 1. Connect two image sources to the "input_image" and "blend_image" inputs 1. Select a blend mode and adjust opacity 1. The composited image will be available at the "output" parameter ### Parameters #### Image Inputs - **input_image**: The base image (background layer) - **blend_image**: The image to blend on top (foreground layer) #### Blend Settings - **blend_mode**: The blending method to use - **normal**: Standard alpha blending - **multiply**: Darkens the base image - **screen**: Lightens the base image - **overlay**: Combines multiply and screen - **soft_light**: Gentle light/dark effect - **hard_light**: Strong light/dark effect - **color_dodge**: Brightens base image - **color_burn**: Darkens base image - **darken**: Keeps darker pixels - **lighten**: Keeps lighter pixels - **difference**: Subtracts colors - **exclusion**: Similar to difference but softer - **opacity** (0.0-1.0, default: 1.0): Controls the strength of the blend - 0.0 = completely transparent (no blend) - 1.0 = completely opaque (full blend) #### Positioning - **x_offset** (pixels, default: 0): Horizontal position of the blend image - **y_offset** (pixels, default: 0): Vertical position of the blend image #### Advanced Options - **resize_blend_to_fit** (boolean, default: false): Resize blend image to fit base image - **preserve_alpha** (boolean, default: true): Preserve transparency in the result - **invert_blend** (boolean, default: false): Invert the blend image before blending ### Outputs - **output**: The composited image with both images blended together ## Example A typical blending workflow: 1. Load two images using LoadImage nodes 1. Connect the first image to "input_image" (background) 1. Connect the second image to "blend_image" (foreground) 1. Adjust the blend settings: - Select "multiply" blend mode for a darkening effect - Set opacity to 0.7 for a subtle blend - Set x_offset to 50 to shift the blend image 50 pixels right - Set y_offset to 30 to shift the blend image 30 pixels down 1. Connect the "output" to DisplayImage to view the result ## Important Notes - **Live Preview**: Changes are applied in real-time as you adjust parameters - **Image Sizes**: The blend image can be positioned outside the base image bounds - **RGBA Support**: The node preserves transparency in RGBA images - **High Quality**: Uses professional-grade blending algorithms ## Common Issues - **No Blend Visible**: Make sure opacity is greater than 0 and blend_mode is not "normal" with full opacity - **Blend Image Not Visible**: Check that the blend image is positioned within the base image bounds - **Unexpected Results**: Different blend modes produce very different effects - experiment to find the right one ## Technical Details The node supports multiple blend modes: - **Mathematical Blends**: multiply, screen, overlay, soft_light, hard_light - **Color Operations**: color_dodge, color_burn, difference, exclusion - **Selection Blends**: darken, lighten - **Standard Blending**: normal (alpha blending) Each blend mode uses different mathematical operations to combine the pixel values, creating unique visual effects. # Image Grid Splitter ## What is it? The Image Grid Splitter node takes a single image that contains a **regular grid** of images and splits it into individual images. It supports: - **Auto** grid detection (best-effort) - **Manual** grid definition (rows/columns) - A **preview overlay** showing dotted yellow grid lines - Outputs as both a **list** and **per-cell outputs** (`r1c1`, `r1c2`, etc.) ## When would I use it? Use this node when you have an image that contains multiple images (e.g., a 2×2 or 3×3 collage) and you want to: - Extract each tile as its own image for downstream processing - Feed individual grid cells into other nodes - Keep both a list output and named per-cell outputs for easy wiring ## How to use it ### Basic setup 1. Add **Image Grid Splitter** to your workflow 1. Connect your grid image to **Input Image** 1. Choose **Grid Detection**: - **auto**: the node estimates rows/columns - **manual**: you specify **Rows** and **Columns** 1. Review **Preview** to confirm the split boundaries 1. Run the node to generate split images Auto detection is best-effort Auto mode assumes a **regular grid** (equal-sized cells) and may be less accurate when adjacent cells are visually similar. If the preview lines don’t match your grid, switch to **manual**. ### Parameters - **Input Image**: The grid image to split. Accepts `ImageArtifact` or `ImageUrlArtifact`. - **Grid Detection**: - **auto**: detect a regular grid - **manual**: use explicit Rows/Columns - **Rows / Columns** (manual only): Sliders from 1–12. - **Preview** (output): The original image with dotted yellow grid lines. - **Detections (auto)** (output group): Detected Rows/Columns/Count (hidden when manual mode is selected). ### Outputs - **Images**: List of `ImageUrlArtifact` in row-major order (left-to-right, top-to-bottom). - **Grid Cells**: One `ImageUrlArtifact` output per cell, named: - `r1c1`, `r1c2`, ..., `r{row}c{col}` Per-cell outputs are created on run The `r{row}c{col}` outputs are created/updated when you run the node, based on the chosen (or detected) grid size. ## Example If your input is a 2×2 grid: - `r1c1` is top-left - `r1c2` is top-right - `r2c1` is bottom-left - `r2c2` is bottom-right And `Images` will contain `[r1c1, r1c2, r2c1, r2c2]`. ## Common issues - **Preview grid lines don’t match**: switch to **manual** and set the correct Rows/Columns. - **Unexpected auto detection**: some grids have weak visual boundaries; manual mode is the reliable fallback. # InvertImage ## What is it? The InvertImage node creates a negative version of an input image by inverting all the color values. It's like creating a photographic negative - dark areas become light, light areas become dark, and colors are inverted to their complementary colors. ## When would I use it? Use this node when you want to: - Create artistic effects by inverting image colors - Generate negative versions of images for creative purposes - Create high-contrast effects for image processing workflows - Experiment with different visual styles in your image generation pipelines - Create complementary color schemes from existing images ## How to use it ### Basic Setup 1. Add the InvertImage node to your workflow 1. Connect an image source to the "input_image" input 1. The inverted image will be available at the "output" parameter ### Parameters - **input_image**: The image to invert (can be connected from other image nodes) ### Outputs - **output**: The inverted (negative) version of the input image ## Example A common workflow pattern: 1. Generate or load an image using nodes like GenerateImage or LoadImage 1. Connect that image to the InvertImage node's "input_image" parameter 1. The InvertImage node will create a negative version of the image 1. Connect the "output" to DisplayImage to view the result, or to other processing nodes ## Important Notes - **RGBA Images**: For images with transparency (RGBA), the node inverts the RGB color channels while preserving the original alpha channel - **Other Formats**: Images in other formats are converted to RGB before inversion - **Real-time Processing**: The node processes the image immediately when a connection is made or when the input value changes - **Output Format**: The inverted image is saved as a PNG file to preserve quality ## Common Issues - **No Output**: Check that your image source is properly connected to the "input_image" parameter - **Unexpected Results**: Remember that inversion creates a negative - dark areas become light and vice versa - **Transparency Issues**: If you need to preserve specific transparency effects, the node will maintain the original alpha channel for RGBA images ## Technical Details The node handles different image modes intelligently: - **RGBA**: Inverts RGB channels, preserves alpha channel - **RGB**: Direct inversion of all color values - **Other modes**: Converts to RGB first, then inverts # InvertMask ## What is it? The InvertMask node creates an inverted version of an input mask. It flips the transparency values - areas that were opaque become transparent, and areas that were transparent become opaque. This is useful for creating complementary masks or switching between positive and negative selections. ## When would I use it? Use this node when you want to: - Create the opposite of an existing mask - Switch between positive and negative selections - Invert transparency effects - Create complementary masks for image compositing - Reverse the effect of a mask in your workflow ## How to use it ### Basic Setup 1. Add the InvertMask node to your workflow 1. Connect a mask source to the "input_mask" input 1. The inverted mask will be available at the "output_mask" parameter ### Parameters - **input_mask**: The mask to invert (can be connected from other mask nodes like DisplayMask or PaintMask) ### Outputs - **output_mask**: The inverted version of the input mask ## Example A common workflow pattern: 1. Create a mask using nodes like DisplayMask or PaintMask 1. Connect that mask to the InvertMask node's "input_mask" parameter 1. The InvertMask node will create an inverted version of the mask 1. Connect the "output_mask" to DisplayImage to view the inverted mask, or to other mask processing nodes ## Important Notes - **Inversion Logic**: White areas in the input mask become black in the output, and black areas become white - **Alpha Channel Handling**: For RGBA images, the node inverts the alpha channel specifically - **Grayscale Conversion**: Non-grayscale images are converted to grayscale before inversion - **Real-time Processing**: The node processes the mask immediately when a connection is made or when the input value changes - **Output Format**: The inverted mask is saved as a PNG file to preserve quality ## Common Issues - **No Output**: Check that your mask source is properly connected to the "input_mask" parameter - **Unexpected Results**: Remember that inversion flips the mask values - what was opaque becomes transparent - **Color Images**: If you input a color image, it will be converted to grayscale before inversion ## Technical Details The node handles different image modes intelligently: - **RGBA**: Inverts the alpha channel while preserving RGB channels - **Grayscale (L)**: Direct inversion of grayscale values - **Other modes**: Converts to grayscale first, then inverts The inversion is performed using the formula: `255 - original_value`, which flips the brightness values. # LoadImage ## What is it? The LoadImage is a simple building block that lets you bring an image into your workflow. Think of it as picking up a photo so you can use it in your project. ## When would I use it? Use this node when you want to: - Use an image that was created by another node (like CreateImage) - Pass an image to other nodes in your workflow - Display an image as part of your project ## How to use it ### Basic Setup 1. Add the LoadImage to your workflow 1. Connect it to a source of images (like a CreateImage) ### Parameters - **image**: The image to load (this can be connected to an output from another node) ### Outputs - **image**: The loaded image that can be used by other nodes in your flow ## Example Imagine you've created an image with the CreateImage and now want to use it elsewhere: 1. Connect the "output" from your CreateImage to the "image" input of the LoadImage 1. The LoadImage will make the image available to use in the rest of your workflow ## Important Notes - The LoadImage simply passes the image through - it doesn't change the image itself - You can click the file browser icon to select an image from your computer - The image preview can be expanded by clicking the expander icon ## Common Issues - **No Image Showing**: Make sure you've properly connected an image source to this node - **Wrong Image Type**: Make sure you're connecting an ImageUrlArtifact, ImageArtifact, or string path/URL to this node # MergeImages ## What is it? The MergeImages node allows you to combine multiple images into a single composition using different layout options. It's perfect for creating collages, image grids, or combining related images into one cohesive composition with flexible arrangement options. ## When would I use it? Use this node when you want to: - Create image collages or grids - Combine multiple related images - Create image mosaics or patterns - Build image galleries or portfolios - Combine images for comparison - Create social media image layouts - Build image-based presentations ## How to use it ### Basic Setup 1. Add the MergeImages node to your workflow 1. Connect multiple image sources to the "input_images" input 1. Choose your layout style and arrangement 1. The merged image will be available at the "output" parameter ### Parameters #### Image Input - **input_images**: List of images to merge (can be connected from multiple image sources) #### Layout Settings - **layout_style**: The arrangement style for the images - **grid**: Regular grid layout with equal-sized cells - **horizontal**: Arrange images in a horizontal row - **vertical**: Arrange images in a vertical column - **masonry**: Pinterest-style layout with varying heights - **columns** (1-10, default: 3): Number of columns in grid layout - **spacing** (0-50, default: 10): Space between images in pixels #### Styling Options - **background_color** (hex color, default: #ffffff): Background color for the merged image - **border_radius** (0-20, default: 0): Corner radius for image borders - **padding** (0-100, default: 20): Padding around the entire composition ### Outputs - **output**: The merged image with all input images combined ## Example A typical image merging workflow: 1. Load multiple images using LoadImage nodes 1. Connect all images to the MergeImages node's "input_images" parameter 1. Set the layout settings: - Select "grid" layout_style for a regular grid - Set columns to 2 for a 2-column layout - Set spacing to 15 for comfortable spacing between images 1. Customize the appearance: - Set background_color to "#f0f0f0" for a light gray background - Set border_radius to 5 for slightly rounded corners - Set padding to 30 for space around the entire composition 1. Connect the "output" to DisplayImage to view the result ## Important Notes - **Flexible Layout**: Choose from multiple layout styles - **Automatic Arrangement**: Images are automatically arranged based on your settings - **Customizable Styling**: Full control over colors, spacing, and appearance - **Multiple Images**: Can handle any number of input images ## Common Issues - **No Images Showing**: Make sure you've connected images to the "input_images" parameter - **Layout Issues**: Adjust columns and spacing for better appearance - **Images Too Small**: Increase the overall composition size or reduce the number of columns ## Technical Details The node creates a composite image that: - Automatically arranges input images in the specified layout - Maintains aspect ratios while fitting images to the layout - Provides consistent spacing and styling - Supports multiple layout styles for different use cases This provides a powerful way to create complex image compositions from multiple sources. # PaintMask ## What is it? The PaintMask node creates an editable mask from an input image and allows you to paint on it to modify the transparency. It automatically generates an initial mask from the image's alpha channel, then provides an interactive painting interface where you can edit the mask by painting on it. The node also applies the mask to the input image to show the final result. ## When would I use it? Use this node when you want to: - Create custom masks for image editing and compositing - Manually refine automatically generated masks - Paint transparency effects on images - Create selective transparency for image overlays - Fine-tune mask boundaries for precise image editing ## How to use it ### Basic Setup 1. Add the PaintMask node to your workflow 1. Connect an image source to the "input_image" input 1. The node will automatically generate an initial mask from the image's alpha channel 1. Use the painting interface in the "output_mask" parameter to edit the mask 1. The final masked image will be available at the "output_image" parameter ### Parameters - **input_image**: The image to create a mask from (can be connected from other image nodes) ### Outputs - **output_mask**: An editable mask image that you can paint on to modify transparency - **output_image**: The final image with the mask applied (transparency based on the mask) ## Example A common workflow pattern: 1. Generate or load an image using nodes like GenerateImage or LoadImage 1. Connect that image to the PaintMask node's "input_image" parameter 1. The node will create an initial mask from the image's alpha channel 1. Use the painting tools in the "output_mask" parameter to refine the mask 1. The "output_image" will show the result with your painted mask applied 1. Connect the "output_image" to DisplayImage to view the final result ## Important Notes - **Automatic Mask Generation**: The node automatically creates an initial mask from the input image's alpha channel - **Interactive Painting**: You can paint directly on the mask using the built-in painting interface - **Mask Persistence**: The mask is preserved when you change the input image, unless the source image URL changes - **Red Channel Usage**: The mask uses the red channel of the painted image as the alpha channel for the final result - **Real-time Updates**: Changes to the mask are immediately reflected in the output image ## Common Issues - **No Output**: Check that your image source is properly connected to the "input_image" parameter - **Mask Not Updating**: If you change the input image, the mask may need to be regenerated - **Painting Not Working**: Make sure you're using the painting interface in the "output_mask" parameter - **Unexpected Transparency**: Remember that white areas in the mask become opaque, black areas become transparent ## Technical Details The node handles mask generation and editing through several key processes: - **Initial Mask Creation**: Extracts the alpha channel from the input image and converts it to a grayscale mask - **Mask Editing**: Provides an interactive interface for painting on the mask - **Mask Application**: Uses the red channel of the painted mask as the alpha channel for the final image - **Persistence**: Tracks whether the mask has been manually edited to determine when to regenerate vs. preserve edits # RescaleImage ## What is it? The RescaleImage node allows you to resize images with precise control over dimensions, scaling methods, and quality. It supports both pixel-based and percentage-based scaling, making it perfect for preparing images for specific output requirements or creating different sizes from a single source. ## When would I use it? Use this node when you want to: - Resize images to specific dimensions - Scale images by percentage - Prepare images for different output formats - Create thumbnails or preview images - Optimize images for web or print - Batch resize multiple images - Maintain aspect ratios while scaling ## How to use it ### Basic Setup 1. Add the RescaleImage node to your workflow 1. Connect an image source to the "input_image" input 1. Choose your resize mode and set the target size 1. The resized image will be available at the "output" parameter ### Parameters #### Resize Settings - **resize_mode**: The method for determining the new size - **percentage**: Scale by percentage (e.g., 50% = half size) - **pixels**: Set exact pixel dimensions - **target_size** (pixels, default: 512): Target size when using pixel mode - For width-based scaling: sets the width, height scales proportionally - For height-based scaling: sets the height, width scales proportionally - **percentage_scale** (1-1000, default: 100): Scale percentage when using percentage mode - 50 = half size - 100 = original size - 200 = double size #### Quality Settings - **resample_filter**: The algorithm used for resampling - **lanczos**: High quality, slower (recommended for most cases) - **bicubic**: Good quality, medium speed - **bilinear**: Lower quality, faster - **nearest**: Pixelated, fastest ### Outputs - **output**: The resized image ## Example A typical rescaling workflow: 1. Load an image using LoadImage 1. Connect that image to the RescaleImage node's "input_image" parameter 1. Set the resize settings: - Select "percentage" resize_mode - Set percentage_scale to 50 to create a half-size version - Select "lanczos" resample_filter for high quality 1. Connect the "output" to DisplayImage to view the result ## Important Notes - **Live Preview**: Changes are applied in real-time as you adjust parameters - **Aspect Ratio**: The node maintains the original aspect ratio - **High Quality**: Lanczos resampling provides the best quality for most use cases - **RGBA Support**: The node preserves transparency in RGBA images ## Common Issues - **Image Too Small**: Increase the percentage_scale or target_size - **Poor Quality**: Use "lanczos" resample_filter for better quality - **Wrong Dimensions**: Check that your resize_mode and target settings are correct ## Technical Details The node uses PIL's resampling algorithms: - **Lanczos**: Uses Lanczos kernel for high-quality resampling - **Bicubic**: Uses bicubic interpolation for good quality - **Bilinear**: Uses bilinear interpolation for faster processing - **Nearest**: Uses nearest neighbor for pixelated results This provides professional-quality image resizing capabilities suitable for any workflow. # SaveImage ## What is it? The SaveImage node allows you to save images to your local file system. It's a simple but essential node for preserving the results of your image processing workflows, allowing you to export images in various formats and quality settings. ## When would I use it? Use this node when you want to: - Save processed images to your computer - Export images in specific formats (PNG, JPEG, etc.) - Control image quality and compression settings - Preserve the results of your image processing workflows - Create backups of important images - Export images for use in other applications ## How to use it ### Basic Setup 1. Add the SaveImage node to your workflow 1. Connect an image source to the "input_image" input 1. Set the output file path and format 1. The image will be saved to the specified location ### Parameters #### Image Input - **input_image**: The image to save (can be connected from any image processing node) #### Output Settings - **output_path**: The file path where the image will be saved - Can be a relative path (e.g., "output/image.png") - Can be an absolute path (e.g., "/Users/username/Desktop/image.png") - File extension determines the format - **image_format**: The format to save the image in - **PNG**: Lossless format, supports transparency - **JPEG**: Compressed format, smaller file sizes - **BMP**: Uncompressed bitmap format - **TIFF**: High-quality format for professional use #### Quality Settings (JPEG only) - **quality** (1-100, default: 95): JPEG compression quality - Higher values = better quality, larger files - Lower values = lower quality, smaller files ### Outputs - **saved_path**: The path where the image was saved - **file_size**: The size of the saved file in bytes ## Example A typical image saving workflow: 1. Process an image using AdjustImageEQ or another processing node 1. Connect the processed image to the SaveImage node's "input_image" parameter 1. Set the output settings: - Set output_path to "processed_image.png" - Select "PNG" image_format for lossless quality 1. The image will be saved to your specified location 1. The saved_path output will show where the file was saved ## Important Notes - **File Paths**: Make sure the output directory exists or the node will create it - **Format Support**: Different formats support different features (transparency, compression, etc.) - **Quality Control**: JPEG quality setting only affects JPEG format - **Overwrite Protection**: The node will overwrite existing files with the same name ## Common Issues - **File Not Saved**: Check that the output_path is valid and you have write permissions - **Wrong Format**: Make sure the file extension matches the image_format setting - **Poor Quality**: For JPEG, increase the quality setting ## Technical Details The node uses PIL's image saving capabilities: - **PNG**: Lossless compression with transparency support - **JPEG**: Lossy compression with quality control - **BMP**: Uncompressed bitmap format - **TIFF**: High-quality format with various compression options This provides reliable image saving functionality for any workflow that needs to preserve results. # Webcam ## What is it? The Webcam node allows you to capture images directly from your device's camera. It's perfect for real-time image capture, creating interactive workflows, or incorporating live camera input into your image processing pipelines. ## When would I use it? Use this node when you want to: - Capture images from your webcam or camera - Create real-time image processing workflows - Build interactive applications with live camera input - Capture photos for immediate processing - Create live image analysis or recognition systems - Build camera-based automation workflows ## How to use it ### Basic Setup 1. Add the Webcam node to your workflow 1. Click the "Capture" button to take a photo 1. The captured image will be available at the "output" parameter ### Parameters #### Camera Settings - **camera_index** (0-10, default: 0): Which camera to use (if multiple cameras are available) - **resolution**: The capture resolution - **640x480**: Standard resolution - **1280x720**: HD resolution - **1920x1080**: Full HD resolution - **Custom**: Set custom width and height - **custom_width** (pixels): Custom width when using "Custom" resolution - **custom_height** (pixels): Custom height when using "Custom" resolution #### Capture Settings - **auto_capture** (boolean, default: false): Automatically capture images at intervals - **capture_interval** (seconds, default: 1): Interval between automatic captures ### Outputs - **output**: The captured image from the camera ## Example A typical webcam workflow: 1. Add the Webcam node to your workflow 1. Set the camera settings: - Set camera_index to 0 for the default camera - Select "1280x720" resolution for HD quality 1. Click the "Capture" button to take a photo 1. Connect the "output" to DisplayImage to view the captured image 1. Optionally connect to other processing nodes for real-time image processing ## Important Notes - **Camera Access**: The node requires camera permissions to work - **Real-time Capture**: Images are captured in real-time when you click "Capture" - **Multiple Cameras**: Use camera_index to select different cameras if available - **Resolution Control**: Choose the appropriate resolution for your needs ## Common Issues - **No Camera Found**: Make sure your camera is connected and not being used by other applications - **Permission Denied**: Grant camera permissions to the application - **Poor Quality**: Increase the resolution setting for better image quality - **Camera Busy**: Close other applications that might be using the camera ## Technical Details The node uses your device's camera system to: - Access available cameras - Capture images at the specified resolution - Provide real-time image capture capabilities - Support multiple camera inputs This provides a powerful way to incorporate live camera input into your image processing workflows. # Display JSON Display JSON data with automatic repair and formatting. ## Description The Display JSON node takes JSON data and formats it for display. It uses `json-repair` to handle malformed JSON strings and provides a clean, readable output for viewing and debugging JSON data in your workflows. ## Parameters ### Input Parameters | Parameter | Type | Description | Default | | --------- | --------------- | ------------------------ | ------- | | `json` | json, str, dict | The JSON data to display | `{}` | ### Output Parameters | Parameter | Type | Description | | --------- | ---- | ----------------------- | | `json` | json | The formatted JSON data | ## Features - **Automatic JSON Repair**: Handles malformed JSON strings using `json-repair` - **Multiple Input Types**: Accepts dictionaries, strings, and other data types - **Error Handling**: Graceful fallbacks when parsing fails - **Display Optimization**: Formats JSON for easy reading and debugging - **Real-time Processing**: Updates automatically when input changes ## Examples ### Basic JSON Display ``` # Input: {"name": "John", "age": 30, "active": true} # Output: {"name": "John", "age": 30, "active": true} ``` ### Malformed JSON Repair and Display ``` # Input: '{"name": "John", age: 30, "city": "New York"}' # Missing quotes around age # Output: {"name": "John", "age": 30, "city": "New York"} # Repaired and formatted ``` ### Complex JSON Structure ``` # Input: {"user": {"name": "Alice", "profile": {"email": "alice@example.com", "preferences": {"theme": "dark"}}}} # Output: {"user": {"name": "Alice", "profile": {"email": "alice@example.com", "preferences": {"theme": "dark"}}}} ``` ### String to JSON Conversion and Display ``` # Input: '{"items": [{"title": "Book", "price": 25}, {"title": "Magazine", "price": 10}]}' # Output: {"items": [{"title": "Book", "price": 25}, {"title": "Magazine", "price": 10}]} ``` ## Use Cases - **Debugging**: View and inspect JSON data during workflow development - **Data Validation**: Verify JSON structure and content - **API Response Inspection**: Examine responses from external APIs - **Configuration Review**: Check JSON configuration files - **Data Flow Monitoring**: Monitor JSON data as it moves through your workflow ## Related Nodes - [JSON Input](https://docs.griptapenodes.com/nodes/json/json_input/index.md) - Create JSON data from inputs - [JSON Extract Value](https://docs.griptapenodes.com/nodes/json/json_extract_value/index.md) - Extract values from JSON - [JSON Replace](https://docs.griptapenodes.com/nodes/json/json_replace/index.md) - Replace values in JSON - [To JSON](https://docs.griptapenodes.com/nodes/convert/to_json/index.md) - Convert other data types to JSON # JSON Extract Value Extract values from JSON using [JMESPath](https://jmespath.org) expressions. ## Description The JSON Extract Value node allows you to extract specific values from JSON data using JMESPath expressions. It supports nested object access, array indexing, and powerful wildcard operations, making it easy to navigate and extract data from complex JSON structures. ## Parameters ### Input Parameters | Parameter | Type | Description | Default | | --------- | --------------- | ------------------------------------------------------------------------------------------ | ------- | | `json` | json, str, dict | The JSON data to extract from | `{}` | | `path` | str | JMESPath expression to extract data (e.g., 'user.name', 'items[0].title', '[\*].assignee') | `""` | ### Output Parameters | Parameter | Type | Description | | --------- | ---- | ------------------- | | `output` | json | The extracted value | ## Features - **JMESPath Expressions**: Use powerful JMESPath syntax for flexible data extraction - **Dot Notation Paths**: Use simple dot notation to navigate JSON structure - **Array Indexing**: Access array elements using `[index]` syntax - **Wildcard Operations**: Extract all values from arrays using `[*]` syntax - **Native Python Types**: Returns raw Python values (strings, dicts, lists, etc.) - no JSON serialization needed - **Real-time Updates**: Automatically updates when input changes - **Safe Extraction**: Returns empty dict `{}` if path doesn't exist ## JMESPath Syntax > **📚 Learn More**: For complete JMESPath syntax and advanced features, see the [JMESPath Documentation](https://jmespath.org/tutorial.html). ### Basic Object Access ``` # Access nested object properties path = "user.name" # Gets user's name path = "user.profile.email" # Gets nested email ``` ### Array Indexing ``` # Access array elements path = "items[0]" # Gets first item path = "items[0].title" # Gets title of first item path = "users[2].name" # Gets name of third user ``` ### Wildcard Operations ``` # Extract all values from arrays path = "items[*].title" # Gets all titles from items array path = "users[*].name" # Gets all names from users array path = "[*].assignee" # Gets all assignees from root array ``` ### Complex Paths ``` # Combine object and array access path = "orders[0].items[1].price" # Gets price of second item in first order path = "projects[*].tasks[*].assignee" # Gets all assignees from all project tasks ``` ## Examples ### Basic Object Extraction ``` # Input JSON: {"user": {"name": "John", "age": 30}} # Path: "user.name" # Output: "John" # Returns raw Python string value ``` ### Array Element Extraction ``` # Input JSON: {"items": [{"title": "Book", "price": 25}, {"title": "Magazine", "price": 10}]} # Path: "items[0].title" # Output: "Book" # Returns raw Python string value ``` ### Nested Array Access ``` # Input JSON: {"orders": [{"items": [{"name": "Product A"}, {"name": "Product B"}]}]} # Path: "orders[0].items[1].name" # Output: "Product B" # Returns raw Python string value ``` ### Non-existent Path ``` # Input JSON: {"user": {"name": "John"}} # Path: "user.email" # Output: {} # Empty dict (Python dict object) for non-existent paths ``` ### Different Return Types The node returns raw Python values directly from JMESPath, matching the extracted data type: ``` # String extraction # Input JSON: {"product": {"name": "Widget", "category": "Electronics"}} # Path: "product.name" # Output: "Widget" # Python string # Object extraction # Input JSON: {"user": {"name": "John", "age": 30}} # Path: "user" # Output: {"name": "John", "age": 30} # Python dict # Array extraction # Input JSON: {"items": [{"title": "Book"}, {"title": "Magazine"}]} # Path: "items" # Output: [{"title": "Book"}, {"title": "Magazine"}] # Python list ``` **Important Notes:** - The node returns native Python types (str, dict, list, int, bool, etc.) - no JSON serialization - This makes the output directly usable by other nodes without parsing - Consistent with other JSON nodes in the library (JsonInput, JsonReplace, etc.) - JMESPath handles all type conversions automatically ## Use Cases - **API Response Processing**: Extract specific fields from API responses - **Data Filtering**: Get only the data you need from large JSON objects - **Configuration Access**: Extract specific settings from configuration files - **Data Transformation**: Prepare data for other nodes in your workflow ## Related Nodes - [JSON Input](https://docs.griptapenodes.com/nodes/json/json_input/index.md) - Create JSON data from inputs - [JSON Replace](https://docs.griptapenodes.com/nodes/json/json_replace/index.md) - Replace values in JSON - [Display JSON](https://docs.griptapenodes.com/nodes/json/display_json/index.md) - Display and format JSON data - [To JSON](https://docs.griptapenodes.com/nodes/convert/to_json/index.md) - Convert other data types to JSON # JSON Find Find items in JSON arrays based on search criteria. ## Description The JSON Find node allows you to search through JSON arrays and find items that match specific criteria. It supports multiple search modes, case sensitivity options, and can return either the first match or all matches. Perfect for filtering and searching through complex data structures. ## Parameters ### Input Parameters | Parameter | Type | Description | Default | | ---------------- | --------------- | ---------------------------------------------------------------------- | --------- | | `json` | json, str, dict | The JSON data to search through (should be an array or contain arrays) | `[]` | | `search_field` | str | Dot notation path to the field to search (e.g., 'attributes.content') | `""` | | `search_value` | str | Value to search for | `""` | | `search_mode` | str | Search mode: 'exact', 'contains', or 'starts_with' | `"exact"` | | `return_mode` | str | Return mode: 'first' for first match, 'all' for all matches | `"first"` | | `case_sensitive` | bool | Whether the search should be case sensitive | `true` | ### Output Parameters | Parameter | Type | Description | | ------------- | ---- | ------------------------------------------------------------------------- | | `found_item` | json | The found item(s) - single item if return_mode is 'first', array if 'all' | | `found_count` | int | Number of items found | | `found_index` | int | Index of the first found item (or -1 if not found) | ## Features - **Flexible Search Modes**: Exact match, contains, or starts with - **Case Sensitivity Control**: Choose case-sensitive or case-insensitive search - **Multiple Return Options**: Get first match or all matches - **Nested Field Access**: Use dot notation to search deep into JSON structures - **Array Auto-Detection**: Automatically finds arrays in common field names - **Real-time Updates**: Automatically updates when input changes ## Search Modes ### Exact Match (`exact`) Finds items where the field value exactly matches the search value. ``` # Search for: "Design" # Matches: "Design" # Does not match: "Design Task", "design", "My Design" ``` ### Contains (`contains`) Finds items where the field value contains the search value as a substring. ``` # Search for: "Design" # Matches: "Design", "Design Task", "My Design Work" # Does not match: "design" (if case_sensitive=true) ``` ### Starts With (`starts_with`) Finds items where the field value starts with the search value. ``` # Search for: "Design" # Matches: "Design", "Design Task", "Designer" # Does not match: "My Design", "design" ``` ## Field Path Syntax The `search_field` parameter uses the same dot notation as JSON Extract Value: ### Basic Object Access ``` search_field = "name" # Search in root name field search_field = "attributes.content" # Search in nested content field search_field = "user.profile.email" # Search in deeply nested email field ``` ### Array Indexing ``` search_field = "items[0].title" # Search in title of first item search_field = "users[2].name" # Search in name of third user ``` ## Examples ### Find Task by Content ``` # Input JSON: [ # {"attributes": {"content": "Design", "status": "wtg"}}, # {"attributes": {"content": "Model", "status": "wtg"}}, # {"attributes": {"content": "Design Review", "status": "ip"}} # ] # search_field: "attributes.content" # search_value: "Design" # search_mode: "exact" # return_mode: "first" # Result: {"attributes": {"content": "Design", "status": "wtg"}} ``` ### Find All Tasks with Status ``` # Input JSON: [ # {"attributes": {"content": "Design", "status": "wtg"}}, # {"attributes": {"content": "Model", "status": "wtg"}}, # {"attributes": {"content": "Review", "status": "ip"}} # ] # search_field: "attributes.status" # search_value: "wtg" # search_mode: "exact" # return_mode: "all" # Result: [{"attributes": {"content": "Design", "status": "wtg"}}, # {"attributes": {"content": "Model", "status": "wtg"}}] ``` ### Case-Insensitive Search ``` # Input JSON: [ # {"name": "Design Task"}, # {"name": "design review"}, # {"name": "DESIGN WORK"} # ] # search_field: "name" # search_value: "design" # search_mode: "contains" # case_sensitive: false # return_mode: "all" # Result: All three items (matches "Design", "design", "DESIGN") ``` ### Find Items Starting With ``` # Input JSON: [ # {"title": "Design Task"}, # {"title": "Design Review"}, # {"title": "My Design Work"} # ] # search_field: "title" # search_value: "Design" # search_mode: "starts_with" # return_mode: "all" # Result: [{"title": "Design Task"}, {"title": "Design Review"}] ``` ### Complex Nested Search ``` # Input JSON: [ # { # "relationships": { # "entity": {"data": {"name": "convertible", "type": "Asset"}} # } # } # ] # search_field: "relationships.entity.data.name" # search_value: "convertible" # search_mode: "exact" # return_mode: "first" # Result: The matching item ``` ## Array Auto-Detection If your input JSON is an object containing arrays, the node will automatically look for common array field names: - `data` - `items` - `results` - `list` ``` # Input JSON: {"data": [{"name": "Item 1"}, {"name": "Item 2"}]} # The node will automatically search in the "data" array ``` ## Use Cases - **Task Management**: Find specific tasks by content, status, or assignee - **Data Filtering**: Filter large datasets based on specific criteria - **API Response Processing**: Search through API response arrays - **Configuration Lookup**: Find specific configuration items - **Content Discovery**: Search through content collections - **User Management**: Find users by name, email, or role ## Related Nodes - [JSON Extract Value](https://docs.griptapenodes.com/nodes/json/json_extract_value/index.md) - Extract single values by path - [JSON Input](https://docs.griptapenodes.com/nodes/json/json_input/index.md) - Create JSON data from inputs - [JSON Replace](https://docs.griptapenodes.com/nodes/json/json_replace/index.md) - Replace values in JSON - [Display JSON](https://docs.griptapenodes.com/nodes/json/display_json/index.md) - Display and format JSON data - [To JSON](https://docs.griptapenodes.com/nodes/convert/to_json/index.md) - Convert other data types to JSON # JSON Input Creates a JSON node from input data with automatic repair capabilities. ## Description The JSON Input node takes various input types and converts them to proper JSON data. It uses `json-repair` to handle malformed JSON strings and provides robust conversion for different data types. ## Parameters ### Input Parameters | Parameter | Type | Description | Default | | --------- | --------------- | --------------------------------- | ------- | | `json` | json, str, dict | The input data to convert to JSON | `{}` | ### Output Parameters | Parameter | Type | Description | | --------- | ---- | ----------------------- | | `json` | json | The processed JSON data | ## Features - **Automatic JSON Repair**: Handles malformed JSON strings using `json-repair` - **Multiple Input Types**: Accepts dictionaries, strings, and other data types - **Error Handling**: Graceful fallbacks when parsing fails - **Real-time Processing**: Updates automatically when input changes ## Examples ### Basic Usage ``` # Input: {"name": "John", "age": 30} # Output: {"name": "John", "age": 30} ``` ### Malformed JSON Repair ``` # Input: '{"name": "John", age: 30}' # Missing quotes around age # Output: {"name": "John", "age": 30} # Repaired JSON ``` ### String to JSON Conversion ``` # Input: '{"user": {"name": "Alice", "active": true}}' # Output: {"user": {"name": "Alice", "active": true}} ``` ### Dictionary Input ``` # Input: {"status": "active", "count": 5} # Output: {"status": "active", "count": 5} # Used as-is ``` ## Use Cases - **Data Validation**: Ensure input data is valid JSON - **API Integration**: Process JSON responses from external APIs - **Data Cleaning**: Repair malformed JSON data - **Workflow Integration**: Convert various data types to JSON format ## Related Nodes - [JSON Extract Value](https://docs.griptapenodes.com/nodes/json/json_extract_value/index.md) - Extract specific values from JSON - [JSON Replace](https://docs.griptapenodes.com/nodes/json/json_replace/index.md) - Replace values in JSON - [Display JSON](https://docs.griptapenodes.com/nodes/json/display_json/index.md) - Display and format JSON data - [To JSON](https://docs.griptapenodes.com/nodes/convert/to_json/index.md) - Convert other data types to JSON # JSON Replace Replace values in JSON using dot notation paths. ## Description The JSON Replace node allows you to modify JSON data by replacing values at specific paths using dot notation. It creates a deep copy of the original data to avoid modifying the source, and can automatically create missing paths if they don't exist. ## Parameters ### Input Parameters | Parameter | Type | Description | Default | | ------------------- | --------------- | ------------------------------------------------------------------ | ------- | | `json` | json, str, dict | The JSON data to modify | `{}` | | `path` | str | Dot notation path to replace (e.g., 'user.name', 'items[0].title') | `""` | | `replacement_value` | json, str, dict | The new value to put at the specified path | `""` | ### Output Parameters | Parameter | Type | Description | | --------- | ---- | -------------------------------------------- | | `output` | json | The modified JSON with the replacement value | ## Features - **Deep Copy Protection**: Creates a deep copy to avoid modifying the original data - **Path Creation**: Automatically creates missing paths if they don't exist - **Array Support**: Handles array indexing like `items[0].name` - **Real-time Updates**: Updates automatically when any input parameter changes - **Safe Operations**: Graceful handling of invalid paths or data types ## Path Syntax ### Basic Object Replacement ``` # Replace nested object properties path = "user.name" # Replace user's name path = "user.profile.email" # Replace nested email ``` ### Array Element Replacement ``` # Replace array elements path = "items[0]" # Replace first item path = "items[0].title" # Replace title of first item path = "users[2].name" # Replace name of third user ``` ### Complex Path Replacement ``` # Replace in nested arrays path = "orders[0].items[1].price" # Replace price of second item in first order ``` ## Examples ### Basic Value Replacement ``` # Original JSON: {"user": {"name": "John", "age": 30}} # Path: "user.name" # Replacement Value: "Jane" # Output: {"user": {"name": "Jane", "age": 30}} ``` ### Array Element Replacement ``` # Original JSON: {"items": [{"title": "Book", "price": 25}, {"title": "Magazine", "price": 10}]} # Path: "items[0].title" # Replacement Value: "Novel" # Output: {"items": [{"title": "Novel", "price": 25}, {"title": "Magazine", "price": 10}]} ``` ### Creating New Paths ``` # Original JSON: {"user": {"name": "John"}} # Path: "user.email" # Replacement Value: "john@example.com" # Output: {"user": {"name": "John", "email": "john@example.com"}} ``` ### Nested Array Replacement ``` # Original JSON: {"orders": [{"items": [{"name": "Product A"}, {"name": "Product B"}]}]} # Path: "orders[0].items[1].name" # Replacement Value: "Product C" # Output: {"orders": [{"items": [{"name": "Product A"}, {"name": "Product C"}]}]} ``` ### Extending Arrays ``` # Original JSON: {"items": [{"title": "Book"}]} # Path: "items[2].title" # Replacement Value: "Magazine" # Output: {"items": [{"title": "Book"}, null, {"title": "Magazine"}]} ``` ## Use Cases - **Data Updates**: Modify specific fields in configuration or user data - **API Integration**: Update request payloads with new values - **Data Transformation**: Modify JSON structure for different systems - **Template Processing**: Fill in template JSON with dynamic values - **Configuration Management**: Update settings in JSON configuration files ## Related Nodes - [JSON Input](https://docs.griptapenodes.com/nodes/json/json_input/index.md) - Create JSON data from inputs - [JSON Extract Value](https://docs.griptapenodes.com/nodes/json/json_extract_value/index.md) - Extract values from JSON - [Display JSON](https://docs.griptapenodes.com/nodes/json/display_json/index.md) - Display and format JSON data - [To JSON](https://docs.griptapenodes.com/nodes/convert/to_json/index.md) - Convert other data types to JSON # AddToList ## What is it? The AddToList node is a utility node that adds an item to an existing list at a specified position. It allows you to insert new items into lists at the beginning, end, or at a specific index. ## When would I use it? Use the AddToList node when: - You need to add new items to an existing list - You want to insert items at specific positions in a list - You're building dynamic lists that need to be updated during workflow execution - You need to maintain ordered collections of items - You want to modify lists without creating new ones ## How to use it ### Basic Setup 1. Add an AddToList node to your workflow 1. Connect an existing list to the "items" input 1. Connect the item you want to add to the "item" input 1. Choose where to add the item using the "position" parameter 1. If using "index" position, specify the index value ### Parameters - **items**: The list to add the item to (list input) - **item**: The item to add to the list (any type) - **position**: Where to add the item (choices: "start", "end", "index") - **index**: The specific position to add the item (only visible when position is "index") - **output**: The modified list with the new item added ### Outputs - **output**: The list containing the original items plus the newly added item ## Example Imagine you want to add a new item to a list of tasks: 1. Add an AddToList node to your workflow 1. Connect your existing task list to the "items" input 1. Connect the new task to the "item" input 1. Set "position" to "end" to add it at the end of the list 1. The output will be the original list with the new task appended ## Important Notes - The node creates a new list rather than modifying the original - When using "index" position, the index must be within the list's bounds - The "index" parameter is only visible when "position" is set to "index" - Items can be of any type, but be aware of type compatibility with the receiving node ## Common Issues - **Invalid Index**: If using "index" position, ensure the index is within the list's bounds - **Empty List**: If the input list is empty, the item will be added as the first element - **Type Mismatch**: Be aware of how the receiving node handles different data types in the list # Combine Lists The Combine Lists node takes two lists and combines them into a single flattened list. ## Inputs - **List A** (list): The first list to combine - **List B** (list): The second list to combine ## Outputs - **Output** (list): The combined list containing all items from both input lists in order ## Example ``` # List A: [1, 2] # List B: [3, 4] # Output: [1, 2, 3, 4] # List A: ["a", "b"] # List B: ["c", "d"] # Output: ["a", "b", "c", "d"] # List A: [1, 2] # List B: [] # Output: [1, 2] ``` ## Notes - The order of items is preserved in the output list - If either input is not a list, it will be treated as an empty list - The original lists are not modified; a new list is returned - The operation is equivalent to list concatenation (list_a + list_b) # Create Bool List The Create Bool List node allows you to create a list of boolean values. ## Inputs - **Items** (bool[]): A list of boolean values to include in the output list ## Outputs - **Output** (list): The created list of boolean values ## Example ``` # Items: [True, False, True] # Output: [True, False, True] # Items: [False, True] # Output: [False, True] ``` ## Notes - All items in the input list must be boolean values - The order of items is preserved in the output list - The node can accept both input and property modes for the items parameter # Create Float List The Create Float List node allows you to create a list of float values. ## Inputs - **Items** (float[]): A list of float values to include in the output list ## Outputs - **Output** (list): The created list of float values ## Example ``` # Items: [1.5, 2.7, 3.14] # Output: [1.5, 2.7, 3.14] # Items: [-0.5, 0.0, 42.0] # Output: [-0.5, 0.0, 42.0] ``` ## Notes - All items in the input list must be float values - The order of items is preserved in the output list - The node can accept both input and property modes for the items parameter # Create Int List The Create Int List node allows you to create a list of integer values. ## Inputs - **Items** (int[]): A list of integer values to include in the output list ## Outputs - **Output** (list): The created list of integer values ## Example ``` # Items: [1, 2, 3] # Output: [1, 2, 3] # Items: [-5, 0, 42] # Output: [-5, 0, 42] ``` ## Notes - All items in the input list must be integer values - The order of items is preserved in the output list - The node can accept both input and property modes for the items parameter # CreateList ## What is it? The CreateList node is a utility node that creates a list from provided items. It allows you to build lists dynamically in your workflow by combining multiple inputs into a single list output. ## When would I use it? Use the CreateList node when: - You need to combine multiple items into a single list - You want to create a list from various inputs in your workflow - You're building data structures that require list format - You need to prepare data for nodes that require list inputs - You want to consolidate multiple values into a single list structure ## How to use it ### Basic Setup 1. Add a CreateList node to your workflow 1. Connect individual items to the "items" input or add them directly in the node's properties 1. The node will combine all inputs into a single list output ### Parameters - **items**: A list of items to combine (supports multiple inputs of any type) - **output**: The combined list containing all input items ### Outputs - **output**: A list containing all the items provided as input ## Example Imagine you want to create a list of different data types: 1. Add a CreateList node to your workflow 1. Connect various inputs to the "items" parameter (e.g., text, numbers, or other data types) 1. The output will be a list containing all these items in the order they were connected ## Important Notes - The CreateList node can handle items of any type - Items are added to the list in the order they are connected - The node automatically updates its output whenever input items change - You can add items both through connections and directly in the node's properties ## Common Issues - **Empty List**: If no items are provided, the output will be an empty list - **Type Mismatch**: The node accepts items of any type, but be aware of how the receiving node handles different data types # Create Text List The Create Text List node allows you to create a list of text values. ## Inputs - **Items** (string[]): A list of text values to include in the output list ## Outputs - **Output** (list): The created list of text values ## Example ``` # Items: ["Hello", "World", "!"] # Output: ["Hello", "World", "!"] # Items: ["First", "Second", "Third"] # Output: ["First", "Second", "Third"] ``` ## Notes - All items in the input list must be text values - The order of items is preserved in the output list - The node can accept both input and property modes for the items parameter # DisplayList ## What is it? The DisplayList node is a utility node that takes a list as input and creates individual output parameters for each item in the list. It automatically determines the type of each item and creates appropriately typed output parameters. ## When would I use it? Use the DisplayList node when: - You need to access individual items from a list separately - You want to display or process list items individually - You're working with lists of mixed types and need type-specific handling - You need to connect list items to different nodes - You want to visualize the contents of a list in a more detailed way ## How to use it ### Basic Setup 1. Add a DisplayList node to your workflow 1. Connect a list to the "items" input 1. The node will automatically create output parameters for each item 1. Connect the individual item outputs to other nodes as needed ### Parameters - **items**: The list to display (list input) - **item_0, item_1, etc.**: Dynamic output parameters for each item in the list ### Outputs - **item_0, item_1, etc.**: Individual outputs for each item in the list, with types matching the items ## Example Imagine you have a list of mixed data types that you want to process separately: 1. Add a DisplayList node to your workflow 1. Connect your list to the "items" input 1. The node will create separate outputs for each item 1. Connect each output to the appropriate processing node based on its type ## Important Notes - The node automatically determines the type of each item - Output parameters are created dynamically based on the list contents - Supported types include: string, boolean, integer, float, and image artifacts - The node updates its outputs whenever the input list changes - Output parameters are named sequentially (item_0, item_1, etc.) ## Common Issues - **Empty List**: If the input list is empty, no output parameters will be created - **Invalid Input**: If the input is not a list, no output parameters will be created - **Type Limitations**: Some complex types may be handled as "any" type - **Dynamic Updates**: The number of output parameters changes with the list length # Get From List The Get From List node takes a list and an index as input and returns the item at that index in the list. ## Inputs - **items** (list): The list to get an item from - **index** (int): The index to get the item from ## Outputs - **item** (any): The item at the specified index in the list. Returns None if the index is invalid or inputs are missing. ## Example ``` # Input list: [1, 2, 3, 4, 5] # Input index: 2 # Output: 3 ``` ## Notes - If the index is out of range or invalid, the node will return None - If either the list or index input is missing, the node will return None - The output type is "any" since the list can contain items of any type - The index parameter can be set either as an input connection or as a property value # GetIndexOfItem ## What is it? The GetIndexOfItem node is a utility node that finds the position (index) of a specific item in a list. It returns the zero-based index of the first occurrence of the item in the list. ## When would I use it? Use the GetIndexOfItem node when: - You need to find the position of an item in a list - You want to locate specific items in a collection - You're building workflows that need to reference items by their position - You need to validate item positions in lists - You want to track the location of items in dynamic lists ## How to use it ### Basic Setup 1. Add a GetIndexOfItem node to your workflow 1. Connect a list to the "items" input 1. Connect the item you want to find to the "item" input 1. The node will output the index of the item in the list ### Parameters - **items**: The list to search in (list input) - **item**: The item to find in the list (any type) - **index**: The position of the item in the list (integer output) ### Outputs - **index**: An integer representing the position of the item (-1 if not found) ## Example Imagine you want to find the position of a specific task in a task list: 1. Add a GetIndexOfItem node to your workflow 1. Connect your task list to the "items" input 1. Connect the task you want to find to the "item" input 1. The "index" output will show the position of the task in the list ## Important Notes - The node returns -1 if the item is not found in the list - Index positions start at 0 (first item is at index 0) - The node finds the first occurrence of the item if it appears multiple times - The node automatically updates when either the list or the item changes ## Common Issues - **Item Not Found**: If the item is not in the list, the output will be -1 - **No Input**: If either the list or item is not connected, the output will be -1 - **Invalid Input**: If the input is not a list, the output will be -1 - **Type Mismatch**: The item type must match the list item types for proper comparison - **Case Sensitivity**: String comparisons are case-sensitive # GetListContainsItem ## What is it? The GetListContainsItem node is a utility node that checks whether a specific item exists in a list. It provides a boolean output indicating if the item is found in the input list. ## When would I use it? Use the GetListContainsItem node when: - You need to check if a specific item exists in a list - You want to validate that required data is present in a collection - You're building conditional workflows based on item presence - You need to filter or process lists based on item existence - You want to monitor for specific items in dynamic lists ## How to use it ### Basic Setup 1. Add a GetListContainsItem node to your workflow 1. Connect a list to the "items" input 1. Connect the item you want to check for to the "item" input 1. The node will automatically check and output whether the item exists in the list ### Parameters - **items**: The list to check (list input) - **item**: The item to look for in the list (any type) - **contains_item**: Boolean indicating if the item was found (true) or not (false) ### Outputs - **contains_item**: A boolean value (true if the item is found, false if not) ## Example Imagine you want to check if a specific task exists in a task list: 1. Add a GetListContainsItem node to your workflow 1. Connect your task list to the "items" input 1. Connect the task you want to check for to the "item" input 1. The "contains_item" output will be true if the task is found, false if not ## Important Notes - The node automatically updates when either the list or the item changes - If either the list or item is not provided, the output will be false - The node uses exact matching to find items - The output is always a boolean value ## Common Issues - **No Input**: If either the list or item is not connected, the output will be false - **Invalid Input**: If the input is not a list, the output will be false - **Type Mismatch**: The item type must match the list item types for proper comparison - **Case Sensitivity**: String comparisons are case-sensitive # GetListIsEmpty ## What is it? The GetListIsEmpty node is a utility node that checks whether a list is empty or not. It provides a boolean output indicating if the input list contains any items. ## When would I use it? Use the GetListIsEmpty node when: - You need to check if a list has any items - You want to validate that a list is not empty before processing - You're building conditional workflows based on list content - You need to ensure data is available before proceeding - You want to monitor the state of dynamic lists ## How to use it ### Basic Setup 1. Add a GetListIsEmpty node to your workflow 1. Connect any list to the "items" input 1. The node will automatically check and output whether the list is empty ### Parameters - **items**: The list to check (list input) - **is_empty**: Boolean indicating if the list is empty (true) or not (false) ### Outputs - **is_empty**: A boolean value (true if the list is empty, false if it contains items) ## Example Imagine you want to check if a task list has any pending tasks: 1. Add a GetListIsEmpty node to your workflow 1. Connect your task list to the "items" input 1. The "is_empty" output will be true if there are no tasks, false if there are tasks ## Important Notes - The node automatically updates when the input list changes - If no list is connected, the output will be true (empty) - The node works with lists of any type of items - The output is always a boolean value ## Common Issues - **No Input**: If no list is connected, the output will be true (empty) - **Invalid Input**: If the input is not a list, the output will be true (empty) - **Type Mismatch**: The node only works with list inputs, other types will result in true (empty) # GetListLength ## What is it? The GetListLength node is a utility node that calculates and outputs the number of items in a list. It provides a simple way to determine the size of any list in your workflow. ## When would I use it? Use the GetListLength node when: - You need to know how many items are in a list - You want to check if a list is empty or has a specific number of items - You're building workflows that depend on list size - You need to validate list lengths before processing - You want to monitor the size of dynamic lists ## How to use it ### Basic Setup 1. Add a GetListLength node to your workflow 1. Connect any list to the "items" input 1. The node will automatically calculate and output the list's length ### Parameters - **items**: The list to measure (list input) - **length**: The number of items in the list (integer output) ### Outputs - **length**: An integer representing the number of items in the input list ## Example Imagine you want to check how many tasks are in a task list: 1. Add a GetListLength node to your workflow 1. Connect your task list to the "items" input 1. The "length" output will show the total number of tasks in the list ## Important Notes - The node automatically updates the length when the input list changes - If the input list is empty or not provided, the length will be 0 - The length is always a non-negative integer - The node works with lists of any type of items ## Common Issues - **Empty List**: If no list is connected, the length will be 0 - **Invalid Input**: If the input is not a list, the length will be 0 - **Type Mismatch**: The node only works with list inputs, other types will result in a length of 0 # RemoveFromList ## What is it? The RemoveFromList node is a utility node that removes items from an existing list based on different criteria. It can remove items by their position (first, last, or specific index) or by matching a specific item value. ## When would I use it? Use the RemoveFromList node when: - You need to remove items from an existing list - You want to filter out specific items from a collection - You're maintaining dynamic lists that need items removed during workflow execution - You need to clean up or modify lists by removing unwanted elements - You want to extract items from specific positions in a list ## How to use it ### Basic Setup 1. Add a RemoveFromList node to your workflow 1. Connect an existing list to the "items" input 1. Choose how to remove items using the "remove_item_by" parameter 1. Depending on the removal method, provide either an index or item value 1. The output will be the list with the specified item removed ### Parameters - **items**: The list to remove items from (list input) - **remove_item_by**: How to remove the item (choices: "first", "last", "index", "item") - **item**: The specific item to remove (only visible when remove_item_by is "item") - **index**: The position to remove from (only visible when remove_item_by is "index") - **output**: The modified list with the item removed ### Outputs - **output**: The list containing the original items minus the removed item ## Example Imagine you want to remove a specific task from a list of tasks: 1. Add a RemoveFromList node to your workflow 1. Connect your task list to the "items" input 1. Set "remove_item_by" to "item" 1. Connect the task you want to remove to the "item" input 1. The output will be the original list with the specified task removed ## Important Notes - The node creates a new list rather than modifying the original - When using "index" removal, the index must be within the list's bounds - When using "item" removal, the item must exist in the list - The "index" and "item" parameters are only visible when their respective removal methods are selected ## Common Issues - **Invalid Index**: If using "index" removal, ensure the index is within the list's bounds - **Item Not Found**: When using "item" removal, the item must exist in the list - **Empty List**: If the input list is empty, no items can be removed - **Type Mismatch**: When using "item" removal, ensure the item type matches the list items # Replace In List The Replace In List node allows you to replace an item in a list either by matching the item or by its index. ## Inputs - **Items** (list): The list to modify - **Item To Replace** (any): The item to replace in the list (shown when Replace By is "item") - **Index To Replace** (int): The index of the item to replace (shown when Replace By is "index") - **New Item** (any): The new item to replace with ## Properties - **Replace By** (str): How to identify the item to replace - Options: - "item": Replace by matching the item value - "index": Replace by index position ## Outputs - **Output** (list): The modified list with the item replaced ## Example ``` # Input list: [1, 2, 3, 4] # Replace by item: # Item To Replace: 3 # New Item: "three" # Output: [1, 2, "three", 4] # Replace by index: # Index To Replace: 1 # New Item: "two" # Output: [1, "two", 3, 4] ``` ## Notes - When replacing by item, the first occurrence of the item will be replaced - When replacing by index, the index must be within the bounds of the list - If the item is not found when replacing by item, no changes will be made - The original list is not modified; a new list is returned # Select From List Select an item from a list of strings with a dropdown interface. ## Description The Select From List node allows users to choose a single item from a list of strings using a dropdown interface. It automatically updates its choices when the input list changes and preserves the current selection when possible. ## Parameters ### Input Parameters | Parameter | Type | Description | Default | | --------- | ---- | ---------------------------- | ------- | | `list` | list | List of items to select from | `[]` | ### Output Parameters | Parameter | Type | Description | | --------------- | ------ | --------------------------- | | `selected_item` | string | The currently selected item | ## Features - **Dynamic Dropdown**: Automatically populates dropdown with items from the input list - **Selection Preservation**: Maintains current selection when list updates (if item still exists) - **String Conversion**: Converts all list items to strings for consistent comparison - **Real-time Updates**: Updates immediately when input list changes - **Safe Handling**: Gracefully handles empty or invalid lists ## Examples ### Basic List Selection ``` # Input list: ["Apple", "Banana", "Cherry", "Date"] # User selects: "Banana" # Output: "Banana" ``` ### Status Selection ``` # Input list: ["In Progress", "Not Started", "Waiting", "Completed", "Blocked"] # User selects: "Completed" # Output: "Completed" ``` ### Dynamic List Updates ``` # Initial list: ["Option A", "Option B", "Option C"] # User selects: "Option B" # List updates to: ["Option A", "Option B", "Option D", "Option E"] # Selection preserved: "Option B" (still exists in new list) # If list updates to: ["Option X", "Option Y", "Option Z"] # Selection resets to: "Option X" (first item in new list) ``` ## Use Cases - **Status Selection**: Choose from predefined status values (e.g., "In Progress", "Completed") - **Category Selection**: Select from a list of categories or types - **Configuration Choices**: Pick from available configuration options - **User Interface**: Provide dropdown selection in workflows - **Data Filtering**: Select specific items from generated lists ## Workflow Examples ### Status Management Workflow ``` Create Text List → Select From List → Process Selected Status ↓ ↓ ["In Progress", "Completed" → Update task status "Not Started", "Waiting", "Completed", "Blocked"] ``` ### Dynamic Option Selection ``` API Response → Extract Options → Select From List → Use Selection ↓ ↓ ↓ JSON data → ["Option 1", → "Option 2" → Process choice "Option 2", "Option 3"] ``` ## Behavior ### Selection Logic 1. **Initial Load**: Selects the first item in the list 1. **List Update**: - If current selection exists in new list → keeps current selection - If current selection doesn't exist → selects first item in new list 1. **Empty List**: Clears selection (empty string) 1. **Invalid Input**: Clears selection (empty string) ### String Conversion All list items are automatically converted to strings for consistent comparison: - Numbers: `123` → `"123"` - Booleans: `True` → `"True"` - Objects: `{"key": "value"}` → `"{'key': 'value'}"` ## Related Nodes - [Create Text List](https://docs.griptapenodes.com/nodes/lists/create_text_list/index.md) - Generate a list of text items - [Create List](https://docs.griptapenodes.com/nodes/lists/create_list/index.md) - Create lists from various inputs - [Get From List](https://docs.griptapenodes.com/nodes/lists/get_from_list/index.md) - Get item by index from list - [Get Index Of Item](https://docs.griptapenodes.com/nodes/lists/get_index_of_item/index.md) - Find index of specific item - [Display List](https://docs.griptapenodes.com/nodes/lists/display_list/index.md) - Display list contents # Split List The Split List node allows you to split a list into two parts either by index or by matching an item value. ## Inputs - **Items** (list): The list to split - **Split Index** (int): The index to split the list at (shown when Split By is "index") - **Split Item** (any): The item to split the list at (shown when Split By is "item") ## Properties - **Split By** (str): How to split the list - Options: - "index": Split at a specific index position - "item": Split at a specific item value - **Keep Split Item** (bool): Whether to keep the split item in the second list (shown when Split By is "item") ## Outputs - **Output A** (list): The first part of the split list - **Output B** (list): The second part of the split list ## Example ``` # Input list: [1, 2, 3, 4] # Split by index: # Split Index: 2 # Output A: [1, 2] # Output B: [3, 4] # Split by item: # Split Item: 3 # Keep Split Item: True # Output A: [1, 2] # Output B: [3, 4] # Split by item: # Split Item: 3 # Keep Split Item: False # Output A: [1, 2] # Output B: [4] ``` ## Notes - When splitting by index, the index is included in the second list - When splitting by item, you can choose whether to keep the split item in the second list - If the item is not found when splitting by item, no split will occur - The index must be within the bounds of the list when splitting by index - The original list is not modified; new lists are returned # Askulator ## What is it? The Askulator is a natural language calculator node that can understand and solve mathematical problems expressed in plain English. It uses an AI model to interpret the question, perform the necessary calculations, and provide both the reasoning and the final answer. ## When would I use it? Use this node when you want to: - Solve mathematical problems expressed in natural language - Get step-by-step reasoning for calculations - Handle complex or ambiguous mathematical questions - Perform calculations that require interpretation or estimation - Get creative or approximate answers when exact numbers aren't available ## How to use it ### Basic Setup 1. Add the Askulator to your workflow 1. Enter your mathematical question in the "instruction" parameter 1. The node will process the question and provide: - A detailed reasoning of how it solved the problem - The final answer in the "result" output ### Parameters - **instruction**: The mathematical question or problem to solve (supports natural language) - **model**: The AI model to use for processing (default: gpt-4.1-mini) - **result**: The final calculated answer - **output**: The detailed reasoning and steps taken to solve the problem ### Outputs - **result**: The final calculated answer - **output**: The detailed reasoning and steps taken to solve the problem ## Example Imagine you want to calculate something complex like "If I have 3 dozen eggs and give away a third, how many do I have left?": 1. Add an Askulator to your workflow 1. Enter the question in the "instruction" parameter 1. The node will: - Interpret that 3 dozen = 36 eggs - Calculate that a third of 36 is 12 - Subtract 12 from 36 - Provide the reasoning in the "output" - Give the final answer (24) in the "result" ## Implementation Details The Askulator uses: - A natural language processing model to understand questions - The Calculator tool to perform precise calculations - JSON output format to separate reasoning from the final answer - Streaming updates to show progress in real-time The node is designed to be creative and helpful, making reasonable estimates when exact numbers aren't available and providing clear explanations of its reasoning. # BoolInput The BoolInput node allows you to create a boolean value. ## Inputs - **Value** (bool): The boolean value to create - Default: False ## Outputs - **Output** (bool): The created boolean value ## Example ``` # Value: True # Output: True # Value: False # Output: False ``` ## Notes - The node can accept both input and property modes for the value parameter - The default value is False if not specified - The value is immediately forwarded to the output when changed # FloatInput ## What is it? The FloatInput node is a simple way to input floating-point number values. ## When would I use it? Use this node when you want to: - Define a numerical value with decimal precision - Create a configurable number parameter for your workflow - Set up values for mathematical operations - Establish thresholds, rates, or other decimal values ## How to use it ### Basic Setup 1. Add a FloatInput node to your workflow 1. Set your desired float value ### Parameters - **float**: A floating-point number value (default is 0.0) ### Outputs - **float**: The floating-point value that can be used by other nodes ## Example Imagine you want to set a temperature value for an AI model: 1. Add a FloatInput node to your workflow 1. Set the "float" value to 0.7 1. Connect the output to a model's temperature parameter 1. The AI model will now use 0.7 as its temperature setting ## Important Notes - This node creates a single floating-point value - The default value is 0.0 if none is specified - You can update the value through the node's properties - Floating-point numbers can represent decimal values like 0.5, 3.14159, or -2.75 ## Common Issues - None # IntegerInput ## What is it? The IntegerInput node is a simple way to input integer (whole) number values. ## When would I use it? Use this node when you want to: - Define a whole number value without decimals - Create a configurable integer parameter for your workflow - Set up counters, indices, or quantity values - Establish limits, thresholds, or other whole-number values ## How to use it ### Basic Setup 1. Add an IntegerInput node to your workflow 1. Set your desired integer value ### Parameters - **integer**: A whole number value (default is 0) ### Outputs - **integer**: The integer value that can be used by other nodes ## Example Imagine you want to set a maximum token limit for an AI response: 1. Add an IntegerInput to your workflow 1. Set the "integer" value to 500 1. Connect the output to a model's max_tokens parameter 1. The AI model will now limit its responses to 500 tokens ## Important Notes - This node creates a single integer value - The default value is 0 if none is specified - You can update the value through the node's properties - Integers represent whole numbers like 1, 42, -7, or 0 # DisplayFloat ## What is it? The DisplayFloat node simply displays a float parameter. ## When would I use it? When you want to inspect a floating-point number value in your graph ## How to use it ### Basic Setup 1. Add the DisplayFloat to your workflow 1. Connect any other node's float output to this input ### Parameters - **float**: A single floating-point number that is displayed by this node (default is 0.0) ### Outputs - **None** ## Example Imagine you want to visualize the temperature setting used in your workflow: 1. Add a DisplayFloat to your workflow 1. Connect the "temperature" output from another node to the DisplayFloat's input 1. The DisplayFloat will now show the current temperature value in your graph ## Important Notes - This node is for visualization purposes only - It doesn't modify the value, just displays it - Useful for debugging and monitoring your workflow ## Common Issues - None # DisplayInteger ## What is it? The DisplayInteger node simply displays an integer parameter. ## When would I use it? Use this node/class when you want to: - When you want to inspect an integer value in your graph ## How to use it ### Basic Setup 1. Add the DisplayInteger to your workflow 1. Connect any other node's integer output to this input ### Parameters - **integer**: A single integer value that is displayed by this node (default is 0) ### Outputs - **None** ## Example Imagine you want to visualize the token count used in your workflow: 1. Add a DisplayInteger to your workflow 1. Connect the "token_count" output from another node to the DisplayInteger's input 1. The DisplayInteger will now show the current token count in your graph ## Important Notes - This node is for visualization purposes only - It doesn't modify the value, just displays it - Useful for debugging and monitoring your workflow ## Common Issues - None # Math Operations ## What is it? The Math node is a versatile mathematical operations node that can perform various arithmetic and mathematical functions on numbers. It supports both unary operations (single input) and binary operations (two inputs). ## When would I use it? Use this node when you need to: - Perform basic arithmetic operations (addition, subtraction, multiplication, division) - Calculate mathematical functions (square root, absolute value, sine) - Round numbers (round, ceil, floor) - Find minimum/maximum values - Calculate averages - Perform modulo operations - Calculate powers ## How to use it ### Basic Setup 1. Add the Math node to your workflow 1. Select the operation you want to perform from the dropdown menu 1. Connect input values to parameters A and B (B is only needed for binary operations) 1. The result will be available in the "result" output ### Available Operations #### Binary Operations (require both A and B inputs) - Add (A + B) - Subtract (A - B) - Multiply (A * B) - Divide (A / B) - Modulo (A % B) - Power (A ^ B) - Average (avg(A, B)) - Min (min(A, B)) - Max (max(A, B)) #### Unary Operations (only require A input) - Square Root (√A) - Round (round(A)) - Ceiling (⌈A⌉) - Floor (⌊A⌋) - Absolute Value (|A|) - Sine (sin(A)) ### Outputs - **result**: The calculated result of the mathematical operation ## Example Imagine you want to calculate the average of two numbers: 1. Add a Math node to your workflow 1. Select "average [avg(A, B)]" from the operation dropdown 1. Connect two number inputs to parameters A and B 1. The average will be available in the "result" output ## Implementation Details The Math node automatically handles: - Type conversion for inputs - Division by zero (returns infinity) - Modulo by zero (returns infinity) - Dynamic parameter visibility (hides B parameter for unary operations) # Ruleset ## What is it? The Ruleset Node allows you to create and output a Ruleset object. Rulesets consist of a name and a list of rules, which can be used with other nodes like agents in your workflow. ## When would I use it? Use this node when you want to: - Constrain or direct the output of another node that generates responses from an LLM ## How to use it ### Basic Setup 1. Add the Ruleset to your workflow 1. Set the "name" parameter to define the name for your ruleset (optional, defaults to "Behavior") 1. Set the "rules" parameter to define the list of rules for your ruleset (optional, defaults to an empty string, comma separated) 1. Connect it to your flow (specifically into nodes that have a Ruleset parameter) ### Parameters - **name**: A single parameter that is created and output by this node - **rules**: A single parameter that is created and output by this node ### Outputs - **ruleset**: The output Ruleset object as a property or input for another node ## Example Imagine you want to create a new ruleset with the following name and rules: 1. Add a Ruleset to your workflow 1. Set the "name" parameter to: ``` MyBehavioralRules ``` 1. Set the "rules" parameter to: ``` Rule 1: This is my first rule. Rule 2: This is my second rule. Rule 3: This is my third rule. ``` 1. Connect the output of this node to an agent's Ruleset parameter ## Important Notes - The Ruleset Node supports string values only - If no initial value is provided, default values are used for name and rules - This node can be used in conjunction with other nodes to create dynamic Ruleset objects based on other parameters or properties # RulesetList ## What is it? The RulesetList node combines multiple rulesets into a single list. This allows for more complex behaviors to be defined for an agent by grouping multiple rulesets together. ## When would I use it? Use the RulesetList node when: - You need to combine multiple rulesets to create more complex agent behaviors - You want to organize different sets of rules into a single collection - You're building an agent that needs to follow multiple sets of rules simultaneously - You want to modularly combine different rule systems ## How to use it ### Basic Setup 1. Add a RulesetList node to your workflow 1. Connect up to four individual ruleset nodes to the input parameters (ruleset_1, ruleset_2, ruleset_3, ruleset_4) 1. Connect the output (rulesets) to any node that accepts a list of rulesets as input ### Parameters - **ruleset_1**: The first ruleset to add to the combined list - **ruleset_2**: The second ruleset to add to the combined list - **ruleset_3**: The third ruleset to add to the combined list - **ruleset_4**: The fourth ruleset to add to the combined list ### Outputs - **rulesets**: A combined list containing all non-null input rulesets ## Example A common use case is combining multiple specialized rulesets for an agent: 1. Add a Ruleset node to your workflow, name it "Conversation Rules" 1. Add a Ruleset node to your workflow, name it "Task Rules" 1. Add a RulesetList node to your workflow 1. Connect a "Conversation Rules" ruleset to your RulesetList node's ruleset_1 1. Connect a "Task Rules" ruleset to your RulesetList node's ruleset_2 1. Connect the output (rulesets) to an Agent node's ruleset input # TextInput ## What is it? The TextInput node is a simple way to input text for use in workflows. ## When would I use it? Use the TextInput node when: - You need to create text content - You want to prepare text input for other nodes in your workflow - You need to define prompts, instructions, or other multi-paragraph content - You're building workflows that require structured text input ## How to use it ### Basic Setup 1. Add a TextInput node to your workflow 1. Set the "text" parameter with your desired multiline content 1. Connect the output to nodes that accept text input ### Parameters - **text**: The multiline text content (string, defaults to empty string) ### Outputs - **text**: The multiline text content as a string ## Example A workflow to create a prompt for an AI assistant: 1. Add a TextInput node to your workflow 1. Set the "text" parameter to: ``` You are a helpful tour guide assistant. Please provide information about the following location: - History of the place - Main attractions - Best time to visit - Local cuisine ``` 1. Connect the output to an Agent node's prompt parameter ## Important Notes - Line breaks are preserved in the output - The node supports any valid string content - Empty strings are valid input and will produce an empty string output - There is no character limit, but extremely large text blocks may impact performance ## Common Issues - Text formatting issues when copying from different sources (check for unexpected characters) - Line breaks might render differently in different env # Date and Time ## What is it? The Date and Time tool is a utility that helps you format and manipulate date and time information as text. ## When would I use it? Use this node when you want to: - Format dates and times in specific text formats - Convert between different date/time formats - Generate date/time strings for use in text - Create time-based text content ## How to use it ### Basic Setup 1. Add the Date and Time tool to your workflow 1. Configure the date/time parameters 1. Connect its output to nodes that need formatted date/time text ### Parameters - **prompt**: The thing you'd like to get the date for (e.g. "Christmas, 2027", "Next friday") - **format**: The format for the date/time (e.g., "%Y-%m-%d %H:%M:%S", "Fri 25, March, 9pm") ### Outputs - **output**: The formatted date/time as text ## Example Imagine you want to format a date in a specific way: 1. Add a Date and Time tool to your workflow 1. Set the desired date format 1. Connect the "output" to another node that needs the formatted date/time ## Implementation Details The Date and Time tool provides flexible date and time formatting capabilities. It supports various format strings and timezone handling to generate properly formatted date/time text. # DisplayText ## What is it? The DisplayText node simply displays text content in your workflow. ## When would I use it? Use the DisplayText node when: - You need to display results or information in your workflow - You want to show text output from previous processing steps - You need a placeholder for text content that will be updated during workflow execution - You want to create readable labels or descriptions within your workflow - You need to visualize string data at specific points in your process ## How to use it ### Basic Setup 1. Add a DisplayText node to your workflow 1. Set the initial text value if desired 1. Connect inputs to the text parameter or manually enter text 1. Connect the text output to other nodes that require text input ### Parameters - **text**: The text content to display (string input, supports multiline text) ### Outputs - **text**: The same text content, available as output to connect to other nodes ## Example Imagine you're building a workflow that processes and displays information: 1. Add a DisplayText node to your workflow 1. Connect the output from a RunAgent node to the DisplayText node's text parameter 1. When the workflow runs, the AI-generated content will replace the initial text ## Important Notes - The DisplayText node is for visualization and doesn't modify the text content - The node passes through text exactly as received, without any processing or formatting # Evaluate Text Result The Evaluate Text Result node allows you to evaluate text outputs against specific criteria using Griptape's Eval Engine. This node is useful for validating AI-generated content, checking factual accuracy, or assessing the quality of text outputs. ## Inputs - **Examples** (Property): Choose from preset examples or create your own evaluation - Options: - Choose a preset.. - Paraphrase - Factual - Analogy - **Input** (Input/Property): The input text to be evaluated - Supports multiline text input - **Expected Output** (Input/Property): The expected or reference output text - Single line text input - **Actual Output** (Input/Property): The actual output text to be evaluated - Single line text input - **Criteria** (Input/Property): The evaluation criteria to use - Supports multiline text input - Example: "Does the output accurately paraphrase the input without losing meaning?" ## Outputs - **Score** (Output): A float value between 0 and 1 representing the evaluation score - 1.0 indicates perfect match - 0.0 indicates complete mismatch - **Reason** (Output): A detailed explanation of the evaluation result - Provides feedback on why the score was given - Explains any discrepancies found ## Example Usage ### Paraphrase Evaluation ``` Input: "The quick brown fox jumps over the lazy dog." Expected Output: "A swift brown fox leaps above a sleeping dog." Actual Output: "A fast fox jumps over a dog that's not awake." Criteria: "Does the output accurately paraphrase the input without losing meaning?" ``` ### Factual Evaluation ``` Input: "The capital of France is Paris." Expected Output: "Paris is the capital city of France." Actual Output: "France's capital is Paris." Criteria: "Is the output factually correct based on the input?" ``` ### Analogy Evaluation ``` Input: "A bird is to sky as a fish is to ______." Expected Output: "water" Actual Output: "concrete" Criteria: "Does the output correctly complete the analogy?" ``` ## Notes - The node uses Griptape's Eval Engine to perform the evaluation - The evaluation is based on the provided criteria - The score is normalized between 0 and 1 - The reason provides detailed feedback about the evaluation - You can use preset examples or create your own custom evaluations # LoadText ## What is it? The LoadText node is a utility node that reads text content from a file on your local system. It allows you to import existing text documents into your workflow for processing. ## When would I use it? Use the LoadText node when: - You need to import content from existing text files - You want to process local documents with AI agents - You're working with saved text data like logs, notes, or reports - You need to feed external content into your workflow ## How to use it ### Basic Setup 1. Add a LoadText node to your workflow 1. Set the "path" parameter to the location of your text file 1. Connect the output to nodes that can process text content ### Parameters - **path**: The file path to the text file you want to load (string) ### Outputs - **output**: The content of the file as a text string - **path**: The path to the loaded file (same as the input) ## Example A workflow to analyze the content of a local text file: 1. Add a LoadText node to your workflow 1. Set the "path" parameter to something like "C:/Users/username/Documents/project_notes.txt" 1. Connect the "output" to an Agent node # MergeTexts ## What is it? The MergeTexts node is a utility node that combines multiple text strings into a single unified text output. It allows you to consolidate separate pieces of text with a configurable separator between them. ## When would I use it? Use the MergeTexts node when: - You need to combine text from multiple sources or nodes - You want to join separate pieces of text with consistent formatting - You're building a workflow that generates content from multiple components - You need to create a comprehensive document from individual sections ## How to use it ### Basic Setup 1. Add a MergeTexts node to your workflow 1. Connect multiple text outputs from other nodes to this node's inputs 1. Optionally configure the separator string 1. Connect the output to nodes that require the combined text ### Parameters - **input_1 through input_4**: Fixed number of text inputs to be combined - **merge_string**: The separator to place between text segments (defaults to "\\n\\n" when trim_whitespace is False) - **whitespace**: Boolean option to trim whitespace from each input and the final result ### Outputs - **output**: The combined text result as a single string ## Example A workflow to create a complete document from separate sections: 1. Add a MergeTexts node to your workflow 1. Connect outputs from three different text nodes (e.g., title, body, conclusion) 1. Set the merge_string parameter to "\\n\\n" for paragraph separation 1. The output will contain all text segments combined with the specified separator ## Important Notes - When `whitespace` is enabled (trim mode), each input is trimmed of whitespace before joining - When `whitespace` is enabled and the separator is empty, inputs are concatenated without any separator - When `whitespace` is disabled, empty inputs are filtered out, and the separator defaults to "\\n\\n" if not specified - The separator is only added between inputs, not at the beginning or end - **Recommendation**: Consider using the **JoinText** node for more flexible input handling ## Common Issues - **Unexpected formatting**: Check that your merge_string contains appropriate whitespace - **Missing content**: Verify all input connections are properly established # Random Text The Random Text node allows you to select random content from input text or generate random content when no input is provided. It supports selecting random characters, words, sentences, or paragraphs. ## Parameters ### Input Text - **Type**: String - **Mode**: Input/Property - **Description**: The text to select random content from. If empty, the node will generate random content based on the selection type. - **UI Options**: Multiline enabled ### Seed - **Type**: Integer - **Mode**: Property - **Description**: A seed value (0-10,000) for reproducible random selection. Using the same seed will produce the same random selection. - **UI Options**: Slider with range 0-10,000 ### Selection Type - **Type**: String - **Mode**: Property - **Description**: The type of content to select or generate: - `character`: Selects a random character - `word`: Selects a random word - `sentence`: Selects a random sentence or generates a new one - `paragraph`: Selects a random paragraph or generates a new one ### Output - **Type**: String - **Mode**: Output - **Description**: The randomly selected or generated content - **UI Options**: Multiline enabled ## Behavior - When input text is provided: - The node selects random content from the input based on the selection type - For sentences and paragraphs, it splits the input on appropriate delimiters - If no matching content is found, it falls back to generating new content - When no input text is provided: - For characters: Generates a random character from letters, digits, and punctuation - For words: Generates a random English word - For sentences: Uses an AI agent to generate a natural-sounding sentence - For paragraphs: Uses an AI agent to generate a coherent paragraph ## Examples ### Selecting Random Content ``` # Input text: "Hello world! This is a test. How are you today?" # Selection type: word # Output: "world" (randomly selected word) ``` ### Generating Random Content ``` # Input text: "" (empty) # Selection type: sentence # Output: "The quick brown fox jumps over the lazy dog." (AI-generated sentence) ``` ## Notes - The seed parameter ensures reproducible results when using the same value - The node will show a loading message while generating content with the AI agent # SaveText ## What is it? SaveText is a utility node that allows you to save text content to a file on your local system. This node provides a simple interface for writing text data to disk with a customizable output path. ## When would I use it? Use this node when you want to: - Save generated text content to a file - Export results from your workflow to a text file - Store conversation logs or outputs for later reference - Create text files as part of your automated workflow - Persist data between workflow runs ## How to use it ### Basic Setup 1. Add the SaveText node to your workflow 1. Connect a text source to the "text" input or enter text directly 1. Specify an output path or use the default 1. Run your workflow to save the text to the specified file ### Parameters - **text**: The text content you want to save to a file (supports multiline text) - **output_path**: The file path where the text will be saved (default is "griptape_output.txt") ### Outputs - **output_path**: The path to the saved file (confirms successful save operation) ## Example Imagine you want to save the output of an AI agent to a text file: 1. Add an Agent node to your workflow 1. Set up the agent with a prompt to generate some text 1. Add a SaveText node 1. Connect the Agent's "output" to the SaveText "text" input 1. Set the "output_path" to "my_agent_response.txt" 1. Run the workflow 1. The agent's response will be saved to "my_agent_response.txt" on your local system ## Important Notes - The node will create the file if it doesn't exist, or overwrite it if it does - The output_path parameter includes a save button in the UI for easy file selection - You can use either relative or absolute paths for the output file - The node will create any necessary parent directories if they don't exist - The text is saved with UTF-8 encoding ## Common Issues - **Permission Errors**: Ensure you have write permissions for the specified directory - **Invalid Path**: Make sure the path is valid for your operating system - **File Already Exists**: Be aware that existing files will be overwritten without confirmation # Scrape Web ## What is it? The Scrape Web tool is a utility that allows you to extract text content from web pages. ## When would I use it? Use this node when you want to: - Extract text content from specific web pages - Gather information from websites - Collect data from online sources - Automate web content extraction ## How to use it ### Basic Setup 1. Add the Scrape Web tool to your workflow 1. Configure the scraping parameters 1. Connect its output to nodes that need the scraped content ### Parameters - **url**: The URL of the web page to scrape ### Outputs - **output**: The text content extracted from the web page ## Example Imagine you want to extract content from a specific web page: 1. Add a Scrape Web tool to your workflow 1. Set the URL of the page you want to scrape 1. Connect the "output" to another node that needs the scraped content ## Implementation Details The Scrape Web tool uses web scraping capabilities to extract text content from web pages. It can target specific content using CSS selectors and handles timeouts and errors gracefully. # SearchReplaceText ## What is it? The SearchReplaceText node allows you to perform search and replace operations on multiline text content. It supports both simple text replacement and regular expression-based search and replace. ## When would I use it? Use the SearchReplaceText node when: - You need to modify multiline text by replacing specific patterns - You want to perform case-sensitive or case-insensitive text replacement - You need to use regular expressions for complex pattern matching - You want to replace either all occurrences or just the first occurrence of a pattern - You need to work with text that contains multiple lines or paragraphs ## How to use it ### Basic Setup 1. Add a SearchReplaceText node to your workflow 1. Connect or set the input text (can be multiline) 1. Set the search pattern 1. Set the replacement text (can be multiline) 1. Configure additional options as needed 1. Connect the output to nodes that accept text input ### Parameters - **input_text**: The multiline text to perform search and replace on (string) - **search_pattern**: The text or pattern to search for (string) - **replacement_text**: The multiline text to replace the search pattern with (string) - **case_sensitive**: Whether the search should be case sensitive (boolean, default: true) - **use_regex**: Whether to treat the search pattern as a regular expression (boolean, default: false) - **replace_all**: Whether to replace all occurrences or just the first one (boolean, default: true) ### Outputs - **output**: The multiline text after performing search and replace (string) ## Examples ### Simple Text Replacement 1. Add a SearchReplaceText node to your workflow 1. Set the input text to: ``` Hello World Welcome to Griptape ``` 1. Set the search pattern to: "World" 1. Set the replacement text to: "Griptape" 1. The output will be: ``` Hello Griptape Welcome to Griptape ``` ### Case-Insensitive Replacement 1. Add a SearchReplaceText node to your workflow 1. Set the input text to: ``` Hello WORLD Welcome to the WORLD ``` 1. Set the search pattern to: "world" 1. Set the replacement text to: "Griptape" 1. Set case_sensitive to: false 1. The output will be: ``` Hello Griptape Welcome to the Griptape ``` ### Regular Expression Examples Basic Regex Patterns ``` Input: "Line 1\nLine 2\nLine 3" Search Pattern: "Line \\d" Replacement: "Item" Use Regex: true Output: "Item\nItem\nItem" ``` This pattern matches "Line" followed by any digit (`\d`). Removing Numbers ``` Input: "Product123, Item456, Order789" Search Pattern: "\\d+" Replacement: "" Use Regex: true Output: "Product, Item, Order" ``` This pattern matches one or more digits (`\d+`). Word Boundaries ``` Input: "cat in the hat" Search Pattern: "\\bcat\\b" Replacement: "dog" Use Regex: true Output: "dog in the hat" ``` This pattern matches the word "cat" only when it appears as a complete word. Multiple Lines ``` Input: "First line\nSecond line\nThird line" Search Pattern: "^.*$" Replacement: "New line" Use Regex: true Output: "New line\nNew line\nNew line" ``` This pattern matches entire lines (`^` start, `.*` any characters, `$` end). ## Regex Reference Common Regex Patterns | Pattern | Description | Example | | ------- | -------------------------- | --------------------------------------- | | `\n` | Match a newline | `Line 1\nLine 2` | | `\s` | Match any whitespace | `Hello\sWorld` | | `\d` | Match any digit | `\d+` matches "123" | | `[a-z]` | Match any lowercase letter | `[a-z]+` matches "hello" | | `[A-Z]` | Match any uppercase letter | `[A-Z]+` matches "WORLD" | | `.` | Match any character | `a.c` matches "abc" | | `*` | Match 0 or more | `a*` matches "", "a", "aa" | | `+` | Match 1 or more | `a+` matches "a", "aa" | | `?` | Match 0 or 1 | `a?` matches "", "a" | | `\b` | Word boundary | `\bcat\b` matches "cat" but not "catch" | Regex Mode When using regex mode, special characters in the search pattern are treated as regex syntax. Make sure to escape special characters if you want to match them literally. Plain Text Mode When not using regex mode, the search pattern is treated as literal text, and special characters are escaped automatically. This is safer for simple text replacements. ## Important Notes - When using regular expressions, make sure to properly escape special characters - Case-insensitive search works for both plain text and regular expressions - If the search pattern is not found, the original text is returned unchanged - Invalid regular expressions will result in the original text being returned - The node preserves the original case of the text when doing case-insensitive replacements - Newlines are preserved in both input and output text - When using regex mode, you can use `\\n` to match newlines in the search pattern ## Common Issues - Regular expression syntax errors when use_regex is enabled - Unexpected results when mixing case-sensitive and case-insensitive operations - Performance impact with very large texts and complex regular expressions - Incorrect handling of newlines when not using regex mode ## Additional Resources For more comprehensive regex examples and patterns, check out: - [Python Regular Expression HOWTO](https://docs.python.org/3/howto/regex.html) - [Regex101](https://regex101.com/) - Interactive regex testing and debugging - [Regular-Expressions.info](https://www.regular-expressions.info/) - Detailed regex tutorials # Search Web ## What is it? The Search Web tool is a utility that allows you to search the web and retrieve text content from search results. ## When would I use it? Use this node when you want to: - Search the web for specific information - Retrieve text content from search results - Gather information from multiple web sources - Research topics online ## How to use it ### Basic Setup 1. Add the Search Web tool to your workflow 1. Connect its output to nodes that need web search results ### Parameters - **prompt**: The search query to use - **summarize**: Whether or not you want an LLM to summarize the text - **search_engine**: The search engine to use. Note: some engines require an API key. ### Outputs - **output**: The text content retrieved from the search results ## Example Imagine you want to search for information about a specific topic: 1. Add a Search Web tool to your workflow 1. Set the search query 1. Connect the "output to another node that needs the search results 1. The tool will return the text content from the search results ## Implementation Details The Search Web tool uses web search capabilities to find relevant information and extracts text content from the search results. It's designed to help you gather information from the web in a structured way. # Summarize Text ## What is it? The Summarize Text Task tool is a utility that summarizes text for you. ## When would I use it? Use this node when you want to: - Create tasks for summarizing text content - Define summarization requirements and parameters - Set up structured summarization workflows - Manage text summarization processes ## How to use it ### Basic Setup 1. Add the Summarize Text Task tool to your workflow 1. Configure the summarization parameters 1. Connect its output to nodes that need summarization tasks ### Parameters - **text**: The text content to be summarized ### Outputs - **output**: The summary of the text ## Example Imagine you want to create a summarization task for a long text: 1. Add a Summarize Text Task tool to your workflow 1. Connect the text input to your source text 1. Connect the "output" to a node that can process the summarization task ## Implementation Details The Summarize Text Task tool helps you summarize long amonts of text. # Load3D ## What is it? The Load3D is a building block that lets you bring a GLTF file into your workflow. It allows you to load 3D models from various sources to be used and displayed within your project. ## When would I use it? Use this node when you want to: - Use a GLTF model from a file - Pass a GLTF model to other nodes in your workflow - Display a GLTF model as part of your project - Create a screenshot of a GLTF model to use as part of your workflow ## How to use it ### Basic Setup 1. Add the Load3D node to your workflow 1. Connect it to a source of GLTF data (file browser, URL, or another node output) ### Parameters - **gltf**: The GLTF file or URL to load. This can be connected to an output from another node or selected via the file browser. ### Outputs - **gltf**: The loaded GLTF artifact that can be used by other nodes in your flow. - **image**: An image snapshot of the loaded GLTF model (available after the model is loaded and a snapshot is saved). ## Important Notes - The node can load GLTF files from your computer using the file browser. - Once a GLTF model is loaded, you can save a snapshot of it as an image using the 'Save Snapshot' button (if available in the UI). - The 'image' output parameter will only be available after a snapshot has been saved. ## Common Issues - **No Model Showing**: Make sure you have properly connected a GLTF source to this node or selected a valid file. - **Invalid File Type**: Ensure you are providing a valid GLTF file or URL. - **Image Output Not Available**: The image output is only generated when explicitly saving a snapshot from the UI. Ensure you have triggered this action. # Calculator ## What is it? The Calculator tool is a mathematical calculation tool that can be given to an agent to help it perform precise calculations. ## When would I use it? Use this node when you want to: - Enable agents to perform precise mathematical calculations - Solve complex math problems within your workflow - Process numerical data without writing custom calculation code ## How to use it ### Basic Setup 1. Add the Calculator to your workflow 1. Connect its output to nodes that need calculation capabilities (like an Agent) ### Outputs - **tool**: The configured calculator tool that other nodes can use ## Example Imagine you want to create an agent that can perform calculations: 1. Add a Calculator to your workflow 1. Connect the "tool" output to an Agent's "tools" input 1. Now that agent can perform calculations when needed in conversations ## Implementation Details The Calculator tool is implemented using Griptape's `CalculatorTool` class and provides a simple interface for performing mathematical calculations. The tool is designed to be used by agents to handle numerical operations accurately and efficiently. # Date Time ## What is it? The Date Time tool is a utility that can be given to an agent to help it perform date and time operations. ## When would I use it? Use this node when you want to: - Enable agents to work with dates and times - Perform date/time calculations and manipulations - Get current time information - Format dates and times in different ways ## How to use it ### Basic Setup 1. Add the Date Time tool to your workflow 1. Connect its output to nodes that need date/time capabilities (like an Agent) ### Outputs - **tool**: The configured date time tool that other nodes can use ## Example Imagine you want to create an agent that can work with dates and times: 1. Add a Date Time tool to your workflow 1. Connect the "tool" output to an Agent's "tools" input 1. Now that agent can perform date and time operations when needed in conversations ## Implementation Details The Date Time tool is implemented using Griptape's `DateTimeTool` class and provides a comprehensive interface for handling date and time operations. The tool is designed to be used by agents to manage temporal data and perform time-based calculations. # File Manager ## What is it? The File Manager tool is a utility that can be given to an agent to help it perform file operations in your workspace directory. ## When would I use it? Use this node when you want to: - Enable agents to read and write files - Manage files in your workspace directory - Perform file operations like copying, moving, or deleting files ## How to use it ### Basic Setup 1. Add the File Manager tool to your workflow 1. Connect its output to nodes that need file operation capabilities (like an Agent) ### Outputs - **tool**: The configured file manager tool that other nodes can use ## Example Imagine you want to create an agent that can manage files: 1. Add a File Manager tool to your workflow 1. Connect the "tool" output to an Agent's "tools" input 1. Now that agent can perform file operations when needed in conversations ## Implementation Details The File Manager tool is implemented using Griptape's `FileManagerTool` class with the `LocalFileManagerDriver`. The tool provides a simple interface for managing files in your workspace directory. Note: Cloud storage functionality is currently disabled and will be available in a future update. # InfoRetriever ## What is it? The InfoRetriever uses Retrieval Augmented Generation or "RAG" capabilities to your workflows. Think of it as a smart researcher that can find and use relevant information to enhance AI responses. ## When would I use it? Use this node when you want to: - Ground AI responses in your own data sources - Enable agents to access and reference specific information - Improve response accuracy with relevant context - Connect knowledge bases to your conversational agents ## How to use it ### Basic Setup 1. Add the InfoRetriever to your workflow 1. Connect its output to nodes that need RAG capabilities (like an Agent) ### Parameters - **description**: A description of what information this tool provides (default is "Contains information") - **off_prompt**: Whether to run RAG operations outside the main prompt (default is true) - **rag_engine**: The engine used to retrieve information (required) ### Outputs - **tool**: The configured RAG tool that other nodes can use - **rules**: The ruleset used by the RAG tool ## Example Imagine you want to create an agent that can answer questions using your company documentation: 1. Add an InfoRetriever to your workflow 1. Connect a vector store containing your documentation to the "rag_engine" input 1. Connect the "tool" output to an Agent's "tools" input 1. Now that agent can retrieve and reference specific information from your documentation when answering questions # ToolList ## What is it? The ToolList node combines multiple tools into a single list. This allows you to connect directly to a "tools" input parameter. ## When would I use it? Use this node when you want to: - Combine multiple tools into a single list - Create a workflow that leverages the strengths of different tools - Integrate tools from various sources in your Griptape workflow ## How to use it ### Basic Setup 1. Add the ToolList to your workflow 1. Connect it to other nodes that provide necessary input parameters (e.g., tools) 1. Run the flow to see the combined list of tools ### Parameters - **tools**: A list of tools to combine into a single list. ### Outputs - **tool_list**: The combined list of tools. ## Example Imagine you have a workflow that generates and saves text: 1. Create a flow with several nodes (like an agent that generates text and a node that saves it) 1. Add the ToolList, connecting it to nodes that provide tools parameters 1. Run the flow to see the combined list of tools in action ## Important Notes - The ToolList requires input tools to be provided when running the flow. - Using nested lists for tool inputs will result in flattened output. ## Common Issues - **Invalid Input Tools**: Ensure that you're providing a valid list of tools when connecting this node to other nodes in your workflow. - **Nested Lists**: Be aware that nested lists for tool inputs will be flattened into a single list. # Web Scraper ## What is it? The Web Scraper tool is a utility that can be given to an agent to help it extract information from web pages. ## When would I use it? Use this node when you want to: - Enable agents to extract data from websites - Scrape content from web pages - Gather information from online sources - Automate web data collection ## How to use it ### Basic Setup 1. Add the Web Scraper tool to your workflow 1. Connect its output to nodes that need web scraping capabilities (like an Agent) ### Outputs - **tool**: The configured web scraper tool that other nodes can use ## Example Imagine you want to create an agent that can scrape web content: 1. Add a Web Scraper tool to your workflow 1. Connect the "tool" output to an Agent's "tools" input 1. Now that agent can perform web scraping operations when needed in conversations ## Implementation Details The Web Scraper tool is implemented using Griptape's `WebScraperTool` class and provides a simple interface for extracting content from web pages. The tool is designed to be used by agents to gather information from websites in a structured way. ## Important Notes - The tool respects website terms of service and robots.txt files - Performance may vary depending on the structure and complexity of websites - Some websites may block automated scraping attempts - The tool works best with text-based content rather than dynamic JavaScript-heavy sites - Consider rate limiting and ethical use to avoid overloading websites ## Common Issues - **Access Denied**: Some websites actively block web scrapers - **Content Not Found**: Dynamic content loaded via JavaScript might not be accessible - **Rate Limiting**: Excessive requests may trigger rate limiting from websites - **Changing Layouts**: Website structure changes can affect scraping reliability - **Processing Large Pages**: Very large web pages may take longer to process or exceed token limits # Web Search ## What is it? The Web Search tool is a utility that can be given to an agent to help it search the web. It supports multiple search engines including DuckDuckGo, Google, and Exa. ## When would I use it? Use this node when you want to: - Enable agents to search the web for information - Access real-time web data - Perform research tasks - Get up-to-date information from the internet ## How to use it ### Basic Setup 1. Add the Web Search tool to your workflow 1. Connect its output to nodes that need web search capabilities (like an Agent) ### Parameters - **search_engine**: The search engine to use (default is "DuckDuckGo") - Options: - DuckDuckGo: Free, no API key required - Google: Requires Google API key and Search ID - Exa: Requires Exa API key ### Outputs - **tool**: The configured web search tool that other nodes can use ## Example Imagine you want to create an agent that can search the web: 1. Add a Web Search tool to your workflow 1. Connect the "tool" output to an Agent's "tools" input 1. Now that agent can perform web searches when needed in conversations ## Implementation Details The Web Search tool is implemented using Griptape's `WebSearchTool` class and supports multiple search engine drivers: - `DuckDuckGoWebSearchDriver`: Free, no API key required - `GoogleWebSearchDriver`: Requires Google API key and Search ID - `ExaWebSearchDriver`: Requires Exa API key When using Google or Exa search engines, you'll need to set up the appropriate API keys in the configuration. The tool will automatically handle authentication and search operations with the selected engine. # Note ## What is it? The Note node allows you to create descriptive notes throughout your workflow. They are not really operative, but more meant for display and documentation within a flow. ## When would I use it? Use the Note node when: - You need to describe what a section of your workflow does. - You want to give yourself a hint as to how to use a particular node. - You want to instruct someone else what to do with your workflow. ## How to use it ### Basic Setup 1. Add a Note node to your workflow 1. Set the "note" parameter with your desired multiline content ### Parameters - **note**: The text content (string, defaults to empty string) ## Example A workflow to create a note for how to use `prompt_context` on an Agent. 1. Add a Note node to your workflow 1. Move the Note near your Agent node 1. Enter the following text ``` To use the prompt_context, create a new KeyValuePair node. Give it something like a key of "style" and a value of "haiku" Then, connect the dictionary output to the prompt_context on your Agent. In the Agent prompt, use the {{ }} character to specify where you want to replace "style" with "haiku". Example: Tell me about skateboards in the style of a {{style}} ``` 1. Position the Note where it is easy to read and helps you understand your workflow ## Important Notes - Line breaks are preserved - The node supports any valid string content - Empty strings are valid - There is no character limit ## Common Issues - There is no control on the font size yet - There is no control for the color of the node yet # AddColorCurves ## What is it? The AddColorCurves node applies color grading effects to video using FFmpeg's `curves` filter. This filter allows for sophisticated color manipulation by adjusting the tonal curves of the video, providing both built-in presets and custom curve options for advanced color grading. ## When would I use it? Use the AddColorCurves node when: - You want to apply cinematic color grading effects to your video - You need to match the color style of multiple video clips - You're creating content that requires a specific visual aesthetic - You want to enhance the mood and atmosphere of your video - You need professional color correction and grading - You're working on projects that require consistent color treatment ## How to use it ### Basic Setup 1. Add an AddColorCurves node to your workflow 1. Connect a video source to the "video" input 1. Select a curve preset or define custom curves 1. Run the workflow to apply the color grading ### Parameters - **video**: The video content to apply color curves to (supports VideoArtifact and VideoUrlArtifact) - **curve_preset**: Built-in curve preset for color grading effects (default: "none") - **none**: No curves applied - **color_negative**: Color negative film look - **cross_process**: Cross-processed film effect - **darker**: Darker overall tone - **increase_contrast**: Moderate contrast boost - **lighter**: Lighter overall tone - **linear_contrast**: Linear contrast adjustment - **medium_contrast**: Medium contrast enhancement - **negative**: Black and white negative effect - **strong_contrast**: High contrast dramatic look - **vintage**: Classic vintage film look - **processing_speed**: Balance between processing speed and output quality (default: "balanced") - **fast**: Fastest processing, lower quality (ultrafast preset, CRF 30) - **balanced**: Good balance of speed and quality (medium preset, CRF 23) - **quality**: Highest quality, slower processing (slow preset, CRF 18) ### Outputs - **video**: The video with color curves applied, available as output to connect to other nodes ## Examples ### Example 1: Apply Vintage Effect 1. Connect the video output from a LoadVideo node to the AddColorCurves's "video" input 1. Set "curve_preset" to "vintage" 1. Run the workflow - the video will have a classic vintage film look 1. The output filename will be `{original_filename}_curves_vintage.{format}` ### Example 2: Apply Strong Contrast 1. Connect a video to the AddColorCurves node 1. Set "curve_preset" to "strong_contrast" 1. Run the workflow - the video will have dramatic contrast enhancement 1. The output filename will be `{original_filename}_curves_strong_contrast.{format}` ### Example 3: Apply Cross-Process Effect 1. Connect a video to the AddColorCurves node 1. Set "curve_preset" to "cross_process" 1. Run the workflow - the video will have a cross-processed film look 1. The output filename will be `{original_filename}_curves_cross_process.{format}` ## Important Notes - The AddColorCurves node uses FFmpeg's curves filter for high-quality color grading - Built-in presets provide quick access to common color grading effects - Processing time depends on video length and resolution - Curves can significantly affect the visual mood and style of your video - For best results, apply curves after other color adjustments ## Parameter Recommendations ### For Cinematic Look - Use "vintage" or "cross_process" presets - Try "color_negative" for a unique film look ### For High Contrast - Use "strong_contrast" or "negative" - Try "increase_contrast" for subtle enhancement # AddFilmGrain ## What is it? The AddFilmGrain node adds realistic film grain to video using sophisticated noise generation and luminance masking. It creates authentic film-like texture that varies based on the brightness of different areas in the video. ## When would I use it? Use the AddFilmGrain node when: - You want to add a cinematic, film-like quality to digital video - You need to create vintage or retro video effects - You want to add texture and character to clean digital footage - You're creating content that needs to look like it was shot on film - You want to enhance the visual depth and atmosphere of your video ## How to use it ### Basic Setup 1. Add an AddFilmGrain node to your workflow 1. Connect a video source to the "video" input 1. Adjust the grain intensity, luminance threshold, and grain scale parameters 1. Run the workflow to add film grain to your video ### Parameters - **video**: The video content to add film grain to (supports VideoArtifact and VideoUrlArtifact) - **grain_intensity**: Film grain intensity (0.05-1.0, default: 0.15) - 0.05 = very subtle grain - 0.15 = moderate grain (default) - 0.5 = heavy grain - 1.0 = maximum grain intensity - **luminance_threshold**: Luminance level where grain is most visible (50-100, default: 75) - 50 = grain visible in darker areas - 75 = grain visible in mid-tones (default, good for lighter areas) - 100 = grain visible in brighter areas - **grain_scale**: Grain scale factor (1.0-4.0, default: 2.0) - 1.0 = fine grain particles - 2.0 = medium grain (default) - 4.0 = larger grain particles - **processing_speed**: Balance between processing speed and output quality (default: "balanced") - **fast**: Fastest processing, lower quality (ultrafast preset, CRF 30) - **balanced**: Good balance of speed and quality (medium preset, CRF 23) - **quality**: Highest quality, slower processing (slow preset, CRF 18) ### Outputs - **video**: The video with film grain added, available as output to connect to other nodes ## Example Imagine you want to add a subtle film grain to a digital video to give it a cinematic look: 1. Add an AddFilmGrain node to your workflow 1. Connect the video output from a LoadVideo node to the AddFilmGrain's "video" input 1. Set the "grain_intensity" to 0.12 for subtle grain 1. Set the "luminance_threshold" to 75 for optimal grain visibility on lighter areas 1. Set the "grain_scale" to 2.0 for medium-sized grain particles 1. Run the workflow - the video will have realistic film grain added 1. The output filename will be `{original_filename}_grain_0.12.{format}` ## Important Notes - The AddFilmGrain node uses FFmpeg with sophisticated noise generation algorithms - Grain intensity is automatically adjusted based on the luminance of each pixel - The effect creates temporal grain that changes between frames for realism - Processing time depends on video length and resolution - The original audio track is preserved - Logs are available for debugging processing issues ## Parameter Recommendations - **For subtle film look**: Use grain_intensity 0.08-0.15, luminance_threshold 75 - **For vintage/retro effect**: Use grain_intensity 0.3-0.6, grain_scale 2.5-3.0 - **For heavy film grain**: Use grain_intensity 0.7-1.0, grain_scale 3.0-4.0 - **For portraits**: Use luminance_threshold 70-80 for optimal grain on lighter areas - **For landscapes**: Use luminance_threshold 60-70 for grain in mid-tones ## Common Issues - **Processing Timeout**: Large videos may take longer to process; the node has a 5-minute timeout - **Too Much Grain**: Reduce grain_intensity if the effect is too strong - **Grain Not Visible**: Increase grain_intensity or adjust luminance_threshold - **No Video Input**: Make sure a video source is connected to the "video" input - **FFmpeg Errors**: Check the logs parameter for detailed error information if processing fails # AddOverlay ## What is it? The AddOverlay node composites two videos together by overlaying one video on top of another using FFmpeg's blend modes. It provides control over the blend mode, channel selection, and sizing of the overlay video. The node supports various blend modes that match industry-standard compositing software. ## When would I use it? Use the AddOverlay node when: - You want to add film grain or noise effects to your video - You need to composite multiple video layers together - You're creating videos with watermarks or logos - You want to add visual effects that require layering - You're working on projects that need video compositing - You want to create vintage or textured video effects ## How to use it ### Basic Setup 1. Add an AddOverlay node to your workflow 1. Connect a base video to the "video" input 1. Connect an overlay video to the "overlay_video" input 1. Choose a blend mode and adjust the amount parameter 1. Run the workflow to composite the videos ### Parameters - **video**: The base video content (supports VideoArtifact and VideoUrlArtifact) - **overlay_video**: The video or image to overlay on top of the base video (supports VideoArtifact, VideoUrlArtifact, ImageArtifact, and ImageUrlArtifact) - **blend_mode**: How to blend the overlay with the base video (default: "overlay") - **overlay**: Original overlay with alpha blending - **screen**: Brighten (good for dust/scratches) - **lighten**: Only brighten where overlay is brighter - **softlight**: Natural blending for film grain - **grainmerge**: Merge grain/noise (recommended for noise effects) - **grainextract**: Extract grain/noise - **glow**: Glow effect - **hardlight**: Hard light blending - **amount**: Strength of the overlay effect (0.0-1.0, default: 0.5) - 0.0 = no effect (original video unchanged) - 0.5 = 50% effect strength (default) - 1.0 = full effect strength - **processing_speed**: Balance between processing speed and output quality (default: "balanced") - **fast**: Fastest processing, lower quality (ultrafast preset, CRF 30) - **balanced**: Good balance of speed and quality (medium preset, CRF 23) - **quality**: Highest quality, slower processing (slow preset, CRF 18) ### Outputs - **video**: The composited video with overlay applied, available as output to connect to other nodes ## Examples ### Example 1: Film Grain Overlay 1. Connect a base video to the AddOverlay's "video" input 1. Connect a film grain video to the "overlay_video" input 1. Set "blend_mode" to "grainmerge" for noise effects 1. Set "amount" to 0.3 for subtle grain effect 1. Run the workflow - the video will have a film grain overlay 1. The output filename will be `{original_filename}_overlay_grainmerge_amount0.30.{format}` ### Example 2: Logo Overlay 1. Connect a base video to the AddOverlay's "video" input 1. Connect a PNG logo with transparency to the "overlay_video" input 1. Set "blend_mode" to "overlay" for alpha blending 1. Set "amount" to 0.8 for visible logo 1. Run the workflow - the video will have a positioned logo ### Example 3: Vintage Effect with Film Grain 1. Use ChangeSpeed to speed up your video 1. Use AddColorCurves with "vintage" preset 1. Use AddOverlay to add film grain texture 1. Set "blend_mode" to "grainmerge" 1. Set "amount" to 0.4 for vintage grain effect 1. Run the workflow for a complete vintage look ## Important Notes - The overlay video will be automatically looped and trimmed to match the base video's duration - PNG images with transparency are supported as overlays - The node uses linear RGB blending for consistent results that match industry standards - For noise effects, use "grainmerge" blend mode with "luminance" channel - For logos with transparency, use "overlay" blend mode with "rgba" channel - The overlay is always centered on the base video for simplicity ## Technical Details The node uses FFmpeg's blend modes and supports both overlay filter (for alpha blending) and blend filter (for other blend modes). It automatically converts to linear RGB space for consistent blending behavior that matches industry-standard compositing software. # AddRGBShift ## What is it? The AddRGBShift node adds RGB shift (chromatic aberration) effects to video, creating visual distortions where the red, green, and blue color channels are offset from each other. It can also add a tear effect that splits the video horizontally and applies different RGB shifts to each part. ## When would I use it? Use the AddRGBShift node when: - You want to create glitch art or digital distortion effects - You need to simulate chromatic aberration or lens distortion - You're creating cyberpunk or retro-futuristic content - You want to add visual interest to otherwise clean footage - You need to create unsettling or disorienting visual effects - You want to add a tear effect that splits the video horizontally ## How to use it ### Basic Setup 1. Add an AddRGBShift node to your workflow 1. Connect a video source to the "video" input 1. Adjust the RGB shift parameters for each color channel 1. Optionally enable the tear effect and configure its position and offset 1. Run the workflow to add the RGB shift effect ### Parameters #### RGB Shift Settings - **red_horizontal**: Red channel horizontal shift (-20 to 20 pixels, default: -6) - **red_vertical**: Red channel vertical shift (-20 to 20 pixels, default: 0) - **green_horizontal**: Green channel horizontal shift (-20 to 20 pixels, default: 6) - **green_vertical**: Green channel vertical shift (-20 to 20 pixels, default: 0) - **blue_horizontal**: Blue channel horizontal shift (-20 to 20 pixels, default: 0) - **blue_vertical**: Blue channel vertical shift (-20 to 20 pixels, default: 0) - **intensity**: Overall intensity of the RGB shift effect (0.0-1.0, default: 1.0) #### Tear Effect Settings - **tear_enabled**: Enable tear effect (default: False) - **tear_position**: Vertical position of tear (0.0-1.0, where 0.5 is center, default: 0.5) - **tear_offset**: Horizontal offset amount for tear effect (-50 to 50 pixels, default: 10) - **processing_speed**: Balance between processing speed and output quality (default: "balanced") - **fast**: Fastest processing, lower quality (ultrafast preset, CRF 30) - **balanced**: Good balance of speed and quality (medium preset, CRF 23) - **quality**: Highest quality, slower processing (slow preset, CRF 18) ### Outputs - **video**: The video with RGB shift effects added, available as output to connect to other nodes ## Example Imagine you want to create a glitch effect on a video: 1. Add an AddRGBShift node to your workflow 1. Connect the video output from a LoadVideo node to the AddRGBShift's "video" input 1. Set red_horizontal to -8, green_horizontal to 8 for classic RGB separation 1. Set intensity to 0.8 for a moderate effect 1. Enable tear_enabled and set tear_position to 0.6 and tear_offset to 15 1. Run the workflow - the video will have RGB shift effects with a tear at 60% down the frame 1. The output filename will include all the effect parameters ## Important Notes - The AddRGBShift node uses FFmpeg with the rgbashift filter for reliable effects - The tear effect splits the video horizontally and applies different RGB shifts to each part - The original audio track is preserved - Processing time depends on video length and effect complexity ## Effect Recommendations - **Subtle RGB shift**: Use small values (2-4 pixels) for red/green horizontal shifts - **Heavy glitch effect**: Use larger values (10-20 pixels) for dramatic separation - **Tear effect**: Position the tear at 0.3-0.7 for best visual impact - **Cyberpunk aesthetic**: Combine red/green horizontal shifts with tear effects - **Retro video look**: Use moderate RGB shifts with intensity around 0.7-0.9 ## Common Issues - **Processing Timeout**: Complex effects may take longer to process; the node has a 5-minute timeout - **Effect Too Strong**: Reduce intensity or RGB shift values if the effect is overwhelming - **No Video Input**: Make sure a video source is connected to the "video" input - **FFmpeg Errors**: Check the logs parameter for detailed error information if processing fails # AddVignette ## What is it? The AddVignette node adds a vignette effect to video, creating a gradual darkening or lightening around the edges of the frame. This effect can enhance the visual focus on the center of the frame and add a cinematic quality to your video. ## When would I use it? Use the AddVignette node when: - You want to draw attention to the center of your video frame - You need to create a cinematic, film-like look - You want to add depth and atmosphere to your video - You're creating content that needs a professional, polished appearance - You want to simulate the natural light falloff of camera lenses - You need to enhance the mood or emotional impact of your video ## How to use it ### Basic Setup 1. Add an AddVignette node to your workflow 1. Connect a video source to the "video" input 1. Adjust the vignette angle, center position, and aspect ratio 1. Choose between "forward" (darken edges) or "backward" (lighten edges) mode 1. Run the workflow to add the vignette effect ### Parameters - **video**: The video content to add vignette to (supports VideoArtifact and VideoUrlArtifact) - **angle**: Lens angle for vignette effect (0.1-3.14, default: 0.628) - Smaller values = less vignette effect - 0.1 = very subtle vignette - 0.628 = moderate vignette (default) - 3.14 = very strong vignette - **center_x**: Center X offset (-1.0 to 1.0, default: 0.0) - -1.0 = far left - 0.0 = center (default) - 1.0 = far right - **center_y**: Center Y offset (-1.0 to 1.0, default: 0.0) - -1.0 = top - 0.0 = center (default) - 1.0 = bottom - **aspect**: Aspect ratio of vignette (0.1-10.0, default: 1.0) - 0.1 = very wide vignette - 1.0 = circular vignette (default) - 10.0 = very tall vignette - **mode**: Vignette mode (default: "forward") - "forward" = darken edges (traditional vignette) - "backward" = lighten edges (inverse vignette) - **processing_speed**: Balance between processing speed and output quality (default: "balanced") - **fast**: Fastest processing, lower quality (ultrafast preset, CRF 30) - **balanced**: Good balance of speed and quality (medium preset, CRF 23) - **quality**: Highest quality, slower processing (slow preset, CRF 18) ### Outputs - **video**: The video with vignette effect added, available as output to connect to other nodes ## Example Imagine you want to add a subtle vignette to a portrait video to draw attention to the subject: 1. Add an AddVignette node to your workflow 1. Connect the video output from a LoadVideo node to the AddVignette's "video" input 1. Set the "angle" to 0.5 for a moderate vignette effect 1. Keep "center_x" and "center_y" at 0.0 to center the vignette 1. Set "aspect" to 1.0 for a circular vignette 1. Choose "forward" mode to darken the edges 1. Run the workflow - the video will have a subtle vignette effect 1. The output filename will be `{original_filename}_vignette_a0.50_x0.00_y0.00_r1.00_forward.{format}` ## Important Notes - The AddVignette node uses FFmpeg with high-quality vignette algorithms - The effect creates smooth, gradual transitions from center to edges - The original audio track is preserved - Processing time depends on video length and resolution - Logs are available for debugging processing issues ## Parameter Recommendations - **For portraits**: Use angle 0.4-0.6, center at (0,0), aspect 1.0 - **For landscapes**: Use angle 0.3-0.5, center at (0,0), aspect 1.5-2.0 - **For cinematic look**: Use angle 0.5-0.8, forward mode - **For subtle enhancement**: Use angle 0.8-1.2 for very gentle vignette - **For dramatic effect**: Use angle 2.0-3.0 for strong vignette - **For off-center subjects**: Adjust center_x and center_y to match subject position ## Common Issues - **Processing Timeout**: Large videos may take longer to process; the node has a 5-minute timeout - **Vignette Too Strong**: Decrease the angle value to make the effect more subtle - **Vignette Not Visible**: Increase the angle value to make the effect stronger - **No Video Input**: Make sure a video source is connected to the "video" input - **FFmpeg Errors**: Check the logs parameter for detailed error information if processing fails # AdjustMaskSize ## What is it? The AdjustMaskSize node adjusts the size of masks in a video by dilating (expanding) or eroding (shrinking) the mask boundaries. It processes each frame individually using morphological operations while maintaining video properties. ## When would I use it? Use the AdjustMaskSize node when: - You need to expand mask boundaries to include more surrounding area (dilation) - You want to shrink masks to remove edge artifacts or thin borders (erosion) - You need to refine mask edges from segmentation or tracking results - You want to adjust mask coverage by a precise number of pixels ## How to use it ### Basic Setup 1. Add an AdjustMaskSize node to your workflow 1. Connect a mask video source to the "mask_video" input 1. Adjust the "adjustment" slider to set the desired expansion or shrinkage 1. Run the workflow to process the mask video ### Parameters - **mask_video**: The mask video to adjust (supports VideoArtifact and VideoUrlArtifact) - **adjustment**: Mask size adjustment in pixels (-25 to +25, default: 0) - Positive values (1 to 25): Expand the mask (dilation) - Negative values (-25 to -1): Shrink the mask (erosion) - Zero (0): Pass through unchanged (no processing) ### Outputs - **output_mask**: The adjusted mask video, available as output to connect to other nodes ## How it works The node applies morphological operations frame-by-frame: - **Dilation (positive adjustment)**: Expands white regions in the mask by applying a maximum filter. Each white pixel expands outward by the specified number of pixels using a circular kernel. - **Erosion (negative adjustment)**: Shrinks white regions in the mask by applying a minimum filter. Each white pixel shrinks inward by the specified number of pixels using a circular kernel. - **Zero adjustment**: When adjustment is 0, the input video is returned unchanged without processing. The circular kernel ensures natural-looking, uniform expansion or contraction in all directions. A progress indicator shows processing status (0-90% for frame processing, 90-100% for video reassembly). ## Example To expand a mask by 5 pixels to include more surrounding area: 1. Connect your mask video to the **mask_video** input 1. Set **adjustment** to `5` 1. Run the workflow 1. The output mask will have boundaries expanded by 5 pixels in all directions To remove a 3-pixel border from a mask: 1. Connect your mask video to the **mask_video** input 1. Set **adjustment** to `-3` 1. Run the workflow 1. The output mask will have boundaries shrunk by 3 pixels in all directions ## Important Notes - The AdjustMaskSize node uses FFmpeg for video frame extraction and reassembly - Morphological operations use PIL (Pillow) MaxFilter and MinFilter for efficient processing - The circular kernel provides smooth, natural-looking results - Input masks are automatically converted to grayscale during processing - The output video maintains the same resolution, frame rate, and duration as the input - Zero adjustment skips all processing for efficiency - Logs are available for debugging processing issues # AdjustVideoEQ ## What is it? The AdjustVideoEQ node allows you to adjust video brightness, contrast, saturation, and gamma settings. It provides precise control over the visual appearance of your video content using FFmpeg's high-quality equalization filters. ## When would I use it? Use the AdjustVideoEQ node when: - You need to correct exposure issues in your video - You want to enhance the visual quality of poorly lit footage - You need to adjust color saturation for artistic or technical reasons - You want to match the look of multiple video clips - You're creating content that needs specific visual adjustments - You want to improve the overall visual appeal of your video ## How to use it ### Basic Setup 1. Add an AdjustVideoEQ node to your workflow 1. Connect a video source to the "video" input 1. Adjust the brightness, contrast, saturation, and gamma parameters 1. Run the workflow to apply the EQ adjustments ### Parameters - **video**: The video content to adjust (supports VideoArtifact and VideoUrlArtifact) - **brightness**: Brightness adjustment (-1.0 to 1.0, default: 0.0) - -1.0 = very dark - 0.0 = normal brightness (default) - 1.0 = very bright - **contrast**: Contrast adjustment (0.0 to 3.0, default: 1.0) - 0.0 = no contrast (flat image) - 1.0 = normal contrast (default) - 3.0 = very high contrast - **saturation**: Saturation adjustment (0.0 to 3.0, default: 1.0) - 0.0 = black and white - 1.0 = normal saturation (default) - 3.0 = very saturated colors - **gamma**: Gamma adjustment (0.1 to 10.0, default: 1.0) - 0.1 = very bright mid-tones - 1.0 = normal gamma (default) - 10.0 = very dark mid-tones - **processing_speed**: Balance between processing speed and output quality (default: "balanced") - **fast**: Fastest processing, lower quality (ultrafast preset, CRF 30) - **balanced**: Good balance of speed and quality (medium preset, CRF 23) - **quality**: Highest quality, slower processing (slow preset, CRF 18) ### Outputs - **video**: The video with EQ adjustments applied, available as output to connect to other nodes ## Example Imagine you want to brighten a dark video and increase its saturation: 1. Add an AdjustVideoEQ node to your workflow 1. Connect the video output from a LoadVideo node to the AdjustVideoEQ's "video" input 1. Set "brightness" to 0.3 to brighten the video 1. Set "contrast" to 1.2 to enhance contrast 1. Set "saturation" to 1.5 to make colors more vibrant 1. Keep "gamma" at 1.0 for normal mid-tone response 1. Run the workflow - the video will be brighter and more saturated 1. The output filename will be `{original_filename}_eq_b0.30_c1.20_s1.50_g1.00.{format}` ## Important Notes - The AdjustVideoEQ node uses FFmpeg with high-quality equalization algorithms - All parameters work together to create the final visual effect - The original audio track is preserved - Processing time depends on video length and resolution - Logs are available for debugging processing issues ## Parameter Recommendations - **For dark footage**: Use brightness 0.2-0.5, contrast 1.1-1.3 - **For overexposed footage**: Use brightness -0.2 to -0.5, contrast 0.8-0.9 - **For flat footage**: Use contrast 1.2-1.5, saturation 1.1-1.3 - **For vibrant colors**: Use saturation 1.3-1.8, gamma 0.9-1.1 - **For cinematic look**: Use contrast 1.1-1.2, saturation 0.8-0.9, gamma 1.1-1.2 - **For vintage effect**: Use saturation 0.7-0.8, gamma 1.2-1.4 ## Common Issues - **Processing Timeout**: Large videos may take longer to process; the node has a 5-minute timeout - **Too Bright/Dark**: Adjust brightness value - use negative values to darken, positive to brighten - **Colors Too Saturated**: Reduce saturation value below 1.0 - **No Video Input**: Make sure a video source is connected to the "video" input - **FFmpeg Errors**: Check the logs parameter for detailed error information if processing fails # ChangeSpeed ## What is it? The ChangeSpeed node allows you to change the playback speed of a video. It uses FFmpeg's setpts filter to adjust video timing and automatically adjusts audio speed to match, creating smooth speed changes while maintaining audio-video synchronization. ## When would I use it? Use the ChangeSpeed node when: - You want to create fast-motion effects (e.g., 2x, 3x faster) - You need to create slow-motion effects (e.g., 0.5x, 0.25x slower) - You want to speed up long videos for quick review - You need to slow down fast action for analysis - You're creating time-lapse or slow-motion content - You want to adjust video pacing for different audiences ## How to use it ### Basic Setup 1. Add a ChangeSpeed node to your workflow 1. Connect a video source to the "video" input 1. Set your desired speed multiplier 1. Run the workflow to change the video's playback speed ### Parameters - **video**: The video content to change speed (supports VideoArtifact and VideoUrlArtifact) - **speed**: Playback speed multiplier (0.1-10.0x, default: 1.0) - 0.1 = 10x slower (very slow motion) - 0.5 = 2x slower (slow motion) - 1.0 = normal speed (default) - 2.0 = 2x faster (fast motion) - 5.0 = 5x faster (very fast motion) - 10.0 = 10x faster (maximum speed) - **include_audio**: When enabled, includes audio in the output file (default: true) - When enabled: Audio is speed-adjusted to match video speed - When disabled: Creates a silent video (no audio track) - **processing_speed**: Balance between processing speed and output quality (default: "balanced") - **fast**: Fastest processing, lower quality (ultrafast preset, CRF 30) - **balanced**: Good balance of speed and quality (medium preset, CRF 23) - **quality**: Highest quality, slower processing (slow preset, CRF 18) ### Outputs - **video**: The video with changed playback speed, available as output to connect to other nodes ## Example Imagine you want to create a fast-motion effect by speeding up a video 3x: 1. Add a ChangeSpeed node to your workflow 1. Connect the video output from a LoadVideo node to the ChangeSpeed's "video" input 1. Set "speed" to 3.0 for 3x faster playback 1. Keep "include_audio" enabled (default) to include speed-adjusted audio 1. Run the workflow - the video will play 3x faster with audio adjusted to match 1. The output filename will be `{original_filename}_speed3.0.{format}` ## Important Notes - The ChangeSpeed node uses FFmpeg's setpts filter for high-quality speed changes - Audio is automatically speed-adjusted using the atempo filter to maintain synchronization (when include_audio is enabled) - Speed changes affect both video and audio simultaneously - Processing time depends on video length and resolution - Extreme speed changes (very fast or very slow) may affect audio quality - When include_audio is disabled, the output video will be silent ## Parameter Recommendations - **For fast motion**: Use 2.0-5.0 for sped-up effects - **For slow motion**: Use 0.25-0.75 for slow-motion effects - **For time-lapse**: Use 5.0-10.0 for very fast playback - **For analysis**: Use 0.5-0.75 to slow down fast action - **For quick review**: Use 2.0-3.0 to speed up long content ## Common Issues - **Processing Timeout**: Large videos may take longer to process; the node has a 5-minute timeout - **Audio Quality**: Extreme speed changes may affect audio clarity - **No Video Input**: Make sure a video source is connected to the "video" input - **FFmpeg Errors**: Check the logs parameter for detailed error information if processing fails ## Speed Examples - **0.25x**: 4x slower - Good for analyzing fast action - **0.5x**: 2x slower - Classic slow motion - **1.0x**: Normal speed - No change - **2.0x**: 2x faster - Fast motion - **3.0x**: 3x faster - Very fast motion - **5.0x**: 5x faster - Time-lapse effect # CombineMasksVideo ## What is it? The CombineMasksVideo node merges multiple mask videos into a single combined mask video by taking the pixel-wise maximum value across all input masks. It handles videos of different durations by padding shorter videos with black frames. ## When would I use it? Use the CombineMasksVideo node when: - You need to merge multiple mask videos into a single mask - You want to combine object masks from different sources - You're working with multiple segmentation outputs that need to be unified - You need to create composite masks from multiple tracking results ## How to use it ### Basic Setup 1. Add a CombineMasksVideo node to your workflow 1. Connect mask video sources to the "mask_videos" input (accepts multiple videos as a list) 1. Run the workflow to combine the masks ### Parameters - **mask_videos**: A list of mask videos to combine (supports VideoArtifact and VideoUrlArtifact) ### Outputs - **combined_mask**: The combined mask video, available as output to connect to other nodes ## How it works The node processes videos frame-by-frame using a pixel-wise maximum operation: - Each frame is converted to grayscale (if not already) - For each pixel position, the maximum value across all input masks is selected - The result is a mask where any white pixel from any input mask appears white in the output - If input videos have different durations, shorter videos are padded with black frames to match the longest video A progress indicator shows processing status (0-90% for frame processing, 90-100% for video reassembly). ## Example To combine two mask videos tracking different objects: 1. Connect both mask videos to the **mask_videos** input 1. Run the workflow 1. The output will contain white pixels wherever either input mask had white pixels ## Important Notes - The CombineMasksVideo node uses FFmpeg for video processing - All input masks are converted to grayscale during processing - The output video resolution matches the first input video - Videos with different durations are automatically handled through black frame padding - The output video maintains the frame rate of the reference (first) video - Logs are available for debugging processing issues # ConcatenateVideos ## What is it? The ConcatenateVideos node combines multiple videos into a single continuous video file with configurable format and codec options. It automatically handles videos of different dimensions by resizing them to match the first video's dimensions, ensuring smooth concatenation without visual discontinuity. ## When would I use it? Use the ConcatenateVideos node when: - You want to combine multiple video clips into one continuous video - Creating video compilations or montages from separate clips - Joining video segments that were split or recorded separately - Building longer videos from shorter generated clips (like from AI video models) - Creating playlists or sequences from individual video files - Combining videos with different resolutions (automatic resizing handles dimension mismatches) ## How to use it ### Basic Setup 1. Add a ConcatenateVideos node to your workflow 1. Connect multiple video inputs to the **video_inputs** parameter (can connect individual videos or a list) 1. Configure the output format and codec settings as needed 1. Connect the **output** to other nodes that require video input ### Parameters - **video_inputs**: List of videos to concatenate (supports VideoArtifact, VideoUrlArtifact, and lists of both types) - **output_format**: Output video format - mp4, avi, mov, mkv, or webm (default: mp4) - **video_codec**: Video codec for output - libx264, libx265, libvpx-vp9, or copy (default: libx264) - **audio_codec**: Audio codec for output - aac, mp3, libmp3lame, or copy (default: aac) - **output_frame_rate**: Frame rate control (inherited from BaseVideoProcessor) - **processing_speed**: Quality vs speed trade-off (inherited from BaseVideoProcessor) ### Outputs - **output**: The concatenated video file as a VideoUrlArtifact - **logs**: Processing logs with detailed information about downloading, dimension checking, and resizing operations ## Automatic Video Resizing The ConcatenateVideos node intelligently handles videos with different dimensions by automatically resizing them: ### How It Works 1. **Dimension Detection**: The node checks the dimensions of all input videos using FFprobe 1. **Reference Selection**: The first video's dimensions are used as the target size for all videos 1. **Smart Resizing**: Videos that don't match the target dimensions are automatically resized using FFmpeg 1. **Aspect Ratio Preservation**: Resizing maintains the original aspect ratio and adds black padding if needed 1. **Detailed Logging**: All resizing operations are logged with before/after dimensions ### Resizing Behavior - **Same Size Videos**: If all videos have matching dimensions, no resizing occurs - **Mixed Size Videos**: Videos are resized to match the first video's dimensions - **Quality Preservation**: Uses high-quality scaling with aspect ratio preservation - **Audio Preservation**: Audio tracks are copied without re-encoding during resize operations ### Log Examples When videos have matching dimensions: ``` ✅ All videos have matching dimensions - no resizing needed ``` When resizing is needed: ``` 🔄 Videos have different dimensions - resizing to match first video... Target dimensions (from first video): 1920x1080 • Video 1: 1920x1080 (reference) • Video 2: 1280x720 → 1920x1080 (resizing...) ✅ Video 2 resized successfully • Video 3: 1920x1080 (no resize needed) ``` ## Example When creating a longer video from multiple AI-generated clips: 1. Add a ConcatenateVideos node to your workflow 1. Connect multiple video generation nodes to the **video_inputs** parameter 1. Set the **output_format** to "mp4" and **video_codec** to "libx264" for wide compatibility 1. Set the **audio_codec** to "aac" for high-quality audio 1. When the node runs, all input videos will be joined into a single continuous video ## Advanced Usage ### Format and Codec Selection - **mp4 + libx264**: Best for web compatibility and general use - **mov + libx264**: Good for professional video editing workflows - **mkv + libx265**: Excellent compression for storage efficiency - **webm + libvpx-vp9**: Optimized for web streaming - **copy codecs**: Fastest processing when all inputs have the same codec ### Performance Tips - Use "copy" for video and audio codecs when all input videos have identical formats for fastest processing - Choose "fast" processing speed for quick previews, "quality" for final output - Consider using libx265 for smaller file sizes with similar quality - **For best performance**: Use videos with matching dimensions to avoid resizing overhead - **Video ordering**: Place your highest-quality or target-dimension video first to set the reference resolution ## Important Notes - **Minimum Input**: Requires at least 2 videos to concatenate - **Format Support**: Supports common video formats (mp4, avi, mov, mkv, webm) - **Automatic Download**: Videos are automatically downloaded and prepared for concatenation - **Automatic Resizing**: Videos with different dimensions are automatically resized to match the first video - **Dimension Reference**: The first video in the list determines the target dimensions for all videos - **Quality Preservation**: Resizing uses high-quality FFmpeg scaling with aspect ratio preservation - **Frame Rate Handling**: Can automatically adjust frame rates or preserve original rates - **Audio Synchronization**: Maintains audio sync throughout the concatenated video - **Web Optimization**: Adds faststart flag for optimized web streaming - **Processing Logs**: Detailed logs show all dimension checking and resizing operations ## Common Issues - **"At least 2 videos required"**: Make sure you've connected at least two video inputs to the node - **Format Mismatch**: If videos have very different formats, avoid using "copy" codecs and let the node re-encode - **Large File Processing**: For very large videos, increase timeout or use "fast" processing speed - **Audio Issues**: If source videos have different audio formats, avoid "copy" audio codec and use aac or mp3 - **Memory Usage**: Processing many large videos simultaneously may require sufficient system memory - **Slow Resizing**: Large videos with different dimensions will take longer to process due to resizing operations - **Unexpected Dimensions**: The first video determines output dimensions - reorder videos if you want different target dimensions - **Quality Loss**: Multiple resizing operations may reduce video quality - use videos with matching dimensions when possible - **FFprobe Errors**: If dimension detection fails, ensure videos are valid and not corrupted # DisplayVideo ## What is it? The DisplayVideo node simply displays video content in your workflow. ## When would I use it? Use the DisplayVideo node when: - You need to display video results or information in your workflow - You want to show video output from previous processing steps - You need a placeholder for video content that will be updated during workflow execution - You want to create readable labels or descriptions within your workflow - You need to visualize video data at specific points in your process ## How to use it ### Basic Setup 1. Add a DisplayVideo node to your workflow 1. Set the initial video value if desired 1. Connect inputs to the video parameter or manually select a video 1. Connect the video output to other nodes that require video input ### Parameters - **video**: The video content to display (supports VideoArtifact and VideoUrlArtifact) ### Outputs - **video**: The same video content, available as output to connect to other nodes ## Example Imagine you're building a workflow that processes and displays video information: 1. Add a DisplayVideo node to your workflow 1. Connect the output from a LoadVideo node to the DisplayVideo node's video parameter 1. When the workflow runs, the video content will be displayed in the node ## Important Notes - The DisplayVideo node is for visualization and doesn't modify the video content - The node passes through video exactly as received, without any processing or formatting - Supports common video formats (mp4, avi, mov, etc.) - The video player controls allow you to play, pause, and scrub through the video ## Common Issues - **No Video Showing**: Make sure you've properly connected a video source to this node - **Video Won't Play**: Check that your video file is in a supported format and not corrupted # ExtractAudio ## What is it? The ExtractAudio node extracts the audio track from a video and outputs it as an audio file for use in your workflow. It supports multiple audio formats and quality settings, with options for both lossless copying and re-encoding. ## When would I use it? Use the ExtractAudio node when: - You need to separate audio from video content for audio processing workflows - You want to convert video audio to different formats (MP3, WAV, AAC, etc.) - You're creating audio-only content from video sources - You need to extract audio for transcription, analysis, or editing - You want to preserve the original audio quality by copying without re-encoding ## How to use it ### Basic Setup 1. Add an ExtractAudio node to your workflow 1. Connect a video input to the video parameter or manually select a video file 1. Choose your desired audio format and quality settings 1. Connect the extracted_audio output to other nodes that require audio input ### Parameters #### Input - **video**: The video content to extract audio from (supports VideoArtifact and VideoUrlArtifact) #### Audio Settings - **audio_format**: Output audio format - `mp3` (default) - Widely compatible, good compression - `wav` - Uncompressed, highest quality - `aac` - Good compression, modern standard - `flac` - Lossless compression - `ogg` - Open-source alternative with good compression - `m4a` - Apple's audio format, similar to AAC - **audio_quality**: Audio quality/bitrate setting - `high` (128k) - High quality for most purposes (default) - `medium` (96k) - Balanced quality and file size - `low` (64k) - Smaller files, lower quality - `copy` - Copy original audio without re-encoding (fastest, preserves original quality) ### Outputs - **extracted_audio**: The audio extracted from the video as an AudioUrlArtifact - **logs**: Processing logs with detailed information about the extraction process ## Audio Format and Quality Guide ### Quality Settings - **Copy**: Fastest option, preserves original audio quality and format exactly. Recommended when you don't need format conversion. - **High (128k)**: Excellent quality for most use cases, good balance of quality and file size. - **Medium (96k)**: Good quality for voice content, smaller file size. - **Low (64k)**: Acceptable for voice-only content where file size is critical. ### Format Recommendations - **MP3**: Most universally compatible, good for general use - **WAV**: Use for highest quality or when working with audio editing software - **AAC**: Modern standard with efficient compression, good for streaming - **FLAC**: Use when you need lossless compression (smaller than WAV but larger than lossy formats) - **OGG**: Open-source alternative to MP3, good compression - **M4A**: Good for Apple ecosystem compatibility ## Example Extracting audio from a video for transcription: 1. Add an ExtractAudio node to your workflow 1. Connect a video source to the **video** input 1. Set **audio_format** to `wav` for high compatibility with transcription services 1. Set **audio_quality** to `high` for best transcription accuracy 1. Connect the **extracted_audio** output to a transcription node 1. When the workflow runs, the audio will be extracted and available for transcription ## Performance Tips - Use **copy** quality when you don't need to change the audio format - it's much faster - Choose **MP3** with **high** quality for a good balance of quality, compatibility, and file size - Use **WAV** format when working with audio editing software that requires uncompressed audio - For long videos, the extraction process may take some time - check the logs for progress ## Important Notes - **Audio Detection**: The node automatically detects if the video contains audio streams - **No Audio Error**: If the input video has no audio track, the node will report an error - **Format Support**: Supports all common video formats (mp4, avi, mov, mkv, webm, etc.) - **Quality Preservation**: Using "copy" quality preserves the original audio exactly without any quality loss - **Processing Speed**: Copy mode is fastest, while re-encoding takes longer but allows format conversion ## Common Issues - **"No audio stream found"**: The input video doesn't contain any audio tracks. Check that your video file has audio. - **Extraction Failed**: Make sure the input video is valid and accessible - **Large File Processing**: For very large videos, extraction may take several minutes - monitor the logs for progress - **Format Issues**: If you encounter format-specific issues, try using "copy" mode first to preserve the original audio format # ExtractLastFrame ## What is it? The ExtractLastFrame node extaracts the last frame from a video as an image for use in your workflow. ## When would I use it? Use the ExtractLastFrame node when: - You need to extract the final frame from a video for use in your workflow. ## How to use it ### Basic Setup 1. Add a ExtractLastFrame node to your workflow 1. Connect an input to the video parameter or manually select a video 1. Connect the last_frame_image output to other nodes that require image input ### Parameters - **video**: The video content to extract the last frame from (supports VideoArtifact and VideoUrlArtifact) ### Outputs - **last_frame_image**: The the last frame from the video, output as an ImageUrlArtifact ## Example When working with image to video models you might want to chain several short video clips together to make one longer video. 1. Add a ExtractLastFrame node to your workflow 1. Connect the output from an image to video node to the **video** input on the ExtractLastFrame node 1. When the ExtractLastFrame node or workflow runs, the last frame from the video will be displayed in the node ## Important Notes - Supports common video formats (mp4, avi, mov, etc.) - The video player controls allow you to play, pause, and scrub through the video ## Common Issues - **No Video Showing**: Make sure you've properly connected a video source to this node # FlipVideo ## What is it? The FlipVideo node flips your video horizontally, vertically, or both directions. This creates a mirror effect that can be useful for correcting orientation or creating artistic effects. ## When would I use it? Use the FlipVideo node when: - You need to correct video that was recorded upside down or backwards - You want to create mirror effects for artistic purposes - You need to flip footage to match other video clips - You're creating content that requires flipped orientation - You want to create symmetrical video effects - You need to correct camera orientation issues ## How to use it ### Basic Setup 1. Add a FlipVideo node to your workflow 1. Connect a video source to the "video" input 1. Choose the flip direction (horizontal, vertical, or both) 1. Run the workflow to flip the video ### Parameters - **video**: The video content to flip (supports VideoArtifact and VideoUrlArtifact) - **direction**: Flip direction (default: "horizontal") - "horizontal" = flip left to right (mirror effect) - "vertical" = flip top to bottom (upside down) - "both" = flip both horizontally and vertically - **processing_speed**: Balance between processing speed and output quality (default: "balanced") - **fast**: Fastest processing, lower quality (ultrafast preset, CRF 30) - **balanced**: Good balance of speed and quality (medium preset, CRF 23) - **quality**: Highest quality, slower processing (slow preset, CRF 18) ### Outputs - **video**: The flipped video, available as output to connect to other nodes ## Example Imagine you want to create a mirror effect on a video: 1. Add a FlipVideo node to your workflow 1. Connect the video output from a LoadVideo node to the FlipVideo's "video" input 1. Set "direction" to "horizontal" to create a mirror effect 1. Run the workflow - the video will be flipped horizontally 1. The output filename will be `{original_filename}_flipped_horizontal.{format}` ## Important Notes - The FlipVideo node uses FFmpeg with high-quality flip processing - Horizontal flip creates a mirror effect (left becomes right) - Vertical flip turns the video upside down - Both directions flip the video in both axes - The original audio track is preserved - Processing time depends on video length and resolution - Logs are available for debugging processing issues ## Direction Recommendations - **For mirror effects**: Use "horizontal" - creates left-to-right mirror - **For upside down correction**: Use "vertical" - flips top to bottom - **For complete rotation**: Use "both" - flips in both directions - **For camera correction**: Use appropriate direction based on how the camera was oriented ## Common Issues - **Processing Timeout**: Large videos may take longer to process; the node has a 5-minute timeout - **Wrong Direction**: Make sure you've selected the correct flip direction for your needs - **No Video Input**: Make sure a video source is connected to the "video" input - **FFmpeg Errors**: Check the logs parameter for detailed error information if processing fails # HoldVideoFrames ## What is it? The HoldVideoFrames node creates a stepped video effect by holding each frame for a specified number of frames. This creates a stop-motion or frame-stepping effect that can add artistic interest to your video content. ## When would I use it? Use the HoldVideoFrames node when: - You want to create a stop-motion animation effect - You need to slow down fast-moving content for better visibility - You're creating artistic video effects with frame manipulation - You want to add a retro or vintage feel to your video - You need to create dramatic slow-motion effects - You're making content that requires frame-by-frame analysis ## How to use it ### Basic Setup 1. Add a HoldVideoFrames node to your workflow 1. Connect a video source to the "video" input 1. Set the "hold_frames" parameter to specify how many frames to hold 1. Run the workflow to create the frame-holding effect ### Parameters - **video**: The video content to apply frame holding to (supports VideoArtifact and VideoUrlArtifact) - **hold_frames**: Number of frames to hold (1-10, default: 2) - 1 = no effect (normal playback) - 2 = each frame held for 2 frames (slower playback) - 5 = each frame held for 5 frames (much slower playback) - 10 = each frame held for 10 frames (very slow playback) - **processing_speed**: Balance between processing speed and output quality (default: "balanced") - **fast**: Fastest processing, lower quality (ultrafast preset, CRF 30) - **balanced**: Good balance of speed and quality (medium preset, CRF 23) - **quality**: Highest quality, slower processing (slow preset, CRF 18) ### Outputs - **video**: The video with frame holding effect applied, available as output to connect to other nodes ## Example Imagine you want to create a stop-motion effect on a video: 1. Add a HoldVideoFrames node to your workflow 1. Connect the video output from a LoadVideo node to the HoldVideoFrames's "video" input 1. Set "hold_frames" to 3 to hold each frame for 3 frames 1. Run the workflow - the video will have a stepped, stop-motion effect 1. The output filename will be `{original_filename}_hold_3_frames.{format}` ## Important Notes - The HoldVideoFrames node uses FFmpeg with high-quality frame processing - The effect reduces the effective frame rate by the hold_frames value - The original audio track is preserved but may not sync perfectly with the visual effect - Processing time depends on video length and hold_frames value - Logs are available for debugging processing issues ## Effect Recommendations - **Subtle slow motion**: Use hold_frames 2-3 for gentle frame holding - **Stop-motion effect**: Use hold_frames 4-6 for noticeable stepping - **Dramatic slow motion**: Use hold_frames 7-10 for very slow playback - **Frame analysis**: Use hold_frames 2-3 to slow down fast content for review ## Common Issues - **Processing Timeout**: Large videos may take longer to process; the node has a 5-minute timeout - **Effect Too Strong**: Reduce hold_frames value if the effect is too dramatic - **Audio Sync Issues**: The frame holding may affect audio synchronization - **No Video Input**: Make sure a video source is connected to the "video" input - **FFmpeg Errors**: Check the logs parameter for detailed error information if processing fails # LoadVideo ## What is it? The LoadVideo is a simple building block that lets you bring a video into your workflow. Think of it as picking up a video file so you can use it in your project. ## When would I use it? Use this node when you want to: - Use a video that was created by another node - Pass a video to other nodes in your workflow - Display a video as part of your project - Load video files from disk into your workflow ## How to use it ### Basic Setup 1. Add the LoadVideo to your workflow 1. Connect it to a source of videos or use the file browser to select a video file ### Parameters - **video**: The video to load (this can be connected to an output from another node or loaded from a file) ### Outputs - **video**: The loaded video that can be used by other nodes in your flow ## Example Imagine you want to load a video file from your computer: 1. Add a LoadVideo node to your workflow 1. Click the file browser icon to select a video file from your computer 1. The LoadVideo will make the video available to use in the rest of your workflow ## Important Notes - The LoadVideo simply passes the video through - it doesn't change the video itself - You can click the file browser icon to select a video from your computer - The video preview can be expanded by clicking the expander icon - Supports common video formats (mp4, avi, mov, etc.) ## Common Issues - **No Video Showing**: Make sure you've properly connected a video source to this node or selected a file - **Unsupported Format**: Check that your video file is in a supported format # ResizeVideo ## What is it? The ResizeVideo node resizes video content using multiple scaling modes (width, height, percentage, or width and height) with FFmpeg. It can preserve aspect ratio, pad with a background color, or crop to fill depending on your fit mode. ## When would I use it? Use the ResizeVideo node when: - You need to scale videos by a precise percentage - You want to target a specific width or height while preserving aspect ratio - You need to fit videos into exact dimensions with padding or cropping - You want consistent resizing controls across video workflows ## How to use it ### Basic Setup 1. Add a ResizeVideo node to your workflow 1. Connect a video source to the "video" input 1. Choose a "resize_mode" 1. Configure the size parameters for that mode 1. Run the workflow to resize the video ### Parameters - **video**: The video content to resize (supports VideoArtifact and VideoUrlArtifact) - **resize_mode**: How the video should be resized - `width`: Resize to a target width while preserving aspect ratio - `height`: Resize to a target height while preserving aspect ratio - `percentage`: Resize by a percentage of the original size - `width and height`: Resize to exact dimensions with fit options - **target_size**: Target size in pixels for width/height modes (1-8000) - **percentage**: Resize percentage (1-500, default: 100) - **target_width**: Target width in pixels for width and height mode - **target_height**: Target height in pixels for width and height mode - **fit_mode**: How to fit the video within the target dimensions - `fit`: Preserve aspect ratio and add padding (letterboxing) - `fill`: Preserve aspect ratio and crop to fill the target - `stretch`: Ignore aspect ratio and stretch to the target - **background_color**: Background color for letterboxing in fit mode (hex) - **scaling_algorithm**: The scaling algorithm used for resizing (default: "bicubic") - `neighbor`, `bilinear`, `bicubic`, `lanczos` - **lanczos_parameter**: Fine-tune the Lanczos scaling algorithm (1.0-10.0, default: 3.0) Only affects the output when `scaling_algorithm` is set to "lanczos". ### Outputs - **resized_video**: The resized video content, available as output to connect to other nodes ## Example To resize a video to 1280x720 while preserving aspect ratio with padding: 1. Set **resize_mode** to `width and height` 1. Set **target_width** to `1280` 1. Set **target_height** to `720` 1. Set **fit_mode** to `fit` 1. Set **background_color** to `#000000` ## Important Notes - The ResizeVideo node uses FFmpeg for video processing - Dimensions are automatically adjusted to be divisible by 2 for codec compatibility - The node supports common video formats (mp4, avi, mov, etc.) - The resized video maintains the original audio track - Logs are available for debugging processing issues # ReverseVideo ## What is it? The ReverseVideo node reverses the playback direction of your video, making it play backwards. It can also handle audio reversal or muting depending on your needs. ## When would I use it? Use the ReverseVideo node when: - You want to create backwards playback effects - You need to reverse footage for artistic or creative purposes - You're creating special effects that require reverse motion - You want to add surreal or dreamlike qualities to your video - You need to reverse specific segments for editing purposes - You're making content that requires backwards motion analysis ## How to use it ### Basic Setup 1. Add a ReverseVideo node to your workflow 1. Connect a video source to the "video" input 1. Choose how to handle audio (reverse, mute, or keep original) 1. Run the workflow to reverse the video ### Parameters - **video**: The video content to reverse (supports VideoArtifact and VideoUrlArtifact) - **audio_handling**: How to handle audio (default: "reverse") - "reverse" = reverse both video and audio (recommended) - "mute" = reverse video only, no audio - "keep" = reverse video, keep original audio (not recommended) - **processing_speed**: Balance between processing speed and output quality (default: "balanced") - **fast**: Fastest processing, lower quality (ultrafast preset, CRF 30) - **balanced**: Good balance of speed and quality (medium preset, CRF 23) - **quality**: Highest quality, slower processing (slow preset, CRF 18) ### Outputs - **video**: The reversed video, available as output to connect to other nodes ## Example Imagine you want to create a backwards playback effect: 1. Add a ReverseVideo node to your workflow 1. Connect the video output from a LoadVideo node to the ReverseVideo's "video" input 1. Set "audio_handling" to "reverse" to reverse both video and audio 1. Run the workflow - the video will play backwards 1. The output filename will be `{original_filename}_reversed_reverse.{format}` ## Important Notes - The ReverseVideo node uses FFmpeg with high-quality reverse processing - Reversing both video and audio creates the most natural backwards effect - Muting audio is recommended if you want to avoid strange backwards audio - The "keep" option may create audio sync issues - Processing time depends on video length - Logs are available for debugging processing issues - The node automatically detects if the video has audio and handles it appropriately - Videos without audio will be processed correctly without errors ## Audio Handling Recommendations - **For natural backwards effect**: Use "reverse" - both video and audio play backwards - **For silent backwards effect**: Use "mute" - video plays backwards with no audio - **For custom audio**: Use "mute" and add your own audio track separately ## Common Issues - **Processing Timeout**: Large videos may take longer to process; the node has a 5-minute timeout - **Strange Audio**: If audio sounds odd, try using "mute" instead of "reverse" - **Audio Sync Issues**: The "keep" option may cause audio/video synchronization problems - **No Video Input**: Make sure a video source is connected to the "video" input - **FFmpeg Errors**: Check the logs parameter for detailed error information if processing fails # SaveVideo ## What is it? The SaveVideo node allows you to save video content from your workflow to a file on your computer. ## When would I use it? Use the SaveVideo node when: - You want to export video results from your workflow - You need to save processed video content to disk - You want to create video files that can be used outside of your workflow - You need to archive video outputs for later use ## How to use it ### Basic Setup 1. Add a SaveVideo node to your workflow 1. Connect a video source to the "video" input 1. Optionally specify the output filename and path 1. Run the workflow to save the video file ### Parameters - **video**: The video content to save (supports VideoArtifact, VideoUrlArtifact, and dict) - **output_path**: The filename and path where the video should be saved (default: "griptape_nodes.mp4") ### Outputs - **output_path**: The path where the video was saved ## Example Imagine you want to save a video that was processed in your workflow: 1. Add a SaveVideo node to your workflow 1. Connect the video output from another node to the SaveVideo's "video" input 1. Set the "output_path" to your desired filename (e.g., "my_video.mp4") 1. Run the workflow - the video will be saved to the specified location ## Important Notes - The SaveVideo node supports common video formats (.mp4, .avi, .mov, etc.) - The output path can include a directory structure (e.g., "videos/output.mp4") - If no extension is provided, .mp4 will be used by default - The node will create directories if they don't exist - You can use the save button in the UI to browse for a save location ## Common Issues - **Permission Denied**: Make sure you have write permissions to the specified directory - **Invalid Path**: Check that the output path is valid and the directory exists - **Unsupported Format**: Ensure you're using a supported video file extension - **No Video Input**: Make sure a video source is connected to the "video" input # SplitVideo ## What is it? The SplitVideo node allows you to split a video into multiple segments using timecodes. It uses AI-powered parsing to understand various timecode formats and creates separate video files for each segment while maintaining high quality. ## When would I use it? Use the SplitVideo node when: - You need to break a long video into smaller, manageable segments - You want to extract specific scenes or sections from a video - You're creating video clips for social media or different platforms - You need to separate different parts of a recording (intro, main content, outro) - You want to create highlight reels from longer footage - You're preparing video content for different audiences or purposes ## How to use it ### Basic Setup 1. Add a SplitVideo node to your workflow 1. Connect a video source to the "video" input 1. Enter timecodes in the "timecodes" parameter 1. Run the workflow to split the video into segments ### Parameters - **video**: The video to split (supports VideoArtifact and VideoUrlArtifact) - **timecodes**: Timecodes to split the video at (required) Supports various formats: **Simple timecode ranges:** ``` 00:00:00:00-00:01:00:00 00:01:00:00-00:02:30:00 ``` **Timecodes with titles:** ``` 00:00:00:00-00:00:04:07|Segment 1: Introduction 00:00:04:08-00:00:08:15|Segment 2: Main Content ``` **JSON format:** ``` [ {"start": "00:00:00:00", "end": "00:01:00:00", "title": "Intro"}, {"start": "00:01:00:00", "end": "00:02:30:00", "title": "Main Content"} ] ``` ### Outputs - **split_videos**: List of split video segments (VideoUrlArtifact array) - **logs**: Processing logs and detailed events ## Example Imagine you want to split a 10-minute video into three segments: 1. Add a SplitVideo node to your workflow 1. Connect the video output from a LoadVideo node to the SplitVideo's "video" input 1. Enter timecodes in the "timecodes" parameter: ``` 00:00:00:00-00:02:30:00|Segment 1: Introduction 00:02:30:00-00:07:30:00|Segment 2: Main Content 00:07:30:00-00:10:00:00|Segment 3: Conclusion ``` 1. Run the workflow - the video will be split into three separate files 1. Each segment will be available as a separate output in the split_videos list ## Important Notes - The SplitVideo node uses AI-powered parsing to understand various timecode formats - Timecodes should be in SMPTE format (HH:MM:SS:FF) where FF represents frames - The node automatically detects video frame rate and drop frame settings - Each segment maintains the original video quality and audio - Processing time depends on video length and number of segments - The node uses FFmpeg for high-quality video processing - Logs provide detailed information about the splitting process ## Timecode Format Guidelines - **Frame rates**: The node automatically detects 24fps, 25fps, 29.97fps, 30fps, 50fps, 59.94fps, and 60fps - **Drop frame**: Automatically detected for 29.97fps and 59.94fps videos - **Frame numbers**: Use 00-23 for 24fps, 00-24 for 25fps, 00-29 for 30fps, etc. - **Separators**: Use hyphens (-) between start and end times, pipes (|) for titles - **Titles**: Optional but recommended for better organization ## Common Issues - **Invalid Timecodes**: Make sure timecodes are in correct SMPTE format (HH:MM:SS:FF) - **Overlapping Segments**: Ensure segments don't overlap in time - **Processing Timeout**: Large videos may take longer to process; the node has a 5-minute timeout - **No Video Input**: Make sure a video source is connected to the "video" input - **Empty Segments**: Check that start time is before end time for each segment - **FFmpeg Errors**: Check the logs parameter for detailed error information if processing fails # VideoColorMatch ## What is it? The VideoColorMatch node transfers color characteristics from a reference image to a video using advanced color matching algorithms. By default, it uses a fast HALD CLUT (Color Lookup Table) method that applies the color transformation to the entire video in one pass, making it 10-50x faster than traditional frame-by-frame processing. This allows you to apply color grading to videos by matching the color palette of a reference image, useful for creating consistent visual styles, matching footage to a specific aesthetic, or applying cinematic color grading. ## When would I use it? Use the VideoColorMatch node when: - You want to apply color grading from a reference image to your video - You need to match the color palette of multiple video clips for consistency - You want to give your video a cinematic look based on a movie still or poster - You're creating content that needs to match a specific aesthetic or mood - You want to apply vintage or stylized color effects to modern footage - You need to transfer the color characteristics from a photograph to video footage ## How to use it ### Basic Setup 1. Add a VideoColorMatch node to your workflow 1. Connect a reference image to "reference_image" (the image with the color palette you want to transfer) 1. Connect your video to "target_video" (the video you want to modify) 1. Select a color transfer method 1. Adjust the strength parameter to control the intensity 1. Run the workflow to apply the color transfer ### Parameters #### Inputs - **reference_image**: The reference image providing the color palette to transfer (supports ImageArtifact and ImageUrlArtifact) - **target_video**: The video to apply color transfer to (supports VideoArtifact and VideoUrlArtifact) #### Color Match Settings - **transfer_method**: The processing method to use - **ffmpeg-haldclut**: Fast HALD CLUT-based transfer (10-50x faster, default) - Applies color matching once to a lookup table, then uses ffmpeg to process the entire video in one pass - **frame-by-frame**: Process each frame individually (slower, more memory intensive) - Extracts and processes each frame separately using the color-matcher library - **transfer_algorithm**: The color transfer algorithm to use - **mkl**: Monge-Kantorovich Linearization (fast, good quality, default) - **hm**: Histogram Matching (fast, moderate quality) - **reinhard**: Reinhard et al. color transfer (fast, good quality) - **mvgd**: Multi-Variate Gaussian Distribution (medium speed, good quality) - **hm-mvgd-hm**: Compound method (slower, best quality) - **hm-mkl-hm**: Alternative compound method (slower, best quality) - **strength** (0.0-10.0, default: 1.0): Controls the intensity of the color transfer - 0.0 = no change (original video colors) - 1.0 = full color transfer (default) - Values > 1.0 exaggerate the effect ### Outputs - **output**: The color-matched video with transferred color characteristics (VideoUrlArtifact) The output video preserves: - Original format (MP4, MOV, etc.) - Original resolution and aspect ratio - Original frame rate - Original audio track (encoded as AAC at 192kbps) ## Example ### Basic Color Grading Apply the color palette from a cinematic movie poster to your video: 1. Add a VideoColorMatch node to your workflow 1. Connect a LoadImage node with your reference movie poster to "reference_image" 1. Connect a LoadVideo node with your video to "target_video" 1. Keep the default "mkl" transfer_algorithm 1. Set "strength" to 1.0 for full color transfer 1. Run the workflow - your video will match the color palette of the movie poster ### Subtle Vintage Effect Apply a subtle vintage film look: 1. Load a vintage photograph as the reference image 1. Connect your modern video as the target 1. Set "transfer_algorithm" to "reinhard" for natural luminance preservation 1. Set "strength" to 0.7 for a subtle effect 1. Run the workflow - the result will have a nostalgic vintage aesthetic ### Experimenting with Methods Try different algorithms to see which aesthetic you prefer: 1. Process a short clip (2-3 seconds) with "mkl" algorithm 1. Process the same clip with "hm-mvgd-hm" algorithm 1. Compare the results to choose your preferred look 1. Apply to the full video ## Important Notes - **Processing Time**: - **ffmpeg-haldclut (default)**: Very fast processing - a 10-second 1080p video at 30fps typically processes in seconds. Recommended for most use cases. - **frame-by-frame**: Slower processing - the same 10-second video may take 3-5 minutes. Useful if you need pixel-perfect control or encounter issues with the HALD CLUT method. - **Progress Bar**: - **ffmpeg-haldclut**: Shows 10% for HALD generation, 20% for color matching, and 70% for video processing. - **frame-by-frame**: Shows 0-90% for frame processing and 90-100% for video reassembly. - **Video Properties**: All original video properties (format, resolution, frame rate, duration) are preserved in the output. - **Audio Preservation**: The original audio track is preserved (copied with ffmpeg-haldclut, re-encoded with AAC at 192kbps for frame-by-frame). - **Memory Usage**: Both methods process efficiently - ffmpeg-haldclut streams through video, frame-by-frame processes sequentially. ## Algorithm Details Different algorithms produce distinct aesthetic results: - **mkl**: Fast optimal transport-based method. Good balance of speed and quality. Best for general use. - **hm**: Matches color distributions using histograms. Can produce more dramatic color shifts. - **reinhard**: Statistical color transfer that preserves luminance structure. Good for natural-looking results. - **mvgd**: Models colors as Gaussian distributions. Better at capturing subtle color relationships. - **hm-mvgd-hm**: Compound method applying histogram matching, then MVGD, then histogram matching again for refined results. - **hm-mkl-hm**: Compound method using MKL as the middle step. Alternative approach for high-quality results. ## Tips and Best Practices ### Choosing a Reference Image ✓ **Good reference images:** - Clear, well-exposed images with the desired color palette - Images with similar lighting conditions to your video - Reference images that represent the final look you want ✗ **Avoid:** - Extremely dark or overexposed images - Images with very different lighting scenarios than your video - References with unnatural or oversaturated colors (unless that's your goal) ### Strength Guidelines | Strength | Use Case | | -------- | ---------------------------------------------------- | | 0.3-0.5 | Very subtle color correction, slight mood adjustment | | 0.6-0.8 | Noticeable but natural color grading | | 0.9-1.1 | Full color transfer, dramatic look change | | 1.2-2.0 | Stylized, artistic effects | | > 2.0 | Experimental, heavily exaggerated results | ### Transfer Method Selection **When to use ffmpeg-haldclut (default):** ✓ For most use cases - it's fast and produces excellent results ✓ When processing longer videos or high-resolution footage ✓ When you want to preview results quickly ✓ For batch processing multiple videos **When to use frame-by-frame:** - If you encounter unexpected visual artifacts with HALD CLUT (rare) - For troubleshooting or comparison purposes - When you need to verify processing against the legacy method ### Workflow Recommendations 1. **Test with short clips first**: Process a 2-3 second clip before running on full videos to preview the effect 1. **Algorithm comparison**: Try 2-3 different color algorithms with the same reference to see which aesthetic you prefer 1. **Batch processing**: Use the same reference image across multiple video clips for consistent look 1. **Strength refinement**: Start at 1.0, then adjust up or down based on results ## Common Issues ### Colors look too extreme - **Solution**: Reduce "strength" to 0.6-0.8 - Or choose a less saturated reference image - Or try "reinhard" algorithm which preserves more original luminance ### Not enough color change - **Solution**: Increase "strength" to 1.2-1.5 - Or choose a reference with more distinct colors - Or try "hm-mvgd-hm" algorithm for stronger transfer ### Processing takes too long - **Solution**: Ensure you're using "ffmpeg-haldclut" transfer method (default, 10-50x faster) - Or use "mkl" color algorithm (fastest color transfer algorithm) - Or pre-trim your video to the desired section using Split Video node - Or reduce video resolution before processing using Resize Video node ### Output looks different than expected - **Solution**: Try different color matching algorithms - each produces different results - Ensure your reference image has the color characteristics you actually want - Adjust strength to find the sweet spot for your specific content ## Performance Considerations ### HALD CLUT Method (Default - Recommended) Processing time with **ffmpeg-haldclut** is very fast and depends primarily on: - **Video length**: Scales linearly with duration - **Resolution**: Higher resolutions take longer but still much faster than frame-by-frame - **Color algorithm**: All algorithms are fast since color matching happens once on the HALD CLUT Approximate processing time for 1080p video at 30fps on modern hardware: - 5 seconds: ~2-5 seconds - 10 seconds: ~3-8 seconds - 30 seconds: ~10-20 seconds - 60 seconds: ~20-40 seconds ### Frame-by-Frame Method (Legacy) Processing time with **frame-by-frame** depends on: - **Video length**: Longer videos take proportionally longer to process - **Resolution**: Higher resolution videos (4K vs 1080p) take significantly longer - **Frame rate**: Higher frame rates mean more frames to process - **Color algorithm**: Compound algorithms (hm-mvgd-hm, hm-mkl-hm) take longer than simple algorithms (mkl, hm) Approximate processing time for 1080p video at 30fps on modern hardware: - 5 seconds (150 frames): ~1-2 minutes - 10 seconds (300 frames): ~3-5 minutes - 30 seconds (900 frames): ~8-12 minutes ## Related Nodes - **Color Match** (Image category) - Single image color transfer, same algorithms - **Adjust Video EQ** (Video category) - Manual brightness/contrast/saturation adjustments - **Add Color Curves** (Video category) - Apply preset color grading curves to video ## Use Case Examples ### Cinematic Grading - **Reference**: Movie still with teal/orange color grading - **Algorithm**: hm-mvgd-hm - **Strength**: 0.9 - **Result**: Professional cinematic look with complementary color palette ### Vintage Film Effect - **Reference**: 1970s photograph with warm, faded colors - **Algorithm**: reinhard - **Strength**: 0.7 - **Result**: Nostalgic vintage aesthetic with authentic film look ### Day-to-Night Conversion - **Reference**: Nighttime city photo with cool blue tones - **Algorithm**: mkl - **Strength**: 1.2 - **Result**: Daytime video with nighttime color palette and mood ### Consistent Multi-Clip Grading - **Reference**: Single color-graded frame from your best clip - **Algorithm**: mkl - **Strength**: 1.0 - **Result**: All clips match the color palette for seamless editing # Advanced Media Library Advanced media generation and manipulation nodes that run models **locally** — diffusion pipelines, image and video pre-processing (depth, edge, pose, segmentation), LoRA tooling, and face detection. - **Repository**: [griptape-ai/griptape-nodes-library-advanced-media](https://github.com/griptape-ai/griptape-nodes-library-advanced-media) - **Requirements**: a GPU is strongly recommended; a Hugging Face account and access token for model downloads (see [Hugging Face Models](https://docs.griptapenodes.com/guides/integrations/hugging_face/index.md)) - **Node categories**: audio, diffusion, image, video, LoRA, and utils in the node picker ## Installation The Advanced Media Library is offered during `gtn init` — you can register it then, or re-run `gtn init` later and answer `y`. See the [FAQ](https://docs.griptapenodes.com/faq/#how-do-i-install-the-advanced-media-library-after-initial-setup) for that path. Alternatively, install it like any other library: in the editor, open **Manage → Library Management**, click **Add Library**, and paste: ``` https://github.com/griptape-ai/griptape-nodes-library-advanced-media ``` See the [Libraries guide](https://docs.griptapenodes.com/guides/libraries/index.md) for general install, update, and troubleshooting help. ## Node reference The library provides 28 nodes. The ones with reference pages so far: - [Diffusion Pipelines](https://docs.griptapenodes.com/nodes/advanced_media_library/diffusion_pipelines/index.md) — build and cache 🤗 Diffusers pipelines, then generate images with them - [YOLOv8 Face Detection](https://docs.griptapenodes.com/nodes/advanced_media_library/yolov8_face_detection/index.md) — detect faces and emit bounding boxes/masks Using alongside the Diffusers Library The Advanced Media Library and the [Diffusers Library](https://docs.griptapenodes.com/libraries/diffusers/index.md) share upstream dependencies (`diffusers`, `transformers`, `torch`) and each maintains its own in-memory pipeline cache. Using both in the same session can hold two copies of overlapping model weights in VRAM/RAM at once. Prefer one library per session. ## Support Found a bug or have a feature request? [Open an issue](https://github.com/griptape-ai/griptape-nodes-library-advanced-media/issues). # OpenColorIO Library Professional color management nodes built on [OpenColorIO](https://opencolorio.org/) (OCIO) — the industry-standard color management system used across film, VFX, and animation pipelines. - **Repository**: [griptape-ai/griptape-nodes-library-opencolorio](https://github.com/griptape-ai/griptape-nodes-library-opencolorio) - **Requirements**: an OCIO config file (`$OCIO` environment variable or an explicit path) - **Node category**: `Colorspace` in the node picker ## Installation In the editor, open **Manage → Library Management**, click **Add Library**, and paste: ``` https://github.com/griptape-ai/griptape-nodes-library-opencolorio ``` Or via the CLI: ``` gtn libraries download https://github.com/griptape-ai/griptape-nodes-library-opencolorio ``` See the [Libraries guide](https://docs.griptapenodes.com/guides/libraries/index.md) for general install, update, and troubleshooting help. ## Prerequisites: the `$OCIO` environment variable This library uses the `$OCIO` environment variable as the primary way to identify your color configuration. Set it in your environment before launching Griptape Nodes: ``` export OCIO=/path/to/your/config.ocio ``` Most studio workstations and render environments will have `$OCIO` set already. If you are working with a Griptape [project](https://docs.griptapenodes.com/guides/projects/index.md), you can set it per-project in `project.yml`: ``` environment: OCIO: "{project_dir}/config/aces.ocio" ``` The `$OCIO` value is re-read live at each node execution, so switching projects automatically picks up the new config without reloading the workflow. The library registers `OCIO` as a known environment variable, so you can also inspect or set it from the Griptape Nodes settings UI without leaving the application. ## Nodes | Node | Description | | ------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------- | | [Load OCIO Config](https://docs.griptapenodes.com/libraries/opencolorio/load_ocio_config/index.md) | Loads an OCIO config from `$OCIO` or an explicit path; emits an `OCIOConfigArtifact` for downstream nodes | | [OCIO Color Parameters](https://docs.griptapenodes.com/libraries/opencolorio/ocio_color_parameters/index.md) | Bundles source colorspace, display, and view into a reusable `OCIOColorParamsArtifact`; dropdowns populate from a connected OCIO config | ## Quick start 1. Add a **Load OCIO Config** node to your canvas. - If `$OCIO` is set, the node detects and displays the path automatically. 1. Wire the `config` output to an **OCIO Color Parameters** node and pick your source colorspace, display, and view. 1. Wire the `color_params` output to any node that accepts an `OCIOColorParamsArtifact` — for example the [Display EXR Part](https://docs.griptapenodes.com/libraries/openexr/display_exr_part/index.md) and [Display EXR Channel](https://docs.griptapenodes.com/libraries/openexr/display_exr_channel/index.md) nodes from the [OpenEXR Library](https://docs.griptapenodes.com/libraries/openexr/index.md). ``` Load OCIO Config → OCIO Color Parameters → (downstream transform/display nodes) ``` ## Used by other libraries Besides its picker nodes, this library provides a colorspace-transform service that other libraries can call. The [OpenEXR Library](https://docs.griptapenodes.com/libraries/openexr/index.md) detects it automatically: when the OpenColorIO Library is installed, the EXR display nodes default to OCIO-managed color (`color_mode: ocio`) instead of local tone mapping. ## Support Found a bug or have a feature request? [Open an issue](https://github.com/griptape-ai/griptape-nodes-library-opencolorio/issues). # Load OCIO Config **Loads an OpenColorIO config from the `$OCIO` environment variable or an explicit override path and emits it as an `OCIOConfigArtifact` for downstream nodes.** Category: `Colorspace` ## TL;DR - If `$OCIO` is set in your environment, the node detects it automatically — just add the node and run. - `$OCIO` is re-read live at every execution, so Griptape project-level environment overrides (`project.yml` `environment:`) are always honored. - Output is an `OCIOConfigArtifact` — wire it into [OCIO Color Parameters](https://docs.griptapenodes.com/libraries/opencolorio/ocio_color_parameters/index.md) or any node with an `OCIO Config` input. - The collapsed **Advanced** group lets you override `$OCIO` with an explicit `.ocio` file path for testing. ## Typical workflow position ``` [Load OCIO Config] → OCIO Color Parameters → (downstream transform/display nodes) ``` ## Node preview ## Inputs | Name | Type | Required | Notes | | -------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------ | | `context_vars` | `dict` | No | OCIO context variables, e.g. `{"SHOT": "sh010", "SEQ": "sq020"}`. Carried on the output artifact. | | `file_path` | `str` | No | Path to an `.ocio` config file. Only used when **Override OCIO Config** is enabled. Has a file picker. | ## Outputs | Name | Type | Notes | | ---------------- | -------------------- | ------------------------------------------------------- | | `config` | `OCIOConfigArtifact` | Carries the resolved config path and context variables. | | `was_successful` | `bool` | Whether the config loaded successfully. | | `result_details` | `str` | Load result details (collapsed status group). | ## Parameters ### Advanced *(collapsed by default)* | Name | Type | Default | Notes | | ---------------------- | --------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------- | | `override_ocio_config` | bool | `False` | When enabled, uses the explicit `file_path` below instead of `$OCIO`. Useful for testing a specific config without changing your environment. | | `file_path` | file path | `""` | Revealed when the override toggle is on. | ## Tips & pitfalls - **Watch the inline messages.** The node shows an info message with the detected `$OCIO` path, a warning when `$OCIO` is missing, and a warning when `$OCIO` changed since the last run (re-run the node to refresh downstream outputs). - **Override mode replaces `$OCIO` entirely.** While **Override OCIO Config** is enabled, the environment variable is ignored; disable the toggle to return to environment-variable mode. - **No `$OCIO` and no override means the node fails.** Set one or the other; per-project configs are cleanest via `project.yml` `environment:`. ## See also - [OCIO Color Parameters](https://docs.griptapenodes.com/libraries/opencolorio/ocio_color_parameters/index.md) — the most common consumer of the `config` output. # OCIO Color Parameters **Bundles source colorspace, display, and view into a single reusable `OCIOColorParamsArtifact` for downstream color-managed nodes.** Category: `Colorspace` ## TL;DR - Wire a `config` from [Load OCIO Config](https://docs.griptapenodes.com/libraries/opencolorio/load_ocio_config/index.md) and the three dropdowns populate from that config. - **Source Colorspace** lists role aliases first (e.g. `scene_linear`, `compositing_log`) so stable, pipeline-safe names appear at the top. - Output is an `OCIOColorParamsArtifact` — one node can drive multiple downstream consumers to keep the selection in one place. - Selections are re-validated against the live config on every run; mismatches produce an inline warning but do not fail execution. ## Typical workflow position ``` Load OCIO Config → [OCIO Color Parameters] → (downstream transform/display nodes) ``` ## Node preview ## Inputs | Name | Type | Required | Notes | | ------------------- | -------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `config` | `OCIOConfigArtifact` | No | From [Load OCIO Config](https://docs.griptapenodes.com/libraries/opencolorio/load_ocio_config/index.md). Enables dropdown population and validation. Without it, dropdowns show a placeholder and the node emits empty strings. | | `source_colorspace` | `str` (choice) | No | Source colorspace name from the connected config. Roles listed first, then all colorspace names. | | `display` | `str` (choice) | No | Display device name from the connected config. | | `view` | `str` (choice) | No | View name for the selected display. Choices update automatically when `display` changes. | ## Outputs | Name | Type | Notes | | ---------------- | ------------------------- | ----------------------------------------------------- | | `color_params` | `OCIOColorParamsArtifact` | Carries `source_colorspace`, `display`, and `view`. | | `was_successful` | `bool` | Whether the bundle was emitted cleanly. | | `result_details` | `str` | Validation and emit details (collapsed status group). | ## Tips & pitfalls - **Prefer role names for source colorspace.** Roles like `scene_linear` survive config changes; raw colorspace names may not exist in the next config revision. - **Warnings don't block execution.** If a selected value is no longer present in the live config (config changed, or a value was wired in via INPUT), the node emits the artifact as-is and flags the mismatch with an inline warning — check it before trusting downstream renders. - **Reuse one node across consumers.** Wire `color_params` to every downstream transform/display node instead of duplicating dropdown selections per node. - **No config connected is fine for scaffolding.** You can lay out the workflow before a config is available; the node emits empty strings until one is connected. ## See also - [Load OCIO Config](https://docs.griptapenodes.com/libraries/opencolorio/load_ocio_config/index.md) — provides the `config` input. # OpenEXR Library Professional [OpenEXR](https://openexr.com/) support for VFX and HDR pipelines. Load EXR files and inspect their structure, display individual parts or manually-assembled channels as tone-mapped images, and save images or channel data back to EXR. - **Repository**: [griptape-ai/griptape-nodes-library-openexr](https://github.com/griptape-ai/griptape-nodes-library-openexr) - **Requirements**: none (optionally pairs with the [OpenColorIO Library](https://docs.griptapenodes.com/libraries/opencolorio/index.md) for color-managed display) - **Node category**: `EXR` in the node picker ## Installation In the editor, open **Manage → Library Management**, click **Add Library**, and paste: ``` https://github.com/griptape-ai/griptape-nodes-library-openexr ``` Or via the CLI: ``` gtn libraries download https://github.com/griptape-ai/griptape-nodes-library-openexr ``` See the [Libraries guide](https://docs.griptapenodes.com/guides/libraries/index.md) for general install, update, and troubleshooting help. ## Nodes | Node | Description | | ---------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | [Load EXR](https://docs.griptapenodes.com/libraries/openexr/load_exr/index.md) | Loads an EXR file and exposes its full structure (parts, channels, header attributes) as typed outputs — header-only by default, so it's fast even on large multi-part renders | | [Display EXR Part](https://docs.griptapenodes.com/libraries/openexr/display_exr_part/index.md) | Renders an EXR part to an 8-bit sRGB/RGBA PNG with exposure and tone mapping (or OCIO color management) | | [Display EXR Channel](https://docs.griptapenodes.com/libraries/openexr/display_exr_channel/index.md) | Combines 1–4 individual EXR channels into an 8-bit display image, with optional alpha compositing over a background | | [Save EXR](https://docs.griptapenodes.com/libraries/openexr/save_exr/index.md) | Saves a single-part EXR from an 8-bit image or from EXR channel artifacts, with compression, pixel type, and header metadata controls | ## Quick start ``` Load EXR → Display EXR Part → (image output) ↘ Display EXR Channel (assemble R/G/B/A from any parts or files) ``` 1. Add a **Load EXR** node and point `file_path` at a `.exr` file. The node scans the header and populates dynamic **Parts** and **Channels** groups. 1. Wire a part output into **Display EXR Part** (or individual channels into **Display EXR Channel**) to get a tone-mapped PNG you can preview in the canvas or feed to other image nodes. 1. Use **Save EXR** to write images or channel data back out as EXR. ## OpenColorIO integration The display nodes support two color modes: - **`basic`** — local exposure + tone mapping (`filmic` or `linear`). - **`ocio`** — a connected `OCIOColorParamsArtifact` (from the [OpenColorIO Library](https://docs.griptapenodes.com/libraries/opencolorio/index.md)) drives an OCIO display-view transform. When the OpenColorIO Library is installed, the display nodes default to `ocio` mode automatically; otherwise they default to `basic`. The OCIO dependency is optional — everything works without it. ## Library settings Settings live in the engine configuration under the `openexr` category. | Setting | Default | Description | | --------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `openexr.header_only` | `true` | When `true`, **Load EXR** reads only the file header (fast). Set to `false` to read accurate per-channel pixel types at the cost of loading pixel data into memory. | | `openexr.viewer_executable` | `""` | Full path to an external HDR viewer executable (e.g. `/usr/bin/djv`, or a Nuke binary). When empty, the OS default file association is used. | | `openexr.viewer_args` | `""` | Additional command-line arguments passed to the viewer before the file path, e.g. `--hdr --linear`. Parsed with shell-style quoting rules. | Every node has an **Open in external viewer** button that opens the source EXR in the configured viewer (or the OS default). The viewer is launched fire-and-forget — clicking the button never blocks the canvas. ## Support Found a bug or have a feature request? [Open an issue](https://github.com/griptape-ai/griptape-nodes-library-openexr/issues). # Display EXR Channel **Combines 1–4 individual EXR channels into an 8-bit sRGB or RGBA PNG, with optional alpha compositing over a background image.** Category: `EXR` ## TL;DR - Each slot (R, G, B, A) accepts an `EXRChannelArtifact` and is optional — channels can come from different EXR files or different parts. - Missing RGB slots are zero-filled; connecting the A slot produces RGBA output with optional background compositing. - At least one RGB slot must be connected, and all connected channels must share the same pixel dimensions. - Same color management as [Display EXR Part](https://docs.griptapenodes.com/libraries/openexr/display_exr_part/index.md): `basic` tone mapping or `ocio` via a connected `OCIOColorParamsArtifact`. ## Typical workflow position ``` Load EXR (channels) → [Display EXR Channel] → (image output) ``` ## Node preview ## Inputs | Name | Type | Required | Notes | | -------------- | ------------------------------------------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | | `channel_r` | `EXRChannelArtifact` | No | Channel mapped to the red plane. | | `channel_g` | `EXRChannelArtifact` | No | Channel mapped to the green plane. | | `channel_b` | `EXRChannelArtifact` | No | Channel mapped to the blue plane. | | `channel_a` | `EXRChannelArtifact` | No | Channel used as alpha. When connected, output is RGBA. | | `exposure` | `float` | No | Exposure in EV stops applied before tone mapping or the OCIO transform. Range −10 to +10, default `0.0`. | | `color_params` | `OCIOColorParamsArtifact` | No | From [OCIO Color Parameters](https://docs.griptapenodes.com/libraries/opencolorio/ocio_color_parameters/index.md). Required when `color_mode` is `ocio`. | | `background` | `ImageArtifact \| ImageUrlArtifact \| str` | No | Background image for A-over-B compositing. Ignored when no alpha channel is connected. | ## Outputs | Name | Type | Notes | | ------------- | ------------------ | --------------------------------------------- | | `image` | `ImageUrlArtifact` | 8-bit sRGB or RGBA PNG for in-canvas display. | | `output_file` | `str` | Path to the saved PNG file. | ## Parameters | Name | Type | Default | Notes | | -------------- | ------------------ | ------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------- | | `color_mode` | `basic \| ocio` | `ocio` if the OpenColorIO Library is installed, else `basic` | `basic` uses local tone mapping; `ocio` uses the connected `OCIOColorParamsArtifact`. | | `tone_mapping` | `filmic \| linear` | `filmic` | Local tone mapping when `color_mode` is `basic`. `filmic` applies the Narkowicz 2015 curve; `linear` clamps to [0, 1]. Hidden in `ocio` mode. | ## Tips & pitfalls - **Connect at least one RGB slot.** The node fails with only an alpha channel connected. - **Dimensions must match across all connected channels.** Mixing channels from different resolutions fails — rescale upstream first. - **Great for inspecting AOVs.** Wire a single AOV channel (e.g. `depth` or `normal.x`) into `channel_r` to visualize it in grayscale-red, or assemble `normal.x/y/z` into RGB. - **Background compositing needs alpha.** The `background` input is silently ignored unless `channel_a` is connected. ## See also - [Load EXR](https://docs.griptapenodes.com/libraries/openexr/load_exr/index.md) — provides the channel inputs. - [Display EXR Part](https://docs.griptapenodes.com/libraries/openexr/display_exr_part/index.md) — render a whole part with automatic channel selection instead. - [Save EXR](https://docs.griptapenodes.com/libraries/openexr/save_exr/index.md) — write assembled channels back to EXR. # Display EXR Part **Renders an EXR part to an 8-bit sRGB (or RGBA) PNG with exposure and tone mapping or OCIO color management.** Category: `EXR` ## TL;DR - Accepts an `EXRPartArtifact` from [Load EXR](https://docs.griptapenodes.com/libraries/openexr/load_exr/index.md), auto-selects RGB display channels, and writes an `ImageUrlArtifact` you can preview in the canvas. - Two color modes: `basic` (local exposure + filmic/linear tone mapping) and `ocio` (a connected `OCIOColorParamsArtifact` drives an OCIO display-view transform). - Alpha is included automatically when an alpha channel (`A`) is found in the part. - An **Open in external viewer** button opens the source EXR in a configured HDR viewer after the node has run (see [Library settings](https://docs.griptapenodes.com/libraries/openexr/#library-settings)). ## Typical workflow position ``` Load EXR → [Display EXR Part] → (image output) ``` ## Node preview ## Inputs | Name | Type | Required | Notes | | -------------- | ------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | | `part` | `EXRPartArtifact` | Yes | EXR part to render. Assumes scene-linear HDR data; no gamut conversion is applied. | | `exposure` | `float` | No | Exposure in EV stops applied before tone mapping or the OCIO transform. Range −10 to +10, default `0.0`. | | `color_params` | `OCIOColorParamsArtifact` | No | From [OCIO Color Parameters](https://docs.griptapenodes.com/libraries/opencolorio/ocio_color_parameters/index.md). Required when `color_mode` is `ocio`. | ## Outputs | Name | Type | Notes | | ------------- | ------------------ | --------------------------------------------- | | `image` | `ImageUrlArtifact` | 8-bit sRGB or RGBA PNG for in-canvas display. | | `output_file` | `str` | Path to the saved PNG file. | ## Parameters | Name | Type | Default | Notes | | -------------- | ------------------ | ------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------- | | `color_mode` | `basic \| ocio` | `ocio` if the OpenColorIO Library is installed, else `basic` | `basic` uses local tone mapping; `ocio` uses the connected `OCIOColorParamsArtifact`. | | `tone_mapping` | `filmic \| linear` | `filmic` | Local tone mapping when `color_mode` is `basic`. `filmic` applies the Narkowicz 2015 curve; `linear` clamps to [0, 1]. Hidden in `ocio` mode. | ## Tips & pitfalls - **OCIO mode requires a connected `color_params`.** Without one, the node fails loudly — switch `color_mode` to `basic` if you don't have an OCIO config. - **Data is assumed scene-linear.** No input gamut conversion is applied; if your EXR stores display-referred or log data, use OCIO mode with the correct source colorspace. - **OCIO failures are surfaced loudly.** A broken config or invalid display/view fails the node rather than silently falling back to basic tone mapping. ## See also - [Load EXR](https://docs.griptapenodes.com/libraries/openexr/load_exr/index.md) — provides the `part` input. - [Display EXR Channel](https://docs.griptapenodes.com/libraries/openexr/display_exr_channel/index.md) — assemble a display image from individual channels instead. - [OCIO Color Parameters](https://docs.griptapenodes.com/libraries/opencolorio/ocio_color_parameters/index.md) — provides the `color_params` input. # Load EXR **Loads an EXR file and exposes its full structure — parts, channels, and header attributes — as typed outputs, without reading pixels by default.** Category: `EXR` ## TL;DR - Header-only by default (`openexr.header_only` setting), so scanning is fast even on large multi-part renders. - Populates dynamic **Parts** and **Channels** groups: one `EXRPartArtifact` per part and one `EXRChannelArtifact` per raw channel. - Wire parts into [Display EXR Part](https://docs.griptapenodes.com/libraries/openexr/display_exr_part/index.md) and channels into [Display EXR Channel](https://docs.griptapenodes.com/libraries/openexr/display_exr_channel/index.md) or [Save EXR](https://docs.griptapenodes.com/libraries/openexr/save_exr/index.md). - An **Open in external viewer** button opens the file in a configured HDR viewer (see [Library settings](https://docs.griptapenodes.com/libraries/openexr/#library-settings)). ## Typical workflow position ``` [Load EXR] → Display EXR Part → (image output) ↘ Display EXR Channel / Save EXR ``` ## Node preview ## Inputs | Name | Type | Required | Notes | | ----------- | ----- | -------- | ------------------------ | | `file_path` | `str` | Yes | Path to the `.exr` file. | ## Outputs | Name | Type | Notes | | -------------------------------- | ----------------------- | ----------------------------------------------------------- | | `image_width` / `image_height` | `int` | Dimensions from the data window. | | `part_count` / `channel_count` | `int` | Counts. | | `compression` | `str` | e.g. `ZIP_COMPRESSION`, `DWAB_COMPRESSION`. | | `storage_type` | `str` | `scanlineimage`, `tiledimage`, `deepscanline`, `deeptiled`. | | `pixel_aspect_ratio` | `float` | Pixel width/height ratio. | | `data_window` / `display_window` | `str` | `"xmin,ymin - xmax,ymax"`. | | `time_code` | `str` | `HH:MM:SS:FF`, empty if absent. | | `software` | `str` | Authoring application, empty if absent. | | `owner` | `str` | Asset owner, empty if absent. | | `chromaticities` | `str` | JSON with `red_x/y`, `green_x/y`, `blue_x/y`, `white_x/y`. | | `custom_attributes` | `str` | JSON of all non-standard header attributes. | | `parts` | `list[EXRPartArtifact]` | Structured descriptor for every part in the file. | Dynamic groups are also populated after the scan: - **Parts** — one `EXRPartArtifact` output per part, each with its own channel outputs (hidden for single-part files; channels appear directly in the Channels group instead). - **Channels** — one `EXRChannelArtifact` per raw channel with name, pixel type, and sampling (single-part files only). ## Tips & pitfalls - **Pixel types may read as approximate in header-only mode.** Set the `openexr.header_only` setting to `false` if you need accurate per-channel pixel types; the cost is loading pixel data into memory at scan time. - **Multi-part files hide the flat Channels group.** For multi-part renders, per-channel outputs live inside each part's group instead. - **Downstream nodes load pixels lazily.** The display and save nodes read pixel data themselves from the descriptor artifacts, so a header-only scan doesn't limit what you can do downstream. ## See also - [Display EXR Part](https://docs.griptapenodes.com/libraries/openexr/display_exr_part/index.md) · [Display EXR Channel](https://docs.griptapenodes.com/libraries/openexr/display_exr_channel/index.md) — render parts/channels to viewable images. - [Save EXR](https://docs.griptapenodes.com/libraries/openexr/save_exr/index.md) — write channels back out. # Save EXR **Saves a single-part EXR file from either an 8-bit image or EXR channel artifacts, preserving float precision in channel mode.** Category: `EXR` ## TL;DR - Two modes: **Mode A** takes an `ImageArtifact`/`ImageUrlArtifact` (8-bit) and normalizes [0, 255] to [0.0, 1.0]; **Mode B** takes up to four `EXRChannelArtifact` slots and preserves float precision. - Mode B takes priority: `image_in` is ignored whenever any channel slot is connected. - Output includes an `EXRPartArtifact` descriptor for the written part, so you can chain straight into [Display EXR Part](https://docs.griptapenodes.com/libraries/openexr/display_exr_part/index.md). - A collapsed **Metadata** group exposes optional header fields (owner, timecode, comments, custom attributes, ...). ## Typical workflow position ``` (image node) → [Save EXR] → Display EXR Part / Load EXR Load EXR (channels) → [Save EXR] ``` ## Node preview ## Inputs | Name | Type | Required | Notes | | ----------- | ----------------------------------- | -------- | ------------------------------------------------------------------------ | | `image_in` | `ImageArtifact \| ImageUrlArtifact` | No | 8-bit image source (Mode A). Ignored when any channel slot is connected. | | `channel_r` | `EXRChannelArtifact` | No | Channel to write as R (Mode B). | | `channel_g` | `EXRChannelArtifact` | No | Channel to write as G (Mode B). | | `channel_b` | `EXRChannelArtifact` | No | Channel to write as B (Mode B). | | `channel_a` | `EXRChannelArtifact` | No | Channel to write as A (Mode B). | ## Outputs | Name | Type | Notes | | ------------- | ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `output_file` | `str` | Path to the written `.exr` file. | | `output_part` | `EXRPartArtifact` | Descriptor for the written part — connectable to [Display EXR Part](https://docs.griptapenodes.com/libraries/openexr/display_exr_part/index.md) or [Load EXR](https://docs.griptapenodes.com/libraries/openexr/load_exr/index.md) consumers. | ## Parameters | Name | Type | Default | Notes | | ------------- | ------------------------------------ | ------- | -------------------------------------------------------------------- | | `compression` | `ZIP \| ZIPS \| PIZ \| DWAA \| NONE` | `ZIP` | Codec applied to the output file. | | `pixel_type` | `HALF \| FLOAT` | `HALF` | `HALF` is 16-bit float (standard); `FLOAT` is 32-bit full precision. | ### Metadata *(collapsed by default)* | Name | Type | Default | Notes | | -------------------- | ------- | ------- | ------------------------------------------------ | | `part_name` | `str` | `""` | Name for this EXR part. | | `pixel_aspect_ratio` | `float` | `1.0` | Pixel width/height ratio. | | `owner` | `str` | `""` | Asset owner. | | `comments` | `str` | `""` | Free-text comments. | | `capture_date` | `str` | `""` | Capture date (e.g. `2025-01-01T12:00:00`). | | `software` | `str` | `""` | Authoring application name. | | `time_code` | `str` | `""` | Editorial timecode (`HH:MM:SS:FF`). | | `custom_attributes` | `str` | `""` | Non-standard header attributes as a JSON object. | ## Tips & pitfalls - **Mode B wins.** If you meant to save `image_in` but a channel slot is still wired, the image is silently ignored — disconnect the channel slots. - **8-bit sources don't become HDR.** Mode A normalizes display-referred 8-bit data into [0.0, 1.0]; it does not recover highlights. Use Mode B with float channels for real HDR data. - **Pick `DWAA` for big beauty renders.** It's lossy but dramatically smaller; keep `ZIP`/`PIZ` for data passes (depth, normals) where exact values matter. - **`FLOAT` doubles file size vs `HALF`.** Only use 32-bit when the extra precision is actually needed (e.g. depth or position passes). ## See also - [Load EXR](https://docs.griptapenodes.com/libraries/openexr/load_exr/index.md) — provides channel inputs and re-reads written files. - [Display EXR Part](https://docs.griptapenodes.com/libraries/openexr/display_exr_part/index.md) — preview the written part via `output_part`. # Nuke Library Build workflows that run inside [Foundry Nuke](https://www.foundry.com/products/nuke-family). The library provides flow-control nodes for authoring Nuke-targeted workflows, a publisher that packages a workflow as a versioned `.gizmo` you can drop into a Nuke script, and a **Nuke Script** node that runs an existing `.nk` script headlessly and surfaces its annotated nodes as typed ports on the Griptape canvas. - **Repository**: [griptape-ai/griptape-nodes-library-nuke](https://github.com/griptape-ai/griptape-nodes-library-nuke) - **Requirements**: a local Foundry Nuke installation and license - **Node category**: `Foundry Nuke` in the node picker ## Installation In the editor, open **Manage → Library Management**, click **Add Library**, and paste: ``` https://github.com/griptape-ai/griptape-nodes-library-nuke ``` Or via the CLI: ``` gtn libraries download https://github.com/griptape-ai/griptape-nodes-library-nuke ``` See the [Libraries guide](https://docs.griptapenodes.com/guides/libraries/index.md) for general install, update, and troubleshooting help. ## Nodes | Node | Description | | ----------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | | [Nuke Start Flow](https://docs.griptapenodes.com/libraries/nuke/nuke_start_flow/index.md) | Entry point for a Nuke-targeted workflow — required to publish the workflow as a gizmo | | [Nuke End Flow](https://docs.griptapenodes.com/libraries/nuke/nuke_end_flow/index.md) | Terminal node for a Nuke-targeted workflow; exposes `was_successful` and `result_details` | | [Nuke Script](https://docs.griptapenodes.com/libraries/nuke/nuke_script/index.md) | Runs a `.nk` script headlessly via `nuke -t`, surfacing annotated Read/Write nodes as typed input/output ports | ## Configuring Nuke installations ### Auto-discovery The library scans standard install locations on startup and populates the **Nuke Version** dropdown automatically: | OS | Locations scanned | | ------- | --------------------------------------------------- | | macOS | `/Applications/Nuke*` | | Windows | `%ProgramFiles%\Nuke*`, `%ProgramFiles(x86)%\Nuke*` | | Linux | `/usr/local/Nuke*`, `/opt/Nuke*`, `~/Nuke*` | It also checks `PATH` for executables named `Nuke` or `nuke`. Click **Refresh UI** on the node to re-run discovery after installing a new version. ### Manual configuration via Engine Settings For installs in non-standard locations, add entries to the `nuke.installations` key in Engine Settings: ``` { "nuke": { "installations": [ { "display_name": "Nuke 16.0v7", "executable_path": "/opt/nuke/16.0v7/Nuke16.0", "annotator_nuke_version": 16 } ] } } ``` | Field | Required | Description | | ------------------------ | -------- | ---------------------------------------------------------------------------------- | | `display_name` | yes | Label shown in the **Nuke Version** dropdown | | `executable_path` | yes | Absolute path to the Nuke binary | | `annotator_nuke_version` | no | Nuke major version; controls which Annotator panel build is loaded (default: `16`) | | `env_overrides` | no | Extra environment variables merged into the Nuke subprocess | | `notes` | no | Free-text notes; not used at runtime | ### Additional engine settings | Key | Description | | ----------------- | ---------------------------------------------------------------------- | | `nuke.executable` | Fallback Nuke binary path used when no installation entry is selected | | `nuke.env` | Global environment variables injected into every Nuke subprocess | | `nuke.nuke_path` | List of extra directories appended to `NUKE_PATH` for every invocation | ### Foundry license Set `foundry_LICENSE` in the Griptape Secrets panel. It is injected into the Nuke subprocess automatically — do not hardcode it in `env_overrides`. ## Publishing a workflow as a Nuke gizmo 1. Build a workflow whose top-level flow starts with a [Nuke Start Flow](https://docs.griptapenodes.com/libraries/nuke/nuke_start_flow/index.md) node and ends with a [Nuke End Flow](https://docs.griptapenodes.com/libraries/nuke/nuke_end_flow/index.md) node. 1. Trigger **Publish Workflow** (see [Publishing Workflows](https://docs.griptapenodes.com/guides/publishing/index.md)). In the dialog, pick: - A **Nuke install** (auto-detected) to resolve plugin path candidates. - A **gizmo install path** — either `~/.nuke`, a path from `NUKE_PATH`, a Nuke-install plugins directory, or a custom path. - An **update mode** to pick between creating a new version and overwriting the current one. 1. Inside Nuke, use the `Griptape` menu on the Nodes toolbar to create the gizmo, or run `Griptape > Refresh Griptape Gizmos` from the main menu bar after publishing to pick up new versions without restarting Nuke. Publishing writes a versioned `.gizmo` plus a runner script under the chosen install directory, and a `menu.py` that adds a `Griptape` submenu to Nuke's Nodes toolbar. Multiple published versions of the same workflow are grouped under a per-workflow submenu. Workflow outputs land next to the `.nk` file (under `griptape_outputs//...`). ## Support Found a bug or have a feature request? [Open an issue](https://github.com/griptape-ai/griptape-nodes-library-nuke/issues). # Nuke End Flow **Terminal node for a Nuke-targeted workflow; reports whether the flow succeeded.** Category: `Foundry Nuke` ## TL;DR - Place this at the end of the top-level flow of any workflow you intend to publish as a `.gizmo`. - Exposes `was_successful` and `result_details` so the published gizmo can report run status back inside Nuke. - Pair with [Nuke Start Flow](https://docs.griptapenodes.com/libraries/nuke/nuke_start_flow/index.md) at the other end of the flow. ## Typical workflow position ``` Nuke Start Flow → (workflow nodes) → [Nuke End Flow] ``` ## Inputs | Name | Type | Required | Notes | | --------- | ------- | -------- | -------------------------------------------------- | | `exec_in` | control | Yes | Control connection from the last node in the flow. | ## Outputs | Name | Type | Notes | | ---------------- | ------ | ---------------------------------------- | | `was_successful` | `bool` | Whether the flow completed successfully. | | `result_details` | `str` | Details about the flow result. | ## Tips & pitfalls - **Required for gizmo publishing.** The publisher validates that the top-level flow starts with a [Nuke Start Flow](https://docs.griptapenodes.com/libraries/nuke/nuke_start_flow/index.md) and ends with a Nuke End Flow; a plain End Flow will not validate. ## See also - [Nuke Start Flow](https://docs.griptapenodes.com/libraries/nuke/nuke_start_flow/index.md) — the matching entry node. - [Nuke Script](https://docs.griptapenodes.com/libraries/nuke/nuke_script/index.md) — run a `.nk` script inside the flow. # Nuke Script **Runs an existing `.nk` script headlessly via `nuke -t`, surfacing annotated Read/Write nodes as typed input/output ports on the Griptape canvas.** Category: `Foundry Nuke` ## TL;DR - Point `script_path` at a `.nk` file, annotate its I/O nodes with the Griptape Annotator panel inside Nuke, and the node grows typed ports matching those annotations. - Inputs accept images, video, or raw file paths; outputs can be images, image sequences, video, or 3D geometry. - **Open in Nuke** launches the script in the Nuke GUI with the Annotator panel loaded; **Refresh UI** re-reads annotations and re-runs installation discovery. - Annotations live in a `