Nodes

Nodes are the building blocks of hypergraph. Wrap functions, compose graphs, adapt interfaces.

  • FunctionNode - Wrap any Python function (sync, async, generator)

  • GraphNode - Nest a graph as a node for hierarchical composition

  • HyperNode - Abstract base class defining the common interface

HyperNode

HyperNode is the abstract base class for all node types. It defines the minimal interface that all nodes share: name, inputs, outputs, and rename capabilities.

Cannot Be Instantiated Directly

from hypergraph import HyperNode

HyperNode()  # TypeError: HyperNode cannot be instantiated directly

Use FunctionNode (via @node decorator) or other concrete node types instead.

Core Attributes

Every HyperNode has these attributes (set by subclass __init__):

node.name: str                           # Public node name
node.inputs: tuple[str, ...]             # Input parameter names
node.outputs: tuple[str, ...]            # Output value names
node._rename_history: list[RenameEntry]  # Internal: tracks renames for error messages

Public Methods

with_name(name: str) -> HyperNode

Return a new node with a different name.

Returns: New node instance (immutable pattern)

Raises: None (always succeeds)

with_inputs(mapping=None, /, **kwargs) -> HyperNode

Return a new node with renamed inputs.

Args:

  • mapping (optional, positional-only): Dict of {old_name: new_name}

  • **kwargs: Additional renames as keyword arguments

Returns: New node instance with updated inputs

Raises:

  • RenameError - If any old name not found in current inputs

  • RenameError - Includes helpful history if name was already renamed

with_outputs(mapping=None, /, **kwargs) -> HyperNode

Return a new node with renamed outputs.

Args:

  • mapping (optional, positional-only): Dict of {old_name: new_name}

  • **kwargs: Additional renames as keyword arguments

Returns: New node instance with updated outputs

Raises:

  • RenameError - If any old name not found in current outputs

  • RenameError - Includes helpful history if name was already renamed

Immutability Pattern

All with_* methods return new instances. The original is never modified:

Type Checking

Use isinstance() to check node types:


FunctionNode

FunctionNode wraps a Python function as a graph node. Created via the @node decorator or FunctionNode() constructor directly.

Constructor

Args:

  • source (required): Function to wrap, or existing FunctionNode (extracts underlying function)

  • name: Public node name. Defaults to source.__name__ if source is a function

  • output_name: Name(s) for the output value(s). If None, node is side-effect only (outputs = ())

    • str - Single output (becomes 1-tuple)

    • tuple[str, ...] - Multiple outputs

    • None - Side-effect only, no outputs

  • rename_inputs: Optional dict {old_param: new_param} for input renaming

  • cache: Whether to cache results (default: False)

  • hide: Whether to hide this node from visualization (default: False)

  • emit: Ordering-only output name(s). Auto-produced when the node runs

  • wait_for: Ordering-only input name(s). Node waits until these values exist and are fresh

Returns: FunctionNode instance

Raises:

  • ValueError - If function source cannot be retrieved (for definition_hash)

  • UserWarning - If function has return annotation but no output_name provided

Creating from a function

Creating from existing FunctionNode

When source is a FunctionNode, only the underlying function is extracted. All other config is discarded:

Properties

func: Callable

The wrapped Python function. Read-only.

name: str

Public node name (may differ from function name).

inputs: tuple[str, ...]

Input parameter names from function signature (after renaming).

outputs: tuple[str, ...]

Output value names (empty if no output_name).

data_outputs: tuple[str, ...]

Output names that carry data (excludes emit outputs). Same as outputs when no emit is set.

wait_for: tuple[str, ...]

Ordering-only inputs. Empty tuple when not set.

cache: bool

Whether results are cached (default: False). Set via constructor.

definition_hash: str

SHA256 hash of function source code (64-character hex string). Computed at node creation.

Used for cache invalidation - if function source changes, hash changes, and cached results are invalidated.

Raises ValueError if source cannot be retrieved (built-ins, C extensions):

is_async: bool

True if function is async or async generator. Read-only, auto-detected.

is_generator: bool

True if function yields values (sync or async generator). Read-only, auto-detected.

Special Methods

__call__(*args, **kwargs)

Call the wrapped function directly. Delegates to self.func(*args, **kwargs).

__repr__() -> str

Informative string representation showing function name and node config.


@node Decorator

Decorator to wrap a function as a FunctionNode. Can be used with or without parentheses.

Signature

Args:

  • source: The function (when used without parens like @node)

  • output_name: Output name(s). If None, side-effect only (outputs = ())

  • rename_inputs: Optional dict to rename inputs

  • cache: Enable result caching for this node. Requires a cache backend on the runner. See Caching. Not allowed on GraphNode

  • hide: Whether to hide this node from visualization (default: False)

  • emit: Ordering-only output name(s). Auto-produced when the node runs. Used with wait_for to enforce execution order without data dependency. See Ordering

  • wait_for: Ordering-only input name(s). Node won't run until these values exist and are fresh. Must reference an emit or output_name of another node

Returns:

  • FunctionNode if source provided (decorator without parens)

  • Decorator function if source is None (decorator with parens)

Usage Without Parentheses

The decorator always uses func.__name__ for the node name. To customize, use FunctionNode directly.

Usage With Parentheses

With All Parameters

Warning on Missing output_name

If your function has a return type annotation but no output_name, a warning is emitted:

