Appearance
Edge
An edge connects a source node or enter port to an executable node, exit port, or failure port. Its conditions constrain the source feature state; its effects constrain a Feature node's candidate state. An edge entering an executable node selects that arrival path's visit.
Conditions
A condition enables an edge only when the current feature state satisfies every listed observation.
Here
| Feature kind | Notation | Required current value |
|---|---|---|
| Boolean | true | |
| Boolean | false | |
| Integer or float | zero | |
| Integer or float | greater than zero | |
| Enum | equal to |
Conditions observe the feature state from before the source node runs; effects inspect a Feature node's proposed state.
Effects
Explicit effects describe how feature state may change across a Feature node. They are allowed only when that node is the edge's source.
| Feature kind | Notation | Required next value |
|---|---|---|
| Boolean | true | |
| Boolean | false | |
| Boolean | unchanged | |
| Boolean | unconstrained | |
| Integer or float | strictly greater than the current value | |
| Integer or float | strictly smaller than the current value | |
| Integer or float | unchanged | |
| Integer or float | unconstrained | |
| Enum | equal to | |
| Enum | unconstrained |
The runtime evaluates every outgoing edge against the source and candidate states. It commits the candidate state only when exactly one edge satisfies both its conditions and effects; zero or multiple compatible edges cause a routing error. An arrow denotes a strict comparison, not a change of exactly one. A question mark permits any initialized value in the feature's domain. A feature omitted from the effect list must remain unchanged, including when its value is None.
Parallel edges
Edge identity is independent of endpoint identity; multiple edges may therefore connect the same pair of nodes. Each edge retains its own declaration, conditions, effects, and visit.
The first automatically derived ID is <source>__<target>. A parallel edge receives a distinct ID such as <source>__<target>__2. Because the edge ID names both its generated declaration directory and the visit directory below the target, parallel edges cannot collide. A custom edge ID uses lowercase snake_case and remains independent of its endpoints.
Generated interface
Each edge module exports EDGE, an EdgeDefinition containing its id, source, target, conditions, effects, and optional visit. Conditions and effects are typed observation objects that refer to entries in the owning subroutine's FEATURE_IDS.
For an executable target, visit refers to the generated VISIT adapter for this incoming edge. The adapter connects the source output type to the target's authored implementation. Edges into exit or failure set visit=None. An exit edge also declares check_exit_output(value: SourceOutput, /) -> GraphOutput, returning value unchanged. This function lets a type checker compare the source payload with the subroutine's output type; the runtime does not call it. Reaching the failure port raises RuntimeError without returning a payload. Editing the graph regenerates these declarations.
Authored files
Edges have no authored files. When an edge targets an executable node, it selects a visit whose generated and authored files are stored below that target node. Edges into exit or failure create no visit files.
Files
Each edge has its own generated package beside the generated edge registry:
General edge structure
edges/
├── ◆ __init__.py
└── <edge>/
└── ◆ __init__.py
| File | Role |
|---|---|
◆ edges/<edge>/__init__.py | Declares one edge's ID, name, endpoints, conditions, effects, and optional visit. |
◆ edges/__init__.py | Imports every directly owned EDGE declaration and exports the ordered EDGES tuple. |
The registry collects declarations in edge-ID order. That order does not define routing priority: execution requires exactly one compatible outgoing edge.
Countdown example
The Countdown graph declares these six edges in project.json:
| Edge ID | Connection | Condition | Effect |
|---|---|---|---|
enter__initialize_counter | enter → initialize_counter | — | — |
initialize_counter__check_counter | initialize_counter → check_counter | — | counter? permits its first value |
check_counter__decrement | check_counter → decrement | counter > 0 | — |
decrement__update_counter | decrement → update_counter | — | — |
update_counter__check_counter | update_counter → check_counter | — | counter decreases |
check_counter__exit | check_counter → exit | counter = 0 | — |
Entering the graph
Source: ◆ subroutines/main/edges/enter__initialize_counter/__init__.py
python
from typing import Final
from verdog_runtime.declarations import EdgeDefinition
from demo.countdown.subroutines.main import EDGE_IDS, NODE_IDS
from demo.countdown.subroutines.main.nodes.initialize_counter.visit.enter__initialize_counter import (
VISIT,
)
EDGE: Final[EdgeDefinition] = EdgeDefinition(
id=EDGE_IDS.enter__initialize_counter,
source=NODE_IDS.enter,
target=NODE_IDS.initialize_counter,
name="enter to initialize counter",
conditions=(),
effects=(),
visit=VISIT,
)This edge has no conditions or effects. It passes the subroutine input to the target's initialization visit. The imported VISIT belongs to initialize_counter; that visit proposes the counter's first value, which the next edge checks and commits.
Accepted decrement
Source: ◆ subroutines/main/edges/update_counter__check_counter/__init__.py
python
from typing import Final
from verdog_runtime.declarations import (
EdgeDefinition,
NumericalEffectObservation,
NumericalFeatureEffect,
)
from demo.countdown.subroutines.main import EDGE_IDS, NODE_IDS, FEATURE_IDS
from demo.countdown.subroutines.main.nodes.check_counter.visit.update_counter__check_counter import VISIT
EDGE: Final[EdgeDefinition] = EdgeDefinition(
id=EDGE_IDS.update_counter__check_counter,
source=NODE_IDS.update_counter,
target=NODE_IDS.check_counter,
name="update counter to check counter",
conditions=(),
effects=(
NumericalFeatureEffect(
feature_id=FEATURE_IDS.counter,
observation=NumericalEffectObservation.DECREASES,
),
),
visit=VISIT,
)source and target connect update_counter to check_counter. conditions=() imposes no condition on the previous state. The single effect requires the candidate counter value to be smaller than its previous value.
The update visit produces that candidate after checking that Count.value is exactly one lower. The edge's decrease constraint is checked against the candidate before the transition commits.
visit=VISIT selects behavior at the target node. The import points to check_counter.visit.update_counter__check_counter, whose forwarding visit returns the accepted payload unchanged. The Visit page shows both the generated adapter and its authored implementation.
At check_counter, the positive-value condition selects decrement; the zero-value condition selects exit. These conditions choose a route, and the decrease effect supports structural termination analysis. Neither replaces the authored check that the agent proposed the correct next integer.
Exiting the graph
Source: ◆ subroutines/main/edges/check_counter__exit/__init__.py
python
from typing import Final
from verdog_runtime.declarations import (
EdgeDefinition,
NumericalConditionObservation,
NumericalFeatureCondition,
)
from demo.countdown.subroutines.main import EDGE_IDS, NODE_IDS, FEATURE_IDS
from demo.countdown.subroutines.main import Output as GraphOutput
from demo.countdown.subroutines.main.nodes.check_counter import Output as SourceOutput
def check_exit_output(value: SourceOutput, /) -> GraphOutput:
return value
EDGE: Final[EdgeDefinition] = EdgeDefinition(
id=EDGE_IDS.check_counter__exit,
source=NODE_IDS.check_counter,
target=NODE_IDS.exit,
name="check counter to exit",
conditions=(
NumericalFeatureCondition(
feature_id=FEATURE_IDS.counter,
observation=NumericalConditionObservation.EQUAL_ZERO,
),
),
effects=(),
visit=None,
)The zero condition selects this edge when the accepted counter reaches 0. Its target is the exit port, so visit=None and the current Count becomes the subroutine result. check_exit_output lets a type checker verify that check_counter.Output is compatible with the subroutine's Output; both are Count here. The runtime returns the payload without calling this helper.