From a33c925216d2b5fc895a9aa1d8542866a53e8259 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Guti=C3=A9rrez?= <35082514+alezmad@users.noreply.github.com> Date: Wed, 8 Apr 2026 00:38:43 +0100 Subject: [PATCH] docs: add simulation controller SDK spec, replace spatial topology The mesh is the communication fabric, not the simulation engine. SimController pattern: external controller drives tick loop, computes visibility, sends observations to peers, collects actions. Co-Authored-By: Claude Sonnet 4.6 --- docs/simulation-sdk-spec.md | 419 ++++++++++++++++++++++++++++++++++++ docs/vision-20260407.md | 15 +- 2 files changed, 431 insertions(+), 3 deletions(-) create mode 100644 docs/simulation-sdk-spec.md diff --git a/docs/simulation-sdk-spec.md b/docs/simulation-sdk-spec.md new file mode 100644 index 0000000..1de8aeb --- /dev/null +++ b/docs/simulation-sdk-spec.md @@ -0,0 +1,419 @@ +# @claudemesh/sim — Simulation Controller SDK + +**Date:** 2026-04-08 +**Status:** Design spec — not yet implemented + +--- + +## Architecture + +The mesh is the communication fabric, not the simulation engine. Simulation logic lives in **controllers** — external processes that connect to the mesh as peers and drive the simulation loop. + +``` +┌─────────────────────────┐ +│ 3D Space / UI │ Renders world state, +│ (Three.js, Unity, │ provides human input +│ dashboard, Python) │ +└───────────┬─────────────┘ + │ reads state, sends commands +┌───────────┴─────────────┐ +│ Simulation Controller │ Owns world model, +│ (@claudemesh/sim) │ computes observations, +│ │ collects actions +└───────────┬─────────────┘ + │ mesh messages + state +┌───────────┴─────────────┐ +│ Mesh (broker) │ Delivers messages, +│ │ holds shared state, +│ │ drives clock ticks +└───────────┬─────────────┘ + ┌─────┼─────┐ + ┌──┴──┐ ┌┴───┐ ┌┴───┐ + │Peer │ │Peer│ │Peer│ AI agents with + │"Rep"│ │"C1"│ │"Mgr"│ personas, react + └─────┘ └────┘ └────┘ to observations +``` + +**Separation of concerns:** +- **Mesh:** messages, state, peers, clock ticks +- **Controller:** world model, physics, visibility, rules, tick loop +- **3D/UI:** rendering, human input (optional) +- **AI peers:** persona behavior, actions, communication + +--- + +## SimController class + +```typescript +import { SimController } from "@claudemesh/sim"; + +const sim = new SimController({ + // Mesh connection (uses @claudemesh/sdk under the hood) + brokerUrl: "wss://ic.claudemesh.com/ws", + meshId: "sim-store-001", + memberId: "controller-floor", + pubkey: keys.publicKey, + secretKey: keys.secretKey, + displayName: "Floor Controller", + + // Controller config + role: "orchestrator", // "orchestrator" | "domain" + domain: "floor", // state namespace prefix: "sim:floor:*" + tickSource: "clock", // "clock" (follow mesh clock) | "self" (drive own ticks) + tickInterval: 1000, // ms, only used when tickSource: "self" +}); +``` + +### Lifecycle + +```typescript +await sim.connect(); + +// Define the world +sim.defineZones({ + "store-floor": { connects: ["checkout", "office"], capacity: 20 }, + "checkout": { connects: ["store-floor", "warehouse"], capacity: 5 }, + "warehouse": { connects: ["checkout"], capacity: 10 }, + "office": { connects: ["store-floor"], capacity: 4 }, +}); + +// Place peers in zones +sim.placePeer("Sales-Rep-1", "store-floor"); +sim.placePeer("Customer-1", "store-floor"); +sim.placePeer("Manager", "office"); + +// Define obstructions (optional — for geometric mode) +sim.addObstruction("wall-1", { from: {x:0, y:5}, to: {x:10, y:5} }); + +// Define visibility rules +sim.setVisibilityMode("zone"); // "zone" | "radius" | "custom" +// zone: peers in same zone + connected zones can see each other +// radius: distance check with optional obstructions (line-of-sight) +// custom: provide your own function + +// Custom visibility (most flexible) +sim.setVisibilityFn((peerA, peerB, world) => { + // Your logic: zones, distance, line-of-sight, roles, anything + return peerA.zone === peerB.zone; +}); + +// Start the simulation +sim.start(); +``` + +### Tick loop + +The controller runs a loop — either driven by mesh clock ticks or self-driven: + +```typescript +sim.onTick(async (tick) => { + // 1. Read world state + const world = sim.getWorldState(); + + // 2. Compute visibility for each peer + const visibility = sim.computeVisibility(); + // Returns: { "Sales-Rep-1": ["Customer-1"], "Customer-1": ["Sales-Rep-1"], "Manager": [] } + + // 3. Build observations per peer + for (const [peerName, visiblePeers] of Object.entries(visibility)) { + const peer = world.peers[peerName]; + const observation = { + tick: tick.number, + simTime: tick.simTime, + you: { zone: peer.zone, role: peer.role }, + visible_peers: visiblePeers.map(name => ({ + name, + zone: world.peers[name].zone, + role: world.peers[name].role, + status: world.peers[name].lastAction, + })), + environment: sim.getZoneState(peer.zone), + events: sim.getEventsFor(peerName), + }; + + // 4. Send observation to the peer + await sim.sendObservation(peerName, observation); + } + + // 5. Collect actions from previous tick + const actions = sim.collectActions(); + // Returns: [{ peer: "Sales-Rep-1", action: "greet", target: "Customer-1" }, ...] + + // 6. Apply actions to world state + for (const action of actions) { + sim.applyAction(action); + } + + // 7. Publish world state (for dashboard/3D renderer) + await sim.publishWorldState(); +}); +``` + +### Observations and actions protocol + +**Observation (controller → peer):** Sent as a mesh message with a structured format. The peer's MCP server receives it as a channel notification. + +```json +{ + "type": "sim_observation", + "tick": 42, + "simTime": "2026-04-08T14:30:00Z", + "you": { + "zone": "store-floor", + "position": { "x": 10, "y": 5 }, + "role": "sales-rep", + "inventory": ["catalog", "tablet"] + }, + "visible_peers": [ + { "name": "Customer-1", "zone": "store-floor", "status": "browsing" } + ], + "environment": { + "zone": "store-floor", + "customers_present": 3, + "noise_level": "moderate", + "time_of_day": "afternoon" + }, + "events": [ + "Customer-1 entered your zone", + "New shipment arrived in warehouse" + ], + "available_actions": [ + "greet ", + "show_product ", + "check_inventory ", + "move_to ", + "radio " + ] +} +``` + +**Action (peer → controller):** Peer sends a structured message back to the controller. + +```json +{ + "type": "sim_action", + "tick": 42, + "actions": [ + { "action": "greet", "target": "Customer-1" }, + { "action": "show_product", "sku": "ABC-123", "target": "Customer-1" } + ] +} +``` + +The controller validates actions (can this peer do this? are they in the right zone?) and applies valid ones to the world state. + +--- + +## Multi-controller pattern + +### Federated controllers + +Each controller owns a domain — its slice of the world. They coordinate through mesh state. + +```typescript +// Floor controller — owns positions, zones, visibility +const floor = new SimController({ + role: "domain", + domain: "floor", + tickSource: "clock", +}); + +// Inventory controller — owns stock, pricing +const inventory = new SimController({ + role: "domain", + domain: "inventory", + tickSource: "clock", +}); + +// Orchestrator — merges domains into unified observations +const orchestrator = new SimController({ + role: "orchestrator", + domains: ["floor", "inventory", "queue"], + tickSource: "clock", +}); + +orchestrator.onTick(async (tick) => { + // Read all domain states + const floor = await orchestrator.getDomainState("floor"); + const inventory = await orchestrator.getDomainState("inventory"); + + // Merge into unified observation per peer + for (const peer of orchestrator.getPeers()) { + const obs = mergeObservation(peer, floor, inventory); + await orchestrator.sendObservation(peer.name, obs); + } +}); +``` + +### State namespacing + +Each domain writes to its own namespace: + +``` +sim:floor:positions → {"Alice": {"x":10,"y":5}, "Bob": {"x":3,"y":8}} +sim:floor:zones → {"store-floor": {"peers": ["Alice","Bob"]}} +sim:inventory:stock → {"ABC-123": 42, "DEF-456": 0} +sim:queue:lines → [{"id": 1, "length": 3, "cashier": "Carol"}] +sim:orchestrator:tick → 42 +``` + +Any peer, controller, or dashboard reads state to render or react. + +--- + +## World state management + +```typescript +// Define entity types +sim.defineEntityType("product", { + sku: "string", + name: "string", + price: "number", + stock: "number", + zone: "string", +}); + +// Create entities +sim.createEntity("product", { sku: "ABC-123", name: "Widget", price: 29.99, stock: 42, zone: "store-floor" }); + +// Query entities +const products = sim.queryEntities("product", { zone: "store-floor" }); + +// Peer actions modify entities +sim.defineAction("purchase", { + validate: (peer, args, world) => { + const product = world.getEntity("product", args.sku); + return product && product.stock > 0 && peer.zone === product.zone; + }, + apply: (peer, args, world) => { + const product = world.getEntity("product", args.sku); + product.stock -= 1; + peer.inventory.push(args.sku); + return { event: `${peer.name} purchased ${product.name}` }; + }, +}); +``` + +--- + +## Dashboard integration + +The 3D/UI reads world state from the mesh and renders it. No special protocol — just `get_state`. + +```javascript +// Three.js dashboard +const positions = JSON.parse(await mesh.getState("sim:floor:positions")); +for (const [name, pos] of Object.entries(positions)) { + updatePeerMarker(name, pos.x, pos.y); +} + +// Or subscribe to a stream +mesh.subscribe("sim:world"); +mesh.on("stream_data", (data) => { + renderFrame(data); +}); +``` + +Human input flows back through the mesh: +```javascript +// User clicks to move a peer +canvas.on("click", (pos) => { + mesh.send("Floor Controller", JSON.stringify({ + type: "sim_command", + command: "move_peer", + peer: selectedPeer, + to: { x: pos.x, y: pos.y }, + })); +}); +``` + +--- + +## Package structure + +``` +packages/sim/ + package.json @claudemesh/sim + src/ + index.ts Main exports + controller.ts SimController class + world.ts WorldState manager + visibility.ts Zone-based + radius + custom visibility + actions.ts Action validation + application + entities.ts Entity type system + observation.ts Observation builder + protocol.ts Message format constants + types.ts TypeScript types + README.md +``` + +**Dependencies:** +- `@claudemesh/sdk` — mesh connectivity +- No physics engine — keep it lightweight. Users bring their own for geometric mode. + +--- + +## Example: retail store simulation + +```typescript +import { SimController } from "@claudemesh/sim"; + +const sim = new SimController({ /* mesh config */ }); +await sim.connect(); + +// World setup +sim.defineZones({ + "entrance": { connects: ["store-floor"] }, + "store-floor": { connects: ["entrance", "checkout", "fitting-room"] }, + "fitting-room": { connects: ["store-floor"] }, + "checkout": { connects: ["store-floor", "back-office"] }, + "back-office": { connects: ["checkout"] }, +}); + +// Peer personas (mesh skills) +await sim.shareSkill("sales-rep", { + instructions: "You are a retail sales rep. Greet customers who enter your zone. Show products when asked. Guide to fitting room or checkout. Be helpful but not pushy.", +}); +await sim.shareSkill("customer", { + instructions: "You are a customer shopping for clothes. Browse, ask questions, try things on. You have a budget of $200. Make realistic decisions.", +}); + +// Place peers +sim.placePeer("Rep-1", "store-floor", { role: "sales-rep", skill: "sales-rep" }); +sim.placePeer("Rep-2", "store-floor", { role: "sales-rep", skill: "sales-rep" }); +sim.placePeer("Cust-1", "entrance", { role: "customer", skill: "customer" }); +sim.placePeer("Cust-2", "entrance", { role: "customer", skill: "customer" }); +sim.placePeer("Manager", "back-office", { role: "manager" }); + +// Set clock to 10x +await sim.setClock(10); + +// Run +sim.start(); + +// After N ticks, generate report +sim.onComplete(async (report) => { + console.log(`Simulation complete: ${report.ticks} ticks`); + console.log(`Actions: ${report.totalActions}`); + console.log(`Sales: ${report.entities.product.filter(p => p.stock === 0).length} sold out`); + console.log(`Customer satisfaction: ${report.metrics.satisfaction}`); +}); +``` + +--- + +## Effort estimate + +| Component | Effort | +|-----------|--------| +| SimController core (tick loop, observations, actions) | 2 days | +| World state + entity system | 1 day | +| Visibility (zone + radius + custom) | 1 day | +| Protocol (observation/action message format) | Half day | +| Dashboard integration examples | Half day | +| Retail store example | Half day | +| **Total** | **5-6 days** | + +--- + +*This replaces the "spatial topology" vision item. Instead of baking simulation features into the broker, we build a simulation framework on top of the mesh.* diff --git a/docs/vision-20260407.md b/docs/vision-20260407.md index bd4ef08..2de6e6c 100644 --- a/docs/vision-20260407.md +++ b/docs/vision-20260407.md @@ -69,10 +69,19 @@ Per-mesh compute sandboxes. Peers request: `execute_code(lang: "python", code: " **Effort:** 2-3 days (E2B), 1-2 weeks (self-hosted). -### Spatial topology (proximity-based visibility) -Extend visibility with `(x, y)` coordinates and visibility radius. Peers only see others within range. Combined with sim clock, enables spatial simulations (customer walks into store zone, sees sales reps). +### Simulation controller SDK (`@claudemesh/sim`) +Replaces the "spatial topology" item. Instead of baking simulation features into the broker, build a framework on top of the mesh. -**Effort:** 1 day. +- **SimController class** — connects as a peer, drives tick loop, computes visibility, sends observations, collects actions +- **World state** — entity system, zone graph, state namespacing (`sim:domain:key`) +- **Visibility modes** — zone-based, radius, custom function (bring your own physics) +- **Multi-controller** — federated domains (floor, inventory, queue) with orchestrator merge +- **Dashboard integration** — reads world state from mesh, human input flows back via messages +- **The mesh stays simple** — messaging + state. All simulation logic lives in the controller. + +Full spec: [`simulation-sdk-spec.md`](./simulation-sdk-spec.md) + +**Effort:** 5-6 days. ### Semantic peer search `search_peers(query, filters?)` — multi-field matching across names, groups, roles, summaries, profile capabilities, skills. Ranked results. For meshes with 50+ peers.