Skip to content

Ruebennase/Recause

Repository files navigation

Recause

Experimental programming model and reference implementation

There is another way to write interactive software.

Write a normal flow.
Stop when information is missing.
Re-run when new state arrives.

Website · Live demonstration


Installation

npm install @recause/core

The current npm package contains the JavaScript reference runtime.


Recause is an experimental programming model and reference implementation for incremental, resumable interaction flows.

A Recause flow is written as ordinary code. The current reference implementation uses JavaScript. The runtime evaluates that function against explicit state. When a derived framework reaches information that is not yet available, it presents the appropriate interaction and stops the flow. When the missing state becomes available, the same function runs again from the beginning.

Earlier operations resolve immediately from state. Execution reaches the next unsatisfied point.

RUN → STOP → STATE → RUN

The result is code that often reads like the interaction itself, without splitting the domain flow across event handlers, callbacks, component lifecycles, workflow nodes, and explicit resume logic.

Project status

Recause is an experimental programming model and reference implementation published under the MIT License.

The repository exists primarily to document the model, provide a working implementation, and encourage discussion and independent experimentation. Development is currently maintainer-driven, and the time available for answering questions, reviewing issues, or evaluating contributions is limited.

I do participate in GitHub Discussions, but responses and follow-up cannot be guaranteed. If the ideas are useful to you, please feel free to fork the project, build on it, and share what you learn. Independent implementations, experiments, articles, and comparisons are especially welcome.

Recause does not preserve a suspended JavaScript call stack.
Progress is represented by explicit, serializable state.

The idea in one example

A small chat-style flow can be written as one function:

function registrationFlow(chat) {
  chat.say("Welcome.");

  chat.askText("What should we call you?", "name");

  chat.askRadio("What are you building?", "goal", [
    { value: "form", label: "A form" },
    { value: "chat", label: "A guided chat" },
    { value: "flow", label: "A larger interaction flow" }
  ]);

  const name = chat.getValue("name");
  const goal = chat.getValue("goal");

  chat.say(`Hello ${name}. Let us shape your ${goal}.`);
}

Run it with a ChatWizard:

const container = document.getElementById("app");

const wizard = new ChatWizard(
  container,
  null,
  registrationFlow
);

wizard.runFlow();

On the first run, name is missing:

registrationFlow()
  say("Welcome.")                 ✓
  askText("...", "name")          STOP

The user answers. The value is written to state. The same function runs again:

registrationFlow()
  say("Welcome.")                 ✓
  askText("...", "name")          ✓  resolved from state
  askRadio("...", "goal")         STOP

After the second answer, the flow runs again and reaches its end:

registrationFlow()
  say("Welcome.")                 ✓
  askText("...", "name")          ✓
  askRadio("...", "goal")         ✓
  say("Hello …")                  ✓

No continuation object is authored. No transition table says where to resume. The accumulated state causes more of the same program to become executable.

What Recause actually does

At the engine level, the model is deliberately small:

  1. Evaluate a flow function.
  2. Let the flow read explicit state.
  3. Stop when required state is missing.
  4. Acquire or produce that state.
  5. Evaluate the flow again.
  6. Continue until the flow completes.

In the reference implementation, intentional suspension and re-entry are represented by two control signals:

throw new StopFlow();
throw new RestartFlow();

These are expected runtime control signals, not application failures.

The application flow normally does not catch them. The Recause engine does.

flowchart LR
    A[Run flow from line 1] --> B{Required state present?}
    B -- yes --> C[Continue]
    C --> B
    B -- no --> D[Derived framework creates interaction]
    D --> E[StopFlow]
    E --> F[New state arrives]
    F --> A
    C --> G[Flow completes]
Loading

State is the continuation

Recause keeps relevant progress in one hierarchical, JSON-friendly state structure.

That state can include:

  • answers;
  • page position;
  • nested collection items;
  • completion markers;
  • data returned by asynchronous work;
  • interaction-specific metadata;
  • optional history.

