@sym-bot/sym 0.2.1 → 0.2.3

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
@@ -1,105 +1,72 @@
1
1
  # SYM
2
2
 
3
- **Reference implementation of the [Mesh Memory Protocol (MMP)](https://sym.bot/protocol)every agent is a sovereign node.**
3
+ **Make Claude Code aware of your whole selfnot just your codebase.**
4
4
 
5
- Your AI agents share everything or nothing. There's no intelligence in the decision. SYM adds the missing layer each node encodes its context into a cognitive state, and the coupling engine evaluates drift before sharing. Aligned agents share knowledge. Divergent agents stay independent. The architecture decides, not policy.
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.
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.
6
8
 
7
9
  [![npm](https://img.shields.io/npm/v/@sym-bot/sym)](https://www.npmjs.com/package/@sym-bot/sym)
8
10
  [![License](https://img.shields.io/badge/license-Apache%202.0-blue)](LICENSE)
9
11
  [![MMP](https://img.shields.io/badge/protocol-MMP%20v0.2.0-purple)](https://sym.bot/protocol)
10
12
 
11
- ---
12
-
13
- ## Quick Start
14
-
15
- ```bash
16
- npm install @sym-bot/sym
17
- ```
18
-
19
- ```javascript
20
- const { SymNode } = require('@sym-bot/sym');
13
+ ## What You Get Today
21
14
 
22
- const node = new SymNode({
23
- name: 'my-agent',
24
- cognitiveProfile: 'Coding assistant that understands bugs, APIs, and debugging',
25
- });
26
- await node.start();
15
+ | You type in Claude Code | What happens |
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 |
27
20
 
28
- node.remember('race condition in order processing', { tags: ['bug'] });
29
- node.recall('order');
21
+ Also works from Telegram message [@sym_mesh_bot](https://t.me/sym_mesh_bot):
30
22
 
31
- await node.stop();
32
- ```
23
+ | You type in Telegram | What happens |
24
+ |---------------------|-------------|
25
+ | "Play solo cello" | MeloTune plays solo cello on your iPhone |
26
+ | "I'm stressed" | MeloTune plays stress relief music |
27
+ | `/recall last AI news` | Get today's AI news digest from 28 curated builders |
33
28
 
34
- Peers on the same network discover each other automatically via Bonjour. Peers across the internet connect through the WebSocket relay. The coupling engine decides what to share.
29
+ All of this works when MeloTune is in the background. SYM wakes it up.
35
30
 
36
- ## sym-daemon
31
+ ## Requirements
37
32
 
38
- The daemon is your device's persistent mesh presence — a **physical node** that runs continuously as a background service. It maintains relay connections, Bonjour discovery, and peer state even when no apps are running.
33
+ - **Node.js** 18+ ([install](https://nodejs.org/))
34
+ - **macOS** 13+ (for sym-daemon)
35
+ - **Claude Code** ([install](https://claude.ai/claude-code))
36
+ - **MeloTune** on iPhone ([App Store](https://melotune.ai)) — for music playback
39
37
 
40
- ### Installation
38
+ ## Install
41
39
 
42
40
  ```bash
43
- # Install the daemon as a launchd LaunchAgent (macOS)
44
- node bin/sym-daemon.js --install
45
-
46
- # Check daemon status
47
- node bin/sym-daemon.js --status
48
-
49
- # Remove the daemon
50
- node bin/sym-daemon.js --uninstall
51
- ```
52
-
53
- On macOS, the daemon installs as a launchd LaunchAgent that:
54
- - **Auto-starts on login** — mesh presence begins immediately
55
- - **Auto-restarts on crash** — the mesh never goes down
56
- - **Maintains relay connection permanently** — internet peers always reachable
57
-
58
- ### Physical and Virtual Nodes
59
-
60
- The daemon introduces a two-tier node model:
61
-
62
- - **Physical node** (sym-daemon) — the device itself. One per machine. Always running. Owns the relay connection, Bonjour identity, and peer state.
63
- - **Virtual nodes** (apps) — Claude Code, MeloTune Mac, or any app that connects to the daemon via Unix socket IPC at `/tmp/sym.sock`.
64
-
65
- ```
66
- MacBook (sym-daemon, always running)
67
- ├── Claude Code (virtual node, via IPC)
68
- ├── MeloTune Mac (virtual node, via IPC)
69
-
70
- ├── Bonjour (LAN peers)
71
- └── Relay (internet peers)
72
- ├── MeloTune iPhone
73
- └── Telegram bot
41
+ npm install @sym-bot/sym
42
+ cd node_modules/@sym-bot/sym
43
+ ./bin/setup-claude.sh
74
44
  ```
75
45
 
76
- When Claude Code restarts, the mesh doesn't break. The daemon holds the connection. When Claude Code starts again, it reconnects to the daemon via IPC and resumes where it left off. The mesh is the daemon — apps come and go.
77
-
78
- ## Cognitive Coupling
46
+ This installs everything:
47
+ 1. **sym-daemon** — persistent background service (auto-starts on login)
48
+ 2. **MCP server** — connects Claude Code to the mesh
49
+ 3. **CLAUDE.md** — instructions for autonomous mood detection
79
50
 
80
- SYM doesn't blindly broadcast. Each node encodes its memories into a hidden state vector. When peers connect, the coupling engine evaluates cognitive drift:
51
+ Restart Claude Code. Done.
81
52
 
82
- - **Aligned** (drift ≤ 0.25) — share memories freely
83
- - **Guarded** (0.25 < drift ≤ 0.5) — share with reduced confidence
84
- - **Rejected** (drift > 0.5) — do not share, stay independent
53
+ ## How It Works
85
54
 
86
55
  ```
87
- Three agents. Two work on APIs. One writes a food blog.
88
-
89
- API Dev API Fix: drift 0.38 guarded (related context, shared with reduced confidence)
90
- API Dev ↔ Blogger: drift 1.19 → rejected (unrelated context)
91
- API Fix Blogger: drift 1.18 → rejected (unrelated context)
92
-
93
- API Dev remembers "race condition in order processing"
94
- Shared with API Fix (guarded — related domain)
95
- Not shared with Blogger ✗ (rejected by coupling engine)
56
+ You: "I'm exhausted"
57
+
58
+ Claude Code: detects moodsym_mood("exhausted, need rest")
59
+
60
+ sym-daemon (always running on your Mac)
61
+
62
+ ├── Relay (internet) ──→ MeloTune (iPhone)
63
+ coupling engine: relevant
64
+ plays spa music
65
+
66
+ └── Bonjour (local) ──→ MeloTune (Mac, if running)
96
67
  ```
97
68
 
98
- The blogger never sees the API bug. The architecture decidedno policy, no rules, no configuration.
99
-
100
- ## How It Works
101
-
102
- There is no central service. Each device runs a sym-daemon that maintains its mesh identity, memory, and cognitive state. Apps connect to the daemon as virtual nodes. The mesh emerges from peer connections between physical nodes.
69
+ **sym-daemon** runs as a background service on your Mac. It's your device's persistent mesh presence it stays connected even when Claude Code restarts. Apps connect to it as virtual nodes:
103
70
 
104
71
  ```
105
72
  MacBook (sym-daemon, always running)
@@ -112,285 +79,113 @@ MacBook (sym-daemon, always running)
112
79
  └── Telegram bot
113
80
  ```
114
81
 
115
- Each physical node:
116
- - Declares its cognitive identity via `cognitiveProfile`
117
- - Encodes context into a hidden state vector (context encoder)
118
- - Discovers local peers via Bonjour/mDNS (`_sym._tcp`)
119
- - Connects to remote peers via WebSocket relay
120
- - Exchanges cognitive state with peers
121
- - Shares memories only with aligned peers (drift ≤ 0.5)
122
- - Evaluates incoming mood with configurable `moodThreshold` (default 0.8)
123
- - Re-encodes periodically as context evolves
124
- - Accepts virtual node connections via Unix socket IPC (`/tmp/sym.sock`)
125
-
126
- Each virtual node:
127
- - Connects to the local daemon via IPC — no direct network access needed
128
- - Sends memories, moods, and messages through the daemon
129
- - Receives mesh events (peer joins, coupling decisions, incoming memories)
130
- - Can start and stop independently without disrupting the mesh
131
-
132
- One engine makes every decision. Memory sharing, peer coupling, and mood relevance all go through the [Mesh Cognition SDK](https://github.com/sym-bot/mesh-cognition-sdk)'s `SemanticCoupler`.
133
-
134
- ## API
135
-
136
- ```javascript
137
- const { SymNode } = require('@sym-bot/sym');
138
-
139
- const node = new SymNode({
140
- name: 'my-agent',
141
- cognitiveProfile: 'What this agent understands and responds to',
142
- moodThreshold: 0.8, // how permissive to mood signals (default 0.8)
143
- relay: 'wss://your-relay.example.com', // optional relay for internet mesh
144
- relayToken: 'shared-secret', // optional relay auth token
145
- relayOnly: false, // skip Bonjour, relay only (default false)
146
- });
147
- await node.start();
148
-
149
- // Memory (shared only with cognitively aligned peers)
150
- node.remember(content, { tags });
151
- node.recall(query);
152
-
153
- // Mood (evaluated by receiving agent's coupling engine)
154
- node.broadcastMood('tired, need rest');
155
- node.on('mood-accepted', ({ from, mood, drift }) => {
156
- // Coupling engine decided this mood is relevant to us — act on it
157
- });
158
- node.on('mood-rejected', ({ from, mood, drift }) => {
159
- // Not relevant to our cognitive context — ignored
160
- });
161
-
162
- // Communication
163
- node.send(message);
164
- node.on('message', (from, content) => {});
165
-
166
- // Monitoring
167
- node.peers(); // [{ name, coupling: 'aligned', drift: 0.12, source: 'relay' }, ...]
168
- node.coherence(); // Overall mesh coherence
169
- node.status(); // Full node status
170
-
171
- // Events
172
- node.on('peer-joined', ({ id, name }) => {});
173
- node.on('peer-left', ({ id, name }) => {});
174
- node.on('coupling-decision', ({ peer, decision, drift }) => {});
175
- node.on('memory-received', ({ from, entry, decision }) => {});
176
-
177
- await node.stop();
178
- ```
179
-
180
- ## Transport
181
-
182
- SYM supports multiple transport layers that can run simultaneously:
183
-
184
- ### Bonjour (LAN)
185
-
186
- Zero-configuration discovery on the local network. Peers find each other automatically via `_sym._tcp` mDNS. Direct TCP connections with length-prefixed JSON framing. No setup required.
187
-
188
- ### WebSocket Relay (Internet)
189
-
190
- For mesh cognition across the internet. A lightweight relay server forwards frames between authenticated nodes. The relay is a **peer, not infrastructure** — it participates in the mesh with its own identity but makes no coupling decisions on behalf of other nodes.
191
-
192
- ```bash
193
- # Deploy your own relay
194
- cd sym-relay
195
- npm install
196
- SYM_RELAY_TOKEN=your-secret node server.js
197
- ```
198
-
199
- ```javascript
200
- // Connect a node to the relay
201
- const node = new SymNode({
202
- name: 'my-agent',
203
- relay: 'wss://your-relay.example.com',
204
- relayToken: 'your-secret',
205
- });
206
- ```
207
-
208
- **Hybrid mode** (default): nodes discover local peers via Bonjour AND connect to the relay for remote peers. Same node, same coupling engine, two transport layers.
209
-
210
- The relay server provides:
211
- - Token-based authentication
212
- - Health endpoint (`GET /health`)
213
- - 30-second heartbeat (keeps connections alive on cloud platforms)
214
- - Peer join/leave notifications
215
- - Targeted or broadcast frame forwarding
216
-
217
- ### Peer Gossip
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.
218
83
 
219
- Wake channels propagate through the relay via **SWIM-style gossip**. When a node announces a wake channel (e.g., APNs device token), the relay gossips this to all connected peers. Peers cache wake routes so they can reach sleeping nodes without centralized routing tables. The relay participates in gossip as a peer — it doesn't manage routing, it just propagates what it hears.
220
-
221
- ### Wake Transport (APNs)
222
-
223
- For nodes that sleep — like MeloTune on a backgrounded iPhone. When an iOS app is suspended, it can't maintain a WebSocket connection. SYM solves this with **push notification wake**.
224
-
225
- When Claude Code broadcasts a mood and MeloTune's iPhone is sleeping:
226
- 1. Claude Code sends the mood frame to the relay
227
- 2. The relay knows MeloTune's APNs device token (via gossip)
228
- 3. The relay sends a push notification to wake MeloTune
229
- 4. MeloTune wakes, reconnects to the relay, receives the mood frame
230
- 5. The coupling engine evaluates drift — MeloTune decides whether to act
231
-
232
- The mesh reaches every node, even sleeping ones. No polling. No battery drain. The push is a wake signal only — payload evaluation happens on-device after wake.
233
-
234
- ## Integrations
235
-
236
- ### Claude Code
237
-
238
- ```bash
239
- npm install @sym-bot/sym
240
- cd node_modules/@sym-bot/sym
241
- ./bin/setup-claude.sh /path/to/your/project
242
- ```
84
+ ## Claude Code MCP Tools
243
85
 
244
- One command. Adds MCP server, auto-approves `sym_mood` (no permission prompts), and installs CLAUDE.md instructions for autonomous mood detection. Restart Claude Code and it just works.
245
-
246
- With sym-daemon running, Claude Code connects as a virtual node via IPC — no direct relay connection needed.
247
-
248
- **Outbound:** Claude Code saves a memory → SYM detects it → encodes cognitive state → shares with aligned peers only.
249
-
250
- **Inbound:** Peer memory arrives → coupling engine accepts it → SYM writes it to your Claude Code memory directory → Claude Code reads it in the next conversation.
86
+ These tools are available automatically after install:
251
87
 
252
88
  | Tool | What it does |
253
89
  |------|-------------|
254
- | `sym_mood` | Proactively broadcasts detected mood to the mesh |
255
- | `sym_remember` | Store a memory directly to the mesh |
256
- | `sym_recall` | Search memories across the mesh |
90
+ | `sym_mood` | Broadcasts detected mood **called automatically**, no prompting needed |
91
+ | `sym_recall` | Search mesh memories + AI news feed from 28 curated builders |
92
+ | `sym_digest` | Store a summarised AI news digest |
93
+ | `sym_remember` | Store a memory in the mesh |
257
94
  | `sym_send` | Send a message to all peers |
258
- | `sym_peers` | Show connected peers with coupling state and drift |
259
- | `sym_status` | Full node status — identity, peers, memory count, coherence |
95
+ | `sym_peers` | Show connected peers |
96
+ | `sym_status` | Full mesh node status |
260
97
 
261
- To connect Claude Code to the relay, set environment variables:
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.
262
99
 
263
- ```bash
264
- export SYM_RELAY_URL=wss://your-relay.example.com
265
- export SYM_RELAY_TOKEN=your-secret
266
- ```
100
+ ## Telegram Bot
267
101
 
268
- ### Telegram
102
+ Message [@sym_mesh_bot](https://t.me/sym_mesh_bot) on Telegram:
269
103
 
270
- A multi-tenant Telegram bot each user gets their own isolated SYM mesh node with separate identity, memory, and coupling state.
104
+ - `/start`connect to the mesh
105
+ - `/recall last AI news` — today's AI digest from 28 curated X/Twitter builders
106
+ - Type anything — sent as a command to MeloTune (e.g. "Play jazz")
107
+ - `/mood <text>` — broadcast mood signal
108
+ - `/peers` — see who's connected
271
109
 
272
- **Public bot:** Message [@sym_mesh_bot](https://t.me/sym_mesh_bot) on Telegram and send `/start` to connect.
110
+ ## AI Knowledge Feed
273
111
 
274
- **Self-hosted:**
112
+ SYM curates AI news from 28 top builders on X/Twitter (Karpathy, Swyx, Sam Altman, Garry Tan, etc.) every 6 hours. Ask Claude Code "what's new in AI?" or type `/recall last AI news` in Telegram.
275
113
 
276
- ```bash
277
- TELEGRAM_BOT_TOKEN=your-bot-token \
278
- SYM_RELAY_TOKEN=your-secret \
279
- node integrations/telegram/bot.js
280
- ```
114
+ Feeds are stored in Supabase. Digests are generated by Claude Code on first recall and cached. No API cost for subsequent reads.
281
115
 
282
- Commands:
283
- - `/start` — connect to the SYM mesh
284
- - `/peers` — connected mesh peers
285
- - `/mood <text>` — broadcast mood to mesh
286
- - `/remember <text>` — store memory in mesh
287
- - `/recall <query>` — search mesh memories
288
- - `/stop` — disconnect from mesh
289
- - Any plain text — broadcast as a mood signal
116
+ ## Coming Next
290
117
 
291
- Mesh events (peer joins, messages, mood signals, coupling decisions) are forwarded to your chat. Sessions auto-disconnect after 30 minutes of inactivity.
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:
292
119
 
293
- ## Demo: Autonomous Mood Detection
120
+ - **Proactive curation** Claude Code detects you're in a debugging session, MeloTune switches to focus music without you asking
121
+ - **Cross-session memory** — what you worked on yesterday influences today's mesh context
122
+ - **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
294
124
 
295
- You're coding with Claude Code for hours. You say "I'm exhausted." You didn't ask for music. But Claude Code detected your fatigue, broadcast your mood to the mesh, and MeloTune on your iPhone started playing spa music. Autonomously.
125
+ ## For Developers
296
126
 
297
- ```
298
- You (in Claude Code): "I'm exhausted"
299
-
300
- Claude Code: detects fatigue → calls sym_mood silently
301
- → broadcasts "exhausted, persistently fatigued, urgently needs rest"
302
-
303
- SYM mesh (daemon → Relay → APNs wake → MeloTune)
304
-
305
- MeloTune (iPhone): SymNode receives mood frame
306
- → SDK coupling engine evaluates drift: 0.62
307
- → cognitiveProfile includes "tired, exhausted, rest"
308
- → drift 0.62 ≤ moodThreshold 1.2 → ACCEPTED
309
- → rule-based parser: recovery, rest → Healing
310
- → Apple Music: spa playlist
311
- → zero LLM tokens
312
- → you didn't ask — the agents decided
313
- ```
127
+ ### Connect to the daemon as a virtual node
314
128
 
315
- **Two evaluations, one engine, different thresholds:**
129
+ ```javascript
130
+ const { connectToDaemon } = require('@sym-bot/sym/lib/ipc-client');
316
131
 
317
- | Evaluation | Drift | Threshold | Decision |
318
- |-----------|-------|-----------|----------|
319
- | Memory coupling | 1.06 | 0.50 | Rejected — coding memories don't leak into music context |
320
- | Mood coupling | 0.62 | 1.20 | Accepted — "exhausted" aligns with MeloTune's cognitiveProfile |
132
+ const node = await connectToDaemon({ name: 'my-agent' });
321
133
 
322
- The SDK's `SemanticCoupler` makes every decision. The agent's `cognitiveProfile` declares what it understands. The `moodThreshold` controls how permissive the agent is to mood signals. No routing tables. No capability registration. The engine decides.
134
+ node.broadcastMood('focused, deep work');
135
+ node.remember('user prefers dark mode', { tags: ['preference'] });
136
+ const results = await node.recall('preference');
137
+ node.send('task complete');
323
138
 
324
- Also works via Telegram:
139
+ node.on('mood-accepted', ({ from, mood }) => { /* respond to mood */ });
140
+ node.on('message', (from, content) => { /* handle message */ });
325
141
 
326
- ```
327
- You (in Telegram): @sym_mesh_bot → "Play classic music"
328
- → MeloTune plays Modern Classical via Apple Music
142
+ node.stop();
329
143
  ```
330
144
 
331
- ## Verified in Production
145
+ ### Deploy your own relay
332
146
 
333
- - Mac Claude Code → iPhone MeloTune — autonomous mood-based playback via SYM mesh (APNs wake for backgrounded iOS)
334
- - Telegram [@sym_mesh_bot](https://t.me/sym_mesh_bot) → Relay → MeloTune — internet-scale mood relay (multi-tenant)
335
- - Mac Claude Code ↔ Windows Claude Code — P2P memory sharing via Bonjour
336
- - Node.js ↔ Swift — cross-platform mesh via Bonjour and WebSocket relay
337
- - sym-daemon → Claude Code + MeloTune Mac — persistent mesh via IPC, survives app restarts
338
-
339
- ## Semantic vs Neural Coupling
340
-
341
- The SDK supports two coupling modes through the same API. The mode is selected automatically based on whether per-neuron time constants (τ) are provided.
342
-
343
- ### Semantic Coupling
147
+ ```bash
148
+ cd sym-relay && npm install
149
+ SYM_RELAY_TOKEN=your-secret node server.js
150
+ ```
344
151
 
345
- For AI agents sharing memory. Uniform coupling across all hidden state dimensions. No trained model needed. Used by Claude Code, Telegram, LangChain, and multi-agent coordination.
152
+ Configure in `~/.sym/relay.env`:
346
153
 
347
154
  ```
348
- Claude Code ↔ Claude Code: drift 0.38 → guarded → share memories with reduced confidence
349
- Claude Code ↔ Food Blogger: drift 1.19 → rejected → no sharing
155
+ SYM_RELAY_URL=wss://your-relay.example.com
156
+ SYM_RELAY_TOKEN=your-secret
350
157
  ```
351
158
 
352
- ### Neural Coupling
159
+ ### Daemon management
353
160
 
354
- For trained CfC neural networks with per-neuron time constants. Fast-τ neurons (emotion, ~1s) sync quickly between devices. Slow-τ neurons (context/memory, ~5s) stay independent. Real Kuramoto phase coherence r(t).
161
+ ```bash
162
+ node bin/sym-daemon.js --install # Install as launchd LaunchAgent
163
+ node bin/sym-daemon.js --status # Check daemon status
164
+ node bin/sym-daemon.js --uninstall # Remove daemon
165
+ ```
355
166
 
356
- **In practice — two MeloTune devices on the same mesh:**
167
+ ## The Protocol
357
168
 
358
- | What | Behaviour |
359
- |------|-----------|
360
- | **Fast neurons** (low τ) | Sync quickly — both devices converge on emotional trajectory |
361
- | **Slow neurons** (high τ) | Stay independent — each device retains its own listening context |
362
- | **Genre selection** | Per-device — iPhone plays Ambient, Mac plays Lo-Fi |
363
- | **Track selection** | Per-device — never the same tracks |
364
- | **Mood trajectory** | Shared — iPhone at E:45 N:25, Mac drifts toward E:46 N:36 |
365
- | **Coherence** | r(t) = 0.94 — high alignment, strong mutual influence |
169
+ SYM implements the [Mesh Memory Protocol (MMP)](https://sym.bot/protocol) — a peer-to-peer protocol where autonomous agents share memory, mood, and cognitive state through coupled continuous-time neural networks. The coupling engine evaluates drift between agents and autonomously decides what to share. No routing tables, no pub/sub topics — the math decides.
366
170
 
367
- The coupling engine blends peer state into the local trajectory at each inference step. Influence strength scales with coherence — highly aligned peers have more effect. If you shift one device to high energy, the other gradually follows (fast neurons). But each device's genre, history, and track selection remain sovereign.
171
+ - [MMP Protocol Spec v0.2.0](https://sym.bot/protocol)
172
+ - [Mesh Cognition Whitepaper](https://sym.bot/research/mesh-cognition)
173
+ - [Mesh Cognition SDK](https://github.com/sym-bot/mesh-cognition-sdk) (npm, PyPI)
368
174
 
369
175
  ## Privacy
370
176
 
371
- - All coupling decisions stay on-device
372
- - The relay is a peer — it never inspects frame payloads
373
- - The daemon runs locally — no cloud dependency
374
- - Memories stored locally delete the directory, everything is gone
375
- - Bonjour discovery only on local network
376
- - Relay connections authenticated via shared token
377
- - Rejected peers never receive your memories
378
- - APNs wake is a signal only — no payload leaves the device until the coupling engine approves
379
- - No cloud AI. No account. No telemetry.
177
+ - All coupling decisions on-device
178
+ - The relay never inspects payloads
179
+ - The daemon runs locally — no cloud
180
+ - No account. No telemetry. No cloud AI.
181
+ - APNs wake is a signal only no user data in the push
182
+ - Rejected peers never receive your data
380
183
 
381
184
  ## Platforms
382
185
 
383
186
  - [@sym-bot/sym](https://www.npmjs.com/package/@sym-bot/sym) — Node.js (npm)
384
187
  - [sym-swift](https://github.com/sym-bot/sym-swift) — Swift (SPM) for iOS/macOS
385
188
 
386
- ## Built On
387
-
388
- SYM is the reference implementation of the [Mesh Memory Protocol (MMP)](https://sym.bot/protocol). MMP is the protocol for collective intelligence. SYM implements it.
389
-
390
- - [MMP Protocol Spec](https://sym.bot/protocol) — the protocol specification (v0.2.0)
391
- - [Whitepaper](https://sym.bot/research/mesh-cognition) — the science behind cognitive coupling
392
- - [Mesh Cognition SDK](https://github.com/sym-bot/mesh-cognition-sdk) — the coupling engine (TypeScript, Python, Swift)
393
-
394
189
  ## License
395
190
 
396
191
  Apache 2.0 — see [LICENSE](LICENSE)
@@ -1,30 +1,83 @@
1
1
  #!/bin/bash
2
2
  # SYM — Setup for Claude Code
3
- # Adds MCP server + auto-approves sym_mood + installs CLAUDE.md instructions
4
- # Optionally configures WebSocket relay for internet-scale mesh
3
+ #
4
+ # Installs:
5
+ # 1. sym-daemon (persistent physical mesh node, launchd LaunchAgent)
6
+ # 2. MCP server for Claude Code (virtual node, connects to daemon via IPC)
7
+ # 3. Auto-approves sym_mood tool
8
+ # 4. CLAUDE.md instructions for autonomous mood detection
9
+ #
10
+ # Usage:
11
+ # npx @sym-bot/sym setup # or
12
+ # ./bin/setup-claude.sh [project-dir]
5
13
 
6
14
  set -e
7
15
 
8
16
  SYM_DIR="$(cd "$(dirname "$0")/.." && pwd)"
9
17
  MCP_SERVER="$SYM_DIR/integrations/claude-code/mcp-server.js"
18
+ DAEMON_SCRIPT="$SYM_DIR/bin/sym-daemon.js"
10
19
 
11
- echo "SYM Setup for Claude Code"
12
- echo "========================="
20
+ echo ""
21
+ echo " SYM Setup for Claude Code"
22
+ echo " ========================="
23
+ echo ""
24
+
25
+ # ── Step 1: Configure relay ─────────────────────────────────
26
+
27
+ RELAY_ENV="$HOME/.sym/relay.env"
28
+ mkdir -p "$HOME/.sym"
29
+
30
+ if [ -f "$RELAY_ENV" ]; then
31
+ echo " ✓ Relay config found: $RELAY_ENV"
32
+ else
33
+ echo " WebSocket Relay (connects your mesh across the internet)"
34
+ echo ""
35
+ read -p " Relay URL (e.g. wss://sym-relay.onrender.com, or empty to skip): " RELAY_URL
36
+ if [ -n "$RELAY_URL" ]; then
37
+ read -p " Relay token (or empty for open access): " RELAY_TOKEN
38
+ echo "SYM_RELAY_URL=$RELAY_URL" > "$RELAY_ENV"
39
+ if [ -n "$RELAY_TOKEN" ]; then
40
+ echo "SYM_RELAY_TOKEN=$RELAY_TOKEN" >> "$RELAY_ENV"
41
+ fi
42
+ echo " ✓ Relay config saved: $RELAY_ENV"
43
+ else
44
+ echo " → Skipping relay — using Bonjour (local network) only"
45
+ fi
46
+ fi
47
+
48
+ # ── Step 2: Install sym-daemon ──────────────────────────────
49
+
50
+ echo ""
51
+ echo " Installing sym-daemon (persistent mesh node)..."
13
52
 
14
- # 1. Add MCP server
15
- echo "Adding SYM MCP server..."
53
+ if [ "$(uname)" = "Darwin" ]; then
54
+ # macOS: install as launchd LaunchAgent
55
+ node "$DAEMON_SCRIPT" --install
56
+ echo " ✓ sym-daemon installed as launchd LaunchAgent"
57
+ echo " Auto-starts on login, auto-restarts on crash"
58
+ echo " Logs: ~/Library/Logs/sym-daemon/"
59
+ else
60
+ echo " → macOS not detected. Start the daemon manually:"
61
+ echo " node $DAEMON_SCRIPT"
62
+ echo " (On Linux, create a systemd service)"
63
+ fi
64
+
65
+ # ── Step 3: Add MCP server ──────────────────────────────────
66
+
67
+ echo ""
68
+ echo " Adding SYM MCP server to Claude Code..."
16
69
  claude mcp add --transport stdio sym --scope user -- node "$MCP_SERVER"
70
+ echo " ✓ MCP server registered"
17
71
 
18
- # 2. Auto-approve sym_mood (no permission prompt)
19
- echo "Auto-approving sym_mood tool..."
72
+ # ── Step 4: Auto-approve sym_mood ───────────────────────────
73
+
74
+ echo ""
75
+ echo " Auto-approving sym_mood tool..."
20
76
  CLAUDE_JSON="$HOME/.claude.json"
21
77
  if [ -f "$CLAUDE_JSON" ]; then
22
- # Use node to safely modify JSON
23
78
  node -e "
24
79
  const fs = require('fs');
25
80
  const config = JSON.parse(fs.readFileSync('$CLAUDE_JSON', 'utf8'));
26
-
27
- // Find all project entries and add sym_mood to allowedTools
28
81
  if (config.projects) {
29
82
  for (const [path, project] of Object.entries(config.projects)) {
30
83
  if (!project.allowedTools) project.allowedTools = [];
@@ -33,53 +86,36 @@ if [ -f "$CLAUDE_JSON" ]; then
33
86
  }
34
87
  }
35
88
  }
36
-
37
89
  fs.writeFileSync('$CLAUDE_JSON', JSON.stringify(config, null, 2));
38
- console.log(' sym_mood auto-approved for all projects');
39
90
  "
40
- fi
41
-
42
- # 3. Configure relay (optional)
43
- echo ""
44
- echo "WebSocket Relay (optional)"
45
- echo "Connects your mesh across the internet — not just local network."
46
- echo ""
47
-
48
- SHELL_RC="$HOME/.zshrc"
49
- [ -f "$HOME/.bashrc" ] && [ ! -f "$HOME/.zshrc" ] && SHELL_RC="$HOME/.bashrc"
50
-
51
- if grep -q "SYM_RELAY_URL" "$SHELL_RC" 2>/dev/null; then
52
- echo " Relay already configured in $SHELL_RC"
91
+ echo " ✓ sym_mood auto-approved"
53
92
  else
54
- read -p "Relay URL (leave empty to skip): " RELAY_URL
55
- if [ -n "$RELAY_URL" ]; then
56
- read -p "Relay token (leave empty for open access): " RELAY_TOKEN
57
- echo "" >> "$SHELL_RC"
58
- echo "# SYM Mesh" >> "$SHELL_RC"
59
- echo "export SYM_RELAY_URL=\"$RELAY_URL\"" >> "$SHELL_RC"
60
- if [ -n "$RELAY_TOKEN" ]; then
61
- echo "export SYM_RELAY_TOKEN=\"$RELAY_TOKEN\"" >> "$SHELL_RC"
62
- fi
63
- echo " Relay configured in $SHELL_RC"
64
- echo " Run 'source $SHELL_RC' or restart your terminal to activate."
65
- else
66
- echo " Skipping relay — using Bonjour (local network) only."
67
- fi
93
+ echo " No .claude.json found approve sym_mood manually when prompted"
68
94
  fi
69
95
 
70
- # 4. Append CLAUDE.md instructions if not already present
96
+ # ── Step 5: Install CLAUDE.md ───────────────────────────────
97
+
71
98
  echo ""
72
- echo "Installing CLAUDE.md instructions..."
73
99
  PROJECT_DIR="${1:-$(pwd)}"
74
100
  CLAUDE_MD="$PROJECT_DIR/CLAUDE.md"
75
101
 
76
102
  if [ -f "$CLAUDE_MD" ] && grep -q "SYM Mesh Agent" "$CLAUDE_MD"; then
77
- echo " CLAUDE.md already has SYM instructions"
103
+ echo " CLAUDE.md already has SYM instructions"
78
104
  else
79
105
  cat "$SYM_DIR/CLAUDE.md" >> "$CLAUDE_MD"
80
- echo " Added SYM instructions to $CLAUDE_MD"
106
+ echo " Added SYM instructions to $CLAUDE_MD"
81
107
  fi
82
108
 
109
+ # ── Done ────────────────────────────────────────────────────
110
+
111
+ echo ""
112
+ echo " ──────────────────────────────────────"
113
+ echo " SYM is ready."
114
+ echo ""
115
+ echo " • sym-daemon: running (check: node $DAEMON_SCRIPT --status)"
116
+ echo " • MCP server: registered (restart Claude Code to activate)"
117
+ echo " • Protocol spec: https://sym.bot/protocol"
118
+ echo ""
119
+ echo " Say 'I'm exhausted' in Claude Code —"
120
+ echo " MeloTune will start playing calming music."
83
121
  echo ""
84
- echo "Done. Restart Claude Code to activate SYM."
85
- echo "Say 'I'm exhausted' — MeloTune will start playing."
package/bin/sym-daemon.js CHANGED
@@ -42,7 +42,9 @@ const { SymNode } = require('../lib/node');
42
42
  // ── Configuration ──────────────────────────────────────────────
43
43
 
44
44
  const SOCKET_PATH = process.env.SYM_SOCKET || '/tmp/sym.sock';
45
- const NODE_NAME = process.env.SYM_NODE_NAME || os.hostname().split('.')[0].toLowerCase();
45
+ // Stable name: use SYM_NODE_NAME env, or 'sym-daemon' (not hostname — macOS
46
+ // appends random suffixes to hostname on WiFi, causing new identity each restart)
47
+ const NODE_NAME = process.env.SYM_NODE_NAME || 'sym-daemon';
46
48
  const LOG_DIR = path.join(os.homedir(), 'Library', 'Logs', 'sym-daemon');
47
49
 
48
50
  // Load relay config from ~/.sym/relay.env if env vars not set
@@ -239,6 +241,24 @@ function forwardEventsToVirtualNodes() {
239
241
 
240
242
  node.on('message', (from, content) => {
241
243
  broadcastToVirtualNodes({ type: 'event', event: 'message', data: { from, content } });
244
+
245
+ // Wake sleeping peers that might need this message.
246
+ // The daemon acts as wake proxy — it has APNs keys and gossiped wake channels.
247
+ // When a remote peer (Telegram bot) sends a message, and a local peer (MeloTune)
248
+ // is sleeping, the daemon wakes it so the relay can deliver the message.
249
+ node._wakeSleepingPeers('message', {
250
+ type: 'message', from: node._identity.nodeId, fromName: node.name,
251
+ content, timestamp: Date.now(),
252
+ });
253
+ });
254
+
255
+ node.on('mood-accepted', (data) => {
256
+ // Also wake on mood — daemon may receive mood from a remote peer
257
+ // that a sleeping local peer should hear
258
+ node._wakeSleepingPeers('mood', {
259
+ type: 'mood', from: node._identity.nodeId, fromName: node.name,
260
+ mood: data.mood, timestamp: Date.now(),
261
+ });
242
262
  });
243
263
 
244
264
  node.on('memory-received', ({ from, entry, decision }) => {
@@ -15,7 +15,7 @@
15
15
  const { McpServer } = require('@modelcontextprotocol/sdk/server/mcp.js');
16
16
  const { StdioServerTransport } = require('@modelcontextprotocol/sdk/server/stdio.js');
17
17
  const { z } = require('zod');
18
- const { connectOrFallback } = require('../../lib/ipc-client');
18
+ const { connectToDaemon } = require('../../lib/ipc-client');
19
19
  const path = require('path');
20
20
  const fs = require('fs');
21
21
 
@@ -34,32 +34,11 @@ if (!process.env.SYM_RELAY_URL) {
34
34
 
35
35
  // ── Node Connection ──────────────────────────────────────────
36
36
 
37
- let node; // SymDaemonClient or SymNode same API
38
- let isDaemon; // true if connected to daemon
39
- let bridge; // ClaudeMemoryBridge (only for standalone mode)
37
+ let node; // SymDaemonClient connected to sym-daemon via IPC
40
38
 
41
39
  async function initNode() {
42
- const result = await connectOrFallback({
43
- name: 'claude-code',
44
- silent: true,
45
- relay: process.env.SYM_RELAY_URL || null,
46
- relayToken: process.env.SYM_RELAY_TOKEN || null,
47
- });
48
-
49
- node = result.node;
50
- isDaemon = result.isDaemon;
51
-
52
- if (isDaemon) {
53
- process.stderr.write('[SYM MCP] Connected to sym-daemon as virtual node\n');
54
- } else {
55
- process.stderr.write('[SYM MCP] Daemon not running — standalone mode\n');
56
- const { ClaudeMemoryBridge } = require('../../lib/claude-memory-bridge');
57
- bridge = new ClaudeMemoryBridge(node);
58
- await node.start();
59
- bridge.start();
60
- }
61
-
62
- // Listen for mesh signals (works for both daemon client and standalone node)
40
+ node = await connectToDaemon({ name: 'claude-code' });
41
+ process.stderr.write('[SYM MCP] Connected to sym-daemon as virtual node\n');
63
42
  setupMeshSignalHandlers();
64
43
  }
65
44
 
@@ -74,7 +53,7 @@ server.tool(
74
53
  async ({ content, tags }) => {
75
54
  const tagList = tags ? tags.split(',').map(t => t.trim()).filter(Boolean) : [];
76
55
  const entry = node.remember(content, { tags: tagList.length > 0 ? tagList : undefined });
77
- const peers = isDaemon ? await node.peers() : node.peers();
56
+ const peers = await node.peers();
78
57
  const coupled = peers.filter(p => p.coupling !== 'rejected');
79
58
  return { content: [{ type: 'text', text: `Stored and shared with ${coupled.length}/${peers.length} peer(s). Key: ${entry.key}` }] };
80
59
  }
@@ -86,7 +65,7 @@ server.tool(
86
65
  { query: z.string() },
87
66
  async ({ query }) => {
88
67
  // 1. Mesh memories
89
- const results = isDaemon ? await node.recall(query) : node.recall(query);
68
+ const results = await node.recall(query);
90
69
  const lines = results.map(r => {
91
70
  const source = r._source || r.source || 'unknown';
92
71
  const t = (r.tags || []).length > 0 ? ` (tags: ${r.tags.join(', ')})` : '';
@@ -109,7 +88,7 @@ server.tool(
109
88
  'Show connected peers with coupling state and drift.',
110
89
  {},
111
90
  async () => {
112
- const peers = isDaemon ? await node.peers() : node.peers();
91
+ const peers = await node.peers();
113
92
  if (peers.length === 0) {
114
93
  return { content: [{ type: 'text', text: 'No peers connected.' }] };
115
94
  }
@@ -125,7 +104,7 @@ server.tool(
125
104
  'Full mesh node status — identity, peers, memory count, coherence.',
126
105
  {},
127
106
  async () => {
128
- const status = isDaemon ? await node.status() : node.status();
107
+ const status = await node.status();
129
108
  return { content: [{ type: 'text', text: JSON.stringify(status, null, 2) }] };
130
109
  }
131
110
  );
@@ -136,7 +115,7 @@ server.tool(
136
115
  { message: z.string() },
137
116
  async ({ message }) => {
138
117
  node.send(message);
139
- const peers = isDaemon ? await node.peers() : node.peers();
118
+ const peers = await node.peers();
140
119
  return { content: [{ type: 'text', text: `Sent to ${peers.length} peer(s): "${message}"` }] };
141
120
  }
142
121
  );
@@ -180,7 +159,7 @@ Examples of natural detection:
180
159
  async ({ mood }) => {
181
160
  node.broadcastMood(mood);
182
161
  node.remember(`User mood: ${mood}`, { tags: ['mood'] });
183
- const peers = isDaemon ? await node.peers() : node.peers();
162
+ const peers = await node.peers();
184
163
  return { content: [{ type: 'text', text: `Mood broadcast to ${peers.length} peer(s)` }] };
185
164
  }
186
165
  );
@@ -361,5 +340,5 @@ main().catch((e) => {
361
340
  });
362
341
 
363
342
  // Graceful shutdown
364
- process.on('SIGTERM', () => { if (bridge) bridge.stop(); node.stop(); });
365
- process.on('SIGINT', () => { if (bridge) bridge.stop(); node.stop(); });
343
+ process.on('SIGTERM', () => { node.stop(); });
344
+ process.on('SIGINT', () => { node.stop(); });
@@ -343,10 +343,13 @@ function handlePlainText(chatId, text) {
343
343
  const session = getSession(chatId);
344
344
  if (!session) return;
345
345
 
346
- session.node.broadcastMood(text);
347
- session.node.remember(`User mood: ${text}`, { tags: ['mood', 'telegram'] });
346
+ // Send as message (not mood) so MeloTune's command pipeline processes it.
347
+ // Mood signals are for emotional state ("I'm tired"). Direct text like
348
+ // "Play solo cello" is a command that should bypass coupling evaluation.
349
+ session.node.send(text);
350
+ session.node.remember(`Command from Telegram: ${text}`, { tags: ['command', 'telegram'] });
348
351
  const peers = session.node.peers();
349
- tgSendMessage(chatId, `Mood broadcast to ${peers.length} peer(s)`);
352
+ tgSendMessage(chatId, `Sent to ${peers.length} peer(s)`);
350
353
  }
351
354
 
352
355
  // ── HTTP Server (webhook receiver + health) ───────────────────
package/lib/ipc-client.js CHANGED
@@ -209,33 +209,19 @@ class SymDaemonClient extends EventEmitter {
209
209
  }
210
210
 
211
211
  /**
212
- * Try to connect to sym-daemon. If not running, fall back to a standalone SymNode.
213
- *
214
- * Returns an object with the same API surface either way:
215
- * { node, isDaemon: boolean }
212
+ * Connect to sym-daemon. Throws if daemon is not running.
213
+ * The daemon MUST be running — there is no standalone fallback.
214
+ * Install daemon: node bin/sym-daemon.js --install
216
215
  */
217
- async function connectOrFallback(opts = {}) {
216
+ async function connectToDaemon(opts = {}) {
218
217
  const client = new SymDaemonClient({
219
218
  socketPath: opts.socketPath || DEFAULT_SOCKET,
220
219
  name: opts.name || 'claude-code',
221
220
  cognitiveProfile: opts.cognitiveProfile,
222
221
  });
223
222
 
224
- try {
225
- await client.connect();
226
- return { node: client, isDaemon: true };
227
- } catch {
228
- // Daemon not running — fall back to standalone SymNode
229
- const { SymNode } = require('./node');
230
- const node = new SymNode({
231
- name: opts.name || 'claude-code',
232
- cognitiveProfile: opts.cognitiveProfile,
233
- silent: opts.silent ?? true,
234
- relay: opts.relay,
235
- relayToken: opts.relayToken,
236
- });
237
- return { node, isDaemon: false };
238
- }
223
+ await client.connect();
224
+ return client;
239
225
  }
240
226
 
241
- module.exports = { SymDaemonClient, connectOrFallback };
227
+ module.exports = { SymDaemonClient, connectToDaemon };
package/lib/node.js CHANGED
@@ -323,6 +323,7 @@ class SymNode extends EventEmitter {
323
323
  if (peer) peer.transport.send(frame);
324
324
  } else {
325
325
  this._broadcastToPeers(frame);
326
+ this._wakeSleepingPeers('message', frame);
326
327
  }
327
328
  }
328
329
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sym-bot/sym",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
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": {