Skip to content

Exceptions ​

Authored code uses Python's raise, try/except, and exception chaining. An unhandled exception aborts the current graph invocation. Outcomes that should select another graph path can instead be represented by payload or feature values.

Raising and catching ​

An exception raised by a visit propagates normally. For example, an in-process Subroutine call visit may catch the child's concrete exception type:

python
try:
    child_output = context.invoke(input)
except ValueError as error:
    # Return an ordinary Success when this visit can recover.
    ...

A call visit and its child execution use the synchronous runtime. Verdog records the child's outcome and may evaluate the deterministic adapter again. On replay, context.invoke() returns the recorded output or raises the recorded exception at the same expression; it does not execute a successfully completed child again. Code in the adapter, including code around the try statement, may run more than once and must remain side-effect-free.

Catch Exception or a concrete child exception, not BaseException, around a call invocation. Verdog reserves BaseException for cancellation and internal unwind control and reports an adapter that intercepts that control.

Verdog adds graph context through Python exception notes. Ordinary propagation within one process preserves the exception object, traceback, cause, context, and existing notes. At a durable Subroutine-call boundary, the child's failure artifact retains its original traceback. Replay raises the same exception object at context.invoke() with its type, cause, context, and notes preserved; the parent traceback begins at the replay boundary.

Fork path rebasing treats a recorded exception object as opaque. A custom exception that stores a run-owned absolute Path in its arguments or attributes therefore retains the source-run path after a fork. Use a relative path, or move fork-sensitive structured data into the call's typed output, input, parameters, or workflow state, whose Path values are rebased.

Failure boundaries ​

The failure port records failure and has no authored handler. When an uncaught Exception crosses the execution boundary of a graph, the runtime:

  1. adds the active project, graph, node, edge, and output path as exception notes where available;
  2. creates a numbered visit directory for the graph's failure port;
  3. writes stacktrace.txt there and records the directory in the run's trace.log; and
  4. re-raises the exception.

The file lives at:

text
<run>/.../<failure-port>/000001/stacktrace.txt

stacktrace.txt contains traceback.format_exception() output: Python frames, source locations, the exception type and message, displayed exception chains, and Verdog notes. Exception chaining follows Python's rules, including context suppression with raise ... from None. Logging describes the containing directory tree.

If an exception propagates through nested Subroutine calls, each graph boundary gets its own failure visit and stacktrace. If a parent call visit catches the child exception, the child's failure artifacts remain, while the parent may return Success and continue normally.

Definition loading, graph validation, and parameter lookup can fail before the runtime enters this exception boundary. Such errors have no failure-port stacktrace for that graph.

Explicit failure edges ​

An edge targeting the failure port is an explicit exceptional outcome. When selected, it discards the flowing value and raises RuntimeError; it does not invoke authored recovery code. The runtime then records the failure boundary in the same way as any other uncaught exception.

Use an edge to an executable node instead when the graph should recover and continue.

Call boundaries ​

The call kind determines which exception the parent process can receive:

Call kindException seen by the call visit
Subroutine callThe original exception object, because the child runs in the same process. Authored code can catch its concrete type.
Workflow callRemoteWorkflowError for an exception reported by the child process. Process startup and transport failures may instead raise local exceptions.

RemoteWorkflowError is available from verdog_runtime.declarations. Its exception_type contains the child's qualified Python type, and its remote_traceback contains the formatted child traceback. The same traceback is also attached as an exception note.

When an exception reaches the child's graph failure boundary, the child writes its artifacts before reporting the error to the parent. A call visit may catch, transform, or re-raise that error. These semantics are the same when Verdog replays a durable call outcome during resumption.

Interruptions ​

Graph failure recording catches Exception, not BaseException. ExecutionCancelled, KeyboardInterrupt, and SystemExit therefore bypass failure-port recording. If a Workflow subprocess terminates without reporting a result, its parent instead observes a process or transport failure, which can produce a parent failure-port stacktrace. Cooperative cancellation or Ctrl+C while Verdog owns a provider or Workflow subprocess terminates and reaps its process tree before the interruption continues upward. See Cancellation and deadlines.