Skip to content

Node ​

An executable node declares an operation performed by its incoming-edge visits. Non-Feature nodes own an output type and local state shared across those visits. Feature nodes update feature state while the runtime forwards their input payload unchanged.

Node kinds ​

KindStructural declarationAuthored behavior
PythonPython()One standard Python visit per incoming edge.
AgentLogical profile and session IDsOne agent-aware visit per incoming edge.
FeatureTransparent feature-state operationOne feature visit per incoming edge; no node impl.py or local state.
Subroutine callTarget subroutine ID and parameter closureA call visit invokes the child in the current process.
Workflow callTarget workflow ID and project pathA call visit invokes the child in its own workflow process.

Generated interface ​

The generated ◆ nodes/<node>/__init__.py exports NODE, which binds the node's identity to its operation. For a non-Feature node, it also imports the authored Output and State types and supplies State as state_type. Python, Agent, and call nodes use NodeDefinition; Feature nodes use FeatureNodeDefinition without an output or local-state type. A control port exports PORT, a PortDefinition containing its typed node ID. Each incoming edge has a separate visit interface that combines the source payload type with these target types.

The node declaration selects the operation and resources. The visit's visit_impl supplies the behavior when that edge enters the node.

Authored files ​

Node types ​

For every non-Feature executable node, ● nodes/<node>/impl.py defines:

TypeResponsibility
OutputThe payload returned by any successful visit to this node and passed to the next step.
StateValues needed by later visits to this node within the same subroutine invocation.

The runtime constructs State() at invocation start and requires a frozen, slotted dataclass with a generated initializer and direct instance fields. Fields have immutable direct defaults; inheritance, ClassVar, InitVar, default_factory, and other field customization are unsupported. Values needed by another node belong in Output. Feature nodes and control ports have no node-level authored file. Per-edge behavior belongs to the visit.

Agent nodes ​

An Agent node selects its profile and session independently. The profile specifies the provider and its options; the session controls conversation continuity. Within a resource scope, one profile can serve multiple independent sessions, while Agent nodes selecting the same persistent session share its conversation.

Call boundaries ​

The generated SubroutineCall or WorkflowCall operation records the child's definition_id, definition_module, and project_path. A subroutine call also imports ChildInput, ChildOutput, ChildParams, and every parameter type reachable through further subroutine calls. Its params_types records that parameter closure; profile_arguments and session_arguments bind child parameter IDs to caller resource IDs.

Local workflow calls expose ChildInput and ChildOutput. External workflow calls use object for both types because the caller does not import the child's process-boundary classes. External subroutine calls expose the resolved child types through imports from the pinned project's package.

A successful call visit must invoke context.invoke() exactly once. It adapts the child input and returned value to the parent node's Output; an optional params= argument overrides the child parameters for that invocation. This is a synchronous durable effect inside a deterministic adapter. See Call visits for its replay and side-effect requirements.

Feature nodes ​

A Feature node has no node-local State or authored Output. Generated visits downstream infer the payload type from its upstream producers. A Feature visit returns only candidate feature state; the runtime retains the original payload.

A Feature node is distinct from a feature declaration, which names one component of subroutine state and specifies its domain. A Feature visit assigns its first value as well as subsequent values.

Control ports ​

Every subroutine has enter, exit, and failure ports. Ports have no impl.py, local state, or visit implementation. Enter introduces the subroutine Input; exit accepts its successful Output; failure records exceptional termination but executes no handler.

Files ​

Each executable node has a generated declaration and, except for Feature nodes, an authored module containing its output and state types. The enclosing subroutine maintains one generated node registry.

General node structure

nodes/
├── ◆ __init__.py
├── <control-port>/
│   └── ◆ __init__.py
└── <executable-node>/
    ├── ◆ __init__.py
    ├── ● impl.py        non-Feature nodes only
    └── visit/<incoming-edge>/
        ├── ◆ __init__.py
        └── ● impl.py
