InputSpec

InputSpec describes what inputs a graph needs and how they must be provided.

What counts as "required" depends on four dimensions that narrow the active subgraph:

Dimension

Method

Narrows from

Effect on required

Entrypoint (start)

with_entrypoint(...)

The front

Excludes upstream nodes

Select (end)

select(...)

The back

Excludes nodes not needed for selected outputs

Bind (pre-fill)

bind(...)

Individual params

Moves params from required to optional

Defaults (fallback)

Function signatures

Individual params

Params with defaults are optional

from hypergraph import node, Graph

@node(output_name="embedding")
def embed(text: str) -> list[float]:
    return [0.1, 0.2, 0.3]

@node(output_name="docs")
def retrieve(embedding: list[float], top_k: int = 5) -> list[str]:
    return ["doc1", "doc2"]

@node(output_name="answer")
def generate(docs: list[str], query: str) -> str:
    return f"Answer based on {len(docs)} docs"

g = Graph([embed, retrieve, generate])

# Full graph: all inputs
print(g.inputs.required)  # ('text', 'query')
print(g.inputs.optional)  # ('top_k',)

# Entrypoint narrows from the front
g2 = g.with_entrypoint("retrieve")
print(g2.inputs.required)  # ('embedding', 'query') - text no longer needed

# Select narrows from the back
g3 = g.select("docs")
print(g3.inputs.required)  # ('text',) - query no longer needed

# Bind pre-fills a value
g4 = g.bind(query="What is RAG?")
print(g4.inputs.required)  # ('text',)

# All four compose
configured = g.with_entrypoint("retrieve").select("answer").bind(top_k=10)
print(configured.inputs.required)  # ('embedding', 'query')
print(configured.inputs.optional)  # ('top_k',)

The InputSpec Dataclass

InputSpec is a frozen dataclass with four fields:

required: tuple[str, ...]

Parameters that must be provided at runtime. They have no default value and aren't bound.

optional: tuple[str, ...]

Parameters that can be omitted at runtime. They have default values.

entrypoints: dict[str, tuple[str, ...]]

Reserved for compatibility. For configured graphs, this field is {}.

Cyclic graphs must be constructed with graph-level entrypoint configuration (Graph(..., entrypoint=...)). Cycle bootstrap parameters are represented directly in canonical required/optional.

bound: dict[str, Any]

Parameters that are pre-filled with values via bind().

How Categories Are Determined

Categorization happens in two phases:

Phase 1: Scope narrowing. Determine which nodes are active using entrypoints (forward-reachable) and select (backward-reachable). Only active nodes contribute parameters to InputSpec.

Phase 2: Parameter classification. For each parameter in the active subgraph, apply the "edge cancels default" rule:

Condition
Category

No incoming edge, no default, not bound

required

No incoming edge, has default OR is bound

optional

Has incoming edge from another node

Not in InputSpec

Is bound via bind()

In bound dict, moves from required to optional

Node excluded by with_entrypoint() or select()

Not in InputSpec

Edge Cancels Default

When a parameter receives data from another node (has an incoming edge), it's not in InputSpec at all:

Cycles Require Entrypoint At Construction

Scope Narrowing (Entrypoint and Select)

with_entrypoint() and select() narrow which nodes are considered when computing InputSpec. Parameters from excluded nodes do not appear in required, optional, or entrypoints.

Accessing InputSpec

From a Graph

Getting All Input Names

Use the all property to get all input names:

Iteration

InputSpec is not directly iterable. Use all or access individual tuples:

DAG Slicing Matrix

  • base: required=('x', 'y'), optional=('z',)

  • base.with_entrypoint("step_b"): required=('a', 'y'), optional=('z',)

  • base.select("b"): required=('x', 'y'), optional=()

  • base.with_entrypoint("step_b").select("b"): required=('a', 'y'), optional=()

Examples

Simple Graph: Required and Optional

Bound Values

Graph with Cycles

Multiple Nodes Sharing a Parameter

When multiple nodes use the same parameter name, defaults must be consistent:

If defaults don't match, Graph construction fails:

Design Decisions

Canonical Scope

InputSpec is computed from graph-level scope only:

  • entrypoint / with_entrypoint(...)

  • select(...)

  • bind(...)

Runtime scope switching (run(..., select=...), run(..., entrypoint=...)) is not supported.

Value Resolution Order

When multiple sources provide the same value, the runner uses: EDGE > PROVIDED > BOUND > DEFAULT

  • Internal edge-produced parameters supplied at runtime are rejected deterministically (ValueError).

Complete Example

Last updated