24. Graph

In plain words: Design rule 24: Step maps and pausing

Partial 9 min read 17 glossary terms 18 connected pages
The short version written for the Snayu team

Defines how a workflow runs as a Step Map (Graph) using Pydantic Graph: serial steps, checked against a pinned manifest, that can checkpoint, Pause (Stasis) for approvals or hardware, and resume without replaying from the start.

Why read it: Read this before writing workflow steps or anything that must pause and resume safely.
  1. 1Steps run serially; parallel branches, joins and map steps are rejected for now.
  2. 2Only declared, JSON-serializable state survives a pause; live clients and sessions stay in dependencies.
  3. 3A step returning an undeclared next step is rejected before that step runs.

Partial The serial engine works; pause and approval resume are partly proven.

Words on this page, decoded click one for its card

Traffic Control…WorkersWorkflow EnginePauseStep Map
Where it sits in LychD

Step Map Graph

The map of steps a workflow moves through, with save points so it can pause and resume.

It's like: A board game track with save points.

Click a dot to open that part's card.

The original text Underlined words open their plain-English card.

Context

AnA InvocationJob Request must survive deterministic work, capability waits, consent, and bounded foreign labor. That requires a recoverable control boundary, not a process image or a second authority ledger.

Decision

LychD uses Pydantic GraphStep Map v2's native GraphBuilder with serial BaseNode stations. A Workflow binds typed GraphStep Map, start-node type, admitted-Intent state factory, deterministic routing trigger, and immutable PatternManifest. Each BaseNode receives mutable state and run-scoped dependencies and returns its next node or End. Python return types declare intended transitions. LychD compares the native builder routes to the pinned manifest and rejects an undeclared returned successor or End before any successor executes. Typed shape and declared routing still cannot prove hidden premises or the meaning of a mutation.

The delivered engine is serial. Broadcast, map/spread, joins, reducers, arbitrary function steps, and parallel execution have no admitted checkpoint contract and are rejected by this adapter.

State and dependencies

Resume retains only declared continuation data in a JSON-round-tripping Pydantic model. Live models, grants, toolsets, sessions, connectors, and service handles remain in deps; any messages needed after a park must first be serialized. Frames, token fragments, temporary objects, and uncommitted work may be lost. This boundary preserves declared memory without claiming total recall.

Pattern binding

PatternManifest is SpellweaverWorkflow Engine's renderer-neutral score: URL-safe key/revision, checkpoint-schema id, unique stations, unique permitted edges, and deterministic digest. Every executable station maps one-to-one to a Python node; mapped implementations equal the node set; delegated stations use DelegatedAgentNode; edge endpoints are declared. The digest proves declared snapshot, not source equivalence or every return path.

In the accepted workflow grammar, one station places a SpellStep contract. The current manifest has no independent SpellStep identity or catalogue: its stations are legacy inline placements bound directly to Python nodes. PatternManifest is therefore not the portable ScrollWorkflow Version ABI.

Admission pins the full manifest. Before execute or resume, worker requires that snapshot to remain valid and exactly equal the registered revision; a revision mismatch fails instead of silently changing score. Fresh execution must begin at the builder's declared entry station; only a validated durable cursor may enter at a later station. Fresh calls retain the caller's state and node objects while checkpoint storage retains detached copies. Both delivered BridgeChat revisions validate fresh and restored state against admitted RunJob Record identity, session, prompt, priority, and capability binding before node execution. Workflow.mermaid() is a projection.

Boundary metadata

A future contribution declares execution plane, eligible local/PortalCloud Model providers, local-only/egress behavior, writes, delegation, quarantine, label propagation, and required declassification receipt, Gate, or consent. station.kind remains mechanics, not a generic danger bit. LoomWorkflow Viewer may badge that declaration; occurrence evidence must name actual provider, decision, payload digest, receipt, and result. Current manifests cannot prove it. Context owns labels; Security owns declassification.

Execution and occurrence identity

perform_run is the single execution site: it rejects stale/duplicate delivery, claims exact enqueue sequence, verifies pinned PatternWorkflow, builds run services, then invokes GraphRunner. Each node attempt gets a process-local station-attempt correlation and an entered, settled, waiting (hardware/consent/delegate), or failed event. Its delivered field remains the legacy occurrence_id; canonical SpellweaverWorkflow Engine Occurrence instead names a schedule or external-trigger firing before InvocationJob Request admission. Dispatch adds its grant event only after lease admission; OrchestratorStage Manager events retain the station-attempt correlation. Events observe; they neither recover work nor establish a global order. Ordinary observer exceptions are logged and cannot replace completion, the original execution failure, or a checkpointed wait. Cancellation still propagates.

Capability handshake

