Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

379 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Hydra

A new interface to building — run coding agents over Discord/Slack.

  • Spawn and manage parallel agents from chat
  • tmux in to manage from terminal

Architecture

┌─────────────┐     ┌─────────────┐
│  Discord    │     │   Slack     │
│  Gateway    │     │  Gateway    │
│ (discord.js)│     │(@slack/bolt)│
└──────┬──────┘     └──────┬──────┘
       │                   │
       └────────┬──────────┘
                │
       ┌────────▼────────┐
       │     Daemon      │     Single process per platform.
       │  (daemon.ts)    │     Holds gateway connection,
       │                 │     routes messages, manages
       │  unix socket    │     sessions and access control.
       └────────┬────────┘
                │  newline-delimited JSON
       ┌────────▼────────┐
       │    Bridge       │     Thin MCP relay. One per
       │  (bridge.ts)    │     Claude session. Platform-
       │                 │     agnostic — doesn't import
       │  stdio ↔ socket │     any chat SDK.
       └────────┬────────┘
                │  MCP (stdio)
       ┌────────▼────────┐
       │   Claude Code   │     Full Claude with tools,
       │                 │     memory, file access, etc.
       └─────────────────┘

Key design decisions:

  • One gateway connection per platform. Prevents token race conditions (Discord) and simplifies state.
  • Daemon ↔ Bridge separation. The daemon is long-lived; Claude sessions come and go. The bridge reconnects automatically.
  • Platform selection via env var. Set CHAT_PLATFORM=discord or CHAT_PLATFORM=slack. Default: discord.
  • Simultaneous platforms. Run two daemons on different state dirs for Discord + Slack at the same time.

Prerequisites

  • Buncurl -fsSL https://bun.sh/install | bash
  • tmuxbrew install tmux
  • Claude Codenpm install -g @anthropic-ai/claude-code

Quick Start

# Install dependencies
bun install

# Create .env with your bot token
mkdir -p ~/.claude/channels/discord
cat > ~/.claude/channels/discord/.env << 'EOF'
DISCORD_BOT_TOKEN=your-token-here
EOF

# Install watchdog + verify setup
bun cli/hydra.ts install discord --cwd ~/your/project

# Start
bun cli/hydra.ts up discord

Platform Setup

CLI Reference

All operations go through the hydra CLI (bun cli/hydra.ts or alias to hydra).

Setup

hydra install <platform>       # Generate launchd watchdog, run preflight
hydra uninstall <platform>     # Remove launchd watchdog
hydra preflight <platform>     # Verify deployment is ready

Lifecycle

hydra up <platform>            # Start daemon + byte
hydra down <platform>          # Stop byte + daemon
hydra restart <platform>       # Restart daemon (picks up code changes)

Session Management

hydra spawn <prompt>           # Spawn a new session
hydra list                     # List active sessions
hydra status <name>            # Session details
hydra kill <name>              # Kill a session
hydra peek [name]              # View live sessions (chooser or direct attach)
hydra health                   # Daemon diagnostics
hydra clear-key <key>          # Clear a stuck idempotency key

Options

--daemon <name>                Target a specific daemon (when multiple running)
--json                         Output raw JSON

Configuration

Bot tokens

Set in ~/.claude/channels/<platform>/.env:

# Discord
DISCORD_BOT_TOKEN=MTIz...

# Slack
SLACK_BOT_TOKEN=xoxb-...
SLACK_APP_TOKEN=xapp-...

Access control

access.json controls who can message the bot. Lives in the state dir (~/.claude/channels/discord/ by default).

{
  "dmPolicy": "pairing",          // pairing | allowlist | disabled
  "allowFrom": ["user-id-here"],  // platform user IDs
  "groups": {                      // channel-level policies
    "channel-id": {
      "requireMention": true,
      "allowFrom": [],
      "threadReply": true
    }
  },
  "ackReaction": "👀",
  "replyToMode": "first",         // first | all | off
  "textChunkLimit": 2000,
  "chunkMode": "newline"           // newline | length
}

See ACCESS.md for full reference.

Running both platforms simultaneously

# Install both
hydra install discord
hydra install slack

# Start both
hydra up discord
hydra up slack

Each platform gets its own daemon, state dir, and watchdog. Use different CLAUDE_CONFIG_DIR values for separate logins.

Voice dictation

Hydra can transcribe inbound audio attachments (Discord voice notes, Slack audio clips) to text, so you can dictate prompts to Claude alongside text and images.

Transcription runs in a self-hosted sidecar (transcribe-server/) so audio never leaves your machine. Claude doesn't accept audio natively, so the daemon transcribes first and merges the text into the message as [voice transcript] ...; the original audio file stays available in downloaded_files. Backend by platform:

  • macOS (Apple Silicon)Parakeet-MLX — NVIDIA Parakeet TDT on Apple's MLX runtime. Native, fast (~50× realtime), no GPU/CUDA. Default on macOS.
  • Linux + NVIDIA GPUCanary-Qwen 2.5B via NeMo (top of the Open ASR leaderboard for English accuracy).

It's on by default on the daemon side — whenever a sidecar is reachable, voice notes are transcribed; when it isn't, audio just passes through. So the only thing to set up is the sidecar.

Try it right now (no model install):

./start-transcribe.sh mock     # GPU-free stub, returns a canned transcript

