Skip to content

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 ​

KindPython valueConditions can observe
Booleanbooltrue or false
Integernon-negative intzero or greater than zero
Floatfinite, non-negative floatzero or greater than zero
Enumstrone 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
FileRole
◆ features/<feature>/__init__.pyDeclares the feature ID, label, description, kind, and allowed enum values.
◆ features/__init__.pyImports 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.