Appearance
Generated declarations
This reference describes the Python declarations produced from a project graph. For authored implementations and examples, see the entity pages and Running example. Change graph declarations through the graph operations; generation maintains the files shown here.
Registries
Within a subroutine, collection modules import directly owned declarations and export tuples. Empty collections export (). Nested workflows/__init__.py and subroutines/__init__.py are package markers; call declarations name their target modules directly.
| Module | Exports |
|---|---|
features/__init__.py | FEATURES |
nodes/__init__.py | NODES (executable nodes only; ports are assembled separately) |
edges/__init__.py | EDGES |
profiles/__init__.py | PROFILES, PROFILE_PARAMETERS |
sessions/__init__.py | SESSIONS, SESSION_PARAMETERS |
For example, Countdown's feature registry contains:
python
from typing import Final
from demo.countdown.subroutines.main.features.counter import (
FEATURE as COUNTER_FEATURE,
)
FEATURES: Final = (COUNTER_FEATURE,)Edge declarations are collected in edge-ID order. This order does not define routing priority: execution requires exactly one compatible outgoing edge. Workflow profile and session registries are described under Workflow.
Feature
Feature declaration
For the Countdown integer feature, ◆ features/counter/__init__.py contains:
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,
)An enum declaration additionally records its permitted values. The type parameters identify the feature's value type and owning state scope. The runtime validates values proposed by Feature visits against the declared kind and domain.
Profile
Profile declaration
A local profile constructs the invoker specified by its configuration in ◆ project.json. For a Codex profile, the generated declaration includes:
python
INVOKER: Final[AgentInvoker] = CodexInvoker(
model=None,
reasoning_effort=None,
extra_args=(),
)
def configure(input: Input, params: Params, /) -> AgentInvoker:
return INVOKER
PROFILE: Final[AgentProfileDefinition[Input, Params]] = AgentProfileDefinition(
id=PROFILE_IDS.planner,
name="Planner",
implementation=configure,
)The generated configure() accepts the subroutine's input and parameters but returns the same configured invoker. A Claude profile constructs ClaudeInvoker instead.
Profile parameter
A profile parameter declares a resource supplied by the caller. Because a parameter has no single definition, the declaration also names every concrete profile it resolves to through the call paths inside this project, so the configuration a node will run with is one "go to definition" away:
python
from demo.countdown.workflows.main.profiles.decrementer import (
INVOKER as WORKFLOW_MAIN__DECREMENTER_INVOKER,
)
PROFILE_PARAMETER: Final[AgentProfileParameter] = AgentProfileParameter(
id=PROFILE_IDS.decrementer,
name="Decrementer",
)
RESOLVED_INVOKERS: Final = (WORKFLOW_MAIN__DECREMENTER_INVOKER,)Resolution follows a workflow entry's profile_arguments to the workflow's concrete profile, and a subroutine call's profile_arguments to the caller's local profile or, when the caller passes one of its own parameters on, further up to that parameter's bindings. Aliases are prefixed WORKFLOW_ or SUBROUTINE_ and carry the binding definition's canonical identifier. A parameter bound only by callers outside this project resolves to an empty tuple. RESOLVED_INVOKERS is informational: the runtime binds parameters at the call.
Session
Session declaration
A local session records its persistence policy:
python
SESSION: Final[AgentSessionDefinition] = AgentSessionDefinition(
id=SESSION_IDS.countdown,
name="Countdown",
persistent=True,
)Session parameter
A session parameter names a resource supplied by the caller; its persistence policy belongs to the supplied session:
python
from demo.countdown.workflows.main.sessions.conversation import (
SESSION as WORKFLOW_MAIN__CONVERSATION_SESSION,
)
SESSION_PARAMETER: Final[AgentSessionParameter] = AgentSessionParameter(
id=SESSION_IDS.conversation,
name="Conversation",
)
RESOLVED_SESSIONS: Final = (WORKFLOW_MAIN__CONVERSATION_SESSION,)RESOLVED_SESSIONS lists the concrete sessions the parameter resolves to through the call paths inside this project, following the same rules as profile parameters; it is informational and does not affect binding.
Node
Executable node declaration
The Countdown Agent node's ◆ nodes/decrement/__init__.py imports Output and State from its authored sibling and declares:
python
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,
),
)A Python node uses the same NodeDefinition with operation=Python(). Call nodes additionally declare their resolved targets and boundary types.
Feature node declaration
A Feature node uses a specialized declaration because it has no output or local state types of its own:
python
NODE: Final[FeatureNodeDefinition] = FeatureNodeDefinition(
id=NODE_IDS.update_counter,
name="Update counter",
operation=Feature(),
)Control-port declaration
The generated file for each control port contains only its typed identifier:
python
PORT: Final[PortDefinition] = PortDefinition(
id=NODE_IDS.enter,
)Visit declarations
Each incoming edge to an executable node creates a generated visit declaration below that node. Its adapter and exported aliases are shown under Visit: Generated files.
Edge
Edge declaration
For the first Countdown edge, ◆ edges/enter__initialize_counter/__init__.py contains this declaration:
python
from typing import Final
from verdog_runtime.declarations import EdgeDefinition
from demo.countdown.subroutines.main import EDGE_IDS, NODE_IDS
from demo.countdown.subroutines.main.nodes.initialize_counter.visit.enter__initialize_counter import (
VISIT,
)
EDGE: Final[EdgeDefinition] = EdgeDefinition(
id=EDGE_IDS.enter__initialize_counter,
source=NODE_IDS.enter,
target=NODE_IDS.initialize_counter,
name="Initialize counter",
conditions=(),
effects=(),
visit=VISIT,
)When the target is executable, the declaration imports VISIT from the target's generated visit module. Edges into exit or failure set visit=None. An exit edge also includes a function whose return annotation lets a type checker compare the source payload with the subroutine output:
python
def check_exit_output(value: SourceOutput, /) -> GraphOutput:
return valueThe runtime does not call this function. Reaching the failure port raises RuntimeError without returning a payload.
Visit
Input type
The generated declaration makes Input exactly the source payload type:
- from enter: the subroutine
Input; - from a non-Feature executable node: that source node's
Output; - from a Feature node: the payload type inferred through its upstream paths.
Each incoming edge has a separate declaration, so its input type does not include unrelated arrival paths. When the source is a Feature node, multiple upstream producer types form a generated union. The runtime passes the payload without conversion.
A non-Feature visit receives that input, the target node's local State, and its context. The call signature exposes neither feature state nor another node's state; values needed from another step belong in the input payload.
Adapter
For an Agent node, the visit's generated ◆ __init__.py imports the source Input, target Output and State, and subroutine Params, then declares:
python
Context = AgentNodeContext[Params]
Result = Success[Output, State]
def visit(
input: Input,
state: State,
context: Context,
/,
) -> Result:
from .impl import visit_impl
return visit_impl(input, state, context)
VISIT: Final[VisitDefinition] = VisitDefinition(
implementation=visit,
)Python, Agent, and Feature visits use the same synchronous forwarding shape, with the context and result specialized to their target kind. The adapter imports visit_impl only when invoked, so authored visits can import their generated aliases without a module-initialization cycle. The generated declaration for the selecting edge imports VISIT; there is no separate visit registry.
Call targets use the same authored name and synchronous calling convention. Their generated declaration exposes a typed Context alias backed by CallContext and wraps the one implementation in CallVisitDefinition. Call authors do not implement separate preparation and completion functions.
python
Context = CallContext[Params, ChildParams, ChildInput, ChildOutput]
Result = Success[Output, State]
def visit(
input: Input,
state: State,
context: Context,
/,
) -> Result:
from .impl import visit_impl
return visit_impl(input, state, context)
VISIT: Final[
CallVisitDefinition[
Input,
Output,
State,
Params,
ChildInput,
ChildOutput,
ChildParams,
]
] = CallVisitDefinition(implementation=visit)Subroutine
Graph declaration
The generated ◆ __init__.py imports Input, Output, and Params from its authored sibling. It declares the graph identity and the type-level state scope:
python
GRAPH_ID: Final[GraphId] = GraphId("demo.countdown.main")
type StateScope = Literal["demo.countdown.main"]
@dataclass(frozen=True, slots=True, kw_only=True)
class FeatureIdentifiers:
counter: FeatureId
FEATURE_IDS: Final[FeatureIdentifiers] = FeatureIdentifiers(
counter=FeatureId("counter"),
)Equivalent records expose NODE_IDS, EDGE_IDS, PROFILE_IDS, and SESSION_IDS. The cached factory imports component registries and control-port declarations before constructing the graph; those imports are omitted below:
python
@cache
def definition() -> SubroutineDefinition[Input, Output, Params, StateScope]:
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,
),
)Workflow
Binding declaration
The generated ◆ bindings/__init__.py exposes the workflow's profile invokers as runtime configuration:
python
from typing import Final
from demo.countdown.workflows.main.profiles import INVOKERS
from verdog_runtime.declarations import WorkflowConfiguration
CONFIGURATION: Final[WorkflowConfiguration] = WorkflowConfiguration(
profile_arguments=INVOKERS,
)The entry call maps the subroutine's profile parameters to IDs in this configuration.
Workflow declaration
The generated ◆ __init__.py imports Input, Output, Params, and StateScope from the entry subroutine. For Countdown, its declarations include:
python
PARAMS_TYPES: Final[dict[tuple[str, GraphId], ParameterType]] = {
(".", ENTRY_GRAPH_ID): Params,
}
@dataclass(frozen=True, slots=True, kw_only=True)
class RuntimeOptions:
pass
def configure(options: RuntimeOptions, /) -> WorkflowConfiguration:
return CONFIGURATION
GRAPH_ID: Final[GraphId] = GraphId("demo.countdown.main")
ENTRY: Final[SubroutineCall] = SubroutineCall(
definition_id=ENTRY_GRAPH_ID,
definition_module="demo.countdown.subroutines.main",
project_path=".",
params_types=PARAMS_TYPES,
profile_arguments={
AgentProfileId("decrementer"): AgentProfileId("decrementer"),
},
session_arguments={},
)
@cache
def definition() -> WorkflowDefinition[Input, Output, Params, StateScope]:
return WorkflowDefinition(
id=GRAPH_ID,
input_type=Input,
entry=ENTRY,
sessions=SESSIONS,
configuration=CONFIGURATION,
)PARAMS_TYPES includes the entry subroutine and every subroutine reachable by in-process calls. RuntimeOptions is currently an empty generated dataclass; provider settings come from the project declaration.
Profile declarations and registry
For Countdown, ◆ profiles/decrementer/__init__.py constructs the configured provider:
python
from typing import Final
from verdog_runtime.agents.codex import CodexInvoker
from verdog_runtime.declarations import AgentInvoker
INVOKER: Final[AgentInvoker] = CodexInvoker(
model=None,
reasoning_effort=None,
extra_args=(),
)The generated ◆ profiles/__init__.py collects workflow profiles by ID:
python
from typing import Final
from verdog_runtime.declarations import AgentInvoker
from verdog_runtime.declarations.ids import AgentProfileId
from demo.countdown.workflows.main.profiles.decrementer import (
INVOKER as DECREMENTER_INVOKER,
)
INVOKERS: Final[dict[AgentProfileId, AgentInvoker]] = {
AgentProfileId("decrementer"): DECREMENTER_INVOKER,
}Session declarations and registry
A workflow that owns a session named conversation receives a generated ◆ sessions/conversation/__init__.py:
python
from typing import Final
from verdog_runtime.declarations import AgentSessionDefinition
from verdog_runtime.declarations.ids import AgentSessionId
SESSION: Final[AgentSessionDefinition] = AgentSessionDefinition(
id=AgentSessionId("conversation"),
name="Conversation",
persistent=True,
)Its generated ◆ sessions/__init__.py collects the declaration:
python
from typing import Final
from demo.countdown.workflows.main.sessions.conversation import (
SESSION as CONVERSATION_SESSION,
)
SESSIONS: Final = (CONVERSATION_SESSION,)Countdown instead owns its session in the subroutine, so the workflow registry exports SESSIONS: Final = (). A workflow with no profiles exports INVOKERS: Final[dict[AgentProfileId, AgentInvoker]] = {}.