Skip to content

feat: durable workflows engine runtime - #7

Merged
schplitt merged 1 commit into
mainfrom
feat/durable-workflows-engine
Jul 25, 2026
Merged

feat: durable workflows engine runtime#7
schplitt merged 1 commit into
mainfrom
feat/durable-workflows-engine

Conversation

@schplitt

Copy link
Copy Markdown
Owner

What

The engine runtime — the layer that ties the authoring surface + execution host to a persistence store, exposing the full instance lifecycle:

  • durableWorkflows({ store, plugins?, alias?, sandbox?, limits?, onEvent? })DurableWorkflowsEngine with create, get, continueWorkflow, terminate, evict, restart, dispose, pendingPromises.
  • memoryStore() — the in-memory reference WorkflowStore adapter (with a deploy seeding helper standing in for the app's deploy layer).

How it works

Every lifecycle call is one replay turn: load the instance record + boundary cache from the store → resolve the pinned definition version via the store's getDefinition → hydrate (cached per pinned version) a runner mounted with the plugin shims + alias re-export modules → execute with per-run handlers → persist the grown cache + new status → return a RunOutcome.

Design decisions

  • The store is ONE persistent world: instances, opaque cache blobs, and READ-ONLY definition access. getDefinition(name) answers "what should a new instance get"; getDefinition(name, pinnedVersion) the exact code for a replay. Writing definitions (upload/versioning/rollback) is deliberately the application's deploy layer against the same backend — contract: a handed-out (name, version) stays fetchable and byte-identical while instances pin it.
  • Handler input without touching the kernel: the durable-workflows:internal operation shim forwards its boundary key as the leading arg (durableCall(key, name, key, ...args)); the engine strips it and hands plugin handlers the structured DurableHandlerInput { instanceId, workflow, run, stepId, payload } with payload = the full forwarded argument list.
  • Resume is plain re-execution — no tokens, no delivery API. Retry/scheduling/wake-ups stay caller-side.
  • evict removes the boundary, everything after it by seq, AND its key-prefix subtree (a scope's children commit before the scope, so a seq-only prune would leave them cached).
  • Limits now flow through the host: engine defaults ← options.limits ← per-definition limits.
  • Errors: kernel/iso4 RunError lifted into SerializedError { name, message, stack?, data?, class? }; class picked up only if a plugin attached a permanent/transient verdict.

Tests

tests/engine.test.ts drives the whole chain end-to-end (engine → shims → real kernel sandbox → handlers): create + version pinning, idempotent create, handler-input shape, suspend → continue, resume on a fresh engine against the pinned version, evict/restart, terminate, failure mapping, alias whitelabeling, onEvent + pendingPromises, definition immutability. 46 tests green across both packages; build/lint/typecheck clean.

Docs

AGENTS.md (three shipped layers, architecture tree, exports) and the package README.md (new "Running workflows: the engine" section) updated; changeset included (durable-workflows, minor).

@schplitt
schplitt merged commit 5bdc348 into main Jul 25, 2026
1 check passed
@schplitt
schplitt deleted the feat/durable-workflows-engine branch July 25, 2026 19:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant