Validation
nae.validation ¶
Compile-time state-availability validation for AgenticGraph.
The core promise: when a graph is built we statically verify that every state key a node READS is produced upstream (or present in the initial schema), and that every key it WRITES is declared in the schema. The build FAILS on a hard error.
The core is a forward dataflow over a tagged edge list (recorded during the
build): may (any-path availability, a monotone-increasing union fixpoint)
and must (guaranteed-on-all-paths, a monotone-decreasing fixpoint that
UNIONs across AND-parallel joins and INTERSECTs within an exclusive
DecisionNode choice-group). Hard errors come from may; must only drives
warnings. Loop-back edges are excluded when computing incoming availability, so
loop-carried reads (a key produced only by a downstream node that cycles back)
are caught as read-before-write on the first iteration rather than silently
credited.
This is a may/must dataflow analysis, not a soundness proof. It reasons over
DECLARED node reads/writes and the graph's edges — it does NOT read the
code inside ConditionNode predicates (arbitrary lambdas), tool bodies, or
other custom callables, so a state key touched only inside such a callable is
invisible to it unless the node declares it. Within those limits it flags
read-before-write and undeclared reads/writes; no false positives have been
observed on our example corpus, but "compiles" is not a guarantee of dataflow
soundness.
GraphValidationError ¶
Bases: Exception
Raised at build time when validation finds a hard error.
ValidationReport ¶
Collected issues for one graph; .ok is True when there are no errors.
check_placeholders ¶
Cross-check a node's scalar reads against its node_prompt placeholders.
A scalar read exists to be interpolated into the prompt. With no scalar
reads the node skips .format_map entirely (brace-heavy prompts pass
untouched), so this check is skipped. Otherwise:
- Check A (hard error): every {name} placeholder must be a declared
scalar read — an undeclared one is a guaranteed runtime KeyError.
- Check B (warning): every scalar read should appear as a placeholder —
an uninterpolated scalar read is a dead read.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
scalar_reads
|
set
|
the node's reads that are NOT message-history channels. |
required |
placeholders
|
set
|
|
required |
collect_issues ¶
collect_issues(graph_name, schema, reduced, node_io: dict, may, must, concurrency_pairs: set, decision_choices: dict, end_with_children: set, unreachable: set)
Run all checks and return a list of Issues. See module docstring / spec.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
node_io
|
dict
|
|
required |
concurrency_pairs
|
set
|
set of frozenset({x, y}) node-name pairs that run in parallel (AND-parallel branches). |
required |
decision_choices
|
dict
|
|
required |
end_with_children
|
set
|
names of declared end nodes that still have children. |
required |
unreachable
|
set
|
names of nodes not reachable from START. |
required |
compute_availability ¶
compute_availability(initial: set, edges: list, writes: dict, send_workers: dict | None = None, spread_collectors: dict | None = None) -> tuple
Forward dataflow fixpoint over the tagged edge list.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
initial
|
set
|
the INPUT keys — keys guaranteed available at invoke (reduced
channels, schema keys no node writes, and user-declared |
required |
edges
|
list
|
list of |
required |
writes
|
dict
|
|
required |
send_workers
|
dict | None
|
|
None
|
spread_collectors
|
dict | None
|
|
None
|
Returns:
| Type | Description |
|---|---|
tuple
|
|
reduced_keys ¶
Keys whose annotation is Annotated[..., <reducer>] (i.e. have a reducer).
schema_keys ¶
Every key the state schema declares (including inherited TypedDict keys).