← Project laboratory · Chapter 2

Project: a document-processing workflow editor

Connect graph validation, node contracts, immutable task snapshots, and recovery in a document project, separating model extraction from authorized external effects.

After this guide, you can

Validate dependencies, define node states, and recover or reconcile effects within a versioned task.

A document-processing workflow editor connects reading, extraction, validation, and human review while showing each operation's result. Its first version can process one synthetic document type: read text, extract records, validate fields, and let a person decide whether to export them.

A visual canvas is an editing interface. Graph validity, node contracts, durable execution state, and side-effect permissions form the underlying system. Establish them before adding drag interactions or more node types.

Limit nodes to inspectable capabilities

A node definition includes a stable identifier, type, configuration, and input and output fields. Types come from a program-maintained allowlist, such as reading existing material, extracting specified fields, validating records, and requesting review. Text inside a document cannot create nodes or broaden permissions.

An extraction node may call a model, but its result remains data requiring validation. Program rules check fields and business constraints. An export node creates an external effect and needs an explicit destination and authority. Separate these roles to locate failures in prompts, data rules, or execution policy.

Use a directed acyclic graph, or DAG, initially. Reject self-loops and dependency cycles. Looping workflows need separate termination, iteration-state, and budget rules. Drawing a cycle does not establish that an executor knows when to stop.

Save an immutable execution snapshot

Separate graph editing versions from task execution versions. Starting a task freezes the graph, configurations, input-material versions, and relevant adapters. Subsequent canvas edits do not alter a partly executed task.

The task records overall state. Node runs record attempts, timings, input and output references, and error categories. Large documents belong in controlled storage with ownership references in state records. Persist recovery information without copying complete documents into logs by default.

Recoverable state is more than conversation history. The program must identify completed nodes, durable results, and unconfirmed side effects. Coordinate result storage and completion markers where possible. Across storage systems, define staging, confirmation, and reconciliation rather than assuming a global transaction.

Validate the graph before scheduling it

Check unique IDs, existing dependencies, allowed types, required configuration, and compatible ports when saving. Recheck permissions and resource bounds before execution so later edits cannot bypass earlier decisions.

Topological ordering makes dependencies run first; it does not require launching everything simultaneously. Independent nodes may run concurrently within limits on active work, input size, total time, and model budget. Failed dependencies block or skip downstream work rather than supplying an empty object as successful output.

Define state transitions explicitly. Waiting nodes become runnable after successful dependencies. Running nodes can finish, fail, or request human input. Cancellation prevents pending work, while already running external tasks require result verification. State names must match actual behavior.

Check dependencies and cycles on a small graph

The example repeatedly selects nodes whose dependencies have completed and orders ready nodes by ID. Unknown dependencies fail immediately. Remaining nodes without a runnable node indicate a cycle.

def order(graph):
    if any(dep not in graph for deps in graph.values() for dep in deps):
        raise ValueError("unknown dependency")
    done = []
    pending = set(graph)
    while pending:
        ready = sorted(node for node in pending if set(graph[node]) <= set(done))
        if not ready:
            raise ValueError("cycle")
        done.extend(ready)
        pending.difference_update(ready)
    return done
assert order({"parse": [], "review": ["parse"]}) == ["parse", "review"]
try:
    order({"a": ["b"], "b": ["a"]})
    raise AssertionError("cycle accepted")
except ValueError:
    pass
print(order({"parse": [], "review": ["parse"]}))

The output is ['parse', 'review'], and mutual dependencies are rejected. This offline check executes no nodes, saves no tasks, and calls no models. For readability it repeatedly scans pending nodes; it is not a linear-time implementation for large graphs. Evaluate algorithms and resource costs separately for a large executor.

Define recovery and retry conditions separately

Recovery reads frozen versions and confirmed results instead of attaching the current canvas to an old task. Reusing pure computations requires matching inputs, configuration, and relevant versions. Nodes depending on current external information also need a freshness policy.

Retries have attempt and total-budget limits and distinguish transient errors from invalid inputs. Each attempt has a record while the logical node retains one identity. Retrying extraction may recompute a result. Retrying an export, notification, or external write may duplicate effects and needs an operation key and verifiable external outcome.

A timeout may occur after the external operation completed. Query or reconcile before declaring that nothing happened. Human review approves a frozen output too; recomputed output cannot automatically reuse an earlier approval.

Preserve responsibilities when choosing a runtime

LangGraph supplies state-graph and durable-execution primitives. Dify Workflow and Chatflow provide their own orchestration and interaction models. Either can be an adapter, while the application still defines node authority, document ownership, validation, and recovery policy.

Start with a fixed graph expressed in code and complete reading, validation, and review. Add task status and recovery, then let a canvas edit that graph format. This separates editor verification from executor reliability.

Show node states, permitted output previews, and recovery actions. Error messages exclude credentials. A review prompt identifies the actual destination and effect rather than offering an unexplained continuation button.

Node contracts should distinguish required and optional fields. Missing required output fails validation before scheduling a dependent node; an empty string must not silently invite a model guess. A semantic field change needs a new version and explicit conversion, while old tasks retain their contract. Validate human-supplied updates through the same boundary and record the actor and changed fields.

Exercise: interrupt each boundary

Test unknown dependencies, cycles, incompatible fields, forbidden documents, and downstream failures. Graph errors must stop startup. Execution errors leave node and attempt records and prevent dependent work.

Then interrupt after extraction, before writing its completion marker, and before receiving an export response. Verify recovery uses the same graph version, reuses the right results, and recognizes potentially completed external effects. Pending nodes must not start after cancellation; define how existing effects are verified or compensated.

Sources & further reading

Sources checked on 2026-11-10. Original engineering explanation; examples are teaching data, not production measurements.