Because progress is explicit, a flow can be serialized while incomplete and reconstructed later and elsewhere.

const serialized = wizard.recauseEngine.getStateAsJSON();

A new engine can be created from restored state and the same flow definition:

const restoredState = JSON.parse(serialized);

const restoredWizard = new ChatWizard(
  document.getElementById("app"),
  restoredState,
  registrationFlow
);

restoredWizard.runFlow();

Recause state includes the information necessary to reconstruct the computation’s effective program counter. It does not preserve a suspended call stack; instead, re-evaluation against full serializable state makes the same continuation point reachable again. The state allows already-satisfied operations to resolve and guides execution toward the next missing information.

Recause is a metaframework

The engine is unlikely to be the API most applications use directly.

Its purpose is to support derived frameworks that interpret missing state in specific ways closer to the intended domain.

The repository currently demonstrates several such interaction frameworks:

Framework Interpretation of missing state
FormWizard Render a form field or form section
ChatWizard Ask the next question conversationally
PagesWizard Organize nested flows into navigable pages
AsyncWizard Wait for data or work to complete

A derived framework decides:

  • how a request for state is expressed;
  • how the interaction is rendered or performed;
  • whether it blocks the parent flow;
  • where the answer is stored;
  • when the flow should run again.

Recause provides the shared execution and state model underneath.

Composition

The interaction styles need not be separate applications. They can be nested while operating on one scoped state tree.

function groupTripFlow(pages) {
  const travelerIds =
    pages.getValue("/onboarding.travelers") || [];

  pages.beginPage("01 / Group");

  pages.askForm("onboarding", form => {
    form.askText("Destination", "destination", {
      required: true,
      errorMsg: "A destination is required"
    });

    form.askList("Travelers", "travelers", item => {
      item.askText("Traveler name", "name", {
        required: true
      });
    });

    form.chat("packingHelp", "assistant", false, chat => {
      chat.say("I can help with difficult packing decisions.");

      chat.askButton("Would you like assistance?", "enabled", [
        { value: "yes", label: "Yes" },
        { value: "no", label: "No" }
      ]);

      if (chat.getValue("enabled") === "yes") {
        chat.askText("What kind of trip is this?", "tripStyle");
      }
    });

    form.submitButton("Continue", "complete");
  });

  pages.endPage();

  pages.beginPage("02 / Review");

  pages.say(
    `Destination: **${
      pages.getValue("/onboarding.destination") || "—"
    }**`
  );

  pages.endPage();
}

Here:

  • PagesWizard controls the outer navigation;
  • a FormWizard collects related values;
  • a ChatWizard is embedded only where guided interaction helps;
  • each nested framework receives a scoped view of the same state;
  • the concrete domain flow remains ordinary JavaScript.

This is the main reason Recause is described as a metaframework rather than a form library.

Scoped state

Nested frameworks use relative paths.

Inside the onboarding form:

form.askText("Destination", "destination");

stores a value below that form's scope.

From the outer page flow, the same value can be addressed absolutely:

pages.getValue("/onboarding.destination");

A leading / addresses the root. Relative paths stay within the current scope.

This lets reusable interaction frameworks operate without knowing where they are mounted in the larger application.

Re-evaluation, not continuation capture

Recause does not:

  • retain the original JavaScript stack;
  • transform the flow into a generator;
  • compile the function into a state machine;
  • persist closures;
  • jump back into the middle of a suspended function.

Instead, it relies on re-evaluation.

That has an important consequence:

A flow may execute many times.

Flow code should therefore be treated as a projection of current state. Pure calculations are naturally safe. External side effects require explicit care.

Good:

const total = items.reduce(
  (sum, item) => sum + item.cost,
  0
);

Dangerous:

sendInvoice(); // could run again on every re-evaluation

External effects should be guarded by explicit state, idempotency keys, or a dedicated effect abstraction:

