Nodes
The building blocks of every Weft project.
A node is a typed box that does one thing. It has a type (picked from the catalog), an ID (how you reference it), some configuration (how it behaves), and ports (how data flows in and out).
Every project is a graph of nodes connected by edges. That is the whole language.
Anatomy of a node
my_llmis the ID. Snake_case, unique within its scope.LlmInferenceis the type, from the node catalog.-> (response: String)is a port signature. It declares the output portresponsewith typeString.- Inside the braces is the config block:
label,temperature, and any other fields the node type defines.
Declaration forms
Nodes range from one-liners to full declarations with custom ports. Pick the shortest one that says what you mean.
Just config, the most common case:
Bare, no config, when defaults are all you need:
Output ports only:
Output ports with config:
Custom input and output ports, with multi-line Python code in a triple-backtick field:
Post-config outputs, when the outputs depend on a config choice:
Ports
A port is a named slot on a node where data goes in or comes out. Every port has a type. The compiler refuses connections where the types do not match.
Ports come from three places:
- Default ports declared by the node in the catalog (every LlmInference has a
promptinput, every Text has avalueoutput). - Custom ports you declare in the signature (only on nodes with
canAddInputPortsorcanAddOutputPorts). - Form-schema ports generated by
HumanQueryand similar nodes, one per form field.
Every port is either required (the default) or optional (add ? after the type). Required ports stop the node from running if they receive null. Optional ports let the node run and pass null through. See null propagation for the full story.
Configuring a node: three equivalent forms
You can fill an input port in three equivalent ways:
Form 1. A config field inside the node's braces:
Form 2. A literal on a separate line after the node is declared:
Form 3. Wire it from another node's output:
All three compile to the same thing. Use forms 1 and 2 for values the user will never need to change at runtime (template strings, system prompts, fixed lists). Use form 3 when the value comes from upstream data.
If both a config literal and a wired edge target the same port, the edge wins.
Media types (Image, Audio, Video, Document) cannot be filled by a literal. They must be wired.
Config values
- Strings: double quotes.
label: "My Node" - Numbers: bare, no quotes in general. A few LLM config fields like
temperatureare usually written quoted in practice (temperature: "0.3"); both forms work. When in doubt, copy from a catalog example or ask Tangle. - Booleans:
true/false. - JSON arrays and objects: written directly, no string wrapping.
value: ["a", "b"]. - Multi-line strings: triple backticks on the same line as the key, content starts at column 0 on the next line, closing triple backticks on their own line.
- Comments: lines starting with
#. Anywhere.
Custom ports
Some node types (ExecPython, Template, Pack, and others marked canAddInputPorts or canAddOutputPorts) let you add ports on the fly. Declare them in the signature with a type:
Every custom port must have a type. Writing name without a type is a compile error.
Adding a port to a node type that does not support custom ports (LlmInference, EmailSend, etc.) is also a compile error. Pipe your data through a Template or a Pack first if you need to reshape it.
Inline nodes
For a one-off intermediate node (a Template that feeds exactly one downstream port, say), you can declare it inline right where its output is wired:
The parser generates a child node and a wiring edge behind the scenes. The ID is stable (person_search__query), so you can reference it from elsewhere in the file if you need to.
Inline nodes nest freely. Use them for short-lived helpers. For anything reusable or anything you want to give a meaningful name to, declare the long way.
@require_one_of
When a node has multiple optional inputs and at least one must be provided, use @require_one_of:
Without this, a node with all optional inputs will run even when every input is null. That is almost never what you want.
What's next
- Connections: how edges wire nodes together.
- Types: the full type system.
- Groups: bundle nodes into reusable sub-graphs.