Skip to content

Visit ​

A visit is the implementation selected by an incoming edge to an executable node. Its input type comes from that edge's source; non-Feature visits share their target node's output and state types. Different arrival paths may therefore accept different input types.

Authored files ​

Implementation ​

● impl.py contains the domain behavior for one arrival path. Its context exposes the dedicated output_dir for this execution of the visit. Countdown's Agent visit renders an authored prompt template and parses the provider's reply:

python
from pathlib import Path

from jinja2 import StrictUndefined, Template
from verdog_runtime.declarations import AgentAccess, Success
from . import Context, Input, Output, Result, State

def visit_impl(
    input: Input,
    state: State,
    context: Context,
    /,
) -> Result:
    template = Template(
        Path(__file__).with_name("prompt.md.j2").read_text(encoding="utf-8"),
        undefined=StrictUndefined,
    )
    reply = context.invoke(
        template.render(value=input.value),
        workspace=context.output_dir,
        access=AgentAccess.READ_ONLY,
    )
    return Success(
        output=Output(value=int(reply.strip())),
        state=State(attempts=state.attempts + 1),
    )

The adjacent authored ● prompt.md.j2 contains:

text
Return only the integer immediately before {{ value }}.

This example requires jinja2 in the workflow's requirements file. Template and parsing errors follow standard Python exception handling.

Signatures by target kind ​

TargetContext and resultAuthored responsibility
PythonNodeContext[Params] → Success[Output, State]Synchronously perform standard Python behavior and return the next output and state.
AgentAgentNodeContext[Params] → Success[Output, State]Synchronously invoke the configured agent zero or more times and return typed output and state.
FeatureNodeContext[Params] with FeatureState[StateScope] → FeatureSuccess[StateScope]Synchronously propose feature values; the runtime forwards the payload unchanged.
Subroutine callCallContext[Params, ChildParams, ChildInput, ChildOutput] → Success[Output, State]Synchronously invoke the child exactly once, optionally override its parameters, and adapt its result. Resource mappings come from the call declaration.
Workflow callCallContext[Params, ChildParams, ChildInput, ChildOutput] → Success[Output, State]Synchronously invoke the isolated workflow exactly once and adapt its result. A local target uses object for ChildParams; an external target also uses object for both child payload types.

Call visits ​

A call visit has one synchronous authored function. For example, a Subroutine call can adapt both sides of its child boundary as ordinary typed Python:

python
from verdog_runtime.declarations import Success

from . import ChildInput, Context, Input, Output, Result, State


def visit_impl(
    input: Input,
    state: State,
    context: Context,
    /,
) -> Result:
    child_output = context.invoke(
        ChildInput(value=input.value),
        params=context.child_params,
    )
    return Success(
        output=Output(value=child_output.value),
        state=state,
    )

context.invoke() is synchronous in a call visit. On its first evaluation, Verdog captures the child request and runs or resumes the child. With checkpointing enabled, that request and the eventual outcome are part of the durable call state. Once a child outcome is available, Verdog evaluates the adapter again and the same invocation returns the recorded output. A recorded child exception is raised at the invocation instead, so normal try/except Exception remains available. At a resumable boundary, only the child execution and its outcome are durable; both pre- and post-invocation adapter code can run again.

Because the adapter can be evaluated more than once, it is deterministic, replay-safe orchestration code rather than a general side-effect boundary. It must:

  • reach exactly one context.invoke() and reproduce the same child input and parameter override on every evaluation;
  • derive decisions only from its arguments and immutable local values; and
  • avoid unjournaled filesystem or network I/O, process launches, time and randomness reads, and mutable global state.

Do not catch BaseException around context.invoke(). Verdog reserves it for cancellation and its internal unwind control, and rejects an adapter that intercepts that control.

Put such effects in ordinary nodes or in the child graph. Pure construction of the child input and pure transformation of its output are safe. Verdog rejects a replay whose child request differs from the journaled request rather than silently invoking a different child. See Runs and resumption for the checkpoint guarantee.

Feature visits ​

A Feature visit receives FeatureState as its state argument and returns FeatureSuccess, which contains no replacement payload. Its candidate feature changes are accepted only when the selected outgoing edge permits them.

Each feature starts as None. Countdown's authored ● nodes/initialize_counter/visit/enter__initialize_counter/impl.py proposes the input value as the first counter value:

python
from verdog_runtime.declarations import FeatureSuccess
from . import Context, Input, Result, State
from demo.countdown.subroutines.main.features.counter import (
    FEATURE as COUNTER_FEATURE,
)

def visit_impl(
    input: Input,
    state: State,
    context: Context,
    /,
) -> Result:
    return FeatureSuccess(
        state=state.replace(COUNTER_FEATURE, input.value),
    )

The outgoing initialize_counter__check_counter edge declares the unconstrained effect counter?, which permits any initialized value in the feature's domain. It has no condition on counter: conditions read the pre-visit value, which is still None. The next node's outgoing conditions observe the committed integer.

Countdown's update_counter visit subsequently checks the exact decrement before proposing each replacement:

python
from verdog_runtime.declarations import FeatureSuccess
from . import Context, Input, Result, State
from demo.countdown.subroutines.main.features.counter import (
    FEATURE as COUNTER,
)

def visit_impl(
    input: Input,
    state: State,
    context: Context,
    /,
) -> Result:
    current = state.get(COUNTER)
    if current is None:
        raise ValueError("counter is uninitialized")
    if input.value != current - 1:
        raise ValueError("the proposed counter must be exactly one lower")
    return FeatureSuccess(
        state=state.replace(COUNTER, input.value),
    )

The outgoing edge independently requires a strict numerical decrease. The runtime checks this qualitative constraint after the authored visit validates the exact decrement.

Countdown example ​

Five of the six edges in Countdown enter executable nodes and therefore have visits:

Incoming edgeAuthored implementationBehavior
enter__initialize_counter● nodes/initialize_counter/visit/enter__initialize_counter/impl.pyPropose the initial counter from Count.value.
initialize_counter__check_counter● nodes/check_counter/visit/initialize_counter__check_counter/impl.pyForward the initial Count after counter is initialized.
check_counter__decrement● nodes/decrement/visit/check_counter__decrement/impl.pyInvoke the decrementer profile and parse its proposed integer.
decrement__update_counter● nodes/update_counter/visit/decrement__update_counter/impl.pyRequire next == current - 1 and propose the new counter value.
update_counter__check_counter● nodes/check_counter/visit/update_counter__check_counter/impl.pyForward the accepted Count into the next loop iteration.
check_counter__exit—No visit; exit is a control port.

Files ​

A visit lives below its target node and is named by the edge that selects it:

General visit structure

nodes/<target>/visit/<edge>/
├── ◆ __init__.py
├── ● impl.py
└── ● prompt.md.j2        optional Agent prompt

The edge ID distinguishes visits for parallel edges as well as edges with different sources.

FileRole
◆ __init__.pyResolves the exact Input type, defines the node-kind-specific adapter, and exports VISIT.
● impl.pyImplements this one arrival path. It exists for Python, Agent, Feature, and call targets.
● prompt.md.j2An optional prompt template authored for this visit; Verdog does not create it.

Edges into exit or failure create no visit directory.

Generated files ​

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