Installs into .claude/skills of the current project.
Are you the author of Nodetool Workflow Builder?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/nodetool-ai-nodetool-workflow-builder)
---
name: nodetool-workflow-builder
description: "Create or edit NodeTool workflow graphs, connections, and properties using UI, headless tools, or requested workflow files."
featured: true
---
# Build NodeTool workflows
Create or edit a workflow that produces the requested outputs from the specified
inputs. Preserve existing nodes and connections outside the requested change.
## A graph is one surface among several
NodeTool holds several document kinds, and most of them are not graphs. Build a
graph when the user asked for one, when the job has to re-run on new inputs, or
when the result must be callable over the API. Otherwise route the request.
| The deliverable | Skill |
|---|---|
| A finished video, still set or campaign | `storyboard-core`, which picks the job skill |
| A screen someone clicks | `nodetool-app-builder` |
| A repair on footage already cut: cutout, upscale, lip sync, generated sound | `nodetool-video-post` |
| A cut, titles, transitions or animation on a timeline | the `motion-graphics` system skill, via `load_skill` |
| Logic in JavaScript rather than nodes | `nodetool-js-scripting` |
| A layered image, mask or overlay | `nodetool-sketch` |
| A 3D model or scene | `nodetool-3d-scene` |
| A reusable video template for batches | `video-workflow` |
| A new node type | `nodetool-custom-node-developer` |
A workflow can still be the right answer alongside one of these: a mini app runs
workflows, and a timeline clip can be produced by one.
Use the authoring surface the user requested. Prefer available UI tools for a
visible canvas, headless workflow tools for saved graphs, and JSON or the
TypeScript DSL when a workflow file is requested. Do not claim a missing tool is
callable. Use the live schemas and registry as the source of identifiers.
## Build and verify
1. Inspect the existing graph for edits. Identify required inputs, outputs, and
behavior from the request and existing context.
2. Search required node types with `include_properties=true` and
`include_outputs=true`. Reuse metadata already retrieved for the same type.
Discover model choices through available model-search tools. Do not invent
node types, properties, handles, or model identifiers.
3. Add or update nodes, set required properties, and connect compatible handles.
Keep existing layout and identifiers when editing. Read
**Graph authoring reference** below for the relevant tool, streaming
pattern, or DSL operation before using it.
4. Validate through `ui_get_graph` or `validate_workflow` as appropriate. Fix
errors and required-input warnings. Read back the saved graph before claiming
it was created or changed. Report any remaining limitation.
5. Execute only when the request includes a run. Respect generation budgets and
tool permissions. Report the workflow identifier, inputs, validation result,
and actual run outcome when executed.
If a node is unavailable, broaden the search and inspect supported alternatives.
Proceed with an equivalent composition when it preserves the requested behavior.
Otherwise explain the missing capability and ask only for the decision needed to
choose an alternative. Continue independent work on the rest of the graph.
## Graph authoring reference
Read only the sections needed for the current operation. Live registry metadata
and tool schemas are authoritative. Catalog entries and DSL examples are search
leads, not proof that a node or argument exists in this installation.
## Tool Reference
| Tool | Purpose | Key params |
|------|---------|-----------|
| `ui_search_nodes` | Find node types | `query`, `include_properties=true`, `include_outputs=true`, `limit` |
| `ui_search_models` | Find models for a model property | `query`, `type` (e.g. `language_model`) |
| `ui_add_node` | Add single node | `id`, `position`, `type` (use `node_type` from search) |
| `ui_graph` | Bulk add nodes+edges when exposed by the host | `nodes[]`, `edges[]` |
| `ui_connect_nodes` | Connect two nodes | `source_id`, `source_handle`, `target_id`, `target_handle` |
| `ui_update_node_data` | Set node properties / sync mode | `node_id`, `data={properties: {…}, sync_mode: "on_all"}` |
| `ui_get_graph` | Read graph + validation | Returns nodes, edges, and validation results |
| `ui_delete_node` | Remove a node | `node_id` |
| `ui_delete_edge` | Remove a connection | `edge_id` |
| `ui_move_node` | Reposition a node | `node_id`, `position` |
| `ui_set_node_title` | Rename a node | `node_id`, `title` |
| `ui_open_workflow` | Open workflow tab | `workflow_id` |
| `ui_run_workflow` | Execute workflow | `workflow_id`, `params` |
> Set a node's `sync_mode` through `ui_update_node_data` (`data.sync_mode`) — there
> is no dedicated sync-mode tool.
### Node Data Fields
When using `ui_add_node` or `ui_graph`, the `data` object supports:
- `properties` — node-specific input values (from metadata)
- `dynamic_outputs` — tool outputs for Agent nodes: `{"tool_name": {"type": "str"}}`
- `dynamic_properties` — runtime-configurable properties (usually `{}`)
- `sync_mode` — `"on_any"` (default, fire on any input) or `"on_all"` (wait for all inputs)
## Node Catalog
### Core Namespaces
| Namespace | Key Nodes | Purpose |
|-----------|-----------|---------|
| `nodetool.agents` | Agent, ResearchAgent, Summarizer, Extractor, Classifier, Decision | LLM-powered processing |
| `nodetool.text` | Concat, Join, Replace, Template, Split, Regex, Compare, Slugify | Text manipulation |
| `nodetool.code` | Code (JS sandbox with lodash, dayjs, cheerio, csvParse, validator) | Custom logic via JavaScript |
| `nodetool.data` | Filter, Schema, GroupBy, Sort | Dataframe operations |
| `nodetool.image` | Load, Save, Resize, Crop, Rotate, Composite | Image processing |
| `nodetool.audio` | Load, Mix, Encode | Audio processing |
| `nodetool.video` | Load, Extract, Metadata, Frames | Video processing |
| `nodetool.control` | If, ForEach, Collect, Switch, Loop | Control flow |
| `nodetool.constant` | String, Integer, Float, Bool, Image, Audio | Constant values |
| `nodetool.input` | FloatInput, StringInput, ImageInput, ChatInput | Workflow parameters |
| `nodetool.output` | Output, Preview | Results and debugging |
| `nodetool.generators` | ListGenerator, DataGenerator, ChartGenerator | LLM-backed generators |
### Library Namespaces (`lib.*`)
`lib.pdf` (rasterize pages to images), `lib.sqlite` (database path), `lib.browser` (screenshots), `lib.svg` (vector graphics), `lib.charts` (charts)
> **Note:** `lib.http`, `lib.os`, `lib.datetime`, `lib.json`, `lib.math`, `lib.uuid`, `nodetool.boolean`, `nodetool.dictionary`, `nodetool.list`, `nodetool.numbers`, `nodetool.text.Chunk`, and all `skills.*` nodes have been removed. Use the **Code node** (`nodetool.code.Code`) and its snippet library instead. Its sandbox provides `fetch` for web requests, `workspace.*` for files, and `@nodetool-ai/sandbox-dates` (date-fns) for date logic. `nodetool.control.RepeatCount` and `nodetool.control.RepeatValue` cover repetition.
### External Service Namespaces
| Namespace | Purpose |
|-----------|---------|
| `kie.image.*` | Image generation services (Flux, SDXL, etc.) |
| `kie.video.*` | Video generation services (Kling, Hailuo, Sora, etc.) |
| `kie.audio.*` | Audio generation services |
| `openai.*` | GPT, GPT-Image, embeddings, TTS |
| `gemini.*` | Google Gemini models |
| `mistral.*` | Mistral models |
| `vector.*` | Vector store nodes (SQLite-vec default; Chroma/Pinecone/Supabase backends) |
### Data Types
- **Primitives**: `str`, `int`, `float`, `bool`, `list`, `dict`, `any`
- **Assets**: `{type: "image|audio|video|document", uri: "..."}`
- **Models**: `language_model`, `image_model`, `video_model`, `embedding_model`, `tts_model`
Edges enforce type compatibility. Use `any` type for flexible connections.
### Search Strategy
- Use broad category terms with `limit=20` to see all options.
- Multi-word queries are split and scored independently — `"dataframe group aggregate"` finds multiple related nodes.
- Use `input_type` / `output_type` filters: `"str"`, `"int"`, `"float"`, `"image"`, `"audio"`, `"list"`, etc.
- If no results, broaden the query or try the namespace prefix (e.g., `"nodetool.text"`).
- Type conversions: dataframe→array via `"to_numpy"`, list→item via iterator, item→list via collector.
## Workflow Patterns
### Pattern 1: Simple Pipeline
**Shape**: Input → Transform(s) → Output
**Use for**: Single-source processing, data conversion, image enhancement.
**Example**: ImageInput → Sharpen → AutoContrast → Output
### Pattern 2: Agent-Driven Generation
**Shape**: Input → Agent → Post-process → Output
**Use for**: Creative generation, multimodal transforms (image→text→audio), semantic understanding.
**Key nodes**: Agent (general LLM), Summarizer (text summarization), ListGenerator (streams items)
### Pattern 3: Streaming with Previews
**Shape**: Inputs → Agent (strategy) → ListGenerator → Processing → Preview nodes at each stage
**Use for**: Complex multi-stage generation where user needs progress visibility.
**Key concept**: Add Preview nodes at intermediate stages for debugging and monitoring.
### Pattern 4: RAG (Retrieval-Augmented Generation)
**Shape**: ChatInput → vector.HybridSearch + FormatText → Agent → Output
**Use for**: Question-answering over documents, factual accuracy from specific sources.
**Index flow**: nodetool.code.Code (`workspace.list`) → LoadDocumentFile → nodetool.code.Code ("Chunk Text" snippet) → vector.IndexTextChunk
**Query flow**: ChatInput → vector.HybridSearch → FormatText → Agent → Output
**Note**: RAG nodes are the single `vector.*` namespace (e.g. `vector.QueryText`, `vector.HybridSearch`, `vector.IndexTextChunk`); there is no `vector.chroma.*`/`vector.faiss.*`.
### Pattern 5: Database Persistence
**Shape**: Input → FormatText → DataGenerator → Insert → Query → Preview
**Use for**: Persistent storage, apps with memory, agent history.
**Key nodes**: CreateTable, Insert, Query, Update, Delete (lib.sqlite namespace)
### Pattern 6: Email & Web Integration
**Shape**: GmailSearch → EmailFields → Summarizer → Preview
**Use for**: Email processing, RSS monitoring, web content extraction.
**Key nodes**: GmailSearch, EmailFields, FetchRSSFeed, GetRequest
### Pattern 7: Realtime Processing
**Shape**: RealtimeAudioInput → RealtimeAgent → Preview
**Use for**: Voice interfaces, live transcription, interactive audio.
**Key nodes**: RealtimeAudioInput, RealtimeAgent, LiveAgent, RealtimeWhisper
**LiveAgent** runs GPT-Live: full-duplex speech with a Responses backend for lookups and web search. Feed it 24 kHz PCM16 microphone chunks.
### Pattern 8: Multi-Modal Workflows
**Shape**: Any modality in → transforms → target modality out
**Common chains**: Audio→Text→Image, Image→Text→Audio, Video→Audio→Text→Summary
### Pattern 9: Advanced Image Processing
**Shape**: ImageInput → edge detection/description → ControlNet generation → Output
**Use for**: Style transfer, controlled generation, structure-preserving transforms.
**Key techniques**: ControlNet (structure), ImageToText (description), Img2Img (style)
### Pattern 10: Data Processing Pipeline
**Shape**: GetRequest → ImportCSV → Filter → ChartGenerator → Preview
**Use for**: Fetch external data, transform datasets, auto-generate visualizations.
> **Video/image generation nodes live under `kie.video.*`, `kie.image.*`,
> `kie.audio.*`** (e.g. `kie.video.Kling26TextToVideo`,
> `kie.video.Hailuo02TextToVideoPro`). Exact model nodes change as providers add
> models — always `ui_search_nodes` for the current node type rather than typing
> a name from memory.
### Pattern 11: Text-to-Video
**Shape**: StringInput (prompt) → `kie.video.*` text-to-video node → Output
**Find nodes**: search `"text to video"` (Kling, Hailuo, Sora, Wan, Bytedance families)
**Config**: Duration 5-10s, Resolution 768P (fast) or 1080P (quality), Aspect 16:9/9:16/1:1
### Pattern 12: Image-to-Video
**Shape**: ImageInput + StringInput (motion guide) → `kie.video.*` image-to-video node → Output
**Find nodes**: search `"image to video"`
### Pattern 13: Talking Avatar
**Shape**: ImageInput (face) + AudioInput (speech) → avatar generation node → Output
**Find nodes**: search `"avatar"` or `"lip sync"`
### Pattern 14: Video Enhancement
**Shape**: VideoInput → upscale node → Output
**Find nodes**: search `"upscale"` / `"video enhance"` (e.g. Topaz family)
### Pattern 15: Storyboard to Video
**Shape**: StringInput (story) + ImageInputs (scenes) → storyboard video node → Output
**Use for**: Narrative videos from keyframes, scene transitions.
### Agent Tool Pattern
Any node can become a tool for an Agent via `dynamic_outputs`:
1. Set `dynamic_outputs` on Agent: `{"search": {"type": "str"}}`
2. Connect downstream nodes to Agent's dynamic output handle (`sourceHandle: "search"`)
3. Agent calls the tool → subgraph executes → result returns to Agent
4. Agent's regular outputs (`text`, `chunk`) route to Preview/Output nodes
### Streaming Architecture
- Everything is a stream; single values are one-item streams.
- Use `nodetool.control.Collect` to gather a stream into a list.
- Use `nodetool.control.ForEach` to process each item in a list.
- Use `nodetool.control.Loop` to repeat part of the graph: wire `value` into
the body, the body's result back into `next`, and a bool into `condition`
(loop again while true). `max_iterations` bounds it and `done` carries the
final value. It is the only way to close a cycle. The DSL cannot express the
back edge, so build loops with the graph tools or workflow JSON.
- Use `nodetool.agents.Decision` when a model should make the call: `prompt`
is a yes/no question, `value` is what it judges (images are shown to the
model), `decision` is the bool and `if_true`/`if_false` route `value` like
`If`. Wire `decision` into `Loop.condition` to loop until a model is
satisfied; phrase the question so yes means "loop again".
- Use `Preview` nodes to inspect intermediate streaming results.
- `sync_mode: "on_any"` fires on each incoming value; `"on_all"` waits for all inputs.
## Debugging & Validation
### Reading Validation Results
`ui_get_graph` returns a `validation` field:
- `errors` — blocking issues (circular deps, invalid node types)
- `warnings` — non-blocking (disconnected required inputs, empty required properties)
- `suggestions` — improvements (orphaned nodes)
Check validation after building. Fix blocking errors and warnings that prevent the requested behavior. Explain any remaining warning without expanding the task.
### Common Errors and Fixes
- **"Required property 'X' is not set"** → `ui_update_node_data` to set it. Common: `model`, `prompt`.
- **"Required input 'X' not connected"** → add an edge or set a default value via properties.
- **Wrong handle name** → re-run `ui_search_nodes` with `include_outputs=true` for exact names.
- **Type mismatch** → verify source output type matches target input type from search results.
- **Node not found** → broaden query, try namespace prefix (`"nodetool.text"`).
### Error Recovery
1. Read the error message carefully.
2. Re-search with `include_properties=true` and `include_outputs=true`.
3. Verify exact property names and handle names from fresh search results.
4. Do not retry the same failing call — adjust parameters first.
### Required Properties
After adding nodes, check for warnings about empty required properties:
- **Agent nodes**: `model` (language_model type) — must be set
- **Generator nodes**: `model`, `prompt`
- **Image generation**: `prompt`
- **ForEach**: requires a list input connection
Set via `ui_update_node_data` or ask the user which value to use.
## Running Workflows from CLI
### JSON Workflows
```bash
# Run a JSON workflow file
nodetool workflows run ./workflow.json
nodetool workflows run ./workflow.json --params '{"input": "hello"}'
nodetool workflows run ./workflow.json --json
# Alternative runner with more options, from a NodeTool checkout
npm run workflow -- ./workflow.json --input text='hello' --show-messages
```
### TypeScript DSL Workflows
```bash
# Run a DSL file (builds graph and executes)
nodetool workflows run ./workflow.ts --json
# Or via the workflow runner, from a NodeTool checkout
npm run workflow -- ./workflow.ts
# Execute directly with tsx (prints workflow JSON only, does not run)
npx tsx ./workflow.ts
```
### Server Management
```bash
nodetool serve # Start backend server
nodetool serve --port 8080 # Custom port
nodetool workflows list # List saved workflows
nodetool workflows get <id> # Get workflow details
nodetool jobs list # List execution jobs
nodetool secrets store OPENAI_API_KEY # Store API key
```
## TypeScript DSL Format
Write workflows as TypeScript with full type safety and IDE autocompletion using `@nodetool-ai/dsl`.
### Basic Pattern
```typescript
import { workflow, constant, text, agents } from "@nodetool-ai/dsl";
// Create nodes — call node.output() to get a connectable handle.
const greeting = constant.string({ value: "hello world" });
// Connect by passing output handles as inputs.
const shout = text.toUppercase({ text: greeting.output() });
const summary = agents.summarizer({ text: shout.output() });
// Build the workflow graph (traces all connections).
const wf = workflow(summary);
console.log(JSON.stringify(wf));
```
### Key Concepts
- **`node.output()`** is a function — call it to get the default output handle. It is NOT a property (`node.output`).
- **Named slots**: `node.output("if_true")` for a specific output of a multi-output node.
- **Connectable**: every input accepts either a literal value or an output handle.
- **`workflow(...terminals)`**: traces from terminal nodes via BFS, returns serializable JSON `{ nodes, edges }`.
- **`run(wf, opts?)`**: executes a workflow in-process via WorkflowRunner; returns the result.
- **`runGraph(...terminals)`**: shorthand — `run(workflow(...terminals))`.
### DSL Namespaces
Namespaces mirror node namespaces. Common ones:
| Import | Example |
|--------|---------|
| `constant` | `constant.float({ value: 5 })`, `constant.string({ value: "x" })` |
| `text` | `text.toUppercase({ text: "hi" })`, `text.split({ ... })` |
| `image` | `image.resize({ ... })` |
| `control` | `control.if_({ condition: true, value: x })`, `control.forEach({ ... })` |
| `agents` | `agents.agent({ ... })`, `agents.summarizer({ text })` |
| `data` | `data.filter({ ... })` |
| `vector` | `vector.hybridSearch({ ... })` |
| `libHttp` | `libHttp.getJSON({ url })`, `libHttp.getText({ url })` |
| `code` | `code.code({ ... })` — JS sandbox for math/JSON/list logic |
> There is no `libMath` or `list` namespace. Use the **Code node** (`code.code`) for
> arithmetic/JSON/list operations.
### Multi-Output (If / ForEach)
```typescript
import { control } from "@nodetool-ai/dsl";
const branch = control.if_({ condition: true, value: "hello" });
branch.output("if_true"); // → output handle for the true branch
branch.output("if_false"); // → output handle for the false branch
```
### Shared Dependencies (diamond)
```typescript
import { workflow, constant, text } from "@nodetool-ai/dsl";
const shared = constant.string({ value: "abc" });
const upper = text.toUppercase({ text: shared.output() });
const lower = text.toLowercase({ text: shared.output() });
const joined = text.concat({ a: upper.output(), b: lower.output() });
const wf = workflow(joined); // `shared` appears once; edges fan out
```