docs(telegram): groups and Jev topic triggers

This commit is contained in:
EugeneTes
2026-10-05 16:29:07 +00:00
parent be3c77b4d0
commit cabec7ad70
5 changed files with 75 additions and 6 deletions

View File

@@ -2,7 +2,8 @@
`hosts/Volcanic.AgenticCore.Telegram` lets you chat with the agent from Telegram. It uses long polling (no public
URL, no webhook), gives every private chat its own agent session and workspace, and runs the model through the
Claude Code provider (`claude-code/claude-opus-5-5`) on your Claude subscription.
Claude Code provider (`claude-code/claude-opus-5-5`) on your Claude subscription. In groups it stays silent and only
watches for the topics its users set up as [triggers](#groups-and-triggers).
## Setup
@@ -45,6 +46,16 @@ settings use the `GoogleCalendar` section, the scheduler the `Scheduler` section
| `STT_MODEL` / `Telegram:Speech:Model` | `gpt-4o-transcribe` | `gpt-4o-mini-transcribe` and `whisper-1` also work |
| `Telegram:Speech:BaseUrl` | `https://api.openai.com/v1` | Any OpenAI-compatible transcription endpoint |
| `Telegram:Speech:Language` | — | Optional ISO-639-1 hint, e.g. `de` |
| `OPENROUTER_API_KEY` / `Telegram:Triggers:ApiKey` | — | Turns on [groups and triggers](#groups-and-triggers). Hidden from `bash` and redacted from tool output |
| `TRIGGER_MODEL` / `Telegram:Triggers:Model` | `typesafe/jev-1.13` | The Jev model that classifies group messages |
| `Telegram:Triggers:BaseUrl` | `https://openrouter.ai/api/v1` | Requests go to `{BaseUrl}/systemone` |
| `Telegram:Triggers:DefaultThreshold` | `0.7` | Yes-probability a message needs when the trigger sets none |
| `Telegram:Triggers:Debounce` | `00:00:10` | Matches of one trigger within this window (opened by the first) reach the chat as one prompt |
| `Telegram:Triggers:MaxRetries` | `3` | Retries after a failed Jev call (network errors, timeouts, 408, 429, 5xx) |
| `Telegram:Triggers:MaxConcurrency` | `4` | Jev calls in flight at once |
| `Telegram:Triggers:MaxTriggersPerChat` | `20` | Triggers per private chat |
| `Telegram:Triggers:MaxSnooze` | `30.00:00:00` | Longest snooze |
| `Telegram:Triggers:MaxQueuedPerTrigger` | `5000` | Messages kept per snoozed trigger; past it the oldest are dropped |
| `Telegram:InlineImageMaxBytes` | `3750000` | Image files up to this size are also shown to the model; must be positive |
| `GOOGLE_CLIENT_ID` / `GoogleCalendar:ClientId` | — | Optional. With the secret, every chat gets the `calendar` tool and the `/calendar_*` commands; see [google-calendar.md](google-calendar.md) |
| `GOOGLE_CLIENT_SECRET` / `GoogleCalendar:ClientSecret` | — | Optional; redacted from tool output and hidden from `bash` |
@@ -206,6 +217,50 @@ the current format, and keeps the original next to it as `jobs.json.secretary-<t
Copy the workspace's `scripts/` into the owner's workspace too: watcher commands such as `python scripts/check.py` run
from there. A one-off that is already due fires on the first start.
### Groups and triggers
With `OPENROUTER_API_KEY` set, the bot can sit in Telegram groups and tell you, in your private chat, when something
you care about comes up. It never writes in a group and never answers there; a group gets no agent session.
**Setup, once per bot and group**
1. In [@BotFather](https://t.me/BotFather), `/setprivacy` → your bot → **Disable**. With privacy mode on, a bot only
sees commands, mentions and replies in groups (the startup log warns about it). Alternatively make the bot a group
admin. Telegram applies the change to groups the bot joins afterwards, so re-add it to groups it is already in.
2. Add the bot to the group. It watches a group only if an allowed user added it, or an allowed user posts there once
while it is a member; groups anyone else adds it to are ignored. Removing the bot pauses the group's triggers.
**Triggers**
Ask the bot in your private chat, for example "tell me when someone in Dev Team talks about deploys or outages". The
agent uses its `trigger` tool: it lists the watched groups, writes a yes/no question about one message with what counts
and what doesn't, and confirms it with you before saving. You must be a member of the group. `/triggers` lists your
triggers.
Every message in a watched group (text or caption; voice notes, files and stickers without a caption are skipped) goes
to TypeSafe's [Jev](https://openrouter.ai/typesafe/jev-1.13), a fast classifier, through OpenRouter's `/systemone`
endpoint: one call per message carries every trigger on that group as its own question, with the message, its sender,
the group and the message it replies to. Jev returns the probability that the answer is yes; a trigger matches at or
above its threshold (0.7 unless set). Matches of one trigger within 10 s reach your chat as one prompt listing each
message with its score and a link, and the agent decides what to tell you. A failed call is retried 3 times.
Matches carry their scores, so tuning is conversational: say a match wasn't relevant and the agent tightens the
question or raises the threshold; describe a message it missed and it lowers the threshold or broadens the question.
**Snooze.** "Keep quiet until tonight" snoozes a trigger (or all of yours) for a duration or until a time. Meanwhile
messages are collected in your workspace (`.volcanic/triggers/snoozed/`), and when the snooze ends they go to Jev as
one batch; if anything is related you get one prompt with the whole batch (a large one is saved under
`.volcanic/triggers/digests/` and the prompt points to it). Unrelated batches stay silent. A snooze that ends while
the bot is down is handled when it starts.
**Logs.** Every group message gets one log line with its Jev results, for example
`Group "Dev Team" (-100123…) msg 789 from Alice (@alice): "prod deploy is failing…" → 1001/t1 deploys=0.93 MATCH`,
or why it wasn't classified (unknown group, no text, no triggers, snoozed, Jev failed). Message texts are cut to 200
characters there.
Watched groups live in `<agent dir>/telegram/groups.json`; each chat's triggers in its workspace under
`.volcanic/triggers/triggers.json`.
### Impersonation
The admin (`ADMIN_USER_ID`) can act as another user with `/impersonate <user_id>`; `/impersonate 0` (or the admin's
@@ -232,7 +287,9 @@ storage and time zones are in [google-calendar.md](google-calendar.md).
## Security
The agent has the built-in tools, including `bash`, and runs them as the bot's OS user. Only allow-listed user ids
are served (everyone else is ignored and logged), and only in private chats. `ADMIN_USER_ID` can act as any
are served (everyone else is ignored and logged), and only in private chats. In groups the bot only classifies
messages for triggers: it never runs the agent for a group member, and the messages of everyone in a watched group
(not only allowed users) are sent to OpenRouter and logged. `ADMIN_USER_ID` can act as any
allow-listed user with `/impersonate`, so it gets that user's workspace, sessions, jobs and connected calendar. Run it in the container image
([docker.md](docker.md)), which is the recommended setup, or at least as a dedicated user, and treat the workspace root
as writable by anyone on the allow-list.