Installation Guide

July 12, 2026 · View on GitHub

Requirements

  1. Python 3.11+
  2. pipx (recommended) or pip
  3. At least one authenticated provider CLI:
    • Claude Code CLI: npm install -g @anthropic-ai/claude-code && claude auth
    • Codex CLI: npm install -g @openai/codex && codex auth
    • Gemini CLI: npm install -g @google/gemini-cli and authenticate in gemini
  4. One of these messaging transports:
    • Telegram: Bot token from @BotFather + user ID from @userinfobot
    • Matrix: install Matrix support first (ductor install matrix or pip install \"ductor[matrix]\"), then provide homeserver URL, user ID, and password/access token
    • Slack: install Slack support first (pip install "ductor[slack]"), then create a Slack app with Socket Mode, the bot/app scopes below, and provide bot/app tokens plus Slack member/channel IDs for the allowlist
  5. Docker optional (recommended for sandboxing)

Install

pipx install ductor

pip

pip install ductor

from source

git clone https://github.com/PleasePrompto/ductor.git
cd ductor
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"

First run

ductor

On first run, onboarding does:

  • checks Claude/Codex/Gemini/Antigravity auth status,
  • asks which transport to use (Telegram, Matrix, or Slack),
  • collects transport credentials,
  • asks timezone,
  • offers Docker sandboxing (with optional AI/ML package selection),
  • offers service install,
  • writes config and seeds ~/.ductor/.

Multiple transports can run in parallel (e.g. Telegram + Slack simultaneously). After initial setup, configure the transports array in config.json. See config.md for details.

If service install succeeds, onboarding returns without starting foreground bot.

Slack setup

ductor's Slack transport follows the same modern pattern Hermes uses: Slack Bolt + Socket Mode. That means no public webhook URL is needed.

1. Install the Slack extra

pip install "ductor[slack]"

2. Create a Slack app

  1. Go to https://api.slack.com/apps
  2. Click Create New App
  3. Choose From scratch
  4. Pick a name and workspace

3. Add bot token scopes

In OAuth & Permissions → Scopes → Bot Token Scopes, add:

ScopeRequiredPurpose
chat:writeyessend bot replies
app_mentions:readyesdetect @bot in channels
channels:historyyesread public-channel messages and thread history
channels:readyesresolve public channel metadata
groups:historyrecommendedread private-channel messages and thread history
im:historyyesread DMs
im:readyesaccess DM metadata
im:writeyesopen/manage DMs
users:readyesresolve Slack user names
files:readyesdownload attached files
files:writeyesupload files back to Slack
groups:readoptionalresolve private-channel metadata

Without channels:history / message.channels, the bot will work in DMs but not in public channels. Without groups:history / message.groups, it will not work in private channels.

4. Enable Socket Mode

In Settings → Socket Mode:

  1. Turn Socket Mode on
  2. Create an app-level token
  3. Grant it the connections:write scope
  4. Copy the resulting xapp-... token

This token goes into slack.app_token.

5. Subscribe to Slack events

In Event Subscriptions → Subscribe to bot events, add:

EventRequiredPurpose
message.imyesdirect messages
message.channelsyespublic-channel messages
message.groupsrecommendedprivate-channel messages
app_mentionyesmention handling in channels

6. Enable direct messages

In App Home:

  1. Turn on Messages Tab
  2. Enable Allow users to send Slash commands and messages from the messages tab

Without this, users cannot DM the bot even if the tokens and scopes are correct.

ductor does not register native Slack slash commands. Instead, its command keywords work in Slack as normal messages (for example help, status, or model) and also accept a leading /.

7. Install or reinstall the app to the workspace

In Install App, click Install to Workspace and authorize the app. Copy the Bot User OAuth Token (xoxb-...) into slack.bot_token.

If you change scopes or event subscriptions later, reinstall the app so Slack applies the new permissions.

8. Collect Slack IDs for the allowlist

  • User IDs (U...) go into slack.allowed_users
  • Channel IDs (C... / G...) go into slack.allowed_channels

You can get them from Slack's profile/channel details UI.

9. Configure ductor

{
  "transport": "slack",
  "group_mention_only": true,
  "slack": {
    "bot_token": "xoxb-your-slack-bot-token",
    "app_token": "xapp-your-slack-app-token",
    "allowed_channels": ["C0123456789"],
    "allowed_users": ["U0123456789"]
  }
}

Then invite the app into each target channel:

/invite @your-bot-name

Behavior summary:

  • DMs: the bot responds to every allowed user message
  • Channels: with group_mention_only=true, a channel conversation starts from a top-level @bot mention or an @bot inside an existing thread
  • Activated threads: once a thread is activated, follow-up replies in that thread continue the same session without another mention

Platform notes

Linux

sudo apt update && sudo apt install python3 python3-pip python3-venv nodejs npm
pip install pipx
pipx ensurepath
pipx install ductor
ductor

Optional Docker:

sudo apt install docker.io
sudo usermod -aG docker $USER

macOS

brew install python@3.11 node pipx
pipx ensurepath
pipx install ductor
ductor

Windows (native)

winget install Python.Python.3.11
winget install OpenJS.NodeJS
pip install pipx
pipx ensurepath
pipx install ductor
ductor

Native Windows is fully supported, including service management via Task Scheduler.

Windows (WSL)

WSL works too. Install like Linux inside WSL.

sudo apt update && sudo apt install python3 python3-pip python3-venv nodejs npm
pip install pipx
pipx ensurepath
pipx install ductor
ductor

Docker sandboxing

Enable in config:

{
  "docker": {
    "enabled": true
  }
}

Notes:

  • Docker image is built on first use when missing.
  • Container is reused between calls.
  • On Linux, ductor maps UID/GID to avoid root-owned files.
  • If Docker setup fails at startup, ductor logs warning and falls back to host execution.

Docker CLI shortcuts:

ductor docker enable
ductor docker disable
ductor docker rebuild
ductor docker mount /path/to/project
ductor docker unmount /path/to/project
ductor docker mounts
ductor docker extras
ductor docker extras-add <id>
ductor docker extras-remove <id>
  • enable / disable toggles docker.enabled in config.json (restart bot afterwards).
  • rebuild stops the bot, removes container + image, and forces fresh build on next start.
  • mount / unmount manage docker.mounts entries.
  • mounts are available in-container under /mnt/<name> (basename-based mapping with collision suffixes).
  • run ductor docker mounts to inspect effective mapping and broken paths.
  • extras lists all optional packages with their selection status.
  • extras-add / extras-remove manage optional AI/ML packages (Whisper, PyTorch, OpenCV, etc.) in config.json. Transitive dependencies are resolved automatically.
  • after changing extras, run ductor docker rebuild to apply. Build output is streamed live to the terminal.

Direct API server (optional)

Preferred enable path:

ductor api enable

This writes/updates the api block in config.json and generates a token if missing.

ductor api enable requires PyNaCl (used for E2E encryption against the direct API). PyNaCl is only needed when the direct WebSocket API is enabled — the core bot, Telegram, and Matrix transports run without it. If it is missing:

# pipx install
pipx inject ductor PyNaCl

# pip install
pip install "ductor[api]"

Manual config equivalent:

{
  "api": {
    "enabled": true,
    "host": "0.0.0.0",
    "port": 8741,
    "token": "",
    "chat_id": 0,
    "allow_public": false
  }
}

Notes:

  • token is generated and persisted by ductor api enable (runtime also generates it on API start if still empty).
  • WebSocket auth frame must include type="auth", token, and e2e_pk (client ephemeral public key).
  • endpoints:
    • WebSocket: ws://<host>:8741/ws
    • health: GET /health
    • file download: GET /files?path=... (Bearer token)
    • file upload: POST /upload (Bearer token, multipart)
  • default API session uses api.chat_id by truthiness (0 falls back), else first allowed_user_ids entry (fallback 1); clients can override chat_id in auth payload.
  • recommended deployment is a private network (for example Tailscale).

Background service

Install:

ductor service install

Manage:

ductor service status
ductor service start
ductor service stop
ductor service logs
ductor service uninstall

Backend details and platform quirks: Service Management

Backends:

  • Linux: systemd --user service ~/.config/systemd/user/ductor.service
  • macOS: Launch Agent ~/Library/LaunchAgents/dev.ductor.plist
  • Windows: Task Scheduler task ductor

Linux note:

  • user services survive logout/start on boot only when user linger is enabled (sudo loginctl enable-linger <user>). Installer attempts this and prints a hint when it cannot set linger.

Windows note:

  • service install prefers pythonw.exe -m ductor_bot (no visible console window),
  • installed Task Scheduler service uses logon trigger + restart-on-failure retries,
  • some systems require elevated terminal permissions for Task Scheduler operations.

Log command behavior:

  • Linux: live journalctl --user -u ductor -f
  • macOS/Windows: recent lines from ~/.ductor/logs/agent.log (fallback newest *.log)

VPS notes

Small Linux VPS is enough. Typical path:

ssh user@host
sudo apt update && sudo apt install python3 python3-pip python3-venv nodejs npm docker.io
pip install pipx
pipx ensurepath
pipx install ductor
ductor

Security basics:

  • keep SSH key-only auth
  • enable Docker sandboxing for unattended automation
  • keep allowed_user_ids restricted
  • use /upgrade or pipx upgrade ductor

Troubleshooting

Bot not responding

  1. check transport credentials (telegram_token / matrix block) + allowlists
  2. run ductor status
  3. inspect ~/.ductor/logs/agent.log
  4. run /diagnose in chat

CLI installed but not authenticated

Authenticate at least one provider and restart:

claude auth
# or
codex auth
# or
# authenticate in gemini CLI

Docker enabled but not running

docker info

Then validate docker.enabled + image/container names in config.

Webhooks not arriving

  • set webhooks.enabled: true
  • expose 127.0.0.1:8742 through tunnel/proxy when external sender is used
  • verify auth settings and hook ID

Upgrade and uninstall

Upgrade:

pipx upgrade ductor

Uninstall:

pipx uninstall ductor
rm -rf ~/.ductor  # optional data removal