@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 +25 -15
- package/bin/setup-claude.sh +1 -1
- package/docs/mesh-memory-protocol.md +33 -5
- package/lib/node.js +27 -25
- package/package.json +1 -1
- package/sym-relay/lib/relay.js +1 -1
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
|
|
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
|
[](https://www.npmjs.com/package/@sym-bot/sym)
|
|
10
10
|
[](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
|
|
18
|
-
| "
|
|
19
|
-
| "I
|
|
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
|
|
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
|
-
│
|
|
64
|
-
│
|
|
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
|
|
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.
|
|
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
|
-
- **
|
|
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
|
|
package/bin/setup-claude.sh
CHANGED
|
@@ -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
|
|
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
|
-
└──
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
//
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
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)}..." [${
|
|
860
|
-
this.emit('memory-received', { from: peerName, entry: msg, 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
package/sym-relay/lib/relay.js
CHANGED
|
@@ -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,
|