Capability nodes ask Dispatcher, never bind runtimes directly. On HardwareTransitionRequired GraphRunner finds the signal even through a task group, snapshots state only when the exception tree resolves unambiguously to that signal; mixed signal/failure groups fail closed rather than swallowing a sibling. It snapshots state and current node, marks waiting, sets AWAITING_HARDWARE, asks Orchestrator to converge, returns RUNNING, resumes persistence, and dispatches again. Default limits are eight hardware resumes per RunJob Record and three consecutive requests for one capability; exceeding either fails. The counters live in checkpointed RunJob Record state, so a durable park or replacement GraphRunner cannot reset the budget. A pre-budget legacy checkpoint decodes with zero counters because past attempts cannot be reconstructed retroactively. The waiting run holds no lease and does not know whether convergence was a load, systemd swap, or no effect.

Live and Durable Stasis

StasisPause has two lifetimes. Live StasisPause keeps the loop resident; the current linear hardware wait uses an in-memory LiveStasisPhylactery. Durable StasisPause saves a mandatory checkpoint, exits the worker, and re-enters through a new queue claim. Gate, DelegatedAgentNode, and PeerTaskNode select the durable tier automatically.

Park Signal Run status
Human verdict ConsentPending AWAITING_CONSENT
Delegated job DelegatedAgentPending AWAITING_DELEGATE
Sovereign peer task PeerTaskPending AWAITING_PEER
Service job (Designed) ServiceJobPending AWAITING_SERVICE

Long Sleep means a durable wait that outlives the worker. The native public peer station uses this same checkpoint and owner-specific re-admission rule. VesselEngine Room lifecycle waits remain designs. AWAITING_SERVICE is accepted law but has no source, persistence, or relay yet. Missing checkpoint fails as stasis lost; process death in RUNNING or AWAITING_HARDWARE recovers only an exact ConsentApproval Step, delegated or peer pre-park boundary bound by the first resumable checkpoint; otherwise startup contains correlated effects and records failure. It never guesses a replay. Reanimation keeps the state-by-state return conditions.

For a future service job, the owning station persists ServiceJobAttempt@1 before first submit, checkpoints the same owner, then parks. Re-entry reads terminal attempt truth and dispatches afresh; it never resumes a Connector, grant, provider SDK object, or process handle. Unknown external effect remains contained rather than being repeated. Workers (14) owns the common attempt and recovery mechanics; the CompositionApp/domain contract owns result meaning and acceptance.

Checkpoint Ownership and Terminal Commit

DurableStasisPhylactery stores one Run-owned snapshot document. PostgreSQL replaces complete validated snapshot history in one JSONB row with INSERT ON CONFLICT UPDATE; memory uses defensive process-local copies. A snapshot contains typed state, next node, status, and completed/end snapshots—not dependencies or event stream.

The native builder has no persistence API. LychD owns the snapshot codec and execution statuses; GraphRunner invokes the public native iterator and task-request API to execute one station at a time. It records running before the native node call, records success/error afterward, and retains the next station or final result before reporting settlement. Re-entry injects the decoded node at the native iterator's initial boundary before any station runs. Native task ids and fork stacks remain local to that invocation.

The codec preserves the supported v1 wire envelope while validating state and output against the pinned graph and resolving node ids only from its declared classes. Unknown nodes or node fields, duplicate snapshot ids, and multiple created cursors fail explicitly. A pending snapshot remains decodable but is not automatically replayed; neither running nor error status grants a new attempt. The node_id field belongs to the checkpoint envelope; node constructors cannot declare it. Constructor fields are checked before writing and after reading, so serialization cannot silently replace a node value with envelope metadata or retain fields the decoder cannot restore.

GraphRunner can create/resume snapshots but cannot decide their deletion. Durable checkpoint deletion requires committed DONE, FAILED, or CANCELLED RunJob Record truth. A worker that owns terminal settlement releases run-scoped context and attempts checkpoint deletion before publishing its terminal event and draining its Step evidence.

API cancellation follows Workers: a worker observing CANCELLING releases volatile execution resources for containment acknowledgement while retaining its checkpoint. After CANCELLED commits, the engine handles terminal evidence and channel closure before best-effort resource cleanup; failed evidence persistence does not prevent that cleanup. For sovereign peers, required containment means durable local adoption closure and actual local exchange retirement under Workers law. Retained remote uncertainty cannot resume the GraphStep Map and is not erased with its checkpoint. This changes no local AgentJob/process containment obligation.

Cleanup is best-effort. Failed deletion retains checkpoint for reconciliation; committed terminal truth remains authority. Repository evidence proves adapter and memory-profile recovery, not a real PostgreSQL consent-plus-checkpoint restart, schema migration, transactional Step-event outbox, or distributed fence.

