24. Graph
In plain words: Design rule 24: Step maps and pausing
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.
- 1Steps run serially; parallel branches, joins and map steps are rejected for now.
- 2Only declared, JSON-serializable state survives a pause; live clients and sessions stay in dependencies.
- 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
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.
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.