if (!flow.getValue("invoice.sent")) {
  // perform an idempotent effect
  // then record its confirmed result
}

The current project is a reference implementation. A more formal effect model is an important future direction.

History, undo, and redo

A root engine can record state history:

const wizard = new PagesWizard(
  container,
  null,
  applicationFlow,
  null,
  { recordHistory: true }
);

The engine exposes:

wizard.recauseEngine.checkpoint(tag?); // Mark a checkpoint (optionally with a string tag)
wizard.recauseEngine.canUndo(tag?);    // Check if undo is possible (optionally filtering by tag)
wizard.recauseEngine.canRedo(tag?);    // Check if redo is possible (optionally filtering by tag)
wizard.recauseEngine.undo(tag?);       // Restore state (optionally jumping back to matching tag)
wizard.recauseEngine.redo(tag?);       // Replay state (optionally jumping forward to matching tag)

After restoring a previous state, the flow is evaluated again and the appropriate interaction is derived from that state.

Undo is therefore not implemented separately by every control.

Revision-aware state

State nodes carry revision information. The engine exposes helpers including:

flow.revisionValue("path.to.value");
flow.ageValue("path.to.value");
flow.forceValueOrder("older.path", "newer.path");

These allow a flow or derived framework to reason about which data are newer and invalidate dependent state when earlier data change.

This is useful when a later answer is only valid under an earlier choice.

Quick start

The current reference implementation is plain JavaScript and can be loaded directly in a browser.

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Recause example</title>
</head>
<body>
  <main id="app"></main>

  <script src="recauseEngine.js"></script>
  <script src="asyncWizard.js"></script>
  <script src="baseWizard.js"></script>
  <script src="formWizard.js"></script>
  <script src="chatWizard.js"></script>
  <script src="pagesWizard.js"></script>

  <script>
    function flow(chat) {
      chat.say("Hello.");
      chat.askText("Your name?", "name");
      chat.say(`Welcome, ${chat.getValue("name")}.`);
    }

    const wizard = new ChatWizard(
      document.getElementById("app"),
      null,
      flow
    );

    wizard.runFlow();
  </script>
</body>
</html>

The sample UI frameworks render directly to the DOM, so their stylesheet must also be included when using the repository's supplied presentation.

The engine and several classes also expose CommonJS exports, but the current UI framework implementations are browser-oriented.

Repository map

The exact organization will evolve, but the central pieces are for now:

recauseEngine.js   state, scoping, history, StopFlow, RestartFlow
baseWizard.js      shared runtime and nesting behavior for UI frameworks
formWizard.js      form-style interaction framework
chatWizard.js      sequential conversational interaction framework
pagesWizard.js     navigable page composition
asyncWizard.js     asynchronous data interaction
premiumDemo.html   composed multi-page demonstration

Selected APIs

Engine state API

Available directly or through framework flow APIs:

getValue(path)
setValue(path, value)
hasValue(path)
getValueElseStop(path)
getValueOrDefault(path, defaultValue)
removeValue(path)

getState()
getStateAsJSON()

revisionValue(path)
ageValue(path)

stopFlow()
restartFlow()

Shared wizard API

Depending on the framework:

say(message)

chat(...)
askChat(...)
embedChat(...)

askForm(...)
embedForm(...)

askData(...)

pushIndent()
popIndent()
setIndent(level)

lockValue(path)
unlockValue(path)

Form interaction

The current FormWizard includes:

askText(...)
askMultiText(...)
askRadio(...)
askCheckbox(...)
askTextWithAction(...)
askList(...)
actionButton(...)
submitButton(...)

Chat interaction

The current ChatWizard includes:

askText(...)
askMultiText(...)
askRadio(...)
askCheckbox(...)
askButton(...)
askButtonForced(...)
askButtonNowait(...)
askList(...)
actionButton(...)

Page interaction

The current PagesWizard includes:

