Skip to content

feat: local-only tutorial executor for chant run (no workflow runtime required) #73

@lex00

Description

@lex00

chant's Op composite currently requires Temporal for execution — chant run <op> spawns a Temporal worker and submits a workflow. That's the right shape for production deploys, but it's a real barrier in the Quick Start: a new user has to install Temporal (or sign up for Temporal Cloud) before they can run chant run hello.

A small local-only executor closes that gap without competing with Temporal. Single Node process, no durability across restarts, no gates, no schedules — just enough to run *.op.ts files end-to-end so the tutorial works in 30 seconds.

The graduation story: tutorial → local runner; production → Temporal lexicon (or, eventually, Inngest / Restate / etc.). Two clearly-scoped offerings, neither watered down.

Approach

A new mode for chant run that parses the Op directly from *.op.ts (chant already builds an Op IR during chant build) and executes phases sequentially in the current process. No codegen output, no SQLite, no worker, no daemon. Activated by chant run --local <op> and auto-detected when no Temporal lexicon is configured in chant.config.ts.

Lives in packages/core/src/runtime/local/. Reuses pre-built activities from the Temporal lexicon (shell, build, kubectlApply, helmInstall, stateSnapshot, stateDiff, etc. — they're plain Node functions; only the workflow side was Temporal-coupled).

Tasks

  • packages/core/src/runtime/local/executor.ts — walks the Op IR, runs phases sequentially (or in parallel when phase.parallel === true)
  • Activity invocation — call the activity function directly with declared args; honour outcomeAttribute by storing the result in an in-memory map keyed by __r0/__r1 and surfacing it to stdout (no search-attribute persistence)
  • Retry policy — honour the activity profile's maximumAttempts / startToCloseTimeout in-process (setTimeout for backoff, Promise.race for timeout)
  • onFailure handling — try/catch around main phases, run onFailure phases on rejection, re-throw at the end
  • Progress output — phase-name banners, per-step status, final summary. No UI; structured stdout is the surface.
  • chant run --local <name> flag wiring in packages/core/src/cli/handlers/run.ts
  • Auto-detect: if chant.config.ts has no Temporal lexicon configured, default to local mode and print a one-liner explaining the choice
  • Tests — unit tests for the executor against fixture Op IRs; smoke test for an existing shell-only Op running end-to-end without Temporal
  • Docs — new section in guide/ops.mdx ("Try it locally without Temporal") and a callout in Quick Start; explicitly document what's NOT supported (gates, schedules, durable cross-process state)

Done when

  • A user with chant installed but no Temporal can chant init → write hello.op.tschant buildchant run hello and see it work in under a minute
  • Existing Temporal-backed Ops continue to work unchanged (no regression)
  • Docs make the local-vs-Temporal trade-off explicit
  • Test suite covers happy path + onFailure + retry

Out of scope (intentionally)

  • Gates (gate() step) — require persistence across processes. Local mode errors with "use the Temporal lexicon for gate-based workflows."
  • Schedules (TemporalSchedule) — require an external scheduler. Same error.
  • Durable state across process restart — explicitly not supported; if the process dies mid-run, you start over.
  • Search attributes / chant run history — local mode has no persistence layer to query.
  • Worker pools / cross-process parallelism.
  • Web UI — stdout is the UI.

Why this and not a full internal runtime

A full internal runtime (SQLite-backed, durable, with gates and schedules) would be technically interesting but breaks chant's stated boundary (philosophy.mdx: "chant doesn't deploy"). It also imports permanent maintenance of state-machine edge cases that Temporal has spent years solving. The local executor is deliberately scoped to be a teaching tool, not a production runtime — anyone going to prod uses the Temporal lexicon (or eventually Inngest / Restate / etc. as additional lexicons).

Why now

The audit thread (#21#67) settled chant's observational-state story. The remaining friction in the Quick Start is the Temporal install. Closing that gap unlocks the tutorial path without committing chant to maintaining a real durable runtime.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions