Contributing to Reaktor

Contribution requires repository setup, a focused change and its verification. Read the Framework Overview for graph, node and port terminology.

1. Which repository

Reaktor is a framework, not an application. It is developed in one repository and consumed from others through Gradle composite builds, so a framework change shows up in the products immediately without publishing anything.

RepositoryWhat it holdsBuild
reaktorThe framework: every reaktor-* module, compose-flow, dependeasyGradle, ./gradlew build
bestbudsA shipping product on Reaktor, the desktop workbench (modules/engine), the Cloudflare workers, and this documentation site (targets/reaktorWeb/docusaurus)Gradle + npm
mannaA second product, mostly a React web app on Cloudflarenpm

The two repositories sit side by side; bestbuds/settings.gradle.kts picks up the framework with includeBuild("../reaktor"). Clone them as siblings or the composite build will not resolve.

2. The module map

reaktor is one Gradle build of about thirty modules. They fall into four bands, and knowing the band tells you what a module is allowed to depend on.

Foundations — every other module may import these. reaktor-core (the Feature slot registry, Capability, Adapter, and the cross-platform primitives), reaktor-flexbuffer (experimental binary codec and direct encoded access), reaktor-ffi (native proofs and a proposed typed frame/C ABI), reaktor-io, reaktor-compiler.

The graph — the composition kernel and its editor stack. reaktor-graph-port (the interfaces), reaktor-graph (the runtime), reaktor-service (graph-neutral service contracts), compose-flow (a generic canvas: viewport, edges, handles, minimap), reaktor-flow (the Reaktor adaptation of that canvas, plus GraphDocument and ReaktorGraphEditor).

Capability modules — the full-stack verticals a product actually consumes. reaktor-auth, reaktor-db, reaktor-ui, reaktor-media, reaktor-notification, reaktor-work, reaktor-location, reaktor-sensors, reaktor-health, reaktor-telemetry, reaktor-secrets, reaktor-security, reaktor-google.

Platform and tooling — where Reaktor meets a runtime or an operator. reaktor-cloudflare, reaktor-cloud, reaktor-web, reaktor-mcp, reaktor-conductor (drives Claude Code and Codex as subprocesses), reaktor-tooling, reaktor-performance, dependeasy (the dependency plugin).

reaktor-tactile is a placeholder with no source. Do not extend it.

3. Set up and build

You need Java 21+, the Android SDK, Xcode with iOS platform support and CocoaPods, and CMake + Ninja for the native bridges. Create local.properties in the reaktor root:

sdk.dir=/Users/<you>/Library/Android/sdk
kotlin.apple.cocoapods.bin=/opt/homebrew/bin/pod

Use the real output of which pod if your CocoaPods install differs. Then:

./gradlew build

The first build is slow on purpose — it bootstraps native dependencies (Hermes, FlatBuffers) and caches them under .github_modules. That cost is front-loaded, not recurring.

The fast inner loop, in the order you will actually use it:

./gradlew :reaktor-core:jvmTest --console=plain
./gradlew :reaktor-flow:jvmTest --console=plain
./gradlew :reaktor-graph-port:allTests :reaktor-graph:allTests

Because the build is composite, touching reaktor-ui, reaktor-flow, reaktor-core or reaktor-graph rebuilds the product too. Run the consumer's tests before you call it done:

(cd ../bestbuds && ./gradlew :engine:jvmTest)

The native bridges have their own targets — ./gradlew :reaktor-ffi:assembleDebug for Android, ./gradlew :reaktor-ffi:iphoneosCMake for Darwin.

When a build fails first time, it is almost always one of four things: a wrong kotlin.apple.cocoapods.bin, a missing iOS platform in Xcode, an Android NDK/CMake version mismatch, or simply the slow native bootstrap not being finished.

4. The two rules

Most review comments on a Reaktor change come down to one of these.

Modules compose through the graph

Reaktor builds capabilities around graph identities, typed ports, ownership, lifecycle, queries and commands. The September 2026 direction in the repository operating guide supersedes the older mandatory dual-surface rule. A graph-agnostic facade can remain useful for a real consumer, but it is not a requirement for every module.

Keep semantic definitions distinct from runtime activations and presentation. A headless graph host must not acquire Compose or workbench dependencies just to execute or inspect a system. Preserve useful existing APIs and explicit ownership boundaries.

Layers do not leak

compose-flow      generic canvas, viewport, pan/zoom/fit, generic edges, handles, minimap
  └─ reaktor-flow Reaktor graph adaptation, measurement, layout, scene, ReaktorGraphEditor,
                  GraphDocument/GraphEditorSession, and the host re-exports consumers need
       └─ app     shell chrome, panes, inspector (bestbuds/modules/engine)

Never put Reaktor graph semantics in compose-flow, workbench chrome in reaktor-flow, or graph layout in an app module. An app that needs a compose-flow type consumes it through a reaktor-flow re-export, never directly.

Follow the repository operating guide before changing a layer boundary or design-token ownership. Graph-first capabilities do not require a parallel graph-agnostic API.

5. Writing the code

Agents preparing a change can also read the agent efficiency field report for its dated observations about context, tool use and retained verification evidence.

  • commonMain stays platform-safe. Platform APIs belong in the platform source sets.
  • Explicit StateFlow backing fields. Never force-unwrap a port with .impl!!.
  • Comments default to none. Write one only to explain a non-obvious why — a constraint or an invariant — never what the code already says.
  • Generated and vendored trees (build/, ts/import/, ts/export/, kotlin-js-store/) are not hand-edited.
  • Visual surfaces read reaktor-ui/machinesignal for palette, spacing, radii and type scale. No hardcoded hex, no hardcoded geometry — the values are transcribed from the design file and parity tests fail when a surface drifts.
  • Performance claims need current source, target, semantics and retained harness evidence. Start with the October FlexBuffers review; historical flamechart/ architecture sketches are not current measurements. Fix the documented codec defects before adoption; FFI hop and mutable-runtime performance still require actual implementations and end-to-end measurements.

6. Working on these docs

The docs are product artifacts, versioned with the code in bestbuds/targets/reaktorWeb/docusaurus.

Markdown is the source of truth. Every document declares doc_catalog (placement, owner, kind, summary and intended use) and reaktor_graph (named anchor-bound ports and explicit typed relationships). Public navigation regions live in docs/home.md; private regions live in docs/private/home.md. Read the graph marker contract before editing a document or its connections.

The sidebar, homepage section blocks, graph, outline and corpus are generated from those declarations. manifest.mjs only reads Markdown. Do not hand-edit sidebars.ts, private-sidebars.ts, generated JSON, or the blocks between the nav:sections and nav:map markers.

# Edit prose and Markdown catalogue/graph declarations together.
npm run theory
npm run nav
npm run corpus
npm run nav:check

The sidebar provides top-mounted expand/collapse controls, expand all, collapse all and locate current. Vision and roadmap starts expanded; Overview starts collapsed. Last-commit dates are navigation hints, not verification receipts. Missing placements, stale anchors, unknown graph endpoints or invalid role positions fail the build. Ordinary prose links are audited; explicit graph declarations determine graph topology.

The following views are generated alongside the site. The documentation library defines the revision and evidence contract for readers, retrieval systems and agents:

EndpointWhat it holds
/docs/graph.jsonThe generated Markdown graph: document metadata, navigation regions, named ports and explicit typed relationships with preserved roles and ordinals.
/docs/outline.jsonEvery document's section headings and anchor ids, for deep-linking straight to a section.
/docs/llms.txtThe same catalog as plain text, grouped and summarised, following the llms.txt convention.
/docs/corpus/index.jsonPublic documents, content and rendering revisions, evidence status, source lineage, typed relations and section addresses.
/docs/corpus/search.jsonFull-text sections from the same corpus revision.
/docs/corpus/map.jsonShared Blueprint graph data and precomputed layout.
/docs/corpus/llms-full.txtFull section text with status, source provenance and citation addresses.
/docs/corpus/quality-audit.jsonStructural review signals and suggested actions for every public document.

Links in the prose create references edges. Frontmatter doc_relations can add requires, explains, implements, derived-from or supersedes when a stronger relationship is justified. Use doc_status: design for proposals. Claiming implemented or qualified also requires verified_at, verified_revision and a nonempty verification_evidence list. Build the corpus with npm run corpus; check heading/link parsing with npm run test:corpus. Authenticated documents and their full text stay in a separate private corpus.

The browser-local docs assistant has been removed. Public pages now hand off the document, revision and section to Manna's library for questions and PDF editions. Private pages do not hand their content to Manna.

Conventions for the prose itself:

  • Say what you scanned and what the source-of-truth status is. A doc that cannot be checked against code should say so.
  • If the doc is a plan, keep current state, proposed model, phases, tests, and open decisions separate.
  • Use Docusaurus admonitions (:::note, :::tip, :::info, :::warning, :::danger) for status and caveats. Markdown is parsed as lenient CommonMark, so literal braces and raw HTML pass through; complex tables can stay as HTML.
  • Put a status admonition near the top of anything that describes unbuilt work. "Designed" and "shipped" must never be ambiguous to a reader.

Deploy in the same change. Docs are public artifacts, so a docs change is not finished until it is live. From the bestbuds root:

CLOUDFLARE_HYPERDRIVE_LOCAL_CONNECTION_STRING_hyperdrive="postgres://localhost:5432/placeholder" npm run deployReaktor

That one command ships both surfaces — the workbench at / and this site at /docs/.

7. Where a change belongs

You are changingIt goes in
A cross-platform primitive, a Feature slot, a resource markerreaktor-core
Graph semantics — nodes, ports, visitors, lifecyclereaktor-graph-port then reaktor-graph
Canvas mechanics with no Reaktor meaningcompose-flow
Graph editing, layout, the editor sessionreaktor-flow
A new capability (a device, a service, a vertical)the owning reaktor-* module, composed through the graph
Workbench chrome, panes, inspectorsbestbuds/modules/engine
Design tokensreaktor-ui/machinesignal, transcribed from the design file
A framework doctargets/reaktorWeb/docusaurus/docs and its graph markers

When none of these fits, that is worth a conversation before the code — an unplaceable change usually means the boundary is wrong, not that a new folder is needed.