beginPage(...)
endPage(...)
affordBack(...)
affordNext(...)

askForm(...)
embedForm(...)
askChat(...)
embedChat(...)

The API is experimental. Names and signatures will change.

Execution Semantics: ask* vs. embed*

When nesting subwizards (e.g., forms inside chats, or chats inside forms), the framework offers two distinct execution semantics:

  1. askChat / askForm (Blocking Execution):

    • Behavior: If the nested subwizard halts waiting for user input (throwing StopFlow), this stop signal is re-thrown up to the parent wizard.
    • Effect: The parent flow is blocked from running any statements below the nested subwizard call.
    • Usage: Use when the parent flow requires the subflow to run to 100% completion before subsequent steps can proceed.
  2. embedChat / embedForm (Parallel / Non-Blocking Execution):

    • Behavior: If the nested subwizard halts waiting for user input, the parent wizard catches and suppresses the stop signal.
    • Effect: The parent flow continues executing and renders subsequent steps in parallel while the subwizard remains active.
    • Usage: Use when you want to show a form or interactive chat inline as part of a larger, ongoing flow, without pausing other parent interactions.

What Recause is — and is not

Recause is:

  • a programming model for incremental flows;
  • a small stateful execution runtime;
  • a foundation for domain-specific interaction frameworks;
  • a way to express interactive logic in ordinary code;
  • an experiment in making state the explicit cause of continued execution.

Recause is not:

  • React or a virtual DOM;
  • a component framework;
  • a form schema;
  • a BPMN engine;
  • a finite-state-machine editor;
  • a durable workflow service;
  • an AI agent framework;
  • a production-ready government forms platform.

It can potentially underpin form, chat, page, approval, configuration, review, voice, or async interaction frameworks. That does not make it a replacement for the operational ecosystems around mature products.

Why not a state machine?

State machines are excellent when states and transitions are the clearest model of a problem.

Recause explores a different formulation.

Instead of explicitly enumerating:

NAME_MISSING
NAME_KNOWN_GOAL_MISSING
NAME_AND_GOAL_KNOWN

the flow simply asks for name, then asks for goal.

The accumulated state makes different portions of the function reachable.

This can be dramatically more concise for interactions that are naturally described as procedural domain logic, especially when they contain:

  • loops;
  • dynamic collections;
  • nested subflows;
  • calculations;
  • ordinary functions;
  • branching based on earlier values.

The trade-off is that repeated execution and side effects require discipline.

Why use exceptions?

StopFlow and RestartFlow need to cross arbitrary layers of normal JavaScript calls without forcing every function to return and propagate a special status value.

Exceptions provide that non-local control transfer with very little ceremony.

In this runtime they mean:

StopFlow     the current purpose cannot progress with current state
RestartFlow  prior state changed; evaluate again from the beginning

Whether exceptions are the ideal implementation mechanism in every language is an open question. The programming model matters more than the specific signal mechanism.

State can outlive a particular flow

Serialized Recause state does not require the receiving runtime to use a byte-for-byte identical flow definition.

The receiving flow must instead be compatible with the portions of state it interprets.

Compatibility may include:

  • agreed state paths and scopes;
  • compatible value types and meanings;
  • compatible completion and revision semantics;
  • preserved domain invariants;
  • compatible handling of recorded effects;
  • an understood flow or schema version.

A compatible flow may read only a subset of the state, add an independent branch, derive additional values, migrate older state, surgically update selected values, invalidate downstream state, present the same state through another interaction framework, or run in another language.

For example:

/application/...       written by the applicant flow
/validation/...        written by a validation flow
/review/...            written by the reviewer flow

The reviewer flow may read the application branch without modifying it. A correction flow may be authorized to change selected application values and invalidate dependent review state.

State exchange in Recause is therefore closer to exchanging a versioned domain document than transferring a suspended call stack.

This opens a broader possibility:

one shared state
multiple compatible flows
different owners
different interfaces
different languages

