Reaktor Framework Overview
Reaktor is a Kotlin Multiplatform framework built on a single idea: a full-stack application is a typed directed graph. Screens, routes, services, actors, repositories, and the data they exchange are nodes; the contracts between them are edges. That graph is not a diagram drawn after the fact — it is the program. It runs on Android, iOS, the JVM, the browser, and Cloudflare Workers; it renders the UI and mounts the services; and because its structure is data, it can be walked, validated, visualized, and — by people and AI agents alike — safely changed.
This document is about what that idea makes possible. The thesis is simple to state and far-reaching in consequence: when the structure of a system is a typed, inspectable graph rather than an implicit fact spread across a thousand files, both humans and machines can build more. Developers compose features by wiring nodes instead of threading globals; the framework derives routing, auth, navigation, telemetry, and deep links from the same structure; and an AI agent reads a precise model of the running system instead of guessing at it from source. The graph is the common language that lets all of them operate on one truth.
1. The model: full-stack modules and a graph that wires them
Two ideas, together, define Reaktor.
1.1 Modules are full-stack
Most app frameworks are client frameworks; most backend frameworks are server frameworks. Reaktor's modules are deliberately full-stack and polyglot — a single module reaches from the UI, through the device, across the wire, onto the server, and out to the edge. The reaktor-auth that drives a "Sign in with Apple" button on iOS is the same module that verifies the resulting JWT inside a Cloudflare Worker and inside a Spring WebFlux filter. The reaktor-service contract a screen calls as a client is the handler a server mounts. The Service types compile to Android, iOS, the JVM, and JavaScript from one source.
A module is a focused capability — auth, data, services, media, work, serialization, native interop — delivered as small, composable primitives rather than a monolithic SDK. The design discipline is explicit and unusually strict:
expressivity & composability ≫ simplicity ⋙ one-shot, bloated APIs
React's declarative model and the actor mailbox are the touchstones: a handful of orthogonal primitives that compose into arbitrarily complex applications. The common 80% of use cases should be easy through abstractions occupying ~20% of the surface, while the rare 20% stays reachable by dropping to lower-level primitives.
1.2 The graph wires them
Modules do not call each other through singletons or ad-hoc injection. They are composed into a graph:
- A
Graphis a scoped runtime container owning its nodes, a dependency-injection scope, a lifecycle state machine, a coroutine scope, and navigation state. - A
Nodeis the unit of behavior — a screen, route, container, interactor, service, actor, or repository. ProviderPort<T>/ConsumerPort<T>are typed contracts. A node provides what it offers and consumes what it needs; it never reaches for a collaborator directly.- An
Edgeconnects a consumer port to a provider port, validated when it is drawn. autoWire()matches every unconnected consumer to a compatible provider by type and key, falling back to DI — so wiring is largely inferred rather than hand-threaded.
The consequence is the load-bearing one: the structure of an application becomes data. It can be walked, serialized to JSON, rendered as a live blueprint, validated, diffed, and handed to a tool or an agent — none of which is possible when structure lives implicitly in import statements and constructor calls. This runtime is reaktor-core, reaktor-graph-port, and reaktor-graph (see the Graph Kernel).
1.3 Wiring that is also a program
Static wiring — which provider feeds which consumer — resolves once, at construction. That is composition, not computation. Reaktor's further ambition is that the same canvas on which modules are wired is also a Turing-complete, statically typed, flow-based programming language: the Reaktor Visual Language, in the lineage of Unreal Engine Blueprints.
The model superimposes two graphs on the same nodes: a data graph (a DAG of pure value dependencies, evaluated on demand) and an exec graph (a control-flow relation among effectful nodes, driven by a moving execution token). It is Turing-complete by two independent routes — recursion and unbounded loops over mutable state — and has a single source of truth: a Kotlin DSL that builds the graph, with the visual canvas as a one-to-one projection of the same syntax tree. Effects are isolated to exec-bearing nodes, which keeps evaluation deterministic and replayable; stateful behavior is delegated to the actor model. The structural wiring is Implemented; the execution layer that makes the canvas a programming language is In design — the one genuinely new runtime capability the visual language requires.
2. The Unreal Engine model
Unreal Engine is the clearest precedent for what Reaktor attempts, and the analogy is structural, not decorative.
Unreal works through a separation of concerns across time. A small number of engine programmers build durable systems — rendering, physics, animation, AI, networking — that change slowly and carefully. A large number of designers compose many different games on top of those systems through Blueprints, a visual node graph, without ever destabilizing the core. The engine is the slow, rigorous layer; composition is the fast, expressive layer; and a shared graph is the glue between them.
Reaktor brings that structure to application and server development:
| Unreal Engine | Reaktor |
|---|---|
| Durable engine systems (rendering, physics, AI, networking) | Reaktor modules — auth, data, services, actors, media, work, FFI, serialization |
| Blueprints visual scripting | The Reaktor Visual Language over reaktor-graph (in design) |
| Level designers composing games | Product teams — and agents — composing apps and services on the graph |
| The engine's scene graph | The Reaktor application graph — the common glue |
| Reflection and inspection | Snapshots, visitors, source maps, inspectors |
| Editor and tools | The reaktorDesktop workbench, the CLI, the agent interface |
What Reaktor borrows is the discipline: slow-changing runtime laws — a node's lifecycle, an actor's serialized mailbox, a port's type contract — that you do not get to violate, paired with a fast-changing composition surface where building a feature means placing nodes and drawing wires. Crucially, the graph an operator drags is the same graph the runtime executes, the profiler annotates, and an agent reads. This is vertical integration as a strategy: owning product, graph, runtime, infrastructure, observability, and tooling end-to-end, the way Apple owns silicon-to-OS and Google owns TPU-to-model. Full context plus integration produces better systems than stitching disconnected tools.
The analogy even has a sandbox. The Reaktor Arena — in design, a standalone Unreal Engine 5 project with no runtime dependency on Reaktor — exists to make these ideas physical: graph execution, actor-owned state, visitor traces, reproducible scenarios, and "why did this happen?" inspection, in a real-time combat world where they are immediately visible.
3. Familiar analogies: Expo, Dapr — and what's new
For readers who know the modern toolchain, the shortest description of Reaktor is (Expo + Dapr) +. Each analogy contributes one thing:
| Analogy | What it contributes | Reaktor's expression |
|---|---|---|
| Expo | One managed, batteries-included toolchain for cross-platform apps | The Reaktor CLI (new/dev/build/deploy/test/doctor), which discovers any project's declared targets and tasks |
| Dapr | Portable building blocks for distributed apps — service invocation, state, pub/sub, bindings, actors — decoupled from infrastructure | Typed ports (request, mailbox, state, stream, value) backed by Service/RequestHandler, ActorNode, ObjectStore, and Flow, addressed by contract identity wherever they run |
| Unreal | Durable engine systems composed visually; slow core, fast composition | Modules as the engine layer, the graph and visual language as the composition layer |
The "+" is the contribution. Expo and Dapr give you a managed toolchain and portable blocks, but no unified model of the running system. Reaktor adds a single typed, inspectable graph as one structured language across the whole value chain — client, server, edge, infrastructure, and observability — and makes that graph a surface people and AI agents co-author together. The graph is the application; it emits live telemetry; and the CLI, the workbench, and the agent interface are three faces of one task catalog. That unification, not any single building block, is the bet.
4. One model across the whole lifecycle
In an ordinary stack, the relationships that define a product live in different worlds and never quite agree: a screen knows a router, a service knows a client, a worker knows a queue, a dashboard knows a metric, a deploy script knows an environment. The developer is the integration layer, holding the cross-platform, cross-environment picture in their head and paying a tax every time a feature crosses a boundary.
Reaktor makes those relationships first-class graph facts, so almost every activity in the development lifecycle has a representation in one model:
| Developer activity | Reaktor representation |
|---|---|
| Design feature boundaries | graphs, modules, capability requirements |
| Build screens and flows | routes, screen / ComposeNodes, navigation edges |
| Build services | Service, request/response DTOs, handlers, ServiceNodes |
| Manage state | actors, repositories, object stores, data descriptors |
| Integrate platforms | feature slots, adapters (media / location / work / auth) |
| Integrate infrastructure | resource nodes, bindings, Pulumi/wrangler materializers, secrets |
| Validate safety | visitors, findings, auth coverage, privacy lint |
| Test | Maestro, Playwright, Karate, k6, shadow replay, graph-linked gates |
| Deploy | Dagger, Pulumi, wrangler, k3s, Fastlane — as graph operations |
| Observe | OpenTelemetry, Grafana, analytics, performance budgets, node attribution |
| Operate | cloud pane, workbench commands, typed operation providers |
The application runtime — the upper rows — is fully implemented and runs in BestBuds today; the lower rows (infrastructure, deploy, the unified operations graph) are in design. The point is the shape: build the graph once, and the same structure renders the UI, mounts the services, validates the auth, attributes the telemetry, and describes the deployment, instead of being re-encoded, slightly differently, in a dozen disconnected tools.
5. Expanding what people and AI agents can build
The graph's deepest payoff is that it gives humans and machines a shared, precise model to author against — and in doing so, raises the ceiling for both.
For people, the graph is legibility and leverage: you can see the whole product as a structure, navigate from a button to the database it ultimately writes, ask which routes are unguarded, and edit behavior by wiring nodes on a canvas instead of memorizing where a feature is scattered. For AI, the change is more fundamental.
Today's coding agents edit text. They are blind to the running system: structure is inferred from source, APIs are guessed, effects are verified only by re-reading or by slow rebuilds, and mistakes land silently. This works, but it leaves enormous capability on the table. Reaktor's stance is not to replace those agents but to ground them — the same agents, with a model of the system instead of a guess. Four things change when an agent operates on a typed graph rather than raw files:
- Deterministic context. A graph snapshot — stable identities, deterministic ordering — paired with live telemetry gives an agent the actual structure and behavior of the running system, repeatable across runs. The graph is a semantic layer: it already encodes that
AppleButton → loginWithApple → AuthInteractor → AuthService → cf.workers/auth → d1.sessions. Reading flat code, an agent would trace eight files and four indirection layers to reconstruct the same fact. - Typed tools, not invented APIs. Ports carry contracts and schemas, so an agent calls real operations, never hallucinated ones.
- Validated, reversible change. Mutations flow through one spine: a structured command compiles to a canonical
GraphPatch(ConnectPort/AttachNode/SetAttribute/Composite), which is dry-run validated (Graph.apply(patch, dryRun = true)returns typedFindings), then committed atomically with rollback (Wiring.commit). A second, behavioral dry-run can replay the change against captured production traffic (the Shadow Dev Environment). - Review by construction. A Critic gates by withholding approval — it never applies. Application is performed by a principal holding the
graph:applycapability, whether an explicitly trusted agent or a human.
Authority is a property of the graph, not an afterthought. Permission scopes (graph:read, graph:propose, graph:apply, tests:run, cloud:read, db:read) decide what an agent may do, and a withheld scope means the corresponding tool is never registered; deploy:* is reserved for humans, auth:admin for no agent at all. The auth kernel even models PrincipalKind.AGENT as a first-class principal, excluded by default until granted. The same task catalog is exposed three ways — the CLI, the desktop workbench, and a Model Context Protocol server — behind one policy gate. This is the Reaktor control plane.
The substrate for this is real: the graph, ports, the Graph.toJsonElement() snapshot, the auth kernel, and a loopback read-only MCP server already run (reaktorDesktop serves a live graph snapshot on 127.0.0.1:8765); the workbench's command queue exists as a real CommandQueueActor. The validated mutation spine, the Critic loop, the deterministic snapshot pass, and the structured-output agent brains are in design. The direction is unambiguous: a graph that people and agents both read and write expands what either can build alone.
6. The graph system
Everything above rests on the graph. This is the technical core.
Reaktor is, in layers, a small runtime substrate (reaktor-core, reaktor-graph-port, reaktor-graph), the full-stack modules on top (§7), and the authoring surface around both (§8). The substrate is a handful of primitives:
| Primitive | What it is | Example |
|---|---|---|
Graph | A scoped runtime and composition boundary (nestable) | the app, a chat feature, a worker |
Node | A unit of structure or behavior | screen, route, service, actor, repository |
Port | A typed contract a node provides or consumes | Auth.SignIn, Chat.Send, Media.Upload |
Edge | A validated connection between two ports | screen → interactor, client → service |
Capability | Composable behavior mixed into a graph | lifecycle, concurrency, DI, navigation |
Visitor | A pass that interprets the graph | wiring, auth coverage, route index, export |
Snapshot | A read-only serialization of the graph | workbench, agent context, exports, diffs |
Command / Patch | A structured, reversible mutation | connect ports, attach node, run operation |
The kernel stays small by design: it knows how to hold and mutate graph facts. Auth, services, actors, data, routing, deployment, and telemetry are modules that add vocabulary — never kernel features.
6.1 What a graph is
The runtime is two layers. reaktor-graph-port is the generic, contract-free port substrate; reaktor-graph adds application semantics — routes, navigation, lifecycle, DI, and Compose UI.
PortGraph<Self, N>owns nodes in a concurrent map and providesattach/detach/close/node(id)/nodesByType<T>().GraphextendsPortGraphand mixes in four capabilities by delegation:LifecycleCapability,DependencyCapability,ConcurrencyCapability,NavigationCapability. It carries a parent graph, a dispatcher, and a DI adapter, so graphs nest fractally: app → feature → container → screen / service / actor.
Nodes form a sealed hierarchy, so the complete set of behaviors is always known:
| Node | Role |
|---|---|
BasicNode | A plain unit of behavior with ports |
ServiceNode | Exposes a Service's handlers as provider ports — local implementation or HTTP proxy, transparently |
RouteNode<P, Binding> | A URL-like RoutePattern (e.g. /chats/{id}) with a route binding and navigation edges |
ControllerNode<State> | Owns observable state (MutableStateFlow) with an update {} helper |
ActorNode<M> | A graph node with a serialized mailbox and exclusive state (§7.5) |
ContainerNode | Manages nested child graphs and activates the right one per route |
ComposeNode<State> / ComposeContainerNode | UI nodes — a screen's @Composable Content() — bridging to Jetpack Compose |
6.2 Ports, edges, and wiring
Ports are how nodes communicate without knowing each other. A Port<F> carries a key and a type; a ConsumerPort<F> holds the edge it is wired through and can be invoked; a ProviderPort<F> holds the implementation and its consumers. They are declared with property delegates:
// A node provides what it offers and consumes what it needs — never a direct call.
class ChatListInteractor(graph: Graph) : BasicNode(graph) {
val sessions by consumes<ChatSessionStore>() // ConsumerPort: a dependency
val ui by provides(ChatListPresenter()) // ProviderPort: an offering
}
Wiring is largely inferred. Graph.autoWire() groups local providers by type, matches each unconnected consumer (respecting keys), and falls back to the DI scope when no local provider exists; requireFullyWired() throws if any consumer is left dangling, so wiring errors surface at composition rather than at runtime. Matching is by the port's type today; a richer ContractId — the cross-language, cross-runtime identity that lets a TypeScript Worker and a local Kotlin implementation satisfy the same port — is in design, and underpins several of the distributed capabilities below.
6.3 Capabilities: behavior composed, not inherited
Four capabilities make a graph behave like a living thing rather than a static diagram, all Implemented:
| Capability | What it provides |
|---|---|
LifecycleCapability | A guarded state machine — Created → Restoring → Attaching → Saving → Destroying — with onTransition hooks |
ConcurrencyCapability | An owned CoroutineScope and dispatcher (SupervisorJob); launch / async / withContext; cancels on close |
DependencyCapability | Scoped DI (Koin) with a parent/child hierarchy chained to the parent graph |
NavigationCapability | An ObservableStack back stack; dispatch(NavCommand) over Push / Replace / Pop / Return, including cross-graph routing |
6.4 Navigation is graph-native
There is no separate navigation library. A RouteNode defines a RoutePattern like /chats/{id} that round-trips parameters; a NavigationEdge connects routes with a typed Payload; a NavCommand drives the ObservableStack; and cross-graph navigation bubbles up automatically to the container that owns the destination. Return completes a deferred, so a pushed screen can hand a result back to its caller. A WebNavigationBridge projects this to browser URLs today. The primitives are Implemented; the unified deep-link resolver above them (§7.13) is in design.
6.5 Visitors: one traversal, many interpretations
This is the idea that turns the graph from a diagram into a programmable substrate. A visitor is logic applied during a traversal; a selector chooses which neighbors to follow; a traverser sets the order. Compose them and you get a pass — selector view, traversal order, visitor logic. Because the graph is one typed structure, every cross-cutting concern becomes a different interpretation of the same data rather than scattered, easily-desynchronized code:
| Pass | What it computes |
|---|---|
Wiring / autoWire | Connect consumers to providers; report unsatisfied contracts |
| Route index and deep links | Build the URL ↔ NavCommand table; plan a back stack to any destination |
| Auth coverage | Walk the parent chain collecting AuthRequirements; find unguarded routes |
| Analytics and telemetry | Attribute screen_views and spans to the exact node that produced them |
| Workflow compilation | Topologically order an async DAG of steps |
| Snapshot / export | Emit a deterministic document of the whole graph |
| Validation | Detect dead-end routes, cycles, cross-graph unreachability |
| Source maps | Map a node back to the source that defined it |
In a conventional codebase each of these lives in different machinery — a router config, an auth middleware, an analytics SDK, a workflow engine, a linter — that shares no model and inevitably drifts. In Reaktor they are the same walk, reinterpreted, and the walk is deterministic, which is what makes snapshot diffs meaningful, exports reproducible, and an agent's context window stable. The same passes that validate an application also generate its documentation and the context an agent reads. The legacy visitor (Selector, Traverser, ExitScope) and several passes — autoWire(), requireFullyWired(), toJsonElement(), Graph.instrument() — are Implemented; the unified pass framework (Visitor<R> with typed results, GraphPass with a dependsOn DAG, stable Finding codes, a graph.compile {} compiler) is in design. See Graph Visitors.
6.6 Snapshots and safe mutation
Two operations turn the live graph into something one can reason about and change:
- Snapshot (Implemented).
Graph.toJsonElement()recursively serializes nodes (id, type, label, route pattern, ports) and edges (port edges, and navigation edges flaggedcrossGraph), descending into container children. This is what the blueprint renderer draws and the read-only MCP server serves to agents. The deterministic, stable-identity snapshot pass needed for perfectly repeatable diffs and agent context is in design. - Mutation (in design). A validated, reversible spine —
GraphPatch→Graph.apply(dryRun)(returning typedFindings) →Wiring.commit(atomic, with rollback). Today mutation is direct and non-transactional (connect,attach,autoWire); the transactional spine is the keystone that lets both the visual editor and an agent change a live graph without risk.
6.7 The unified system graph
The most expansive form of the idea is one graph for the entire system — not only the app, but the Workers, the JVM servers, the cloud resources, the CI pipelines, the releases, the tests, the observability signals, and the operations that act on all of them (the Unified System Graph).
The model is careful where it counts. Node families are distinguished: a runtime interface ("I need a FileStorageApi") is not a binding (an R2 bucket binding) is not a resource (the actual bucket) is not infrastructure (the Pulumi definition) is not an operation (preview / apply / restart). Product code imports only interfaces; the engine sees the rest. Each system node can carry a desired state, an observed state, a Provenance with a confidence, and a DriftStatus when they disagree — the graph is materialized from real sources, never hand-drawn. Operations carry a SafetyClass (ReadOnly → LocalMutation → PreviewMutation → DeployMutation → Destructive) that decides who may run them. Polyglot services you did not write in Kotlin appear as nodes too, labeled by how they were learned — Native, Wrapped, Source-scanned, Runtime-observed — with trust tiers that gate which actions are safe (Graph Sources).
This is largely in design; one slice ships today: reaktor-cloud parses a real wrangler.json into a live resource inventory and runs Dagger and Pulumi through a process runner, and a Pulumi-via-Java program already materializes the observability stack (Cloudflare, Grafana, k3s, Supabase, Pub/Sub).
6.8 Polyglot: one graph, every target
The graph runs everywhere Kotlin Multiplatform runs, and the same composition targets the platforms the build actually wires: Android, the JVM (server and desktop), JavaScript (browser and Node, emitting a typed npm library with generated .d.ts), and iOS (iosArm64 plus the Apple-Silicon simulator). Cloudflare Workers are the edge deployment of the JS target. Native C++ — and the FlexBuffer and security libraries — is reached through FFI (prefab on Android, cinterop on Darwin, JSI for the embedded engine), not as a separate compilation target. The mechanism that lets one composition run across all of them is four primitives working together: Kotlin's expect/actual, the Adapter weak-reference bridge, the Feature slot registry, and capability composition. A node reads Feature.Database or Feature.Camera; whoever booted the platform filled the slot; the node never asks whether it is on Android. Implemented.
7. The full-stack subsystems
7.0 BestBuds, end to end
The abstractions become concrete in a real product. BestBuds source-builds Reaktor (a framework edit lands in the product instantly) and is assembled as one graph across three Gradle modules — modules/app (the shared KMP product: root graph, screens, interactors, actors, repositories, typed service contracts, and the Cloudflare Worker logic in Kotlin/JS), modules/design (a product theme over reaktor-ui), and modules/engine (the workbench host) — and deploys to Android, iOS, a web app, a Spring Boot server on k3s, and roughly fourteen Cloudflare Workers.
Follow one feature, opening the chat list, through the actual graph; every name below is real:
Route("/chats") ──▶ ChatListBinding ──▶ ChatListScreen (a ComposeNode; consumes the binding + interactor)
│
▼ consumes(ChatActors.Session)
ChatListInteractor
│
▼ provides(ChatActors.Session, ::ChatActor)
ChatActor : ActorNode<ChatMsg>
│
▼ PartySocket → "chat-service" Durable Object
ChatSocketInteractor
│
▼ MessagingApi (one Service contract, two implementations)
┌───────────────────┴────────────────────┐
MessagingServiceClient (device) CloudflareMessagingService (Worker)
│
▼ request.postgres(Hyperdrive)
Supabase Postgres
Notice what the graph buys. The interactor does not know whether ChatActor is local or remote. The screen does not know whether MessagingApi resolves to a local implementation or an HTTP proxy — ServiceNode makes that transparent. Sending a message renders optimistically in the actor, flows over the PartySocket to the chat-service Durable Object, which persists through the same MessagingApi and broadcasts to every peer in the room; an @bestbud mention forks to a bot Worker. The realtime path, the persistence path, and the client are three projections of one set of typed contracts — the whole thesis in a single feature.
The backend is a real fleet — messaging, chat (a PartyServer Durable Object), social, media, moderation, analytics, bot, and notification Workers, a Spring Boot server on k3s, Supabase Postgres (via one Hyperdrive binding), R2, Durable Object storage, and Memgraph and ClickHouse on k3s. And the same graph is what the workbench renders: reaktorDesktop registers BestBuds and draws its real route/port/actor topology in the Graph pane, served read-only to agents over the MCP loopback; the remaining panes are still fixtures.
The modules share one architectural rule worth stating before the tour:
7.1 reaktor-core — the foundation
Implemented. Every module imports core, which stays deliberately small. Feature is a process-global slot registry (object Feature : DependencyModule): platform services are declared as slots (var Feature.Auth by CreateSlot<AuthAdapter<*>>(), plus Database, Camera, Work, Telemetry, Secrets, and more) and read by name, which is what eliminates the "is this Android?" branch. Adapter<Controller> is a weak-reference bridge to a platform controller with null-safe invoke {} / suspended {}. Capability : AutoCloseable is a closable-resource marker — the third sense of the overloaded word "capability," distinct from the graph capabilities and the device-tier runtime of §7.15. See Reaktor Core.
7.2 UI: Compose-first, with React × Compose ahead
Implemented (Compose); interop in design. UI is not bolted beside the graph — it is graph nodes. A screen is a ComposeNode<State> (a ControllerNode whose @Composable Content() collects its interactor state); a ComposeContainerNode renders the active child graph for tabbed and bottom-nav layouts; one renderer, GraphContent, draws the top of the back stack on Android, iOS, desktop, and web. The design system is reaktor-ui: a DesignTokens system bridged into Material 3 by ReaktorTheme, a component set (atoms Button/Input/Card/Avatar, molecules EmptyState/SearchBar/ListItem, Responsive helpers), with bestbuds/modules/design layering a branded theme and component set on top. The frontier — React × Compose — is in design: React as a control-plane language over native Compose, and Compose-authored UI projected onto React DOM, over a shared canonical host tree. A foundational rule from the Visual Language: the structural app graph and the UI render tree are distinct surfaces — you navigate from a screen node into its UI, you do not merge them.
7.3 reaktor-auth and multitenancy
Implemented, and live in production. The graph-agnostic surface is a pure kernel with no DB, Spring, or graph dependency: AuthContext (principal, identity, app, tenant, session, token, scopes, roles, permissions, delegation, claims), an AuthRequirement DSL (forUsers(), audience(), inApp(), anyOf(), allOf(), allowDelegatedActor()), and one authorization law — LocalAuthorizer.authorize(context, requirement) : AuthDecision returning Allow or Deny(reason, statusCode, safeMessage). Principal spans USER, SERVICE, AGENT, and ACTOR. Client adapters let a mobile or web app sign in with no graph at all — Google on Android/iOS/Web, Apple on iOS/Web.
The graph projection is opt-in: AuthNode / AuthPolicyNode, AuthGraph.install(graph), SecuredProviderPort / SecuredConsumerPort that gate port connection on a requirement, and a JVM AuthRuntimeGraph wiring roughly a dozen nodes via autoWire(). Boundaries are thin and stateless: a ReaktorSecurityWebFilter for Spring WebFlux and a WorkerJwtVerifier for Cloudflare, both verifying locally against JWKS. The same identity, three runtimes. Multitenancy rides on this: tenant context enters once as an unforgeable AuthContext.tenantId (the tid claim, minted and round-tripped today) and is bound by adapters rather than re-supplied by callers; the shipped enforcement point, MandatoryTenantParameterization, rejects any Cypher that fails to scope by $tenant_id. The framework-wide version — Postgres RLS, tenant-keyed stores, cells — is in design. See Auth and Multitenancy.
7.4 reaktor-service — the service layer
Implemented. Define a contract once; use it as client and server. Request and Response are @Serializable with @Transient transport side-channels. A RequestHandler binds an endpoint to logic; GetHandler / PostHandler / PutHandler / DeleteHandler are thin factories. Endpoint identity is decoupled from address: the portKey is the operation (e.g. Health.Ping), the portType encodes transport and operation, so the route is a runtime address while the operation is the stable identity telemetry and the graph track. A Service owns its handlers and a generic Ktor client proxy, so the same contract a server mounts is what a client calls. It mounts onto Spring WebFlux (Service.toRouter()) and Cloudflare Workers via Hono (Service.toHono()) — exactly the two-implementations pattern of §7.0. ServiceInterceptor chains host auth, logging, and caching; in the graph, a ServiceNode exposes each handler as a provider port. See Reaktor Service.
7.5 The actor model
Implemented (local); distribution in design. Stateful, concurrent behavior is only the actor model — no second runtime, no shared mutable state. An ActorNode<M> is a normal graph node pinned to a single-threaded dispatcher (limitedParallelism(1)), draining a private Channel mailbox in order. An ActorRef<M> exposes send / trySend / ask (request-reply via CompletableDeferred with a timeout); exclusive state lives in an ObjectStore; supervision turns a failure into an ActorDirective — Resume / Restart / Stop / Escalate — so one bad message never kills the actor. The shipped reference is BestBuds' ChatActor : ActorNode<ChatMsg> (§7.0). Distributed actors — location transparency across k3s pods and Durable Objects, virtual-actor activation, sharding — are in design over the existing ActorNode. See the Actor Model.
7.6 The data layer — reaktor-db
The Data layer reading map is the canonical entry point. It groups storage, scope, transactions, Bus/Mesh, Queues/Workflows, inspection and implementation evidence in one section.
Partial. The discipline is the spine: each dataset declares its authority and the policy for other representations. Derived data is only disposable when its inputs, transform and retention make rebuilding reliable. Implemented today is a genuinely useful on-device, observable store: an ObjectDatabase of named store {}s with a DatabaseEvent stream whose Origin (Local / External / Sync / Migration) is the seam a sync engine uses; ObjectStore guards writes with a striped KeyedGate and a configurable cache; ObjectState<T> is a StateFlow<StoredObject<T>?> handle; SqliteObjectDatabase persists via SQLDelight on every platform. On the server, GraphDb wraps Memgraph over Bolt with mandatory tenant scoping, and CrudRepository rides Exposed for Postgres. The larger design — the DataDescriptor authority/projection model, the five-tier topology (Postgres, Memgraph, ClickHouse, D1, Durable Objects, R2, Pub/Sub, Queues, on-device SQLite), and the server-authoritative sync engine — is in design. See the Data Layer.
7.7 reaktor-io — the transport spine
Implemented. The networking layer every module rides: RoutePattern, an expect Ktor client with real per-platform engines and shared middleware, a WebSocket stack with a ConnectionState flow and ExponentialBackoffStrategy, PartySocket (the PartyKit / Durable-Object connector used by chat), and FileAdapter / Compressor capability slots. This is the concrete substrate behind §6.8.
7.8 FlexBuffers — reaktor-flexbuffer
Implemented, experimental. Reaktor's Kotlin Multiplatform FlexBuffers codec
provides direct encoded access, generated @Struct coders/accessors, caller-owned
output and pooling. Generated full decode avoids navigation wrappers but still
allocates models, strings and collections; copying encode returns a new ByteArray.
The October review measures JVM, actual Chrome
and physical Android/iOS against JSON and Protobuf. Gains depend on direction and
shape: numeric vectors are strong, browser object encode often loses to JSON, and
caller-owned output does not always improve time. Android bulk-view experiments
show large opportunities, but production Android bulk actuals remain scalar.
Four correctness repros remain open: map hashing, valid-empty-map validation, explicit-null/default handling and generated required-field/layout rejection. Fingerprints/keyed negotiation are also absent, so this is not a qualified default for independently deployed schemas or persistence. The signed-width upgrade needs readers first, writers second; existing wide-negative buffers need no eager re-encoding. See the implementation guide and the complete wire-to-mutable-memory book, whose LiveFlex runtime is a proposal distinct from the implemented wire codec.
7.9 FFI — reaktor-ffi
Partial — an experimental, verified bridge. Kotlin talks to native C++ and an embedded JS engine over a FlexBuffer-RPC envelope: a payload vector positionally encodes [moduleName, functionName, sequenceNumber, ...args], where a non--1 sequence number marks a streaming call, dispatched through a Switch registry. On Android, a bidirectional JNI/fbjni round-trip is real and tested; on iOS, a cinterop call proves linkage; the embedded Hermes engine boots and evaluates a bundled sample over JSI. Typed argument decoding, the generic transport, ABI versioning, and cancellation are the roadmap. See Reaktor FFI.
7.10 Background work — reaktor-work
Partial. Current source is a legacy TaskManager<Controller> facade over Meeseeks with thin platform context adapters and a Feature.Work slot; its bundled worker bodies are placeholders. The selected replacement removes Meeseeks and separates graph-owned WorkStore, WorkRuntime and WorkScheduler. The Work architecture defines scoped admission, claims, effect recovery and direct WorkManager, BGTaskScheduler, browser, Cloudflare and Spring profiles. Those replacement profiles remain proposed, rather than established by the current wiring.
7.11 Media — reaktor-media
Partial. Built on the core Adapter + Feature pattern: a CameraAdapter (CameraX on Android, AVFoundation on iOS) with a Compose Render(), a GalleryAdapter (a full PHPickerViewController on iOS), an image cache over Coil, and a typed MediaService for upload and download over the Service contract. Camera, gallery, and image caching ship on mobile; the speech recognizer and synthesizer are placeholders.
7.12 Notifications — reaktor-notification
Partial — the client ships, the cloud send-path is in design. A feature that looks like "push notifications" is in fact a full-stack graph slice. The client SDK is real and Maestro-verified on Android and iOS: permissions, channels, FCM/APNs token bridging, local scheduling, tap and direct-reply, and a typed NotificationRoute (OpenPath / GraphAction) so a tap becomes a graph action rather than a string. The cloud side — Workers for fan-out, Durable Objects for coordination, Queues, D1 operational state, FCM HTTP v1 sends, with the server acting as a narrow token broker — is in design. See Notifications.
7.13 Deep links and device capabilities
Navigation is the graph-native system of §6.4 (Implemented). Deep links add a single canonical DeepLink resolved by read-only visitors (route index, back-stack planner, guard collector, schema emitter for assetlinks.json and apple-app-site-association) over thin platform bridges — in design over those primitives. Other device capabilities follow the same Adapter + Feature shape, including reaktor-location (mobile location adapters) and reaktor-secrets / reaktor-security (§7.15).
7.14 Telemetry, analytics, and performance
Implemented foundation; governance in design. Real today: AnalyticsAdapter (Firebase), TelemetryAdapter (OpenTelemetry over a Feature.Telemetry slot), CrashlyticsAdapter, and the differentiator, Graph.instrument() / GraphTelemetry, which non-invasively observes a graph's lifecycle, navigation, and port flows and emits OTel spans and automatic screen_views, recursing into child graphs. BestBuds runs an end-to-end analytics pipeline (a batching HttpAnalyticsSink → a Worker → Supabase). reaktor-performance makes "performance is earned, never guessed" concrete: real report and budget models (ReaktorPerformanceBudgets.requireWithinBudgets() fails CI), flamegraph and profiler captures, the FlexBuffer adversarial harness, a JMH suite, and a Lighthouse driver wired end-to-end. The unifying layer — a typed event-catalog DSL with privacy lint as a gate, explicit per-signal graph attribution, and the export to Grafana LGTM — is in design. See Analytics & Telemetry and Performance & Observability.
7.15 The distributed fabric
The most forward-looking designs extend the substrate above; they are not yet separate modules. Server primitives (reaktor-server) map idempotency, retries, rate limiting, circuit breakers, queues, outbox, CQRS, saga, and event sourcing each to a single ServiceInterceptor, with JVM and edge implementations — auth being the one already live. Bus (reaktor-bus) defines the durable async fabric over GCP Pub/Sub, Cloudflare Queues, outbox, idempotent consumers, DLQ, replay, and graph-visible routing; the PubSubAdapter substrate is real and wired to Google Pub/Sub with ordering keys. Mesh (reaktor-mesh) owns live peer/session/media/data topology over WebRTC, Cloudflare TURN, Cloudflare Realtime SFU, and DataChannels. The capability/degradation runtime (reaktor-capability) lets product code declare a CapabilityRequest and branch on a five-level CapabilityTier derived from a privacy-bucketed EnvironmentSnapshot rather than raw device facts. And reaktor-security models each conversation as an MLS group (the C++ tree is real, the Kotlin surface in progress) — message encryption atop the identity of reaktor-auth.
Module map
| Module | Layer | Status | What it gives you |
|---|---|---|---|
reaktor-core | foundation | Implemented | Feature slots, Adapter, Capability, primitives |
reaktor-graph-port / reaktor-graph | foundation | Implemented | Graph runtime, nodes, ports/edges, wiring, lifecycle, DI, navigation, actors, Compose UI |
reaktor-io | foundation | Implemented | Route patterns, Ktor HTTP, WebSocket/PartySocket, file, compression |
reaktor-ui | UI | Partial | Design tokens + theme, Compose atoms/molecules; web React wrappers partial |
reaktor-auth | identity | Implemented | OAuth2/OIDC, JWT/JWKS, sessions, RBAC, tenant claim; dual-surface reference |
reaktor-service | services | Implemented | Typed Request/Response, handlers, client proxy, Spring/Hono mounts |
reaktor-db | data | Partial | Observable on-device store; server Memgraph/Postgres + tenant scoping; sync engine in design |
reaktor-flexbuffer | serialization | Implemented / experimental | Generated coders, direct encoded access and measured target-specific performance; four correctness defects open |
reaktor-compiler | codegen | Partial | KSP FlexCoder generator; JS Promise-wrappers (dormant); multi-language schema gen |
reaktor-ffi | native | Partial | Native hello/JNI/cinterop proof; typed argument/transport TODOs and versioned frame design |
reaktor-work | runtime | Partial | Legacy facade; Meeseeks removal and graph-owned Store/Runtime/Scheduler with direct platform profiles in design |
reaktor-media | device | Partial | Camera, gallery, image cache (mobile); speech placeholder |
reaktor-notification | device + cloud | Partial | Real Android/iOS notification SDK (Maestro-verified); cloud send path in design |
reaktor-location | device | Partial | Android/iOS location adapters |
reaktor-telemetry | observability | Implemented | OTel adapter, Firebase analytics/crashlytics, graph instrumentation |
reaktor-performance | observability | Partial | Report/budget/flamegraph models and harnesses; Lighthouse wired; OTel export in design |
reaktor-cloudflare | cloud | Implemented | Workers, D1, R2, KV, Durable Objects, PartyServer, Hyperdrive, bound secret store |
reaktor-google | cloud | Implemented | Pub/Sub adapter (ordering keys); Sheets |
reaktor-cloud | ops | Partial | Wrangler inventory, Dagger/Pulumi runners, Pulumi observability program |
reaktor-secrets | ops | Implemented | GCP Secret Manager stores, Feature.Secrets |
reaktor-security | security | Partial | End-to-end (MLS) conversation crypto: C++ real, Kotlin surface in progress |
compose-flow / reaktor-flow | editor | Partial | Generic flow canvas + Reaktor graph scene/editor |
dependeasy | build | Implemented | Gradle convention plugin: KMP targets + native CMake/fbjni/Hermes |
reaktor-react | UI bridge | Paused | React Native JSI bridge (historical) |
Specified over these modules, not yet modules: server resilience primitives, bus, mesh, the capability/degradation runtime, and the unified system graph.
8. The authoring surface
The modules are the engine; this is where people and agents compose on top — Reaktor's fast layer.
- The Visual Language (in design). A Blueprints-style canvas where every node, pin, and wire is a projection of graph facts, with a Kotlin DSL as the source of truth and two backends: an interpreter for live editing and a compiler that lowers a finished program to reviewable Kotlin, actor behaviors, or workflow DAGs. The canvas edits source; it is never opaque state.
- The editor stack (Partial).
compose-flowis a generic, Compose-native flow canvas (pan/zoom/fit, nodes, edges, handles, minimap);reaktor-flowadapts a realGraphinto a scene —buildReaktorFlowGraph(graph),ReaktorGraphEditor, twentyReaktorNodeKinds, a Blueprint layout strategy. It renders the graph today; the bidirectional editor that emits patches is the next step. - Code generation (Partial). The FlexCoder KSP processor (§7.8), a JS
Promise-wrapper generator, and a multi-language (Kotlin/C++/TypeScript) schema generator that produces the typed*/ts/exportbridges. - The control plane for agents (in design; substrate real). The agent interface of §5 — snapshots, typed tools, permission scopes, the command queue, dry-run patches, and the Critic — exposed over MCP. A read-only MCP server and the workbench's
CommandQueueActorrun today; the structured-output agent brains, evaluated and specified, are next. See Control Plane, Koog Workbench, Browser-Local AI. - The CLI (Partial). Like
cargoorflutterfor any Reaktor app — discovers a project's targets and tasks, self-installs, runs through JVM-native runners, gates mutating commands. One face of the shared task catalog, alongside the desktop workbench and the agent interface. - DevOps and operations (Partial). Deployment as a graph operation (DevOps):
reaktor-cloud's wrangler inventory, Dagger and Pulumi runners, the Pulumi-via-Java observability program, wrangler fleet deploys, k3s on GCE, and Fastlane ship today; the unifyingPlatformmodel and deployment-as-graph-partition are in design. Secrets (reaktor-secrets) are GCP Secret Manager-backed and keyless. - Testing and shadow (Partial). Maestro, Playwright, Karate, k6, and the performance budgets of §7.14 are real; the Shadow Dev Environment — a moving production mirror with anomaly capsules feeding one R2 corpus — has a shipped Keploy capture slice, with the edge proxy and capture-as-visitor ahead.
9. What runs today, and where it goes
Reaktor today is a substantial, coherent, product-backed runtime. The graph kernel — graph, nodes, typed ports, wiring, lifecycle, DI, navigation, Compose UI — runs; so do local actors, the auth system (in production), the service layer with Spring and Workers hosts, the observable data store with server graph and Postgres access, the transports, FlexBuffers, the Cloudflare and GCP bindings, the telemetry foundation with graph instrumentation, the performance harnesses, the native FFI bridge on Android, the notification client SDK, the design system, and the secrets stores. BestBuds runs on all of it.
The frontier is wide and deliberately so: the unified pass framework and the validated mutation spine; the unified system graph and polyglot sources; the tiered data topology and sync engine; the server resilience primitives and the bus-and-mesh fabric; the capability runtime; framework-wide multitenancy; the distributed-actor cluster; the React × Compose kernel; the visual language and its bidirectional editor; and the full control plane for agents. Each is specified in depth, and each extends — rather than replaces — the core that already runs.
The through-line is a single discipline: one typed graph, shared by people, the runtime, the tools, and AI agents. A solid core proves the model; the program is to carry that one idea across the whole system — and as models improve, a graph that an agent can read and safely change only grows more valuable.
10. Further reading
Start with the model: this overview → §7.0 (the BestBuds trace) → the Graph Kernel → Reaktor Core → Auth, the cleanest end-to-end module.
Go deep on the graph: Graph Visitors, the Actor Model, Flow Analysis, and the Unified System Graph.
The authoring frontier: the Visual Language, the Control Plane, and React × Compose.
Operating it: DevOps, the Cloud Pane, Secrets, Performance & Observability, and the Shadow Dev Environment.
The one idea to carry away: Reaktor turns the structure of a full-stack, cross-platform application into a typed graph — so the people who build it, the runtime that executes it, the tools that inspect it, and the agents that help extend it all read and write the same thing.
Related docs
- Graph Kernel - Use to keep the core language small while modules add vocabulary.
- Graph Visitors - Use before designing a new graph pass or selector.
- Unified Graph - Use before building Reaktor Engine graph importers, cloud/resource nodes, operation providers, or MCP graph tools.
- Core - Use before touching the platform boundary: registering a Feature slot, writing a platform Adapter, or branching on a device/network capability (which must be a normalized tier, never a raw model string).
- UI & Design System - Use before touching reaktor-ui tokens/components/theming, designing the tactile runtime (interaction/physics/field/skins/host-tree/reconciler), or planning adaptive layouts and AI/server-driven UI.
- Competitive Landscape - Use when clarifying Reaktor's graph-native differentiation, competitor pressure, and where to integrate instead of compete.