# Reflow A reactive, node-based visual programming language and editor — with an AI layer. One live reactive graph, three synchronized surfaces: a **notebook** of expression cells, a **canvas** of wired nodes, and **MCP channels** so Claude Code can read, edit, and subscribe to the same running program. Nodes whose value is a UI spec render **real React UI in place** — including a chat assistant that is itself written in Reflow cells and can create new nodes. | Notebook (cells + inline UI) | Canvas (nodes, chat rendering in-node) | | --- | --- | | ![notebook](docs/notebook.png) | ![canvas](docs/canvas.png) | ## Highlights - **Reactive engine** (`@reflow/core`): dirty-propagation scheduler over a DAG with memoized outputs and equality-pruned change events; recompute is **O(affected subgraph)**. Measured: a 10,000-node chain updates in ~17 ms (**≈560k node updates/sec**). - **Turing complete**: recursion via lambda cells and function subgraphs (lazy `if` + `call`), bounded `while` iteration. Proven by programs run as tests: a Turing machine simulator and Rule 110, both verified against independent JS references. - **Automatically persistent or ephemeral variables**: `count = var(0)` is session-scoped; `best = pvar(0)` auto-loads on startup and auto-saves on write. Persistence is a property of the variable — writes look identical. - **One graph, every surface**: a notebook cell *is* a graph node (type `expr`), dependencies auto-wired from free identifiers. Edit in notebook, canvas, chat, or MCP — everything updates live everywhere. - **UI in nodes**: any cell/node whose value is built with `ui.*` renders as real React components inline (toggle `{}`/`ui` to see the raw spec). Event handlers are serializable actions (`set('count', count + 1)`) dispatched back into the engine. - **LLM as a language primitive**: `llm(prompt, {system, model}?)` is an async reactive source — `null` while pending, then the text flows through the graph; results cache per node+prompt, and prompt changes re-request reactively. `agent(conversation)` additionally lets Claude **edit the program** (create/ update/remove cells) via a tool. Both run server-side on the [Claude Agent SDK](https://www.npmjs.com/package/@anthropic-ai/claude-agent-sdk). - **The chat is dogfooded**: the assistant UI is ~6 Reflow cells — `chatMessages = pvar([])` (persistent transcript), `chatReply = … agent(chatConvo) …`, and a `ui.*` view. Ask it to "create a cell that…" and the node appears, live. - **MCP channels for Claude Code**: an MCP server exposes the running program — `list_nodes / read_node / add_cell / update_cell / remove_cell / set_variable`, plus **`subscribe(channel)`** (a channel = a node output) and long-polling **`get_updates`** so an agent can react to data flowing through the graph. ## The language Cells are single pure expressions (`name = expression`), JS-flavoured: ```text count = var(0) // ephemeral variable best = pvar(0) // persistent (auto-saved/restored) doubled = count * 2 // recomputes when count changes fib = n => n < 2 ? n : fib(n-1) + fib(n-2) haiku = llm('write a haiku about ' + topic) ?? '…thinking' view = ui.column(ui.h1('Counter'), ui.text('Count: ' + count), ui.button('+1', set('count', count + 1))) ``` Lambdas, ternary, structural `==`, arrays/objects, and a pure stdlib (`map filter reduce range len sum sort join split slice push assoc merge keys values get str num while(init, cond, body)`). Mutual recursion works (lambda-body references are late-bound). A Turing machine, from the test suite: ```text final = while({tape: {}, pos: 0, state: 'A', steps: 0}, s => s.state != 'H', s => tmStep(s), 100000) ``` ## Architecture **Code and data live together** in one hub process; every client is a peer: ``` ┌────────────────────────────────────┐ browser editor ──────┤ apps/server — the HUB │ (SSE + ops) │ · @reflow/core Engine (the graph) │ │ · vars.json + graph.json persist │ Claude Code ─────────┤ · llm()/agent() via Claude Agent │ (MCP stdio bridge) │ SDK, agent edits applied live │ │ · REST /api/state /api/op │ curl / scripts ──────┤ · SSE /api/events (state stream) │ └────────────────────────────────────┘ ``` - `packages/core` — the language + engine (parser, evaluator, scheduler, variables, node registry, function graphs, llm/agent providers as injected interfaces). No DOM, no network. - `apps/server` — hub (`src/hub.ts`), Claude SDK providers (`src/providers.ts`), MCP server (`src/mcp.ts`), demo seed. The reactive graph stays acyclic; Turing-complete recursion/iteration lives in the function layer with depth/iteration caps, so the editor can never hang. - `apps/web` — React + Vite + @xyflow/react thin client: state mirrors in over SSE, edits go back as ops. ## Run it ```bash pnpm install pnpm dev # hub :5200 + editor :5199 (real Claude via Agent SDK auth) REFLOW_LLM_MOCK=1 pnpm dev:hub # deterministic mock LLM/agent instead pnpm test # 97 core tests pnpm e2e # 12 Playwright end-to-end tests (mock hub, auto-started) pnpm verify:mcp # MCP client end-to-end check (needs a running hub) ``` Hook Claude Code up to the running program: ```bash claude mcp add reflow -- pnpm --dir /apps/server mcp # then: subscribe to a channel, e.g. "subscribe to the count cell and tell me when it changes" ``` Models default to `claude-haiku-4-5` for `llm()` and `claude-opus-4-8` for the chat agent (`REFLOW_LLM_MODEL` / `REFLOW_AGENT_MODEL`, or per-call `{model}`). ## Verification - **Core (vitest, 97 tests)**: parser/evaluator semantics; incremental-recompute proofs (compute-count assertions); cycle detection/recovery; error propagation with provenance; persistence round-trips through real files; recursive + mutually recursive programs; **Turing machine + Rule 110 vs independent JS references**; llm()/agent() async semantics (dedupe, reactivity, error paths, graph edits); performance bounds. - **E2E (Playwright, 12 tests)**: live values + inline UI rendering; action dispatch through the hub; UI/raw toggling; cell editing with error recovery; fib typed into the notebook; async llm() cells; **chat conversing, persisting across engine restarts, and creating nodes via the agent**; pvar-vs-var restart semantics; canvas editing, port-drag edge creation, cross-surface sync. Zero uncaught page errors. - **MCP (`verify:mcp`)**: real MCP client over stdio exercises every tool, including subscribe → external change → get_updates long-poll delivery. - **Real-Claude spot checks**: `llm('Reply with exactly: pong')` → `"pong"`; chat asked for "a cell named triple that multiplies count by 3" → agent created `triple = count * 3`, which recomputed to 21 when count was set to 7.