Recause is not only about resuming one interaction. It may also support causing compatible computations around shared, versioned state.

Possible compatibility levels

Read-compatible
The flow understands and may read selected state.

Extension-compatible
The flow may add state without changing existing meanings.

Mutation-compatible
The flow may change specified existing paths while preserving invariants.

Resume-compatible
The flow can continue the original interaction toward an equivalent
completion condition.

Migration-compatible
The flow can transform state from one declared version to another.

A future Recause specification may formalize these levels.

Portability

A Recause computation can move between runtime instances when:

  1. all relevant progress is represented in serializable state;
  2. the receiving runtime has a compatible flow;
  3. the receiving runtime understands the relevant state semantics;
  4. external effects and resources are handled explicitly.

Conceptually:

browser
  → serialize state
  → store or transmit
  → create another runtime
  → load state
  → run a compatible flow

The compatible flow may be another version of the same interaction, a reviewer, validator, migration, terminal flow, or a flow written in another language.

The current implementation demonstrates the state side of this model. Production-grade distributed execution would require versioning, migration, effect guarantees, authorization, and durable storage around it.

Potential crystallisation points

The Recause model does not prescribe one language-level control mechanism. Different implementations may crystallise around different constructs.

1. Exceptions

A missing value raises a dedicated control signal. The outer runtime catches it, retains explicit state, and waits for another cause of execution.

This is the mechanism used by the JavaScript reference implementation and is a natural first path for Python, Java, Kotlin, C#, Ruby, PHP, Swift, and similar languages.

2. Explicit control-flow results

Each operation returns either a value or an indication that execution must stop.

Value<T>
Missing<Request>
Restart
Failure<Error>

This avoids using exceptions for expected control flow, but requires the stop result to propagate through intervening functions.

3. Algebraic effects and handlers

A request for missing state may be represented as an effect. A Recause handler can interpret that effect as a state lookup, interaction, or suspension.

4. Delimited continuations

A runtime may capture a bounded continuation at the point where state is missing. This creates a resumable variant of Recause, distinct from the reference model's deliberate re-evaluation from the flow entry point.

5. Generators and coroutines

A flow may yield requests and receive values in return. This can provide convenient syntax, but retained generator state is normally not directly serializable.

6. Interpreters and embedded DSLs

A Recause flow may be represented as data and interpreted by a runtime. This improves inspection and migration, but gives up some of the attraction of unrestricted ordinary code.

7. Replay-oriented runtimes

A runtime may record decisions or state changes and deterministically replay the program, provided effects and non-determinism are controlled.

8. Conditions and restarts

Languages with resumable condition systems may treat missing state as a condition and expose ways to satisfy or defer it.

Recause is not “a clever use of exceptions.” Exceptions are one possible crystallisation point for the model.

Languages likely to support the model

The minimum requirements are modest:

  1. A flow can be evaluated from its beginning.
  2. Progress can be stored outside the local call stack.
  3. A missing value can stop the current evaluation.
  4. The runtime can distinguish an intentional stop from a real failure.
  5. The flow can later be evaluated again against changed state.
Language family Likely mechanism Initial suitability
JavaScript / TypeScript Exceptions Excellent
Python Exceptions Excellent
Ruby Exceptions or throw / catch Excellent
Java / Kotlin Exceptions Very good
C# / F# Exceptions or typed results Very good
PHP Exceptions Good
Swift Throwing functions or result values Good
Go Explicit result propagation Possible, more explicit
Rust Result, ?, control-flow enum Possible, strongly typed
OCaml Exceptions or effect handlers Especially interesting
Racket / Scheme Exceptions or continuations Especially interesting
Common Lisp Conditions and restarts Especially interesting
Haskell Effect, state, or continuation abstractions Powerful, less ordinary-looking
C Return codes or non-local jump facilities Technically possible

One of the interesting questions around Recause is how much of the model remains stable while the language-level crystallisation point changes.

