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.

Countdown example ​

The Countdown graph contains four executable nodes:

NodeKindRole
initialize_counterFeatureProposes input.value as the initial counter; its outgoing edge permits initialization with counter?.
check_counterPythonForwards the current Count; outgoing edge conditions select the next step.
decrementAgentUses the decrementer profile and countdown session, then outputs the proposed next Count.
update_counterFeatureProposes the new counter value while forwarding the payload unchanged.

The graph also contains the required enter, exit, and failure control ports. failure has no incoming edge in this example.

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.

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.

Authored files ​

Node types ​

For every non-Feature executable node, ● impl.py defines one output type and one immutable state type shared by all incoming visits:

python
from dataclasses import dataclass
from demo.countdown.subroutines.main import Output as SubroutineOutput

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

Output = SubroutineOutput

Output is the payload placed on outgoing edges after a successful visit. State persists across revisits within one subroutine invocation. The runtime constructs it with State() at invocation start and requires a frozen, slotted dataclass whose fields have immutable direct defaults. A field cannot use default_factory. Feature nodes and control ports have no node-level authored file. Per-edge behavior belongs to the visit.

Call boundaries ​

The generated call-node declaration records the child's ID, module, and project path. A subroutine call also imports ChildInput, ChildOutput, ChildParams, and every parameter type reachable through further subroutine calls. Its operation has this form:

python
SubroutineCall(
    definition_id=GRAPH_ID,
    definition_module="demo.countdown.subroutines.main.subroutines.worker",
    project_path=".",
    params_types=PARAMS_TYPES,
    profile_arguments={},
    session_arguments={},
)

Resource mappings are empty here because the example child has no profile or session parameters. Nonempty mappings bind child parameter IDs to caller resource IDs.

A workflow call declares the isolated target:

python
WorkflowCall(
    definition_id=GRAPH_ID,
    definition_module="demo.countdown.subroutines.main.workflows.review",
    project_path=".",
)

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, replay-safe adapter. The runtime may evaluate the adapter again while restoring the invocation, so call visits must not perform unjournaled I/O or depend on time, randomness, or mutable globals. Visit gives the complete contract.

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.

Generated files ​

See the generated declaration reference for the Python declarations and registries maintained by generation.