Conversation Channels¶
Conversation channels let people reach vornik from the tools they already use. Connect a channel and inbound messages turn into tasks or chat turns, while vornik's replies flow back out the same way they came in. vornik ships four channels: GitHub, Slack, email, and voice messages (experimental).
You configure each channel per project, in that project's YAML file. After you change a channel's settings, reload the project configuration — you do not need to take the whole service down to pick up new channels.
Things every channel has in common¶
- Secrets never live in YAML. Tokens, passwords, and signing secrets are
supplied through environment variables. The YAML only names the environment
variable to read (for example
bot_token_env), so your config files stay safe to commit. - Allowlists are your front door. Each channel has a sender allowlist and, where it applies, a channel/repo allowlist. Leaving an allowlist empty means "accept everyone" — fine while you are testing, but you should tighten it before going to production.
- Routing is automatic. A single channel connection can serve several projects when the upstream service gives a stable identifier (a GitHub installation, a Slack workspace, an email login). vornik routes each inbound message to the right project for you.
Tip
Start every channel with an allowlist locked down to just the people and places you expect. Loosen it once you have confirmed the flow works.
GitHub¶
The GitHub channel lets vornik respond to issues and pull requests in the repositories you choose. It can:
- Open a task when an issue gets a label you have nominated.
- Open a review task when a pull request is opened.
- Reply when someone mentions
@vornikon an issue or pull request.
Setup¶
- Create a GitHub App in your organization's settings.
- Point the App's webhook URL at your vornik host's GitHub webhook endpoint
(
/api/v1/github-app/webhook). - Generate a webhook secret, download the App's private key, and note the App ID and installation ID.
- Subscribe the App to issue, issue-comment, pull-request, and pull-request-review-comment events.
- Grant the installation read and write access to issues and pull requests on every repository you want vornik to handle.
Configuration¶
github_app:
app_id: 1234567
private_key_path: /path/to/github-app.pem
installation_id: 89012345
webhook_secret_env: VORNIK_GITHUB_APP_WEBHOOK_SECRET
repo_allowlist:
- "your-org/your-repo"
task_labels: # labels that create a task (the label name becomes the task type)
- "vornik"
- "research"
pr_review_labels: # leave empty to review every opened PR
- "needs-review"
sender_allowlist: # GitHub login names allowed to trigger vornik
- "your-github-login"
Supply the webhook secret in the environment before starting vornik:
export VORNIK_GITHUB_APP_WEBHOOK_SECRET="your-webhook-secret"
A webhook secret and a non-empty repo_allowlist are the minimum for vornik to
receive events. To let it reply, also set app_id, private_key_path, and
installation_id.
Good to know¶
- Label matching is case-sensitive — GitHub keeps your label's exact
casing, so
Vornikandvornikare different. - An empty
pr_review_labelsmeans every opened pull request creates a review task. List specific labels to require at least one of them. sender_allowlistmatches GitHub usernames, including bots. If you want a bot to be able to trigger vornik, add it explicitly.- If you are testing by re-sending a webhook, change the delivery so it looks new — vornik deduplicates redelivered events so it never double-comments.
Slack¶
The Slack channel lets vornik answer in direct messages, when it is
@-mentioned in a channel, or when it is mentioned in a public channel it has
been allowed into.
Setup¶
- Create a Slack App at https://api.slack.com/apps.
- Enable the Events API and set the request URL to your vornik host's Slack
webhook endpoint (
/api/v1/slack/webhook). vornik answers Slack's verification challenge automatically the first time. - Subscribe the bot to the
app_mention,message.im, andmessage.channelsevents. - Under OAuth & Permissions, add the
app_mentions:read,chat:write,im:history,im:read,channels:history,files:read,files:write, andcommandsscopes.files:readlets vornik consume inbound attachments;files:writelets it return reports and other task artifacts to Slack. - Under Slash Commands, create
/vornikand set its request URL to the same/api/v1/slack/webhookendpoint. Use/vornik <prompt>to start or continue the per-user channel session. - Install (or reinstall) the App to your workspace and capture the signing secret and the bot
token (it starts with
xoxb-).
Configuration¶
slack:
team_id: "T01234567"
signing_secret_env: VORNIK_SLACK_SIGNING_SECRET
bot_token_env: VORNIK_SLACK_BOT_TOKEN
channel_allowlist:
- "C0ABCDEF12"
sender_allowlist:
- "U0AAAAAAA"
# Optional. The command this deployment answers; defaults to /vornik. Set it
# when the bot is branded differently, and match the app's own manifest.
slash_command: "/vornik"
# Optional. A turn can take a minute, and Slack gives bots no typing
# indicator, so vornik posts a short "working on it" placeholder and replaces
# it with the answer. Set false to keep the channel silent until the reply.
progress_signal: true
# Optional. How long a turn may run before that placeholder appears.
progress_signal_gap: "2s"
Supply the secrets in the environment before starting vornik:
export VORNIK_SLACK_SIGNING_SECRET="your-signing-secret"
export VORNIK_SLACK_BOT_TOKEN="xoxb-..."
A signing secret and team_id are the minimum to receive events; the bot token
is what lets vornik reply.
Good to know¶
- An empty
channel_allowlistorsender_allowlistdenies everything (direct messages excepted fromchannel_allowlist— see below). A Slack bot is addressable by every member of the workspace it is installed in, so an unconfigured allowlist would otherwise mean the whole workspace could drive the project — spending its budget and reaching its tools — with nothing in the config saying so. List the channels and senders you intend, or setallow_unlisted_senders: trueto opt into the open behaviour deliberately (dev only). - Slack rejects requests whose timestamps are more than five minutes off, a standard replay defense. If valid messages get rejected, check that your server's clock is in sync.
- Slack limits how fast a bot can post (roughly one message per second per channel). vornik respects this automatically and waits when Slack asks it to.
- Slack retries Events API deliveries when acknowledgement is slow. vornik
drops Slack-marked retries (including when they reach another replica) and
deduplicates repeated
event_idvalues, so one message produces one dispatcher turn. /vornikis the Slack command entry point. Telegram-specific commands such as/tasksare not separately registered in Slack; ask for those operations through/vornik, for example/vornik show my running tasks.- Slash commands use a per-user, per-channel session separate from ordinary message threads. Their delayed answers post at channel level because a slash invocation has no Slack message timestamp to reply under.
- Direct messages are never gated by
channel_allowlist, whether it lists channels or is left empty. A DM's channel id is created by Slack the first time each person writes to the bot, so it cannot be listed in advance.sender_allowlistis the control for DMs — so a DM-only bot lists its senders and leaveschannel_allowlistempty, which denies every real channel while direct messages keep working. This covers one-to-one DMs only: a group DM is not a DM for this purpose, and vornik does not support group DMs at all — the bot is not subscribed to group-DM history, and a group-DM message is dropped as an unhandled conversation type. Adding the bot to a group DM produces silence, not an answer. - In a thread, you only tag the bot once. Replies inside a thread vornik is already part of continue the conversation without a mention. A new top-level message in a channel still needs an @-mention, which is what keeps the bot out of conversations between colleagues.
- Images posted to Slack are read: they go to the model directly when it can see images, and to the vision role when it cannot. Voice memos are transcribed when voice is configured.
- Reports and other operator-facing task outputs can be uploaded back into the
originating Slack thread. This requires the
files:writeOAuth scope; after adding it to an existing app, reinstall the app to the workspace. - Opening the app's Home tab shows what this deployment serves and how to
reach it. Subscribe to the
app_home_openedbot event to enable it; no extra OAuth scope is needed. - You can see what a turn is doing. While vornik works, the "working on it" message is rewritten in place to name the current step — searching memory, scheduling the job, calling an external service. It is replaced by the answer when the turn finishes, and it never becomes part of the conversation.
- A job scheduled from chat reports back. When a task vornik created for you finishes, it posts the outcome and a link into the same conversation, whether or not it succeeded. That works across a daemon restart: the link between the task and your conversation is stored in the database, not in memory.
Reporting a problem with vornik¶
Ask the bot to report a bug ("something is broken, please file a report") on any channel and it prepares an anonymised report: hostnames, file paths, IP addresses, email addresses and anything resembling a credential are stripped automatically.
It gives you back a link, and does not submit anything itself. The report
would go to a public repository, and automatic redaction cannot know whether
your own description mentions a customer or a system you would rather not name —
so you read it on GitHub and press submit yourself. The same rule applies to
vornikctl report on the host.
Email¶
The email channel reads inbound mail over IMAP and replies over SMTP, so anyone can interact with vornik just by sending it a message. It handles multipart and HTML mail, decodes international character sets, and saves attachments as task artifacts.
Setup¶
Bring your own IMAP and SMTP service. The channel works with common providers. For Gmail, for example:
- Turn on two-factor authentication for the account.
- Create an App Password and use it for both IMAP and SMTP.
- Use
imap.gmail.com:993(TLS) andsmtp.gmail.com:587(STARTTLS). - If you want vornik to read from a folder other than
INBOX, create it first and setimap_mailbox.
Configuration¶
email:
imap_host: imap.gmail.com
imap_port: 993
imap_username: [email protected]
imap_password_env: VORNIK_ASSISTANT_IMAP_PASSWORD
imap_mailbox: "INBOX"
smtp_host: smtp.gmail.com
smtp_port: 587
smtp_username: [email protected]
smtp_password_env: VORNIK_ASSISTANT_SMTP_PASSWORD
from_address: "Assistant <[email protected]>"
sender_allowlist:
- "[email protected]"
- "example.com" # bare domains are allowed too
poll_interval: "60s"
attachment_size_cap_bytes: 26214400 # 25 MiB
attachment_store_dir: /path/to/email-attachments
Supply the credentials in the environment before starting vornik:
export VORNIK_ASSISTANT_IMAP_PASSWORD="your-imap-password"
export VORNIK_ASSISTANT_SMTP_PASSWORD="your-smtp-password"
The IMAP host, username, and password are the minimum to receive mail. Add the
SMTP block and from_address to let vornik reply; an inbound-only setup is
valid if you only want it to act on incoming mail.
Good to know¶
sender_allowlistaccepts full addresses ([email protected]) and bare domains (example.com). An empty allowlist accepts everyone — tighten it in production.- Set
attachment_size_cap_bytesto a sensible limit. Leaving it unset means unlimited, so a single huge attachment could land in your artifact store. Matching your mail provider's per-message cap (Gmail's is 25 MiB) is a good default. - Set
attachment_store_dirthe first time you enable the channel. If it is empty, attachment contents are silently dropped. - The channel polls on
poll_interval(60 seconds by default). Very short intervals are accepted but discouraged. - Optional inbound authentication checking (
verify_inbound_auth) relies on your mail server stamping SPF/DKIM results. If you turn it on, start with therelaxedpolicy and confirm legitimate mail passes before moving tostrict.
Voice messages¶
Experimental
Voice messages are an experimental feature. The transcription/synthesis pipeline and its configuration may change between releases, and quality depends on your chosen STT/TTS models. Use it for evaluation rather than production-critical conversations for now.
The voice channel adds spoken conversation to Telegram and Slack. When someone sends a voice message, vornik transcribes it, treats the transcript as a normal message, and — if the conversation started in voice — speaks its reply back.
A conversation's "voice mode" is sticky: once someone speaks, vornik keeps replying with audio until the next message comes in as text.
The current release uses local, open-weight speech engines:
- Speech-to-text: whisper.cpp
(
whisper-cli) with a Whisper ggml model file. - Text-to-speech: Piper with a Piper voice.
- Audio conversion:
ffmpeg, with Opus for Telegram and AAC for Slack.
Since 2026.9.7 the programs ship in the agent image and every voice note is
processed there, in a one-shot container with no network and a memory, CPU and
time limit (sandbox_tools.limits.voice_stt / voice_tts, see
Artifacts and delivery). Nothing is installed on,
or run on, the daemon host. You download only the model files; vornik
mounts each model's directory read-only into the container.
Configuration¶
voice:
stt:
provider: "whisper-local"
model: "/path/to/ggml-base.en.bin"
language_hint: "en" # leave empty for English
tts:
provider: "piper"
voice: "/path/to/en_US-amy-medium.onnx"
speed: 1.0
max_text_runes: 1500
You can configure stt alone (vornik transcribes incoming audio and replies
with text) or tts alone (vornik reads its replies aloud) — you do not need
both.
Good to know¶
max_text_runescaps how much text gets spoken (1500 is about 90 seconds). Telegram limits voice messages to 60 seconds, so set this to suit your channel; vornik trims overly long replies rather than failing silently.binary_pathandffmpeg_pathare ignored since 2026.9.7: the programs come from the agent image. The daemon logs a warning while they are still set; remove them. An agent image older than the release lacks the tools, and voice then reports "not available in the agent image" — there is no fallback to a program on the host.- Run
vornikctl doctor: itssandbox_toolscheck transcribes a one-second clip with your Whisper model, synthesises a phrase with your Piper voice, and round-trips Opus and AAC throughffmpeg, all in the sandbox, so a wrong or missing model shows up there rather than at the first voice note. - Use the smallest Whisper model that is accurate enough for you. On a machine without a GPU, larger models can take many seconds to transcribe a clip.
- For Piper, point
voiceat the.onnxfile; its matching.onnx.jsonmust sit beside it (the whole directory is mounted). voice.stt.modelalso serves audio document extraction unlessextractors.audio.model_pathnames a different model.
A note on history and replies¶
Conversation history belongs to the conversation, not to the channel, so vornik remembers context across messages even if a channel reconnects. vornik sends one complete reply per turn rather than streaming it word by word (live typing indicators are specific to the Telegram and web chat experiences).
For tuning the model behavior and safety controls that sit behind every reply, see Workflows and LLM controls.