Architecture
Shinro has three layers: Shinro Studio (the app), the Cake kernel, and the module descriptor ecosystem the kernel manages. Each layer is data-driven: its behavior is derived from a contract (a data schema), not from another layer’s implementation. This decouples the layers: each can be reasoned about, tested, and replaced in isolation, without rewriting the others.
Data-driven design treats the data contract, not the code that consumes it, as the stable unit of a system. A layer’s behavior follows from the data it’s given, and other layers interact with it only through that data, never through internal state or implementation details. This is what makes independent replacement possible: as long as the contract holds, the implementation behind it is free to change
- Contract: the data schema a layer exposes and guarantees to honor (its interface, not its internals)
- Coupling: a dependency between two components. Data-driven design minimizes coupling to the contract only, never to implementation
- Isolation: the ability to reason about, test, or modify one layer without knowledge of the others’ internals
- Substitutability: a layer can be replaced by any implementation that satisfies the same contract, with no change required in the layers that depend on it
Together, these properties mean the three layers evolve independently, as long as each preserves its contract
Local reasoning
Local reasoning is the property that lets you understand a component from its own interface alone, without tracing the rest of the system’s state. Sean Parent frames this at the function level, through value semantics: a function’s behavior is fully described by its inputs and output, so no external context is needed to verify it. Shinro applies the same property one level up, at the module and layer boundary, enforced by declared contracts
Module topology
- A module’s only visible surface is its descriptor: what it provides, what it requires, its inputs and outputs
- Other modules connect to it through that surface, never through its implementation
- The module graph is therefore a graph of contracts, not of execution flows. Reading one descriptor is enough to know how a module composes.
Module semantics
- The kernel checks provides against requires at compile time, not at runtime. A mismatch is a static error, not a crash mid-deployment
- Hot-swap depends on this: a module is replaced as a unit, and the kernel only needs to recheck the contract, not the internals of the modules around it
- Shared memory changes the transport, not the contract: it removes serialization cost (no serialize/deserialize step between modules), but a module’s declared inputs and outputs still define what it may read, not the shared region’s layout. This is zero-copy IPC, not unscoped aliasing
Studio’s topology
- This is one hop of indirection, and the same mechanism as the module graph: Studio reasons about the system through the kernel’s data (descriptors, state), not through hardcoded knowledge of any module’s internals
- A data-driven frontend is local reasoning applied at the UI layer: Studio’s rendered state is a function of the data it receives, so understanding what Studio shows requires only that data, not the history of how the system arrived at it
Three mechanisms (descriptor contracts, compose-time checks, data-driven rendering), one invariant: reasoning about one part of Shinro never requires reasoning about all of it
1. Shinro Studio — the app
The surface where users blueprint, flow, code, simulate, and deploy. It interacts with the system through data, not through runtime API calls: it reads and renders the same state (descriptors, execution graph, module status) the Kernel exposes, and writes back to that state, rather than invoking methods on a running system
This is what data-driven design buys at the interface level:
- Multiple views can render the same underlying data: a blueprint canvas, a flow graph, a 3D viewer, a dashboard, each a different projection of one state, not a separate integration
- Those views interoperate by construction: since they consume the same contract, a change made in one is reflected, consistently, in every other
- The interface isn’t bound to one surface: desktop, web, an embedded panel on the hardware itself, a CLI, or a headless dashboard are all valid renderings of the same data. None requires a different integration with the Kernel
2. Backend Kernel
The Backend Kernel — Cake — is the core modular infrastructure that enables the Studio to work. Written in Zig. The kernel manages modules and orchestrates the build and deploy pipeline.
Five responsibilities define what “orchestrates” means:
- Acquisition and build: for every module a project depends on (application, library, dataset, script, or other asset), the kernel resolves where it comes from and how it gets built, before it becomes available to the graph
- Isolated workspace: the kernel materializes each project into its own working folder, isolated from the host system the kernel runs on. Each project resolves its own tool versions inside that folder, so two projects can depend on incompatible versions of the same tool without conflict
- Stores: reusable workspaces, shared across projects. Where a working folder belongs to one project, a store holds acquired or built module content that any project resolving it can reuse, without re-acquiring or rebuilding it
- Version locking: the kernel resolves each module’s version constraints once per project, and that resolution stays fixed. A library or tool’s version doesn’t drift or get misconfigured between builds
- Contract enforcement: the kernel composes the application as a contract between its modules. It checks provides against requires, and guarantees the input/output declared in each descriptor, before anything runs
A thin Cake kernel is resident wherever modules run — typically the robot’s onboard computer, equally an edge device or cloud host. Residency is what makes hot-swap possible: the kernel loads, supervises, and swaps modules on the running system. Unlike runtime-imposing frameworks, it does not interpose on the data modules exchange: modules communicate through the interprocess plumbing generated at compile time, not through a Shinro message layer or broker. It also does not interfere with data-processing workflows that already exist on the robot — it runs side-by-side with systems like ROS and acts independently, or integrates with them (a ROS graph wrapped as a kernel module, or the kernel running as a ROS module). Determinism, scheduling, and execution policy are decided per module, by the module’s author
3. Module descriptor ecosystem
Descriptors are data-driven files: each one declares every contract a module satisfies at both compile time and runtime, in one place. TOML-inspired syntax is used in the descriptors. Descriptors span toolchains, libraries, hardware abstractions, and higher-level capabilities. See The Module System for the schema and the composition rules
- Composable and reusable: since a module’s contract, not its implementation, is what other modules depend on, families of modules combine into higher-level capabilities
- Compile time and runtime: a descriptor governs both phases. Acquisition and build resolve before anything runs, governed by provides/requires; input/output governs what executes once composed.
- Minimal by construction: the kernel acquires and builds only the fraction of the graph a project actually requires. What isn’t declared as a dependency is never pulled in, so the system’s footprint is bounded by what’s needed, not by what’s available
Workstream alignment
Two workstreams run in parallel — the Studio frontend and the Backend Kernel. The frontend ↔ kernel integration is the immediate critical path; until it lands, any end-to-end description in these docs is architectural, not operational.