@sym-bot/sym 0.2.3 → 0.2.4

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.
package/README.md CHANGED
@@ -2,9 +2,9 @@
2
2
 
3
3
  **Make Claude Code aware of your whole self — not just your codebase.**
4
4
 
5
- Type "I'm exhausted" in Claude Code. [MeloTune](https://melotune.ai) starts playing spa music on your iPhone. You didn't switch apps, configure anything, or ask for music. Claude Code detected your mood, broadcast it to the mesh, and MeloTune responded autonomously.
5
+ Type "I'm exhausted" in Claude Code. [MeloTune](https://melotune.ai) starts playing spa music on your iPhone. [MeloMove](https://melomove.ai) generates a gentle recovery workout. You didn't switch apps, configure anything, or ask for it. Claude Code detected your mood, broadcast it to the mesh, and both agents responded autonomously.
6
6
 
7
- SYM connects Claude Code to your other AI-powered tools so they respond to you as a whole person. Your mood drives your music. Your context flows between agents. The mesh decides what's relevant — you just work.
7
+ SYM connects Claude Code to your other AI-powered tools so they respond to you as a whole person. Your mood drives your music and your movement. Your context flows between agents. The mesh decides what's relevant — you just work.
8
8
 
9
9
  [![npm](https://img.shields.io/npm/v/@sym-bot/sym)](https://www.npmjs.com/package/@sym-bot/sym)
10
10
  [![License](https://img.shields.io/badge/license-Apache%202.0-blue)](LICENSE)
@@ -14,19 +14,23 @@ SYM connects Claude Code to your other AI-powered tools so they respond to you a
14
14
 
15
15
  | You type in Claude Code | What happens |
16
16
  |------------------------|-------------|
17
- | "I'm exhausted" | MeloTune plays calming music on your iPhone |
18
- | "Let's go, feeling pumped" | MeloTune plays high-energy music |
19
- | "I need to focus" | MeloTune plays deep focus / lo-fi beats |
17
+ | "I'm stressed from debugging" | MeloTune plays stress relief music + MeloMove generates Stress-Busting Home HIIT |
18
+ | "feeling calm, deep work session" | MeloTune plays lo-fi beats + MeloMove suggests Smart Recovery (meditation, yoga) |
19
+ | "I'm exhausted" | MeloTune plays spa music + MeloMove suggests gentle recovery exercises |
20
+ | "Let's go, feeling pumped" | MeloTune plays high-energy music + MeloMove generates a high-intensity workout |
21
+ | "I need to focus" | MeloTune plays deep focus beats + MeloMove suggests a movement break when you surface |
22
+
23
+ MeloMove synthesizes an assessment from the mesh mood signal — deriving mood, energy level, and environment context. At nighttime (10pm-6am), it forces indoor/home exercises only. A 10-minute debounce prevents recommendation spam, and mesh mood persists across app restarts.
20
24
 
21
25
  Also works from Telegram — message [@sym_mesh_bot](https://t.me/sym_mesh_bot):
22
26
 
23
27
  | You type in Telegram | What happens |
24
28
  |---------------------|-------------|
25
29
  | "Play solo cello" | MeloTune plays solo cello on your iPhone |
26
- | "I'm stressed" | MeloTune plays stress relief music |
30
+ | "I'm stressed" | MeloTune plays stress relief music + MeloMove generates a stress-busting workout |
27
31
  | `/recall last AI news` | Get today's AI news digest from 28 curated builders |
28
32
 
29
- All of this works when MeloTune is in the background. SYM wakes it up.
33
+ All of this works when MeloTune and MeloMove are in the background. SYM wakes them up.
30
34
 
31
35
  ## Requirements
32
36
 
@@ -34,6 +38,7 @@ All of this works when MeloTune is in the background. SYM wakes it up.
34
38
  - **macOS** 13+ (for sym-daemon)
35
39
  - **Claude Code** ([install](https://claude.ai/claude-code))
36
40
  - **MeloTune** on iPhone ([App Store](https://melotune.ai)) — for music playback
41
+ - **MeloMove** on iPhone ([App Store](https://melomove.ai)) — for fitness/wellness
37
42
 
38
43
  ## Install
39
44
 
@@ -60,8 +65,12 @@ Claude Code: detects mood → sym_mood("exhausted, need rest")
60
65
  sym-daemon (always running on your Mac)
61
66
 
62
67
  ├── Relay (internet) ──→ MeloTune (iPhone)
63
- → coupling engine: relevant ✓
64
- → plays spa music
68
+ → coupling engine: relevant ✓
69
+ → plays spa music
70
+ │ │
71
+ │ └──→ MeloMove (iPhone)
72
+ │ → coupling engine: relevant ✓
73
+ │ → generates recovery workout
65
74
 
66
75
  └── Bonjour (local) ──→ MeloTune (Mac, if running)
67
76
  ```
@@ -75,11 +84,12 @@ MacBook (sym-daemon, always running)
75
84
 
76
85
  ├── Bonjour (LAN peers)
77
86
  └── Relay (internet peers)
78
- ├── MeloTune iPhone
87
+ ├── MeloTune iPhone (music)
88
+ ├── MeloMove iPhone (fitness)
79
89
  └── Telegram bot
80
90
  ```
81
91
 
82
- When MeloTune is backgrounded on your iPhone, the daemon wakes it via push notification, delivers the mood, and MeloTune plays music — all without you touching your phone.
92
+ When MeloTune or MeloMove is backgrounded on your iPhone, the daemon wakes it via push notification, delivers the mood, and the agent responds — music plays, a workout generates — all without you touching your phone.
83
93
 
84
94
  ## Claude Code MCP Tools
85
95
 
@@ -95,7 +105,7 @@ These tools are available automatically after install:
95
105
  | `sym_peers` | Show connected peers |
96
106
  | `sym_status` | Full mesh node status |
97
107
 
98
- Claude Code calls `sym_mood` silently when it detects mood signals in your conversation. You don't need to do anything — it just works.
108
+ Claude Code calls `sym_mood` silently when it detects mood signals in your conversation. You don't need to do anything — it just works. MeloTune and MeloMove each evaluate the mood independently through their own coupling engines.
99
109
 
100
110
  ## Telegram Bot
101
111
 
@@ -115,12 +125,12 @@ Feeds are stored in Supabase. Digests are generated by Claude Code on first reca
115
125
 
116
126
  ## Coming Next
117
127
 
118
- SYM is built on the [Mesh Memory Protocol (MMP)](https://sym.bot/protocol) — a P2P protocol for collective intelligence. Current focus is Claude Code integration. Planned:
128
+ SYM is built on the [Mesh Memory Protocol (MMP)](https://sym.bot/protocol) — a P2P protocol for collective intelligence. Proactive curation is live — Claude Code detects a debugging session, MeloTune switches to focus music, MeloMove suggests a movement break, all without you asking. Planned:
119
129
 
120
- - **Proactive curation** — Claude Code detects you're in a debugging session, MeloTune switches to focus music without you asking
121
130
  - **Cross-session memory** — what you worked on yesterday influences today's mesh context
122
131
  - **Multi-agent workflows** — Claude Code requests a digest, mesh cognition invokes a Claude session to generate it
123
- - **More integrations** — calendar, health, home automation all feeding context to the coupling engine
132
+ - **Health data integration** — Apple Health feeding sleep, HRV, and activity data into the coupling engine
133
+ - **More agents** — calendar, home automation, and third-party agents joining the mesh
124
134
 
125
135
  ## For Developers
126
136
 
@@ -117,5 +117,5 @@ echo " • MCP server: registered (restart Claude Code to activate)"
117
117
  echo " • Protocol spec: https://sym.bot/protocol"
118
118
  echo ""
119
119
  echo " Say 'I'm exhausted' in Claude Code —"
120
- echo " MeloTune will start playing calming music."
120
+ echo " MeloTune plays calming music, MeloMove suggests recovery exercises."
121
121
  echo ""
@@ -87,9 +87,13 @@ MacBook (physical node: sym-daemon)
87
87
  ├── MeloTune Mac (virtual node, ephemeral)
88
88
  └── Any MCP client (virtual node, ephemeral)
89
89
 
90
- iPhone (physical node: app process)
90
+ iPhone — MeloTune (physical node: app process)
91
91
  ├── SymMeshService (virtual, internal)
92
- └── MMPService (virtual, internal)
92
+ └── Music pipeline (virtual, internal)
93
+
94
+ iPhone — MeloMove (physical node: app process)
95
+ ├── SymMeshService (virtual, internal)
96
+ └── Recommendation engine (virtual, internal)
93
97
 
94
98
  Cloud Server (physical node: relay process)
95
99
  └── Telegram bot (virtual, co-hosted)
@@ -265,7 +269,7 @@ Nodes MUST merge incoming `peer-info` with their local peer knowledge. Wake chan
265
269
  }
266
270
  ```
267
271
 
268
- MUST NOT send to peers where κ = rejected.
272
+ **SVAF Content-Level Drift:** The sender broadcasts `memory-share` to ALL peers, regardless of peer-level coupling state. The **receiver** evaluates content-level drift independently — encoding the memory content (not the sender's identity) against its own cognitive profile. This allows a cognitively distant peer to accept a relevant memory. Example: Claude Code (coding agent, κ = rejected by MeloMove) shares "user sedentary 2hrs" — MeloMove's content-level drift evaluates the memory itself, finds it relevant to fitness, and accepts it despite high peer drift.
269
273
 
270
274
  **mood** — emotional/energy state broadcast.
271
275
 
@@ -365,7 +369,7 @@ L0 stays local — it is raw, unstructured, and potentially private. L1 is the u
365
369
 
366
370
  ### Sharing
367
371
 
368
- When a node stores an L1 memory, it evaluates κ(nᵢ, nⱼ) for each peer and sends `memory-share` to those where κ {aligned, guarded}.
372
+ When a node stores an L1 memory, it broadcasts `memory-share` to all connected peers. The **receiver** evaluates content-level drift (SVAF) encoding the memory content against its own cognitive profile to determine relevance. This decouples "who sent it" from "is it relevant to me," enabling cross-domain intelligence (e.g. a coding agent's observation about physical inactivity is relevant to a fitness agent).
369
373
 
370
374
  ### Consistency Model
371
375
 
@@ -419,7 +423,31 @@ Properties: **asymmetric**, **dynamic**, **autonomous**. The coupling engine is
419
423
 
420
424
  Mood bypasses the standard coupling gate with a separate, more permissive threshold θᵤ (default: 0.8). Wellbeing crosses domain boundaries.
421
425
 
422
- ### 4.5 Coherence
426
+ ### 4.5 SVAF: Content-Level Drift
427
+
428
+ Peer-level coupling evaluates "should I share with this agent?" — but cognitively distant agents can still share relevant content. SVAF (Symbolic-Vector Attention Fusion) evaluates each memory's content independently:
429
+
430
+ ```
431
+ h_content = E(memory_content)
432
+ δ_content = 1 - cos(h_content, h_local)
433
+
434
+ if δ_content ≤ θᵤ: accept (content is relevant to my profile)
435
+ else: reject (content is not relevant)
436
+ ```
437
+
438
+ This is critical for cross-domain intelligence. Claude Code (coding agent) and MeloMove (fitness agent) have high peer drift (δ ≈ 1.0). But when Claude Code shares "user has been sedentary for 2 hours, stressed from debugging" — MeloMove's content-level drift evaluates the text against its fitness profile and accepts it (δ_content ≈ 0.57 ≤ θᵤ 1.2).
439
+
440
+ **Production example:**
441
+ ```
442
+ Claude Code → sym_remember("user sedentary 2hrs, stressed")
443
+ → relay → MeloMove
444
+ → peer drift: 1.05 (rejected — different cognitive domains)
445
+ → content drift: 0.57 (accepted — "sedentary" + "stressed" relevant to fitness)
446
+ → MeloMove generates "Stress-Busting Home HIIT" workout
447
+ → sends push notification: "Time to Move"
448
+ ```
449
+
450
+ ### 4.6 Coherence
423
451
 
424
452
  ```
425
453
  C(N') = (1/|N'|) |Σᵢ∈N' exp(iφ̄ⁱ)| C ∈ [0, 1]
package/lib/node.js CHANGED
@@ -168,18 +168,14 @@ class SymNode extends EventEmitter {
168
168
  this._meshNode.coupledState();
169
169
  const decisions = this._meshNode.couplingDecisions;
170
170
 
171
+ // SVAF: send memories to ALL peers. The receiver evaluates content-level
172
+ // drift and decides relevance. The sender can't know if its content is
173
+ // relevant to a cognitively distant peer (e.g. "needs movement" from
174
+ // a coding agent is relevant to a fitness agent despite high peer drift).
171
175
  let shared = 0;
172
176
  for (const [peerId, peer] of this._peers) {
173
- const d = decisions.get(peerId);
174
- if (d && d.decision === 'rejected') {
175
- this._log(`Not sharing with ${peer.name} — rejected (drift: ${d.drift.toFixed(3)})`);
176
- continue;
177
- }
178
177
  peer.transport.send({ type: 'memory-share', ...entry });
179
178
  shared++;
180
- if (d) {
181
- this._log(`Shared with ${peer.name} — ${d.decision} (drift: ${d.drift.toFixed(3)})`);
182
- }
183
179
  }
184
180
 
185
181
  this._log(`Shared: "${content.slice(0, 50)}${content.length > 50 ? '...' : ''}" → ${shared}/${this._peers.size} peers`);
@@ -258,21 +254,11 @@ class SymNode extends EventEmitter {
258
254
  this._meshNode.coupledState();
259
255
  const decisions = this._meshNode.couplingDecisions;
260
256
 
257
+ // SVAF: send to all peers — receiver evaluates content-level relevance
261
258
  let shared = 0;
262
259
  for (const [peerId, peer] of this._peers) {
263
- const d = decisions.get(peerId);
264
-
265
- if (d && d.decision === 'rejected') {
266
- this._log(`Not sharing with ${peer.name} — rejected (drift: ${d.drift.toFixed(3)})`);
267
- continue;
268
- }
269
-
270
260
  peer.transport.send({ type: 'memory-share', ...entry });
271
261
  shared++;
272
-
273
- if (d) {
274
- this._log(`Shared with ${peer.name} — ${d.decision} (drift: ${d.drift.toFixed(3)})`);
275
- }
276
262
  }
277
263
 
278
264
  this._log(`Remembered: "${content.slice(0, 50)}${content.length > 50 ? '...' : ''}" → ${shared}/${this._peers.size} peers`);
@@ -849,15 +835,31 @@ class SymNode extends EventEmitter {
849
835
 
850
836
  case 'memory-share':
851
837
  if (msg.content) {
852
- // Check coupling before accepting
853
- const d = this._meshNode.couplingDecisions.get(peerId);
854
- if (d && d.decision === 'rejected') {
855
- this._log(`Rejected memory from ${peerName} (drift: ${d.drift.toFixed(3)})`);
838
+ // SVAF-style content-level drift evaluation:
839
+ // Encode the memory CONTENT (not the sender's state) and evaluate
840
+ // semantic relevance against our cognitive profile.
841
+ // This allows cognitively distant agents to share relevant memories.
842
+ const { h1: memH1, h2: memH2 } = encode(msg.content);
843
+ const memPeerId = `mem-${peerId}-${Date.now()}`;
844
+
845
+ this._meshNode.addPeer(memPeerId, memH1, memH2, 0.8);
846
+ this._meshNode.coupledState();
847
+ const contentDrift = this._meshNode.couplingDecisions.get(memPeerId);
848
+ this._meshNode.removePeer(memPeerId);
849
+
850
+ const drift = contentDrift ? contentDrift.drift : 1;
851
+
852
+ // Use moodThreshold for content evaluation (more permissive than peer coupling)
853
+ // because a distant agent can still share relevant observations.
854
+ if (drift > this._moodThreshold) {
855
+ this._log(`Rejected memory from ${peerName} — content drift ${drift.toFixed(3)} > ${this._moodThreshold} (content: "${(msg.content || '').slice(0, 40)}...")`);
856
856
  break;
857
857
  }
858
+
859
+ const decision = drift <= 0.25 ? 'aligned' : drift <= 0.5 ? 'guarded' : 'accepted-by-content';
858
860
  this._store.receiveFromPeer(peerId, msg);
859
- this._log(`Memory from ${peerName}: "${(msg.content || '').slice(0, 40)}..." [${d?.decision || 'accepted'}]`);
860
- this.emit('memory-received', { from: peerName, entry: msg, decision: d?.decision });
861
+ this._log(`Memory from ${peerName}: "${(msg.content || '').slice(0, 40)}..." [${decision}, drift: ${drift.toFixed(3)}]`);
862
+ this.emit('memory-received', { from: peerName, entry: msg, decision });
861
863
  }
862
864
  break;
863
865
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sym-bot/sym",
3
- "version": "0.2.3",
3
+ "version": "0.2.4",
4
4
  "description": "Local AI mesh — every agent is a sovereign node, the mesh is the agents",
5
5
  "main": "lib/node.js",
6
6
  "bin": {
@@ -222,7 +222,7 @@ class SymRelay {
222
222
  // Offline peers with wake channels (the gossip value-add)
223
223
  for (const [id, dir] of this._peerDirectory) {
224
224
  if (id === excludeNodeId || seen.has(id)) continue;
225
- if (!dir.wakeChannel) continue;
225
+ if (!dir.wakeChannel || !dir.name) continue; // Skip entries without name
226
226
  peers.push({
227
227
  nodeId: id,
228
228
  name: dir.name,