FileRole
◆ nodes/<node>/__init__.pyDeclares NODE, including the operation and, for non-Feature nodes, the authored output and state types.
◆ nodes/<control-port>/__init__.pyDeclares the control port's PORT identifier.
◆ nodes/<node>/visit/<edge-id>/__init__.pyResolves that incoming edge's exact input type and declares the node-kind-specific visit adapter.
◆ nodes/__init__.pyImports every executable NODE in the subroutine and exports the NODES tuple. Control ports are assembled separately.
● nodes/<node>/impl.pyDeclares the successful Output and private State for every non-Feature executable node.
● nodes/<node>/visit/<edge-id>/impl.pyImplements the behavior selected when that edge enters this node.

Countdown example ​

The Countdown graph contains four executable nodes:

NodeKindRole
initialize_counterFeatureProposes the input count as the initial counter.
check_counterPythonForwards the current Count; outgoing conditions select the next step.
decrementAgentProposes the next Count using the decrementer profile and countdown session.
update_counterFeatureValidates the proposal and updates counter, forwarding the payload unchanged.

The graph also contains the required enter, exit, and failure ports. failure has no incoming edge in this example; an exception still terminates the graph through its failure boundary.

Decrement node ​

Source: project.json

json
{
  "id": "decrement",
  "name": "Decrement",
  "kind": "agent",
  "operation": {
    "profile": "decrementer",
    "session": "countdown"
  }
}

The decrement entry declares an Agent node and selects the decrementer profile parameter and countdown session. Generation resolves these names to the typed identifiers in the following module.

Source: ◆ subroutines/main/nodes/decrement/__init__.py

python
from typing import Final
from verdog_runtime.declarations import NodeDefinition, Agent
from demo.countdown.subroutines.main import (
    NODE_IDS,
    StateScope,
    PROFILE_IDS,
    SESSION_IDS,
)
from .impl import Output as Output, State

NODE: Final[NodeDefinition[State, StateScope]] = NodeDefinition(
    id=NODE_IDS.decrement,
    name="Decrement",
    state_type=State,
    operation=Agent(
        profile=PROFILE_IDS.decrementer,
        session=SESSION_IDS.countdown,
    ),
)

NODE binds the node's identity to its Agent operation. state_type=State specifies the local state that its visits receive. Output and State are imported from the authored sibling module.

Source: ● subroutines/main/nodes/decrement/impl.py

python
"""The agent returns a count and retains its number of proposals."""

from dataclasses import dataclass

from demo.countdown.subroutines.main.impl import Count


@dataclass(frozen=True, slots=True, kw_only=True)
class State:
    attempts: int = 0


Output = Count

Output = Count makes the proposed count the payload passed to update_counter. The number of proposals stays in decrement's State, because only revisits to this node need it. The Agent visit implements the provider call and returns both values in Success.

Feature node ​

Source: ◆ subroutines/main/nodes/update_counter/__init__.py

python
from typing import Final
from verdog_runtime.declarations import Feature, FeatureNodeDefinition
from demo.countdown.subroutines.main import NODE_IDS

NODE: Final[FeatureNodeDefinition] = FeatureNodeDefinition(
    id=NODE_IDS.update_counter,
    name="Update counter",
    operation=Feature(),
)

update_counter uses FeatureNodeDefinition, so it declares neither Output nor a node-local State. Its update visit validates the incoming proposal and returns candidate feature state; the runtime forwards the original payload.

Enter port ​

Source: ◆ subroutines/main/nodes/enter/__init__.py

python
from typing import Final
from verdog_runtime.declarations import PortDefinition
from demo.countdown.subroutines.main import NODE_IDS

PORT: Final[PortDefinition] = PortDefinition(id=NODE_IDS.enter)

The declaration gives the enter port its typed identity. It has no operation or visit implementation. The subroutine's graph factory imports this PORT as ENTER and uses it as the starting point; exit and failure ports follow the same declaration shape.