Appearance
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:
| Node | Kind | Role |
|---|---|---|
initialize_counter | Feature | Proposes input.value as the initial counter; its outgoing edge permits initialization with counter?. |
check_counter | Python | Forwards the current Count; outgoing edge conditions select the next step. |
decrement | Agent | Uses the decrementer profile and countdown session, then outputs the proposed next Count. |
update_counter | Feature | Proposes 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
| Kind | Structural declaration | Authored behavior |
|---|---|---|
| Python | Python() | One standard Python visit per incoming edge. |
| Agent | Logical profile and session IDs | One agent-aware visit per incoming edge. |
| Feature | Transparent feature-state operation | One feature visit per incoming edge; no node impl.py or local state. |
| Subroutine call | Target subroutine ID and parameter closure | A call visit invokes the child in the current process. |
| Workflow call | Target workflow ID and project path | A 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 = SubroutineOutputOutput 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
| File | Role |
|---|---|
◆ nodes/<node>/__init__.py | Declares NODE, including the operation and, for non-Feature nodes, the authored output and state types. |
◆ nodes/<control-port>/__init__.py | Declares the control port's PORT identifier. |
◆ nodes/<node>/visit/<edge-id>/__init__.py | Resolves that incoming edge's exact input type and declares the node-kind-specific visit adapter. |
◆ nodes/__init__.py | Imports every executable NODE in the subroutine and exports the NODES tuple. Control ports are assembled separately. |
● nodes/<node>/impl.py | Declares the successful Output and private State for every non-Feature executable node. |
● nodes/<node>/visit/<edge-id>/impl.py | Implements 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.