A Python implementation and CLI framework

A Python implementation is a natural second reference implementation. It would show that Recause is not fundamentally a browser library or JavaScript-specific, and that a wizard is an interpretation of missing state rather than a particular visual component.

def onboarding(cli):
    cli.say("Welcome.")

    cli.ask_text("What is your name?", "name")

    cli.ask_choice(
        "What are you building?",
        "project_type",
        ["library", "application", "experiment"],
    )

    name = cli.get_value("name")
    project_type = cli.get_value("project_type")

    cli.say(f"Hello {name}. Let us build your {project_type}.")

A terminal framework could eventually support text, passwords, confirmations, choices, numbers, dates, file paths, editor input, review, back, undo, save-and-exit, and resume.

Possible uses include project scaffolding, deployment configuration, installation assistants, administrative workflows, guided diagnostics, data-cleaning tools, migrations, and AI-assisted terminal applications.

The Python runtime and CLI framework are planned experiments, not yet part of the current JavaScript implementation.

Traps and Pitfalls

Because Recause binds flow execution directly with visual representation order in declarative, reactive loops, developers should be aware of a few specific pitfalls and architectural traps.

1. The State Dependency Paradox (Deadlocks)

Since the flow is executed top-down and evaluated on every state change, conditional checks depending on future values of a subflow to decide whether to mount/show that subflow can cause deadlocks.

  • The Trap:
    const val = pages.getValue("hobbyForm.activity");
    if (!val) {
      // Conditionally show the form only if not filled in
      pages.askForm("hobbyForm", function(form) {
        form.askText("Activity Name", "activity");
      });
    }
    Once the user types an activity (e.g. "Chess") and saves, the condition !val becomes false. The form is instantly unmounted and disappears from view, leaving the user unable to edit their input.
  • The Solution: Do not use a subflow's output values to conditionally hide or show that subflow itself. Keep input flows visible or utilize explicit "Edit" states or state variables separate from the inputs themselves.

2. The "Ghost State" Trap (Stale Hidden Values)

Hiding a form or wizard from visual execution does not automatically delete its stored keys and values from the underlying engine state.

  • The Trap:
    const wantsNewsletter = pages.getValue("wantsNewsletter");
    if (wantsNewsletter) {
      pages.askForm("newsletterForm", function(form) {
        form.askText("Email Address", "email");
      });
    }
    
    // Downstream process:
    const email = pages.getValue("newsletterForm.email");
    If the user checks "Yes", enters their email, then changes their mind and unchecks "Yes", the input fields disappear. However, { newsletterForm: { email: "..." } } still remains in the engine state. Downstream processes will still read and submit the hidden email!
  • The Solution: Downstream code must always guard against "ghost state" by validating the parent conditions (e.g. checking wantsNewsletter), or the developer must explicitly clear or prune the subflow's state paths inside the flow when conditions change.

3. The Non-Idempotent Evaluation Trap (Side Effects)

Because Recause re-evaluates the entire flow tree on every keystroke or click to keep the UI reactive, flow functions must be pure, deterministic, and free of side effects.

  • The Trap:
    pages.askForm("profileForm", function(form) {
      form.askText("Username", "username");
      
      // Incrementing state inside a flow:
      const count = form.getValue("renderCount") || 0;
      form.setValue("renderCount", count + 1); 
    });
    Every keystroke in the username field triggers a re-evaluation of the flow, causing renderCount to rapidly spin up and increment infinitely or cause infinite render loop errors.
  • The Solution:
    • Action Callbacks: Use event handlers or custom buttons (e.g., actionButton(label, path, callback)) whose execution only happens once upon user interaction.
    • Transition Guards: Check state transitions explicitly using boolean flags to guarantee code runs exactly once (e.g. if (isDone && !wasCounted) { increment(); setValue("wasCounted", true); }).

4. Premature Evaluation in Parallel Scopes (embed)

