WebAssembly runtime for constraint-aware AI — conservation budgets, spectral ranking, capability discovery in the browser.
si-runtime-wasm brings the SuperInstance runtime primitives to the browser via a high-performance WebAssembly module compiled from Rust. It provides:
- 💰 Conservation Budgets — Track resource allocation with invariant-preserving transfers
- 📊 Spectral Ranking — Eigenvector centrality via power iteration for importance ranking
- 🔍 Capability Registry — Dependency-aware service discovery and resolution
- 🧬 Cellular Automata — Grid simulations with pluggable rules (threshold, diffusion, SmoothLife)
- 🤖 Homeostatic Agents — PID-controlled agents with gauge tracking
All running at native speed in the browser, with zero external dependencies.
<script type="module">
import init, { Budget, spectralRank, Capability, Registry, Grid, Agent } from './pkg/si_runtime_wasm.js';
async function run() {
await init();
// Conservation budget
const budget = new Budget(100.0);
console.log('Total:', budget.total());
console.log('Remaining:', budget.remaining());
budget.allocate(30.0);
console.log('After allocating 30:', budget.allocated());
console.log('Audit passes:', budget.audit());
budget.spend(10.0);
console.log('After spending 10:', budget.remaining());
// Spectral ranking — pass flat adjacency matrix + dimension
const adj = [
[0, 1, 1, 1],
[1, 0, 0, 0],
[1, 0, 0, 0],
[1, 0, 0, 0],
];
const flat = adj.flat();
const n = adj.length;
const ranking = spectralRank(flat, n);
console.log('Spectral ranking:', ranking); // [0, 1, 2, 3] — hub is #1
// Capability registry
const reg = new Registry();
const cap = new Capability('my-service', '1.0.0');
cap.provides('compute');
cap.requires('storage');
reg.register(cap);
const providers = reg.resolve('compute');
console.log('Compute providers:', providers.length);
// Cellular automaton
const grid = Grid.new(20, 'threshold');
console.log('Grid state:', grid.getState());
grid.run(10);
console.log('After 10 steps:', grid.getState());
}
run();
</script>npm install si-runtime-wasmimport init, { Budget } from 'si-runtime-wasm';
await init();
const b = new Budget(100.0);<script type="module">
import init, { Budget } from 'https://unpkg.com/si-runtime-wasm/pkg/si_runtime_wasm.js';
await init();
const b = new Budget(100.0);
</script>curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | shgit clone https://github.com/SuperInstance/si-runtime-wasm.git
cd si-runtime-wasm
# Build for web targets
wasm-pack build --target web
# Build for bundler (webpack, vite, etc.)
wasm-pack build --target bundler
# Build for Node.js
wasm-pack build --target nodejsThe output goes to pkg/ — a ready-to-publish npm package.
# Serve the demo page (any static file server)
python3 -m http.server 8080
# Open http://localhost:8080/index.htmlOr use the pre-built demo:
npx serve .Conservation budget with gamma/eta tracking. The total is invariant: total = gamma + eta + allocated.
const b = new Budget(300.0);
// Inspect
b.total(); // 300
b.gamma(); // ~100
b.eta(); // ~100
b.allocated(); // ~100
b.remaining(); // ~200 (gamma + eta)
// Allocate from free pool to allocated
b.allocate(50.0); // Returns void, throws on over-allocation
// Spend from allocated (returns to gamma/eta)
b.spend(20.0);
// Transfer between pools
b.transferGammaToEta(5.0);
b.transferEtaToGamma(3.0);
// Verify conservation invariant
b.audit(); // true — total = gamma + eta + allocated
// Serialize
const json = b.toJson();Key invariant: b.audit() always returns true. The total never changes; only the distribution shifts.
Compute eigenvector centrality ranking via power iteration.
// Adjacency matrix (symmetric for undirected graph)
const adj = [
[0, 1, 1, 1, 1], // Node 0 is the hub
[1, 0, 0, 0, 0],
[1, 0, 0, 0, 0],
[1, 0, 0, 0, 0],
[1, 0, 0, 0, 0],
];
const flat = adj.flat();
const n = adj.length;
const ranking = spectralRank(flat, n);
// ranking = [0, 1, 2, 3, 4] — hub ranks firstReturns indices sorted by eigenvector centrality (highest first).
Get raw eigenvector centrality scores.
const flat = adj.flat();
const n = adj.length;
const scores = eigenvectorCentrality(flat, n);
// scores[0] ≈ 0.707 (hub)
// scores[1..4] ≈ 0.354 (leaves)A named, versioned capability with provides/requires lists.
const cap = new Capability('renderer', '1.0.0');
cap.provides('ui');
cap.provides('canvas');
cap.requires('events');
cap.getName(); // 'renderer'
cap.getVersion(); // '1.0.0'
cap.satisfies('ui'); // true
cap.satisfies('audio'); // falseService registry with dependency resolution.
const reg = new Registry();
const renderer = new Capability('canvas-renderer', '1.0.0');
renderer.provides('ui');
renderer.provides('canvas');
reg.register(renderer);
const audio = new Capability('web-audio', '2.0.0');
audio.provides('audio');
audio.requires('ui');
reg.register(audio);
// Resolve by interface
const uiProviders = reg.resolve('ui');
// → [canvas-renderer]
// Resolve dependencies for a capability
const deps = reg.resolveDeps(audio);
// → [canvas-renderer] (satisfies 'ui' requirement)
reg.count(); // 2Cellular automaton with pluggable rules.
// Create grid with rule: "threshold" | "diffusion" | "smoothlife"
const grid = Grid.new(50, 'threshold');
// Or blank
const blank = Grid.newBlank(50);
// Step simulation
grid.step();
// Run multiple steps
grid.run(100);
// Read state
const state = grid.getState(); // Float64Array of size*size
grid.getSize(); // 50
grid.getRule(); // "threshold"
// Set individual cells
grid.setCell(10, 20, 1.0);
grid.getCell(10, 20); // 1.0
// Change rule on the fly
grid.setRule('diffusion');Built-in rules:
| Rule | Description |
|---|---|
threshold |
Conway-like: alive if neighbor average in [0.2, 0.8], born if in [0.35, 0.65] |
diffusion |
Smooth blend: each cell moves 50% toward neighbor average |
smoothlife |
Continuous sigmoid-based birth/survival |
Homeostatic agent with PID controller and gauge tracking.
const agent = new Agent('controller-1');
// Configure PID
agent.configurePid(1.0, 0.1, 0.05);
// Set target
agent.setSetpoint(0.5);
// Add a gauge
const gauge = new Gauge('temperature', 0.8);
agent.addGauge(gauge);
// Run homeostatic ticks
agent.tick(); // Returns correction value
agent.runTicks(50); // Run 50 ticks
agent.primaryGauge(); // Current value after PID correction
agent.getTick(); // Tick counter
agent.getSetpoint(); // Current setpointsi-runtime-wasm is the WASM backend for si-runtime-js. The JS runtime wraps the WASM module with a higher-level API:
// si-runtime-js internally uses si-runtime-wasm
import { Runtime } from 'si-runtime-js';
const runtime = new Runtime({
wasmModule: await import('si-runtime-wasm'),
});
// The JS runtime delegates hot-path computations to WASM:
const budget = runtime.createBudget(1000);
const ranking = runtime.spectralRank(myGraph);If you're building a custom integration:
import init, * as si from 'si-runtime-wasm';
let loaded = false;
export async function load() {
if (!loaded) {
await init();
loaded = true;
}
return si;
}┌─────────────────────────────────────────────────┐
│ Browser / JS │
│ │
│ ┌──────────┐ ┌──────────┐ ┌───────────────┐ │
│ │ Budget │ │ Registry │ │ Grid (CA) │ │
│ └────┬─────┘ └────┬─────┘ └───────┬───────┘ │
│ │ │ │ │
│ ═════╪═════════════╪════════════════╪═════════ │
│ │ wasm-bindgen bridge │ │
│ ═════╪═════════════╪════════════════╪═════════ │
│ │ │ │ │
│ ┌────▼─────┐ ┌────▼─────┐ ┌──────▼───────┐ │
│ │ Budget │ │ Registry │ │ Grid │ │
│ │ (Rust) │ │ (Rust) │ │ (Rust) │ │
│ └──────────┘ └──────────┘ └──────────────┘ │
│ │
│ WASM (wasm32-unknown-unknown) │
└─────────────────────────────────────────────────┘
src/
├── lib.rs — Entry point, exports, init
├── conservation.rs — Budget with gamma/eta tracking
├── spectral.rs — Power iteration eigenvector centrality
├── capability.rs — Capability + Registry with resolution
├── cell.rs — Cellular automaton with pluggable rules
└── agent.rs — Agent with gauges and PID homeostasis
The budget system maintains a strict conservation law:
total = gamma + eta + allocated
- total: Fixed at creation. Never changes.
- gamma: First pool, weighted toward system overhead
- eta: Second pool, weighted toward reactive reserves
- allocated: Budget committed to active operations
All operations are transfers — nothing is created or destroyed:
allocate(amount)— Moves from gamma/eta (proportionally) to allocatedspend(amount)— Returns from allocated to gamma (60%) and eta (40%)transferGammaToEta(amount)— Moves between free poolsaudit()— Verifies|total - gamma - eta - allocated| < 1e-10
This models physical conservation laws — the budget is a closed system.
Eigenvector centrality computed via power iteration:
- Start with uniform vector
v₀ = [1/n, 1/n, ..., 1/n] - Iterate:
v_{k+1} = A · v_k / ||A · v_k|| - Converge when
||v_{k+1} - v_k|| < 1e-10 - Max 200 iterations
The ranking sorts nodes by their eigenvector centrality score — nodes connected to other high-scoring nodes rank higher. This is the same algorithm used by PageRank (with modifications).
cargo testwasm-pack test --headless --firefox
# or
wasm-pack test --headless --chromeThe test suite includes 14+ tests covering:
- Budget creation, allocation, spending, transfer, audit
- Spectral ranking (empty, single, star graph)
- Capability satisfies and registry resolution
- Grid creation, stepping, cell get/set
- Agent PID homeostasis convergence
Compiled with -O s (size optimization) and LTO:
| Metric | Value |
|---|---|
| WASM binary size | ~15-20 KB gzipped |
| Budget operations | < 1μs per call |
| Spectral ranking (50 nodes) | < 100μs |
| Grid step (50×50) | < 500μs |
| First load + init | < 50ms |
All benchmarks run in Chrome on a modern laptop. Your mileage may vary.
Tested on:
- Chrome 90+
- Firefox 90+
- Safari 15+
- Edge 90+
Requires WebAssembly.instantiateStreaming support.
- Fork the repository
- Create a feature branch (
git checkout -b feature/my-feature) - Make your changes
- Add tests
- Run
cargo testandwasm-pack test --headless --chrome - Commit and push
- Open a Pull Request
Add your rule function in src/cell.rs:
pub fn my_rule(cell: f64, neighbors: &[f64]) -> f64 {
// Your logic here
let sum: f64 = neighbors.iter().sum();
// ...
}Then add it to the apply_rule match in Grid:
fn apply_rule(&self, cell: f64, neighbors: &[f64]) -> f64 {
match self.rule.as_str() {
"diffusion" => rules::diffusion(cell, neighbors),
"smoothlife" => rules::smoothlife(cell, neighbors),
"my_rule" => rules::my_rule(cell, neighbors),
_ => rules::threshold(cell, neighbors),
}
}- Create
src/my_module.rs - Add
pub mod my_module;tosrc/lib.rs - Add
pub use my_module::MyType;for JS export - Add
#[wasm_bindgen]annotations - Update this README
MIT © SuperInstance Contributors
- SuperInstance Organization
- si-runtime-js — JavaScript wrapper
- wasm-bindgen — Rust ↔ JS bridge
- wasm-pack — Build tooling
- Initial release
- Conservation budget with gamma/eta tracking
- Spectral ranking via power iteration
- Capability registry with dependency resolution
- Cellular automaton with threshold, diffusion, and SmoothLife rules
- Homeostatic agent with PID controller
- Demo page with interactive visualizations
- Full wasm-bindgen test suite