A Python service that bridges Restim e-stim devices to LLMs via MCP, REST APIs, and a web UI — driving TCode axes, reading device state, composing reusable patterns, and reading sensors.
LLM (Claude, etc.)
│ MCP tools
▼
┌─────────────────────────────────────────────┐
│ Synapse │
│ │
│ Pattern System Sensor Inputs REST API │
│ (oscillators, (AS5311, HR, (FastAPI) │
│ sequences) Restim vol) │
│ │
│ TCode Engine @ 50Hz │
└──────────┬──────────────────┬──────────────┘
│ TCP TCode │ HTTP
▼ ▼
Restim device Restim app
(port 12347) (port 12348)
Install or upgrade to the latest release with a single fixed URL — no version number to update:
pip install "joyware-synapse @ https://github.com/joywareapps/synapse/releases/latest/download/joyware_synapse-latest-py3-none-any.whl"To upgrade an existing install:
pip install --upgrade "joyware-synapse @ https://github.com/joywareapps/synapse/releases/latest/download/joyware_synapse-latest-py3-none-any.whl"git clone https://github.com/joywareapps/synapse.git
cd synapse
pip install -e ".[dev]"After either option, the synapse command is available:
synapse --help
synapse init # interactive setup wizard
synapse --config synapse.yamlNote: the PyPI package name is
joyware-synapsebecausesynapseis taken. The CLI command and Python import path are stillsynapse.
The fastest way to get a working config:
synapse initThis probes for a running Restim instance, searches common OS locations for restim.ini, lists detected Ollama/LM Studio models so you can pick one, optionally scans for BLE heart rate devices, and writes a ready-to-use synapse.yaml.
Synapse setup wizard
==========================================
Restim connection
--------------------
✓ Restim found (TCode :12347, REST :12348)
Axis configuration (restim.ini)
--------------------
Found: /home/user/.config/Restim/restim.ini
Use this path? [Y/n]:
Embedded agent (LLM)
--------------------
✓ ollama at http://localhost:11434
Models: llama3.1, phi4, mistral
Tool-capable: llama3.1, phi4, mistral
Default model for agent [llama3.1]:
Sensors (optional)
--------------------
Enable heart rate sensor (BLE)? [y/N]:
Enable AS5311 position sensor? [y/N]:
Write config to [synapse.yaml]:
✓ Config written to synapse.yaml
Run: synapse --config synapse.yaml
To write to a different path: synapse init --output my-setup.yaml
- Restim — the e-stim control app. Must be running before Synapse starts.
- Python 3.10+
- Optional: Ollama or LM Studio for the embedded local LLM agent.
# 1. Install (see Installation section below for all options)
pip install -e . # from source, or download a release wheel
# 2. Generate config interactively
synapse init # probes Restim, finds restim.ini, picks LLM model
# 3. Run
synapse --config synapse.yamlWeb UI: http://localhost:8080
MCP SSE endpoint: http://localhost:8081/sse
synapse.yaml controls all behaviour. Key settings:
restim:
ini_path: "./restim.ini" # path to Restim's exported axis config
instances:
- id: "primary"
host: "localhost"
tcode_port: 12347
rest_port: 12348
engine:
update_rate_hz: 50 # TCode output rate
api:
port: 8080
auth_key: "" # optional REST API key
mcp:
transport: "sse" # "sse" or "stdio"
port: 8081
localhost_only: true # recommended; set false + bearer_token for network access
bearer_token: ""
sensors:
as5311:
enabled: false
url: "ws://localhost:12346/sensors/as5311"
threshold_mm: 0.0
range_mm: 2.0
heart_rate:
enabled: false
device_address: ""
scale_min_bpm: 40
scale_max_bpm: 180
patterns:
directory: "./patterns"
transition:
duck_amount: 1.0 # 1.0 = silence gap between patterns
duck_ms: 200
ramp_ms: 400
sessions:
directory: "./sessions" # funscript output directory
profiles:
directory: "./profiles" # user profile YAML files
agent:
provider: "auto" # "auto" probes Ollama then LM Studio
ollama_url: "http://localhost:11434"
lm_studio_url: "http://localhost:1234"
model: "" # leave blank to auto-pick first tool-capable model
loop_interval_s: 30 # autonomous loop tick interval
loop_mode: "observe" # "observe" (read-only) or "act" (full tool access)
max_tool_calls_per_tick: 2
system_prompt_extra: "" # appended to the system prompt every callAll values can be overridden with environment variables:
SYNAPSE_API_PORT=9090
SYNAPSE_ENGINE_UPDATE_RATE_HZ=100
SYNAPSE_MCP_BEARER_TOKEN=secret
Synapse reads your Restim device config to get the exact axis limits your hardware is calibrated for. In Restim: File → Export settings → restim.ini, then set restim.ini_path in synapse.yaml. Without it, Synapse falls back to Restim's built-in defaults.
External volume: For best results, configure the
VOLUME_EXTERNALTCode axis in Restim's device wizard. This lets Synapse control volume even during non-Synapse Restim playback.
Patterns are YAML files in ./patterns/. Two types:
Leaf patterns — layered oscillators:
name: "gentle-pulse"
base_period: 3.0
layers:
- name: "base"
blend: set
axes:
V0: { waveform: sine, freq_multiple: 1.0, center: 0.4, amplitude: 0.2 }
C0: { waveform: hold, center: 0.5, amplitude: 0.0 }Sequence patterns — ordered steps referencing other patterns:
name: "exploration-program"
loop: true
steps:
- pattern: "gentle-pulse"
duration: 60.0
- pattern: "escalating-pulse"
duration: 30.0Layer blend modes:
set— base oscillator; defines the axis valueadd— adds a delta on top (spikes, intensity overlays)mul— multiplies the accumulated value (tremolo, AM)
See SPEC.md §5 for the full pattern system documentation.
Add to your Claude config (~/.claude.json or project config):
{
"mcpServers": {
"synapse": {
"type": "sse",
"url": "http://localhost:8081/sse"
}
}
}Or for stdio (local only):
{
"mcpServers": {
"synapse": {
"command": "synapse",
"args": ["--mcp-stdio"]
}
}
}See docs/mcp-guide.md for the operational guide the LLM uses when controlling the device.
Synapse can run a local LLM that controls the device autonomously and learns user preferences across sessions.
-
Install Ollama or LM Studio and pull a tool-capable model:
# Ollama ollama pull llama3.1 # or LM Studio — download a model via the UI
-
Synapse auto-detects running providers at startup. Check what's available:
curl http://localhost:8080/api/agent/providers
-
Start a chat session (picks the first tool-capable model automatically):
curl -X POST http://localhost:8080/api/agent/chat \ -H 'Content-Type: application/json' \ -d '{"message": "Start a gentle session", "profile_name": "default"}'
-
Or start the autonomous loop (polls every 30 s, read-only by default):
curl -X POST http://localhost:8080/api/agent/loop/start \ -H 'Content-Type: application/json' \ -d '{"mode": "observe", "profile_name": "default"}'
The agent persists what it learns about user preferences inside the user profile. You can also call these as MCP tools:
| Tool | Purpose |
|---|---|
remember(text, category) |
Save a preference or observation for future sessions |
recall(query) |
List saved memories, optionally filtered |
note_observation(text) |
In-session observation (not persisted to profile) |
forget(memory_id) |
Remove a memory by ID |
Categories: preference, reaction, observation, todo, general.
| Mode | What the agent can do |
|---|---|
observe |
Read-only: check sensors, recall memories, note observations |
act |
Full tool access: play patterns, adjust volume, save memories |
GET /api/agent/providers # list detected LLM providers + models
POST /api/agent/chat # send a message, get response
GET /api/agent/history # conversation history
DELETE /api/agent/history # clear history
POST /api/agent/loop/start # start autonomous loop
POST /api/agent/loop/stop # stop loop
GET /api/agent/loop/status # loop state + tick count
WS /ws/agent # stream tool calls and agent messages
Validate the running configuration — checks Restim connectivity, INI path, LLM providers, and sensor states:
curl http://localhost:8080/api/setup/check{
"ok": false,
"restim": [{"id": "primary", "tcode_reachable": true, "rest_reachable": true, ...}],
"ini": {"path": "./restim.ini", "exists": true},
"llm_providers": [{"name": "ollama", "models": ["llama3.1"], ...}],
"sensors": []
}Find the address of a BLE heart rate monitor to put in synapse.yaml:
curl http://localhost:8080/api/sensors/ble-scan
# [{"name": "Polar H10", "address": "AA:BB:CC:DD:EE:FF"}, ...]Takes ~5 seconds. Requires Bluetooth access (may need elevated permissions on Linux).
- Emergency stop:
POST /api/emergency-stop— zeroes all axes immediately - Volume ramp: Volume changes are rate-limited to 10%/s by default — never jumps
- Axis limits: Values are clamped to
[limit_min, limit_max]from the INI; never exceed hardware limits - Pattern transitions: Duck-and-switch — volume dips to zero before switching patterns, so axis changes are imperceptible
- MCP localhost-only: Default config binds MCP to 127.0.0.1 only
| Sensor | Source | What it provides |
|---|---|---|
| AS5311 | Restim WebSocket | Position (mm), velocity (m/s), normalized 0–1 |
| Heart rate | BLE (GATT 0x2A37) | BPM, normalized 0–1 |
| Restim volume | Restim REST API | UI volume, device volume |
Enable sensors in synapse.yaml and they appear in /api/sensors and MCP get_sensors.
Record a session to funscript files for sharing or replay:
# Via API
curl -X POST /api/sessions/start -d '{"name": "my-session", "instance": "primary"}'
# ... play patterns, interact ...
curl -X POST /api/sessions/stop -d '{"instance": "primary"}'Produces files in ./sessions/:
my-session.V0.funscript
my-session.L0.funscript
my-session.C0.funscript
Compatible with Restim's funscript format — load directly in Restim or share with funscript-tools.
# Install with dev deps
pip install -e ".[dev]"
# Run tests
pytest tests/ -v
# Run with auto-reload
uvicorn synapse.api.app:create_app --reload --factorysrc/synapse/
├── main.py # entry point
├── config.py # config loading + env overrides
├── axes.py # axis definitions + INI-based mapping
├── ini_parser.py # Restim INI parser + file watcher
├── tcode.py # TCode protocol formatting
├── engine.py # 50Hz TCode output loop
├── restim_client.py # Restim HTTP API client
├── tools/
│ └── registry.py # shared tool functions (used by MCP + agent)
├── patterns/
│ ├── models.py # AxisOscillator, Layer, Pattern, SequenceStep
│ ├── store.py # YAML pattern library
│ └── player.py # pattern evaluation + transitions
├── sensors/
│ ├── models.py # SensorReading, configs
│ ├── manager.py # sensor lifecycle
│ ├── as5311.py # AS5311 WebSocket reader
│ └── heart_rate.py # BLE HR reader
├── sessions/
│ ├── models.py # SessionMeta
│ ├── recorder.py # per-tick axis → funscript buffer
│ └── manager.py # session lifecycle
├── profiles/
│ ├── models.py # UserProfile, Memory, ABTestResult
│ └── store.py # YAML profile persistence + memory CRUD
├── agent/
│ ├── llm_client.py # Ollama/LM Studio auto-detection + chat client
│ ├── tool_dispatch.py # OpenAI tool schema builder + dispatcher
│ ├── agent.py # SynapseAgent: system prompt, chat loop
│ └── loop.py # AgentLoop: autonomous observe/act loop
├── api/
│ ├── app.py # FastAPI app factory
│ ├── deps.py # shared AppContext singleton
│ ├── routes_state.py
│ ├── routes_control.py
│ ├── routes_patterns.py
│ ├── routes_sessions.py
│ ├── routes_profiles.py
│ ├── routes_agent.py
│ └── websocket.py # live axis/sensor push + agent stream
└── mcp/
└── server.py # MCP tool wrappers (@mcp.tool)