docs(telegram): groups and Jev topic triggers
This commit is contained in:
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user