Appearance
Subroutine
A subroutine owns a directed graph, its input and output types, parameters, and lexically nested definitions. Its graph combines features, agent resources, nodes, edges, and visits. A subroutine call executes the graph in the caller's process and Python environment.
Authored files
Boundary values
● impl.py declares the type boundary seen by callers. The generated ◆ __init__.py imports and exposes these types:
| Type | Role |
|---|---|
Input | The payload entering through the enter port. |
Output | The payload returned through the exit port. |
Params | Immutable configuration for this definition; it does not flow along edges. |
The graph connects these boundary types through its generated visit declarations. The behavior of each visit belongs in its authored implementation.
Profiles and sessions may be defined locally or declared as resources that a caller must supply. A generated call-node declaration records the child parameter closure and maps those resource arguments.
Generated interface
The subroutine's generated ◆ __init__.py exposes the authored boundary types and the following declarations:
| Declaration | Role |
|---|---|
GRAPH_ID | The canonical graph identifier. |
StateScope | A Literal type containing the graph identifier, used to distinguish state belonging to different subroutines. |
NODE_IDS, EDGE_IDS, FEATURE_IDS | Immutable records of typed identifiers for directly owned nodes, ports, edges, and features. |
PROFILE_IDS, SESSION_IDS | Immutable records of typed identifiers for local resources and resource parameters. |
definition() | A cached factory returning SubroutineDefinition[Input, Output, Params, StateScope]. |
The factory imports the control-port declarations and component registries inside its body, then assembles a typed GraphDefinition. These imports occur when the factory is first called; the boundary types and identifiers are available before that assembly.
Registries
Collection modules import directly owned declarations and export tuples. They do not collect declarations from nested subroutines. Empty collections export (). Ports are assembled separately from executable nodes.
| Module | Exports |
|---|---|
features/__init__.py | FEATURES |
nodes/__init__.py | NODES (executable nodes only) |
edges/__init__.py | EDGES |
profiles/__init__.py | PROFILES, PROFILE_PARAMETERS |
sessions/__init__.py | SESSIONS, SESSION_PARAMETERS |
EDGES is collected in edge-ID order. This order does not establish routing priority: execution requires exactly one compatible outgoing edge. Nested workflows/__init__.py and subroutines/__init__.py are empty package markers; call declarations name their target modules directly. Workflow resource registries follow the workflow's interface.
Files
Each subroutine directory contains a generated declaration, component registries, package markers for nested definitions, and an authored boundary module.
General subroutine structure
src/<package>/subroutines/<subroutine>/
├── ◆ __init__.py
├── ◆ workflows/__init__.py
├── ◆ subroutines/__init__.py
├── ◆ profiles/__init__.py
├── ◆ sessions/__init__.py
├── ◆ features/__init__.py
├── ◆ nodes/__init__.py
├── ◆ edges/__init__.py
└── ● impl.py
Nested subroutines repeat this complete layout under subroutines/<owner>/subroutines/<id>/.
| File | Role |
|---|---|
◆ __init__.py | Exposes boundary types, declares typed identifiers, and assembles ports and component registries in the cached definition() factory. |
◆ workflows/__init__.py and ◆ subroutines/__init__.py | Empty package markers for nested definitions. |
Other collection ◆ __init__.py files | Collect directly owned features, profiles, sessions, nodes, and edges. |
● impl.py | Declares the immutable Params type and the subroutine's Input and Output types. |
Countdown example
Source: ● subroutines/main/impl.py
python
"""Input and output for the Countdown workflow."""
from dataclasses import dataclass
@dataclass(frozen=True, slots=True, kw_only=True)
class Count:
"""A non-negative countdown value; defaults support one-click execution."""
value: int = 10
def __post_init__(self) -> None:
if self.value < 0:
raise ValueError("the countdown value must be non-negative")
@dataclass(frozen=True, slots=True, kw_only=True)
class Params:
pass
Input = Count
Output = CountThe Countdown subroutine declares its boundary in this authored module. The same Count type crosses both ports, and Params has no fields. The non-negative check is ordinary authored Python; the annotation value: int does not express that constraint.
Source: ◆ subroutines/main/__init__.py
python
from __future__ import annotations
from dataclasses import dataclass
from functools import cache
from typing import Final, Literal
from verdog_runtime.declarations.ids import (
EdgeId,
GraphId,
NodeId,
FeatureId,
AgentProfileId,
AgentSessionId,
)
from verdog_runtime.declarations import GraphDefinition, SubroutineDefinition
from .impl import Input, Output, Params
GRAPH_ID: Final[GraphId] = GraphId('demo.countdown.main')
type StateScope = Literal['demo.countdown.main']
@dataclass(frozen=True, slots=True, kw_only=True)
class NodeIdentifiers:
check_counter: NodeId
decrement: NodeId
enter: NodeId
exit: NodeId
failure: NodeId
initialize_counter: NodeId
update_counter: NodeId
NODE_IDS: Final[NodeIdentifiers] = NodeIdentifiers(
check_counter=NodeId('check_counter'),
decrement=NodeId('decrement'),
enter=NodeId('enter'),
exit=NodeId('exit'),
failure=NodeId('failure'),
initialize_counter=NodeId('initialize_counter'),
update_counter=NodeId('update_counter'),
)
@dataclass(frozen=True, slots=True, kw_only=True)
class EdgeIdentifiers:
check_counter__decrement: EdgeId
check_counter__exit: EdgeId
decrement__update_counter: EdgeId
enter__initialize_counter: EdgeId
initialize_counter__check_counter: EdgeId
update_counter__check_counter: EdgeId
EDGE_IDS: Final[EdgeIdentifiers] = EdgeIdentifiers(
check_counter__decrement=EdgeId('check_counter__decrement'),
check_counter__exit=EdgeId('check_counter__exit'),
decrement__update_counter=EdgeId('decrement__update_counter'),
enter__initialize_counter=EdgeId('enter__initialize_counter'),
initialize_counter__check_counter=EdgeId('initialize_counter__check_counter'),
update_counter__check_counter=EdgeId('update_counter__check_counter'),
)
@dataclass(frozen=True, slots=True, kw_only=True)
class FeatureIdentifiers:
counter: FeatureId
FEATURE_IDS: Final[FeatureIdentifiers] = FeatureIdentifiers(
counter=FeatureId('counter'),
)
@dataclass(frozen=True, slots=True, kw_only=True)
class AgentProfileIdentifiers:
decrementer: AgentProfileId
PROFILE_IDS: Final[AgentProfileIdentifiers] = AgentProfileIdentifiers(
decrementer=AgentProfileId('decrementer'),
)
@dataclass(frozen=True, slots=True, kw_only=True)
class AgentSessionIdentifiers:
countdown: AgentSessionId
SESSION_IDS: Final[AgentSessionIdentifiers] = AgentSessionIdentifiers(
countdown=AgentSessionId('countdown'),
)
@cache
def definition() -> SubroutineDefinition[Input, Output, Params, StateScope]:
from demo.countdown.subroutines.main.nodes import NODES
from demo.countdown.subroutines.main.features import FEATURES
from demo.countdown.subroutines.main.profiles import PROFILES, PROFILE_PARAMETERS
from demo.countdown.subroutines.main.sessions import SESSIONS, SESSION_PARAMETERS
from demo.countdown.subroutines.main.nodes.enter import PORT as ENTER
from demo.countdown.subroutines.main.nodes.exit import PORT as EXIT
from demo.countdown.subroutines.main.nodes.failure import PORT as FAILURE
from demo.countdown.subroutines.main.edges import EDGES
return SubroutineDefinition(
graph=GraphDefinition[Input, Output, Params, StateScope](
id=GRAPH_ID,
params_type=Params,
enter=ENTER,
exit=EXIT,
failure=FAILURE,
nodes=NODES,
edges=EDGES,
features=FEATURES,
profiles=PROFILES,
profile_parameters=PROFILE_PARAMETERS,
sessions=SESSIONS,
session_parameters=SESSION_PARAMETERS,
),
)The generated module exposes the authored types and declares typed identifiers for the graph in project.json. StateScope identifies this subroutine's state at the type level.
The cached definition() combines Countdown's ports, executable nodes, edges, features, and agent resources in one graph. The workflow selects this subroutine and supplies its execution environment.
Countdown's feature registry contains the counter declaration. The factory above imports its FEATURES tuple when assembling the graph.