Because embedChat or embedForm does not block execution of the parent flow, the statements following it are evaluated immediately and repeatedly during every input interaction inside the embedded flow.

  • The Trap:
    chat.embedChat("profile", profileFlow);
    sendConfirmationEmail(); // ❌ DANGEROUS!
    While the user is typing their first name in the embedded profile chat, the parent flow re-runs on every keystroke. This causes sendConfirmationEmail to run repeatedly, sending dozens of emails before the profile is even complete.
  • The Solution: Place side-effects strictly after a blocking call (ask), or protect them with explicit completion flags (transition guards).

5. Stale/Incomplete Value Dependency in Parallel Scopes

Reading output values from an active, non-blocking embedded wizard before it is completed can cause logic errors due to undefined or partially entered values.

  • The Trap:
    chat.embedForm("identity", identityFlow);
    const email = chat.getValue("identity.email"); 
    registerUser(email); // ❌ DANGEROUS!
    Since embedForm is non-blocking, the parent flow executes registerUser with undefined on the very first render, long before the user has actually filled in their email.
  • The Solution: Never read or act on output variables from a subwizard unless you have blocked on it using ask, or verified its completeness state explicitly first (e.g. checking if (isCompleted)).

Related work

Recause sits near several established programming ideas, but does not identify completely with any one of them.

Exceptions and non-local control transfer

The current JavaScript implementation uses exceptions as lightweight control signals. The exception does not preserve the interrupted stack; the flow is later evaluated again against updated state.

Replay and durable execution

Durable execution systems also reconstruct progress from persisted information. Recause shares the idea that execution can be recreated from explicit state, but focuses on interactive programming and derived interaction frameworks rather than providing a distributed workflow service.

Continuations and effect handlers

Continuations, delimited continuations, algebraic effects, and effect handlers may offer alternative implementation strategies. Recause itself does not require a captured continuation.

State machines and workflow graphs

State machines explicitly represent states and transitions. Recause explores a complementary formulation in which ordinary procedural logic is evaluated against the current data.

Generators, coroutines, and async functions

Generators and coroutines retain local execution state. A Recause flow normally discards local execution state while preserving relevant progress in explicit serializable application state.

Incremental and reactive computation

Incremental and reactive systems re-evaluate computations when inputs change. Recause focuses particularly on flows that stop because information is missing and on frameworks that turn that absence into interaction.

Recause ecosystem

Recause is intended to become more than one JavaScript implementation.

Projects may implement the execution model in another language, build a derived interaction framework, provide tooling, define compatibility conventions, or explore a new application domain.

Implementations

Project Language Status Description
recause-js JavaScript Experimental Current reference implementation
recause-py Python Planned Python implementation of the core model

Interaction frameworks

Project Runtime Status Description
recause-js-dom recause-js Experimental Forms, chats, pages, and async browser interaction
recause-py-cli recause-py Planned Interactive terminal flows

The names above describe a possible canonical project family. Repository and package boundaries may still change as the model stabilizes.

Experiments and production use

Experiments test the model without claiming production readiness. A future production section may separately list projects that report using Recause or a compatible implementation in deployed systems. Listing would not imply certification or endorsement.

Add a project

Open an issue or pull request containing:

  • the project URL;
  • the Recause implementation or idea it uses;
  • its language and runtime;
  • whether it is experimental or used in production;
  • a one-sentence description.

Projects do not need to depend on the JavaScript reference implementation. Independent implementations of the Recause programming model are welcome.

Possible repository family

recause
recause-js
recause-js-dom
recause-py
recause-py-cli

Conceptually:

recause
  model, terminology, compatibility, ecosystem, specification

recause-js
  JavaScript runtime and state model

recause-js-dom
  browser interaction frameworks

recause-py
  Python runtime and state model

recause-py-cli
  terminal interaction framework

Potential package names:

npm
  @recause/core
  @recause/dom

PyPI
  recause
  recause-cli

These names are proposals until actually published and should not be assumed to be reserved.