This helps catch accidental omissions. If the function is truly side-effect only, add type hints:


RenameError

Exception raised when a rename operation references a non-existent name.

Error Messages Include History

When a name was previously renamed, the error message helps you understand what happened:

Exception Details

  • Type: Exception

  • Module: hypergraph.nodes._rename

  • Public export: from hypergraph import RenameError


Execution Modes

FunctionNode supports four execution modes, auto-detected from the function signature:

1. Synchronous Function

2. Asynchronous Function

3. Synchronous Generator

4. Asynchronous Generator


Complete Example

Combining all features:

GraphNode

GraphNode wraps a Graph for use as a node in another graph. This enables hierarchical composition: a graph can contain other graphs as nodes.

Creating GraphNode

Create via Graph.as_node() rather than directly:

Overriding the Name

You can override the name when calling as_node():

Properties

GraphNode inherits from HyperNode and has these properties:

name: str

The node name. Either from graph.name or explicitly provided to as_node().

inputs: tuple[str, ...]

All inputs of the wrapped graph (required + optional + entry point params).

outputs: tuple[str, ...]

All outputs of the wrapped graph.

graph: Graph

The wrapped Graph instance.

is_async: bool

True if the wrapped graph contains any async nodes.

definition_hash: str

Delegates to the wrapped graph's definition_hash.

Type Annotation Forwarding

GraphNode forwards type annotations from the inner graph for strict_types validation:

Nested Composition Example

Rename Methods

GraphNode supports the same rename methods as other nodes:

map_over()

Configure a GraphNode to iterate over input parameters. When the outer graph runs, the inner graph executes multiple times—once per value in the mapped parameters.

Args:

  • *params - Input parameter names to iterate over

  • mode - How to combine multiple parameters:

    • "zip" (default): Parallel iteration, equal-length lists required

    • "product": Cartesian product, all combinations

  • error_handling - How to handle failures during mapped execution:

    • "raise" (default): Stop on first failure and raise the error

    • "continue": Collect all results, using None as placeholder for failed items (preserving list length)

Returns: New GraphNode with map_over configuration

Raises:

  • ValueError - If no parameters specified

  • ValueError - If parameter not in node's inputs

Example: Basic Iteration

Example: Zip Mode (Multiple Parameters)

Example: Product Mode

Example: Error Handling

Output Types with map_over

When map_over is configured, output types are automatically wrapped in list[]:

This enables strict_types=True validation in outer graphs.

Rename Integration

When you rename inputs, map_over configuration updates automatically:

map_config Property

Check the current map_over configuration:

Error: Missing Name

If neither the graph nor as_node() provides a name, an error is raised:


NodeContext

NodeContext provides framework capabilities to nodes that need them: cooperative stop signals and live streaming. Injected automatically when detected in the function signature via type hint.

Usage

Add ctx: NodeContext to any node function:

The framework detects NodeContext in the signature and injects it at execution time. The parameter is excluded from the node's inputs — it never appears in node.inputs and cannot be provided via bind() or values.

Properties

stop_requested: bool

Read-only. True when runner.stop(workflow_id) has been called. The node checks this cooperatively and decides when to break.

Methods

stream(chunk: Any) -> None

Emit a StreamingChunkEvent for live UI preview. Does not affect the node's return value — the node controls its own output type.

Streaming is a side-channel. The framework doesn't accumulate chunks, manage reducers, or touch output types. ctx.stream() is silently skipped if stop_requested is True.

Injection Mechanism

NodeContext uses the same type-hint inspection that powers automatic edge inference. This is the same pattern FastAPI uses for Request and BackgroundTasks:

  • The type annotation determines injection, not the parameter name. ctx, context, nc — all work.

  • Functions without NodeContext work exactly as before. Backward compatible.

  • Testing is plain Python: llm_reply(messages=["hi"], ctx=mock_context).

Testing

No framework setup needed — pass a mock or stub directly.


InterruptNode

InterruptNode is a thin FunctionNode subclass that acts as a declarative pause point for human-in-the-loop workflows. When the handler returns None, execution pauses. When it returns a value, the interrupt auto-resolves.

@interrupt Decorator

The @interrupt decorator creates an InterruptNode from a function, just like @node creates a FunctionNode:

Decorator args:

  • output_name (required): Output name(s)

  • rename_inputs: Optional dict to rename inputs

  • cache: Enable result caching (default: False)

  • emit: Ordering-only output name(s) (see emit/wait_for)

  • wait_for: Ordering-only input name(s)

  • hide: Whether to hide from visualization

Constructor

Like FunctionNode, InterruptNode can also be created via the constructor. output_name is required.

Properties

Property
Type
Description

inputs

tuple[str, ...]

Input parameter names (from function signature)

outputs

tuple[str, ...]

All output names (data + emit)

data_outputs

tuple[str, ...]

Data-only outputs (excluding emit)

is_interrupt

bool

Always True

cache

bool

Whether caching is enabled (default: False)

hide

bool

Whether hidden from visualization

wait_for

tuple[str, ...]

Ordering-only inputs

is_async

bool

True if handler is async

is_generator

bool

True if handler yields

definition_hash

str

SHA256 hash of function source

Methods

Inherited: with_name(), with_inputs(), with_outputs()

All HyperNode rename methods work as expected.

Example: Pause and Resume

For a full guide with multiple interrupts, nested graphs, and handler patterns, see Human-in-the-Loop.

Last updated