@dsiloed/silo-link
v1.11.0
Published
Claude Code Remote Bridge — connects Claude Code sessions to DSiloed via MCP + ActionCable
Maintainers
Readme
SiloLink — Claude Code Remote Bridge
SiloLink is a local daemon that connects Claude Code sessions to DSiloed conversations. It provides an MCP server that Claude Code connects to, and maintains a real-time WebSocket connection to DSiloed via ActionCable. Messages flow bidirectionally — you can launch, monitor, interact with, and command Claude Code sessions from the DSiloed web UI, Slack, SMS, or Discord.
See it in action — Interactive Demo | Documentation
┌──────────────────────┐
│ DSiloed Server │
│ │
Claude Code (tmux) ──MCP──► │ │
Claude Code (tmux) ──MCP──► SiloLink ──WebSocket──► ActionCable
Claude Code (tmux) ──MCP──► :3579 ──REST API──► ├── ConversationChannel
│ ├── ControlChannel
│ Claude Launcher │
│ Message Queue │ Web UI / Slack
│ Session Manager │ Discord / SMS
└──────────────────────┘Features
- Launch Claude sessions from the web UI — click "+" in the SiloLinks section to spawn a new session
- Auto-launch — send a message to any SiloLink conversation and Claude starts automatically
- Multi-session — each conversation gets its own Claude Code tmux session
- Message buffering — messages that arrive before Claude registers are queued and delivered
- Status notifications — "Starting session..." and "Session ready!" feedback in the conversation
- Agent Dashboard — active sessions appear in Mission Control with real-time status
- Control Channel — launch/stop/status commands via ActionCable from the UI
- Channel linking — command Claude from Slack, Discord, Teams, or SMS
- Session continuity —
remote_load_contextrestores prior conversation history on session restart - Workspace isolation — git worktrees give each session its own branch and working directory
- File claim tracking — advisory soft-locks with cross-session conflict notifications
- Provider abstraction —
AgentLauncherinterface supports Claude, Gemini, and OpenAI adapters
Installation
npm install -g @dsiloed/silo-linkThis installs the silolink CLI globally.
Configuration
silolink configThe wizard first asks whether the daemon is running for a user or an agent and then collects only what's needed. Your answer is persisted as identity_type in the config and changes how the spawned Claude Code session is launched (see below).
- User (default): prompts for host, tenant, login (email or username) and password. SiloLink calls
POST /api/v1/users/login, captures the returned JWT, and stores it in~/.silolink/config.json(mode0600). Your password is never written to disk — only the JWT. - Agent: prompts for host, tenant, agent name (the
subclaim), and the JWT to paste in. Generate the token from the LLM Manager UI: open the agent's edit dialog, expand SiloLink Authentication, and click Generate SiloLink Token. The agent path never needs Sorcery credentials, since agents don't have a human login.
The token is long-lived. If the server ever rejects it (password change, tenant key rotation, agent rename), re-run silolink config to refresh it.
How identity_type changes session launches
The choice you make in the wizard controls where the spawned Claude Code session starts (which is the same dir CC reads .mcp.json from):
identity_type: "user"— single-env remote-bridge use case. CC launches inprojects_path(or inrepo_pathif a launch command supplies one). CC inherits anyCLAUDE.mdfrom your projects dir, and you typically runsilolink setup-claude-mdonce to inject the SiloLink protocol section into it. There's only one writer of the.mcp.jsoninprojects_path, so collisions are not a concern.identity_type: "agent"— multi-agent SDLC orchestration where several silolink envs (e.g.lead-tester,vixen) run on the same laptop. Each env gets its own per-env staging dir at~/.silolink/staging/<env>/, and CC launches there so its.mcp.json(which includes the env-specific JWT) is never shared with a sibling env. A barerepo_pathin a launch command is treated as informational; the agent's runbook tells the spawned CC where tocdfor repo work. When a launch supplies bothrepo_pathandbranch, silolink creates a per-ticket worktree under~/.silolink/worktrees/<repo>/<branch>/— also identity-isolated.
Worktree-mode (repo_path + branch) is identity-safe in either mode and works the same way for both.
If you ever migrate an env from user to agent (e.g. a personal config that's grown into an agent setup), re-run silolink config --env <name> and answer agent — the daemon will pick up the new identity_type on next restart.
Non-interactive setup
For automated installs (e.g. a prompt generated by the DSiloed UI for Claude Code to execute), skip the wizard:
silolink config --non-interactive \
--host https://www.dsiloed.com \
--tenant <tenant_id> \
--user-sub <email-or-username> \
--jwt <pre-issued-jwt> \
[--mcp-port 3579] \
[--projects-path /home/user/projects] \
[--env dev]All four of --host, --tenant, --user-sub, --jwt are required when --non-interactive is set. The JWT is written to ~/.silolink/config.json exactly as supplied — generate it via the same flow used by the user's MCP config dialog (or the agent's "Generate SiloLink Token" button).
~/.silolink/config.json schema:
| Field | Description | Default |
|-------|-------------|---------|
| host | DSiloed server URL | https://www.dsiloed.com |
| tenant_id | Tenant enterprise identifier | harmoniq |
| user_sub | Your username or email | — |
| jwt | JWT obtained at silolink config time (do not edit by hand) | — |
| mcp_port | Local port for the MCP server | 3579 |
| reconnect_interval_ms | Base reconnect delay | 5000 |
| max_reconnect_attempts | Max reconnect tries | 20 |
| agent_provider | AI provider: claude, gemini, openai | claude |
| claude_command | Path to Claude CLI binary | claude |
| claude_working_directory | Working directory for spawned sessions | — |
| claude_auto_respawn | Auto-respawn on idle/exit | false |
| claude_idle_timeout_ms | Idle threshold before respawn | 30000 |
| claude_session_prompt | Default prompt for spawned sessions | Register with SiloLink... |
Example config file (after running silolink config):
{
"host": "https://www.dsiloed.com",
"tenant_id": "portablemind",
"user_sub": "[email protected]",
"jwt": "eyJhbGciOiJIUzI1NiJ9...",
"mcp_port": 3579,
"claude_command": "/home/user/.local/bin/claude",
"claude_working_directory": "/home/user/projects",
"claude_auto_respawn": true,
"claude_idle_timeout_ms": 30000
}Usage
Starting SiloLink
# Foreground (see logs in terminal)
silolink start
# Background (daemon mode)
silolink start --daemonManaging the Daemon
silolink status # Check connection state and active sessions
silolink sessions # List active sessions
silolink stop # Stop daemon (kills all Claude tmux sessions)Launching Claude Sessions
# Launch from CLI
silolink launch
silolink launch -p "Work on the auth refactor"
# Launch from the DSiloed web UI
# Click "+" in the SiloLinks section of TeamChatConnecting Claude Code (Manual)
Add SiloLink as an MCP server in your Claude Code settings (.claude.json or project .mcp.json):
{
"mcpServers": {
"silolink": {
"command": "npx",
"args": ["mcp-remote", "http://localhost:3579/mcp"]
}
}
}Claude Session Launcher
SiloLink spawns and manages Claude Code sessions using tmux:
On-Demand (from DSiloed UI)
- Click + in the SiloLinks section of TeamChat
- Enter a session name and optional prompt → Launch Session
- Conversation appears immediately — "Starting session..." shows while Claude initializes
- Claude sends "Session ready!" when the poll loop begins (~20-30 seconds)
Auto-Launch (on inbound message)
When a message arrives on a SiloLink conversation with no active session:
- SiloLink posts "Restarting session..." immediately
- Spawns Claude Code in a new tmux session
- The triggering message is buffered and delivered once Claude registers
- Claude sends "Session ready" and enters the poll loop
Multi-Session
Each conversation gets its own tmux session. Multiple sessions run simultaneously.
# List active tmux sessions
tmux list-sessions | grep silolink
# Attach to watch Claude work
tmux attach -t silolink-claude-1774363497579
# Detach: Ctrl+B, DSession Lifecycle
- Launch → SiloLink posts "Starting session..." → spawns tmux → Claude registers → "Session ready!"
- Messages → user sends message → SiloLink enqueues → Claude polls and responds
- Delete → gear icon → "Delete SiloLink" stops the Claude session and deletes the conversation
- Shutdown →
silolink stopkills all tmux sessions and completes agent sessions on DSiloed
MCP Tools
| Tool | Description | Blocking |
|------|-------------|----------|
| remote_register | Register session, create/attach conversation | No |
| remote_load_context | Load prior conversation history for session continuity | No |
| remote_notify | Fire-and-forget message | No |
| remote_ask | Post question, wait for reply | Yes |
| remote_poll | Non-blocking check for next message (recommended) | No |
| remote_check_messages | Non-blocking check for ALL pending messages | No |
| remote_wait_for_command | Block until next message (prefer remote_poll) | Yes |
| remote_sessions | List active sessions | No |
| remote_unregister | Unregister and clean up | No |
| remote_workspace_create | Create an isolated git worktree for this session | No |
| remote_workspace_claim | Claim files (advisory soft-lock) with conflict detection | No |
| remote_workspace_check | Check file claims across all sessions | No |
| remote_workspace_merge | Merge worktree branch back and clean up | No |
| remote_workspace_list | List all active workspaces | No |
remote_register
Input: { session_name?: "my project", conversation_id?: 453 }
Output: { session_id, conversation_id, conversation_url }When conversation_id is provided, attaches to that existing conversation (used by auto-launcher).
remote_load_context
Input: { conversation_id?: 109, limit?: 50 }
Output: { success: true, conversation_id, resume_id, message_count, history: [...] }Fetches prior conversation history and the last claude_resume_id from agent session metadata. Call after remote_register when attaching to an existing conversation to restore session continuity.
remote_poll (Recommended)
Input: {}
Output: { success: true, message: "...", sender_name: "..." }
— or —
{ success: true, pending: true }Non-blocking. Safer than remote_wait_for_command — can't lose messages on cancellation.
remote_notify
Input: { message: "Build completed successfully" }
Output: { success: true, message_id }remote_ask
Input: { question: "Should I proceed?", timeout?: 300 }
Output: { response: "Yes", sender_name: "rholmes" }Workspace Isolation
Multiple sessions can work on the same repo without conflicts using git worktrees:
Session A: feature/auth ──► ~/.silolink/worktrees/myapp/feature/auth/
Session B: feature/api ──► ~/.silolink/worktrees/myapp/feature/api/Creating a Workspace
// Agent creates a worktree on session start
remote_workspace_create({ repo: "/home/user/myapp", branch: "feature/auth" })
// → { worktree_path: "~/.silolink/worktrees/myapp/feature/auth", branch: "feature/auth" }
// Claim files before editing (advisory — warns on conflicts, doesn't block)
remote_workspace_claim({ files: ["src/auth.ts", "src/middleware.ts"] })
// → { claimed: [...], conflicts: [] }
// Check what other sessions are working on
remote_workspace_check({})
// → { all_claims: [{ session_id: "session-B", files: ["src/api.ts"] }] }
// When done, merge back
remote_workspace_merge({})
// → { success: true, merged_branch: "feature/auth" }Cross-Session Conflict Notifications
When session A claims files that session B already owns, both sessions receive automatic notifications via their message queues and DSiloed conversations.
Launcher Integration
The control channel launch command supports workspace params:
{ "type": "launch", "conversation_id": 123, "repo_path": "/home/user/myapp", "branch": "feature/auth" }Provider Selection
Set agent_provider in ~/.silolink/config.json:
{ "agent_provider": "claude" }Supported providers: claude (default, fully implemented), gemini (stub), openai (stub).
Control Channel API
SiloLink subscribes to a tenant-scoped ActionCable control channel on startup. The DSiloed UI sends commands via REST:
| Endpoint | Method | Description |
|----------|--------|-------------|
| /api/v1/silolink/launch | POST | Launch new session (creates conversation) |
| /api/v1/silolink/stop | POST | Stop session (by conversation_id or all) |
| /api/v1/silolink/status | GET | Request SiloLink status |
# Launch
curl -X POST /api/v1/silolink/launch \
-d '{"name": "my-feature", "prompt": "Work on auth"}'
# Response
{"success": true, "conversation_id": 458, "message": "Launch command sent"}CLAUDE.md Integration
Add to your project's CLAUDE.md:
## Remote Communication (SiloLink)
When the SiloLink MCP server is connected:
1. Register with `remote_register({ session_name: "<name>" })`.
If a conversation_id is provided, pass it too.
2. **Session Continuity**: If you registered with an existing conversation_id,
call `remote_load_context()` to load prior conversation history.
3. ALL communication MUST go through SiloLink — never write to terminal.
4. Send progress updates with `remote_notify()`.
5. When idle, use the poll loop:
- Call `remote_poll()` — returns instantly
- If `{ pending: true }`: sleep 3s, poll again
- If message received: process it, notify result, resume
6. Use `remote_ask()` for questions needing immediate reply.Poll Loop Pattern
// Register (use conversation_id if provided in launch prompt)
remote_register({ session_name: "portablemind", conversation_id: 123 })
// If attaching to existing conversation, load prior context
context = remote_load_context()
// Review context.history to understand what was discussed
// Poll loop
while (idle) {
result = remote_poll()
if (result.pending) { sleep(3s); continue }
handle(result.message)
remote_notify({ message: "Done: ..." })
}Agent Sessions
Active SiloLink sessions appear in the Mission Control Agent Dashboard with:
- Session name (matches conversation name)
- Status: idle, working, waiting for human, error, completed
- Heartbeats on every tool call
- Conversation link for quick navigation
Permissions
SiloLink-spawned sessions run headlessly — no human at the keyboard. Permission prompts hang the pane, so the launcher pre-clears two gates so --dangerously-skip-permissions actually takes effect on every command (the flag alone is not enough on CC 2.x).
Pre-cleared automatically on each launch:
- Workspace trust dialog (per cwd) — written to
~/.claude.jsonunderprojects.<cwd>.hasTrustDialogAccepted. - Bypass-permissions consent (user scope) — written to
~/.claude/settings.jsonas a top-levelskipDangerousModePermissionPrompt: true.
The second one is the gotcha: without that consent on file, CC still re-prompts on the "dangerous pattern" class (rm -rf, sudo, ...) even when launched with --dangerously-skip-permissions and even when the in-session footer shows ⏵⏵ bypass permissions on. The footer just reflects mode; the consent is the gate. CC's internal check (Sd() in the binary) reads this key from userSettings / localSettings / flagSettings / policySettings — all at the top level of the settings object, not nested under permissions. The schemastore claude-code-settings schema doesn't document this key as of CC 2.1.x; the binary is authoritative.
Both writes are idempotent and only touch the specific keys involved — other settings (theme, plugins, permissions block, project MCP enablement, etc.) are preserved by a shallow merge.
Manual verification:
# Should print: True
python3 -c 'import json; print(json.load(open("'$HOME'/.claude/settings.json")).get("skipDangerousModePermissionPrompt"))'If the value is missing or false, restart silolink — ClaudeLauncher.ensureBypassPermissionsConsent() runs on each session spawn and will write it.
What still prompts: CC has a hardcoded circuit breaker for catastrophic patterns (rm -rf /, rm -rf ~, etc.) that re-prompts regardless of settings. Those need a human response — see the send_keys control command for driving the prompt from the web UI.
For sessions launched outside silolink — if you're running claude by hand in a terminal:
- Option A — Skip all prompts:
claude --dangerously-skip-permissions. Requires the user-scope consent above (runsilolink startonce and it's set, or write the file manually). - Option B — Allow SiloLink tools only:
{ "permissions": { "allow": ["mcp__silolink__*"] } }in.claude/settings.json.
Technical Details
- Authentication: HS256 JWTs (24h validity, auto-refresh every 12h)
- Reconnection: Exponential backoff (1s → 60s cap)
- Echo Prevention: Tracked outbound message IDs + prefix matching
- Session Cleanup: Idle sessions cleaned after 1 hour, agent sessions completed on DSiloed
- Multi-Session: Multiple tmux sessions, one per conversation
- Message Buffering: Pre-registration messages delivered on session register
- API Timeouts: 30-second abort on all DSiloed API calls
- Map Validation: Session manager validates 3-map consistency every 5 minutes
- Shutdown: Kills all Claude tmux sessions first, then completes agent sessions
Health Check
curl http://localhost:3579/health
# { "status": "ok", "sessions": 2, "cable": "connected" }Development
pnpm dev start # Dev mode (no build)
pnpm exec tsc --noEmit # Type check
pnpm build # Build
pnpm test # TestsProject Structure
src/
index.ts # CLI entry point
cli/
commands.ts # CLI commands (start, stop, status, launch, config)
daemon.ts # PID file management
config/
config-manager.ts # ~/.silolink/config.json management
jwt-generator.ts # HS256 JWT generation + auto-refresh
core/
bridge.ts # Main orchestrator — startup, shutdown, control channel
session-manager.ts # Session registry with 3-map lookup + validation
message-queue.ts # Per-session inbound queue with acknowledgment protocol
agent-launcher.ts # AgentLauncher interface and types
claude-launcher.ts # Claude Code launcher (tmux-based, implements AgentLauncher)
gemini-launcher.ts # Gemini launcher stub (implements AgentLauncher)
openai-launcher.ts # OpenAI launcher stub (implements AgentLauncher)
launcher-factory.ts # Provider selection factory
workspace-manager.ts # Git worktree lifecycle, file claims, conflict detection
cable/
action-cable-client.ts # ActionCable WebSocket client with auto-reconnect
subscription-manager.ts # Conversation subscriptions, echo prevention, message buffering
api/
dsiloed-client.ts # REST client for DSiloed API (30s timeouts)
mcp/
server.ts # MCP server (Streamable HTTP on Express)
tools/
register-tools.ts # All 14 MCP tool definitions
types/
index.ts # Shared TypeScript interfacesLicense
MIT
