Blacki connects to Telegram with long polling. It does not implement webhook mode, so you do not need a public domain, TLS certificate, or inbound Telegram port.
- Open the verified BotFather account in Telegram.
- Send
/newbot. - Choose a display name and a username ending in
bot. - Copy the token.
Treat the token like a password. Anyone with it can control the bot.
In .env:
TELEGRAM_ENABLED=true
TELEGRAM_BOT_TOKEN=replace-me
# Optional: restrict new private chats with one shared access code.
# TELEGRAM_ACCESS_CODE=replace-with-a-dedicated-high-entropy-codeReplace replace-me with the token from BotFather. Blacki validates that the
token is present and follows Telegram's number:string format at startup.
When TELEGRAM_ACCESS_CODE is set, new users must send /start <access-code>
in a private chat before Blacki processes their messages. The bot consumes this
command locally and attempts to delete it, but Telegram may retain it in
server-side history or backups, so use a dedicated code rather than a password
you use elsewhere. Historical private chats with persisted Blacki sessions are
grandfathered; group chats and forum topics are rejected. Changing the access
code requires passphrase-authorized users to authenticate again but does not
delete any chat history, preferences, reminders, files, or health data.
At least one model provider must also be configured. See Configuration.
To let Blacki turn Telegram's native voice-note button into a normal text conversation, add a Cloudflare Workers AI account ID and API token:
CLOUDFLARE_ACCOUNT_ID=replace-me
CLOUDFLARE_API_TOKEN=replace-meThe token should have Workers AI Read and Write permissions. Blacki sends
voice notes to Cloudflare's hosted
@cf/openai/whisper-large-v3-turbo model, then passes the transcript through
the existing Telegram text-turn path so conversation history and tool
confirmations continue to work. Voice notes are kept transiently in memory;
regular audio files continue to use the existing file-upload behavior. If the
credentials are absent, Blacki reports that voice transcription is not
configured instead of failing startup. The raw voice bytes are transient; the
resulting transcript is handled like ordinary Telegram text and follows the
existing conversation-history and logging/privacy settings.
To let the Telegram-only root agent turn text into playable MP3 audio, add:
KOKORO_TTS_BASE_URL=http://100.x.y.z:8880
KOKORO_TTS_VOICE=af_heartUse the Tailscale IP or MagicDNS name that is reachable from the Blacki
container. localhost refers to the Blacki container itself, not a Kokoro
server on another machine. Blacki calls Kokoro's OpenAI-compatible
/v1/audio/speech endpoint, keeps the bounded MP3 in memory, and uploads it to
the current Telegram chat or topic. Synthesis and upload are serialized so only
one audio payload is retained at a time. The tool is not registered on the
public ADK HTTP runner or delegated task worker.
When this private tool is configured, Blacki disables content-rich ADK and OpenInference logging. Tool notifications may show that speech synthesis is running, but never include the text being spoken. ADK session history still retains the model's tool call and arguments.
Blacki can read normalized health summaries after a user completes Google OAuth from a private Telegram chat. If the user grants both nutrition permissions, Blacki also queues existing meals from that private chat once for the connected Google account, then exports future meal logs, edits, and deletions. This is intentionally named Connect Google Health: Blacki does not request Apple ID credentials, access HealthKit, scrape Fitbit, or receive arbitrary Apple Health records. The user must first configure an Apple Health-to-Google Health/Fitbit-compatible import path if their account and app version support it.
Configure the Google Cloud OAuth web client and the GOOGLE_HEALTH_* values in
Configuration, then set the callback URL
to the exact public HTTPS URL. In Telegram:
- Send
/connect_healthin a private chat. - Open the one-time Google authorization link. Grant the read-only categories
for summaries. Grant both
googlehealth.nutrition.readonlyandgooglehealth.nutrition.writeonlyif you want future meal export. Existing connections must reconnect to add these nutrition permissions. The nutrition read permission lets Blacki verify records it created by exact data point ID; it does not import unrelated food logs. - Return to Telegram and use
/health_refreshfor an on-demand sync or/health_summaryfor the latest stored records. - Log meals normally. Existing eligible meals are queued once after the connection is saved. New meals continue through the background export worker. A local Blacki save remains successful when remote sync is pending or fails, and the meal must not be logged again. Ask Blacki for meal sync status when you want to check the queue, or ask it to retry failed exports.
- Use
/disconnect_health, then confirm the button, to revoke the token best-effort, cancel pending meal sync, and remove Blacki's stored token and normalized health summaries. Local calorie logs remain. Blacki does not delete records already sent to Google Health, and requests already submitted may still finish.
The background sync runs every 12 hours by default and fetches a bounded recent
window so late device imports can replace earlier daily records. Missing values
are omitted rather than guessed. Stored data is limited to normalized daily
activity, workout, sleep, heart-rate, weight, and body-fat summaries; raw
Google payloads and provider IDs are not persisted in the summary table. Meal
exports are persisted separately with retry state and opaque data point IDs. A
one-time per-account backfill queues existing local meals with a durable cursor;
the meal export worker runs every minute independently of health imports. Run
only one active scheduler process per tools.db so a deployment does not
dispatch duplicate work.
Google's v4 discovery document currently lists nutrition-log as a supported
data type. Blacki writes only the local meal description, kcal, available
macros, meal type, and selected local date; it omits unknown nutrients and does
not substitute food-database estimates. See Google's nutrition data type
and data point REST reference for the provider contract.
Google Health availability does not prove that a particular Apple Health metric was imported. Test the desired categories on a non-production account before promising steps, workouts, sleep, or heart-rate coverage to users.
Docker Compose:
docker compose -f compose.yaml -f compose.prod.yaml up --build -d
docker compose -f compose.yaml -f compose.prod.yaml logs --tail=100 agentLocal Python:
uv run python -m blacki.serverLook for startup logs confirming that the Telegram configuration was detected and polling started.
Open the bot in Telegram and send:
| Command | Behavior |
|---|---|
/start |
Show the welcome message |
/help |
Show the supported command summary |
/model |
Open the model and thinking settings panel |
/thinking |
Open the supported reasoning-effort choices for the active model |
/reset |
Start a fresh conversation session |
/connect_health |
Send a Google Health authorization link and consent for optional meal sync |
/health_refresh |
Fetch recent Google Health data (rate limited) |
/health_summary |
Show normalized daily records and trends |
/disconnect_health |
Confirm disconnection and cancellation of meal sync |
Then send a normal message and confirm the model responds. Blacki does not
currently implement a /clear command.
If Kokoro speech is configured, ask the bot to “send that as audio” and confirm Telegram receives an MP3 in its native audio player. Kokoro failures return a text error and do not create a local audio file.
The /model panel stores one profile for the Telegram chat. Choose a model,
then select a thinking effort supported by that model. Default uses
ROOT_AGENT_REASONING_EFFORT when configured and otherwise leaves the
provider's reasoning setting unchanged. Off is shown only when the model
advertises that it can disable reasoning. Changes apply to the next turn and
do not change existing conversation history. Changing models resets the
chat's explicit thinking choice to Default, avoiding an unsupported setting
being carried to a different model. /thinking is a shortcut to the same
capability-aware menu.
To verify image input, select a vision-capable model, send a Telegram photo,
and optionally add a caption as the instruction. Without a caption, Blacki asks
the model to describe the image. Native photo input is limited to 10 MB;
documents, audio, video, and voice messages continue to use the sandbox upload
path. When an image is sent as a Telegram file/document, Blacki exposes its
/workspace/uploads/... path to the agent and the agent can call
sandbox_view_image to attach it as a visual input. Call the tool once per
image when several files are present; each image remains a separate model
input rather than being combined into a collage. The tool accepts a path below
/workspace, validates common PNG, JPEG, GIF, WebP, and BMP files, and is
read-only. A model that does not support images will return the normal photo
processing error without changing the selected model.
Telegram turns automatically show one live-updating tool status message while tools run. The status is updated in place and ends with the elapsed working time, so tool names and arguments are not posted as separate chat messages.
- Never paste the token into an issue, log, command history, or committed file.
- Keep
.envmode600. - If the token is exposed, revoke and regenerate it through BotFather.
- Group privacy settings are controlled through BotFather; review them before adding the bot to a group.
If the bot does not respond:
docker compose -f compose.yaml -f compose.prod.yaml ps
docker compose -f compose.yaml -f compose.prod.yaml logs --tail=200 agentCheck that:
TELEGRAM_ENABLED=true;- the token contains no quotes or trailing whitespace;
- one real model API key is active;
- the model identifier matches that provider; and
- the VPS can make outbound HTTPS requests.
See Troubleshooting for startup and model failures.