docs: add simulation controller SDK spec, replace spatial topology
Some checks failed
CI / Lint (push) Has been cancelled
CI / Typecheck (push) Has been cancelled
CI / Broker tests (Postgres) (push) Has been cancelled
CI / Docker build (linux/amd64) (push) Has been cancelled

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 <noreply@anthropic.com>
This commit is contained in:
Alejandro Gutiérrez
2026-04-08 00:38:43 +01:00
parent 6ab3fbbea3
commit a33c925216
2 changed files with 431 additions and 3 deletions

419
docs/simulation-sdk-spec.md Normal file
View File

@@ -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 <peer>",
"show_product <sku> <peer>",
"check_inventory <sku>",
"move_to <zone>",
"radio <message>"
]
}
```
**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.*

View File

@@ -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). **Effort:** 2-3 days (E2B), 1-2 weeks (self-hosted).
### Spatial topology (proximity-based visibility) ### Simulation controller SDK (`@claudemesh/sim`)
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). 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 ### 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. `search_peers(query, filters?)` — multi-field matching across names, groups, roles, summaries, profile capabilities, skills. Ranked results. For meshes with 50+ peers.