cross-node-sync
$
npx mdskill add sonichi/sutando/cross-node-syncSynchronizes memory and notes between Sutando nodes using rsync-over-ssh
- Shares bot memory and user notes between Mac Studio and MacBook
- Uses macOS-native rsync and existing SSH trust relationships
- Syncs 17 memory files and 34 notes with union semantics via --update
- Runs automatically during proactive-loop cron for consistent convergence
SKILL.md
.github/skills/cross-node-syncView on GitHub ↗
---
name: cross-node-sync
description: "Rsync-over-ssh sync between Sutando nodes (Mac Studio and MacBook) for shared memory + notes. Optional — core runs fine without it; enables automatic cross-bot learning and note propagation by running from the proactive-loop cron on each pass."
user-invocable: false
---
# cross-node-sync
Rsync-over-ssh cross-node sync for Sutando-Studio (Mac Studio) and Sutando-Mini (MacBook). Shares bot memory and user notes so both nodes converge automatically on each proactive loop pass.
## Why rsync (not Syncthing)
Initial design used Syncthing (peer-to-peer daemon, continuous sync). Pivoted to rsync-over-ssh after manifest comparison showed the sync scope is narrow (17 memory files, 34 notes) — a daemon + web UI is overkill. Rsync wins on:
- **No new binary** — rsync is macOS-native, no brew install needed.
- **No daemon** — fires from the existing proactive-loop cron, no process to babysit.
- **Auditable** — each sync pass logs exactly what moved; `--dry-run` preview is first-class.
- **SSH-keyed auth** — reuses the ssh trust relationship you already have for git, no Device-ID pairing dance.
- **Lighter on disk** — no `.stversions/` per-file versioning, no config.xml, no index database.
Syncthing would still be the right call if: scope grew past a few hundred files, we hit conflict frequency > daily, or we wanted sub-second propagation. Neither is true today.
## Scope
**Syncs (both directions, union semantics via `rsync --update`):**
- `~/.claude/projects/-Users-xueqingliu-Documents-sutando-sutando/memory/` — cross-session bot memory
- `<repo>/notes/` — user's second-brain notes
- `<repo>/assets/` — owner personal runtime assets (e.g. gitignored `stand-avatar.png`); `/assets` is entirely gitignored, rsync is the only transport
**Excluded (per-node state):**
- `state/`, `tasks/`, `results/`, `logs/` — per-bot queues + histories
- `core-status.json`, `build_log.md`, `contextual-chips.json` — per-bot proactive state
- `.env`, `~/.claude/channels/*/.env` — different tokens per node
- `data/voice-metrics.jsonl`, `data/call-metrics.jsonl` — frozen historical archives (writers removed in #603; new session rollups go to `data/conversation.sqlite` instead)
- `src/.discord-pending-replies.json` (legacy location), `src/Sutando/SutandoApp` (Mac binary build artifact)
- `~/.claude/projects/` (other projects), `~/.claude/skills/` (installed per-node)
- `.DS_Store`, `*.swp`, `*.swo`, `.stversions`, `.stfolder` (OS/editor/Syncthing-legacy noise)
## Setup
Two steps, once per node:
```bash
# 1. Generate / authorize SSH key on the peer (prints instructions)
bash skills/cross-node-sync/scripts/setup-rsync-sync.sh --setup
# 2. Set the peer host — the ONLY required env var
echo 'export SUTANDO_SYNC_PEER="susan@MacBook-Pro.local"' >> .env # on Studio
echo 'export SUTANDO_SYNC_PEER="susan@Mac-Studio.local"' >> .env # on Mini
```
`SUTANDO_PEER_MEM_DIR` / `SUTANDO_PEER_NOTES_DIR` default to the **same literal paths as local**, so the defaults only work when both nodes share the same OS username AND the same repo location. In practice that's rare (e.g. Studio's `/Users/xueqingliu/...` vs MacBook's `/Users/xliu/...`), so most setups will want to set them explicitly:
```bash
# Example: Studio talking to a MacBook with a different username + repo path
export SUTANDO_PEER_MEM_DIR="/Users/xliu/.claude/projects/-Users-xliu-.../memory/"
export SUTANDO_PEER_NOTES_DIR="/Users/xliu/path/to/sutando/notes/"
```
Get the peer's values with `ssh $SUTANDO_SYNC_PEER 'echo $HOME; ls -d ~/.claude/projects/-*sutando*'`.
**Cron wiring (explicit opt-in):** as of #936, the `cross-node-sync` entry is **no longer** in `skills/schedule-crons/crons.example.json` — the default `cp ... crons.json` doesn't wire it. Cross-node-sync requires `SUTANDO_SYNC_PEER` + SSH peer auth + the per-file-frontmatter memory model, none of which apply to a single-host setup, so it's opt-in.
If you want the 7-minute sync, add this block to your live `skills/schedule-crons/crons.json` (inside the top-level array):
```json
{
"name": "cross-node-sync",
"cron": "*/7 * * * *",
"prompt": "Run bash skills/cross-node-sync/scripts/setup-rsync-sync.sh to sync memory/ + notes/ with the peer node (requires SUTANDO_SYNC_PEER in .env). Then regenerate MEMORY.md from frontmatter."
}
```
(7 min chosen to avoid `:00/:30` collision with other crons.) Verify with:
```bash
jq '.[].name' skills/schedule-crons/crons.json | grep cross-node-sync || echo "MISSING — add manually"
```
**Manual sync (optional):**
```bash
bash skills/cross-node-sync/scripts/setup-rsync-sync.sh --dry-run # preview
bash skills/cross-node-sync/scripts/setup-rsync-sync.sh # actual run
```
## Conflict handling
Two-direction rsync with `--update` flag: files are copied to the receiver only if newer than the receiver's copy. If both nodes edited the same file since last sync, the node with the later mtime wins. For our scope (mostly append-only memory + notes), conflicts are rare.
If conflicts become a problem, add `--backup --backup-dir=../.sutando-sync-conflicts/` to preserve losers for manual merge. Not needed day one.
## Diagnostics
```bash
# Show what would sync without doing it
bash skills/cross-node-sync/scripts/setup-rsync-sync.sh --dry-run
# Show SSH keypair state + setup instructions
bash skills/cross-node-sync/scripts/setup-rsync-sync.sh --setup
# Run the smoke tests
bash skills/cross-node-sync/scripts/test-setup-rsync-sync.sh
```
## Status
- 2026-04-17: Design approved in #susan. Syncthing prototype replaced with rsync after manifest comparison. `setup-rsync-sync.sh` + `test-setup-rsync-sync.sh` (10/10 pass) committed locally, NOT pushed. Still awaiting Mini's actual file inventory to validate the sync scope closes the gap.
More from sonichi/sutando
- agent-registryLocal Agent Registry — a standalone, dependency-free service that tracks running Claude Code (and other) agent instances. Agents self-register on startup and heartbeat while alive; the Electron overlay and Sutando dashboard read the live list. Use when you need to know which coding agents are running, where, and since when.
- bot2bot-postPost a coordination message from this bot to the shared bot2bot channel, @-mentioning the other Sutando node.
- claude-codexBash wrapper around the local Codex CLI for non-interactive runs from inside Sutando (bridges, cron, scripts). For interactive code review or task hand-off from this Claude Code session, prefer the official `/codex:*` plugin commands; this skill is the file-bridge-compatible path that `discord-bridge.py` invokes for team-tier sandboxed delegation.
- claude-geminiUse the local Gemini CLI from Claude Code with the user's existing Gemini authentication or API configuration. Use for large-context repo scans, multimodal analysis, second-opinion planning, or structured Gemini runs in the current workspace.
- claude-routerChoose between the local Codex CLI and Gemini CLI from Claude Code. Use for automatic model selection when the user wants the best local delegate for code review, repo-wide analysis, planning, or implementation.
- deal-finderScan configured sources (Craigslist now; eBay + Facebook Marketplace planned) for used-item listings matching the owner's criteria. Currently configured for a Mac mini search (M2+, 16GB+, 512GB+, ≤$500, near 94566). Notify owner via SMS + Telegram on a match.
- electron-overlay-dimmingReusable pattern for focus-based auto-dimming of Electron overlay windows — when the app loses focus, all overlay windows fade to a low opacity; when an overlay regains focus, they return to their configured opacity. Use when building always-on-top Electron overlays that should recede while the user works in other apps.
- gemini-ttsRender text to mp3 via Google Gemini Flash TTS. Free-tier eligible (1500 req/day). Use for video narration, demo voiceovers, audio notes. Parallels openai-tts; default for make-viral-video.
- macos-toolsmacOS native integrations: screen capture, calendar, reminders, contacts, email (Mail.app), Spotlight search. Use when the user asks about their screen, schedule, to-do list, contacts, or wants to send email on macOS.
- macos-useGUI control for macOS apps via mediar-ai's mcp-server-macos-use. Click, type, scroll, key-press, open apps — driven by accessibility tree, works in non-interactive Claude Code mode. Use this for any Sutando task that needs to drive another macOS application (Safari, Zoom, Mail, Finder, etc.).