Send a voice note → Claude receives [voice transcript] This is a mock transcription.... (The mock is manual-only: the daemon never auto-starts it, so a leftover mock setting can't silently feed canned text into real messages. hydra down or tmux kill-session -t hydra-transcribe stops it.)

Real transcription (one-time; needs ffmpeg — brew install ffmpeg):

./transcribe-server/setup.sh   # venv + the right backend for your platform

That's it. Once set up, the sidecar starts and stays up with the daemonhydra up, start-daemon.sh, and the watchdog all bring it along; hydra down stops it. One shared tmux session (hydra-transcribe) serves every platform daemon. Run ./start-transcribe.sh to start it by hand. Set HYDRA_TRANSCRIBE_AUTOSTART=0 to keep the daemon from managing it, or =1 to force autostart even before setup (loud failure instead of a quiet skip).

If the sidecar is unreachable, the daemon logs it and delivers the message without a transcript — dictation never blocks normal messages (a down sidecar fails fast; a live-but-slow one delays only the voice message itself, up to HYDRA_TRANSCRIBE_TIMEOUT_MS, 60s default). Disable entirely with HYDRA_TRANSCRIBE_ENABLED=0. Full setup, env vars, and tuning: transcribe-server/README.md.

Tools

Tool Description
reply Send a message. Takes chat_id + text, optionally reply_to for threading and files for attachments (max 10, 25MB each). Auto-chunks long messages.
react Add emoji reaction to a message.
edit_message Edit a previously sent message.
fetch_messages Pull recent history (up to 100).
download_attachment Download attachments from a message to local inbox.
create_thread Create a thread on a message or standalone.
spawn_session Spawn a new Claude session for a topic (main session only).
list_sessions List active spawned sessions (main session only).
kill_session Kill a spawned session (main session only).

Sessions

Spawn isolated Claude sessions from chat:

Command Action
spawn: <topic> Create a new session with a thread
kill: <name> Kill a session by name
/sessions List active sessions
listen / pause Toggle auto-routing in a session thread
help / commands Show all available commands

Sessions get cute names (spark, pixel, nova...) and run in their own tmux sessions. State persists across daemon restarts.

Troubleshooting

Symptoms first — each maps to one root cause. See docs/ONBOARDING_TIPS.md for a full first-machine checklist.

Bot is online but a spawned thread stays empty, or spawn: does nothing. Command interception (spawn:, kill:, /sessions, /health) fires only for senders in the top-level access.json allowFrom — separate from a channel group's allowFrom. A group lets replies through; commands need you in the global allowlist. → /discord:access allow <your-snowflake>. (The daemon logs command-shaped message from non-allowlisted sender … when this happens.)

Byte or spawned session hangs on a theme picker, login, or "trust this folder" screen. The byte is a second, headless Claude in tmux using CLAUDE_CONFIG_DIR (default ~/.claude), so it reads $CLAUDE_CONFIG_DIR/.claude.json — not ~/.claude.json. A fresh config dir triggers first-run gates that block a detached session. → Complete them once via tmux attach -t <platform>-byte, or pre-seed theme, hasCompletedOnboarding, bypassPermissionsModeAccepted, and per-project hasTrustDialogAccepted. hydra preflight now flags this.

Bot connects but never sees inbound (looks healthy, ignores everyone). → Enable Message Content Intent (Developer Portal → Bot → Privileged Gateway Intents), or the bot receives empty message content.

Byte dies instantly, or spawns fail to launch. → Check SPAWN_CWD points at a directory that exists. Inspect ~/hydra-<platform>-byte.log and ~/hydra-<platform>-daemon.log.

usage shows ? instead of context percentage. Claude Code doesn't display context % in the status bar by default. Add a statusLine hook to $CLAUDE_CONFIG_DIR/settings.json:

{
  "statusLine": {
    "type": "command",
    "command": "~/.claude/statusline.sh",
    "refreshInterval": 5
  }
}

Where ~/.claude/statusline.sh extracts the percentage from the JSON passed on stdin:

#!/bin/bash
input=$(cat)
pct=$(echo "$input" | jq -r '.context_window.used_percentage // 0' | cut -d. -f1)
echo "ctx: ${pct}%"

The hook receives context_window.used_percentage, model.id, cost.total_cost_usd, and more. Restart the byte to pick up the change.

Verify inbound end-to-end: grep -E "main bridge connected|running tmux new-session" ~/hydra-<platform>-daemon.log

Files

File Purpose
gateway.ts ChatGateway interface and shared types
discord-gateway.ts Discord implementation (discord.js)
slack-gateway.ts Slack implementation (@slack/bolt Socket Mode)
daemon.ts Platform-agnostic message router and session manager
bridge.ts MCP relay between Claude and daemon (unix socket ↔ stdio)
cli/hydra.ts CLI entry point — routes commands
cli/helpers.ts Config resolution, tmux wrappers, socket comms, compile check
cli/lifecycle.ts Lifecycle commands: up/down/restart/watchdog/preflight/install
cli/peek.ts View live sessions via tmux linked windows with filtered chooser

Logs land at ~/hydra-<platform>-daemon.log and ~/hydra-<platform>-byte.log.

About

A new interface to building — run coding agents over Discord/Slack

Resources

Stars

8 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages