A central MCP hub for your team: it takes the one genuinely-working part of the Ruflo CLI — its memory layer (real embeddings + HNSW + SQLite + auto-memory) — and makes it shared and networked over HTTP, so Claude Code sessions across a whole team share one persistent memory. A statusline backed by remote data comes along for free. Active memory is local sql.js; PostgreSQL (pgvector) is an optional backup for ruflo ruvector import/export.
What this is, in one line: the valuable, working ~1% of ruflo (memory) — extracted, made team-shareable, with the swarm/neural "theater" left out. See What this actually is before you judge it by ruflo's marketing.
🇷🇺 Russian version: docs/ru/README.md
Guides:
- Use cases — scenarios for personal / team / multi-team use, pattern transfer between instances
- Ruflo usage guide — practical distillation of the official README: what hooks do, which MCP tools you actually need, skills, 3-tier routing
- Swarm management — swarm operations: concept, lifecycle, ruflo@3.5.x quirks
- Multi-project work with ruflo — knowledge transfer, task coordination, claims, hive-mind
Ruflo MCP (stdio) → Express proxy (Streamable HTTP) → port 3000
↕
sql.js (/app/.swarm/memory.db) ← active memory
↕ (optional, manual commands)
PostgreSQL + pgvector (RuVector) ← archive/bridge
Be skeptical of ruflo's marketing — and so are we. Independent audits (and our own teardown) show that most of ruflo's 300+ "MCP tools" are non-functional stubs: agent_spawn just writes a Map entry, swarm_init leaves agentCount: 0, "hive-mind" is a claude subprocess with a role-play prompt, neural_train returns Math.random(). The 100+ "agents" are markdown. As an agent-swarm orchestrator, ruflo is largely theater.
One part is genuinely real, and it's the part we use: the memory layer — real all-MiniLM-L6-v2 embeddings, a real HNSW index, real SQLite persistence, plus the auto-memory hook. ruflo-hub is a thin wrapper that exposes only that layer over HTTP and bridges it into Claude Code. We don't ship the swarm/neural theater as a feature, and we don't load 300 stub tool-definitions into your context as a selling point.
So judge this project as exactly one thing: shared, networked, persistent memory for Claude Code — not a swarm framework.
- The memory layer had a real leak — now fixed upstream. sql.js's WASM in-memory filesystem (MEMFS) hoards one full DB image per database open — a prod instance grew to ~36 GB RSS over six weeks. The root cause (the controller registry replaced controllers without closing the prior instance, leaking its native/WASM resources) is fixed upstream in
@claude-flow/memory@3.0.0-alpha.21(closePriorIfAny), shipped in ruflo 3.14.2 — which this hub ships as of v1.3.0. We keep the RSS watchdog (graceful child respawn pastRUFLO_CHILD_MAX_RSS_MB, default 3000) as a backstop. Full heap-snapshot writeup:ruvnet/ruflo#2432. - Security history (important): ruflo issue #1375 reported a malicious obfuscated preinstall script and a hidden prompt-injection in MCP tool descriptions — in old versions (3.1.0-alpha.55 – 3.5.2). Because this hub installs the package and serves tool descriptions to clients, we re-verified the version we ship (3.14.2): no
preinstallhooks anywhere in the dependency tree;installscripts are standard native builds only (better-sqlite3/argon2/bcrypt) and thepostinstallscripts (agentdb,@claude-flow/cli) are benign — no obfuscation, network, or eval; and all 305 tool descriptions scan clean (no hidden/bidi unicode, no instructions embedded for the assistant). Treated as remediated in current versions — but pin your version and verify yourself. - Backup is WAL-safe by volume.
memory.dbis SQLite in WAL mode; the volumetar(below) capturesmemory.db+-wal+-shmtogether. Copyingmemory.dbalone yieldsdatabase disk image is malformed.
cp .env.example .env
# Edit .env — change POSTGRES_PASSWORD
docker compose up -d.env must contain the line COMPOSE_PROFILES=pg (it's in .env.example by default) — this enables the ruflo-db service.
cp .env.example .env
# Comment out or remove the COMPOSE_PROFILES=pg line
docker compose up -dOnly the ruflo services will start. Memory will be stored in sql.js (/app/.swarm/memory.db), persistent via a volume. Downside: ruflo ruvector import/export commands are unavailable — for transferring patterns between instances see alternatives in docs/use-cases.md.
Server: http://localhost:3000/mcp
The jazzmax/ruflo-hub image can be added to any existing docker-compose.yml.
The active memory store (
/app/.swarm/memory.db, sql.js + HNSW) lives inside the container's filesystem by default. If you rundocker compose pull && up -d(or otherwise recreate the container) without a named volume mounted at/app/.swarm, your entire memory is wiped. PostgreSQL is not a substitute — it's an optional archive used only byruflo ruvector import/export.Always declare the memory volumes before the first
docker compose up -d:services: ruflo: # ... volumes: - ruflo-memory:/app/.swarm # memory.db + WAL + stderr log - ruflo-state:/app/.claude-flow # config + system_health stubs volumes: ruflo-pgdata: ruflo-memory: ruflo-state:All three examples below already include these volumes. If you are migrating an existing deployment that has no volume on
/app/.swarm, see Migrating an existing deployment to volumes below — there is a procedure to copy live data into a new volume without losing it.
If your project doesn't yet have PostgreSQL with pgvector:
services:
# ... your services ...
ruflo:
image: jazzmax/ruflo-hub:latest
restart: unless-stopped
ports:
- "3000:3000"
environment:
RUFLO_PORT: 3000
POSTGRES_HOST: ruflo-db
POSTGRES_PORT: 5432
POSTGRES_DB: ruflo
POSTGRES_USER: ruflo
POSTGRES_PASSWORD: changeme
volumes:
- ruflo-memory:/app/.swarm
- ruflo-state:/app/.claude-flow
depends_on:
ruflo-db:
condition: service_healthy
ruflo-db:
image: pgvector/pgvector:pg17
restart: unless-stopped
environment:
POSTGRES_DB: ruflo
POSTGRES_USER: ruflo
POSTGRES_PASSWORD: changeme
volumes:
- ruflo-pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ruflo"]
interval: 5s
timeout: 3s
retries: 10
volumes:
ruflo-pgdata:
ruflo-memory:
ruflo-state:If PostgreSQL (with pgvector) already exists in the project:
services:
# ... your existing postgres ...
# postgres:
# image: pgvector/pgvector:pg17
# ...
ruflo:
image: jazzmax/ruflo-hub:latest
restart: unless-stopped
ports:
- "3000:3000"
environment:
RUFLO_PORT: 3000
POSTGRES_HOST: postgres # name of your PostgreSQL service
POSTGRES_PORT: 5432
POSTGRES_DB: ruflo # a separate database for ruflo
POSTGRES_USER: ruflo
POSTGRES_PASSWORD: changeme
volumes:
- ruflo-memory:/app/.swarm
- ruflo-state:/app/.claude-flow
depends_on:
postgres:
condition: service_healthy
volumes:
ruflo-memory:
ruflo-state:PostgreSQL must have the pgvector extension. The
pgvector/pgvector:pg17image ships with it. A plainpostgres:17without pgvector will not work.
services:
ruflo:
image: jazzmax/ruflo-hub:latest
restart: unless-stopped
ports:
- "3000:3000"
environment:
RUFLO_PORT: 3000
POSTGRES_HOST: 192.168.1.100 # IP of your server
POSTGRES_PORT: 5432
POSTGRES_DB: ruflo
POSTGRES_USER: ruflo
POSTGRES_PASSWORD: changeme
volumes:
- ruflo-memory:/app/.swarm
- ruflo-state:/app/.claude-flow
volumes:
ruflo-memory:
ruflo-state:The image has a built-in healthcheck. Other services can depend on ruflo:
services:
my-app:
image: my-app:latest
depends_on:
ruflo:
condition: service_healthyservices:
ruflo-team-alpha:
image: jazzmax/ruflo-hub:latest
ports:
- "3001:3001"
environment:
RUFLO_PORT: 3001
POSTGRES_HOST: ruflo-db
POSTGRES_DB: ruflo_alpha
POSTGRES_USER: ruflo
POSTGRES_PASSWORD: changeme
ruflo-team-beta:
image: jazzmax/ruflo-hub:latest
ports:
- "3002:3002"
environment:
RUFLO_PORT: 3002
POSTGRES_HOST: ruflo-db
POSTGRES_DB: ruflo_beta
POSTGRES_USER: ruflo
POSTGRES_PASSWORD: changeme
ruflo-db:
image: pgvector/pgvector:pg17
environment:
POSTGRES_USER: ruflo
POSTGRES_PASSWORD: changeme
volumes:
- ruflo-pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ruflo"]
interval: 5s
timeout: 3s
retries: 10
volumes:
ruflo-pgdata:With a shared PostgreSQL, each instance uses its own database (
ruflo_alpha,ruflo_beta). Databases are created automatically on the first RuVector startup.
A single command from the project root:
curl "http://your-server:3000/setup?token=YOUR_TOKEN&name=ruflo-team" | bashOr with an explicit project path:
curl "http://your-server:3000/setup?token=YOUR_TOKEN&name=ruflo-team" | bash -s /path/to/project| Parameter | Default | Description |
|---|---|---|
token |
— | Bearer token for authorization (the server's MCP_AUTH_TOKEN value) |
name |
ruflo |
MCP server name in .mcp.json (determines the tool prefix: mcp__<name>__*) |
skills |
1 |
Install the skills/agents/commands bundle. 0 (false/no/off) disables it |
- Downloads hooks from the server (
auto-memory-hook.mjs,hook-handler.cjs,statusline.cjs) into.claude/helpers/ - Downloads and unpacks the
skills+agents+commandsbundle into.claude/(existing files are not overwritten — customizations are preserved; disable with?skills=0) - Creates
.claude-flow/ruflo.jsonwith the server URL and token (for the memory bridge) - Creates or amends
.mcp.jsonwith the MCP connection and authorization header - Creates
.claude/settings.jsonwith hook configuration (if the file doesn't already exist) - Verifies connectivity to the server
# Minimal (no auth, default name "ruflo")
curl http://192.168.1.100:3000/setup | bash
# With authorization
curl "http://192.168.1.100:3000/setup?token=572fd23e-ae2e-4e3b-9ea5-59e7a84c09a7" | bash
# Custom name per team
curl "http://192.168.1.100:3001/setup?token=TOKEN_A&name=ruflo-alpha" | bash
curl "http://192.168.1.100:3002/setup?token=TOKEN_B&name=ruflo-beta" | bash
# MCP bridge only, no skills/agents (legacy behavior)
curl "http://192.168.1.100:3000/setup?token=TOKEN&skills=0" | bashIf ruflo-hub is running on your laptop/desktop, don't pin to an IP — it changes when you switch Wi-Fi/VPN. Use your machine's mDNS name (macOS and most Linux distros support .local out of the box via Bonjour/Avahi):
# macOS/Linux — hostname substitution
curl "http://$(hostname):3201/setup?token=TOKEN" | bash
# Explicit:
curl "http://MacBook-Pro-3.local:3201/setup?token=TOKEN" | bashThe same rule applies to .claude-flow/ruflo.json and .mcp.json — prefer storing http://MacBook-Pro-3.local:3201/mcp instead of an IP. Then the client keeps working across any network change.
When .local is NOT suitable:
- Clients outside the local network (another team's VPN, a remote VPS) —
hostname.localwon't resolve for them. You'll need public DNS (ruflo.mycompany.com) or a tunnel (Tailscale/Cloudflare Tunnel). - Corporate networks with restrictive policies — Bonjour/mDNS may be disabled by IT. Check with
ping $(hostname)from a client machine.
When new skills/agents/commands appear in the hub (or they get updated in the Docker image), you can update only the bundle — without re-running /setup, which would overwrite your configs:
# Current directory
curl http://your-server:3000/update-bundle | bash
# With an explicit project path
curl http://your-server:3000/update-bundle | bash -s /path/to/project
# Force-overwrite existing files
curl "http://your-server:3000/update-bundle?force=1" | bashBy default tar -xzkf (the -k flag) doesn't touch existing files — it only adds missing ones. With ?force=1 it's a full overwrite. Unlike /setup, this endpoint does not create .claude-flow/ruflo.json, .mcp.json, or settings.json — bundle only.
Don't forget to restart Claude Code in the project — skills load at SessionStart.
If you only need MCP without hooks or the memory bridge:
Claude Code CLI:
claude mcp add --transport http \
-H "Authorization: Bearer YOUR_TOKEN" \
ruflo-team http://your-server:3000/mcpClaude Desktop / VS Code / Cursor / JetBrains (.mcp.json):
{
"mcpServers": {
"ruflo-team": {
"type": "http",
"url": "http://your-server:3000/mcp",
"headers": {
"Authorization": "Bearer YOUR_TOKEN"
}
}
}
}An MCP connection exposes ruflo's tool surface, but in practice the tools worth calling are the memory ones (
memory_store,memory_search,memory_list, …) — the swarm/neural/agent tools are mostly stubs (see What this actually is). Automatic setup via/setupadditionally wires the memory bridge — pattern synchronization between Claude Code sessions.
| Method | URL | Description |
|---|---|---|
| POST | /mcp |
JSON-RPC proxy to ruflo MCP (main endpoint) |
| GET / DELETE | /mcp |
Returns 405 Method Not Allowed (MCP is POST-only) |
| GET | /health |
Server status ({"status":"ok","tools":305, ...}) — includes RSS-watchdog stats (childRssMB, childRespawnCount) |
| GET | /stats |
Statusline summary: vectors, namespaces, dbSizeKB, swarm state, intelligence score |
| GET | /setup |
Shell script for automatic project setup |
| GET | /update-bundle |
Shell script for bundle-only updates (skills+agents+commands) |
| GET | /bundle.tar.gz |
Tar.gz archive of the bundle (used by /setup and /update-bundle) |
| GET | /templates |
List of available templates |
| GET | /templates/:name |
Download a specific template |
| GET | /.well-known/oauth-authorization-server |
OAuth discovery stub (returns 404 so clients fall back to no-auth) |
| GET | /.well-known/oauth-protected-resource |
OAuth discovery stub (returns 404) |
| POST | /register |
OAuth dynamic-client stub (returns 404) |
# Tool invocation
curl -X POST http://your-server:3000/mcp \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"tools/call","params":{"name":"memory_store","arguments":{"key":"my-pattern","value":"pattern content","namespace":"my-project"}},"id":1}'
# Memory search
curl -X POST http://your-server:3000/mcp \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"tools/call","params":{"name":"memory_search","arguments":{"query":"my search"}},"id":1}'
# List tools
curl -X POST http://your-server:3000/mcp \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"tools/list","id":1}'If MCP_AUTH_TOKEN is set, all requests to /mcp require the header:
Authorization: Bearer <token>
The /health, /stats, /setup, /update-bundle, /bundle.tar.gz, /templates, and the OAuth discovery stubs are available without authorization.
The bridge automatically synchronizes knowledge between Claude Code sessions and the ruflo server.
┌─────────────────────┐ ┌──────────────┐
│ Claude Code │ │ Ruflo Server │
│ │ HTTP │ │
│ SessionStart ──────┼────────→│ memory_list │
│ (import) │←────────┼ memory_get │
│ │ │ │
│ Stop ──────────────┼────────→│ memory_store│
│ (sync) │ │ │
└─────────────────────┘ └──────────────┘
At session start (auto-memory-hook.mjs import):
- loads project patterns from the namespace named after the project directory
- loads shared patterns (common to all projects)
- emits them into the Claude Code session context
At stop (auto-memory-hook.mjs sync):
- reads Claude auto-memory files (
~/.claude/projects/.../memory/*.md) - pushes feedback and project entries to ruflo-hub
- available in the next session and from other projects
# Bridge status
node .claude/helpers/auto-memory-hook.mjs status
# Force sync
node .claude/helpers/auto-memory-hook.mjs sync
# Load patterns
node .claude/helpers/auto-memory-hook.mjs importFiles in templates/ are served via /templates/:name and used by the /setup script:
| File | Purpose |
|---|---|
auto-memory-hook.mjs |
Memory bridge — HTTP client for ruflo-hub |
hook-handler.cjs |
Claude Code hook handler (routing, status, edit tracking) |
statusline.cjs |
Statusline generator (git, model, context, cost, swarm) |
settings.json |
Template for .claude/settings.json with hook configuration |
auto-memory-hook.mjs resolves the server URL in this priority order:
- The
RUFLO_URLenvironment variable - The
.claude-flow/ruflo.jsonfile (created by/setup) - Auto-discovery from a sibling
ruflo-hub/project - Fallback:
http://localhost:3000/mcp
| Variable | Default | Description |
|---|---|---|
RUFLO_PORT |
3000 |
MCP server port |
POSTGRES_HOST |
localhost |
PostgreSQL host |
POSTGRES_PORT |
5432 |
PostgreSQL port |
POSTGRES_DB |
ruflo |
Database name |
POSTGRES_USER |
ruflo |
User |
POSTGRES_PASSWORD |
ruflo |
Password (change it!) |
MCP_AUTH_TOKEN |
— | Bearer token for authorization (if empty — no auth) |
The active memory lives in /app/.swarm/memory.db inside the ruflo container, mounted from the <service>-memory volume. That volume is what you back up — pg_dump of the PostgreSQL database is not enough, because the regular memory_store / memory_search tools write into sql.js, not into PostgreSQL. The claude_flow.* tables in PostgreSQL are used only by the optional ruflo ruvector import/export flow and may legitimately be empty.
# Backup the memory volume (active memory + HNSW indexes)
docker run --rm -v ruflo-server_ruflo-memory:/d -v "$PWD":/b alpine \
tar czf /b/ruflo-memory.tgz -C /d .
# Restore
docker run --rm -v ruflo-server_ruflo-memory:/d -v "$PWD":/b alpine \
sh -c "rm -rf /d/* && tar xzf /b/ruflo-memory.tgz -C /d"
# Optional — PostgreSQL dump (only if you actively use ruvector import/export)
docker exec <postgres-container> pg_dump -U ruflo ruflo > backup.sql
cat backup.sql | docker exec -i <postgres-container> psql -U ruflo rufloIf you deployed ruflo-hub before adding the ruflo-memory / ruflo-state volumes, your /app/.swarm/memory.db lives inside the container's R/W layer and the next docker compose up -d will erase it. Migrate live data into a named volume before recreating the container.
Run this on the host where ruflo-hub lives. Replace <service> with your project name (e.g. ruflo-hub) — Compose prefixes volume names with it.
cd /path/to/ruflo-hub
SNAP=/tmp/ruflo-snap-$(date +%s)
mkdir -p $SNAP/swarm $SNAP/claude-flow
# 1. Snapshot live data while the container is still running (small dataset)
docker cp ruflo:/app/.swarm/. $SNAP/swarm/
docker cp ruflo:/app/.claude-flow/. $SNAP/claude-flow/
# 2. Graceful stop so SQLite checkpoints the WAL, then take a final snapshot
docker compose stop ruflo
docker cp ruflo:/app/.swarm/. $SNAP/swarm/
docker cp ruflo:/app/.claude-flow/. $SNAP/claude-flow/
# 3. Create the named volumes and load the snapshot
docker volume create <service>_ruflo-memory
docker volume create <service>_ruflo-state
docker run --rm -v <service>_ruflo-memory:/dst -v $SNAP/swarm:/src alpine \
sh -c "rm -rf /dst/* && cp -a /src/. /dst/"
docker run --rm -v <service>_ruflo-state:/dst -v $SNAP/claude-flow:/src alpine \
sh -c "rm -rf /dst/* && cp -a /src/. /dst/"
# 4. Add the volume mappings to docker-compose.yml (see the warning above for the
# exact YAML), then recreate the container
docker compose up -d ruflo
# 5. Verify
docker inspect ruflo --format '{{range .Mounts}}{{.Type}} {{.Source}} -> {{.Destination}}{{println}}{{end}}'
curl -s http://localhost:3000/health # state should be "ready"
# 6. Keep $SNAP for a few days as a safety net before deletingA few things that look broken but are normal — knowing them up front avoids unnecessary "fixes" that can cause data loss:
system_healthmay reportscore: 20/100, unhealthy. The check looks for marker files at default paths (./.claude-flow/memory/store.json,./.claude-flow/config.json) and ignores the real backend at/app/.swarm/memory.db. The entrypoint creates empty marker files on startup so other Claude instances don't suggest "run memory init" — this is cosmetic only. Usememory_statsto see real storage.- Don't run
ruflo initorruflo memory initinside the container. They would create a second store at the default path and split your data between two places. The container is preconfigured; the active store at/app/.swarm/memory.dbalready works. claude_flow.embeddingsin PostgreSQL is normally empty (0 rows). That table is for the RuVector backend (1536-dim), but the regular memory tools write 768-dim ONNX embeddings into sql.js. The two backends are independent. PostgreSQL is touched only byruflo ruvector import/export.memory_bridge_status: not-syncedis fine if no project under~/.claude/projects/*/memory/MEMORY.mdexists yet. The bridge has nothing to mirror; directmemory_storecalls still work.- High CPU at idle comes from background controllers (consolidation, causal graph, nightly learner). They run regardless of whether you've fed them data.
The ruflo package is pinned in the image at build time.
Image rebuild (recommended):
# Locally
docker compose build --no-cache
docker compose up -d
# For Docker Hub
docker build --no-cache -t jazzmax/ruflo-hub:latest .
docker push jazzmax/ruflo-hub:latestUpdate inside the container (fast, doesn't survive a restart):
docker exec <ruflo-container> npm install -g ruflo@latest
docker restart <ruflo-container>docker pull jazzmax/ruflo-hub:latest # rolling: every push to main
docker pull jazzmax/ruflo-hub:1 # major: latest 1.x.x
docker pull jazzmax/ruflo-hub:1.1 # minor: latest 1.1.x
docker pull jazzmax/ruflo-hub:1.1.0 # exact pin
docker pull jazzmax/ruflo-hub:<git-sha> # any commit, for trace/rollbackVersioned tags are published when you push a vX.Y.Z git tag (see Release process). :latest only moves on main pushes / weekly rebuilds — never on tag pushes — so pinning to :1/:1.1 stays stable through patch releases.
Versions follow SemVer. Notable changes are recorded in CHANGELOG.md.
# 1. Update CHANGELOG.md and bump package.json version (e.g. 1.1.0 → 1.2.0)
# 2. Commit
git commit -am "chore: release v1.2.0"
git push origin main
# 3. Tag the release commit and push
git tag v1.2.0
git push origin v1.2.0The v*.*.* tag triggers GitHub Actions to publish jazzmax/ruflo-hub:1.2.0, :1.2, :1, and :<sha> to Docker Hub.
git clone https://github.com/jazz-max/ruflo-hub.git
cd ruflo-hub
docker build -t jazzmax/ruflo-hub:latest .
docker push jazzmax/ruflo-hub:latest