Consent re-entry

A Gate stores serializable message suffix/call ids, snapshots itself, and parks. Worker commits the consent relation and AWAITING_CONSENT; one guarded verdict edge admits QUEUED and the next worker resumes the same GraphStep Map. Typed checkpoint shape alone does not authorize that re-entry: before the first node executes, its Gate/delegated station kind and retained owner id must match the RunJob Record's single current wait owner. Workers (14) owns replacement of that relation across chained or mixed waits; historical records remain evidence. One approval call per model round, with bounded chained rounds, is current; ADR 25 owns verdict order and recovery.

Delegated Agent Macro-Nodes

DelegatedAgentNode is one typed opaque station. Graph owns purpose and result routing, not the foreign planner, subagents, tool loop, or events. DelegatedAgentRequest names request/run/step, exact runtime adapter, read/candidate/verify containment, bounded prompt, and immutable input ArtifactRefs.

AgentJob follows:

QUEUED → ADMITTED → PREPARING → RUNNING → terminal

Terminals are SUCCEEDED, FAILED, CANCELLED, TIMED_OUT, LOST. Request id is idempotency key: different reuse fails. For result adoption and ordinary polling, the first admitted terminal wins and late results are inert. LOST records indeterminate external truth and never authorizes automatic repetition. Explicit cancellation still contains the owning runtime and may record LOST → CANCELLED only after containment returns, as required by Workers.

delegated_rite@1 proves no-effect reference submit, park, adoption, re-admission, and projection. It performs no subprocess, provider, network, credential, or workspace effect. Runtime selection is exact-name only, not DispatcherTraffic Controller grant or OrchestratorStage Manager capacity; memory store is process-local. Optional delegated_rite@2 retains the configured exact runtime/profile in admitted RunJob Record intent and state. Its checkpoint must match that binding; current Settings cannot redirect resumed work. It preserves the same no-replay and uncertain-outcome rules, with no automatic fallback. The artifact-reference receipt covers a bounded PostgreSQL metadata round-trip through job creation and result adoption, using tables created from schema metadata. It does not establish migration execution, process restart, provider effects, GraphStep Map park/resume, or artifact-byte custody.

CoffinSandbox, Provider Gate, effectful adapters, cancellation, artifact custody, budgets, and cross-process recovery retain their State boundaries. Provider testimony is labelled, bounded evidence, never hidden reasoning.

Runtime migration

The installed version is owned by Agents and the lockfile. The v2 migration uses the public builder and BaseNode interoperability; it does not retain the removed v1 runner or vendor upstream persistence. Official builder documentation and the migration map explain that native graph snapshotting is absent. The LychD serial adapter preserves the existing RunJob Record/StasisPause boundary instead of introducing an external durability engine or agent-Harness authority.

Captured Pydantic GraphStep Map 1.107.5 consent, delegated, and hardware-wait documents exercise the owned decoder in tests/integration/test_graph_checkpoint_codec.py. The captured consent continuation runs on the native builder with either verdict; pending remains nonreplayable. These receipts do not establish process restart, database schema migration, or broker recovery. Parallelism and external durability remain separate designs.

Future parallel topology

Parallel execution needs an adapter that retains typed isolated branch state, fan-out identity/parentage, declared join/reduction, bounded concurrency/capability admission, checkpoint compatibility, and cancellation settlement for every branch. Its evidence must distinguish topology from factual agreement. A reducer applies a predicate; consensus, first completion, or a model judge cannot thereby establish truth.

Runtime evidence

Record Answers
Pinned PatternWorkflow manifest What passage was permitted?
Station-attempt events (legacy occurrence_id) Which station attempts were observed?
Dispatch/transition events Which grant and hardware change correlated?
Run ledger What lifecycle truth settled?
Graph checkpoint Where may execution lawfully resume?

Event replay is process-local. A terminal manifest station is declarative: Pydantic GraphStep Map returns End directly, so observers must not fabricate a terminal-node occurrence.

Following a pause and return

Stasis and return follows a running or parked workflow. Reanimation explains how a new VesselEngine Room judges the durable records after process loss.

Consequences

Accepted

  • Serial typed execution has bounded hardware recovery, durable parks, pinned revisions, and terminal-before-cleanup law.
  • Later parallelism has a narrow compatibility seam.

Cost

  • Checkpoint schemas require deliberate versioning; durable replacement rewrites whole history.
  • Live waits die with process; valid routing cannot prove factual correctness or effect meaning.

Verification

State round-trip/tier-selection, GraphRunner, worker, consent, delegation, checkpoint, and event tests cover the current path. State of Work separates those memory-profile facts from PostgreSQL and external-runtime receipts.