Module Declarations
Subject and compatibility boundary
A module is code, private state and lifecycle declarations. Its name/version and content identity identify data. It has no signing key or account. A neuron may install a module as a prog through an adapter that pins the machine, environment, proof profile and worker contract; that adapter supplies durable continuations, operation claims and resource accounting. Rs hot-swap migrates module state at a safe step boundary and preserves public interface metadata. It does not itself persist a neuron invocation or authorize effects.
The canonical APIs are module!, Module and ModuleMetadata. For the 0.1
compatibility release only, cell!, Cell and CellMetadata are deprecated
aliases of exactly these APIs, without independent state or identity. They are
scheduled for removal in 0.2. Generated struct names, field layout, function
metadata and migration semantics remain unchanged. Old reference URLs redirect
here. Rust core::cell and UnsafeCell retain their standard meanings.
Budget/deadline/heartbeat declarations describe requirements. A worker must
advertise and enforce the supported guarantees. DeadlineFuture wraps a future
and exposes its deadline; it is not a preemptive timer or durable scheduler.
Compiler vs Runtime Boundary
Rs is a compiler. It defines the shape of a module and enforces structural correctness at compile time. The runtime consumes module declarations and provides lifecycle management.
Rs enforces (compile time):
- State and step_state structs are well-formed
- Migration implementations type-check against previous version
- Step state resets are generated correctly
- Public interface is introspectable via
ModuleMetadata - Bounded async deadlines are present on async methods
Rs declares (consumed by runtime):
budget— resource limit per module; the runtime enforces itheartbeat— liveness interval; the runtime monitors ithealth_check()— the runtime calls it; the module implements it- Channel connections — the runtime wires them at initialization
- Hot-swap protocol — the runtime drives step boundaries and triggers migration
Rs validates that these declarations are syntactically and type-correct. The runtime decides what to do with them.
Problem
Operating system modules need: a private state, a public interface, a resource budget, health monitoring, hot-swap capability, and state migration between versions. Rust has none of these as a first-class concept. Crates provide modularity but not lifecycle management.
Solution
The module! macro declares a self-contained, hot-swappable OS module.
Syntax
module!
Generated Code
The module! macro generates:
// 1. State struct
// 2. Step state struct (with auto-reset)
// 3. Module struct wrapping both
// 4. Module trait implementation
// 5. Migration from v2
// 6. Public interface methods (impl block)
// 7. Metadata for introspection
Migration State Schema
The migrate from vN block generates MigrateFrom<XxxStateVN> impl. The previous version's state struct must be in scope at compile time. Convention: keep old state definitions in versioned modules alongside the module.
// Previous version state — kept for migration
// Current module uses v2::ConsensusState as the migration source
module!
The old binding has the type of the previous state struct (v2::ConsensusState). The macro resolves vN to {ModuleName}StateVN (e.g. ConsensusStateV2) by convention. If a different type name is needed, use the full path: migrate from my_module::OldState { ... }.
Hot-Swap Protocol
cyb os is a living system. Living systems replace their components continuously without stopping — biological modules divide, differentiate, and die while the organism keeps running. cyb os modules follow the same principle: the system never reboots, modules update in place, and state migrates forward version by version.
The hot-swap protocol is a mechanical process. The spec defines how modules swap, not who or what triggers the swap. The trigger is external to the protocol — it could be a local operator, a governance decision, an automated upgrade pipeline, or the module itself detecting that a new version is available. The protocol is the same regardless.
Step N Step N+1 Step N+2
┌──────┐ ┌──────────┐ ┌──────┐
│Module │ drain │Module v2 │ │Module │
│v1 │───────>│loading + │────>│v2 │
│active│ │migration │ │active│
└──────┘ └──────────┘ └──────┘
state transfer
via MigrateFrom
- New module version is loaded (trigger is external to the protocol)
- Current step completes normally
- At step boundary: old module's state is serialized via
CanonicalSerialize - Serialized bytes are deserialized into the previous version's state struct (
XxxStateVN) MigrateFrom::migrate()transforms the old state struct to the new version- New module is initialized with migrated state
- New module starts processing next step
- Old module binary is unloaded
Total downtime: zero. Migration happens in the gap between steps. The system never stops.
Error Types
The module! macro generates a module-specific error enum from the error variants used in the module's methods. Error variants are referenced as Error::VariantName in the module body. The macro collects all referenced variants and generates:
Inside the module body, Error::GraphFull resolves to ConsensusError::GraphFull. The Result type inside module methods is Result<T, ConsensusError>.
Module-to-Module Communication
Modules communicate through two mechanisms:
- Bounded channels — the
module!macro can declare typed input/output channels:
module!
Channels are wait-free (try_send/try_recv). The module runtime connects channels between modules at initialization.
- Read-only state references — a module can declare a dependency on another module's public interface. The runtime provides a read-only reference at initialization. The dependent module calls methods on this reference but cannot mutate the other module's state.
Implementation: proc-macro, ~2000 lines.