AI and Recause

Recause does not require AI.

Its deterministic state-and-flow model may, however, be useful for placing AI surgically inside a larger controlled interaction.

For example:

  • a normal form collects established information;
  • a guided chat helps with one ambiguous legal question;
  • an AI proposes a classification;
  • the user confirms it;
  • the confirmed value becomes explicit state;
  • the deterministic flow continues.

A future framework could preserve provenance such as:

stated by applicant
extracted from document
proposed by model
confirmed by applicant
approved by reviewer

instead of collapsing all of these into one opaque answer.

Current status

Recause is experimental.

The repository currently demonstrates that the model can support:

  • repeated flow evaluation;
  • intentional suspension and restart;
  • hierarchical scoped state;
  • serialization;
  • nested interaction frameworks;
  • page, form, chat, and async composition;
  • repeatable lists;
  • validation;
  • history, undo, and redo;
  • a non-trivial composed application.

It has not yet established:

  • a stable public API;
  • a formal effect system;
  • state-schema migrations;
  • a formal compatibility specification;
  • durable distributed execution guarantees;
  • a renderer-independent semantic interaction tree;
  • complete accessibility and internationalization abstractions;
  • production security hardening;
  • comprehensive tests across environments;
  • cross-language conformance tests.

Use it to explore the model, build experiments, and challenge its assumptions.

Do not yet depend on it for production-critical processes.

Possible next directions

The project may evolve in several directions:

  • formalizing the engine semantics;
  • defining compatibility levels;
  • extracting semantic interaction from direct DOM rendering;
  • typed state and field contracts;
  • richer validation and computed values;
  • explicit effect and command handling;
  • persistence and flow-version migration;
  • visual state and execution inspection;
  • additional derived frameworks;
  • AI-assisted structural authoring of flows;
  • a Python reference implementation;
  • a terminal interaction framework;
  • cross-language conformance fixtures;
  • implementations in other programming languages.

The priority is to understand and refine the programming model before turning it into a large widget library.

Why the name?

New state re-causes execution.

The flow is not resumed by restoring its old stack. It is caused to run again under changed state data.

There is also a pleasing secondary association with the French recauser: to talk together again.

Design principle

Motion explains the model.

The interactive visualization on recause.org presents the same cycle embodied by the runtime:

FLOW → STOP → STATE → RE-ENTRY

Contributing

Recause is currently maintained as a research and reference project.

GitHub Discussions are the preferred place for questions, ideas, comparisons, examples, and technical discussion. While I do participate in discussions, my available time is limited and I cannot guarantee responses or follow-up.

At this stage:

  • GitHub Discussions are the preferred place for community discussion.
  • Pull requests are generally not reviewed or merged.
  • Issues are reserved for repository maintenance rather than general discussion or support.
  • Independent forks, experiments, implementations, articles, and comparisons are warmly encouraged.

The goal of publishing Recause as open source is to make the ideas easy to study, critique, adapt, and build upon. If you find the model interesting, I would be delighted to see independent experiments and hear about your experiences.

License

Recause is released under the MIT License.

A final note

Recause is deliberately small enough to read, question, copy, and change.

It is not presented as the final answer to interactive programming.

It is a concrete proposal:

Perhaps an interaction can be written as one ordinary flow,
and perhaps missing information can be treated as a normal reason for that flow to stop and be caused again.

Beyond that lies a broader possibility:

Explicit state may allow several compatible flows, frameworks, runtimes, and languages to cause useful computations around the same evolving domain document.

If that way of thinking resonates with you, I hope it proves useful—and I would be delighted to see where others take the idea.

About

Recause is an experimental programming model and runtime for writing interactive software as ordinary code. Flows stop when information is missing, represent progress in explicit serializable state, and run again when new state arrives—without callbacks, explicit resume logic, or hand-authored state machines.

Topics

Resources

License

Stars

1 star

Watchers

1 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors