Overview
wity-rkf is the reactive-knowledge layer over wity-graph: reactive knowledge documents modelled as state machines, with a headless, isomorphic reference implementation. It defines element types and evaluation semantics over a knowledge graph; documents are stored as <wity-knowledge> — the same representation wity-graph, wity-workbook and wity-ideation use — so RKF has no wire format of its own. It is the shared reactive layer between:
- Authoring tools -- a visual editor that produces RKF documents
- Renderers -- DOM, Canvas, SVG, WebGL, or any other rendering target
- Notebooks -- interactive explorable explanations with prose, math, and simulations
Core model
An RKF document is a pure function of state:
f(document: RkfDocument, state: Object) -> ComputedFramedocumentis the document model — fromparse(), or built directly (immutable during evaluation)stateis a plain object of variable valuesComputedFrameis the complete render state: resolved marks, controls, envs, gates, outputs, and section tree
No interpolation state, no DOM, no framework, no side effects.
Documents as state machines
RKF v2.0 reframes the spec around state machine theory, grounded in three vector spaces derived from first principles:
Feature-space (rkf-var)
The dimensions of a machine's state. Each variable is an axis in feature-space:
<rkf-var id="t" type="number" init="0" />
<rkf-var id="running" type="boolean" init="false" />
<rkf-var id="pulse" init="0" elasticity="2" elastic-default="0" />Variables can have elasticity -- self-resetting temporal behaviour where the variable snaps back to a default value after a delay.
Observation-space (rkf-gate)
Named boolean conditions over feature-space. Gates recognise regions in feature-space:
<rkf-gate id="isRunning" expr="running" />
<rkf-gate id="trained" expr="epoch > 100" />State is emergent, not declared -- it arises from regions in feature-space recognised by gates. The gate attribute on visual elements can reference a gate id or an inline expression.
Transformation-space (rkf-transform)
State transitions triggered by gates (or expressions). Replaces the v1.0 rkf-update element:
<rkf-transform trigger="isRunning" on="tick">
<rkf-set var="t" expr="t + 1" />
</rkf-transform>The trigger attribute accepts a gate id or an inline expression. The optional on attribute filters by event name.
Document structure
RkfDocument
├── RkfVar[] (document-level feature variables)
└── RkfSection[] (top-level sections; optional gate)
├── RkfEnv (opaque environments: prose, math, image, video, model)
└── RkfMachine (state machine blocks)
├── RkfVar[] (feature-space: state dimensions)
├── RkfGate[] (observation-space: named conditions)
├── RkfDerive[] (derived features)
├── RkfTransform[] (transformation-space: state transitions)
│ └── RkfSet[] (variable assignments)
├── RkfPort[] (typed I/O interfaces, dir=in|out)
├── RkfMachine[] (nested sub-machines, recursive)
├── RkfCanvas (visual output area)
│ ├── RkfScale[] (data-to-pixel mapping)
│ ├── RkfAxis[] (axis descriptors)
│ └── Marks[] (scatter, line, arrow, rect, circle, arc, text, path, area)
└── RkfEnv[] (opaque environments with optional ports)State model
State is the primary axis (unlike wity-scene, where time is primary).
Variables (rkf-var) declare feature-space dimensions with typed initial values:
<rkf-var id="learning_rate" type="number" init="0.1" />
<rkf-var id="mode" type="string" init="linear" />
<rkf-var id="running" type="boolean" init="false" />
<rkf-var id="pulse" init="0" elasticity="2" elastic-default="0" />Derived values (rkf-derive) compute from variables using expressions:
<rkf-derive id="slope" expr="-w1 / w2" />
<rkf-derive id="intercept" expr="-bias / w2" />Derives are resolved in topological (dependency) order, not declaration order. Each machine's variables, derives and gates form a typed dependency graph; a derive that references another derive is always evaluated after its dependency, regardless of the order they were authored in. Combinational cycles among derives/gates are illegal and reported by validate(). Transform feedback (t = t + 1) is not a combinational cycle — it flows across time via rkf-transform, so it is permitted (see Dependency graph).
Gates (rkf-gate) declare named boolean conditions over feature-space:
<rkf-gate id="isRunning" expr="running" />
<rkf-gate id="converged" expr="error < 0.01" />Transforms (rkf-transform) define state transitions triggered by gates:
<rkf-transform trigger="isRunning" on="tick">
<rkf-set var="epoch" expr="epoch + 1" />
<rkf-set var="error" expr="computeError(weights, data)" />
</rkf-transform>Gate attributes control conditional visibility on any element, referencing a gate id or an inline expression:
<rkf-env type="prose" gate="mode == 'linear'">In linear mode, the output is unbounded.</rkf-env>
<rkf-group gate="epoch > 0">
<rkf-text x="10" y="10" text="Training started" />
</rkf-group>Recursive composition
rkf-machine elements can contain nested rkf-machine elements. Inner variables are accessible via dot notation:
<rkf-machine id="outer">
<rkf-machine id="sub">
<rkf-var id="count" type="number" init="0" />
</rkf-machine>
<rkf-derive id="total" expr="sub.count * 2" />
</rkf-machine>Cross-representation composition
Beyond nesting machines, RKF environments can embed other <wity-knowledge> representations inline — workbooks, system maps, decision frameworks. Each embedded representation is stored as a snapshot within the RKF document's knowledge graph and rendered by its own domain editor.
This makes an RKF document a composite document: prose, math, reactive state machines, and live domain editors coexist in a single serializable knowledge graph. See the Composition guide for details.
Rendering targets
wity-rkf is rendering-target-agnostic. evaluate() returns resolved coordinates and props. The renderer decides what to do with them:
| Target | How |
|---|---|
| SVG | Map marks to SVG elements, ports to HTML form elements |
| Canvas 2D | Draw marks using fillRect, arc, moveTo/lineTo, fillText |
| DOM/HTML | Position elements with CSS, render envs as HTML |
| WebGL/Three.js | Map marks to 3D scene objects |
| Server-side | Evaluate for testing, validation, or static rendering |
Expression evaluator
RKF includes a safe, hand-rolled recursive descent expression evaluator. No eval(), no Function().
Supports:
- Arithmetic:
+,-,*,/,% - Comparison:
==,!=,<,>,<=,>= - Logical:
&&,||,! - Ternary:
cond ? a : b - Parentheses:
(expr) - Math functions:
Math.abs,Math.floor,Math.ceil,Math.round,Math.min,Math.max,Math.sqrt,Math.sin,Math.cos,Math.tan,Math.PI - Dot access:
obj.field
Dependency graph
Each machine's feature-space (rkf-var), derived features (rkf-derive) and observation-space (rkf-gate) form a typed, directed dependency graph:
- nodes — vars, derives, gates
depends-onedges — from each referenced id to the derive/gate that reads it (combinational dataflow, must be acyclic)reads/writesedges — transform inputs/outputs, which may close a cycle across time
This graph is the substrate for two guarantees:
- Order-independent evaluation. Derives resolve in topological order, so authoring order never affects results.
- Structural cycle detection.
validate()reports illegal combinational cycles among derives/gates, while leaving legal transform feedback (t = t + 1) untouched — the two are distinguished by edge type.
Documents are <wity-knowledge> graphs
RKF has no serialization of its own. An RKF document is a knowledge graph — every element is a typed node, every reference a typed edge — and its canonical stored form is a <wity-knowledge> document, exactly as wity-workbook stores spreadsheets as knowledge graphs. parse() and serialize() delegate to graph-headless, which is injected once at startup:
import * as graph from '@wity/graph-headless';
import { setGraphProvider, setXmlParser } from '@wity-ai/rkf-core';
setGraphProvider(graph); // required for parse() / serialize()
setXmlParser(new DOMParser()); // Node.js only; forwarded to graph-headlessevaluate(), validate() and applyUpdate() operate on an already-parsed document and need no injection. Documents authored in the retired <wity-rkf> format upgrade via rkfToKnowledge(oldXml).
This is the substrate that makes RKF the reactive interpretation layer over a knowledge graph — wity-graph answers "what is true," wity-rkf answers "what happens next," over the same <wity-knowledge> representation.
Relation to wity-scene
| wity-scene | wity-rkf | |
|---|---|---|
| Primary axis | Time (t in seconds) | State (variable values) |
| Core function | f(scene, t) -> ComputedFrame | f(document, state) -> ComputedFrame |
| Content model | Layers with positioned visual elements | Sections with envs, machines, ports |
| Use case | Motion graphics, title cards, video overlays | Interactive notebooks, explorable explanations |
| Shared patterns | Declarative format, headless evaluation, isomorphic, zero deps | Same |
