Guiding Principles

These principles govern hypergraph's design and day-to-day usage.

This version combines:

  • explicit principles from README/docs/API

  • implicit invariants enforced in implementation and tests

Use this as a practical rubric for deciding whether a workflow design fits hypergraph.


1. Portable Functions

Your functions should look the same whether they run inside hypergraph or not. The @node decorator adds metadata; it should not force a framework-specific coding style.

Explicit signals

  • Docs emphasize pure/testable functions and testing via node.func(...).

  • Comparison docs position hypergraph against state-schema-driven frameworks.

Implicit invariants

  • FunctionNode remains directly callable.

  • Core behavior is normal Python function execution.

Good example

Break example

  • Function logic requires framework state object structure to work.

  • Removing @node breaks core business logic.


2. Zero Ceremony

Graph complexity should match problem complexity, not framework requirements.

No state schemas. No manual edge wiring for ordinary data flow.

Explicit signals

  • “Minimal”, “No state schemas”, “No boilerplate”.

Implicit invariants

  • Data edges are inferred by name.

  • Graph([nodes...]) is the standard happy path.

Good example

Break example

  • Adding adapter/plumbing nodes only to satisfy orchestration mechanics.


3. Names Are Contracts

Edges are inferred from matching output and input names. Output names are API contracts.

Explicit signals

  • README/API repeatedly explain automatic wiring by matching names.

Implicit invariants

  • Graph construction infers data edges from output/input matches.

  • Name validity is enforced (identifiers, keyword checks, reserved-name checks).

Good example

Break example

  • Invalid names ("bad-output", keyword output names).

  • Ambiguous naming collisions.


4. Validate Early, Fail Clearly

Structural errors should fail when building the graph, not while a long run is in progress.

Explicit signals

  • “Fail fast”, “Build-time validation”, strict_types=True docs.

Implicit invariants (enforced checks)

  • Duplicate node names.

  • Invalid graph/node/output names.

  • Shared-parameter default consistency.

  • Invalid gate targets and gate self-targeting.

  • multi_target output conflicts.

  • Disallowed cache usage on unsupported node types.

  • Strict type compatibility when enabled.

Concrete break example


5. Composition Over Configuration

When workflows grow, nest graphs instead of adding flags or ad-hoc configuration surfaces.

A graph is a node: build and test independently, then reuse via .as_node().

Explicit signals

  • Hierarchical composition is a core design pillar.

Implicit invariants

  • GraphNode projects inner graph inputs/outputs.

  • Nested graph naming and namespace rules prevent ambiguous access.

  • Nested graphs execute as encapsulated units inside outer graphs.

Good example

Break example

  • Forcing unrelated concerns into one flat, hard-to-reason-about graph.


6. Keep Routing Simple

Routing nodes decide where execution goes, not what heavy computation happens.

Explicit signals

  • Routing should be quick and based on already-computed values.

Implicit invariants

  • Async routing functions are rejected.

  • Generator routing functions are rejected.

  • Returned decisions are validated against declared targets.

  • String "END" is guarded against sentinel confusion.

Good example

Break example

  • Doing expensive LLM/tool work inside a routing function.

  • Returning undeclared targets.


7. Cycles Require Entry Points

When a value participates in a cycle, the first iteration needs an initial value. Entry points group these by the node where execution starts.

Explicit signals

  • InputSpec docs define entrypoints for cyclic inputs, grouped by node name.

Implicit invariants

  • Cycle parameters are classified as entrypoint parameters.

  • Missing entrypoint values fail input validation.

  • Entry point values can be bound with bind() or provided at runner.run() time.

Good example

Break example

  • Running cyclic flows without initializing entrypoint values.


8. Immutability

Nodes and graphs behave like values. Transformations produce new instances.

Explicit signals

  • with_*, bind, select are documented as immutable transformations.

Implicit invariants

  • Node rename APIs clone.

  • Graph transformations clone (bind, select, unbind).

Good example

Break example

  • Assuming graph.bind(...) mutates in place and discarding the returned graph.


9. Explicit Over Implicit

Be explicit about outputs, renames, and control flow intent.

Explicit signals

  • Output names are declared.

  • Renames are deliberate (with_inputs, with_outputs, with_name).

Implicit invariants

  • Warnings surface potentially ambiguous intent (for example return annotation without output_name).

  • Reserved names and collisions are rejected.

Good example

Break example

  • Relying on hidden conventions instead of explicit naming and routing declarations.


10. Think Singular, Scale with Map

Write logic for one item. Scale orchestration with .map() or GraphNode.map_over(...).

Explicit signals

  • Design docs emphasize this workflow pattern.

Implicit invariants

  • map_over validates mapped parameters exist.

  • Zip/product modes define combination semantics.

  • Mapped outputs are list-shaped.

  • Interrupts are incompatible with map execution.

Good example

Break example

  • Embedding manual batch loops into node logic instead of mapping execution.


11. Caching Is Opt-In and Deterministic

Caching should depend on stable node definition + input values.

Explicit signals

  • Requires node opt-in (cache=True) and a runner cache backend.

Implicit invariants

  • Cache key includes definition_hash and resolved inputs.

  • Code changes invalidate cache naturally via definition hash changes.

  • GraphNode caching is disallowed. InterruptNode defaults to cache=False but is configurable.

  • Gate routing decisions can be cached and replayed safely.

Good example

Break example

  • Expecting cache semantics on human-interrupt nodes.


12. Use .bind() for Shared Resources

Provide stateful/non-copyable dependencies through bindings, not mutable signature defaults.

Explicit signals

  • Graph docs recommend .bind() for dependency injection and shared resources.

Implicit invariants

  • Bound values are intentionally shared (not deep-copied).

  • Signature defaults are deep-copied per run; non-copyable defaults fail clearly.

Good example

Break example


13. Separate Computation From Observation

Execution logic and observability should stay decoupled.

Explicit signals

  • Event processors are the first-class way to observe execution.

Implicit invariants

  • Runners emit structured lifecycle events.

  • Processors are best-effort; observability should not alter business logic.

Good example

Break example

  • Embedding logging/telemetry control flow directly into node computation.


14. One Framework, Full Spectrum

The same primitives (@node, @route, Graph, runners) span DAGs, branches, loops, and nested workflows.

Explicit signals

  • Core docs position hypergraph as a unified model across orchestration styles.

Implicit invariants

  • Same execution model supports DAGs and cyclic patterns.

  • Composition and routing remain consistent as complexity grows.

Good example

Break example

  • Switching programming models mid-workflow because abstractions don’t compose.


Common Design Dilemmas (Option A vs Option B)

Dilemma
Option A
Option B
Prefer
Why

Function design

Framework-coupled state dict

Plain function params/returns

B

Keeps portability and local testability

Growing complexity

Flat mega-graph

Nested graphs via .as_node()

B

Better encapsulation and reuse

Validation timing

Catch during execution

Catch at Graph(...) construction

B

Faster feedback, lower run-time risk

Scaling strategy

Manual loops in nodes

.map() / map_over

B

Cleaner node semantics

Shared dependencies

Signature defaults for clients/connections

.bind() shared resources

B

Correct lifecycle and copy semantics


The Underlying Test

A design likely fits hypergraph when:

  • Functions are testable as plain Python.

  • Graph wiring mostly comes from meaningful names.

  • Structural mistakes surface before execution.

  • Nested composition reduces complexity.

  • Diffs track business logic, not framework plumbing.

Last updated