4 · Expert
Goal: stop adapting to threadle and make threadle yours — your own node types, your own reusable building blocks, graphs other people can run.
4.1 Custom nodes: your code on the canvas
Section titled “4.1 Custom nodes: your code on the canvas”A custom node is a directory under ~/.config/threadle/nodes/<name>/ containing a node.json descriptor plus either a TypeScript class or any executable at all:
{ "id": "shout", "label": "Shout", "glyph": "!", "entry": "node.ts", "input": "text", "output": "text"}export default class Shout { async run(input: string): Promise<string> { return input.toUpperCase() + "!!!"; }}Text arrives on stdin, text leaves on stdout. Prefer Python, jq, or a compiled binary? Swap entry for a command argv array. Nodes run as isolated child processes with a timeout, an output cap, and a minimal environment — a crashing node can’t take the server with it.
Settings → custom nodes → + Create node scaffolds both files and opens your editor.
See Custom nodes.
4.2 Params and named ports
Section titled “4.2 Params and named ports”Two declarations turn a script into a first-class node.
Params render as widgets directly on the node card — numbers, checkboxes, dropdowns, textareas — and their values are stored per node in the graph:
"params": [ { "name": "width", "type": "int", "label": "max width", "default": 72, "min": 10 }, { "name": "mode", "type": "choice", "options": ["fold", "strip"] }]They reach your process as THREADLE_PARAMS (one JSON object), as THREADLE_PARAM_<NAME> variables, and as ctx.params for class nodes.
Named ports give the node more than one lane in and out:
"inputs": [ { "name": "text" }, { "name": "parts", "maxConnections": 8 }],"outputs": [{ "name": "head" }, { "name": "tail" }]The card grows one labeled, typed handle per port; wires are validated per port; unwired required ports are flagged before the run. Each port is a single-wire slot by default — set maxConnections to allow fan-in / fan-out (the card shows ×N). The protocol switches to JSON — stdin is an object keyed by input port, stdout must be an object keyed by output port (a class node can simply return { head, tail }).
See Params & named ports.
4.3 Typed lanes end to end
Section titled “4.3 Typed lanes end to end”Ports carry a value type: text, int, float, bool, json. Incompatible wires are refused while you drag (int widens to float; anything fits text), and values are validated again at run time — a node that claims int output but prints prose fails loudly instead of poisoning everything downstream.
See Lanes & value types.
4.4 Sub-workflows — build once, reuse everywhere
Section titled “4.4 Sub-workflows — build once, reuse everywhere”Select a working cluster of nodes and save selection as sub-workflow. You get a linked ▦ frame: edit the sub-workflow once and every graph that uses it follows. At run time the frame resolves to its entry and exit members, so wiring into a frame behaves exactly like wiring into the subgraph.
4.5 Ship it
Section titled “4.5 Ship it”Workflows are portable JSON (threadle/graph@1). ⤒ Export copies a runtime-stripped graph to the clipboard; import pastes it back as a fresh copy with new ids, idle statuses, and edges re-validated. Machine-local references — session ids, payload hashes, absolute skill paths — are cleared on export, and skills/rules re-resolve by name on import, so a graph survives the trip to another machine.
threadle run ./my-flow.json --approve-all --param task="..."Custom nodes ship the same way: push the folder to git, and anyone installs it with ⇣ Import. Importing never executes anything.
See Portable graphs and Sharing & importing.
4.6 The guarded fan — everything at once
Section titled “4.6 The guarded fan — everything at once”The Guarded parallel example is the expert-level composite: parallel agents under a ply cap, per-branch tripwires, a knot merging the survivors, a splice gate before the result lands, and a graph-wide spend ceiling over the whole thing.
If you can read that graph and predict its behavior, you know threadle.
▶ Example: Guarded parallel fan
You’re done when…
Section titled “You’re done when…”- you’ve written a custom node with at least one param
- you have a sub-workflow used by two different graphs
- you’ve exported a workflow and run it on another machine
- you reach for a tripwire before letting anything run unattended
Where to go from here
Section titled “Where to go from here”- CLI — automation and scripting
- Costs & billing — tracked vs actual, per model
- Trust model — what runs where, and what never leaves
- Troubleshooting — when something misbehaves