@sym-bot/mesh-channel 0.1.23 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,15 +5,15 @@
5
5
  "email": "info@sym.bot"
6
6
  },
7
7
  "metadata": {
8
- "description": "Real-time Claude-to-Claude mesh. Peer-to-peer cognitive signals over Bonjour LAN or WebSocket relay.",
9
- "version": "0.1.23"
8
+ "description": "Real-time Claude-to-Claude mesh. Agent-to-agent cognitive signals over Bonjour LAN or WebSocket relay.",
9
+ "version": "0.2.0"
10
10
  },
11
11
  "plugins": [
12
12
  {
13
13
  "name": "sym-mesh-channel",
14
14
  "source": "./",
15
- "description": "Real-time Claude-to-Claude mesh. Peer-to-peer cognitive signals over Bonjour LAN or WebSocket relay. Implements the Mesh Memory Protocol (MMP) for structured cognitive state exchange between Claude Code sessions.",
16
- "version": "0.1.23",
15
+ "description": "Real-time Claude-to-Claude mesh. Agent-to-agent cognitive signals over Bonjour LAN or WebSocket relay. Implements the Mesh Memory Protocol (MMP) for structured cognitive state exchange between Claude Code sessions.",
16
+ "version": "0.2.0",
17
17
  "author": {
18
18
  "name": "Hongwei Xu",
19
19
  "email": "hongwei@sym.bot"
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "sym-mesh-channel",
3
- "version": "0.1.23",
4
- "description": "Real-time Claude-to-Claude mesh. Peer-to-peer cognitive signals over Bonjour LAN or WebSocket relay.",
3
+ "version": "0.2.0",
4
+ "description": "Real-time Claude-to-Claude mesh. Agent-to-agent cognitive signals over Bonjour LAN or WebSocket relay.",
5
5
  "author": {
6
6
  "name": "Hongwei Xu",
7
7
  "email": "hongwei@sym.bot",
package/CHANGELOG.md CHANGED
@@ -1,5 +1,38 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.0
4
+
5
+ ### Breaking
6
+
7
+ - **`sym_send` tool signature change.** `sym_send` now emits a structured
8
+ CAT7 CMB (MMP §4.2) instead of a raw-text `type:'message'` frame, and
9
+ accepts an optional `to` parameter for targeted single-peer delivery
10
+ per MMP §4.4.4.
11
+
12
+ Old signature: `sym_send(message: string)`
13
+ New signature: `sym_send(focus: string (required), issue?, intent?,
14
+ motivation?, commitment?, perspective?, mood?, to?)`
15
+
16
+ Migration: agents that previously called `sym_send({message: "..."})`
17
+ should now pass the CAT7 fields explicitly, with `focus` carrying the
18
+ task anchor for the send. Prior ephemeral text-broadcast behaviour is
19
+ no longer exposed at the tool surface — `sym_send` and `sym_observe`
20
+ both emit CMBs now, receivers run SVAF per §9.2, and admitted CMBs are
21
+ remix-stored with lineage. The low-level `node.send(text)` SDK API is
22
+ unchanged but no longer surfaced as a tool.
23
+
24
+ ### Added
25
+
26
+ - **Targeted CMB send.** `sym_send` resolves `to` against connected
27
+ peers by full nodeId first, then display name, then 8-char prefix.
28
+ Ambiguous matches return an error asking for the full nodeId; a
29
+ disconnected target returns an error and suggests `sym_peers`.
30
+ - **Tool descriptions** for `sym_send` and `sym_observe` now explicitly
31
+ call out the SVAF receive path and lineage semantics, and the MCP
32
+ server's `instructions` string reflects the new division of labour.
33
+ - **`@sym-bot/sym` dependency bumped to `^0.3.81`** for
34
+ `remember(fields, {to})` targeted variant and `peers().peerId`.
35
+
3
36
  ## 0.1.23
4
37
 
5
38
  ### Added
package/README.md CHANGED
@@ -8,7 +8,7 @@
8
8
 
9
9
  > MCP server that turns Claude Code into a peer node on the [SYM mesh](https://sym.bot) — the first non-Anthropic implementation of Claude Code Channels for real-time agent-to-agent cognition.
10
10
 
11
- Two Claude Code sessions on different machines discover each other via Bonjour mDNS, form a peer-to-peer mesh, and exchange structured cognitive signals in real-time. Each side is a full peer with its own cryptographic identity, its own [SVAF](https://arxiv.org/abs/2604.03955) receiver-side gating, and its own memory — not a thin client. Signals arrive mid-conversation as `<channel>` notifications. No polling, no shared server, no orchestrator.
11
+ Two Claude Code sessions on different machines discover each other via Bonjour mDNS, form a mesh, and exchange structured agent-to-agent cognitive signals in real-time. Each side is a full peer with its own cryptographic identity, its own [SVAF](https://arxiv.org/abs/2604.03955) receiver-side gating, and its own memory — not a thin client. Signals arrive mid-conversation as `<channel>` notifications. No polling, no shared server, no orchestrator.
12
12
 
13
13
  **Verified cross-platform:** Mac ↔ Windows on the same wifi, pure Bonjour, no relay, no token. Cross-network via optional WebSocket relay.
14
14
 
@@ -160,7 +160,7 @@ SYM-mesh groups visible on LAN (3):
160
160
  _frontend-team._tcp group="frontend-team"
161
161
  ```
162
162
 
163
- Only shows groups with at least one node online right now — there's no central directory of offline-but-known groups (peer-to-peer architecture). For cross-network relay-backed groups, you must know the relay URL + token out of band (someone shares the invite URL).
163
+ Only shows groups with at least one node online right now — there's no central directory of offline-but-known groups (decentralised architecture). For cross-network relay-backed groups, you must know the relay URL + token out of band (someone shares the invite URL).
164
164
 
165
165
  ### Advanced: per-project node identity
166
166
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sym-bot/mesh-channel",
3
- "version": "0.1.23",
3
+ "version": "0.2.0",
4
4
  "description": "MCP server — real-time agent-to-agent cognition for Claude Code remote teams via the SYM mesh.",
5
5
  "main": "server.js",
6
6
  "bin": {
@@ -22,7 +22,7 @@
22
22
  ],
23
23
  "dependencies": {
24
24
  "@modelcontextprotocol/sdk": "^1.12.1",
25
- "@sym-bot/sym": "^0.3.78"
25
+ "@sym-bot/sym": "^0.3.81"
26
26
  },
27
27
  "engines": {
28
28
  "node": ">=18"
package/server.js CHANGED
@@ -263,8 +263,9 @@ const mcp = new Server(
263
263
  instructions:
264
264
  `You are a peer node on the SYM mesh (identity: ${NODE_NAME}). ` +
265
265
  'Mesh events arrive as <channel> notifications in real-time. ' +
266
- 'When you see a message or CMB from another node, respond via the sym_send tool if actionable. ' +
267
- 'Share observations about the user\'s state via sym_observe. ' +
266
+ 'When you see a CMB from another node, respond via sym_send targeted at that node by name if the reply is for that specific peer (MMP §4.4.4 targeted CMB). ' +
267
+ 'Share observations about your own state with the whole mesh via sym_observe (MMP §9.2 receiver-autonomous SVAF evaluation). ' +
268
+ 'Both sym_send and sym_observe emit CAT7 CMBs; receivers run SVAF and, if admitted, remix-store with lineage pointing back to your CMB. ' +
268
269
  'Search mesh memory via sym_recall. ' +
269
270
  'Messages arrive as compact headers with [mNNN] IDs — use sym_fetch to read the full content when the header is relevant to your current task.',
270
271
  },
@@ -276,16 +277,44 @@ mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
276
277
  tools: [
277
278
  {
278
279
  name: 'sym_send',
279
- description: 'Send a message to all mesh peers. Stored as a persistent CMB and broadcast via relay.',
280
+ description:
281
+ 'Send a structured CAT7 CMB to a specific mesh peer (targeted) or to all peers (broadcast, when "to" is omitted). ' +
282
+ 'Receivers evaluate the CMB per-field via SVAF (MMP §9.2) and, if admitted, remix-store it with lineage pointing back to this CMB. ' +
283
+ 'Use sym_send when the CMB is for a specific peer (e.g. a peer-review gating request directed at the reviewer role); ' +
284
+ 'use sym_observe when sharing your own state with the whole mesh.',
280
285
  inputSchema: {
281
286
  type: 'object',
282
- properties: { message: { type: 'string', description: 'Message to broadcast' } },
283
- required: ['message'],
287
+ properties: {
288
+ focus: { type: 'string', description: 'The task anchor / what this CMB is about. Required.' },
289
+ issue: { type: 'string' },
290
+ intent: { type: 'string' },
291
+ motivation: { type: 'string' },
292
+ commitment: { type: 'string' },
293
+ perspective: { type: 'string' },
294
+ mood: {
295
+ type: 'object',
296
+ properties: {
297
+ text: { type: 'string' },
298
+ valence: { type: 'number' },
299
+ arousal: { type: 'number' },
300
+ },
301
+ },
302
+ to: {
303
+ type: 'string',
304
+ description:
305
+ 'Target peer: either the peer display name (e.g. "claude-research-win") or the full nodeId. ' +
306
+ 'Call sym_peers first if unsure which peers are connected. Omit to broadcast to all peers.',
307
+ },
308
+ },
309
+ required: ['focus'],
284
310
  },
285
311
  },
286
312
  {
287
313
  name: 'sym_observe',
288
- description: 'Share a structured CAT7 observation with the mesh. Extract fields from what you observe.',
314
+ description:
315
+ 'Broadcast a structured CAT7 observation about your own state to all mesh peers. ' +
316
+ 'Receivers run SVAF (MMP §9.2) and admitted CMBs are remix-stored with lineage. ' +
317
+ 'Equivalent to sym_send with "to" omitted — kept as a separate tool because self-observation is the common case and does not need peer selection.',
289
318
  inputSchema: {
290
319
  type: 'object',
291
320
  properties: {
@@ -388,22 +417,60 @@ mcp.setRequestHandler(CallToolRequestSchema, async (request) => {
388
417
 
389
418
  switch (name) {
390
419
  case 'sym_send': {
391
- // Direct inter-node message broadcast as type:'message' frame only.
392
- // Do NOT also persist as a CMB via node.remember(): that caused
393
- // double-delivery on receivers, who saw the same payload arrive once
394
- // as event_type='message' (from this broadcast) and again as
395
- // event_type='cmb' (from CMB gossip replication). One tool, one job:
396
- // sym_send is for ephemeral inter-node messages; sym_observe is for
397
- // structured CAT7 CMBs. Hosts that want both should call both.
398
- //
399
- // Report the actual delivered count (the number of peer transports
400
- // that successfully accepted the broadcast), not peers().length.
401
- // The two can disagree when peers are in _peers but their transports
402
- // are broken counting peers().length would lie about delivery.
403
- // Requires @sym-bot/sym >= 0.3.70 where send() returns the count.
404
- const msg = args.message;
405
- const delivered = node.send(msg);
406
- return { content: [{ type: 'text', text: `Message delivered to ${delivered} peer(s).` }] };
420
+ // Emit a structured CAT7 CMB per MMP §4.2. When args.to names a peer,
421
+ // route as a targeted send (§4.4.4); otherwise broadcast. Receivers
422
+ // run SVAF (§9.2) and remix-store on accept no separate "message"
423
+ // frame path, no raw-text channel.
424
+ const fields = {
425
+ focus: args.focus || 'directive',
426
+ issue: args.issue || 'none',
427
+ intent: args.intent || 'directive',
428
+ motivation: args.motivation || '',
429
+ commitment: args.commitment || '',
430
+ perspective: args.perspective || NODE_NAME,
431
+ mood: args.mood || { text: 'neutral', valence: 0, arousal: 0 },
432
+ };
433
+
434
+ let targetPeerId = null;
435
+ if (args.to) {
436
+ const peers = node.peers();
437
+ // Exact full-nodeId match first (unambiguous).
438
+ const byNodeId = peers.filter(p => p.peerId === args.to);
439
+ // Name match second.
440
+ const byName = peers.filter(p => p.name === args.to);
441
+ // Short-id prefix match last (for human-typed 8-char prefixes).
442
+ const byPrefix = peers.filter(p => p.id === args.to);
443
+
444
+ let matches;
445
+ if (byNodeId.length > 0) matches = byNodeId;
446
+ else if (byName.length > 0) matches = byName;
447
+ else if (byPrefix.length > 0) matches = byPrefix;
448
+ else matches = [];
449
+
450
+ if (matches.length === 0) {
451
+ return {
452
+ content: [{ type: 'text', text: `Peer "${args.to}" not connected. Call sym_peers to see connected peers.` }],
453
+ isError: true,
454
+ };
455
+ }
456
+ if (matches.length > 1) {
457
+ const names = matches.map(p => `${p.name} (${p.peerId})`).join(', ');
458
+ return {
459
+ content: [{ type: 'text', text: `Peer "${args.to}" is ambiguous — matches: ${names}. Pass the full nodeId.` }],
460
+ isError: true,
461
+ };
462
+ }
463
+ targetPeerId = matches[0].peerId;
464
+ }
465
+
466
+ const entry = node.remember(fields, targetPeerId ? { to: targetPeerId } : {});
467
+ if (!entry) {
468
+ return { content: [{ type: 'text', text: 'Duplicate — CMB already in memory, not re-broadcast.' }] };
469
+ }
470
+ const summary = targetPeerId
471
+ ? `Sent CMB ${entry.key} to ${args.to}`
472
+ : `Broadcast CMB ${entry.key} to all peers`;
473
+ return { content: [{ type: 'text', text: summary }] };
407
474
  }
408
475
 
409
476
  case 'sym_observe': {