Appearance
Feature
A feature is a typed component of subroutine state. Edge conditions observe its value; edge effects constrain its change. Every feature starts uninitialized and receives values through Feature-node visits.
Feature kinds
| Kind | Python value | Conditions can observe |
|---|---|---|
| Boolean | bool | true or false |
| Integer | non-negative int | zero or greater than zero |
| Float | finite, non-negative float | zero or greater than zero |
| Enum | str | one declared value |
Initialization
Every subroutine invocation starts with all feature values set to None. A Feature visit proposes the first domain value through state.replace(FEATURE, value). Evaluating a condition on an uninitialized feature raises ValueError. Increase and decrease effects also require an initialized source value, whereas assignment and unconstrained effects can initialize a feature. An unchanged effect preserves None. Once initialized, a feature cannot be reset to None.
Conditions read the state before the source visit. An initialization node must therefore precede any node whose outgoing conditions read the initialized feature; a condition on the initialization node's own outgoing edge would still read None.
Conditions and effects
Only an edge originating at a Feature node may carry explicit effects. Edge defines the observations; Visit describes the candidate values against which the runtime evaluates them.
Generated interface
Each feature module exports FEATURE, a FeatureDefinition[Value, StateScope]. It identifies a typed state slot and its domain; it does not hold the current value. A Feature visit uses state.get(FEATURE) to read that slot and state.replace(FEATURE, value) to propose a replacement. StateScope associates the feature with its owning subroutine.
The declaration records its kind; an enum also records its permitted values. The runtime validates proposed values against this kind and domain. The type parameters express the value type and owning state scope for static checking.
Edge declarations refer to the feature's generated ID in FEATURE_IDS. Changing a condition or effect updates the edge declaration, not the feature module.
Authored files
Features have no authored module or initializer callback. Declare their kinds in project.json; generation maintains the Python interface. Initialization and updates belong to authored Feature visits, stored at ● nodes/<node>/visit/<edge-id>/impl.py.
Files
Each feature has a generated declaration and no authored file. Its subroutine also has one generated registry for all feature declarations.
General feature structure
features/
├── ◆ __init__.py
└── <feature>/
└── ◆ __init__.py
| File | Role |
|---|---|
◆ features/<feature>/__init__.py | Declares the feature ID, label, description, kind, and allowed enum values. |
◆ features/__init__.py | Imports every FEATURE in the subroutine and exports them as the FEATURES tuple. |
Countdown example
Countdown keeps its accepted routing state separate from its payload.
Source: project.json
json
{
"id": "counter",
"label": "Counter",
"description": "The current non-negative countdown value",
"kind": "integer"
}This entry in subroutine.features declares counter as a non-negative integer feature.
Source: ◆ subroutines/main/features/counter/__init__.py
python
from typing import Final
from verdog_runtime.declarations import FeatureDefinition, FeatureKind
from demo.countdown.subroutines.main import FEATURE_IDS, StateScope
FEATURE: Final[FeatureDefinition[int, StateScope]] = FeatureDefinition(
id=FEATURE_IDS.counter,
label='Counter',
description='The current non-negative countdown value',
kind=FeatureKind.INTEGER,
)FEATURE identifies the state slot; its value starts as None. The initialize_counter visit imports this declaration and proposes the input count as its first value. The update_counter visit checks every subsequent proposal before replacing the feature value.
The Count payload carries the proposed integer between nodes; counter records the accepted integer used by routing conditions and termination analysis. Starting with Count(value=2) initializes counter to 2. A proposal of Count(value=1) passes the authored exact-decrement check; a proposal of 0 fails it even though it would satisfy a strict-decrease edge effect. Feature visits forward the payload unchanged.
Feature registry
Source: ◆ subroutines/main/features/__init__.py
python
from typing import Final
from demo.countdown.subroutines.main.features.counter import FEATURE as COUNTER_FEATURE
FEATURES: Final = (COUNTER_FEATURE,)The registry collects this subroutine's feature declaration in FEATURES, which the subroutine factory uses to assemble the graph.