A driver connects an outside event source (currently Slack and Telegram, with possible future transports such as Discord or HTTP webhooks) to a Blacknode agent workflow. Drivers are runtimes around the graph engine, not graph nodes: the cook stays synchronous and pull-based, and the driver runs one cook per incoming message. See slack-nim-demo.md for Slack and telegram-nim-demo.md for the local Telegram agent.
Drivers self-register at import (like nodes and exporters do). List them and their readiness:
blacknode driversBlacknode drivers
[needs install] slack - Slack bot (Socket Mode): answers @mentions with the agent workflow.
extra: blacknode[slack] (missing)
env: SLACK_BOT_TOKEN (missing), SLACK_APP_TOKEN (missing)
[ready] telegram - Telegram bot (long polling): answers messages with the agent workflow.
extra: blacknode[telegram]
env: TELEGRAM_BOT_TOKEN (set)
Status is one of:
| Status | Meaning |
|---|---|
ready |
the optional extra is installed and all required env vars are set |
needs install |
the optional extra (pip install 'blacknode[<extra>]') is missing |
needs env |
deps are present but a required env var (e.g. a token) is unset |
blacknode drivers --json prints the same data machine-readably (name, status,
extra, packages_installed, per-var env map, missing_env) for scripting.
Only the transport differs between drivers. The runtime engine is reusable:
ConversationMemory— optional per-conversation history, keyed by a thread/channel id; setmax_turns = 0to disable itdetect_input_node/inject_input— write the incoming text into the graphAgentRuntime.handle_message(text, thread_id) -> reply— the universal call
AgentRuntime (aliased from SlackAgentRuntime) knows nothing about Slack; a
new driver only provides a transport that calls handle_message.
The editor can install an optional driver package, save its token in the local key store, start and stop its subprocess, display heartbeat state, and show a tail of its logs. Drivers started by the editor cook the graph currently open, so most workflow edits take effect on the next message without restarting.
Long polling and Socket Mode are local-friendly outbound connections. Webhook transport is a separate hosted-deployment design and is not currently a switch in the built-in Telegram driver.
Three steps, mirroring Slack:
1. A transport module python/blacknode/integrations/<name>_runtime.py that
reuses AgentRuntime and registers itself:
import os
from blacknode.integrations.registry import DriverSpec, register_driver
from blacknode.integrations.slack_runtime import AgentRuntime # the reusable seam
def serve(runtime, *, token):
try:
import discord
except ImportError as exc:
raise RuntimeError("pip install 'blacknode[discord]'") from exc
client = discord.Client(intents=discord.Intents.default())
@client.event
async def on_message(msg):
if msg.author != client.user:
await msg.channel.send(runtime.handle_message(msg.content, str(msg.channel.id)))
client.run(token)
def _run(runtime):
serve(runtime, token=os.environ.get("DISCORD_TOKEN", ""))
register_driver(DriverSpec(
name="discord",
description="Discord bot: answers messages with the agent workflow.",
run=_run,
required_extra="discord",
required_packages=("discord",),
required_env=("DISCORD_TOKEN",),
))2. Import it for registration — add it to
python/blacknode/integrations/__init__.py so import blacknode.integrations
registers it (the same place the Slack driver is imported).
3. Wire the CLI — add a discord subparser in cli.py (copy the slack
one) and dispatch it through the shared runner: _run_driver("discord", args).
That runner reads the registry, checks the extra and env vars, builds the
AgentRuntime, and starts the transport — so the new driver gets the same
readiness checks and error messages for free.
4. Declare the extra in pyproject.toml:
discord = ["discord.py>=2.0"]Once registered, the new driver shows up in blacknode drivers with its own
readiness, and blacknode <name> <workflow.json> runs it.