A Telegram and Discord bot (@NearHackathonBot) that answers questions about NEAR Hackathon (Nov 1–14, 2026): rules, dates, tracks, prizes and submitting, plus how to build on NEAR, answered from the official docs. It's run by NEAR Builders. See SCOPE.md for the product scope.
- Hermes Agent in Docker on a small VPS, image pinned
by digest to
nousresearch/hermes-agent:v0.22.5(seeIMAGEindeploy.sh), with memory and process limits. - Model:
z-ai/glm-5.3-flashon NEAR AI Cloud, which runs inside a TEE (replies are signedprovider_tee). Every model call goes there; there is no fallback provider. - Knowledge:
data/SOUL.mdholds the bot's instructions and a copy of the public rules.sources/is the live text that copy was built from. - Tools: one plugin,
data/plugins/near-docs, with two read-only tools:search_near_docsandread_near_docs. They fetch only Markdown pages andllms.txtfrom docs.near.org, docs.near.ai and docs.near-intents.org, plusnearhackathon.com/HACKATHON.md. HTTPS only, no query strings, no redirects off those hosts. All built-in Hermes tools are off. - Link filter: the same plugin rewrites every reply so only links to an allowlist (the hackathon,
NEAR's sites, the official community links and partner sites) survive; any other link, with or
without
https://, becomes "(link removed)". Names Telegram would turn into links but are usually file names or accounts (HACKATHON.md,src/lib.rs,user.tg) are shown as code instead: readable, not clickable. Full links inside code are filtered too, since code is easy to copy. In tests the bot wrote planted scam links into its own warnings, and Telegram makes those clickable. - In groups it answers only when mentioned or replied to (Telegram privacy mode on, not an admin). Anyone can add it to a group (BotFather's Allow Groups is on), so teams can use it in their own chats. Each person has their own conversation; memory across conversations is off.
- Rate limit:
data/plugins/rate-limitdrops a person's questions past 10 in 10 minutes, with one notice, before they reach the model, so no one can use up the NEAR AI credits. Commands don't count; counts reset when the bot restarts. - On Discord it's in the NEAR Discord and a test server only (
DISCORD_GUILD_IDS):data/plugins/discord-servermakes it leave any other server it's added to. There it answers in any channel it can see, only when mentioned or replied to, in place (no threads), and in DMs. Other people's channel messages never go into someone's conversation. - Commands: users can run
/new,/start,/helpand/whoami. Everything else is refused. Discord's/menu has only/newand/help(data/plugins/welcome).
data/ the bot: synced to the server's Hermes home (/opt/data in the container)
config.yaml model, lockdown, Telegram and Discord settings
SOUL.md instructions and hackathon knowledge
plugins/near-docs/ the read-only docs tools and the link filter
plugins/welcome/ Telegram's /start and /help, Discord's /new and /help
plugins/discord-server/ keeps the Discord bot in its own servers
plugins/rate-limit/ a cap on questions per person
plugins/prompt-trim/ removes Hermes' own prompt blocks that config.yaml can't switch off
sources/ live rules and homepage text that SOUL.md is built from (baseline for the check)
deploy.sh push data/, ops/ and sources/ to the server and restart the bot
ops/check.py hourly: is the bot running, can it reach its model, do the rules still match sources/?
ops/sources.py fetches the rules in a stable, comparable form
ops/test.sh ask the bot questions using the local data/, safely (see Testing)
ops/test-questions.txt the default test questions
ops/eval.py ask the cases in ops/eval-cases.txt several times each and check every answer
ops/eval-cases.txt the cases and their checks: regression (blocks deploys) and capability
ops/teardown.sh end of event: delete everything the bot stored on the server
tests/ checks for the security boundary: what the docs tools may fetch, which links survive,
which Discord servers the bot stays in, how many questions a person can ask
SCOPE.md product scope
A Hetzner VPS named hack-bot with Docker, reachable as root over SSH (key only). Its firewall
allows SSH and nothing else; the bot only makes outbound connections. The scripts find the server
through hcloud server ip hack-bot, so you need the hcloud CLI with access to the project.
Secrets live only on the server, in /opt/hack-bot/data/.env (readable by the bot's user only). They
are never in this repo and the scripts never copy them:
| Key | What |
|---|---|
TELEGRAM_BOT_TOKEN |
from @BotFather |
TELEGRAM_ALLOW_ALL_USERS |
true: anyone can talk to it |
NEARAI_API_KEY |
NEAR AI Cloud key for this bot only |
TELEGRAM_HOME_CHANNEL |
the maintainer's Telegram user ID: alerts and Hermes notices go there |
DISCORD_BOT_TOKEN |
from the Discord Developer Portal (see Discord setup) |
DISCORD_ALLOW_ALL_USERS |
true: anyone in the server can talk to it |
DISCORD_GUILD_IDS |
the Discord servers it stays in, comma-separated (NEAR's, test) |
DISCORD_HOME_CHANNEL |
a private channel in one of them: Hermes notices go there |
Hermes adds API_SERVER_KEY itself on first start. To edit the file, SSH in and keep it
newline-terminated.
Once, in the Discord Developer Portal:
- New Application, named like the bot. On Bot: Reset Token and put it in
.envasDISCORD_BOT_TOKEN; turn on Message Content Intent (it can't read questions without it). Leave the other privileged intents off. - On Installation: untick User Install (a user-installed bot runs its commands in any server, past the server lock), keep Guild Install, set Install Link to None.
- Before the bot joins any server, put the servers' IDs in
.envasDISCORD_GUILD_IDS, comma-separated (Developer Mode on in Discord's settings, then right-click each server, Copy Server ID), addDISCORD_ALLOW_ALL_USERS=true, and./deploy.sh. The bot leaves any server not listed, anddeploy.shrefuses to start with a Discord token but no server IDs. - Add it to the servers with this link, using the Application ID from General Information. It
asks only for View Channels, Send Messages, Send Messages in Threads, Read Message History and Add
Reactions:
https://discord.com/oauth2/authorize?client_id=<application id>&scope=bot+applications.commands&permissions=274877975616&integration_type=0Adding it needs Manage Server in the NEAR Discord, so send the link to its admins. - Make a private channel in one of them that the bot can see and post in, and put its ID (right-click it,
Copy Channel ID) in
.envasDISCORD_HOME_CHANNEL. Without one, Hermes tells every new conversation to run/sethome.
Admins can limit it to some channels with the server's own channel permissions.
./deploy.sh # push changes and restart; waits until the bot is connected to Telegram (and Discord)
./deploy.sh --reset # same, and start every conversation fresh
./deploy.sh --no-eval # skip the regression cases (emergencies only)Hermes freezes SOUL.md into a conversation when it starts, and Telegram conversations never end,
so after changing SOUL.md use --reset. It deletes the existing conversations. Messages sent during a
restart are kept and answered afterwards.
ops/test.sh # the questions in ops/test-questions.txt
ops/test.sh "when is the deadline?" "..." # your own
ops/eval.py # every case in ops/eval-cases.txt, 3 runs each
ops/eval.py --model Qwen/Qwen3.6-35B-A3B-FP8 # the same cases on another NEAR AI modeldeploy.sh first runs the regression cases in ops/eval-cases.txt: questions where a wrong answer would
mislead or harm someone (dates, rules, scam links, seed phrases). Each is asked 3 times, since answers vary
between runs, and the deploy stops unless every run passes. Capability cases are known weak spots,
reported but never blocking. ops/eval.py saves every answer to a file: read them, not just the score.
When a real conversation goes wrong, add it as a case.
Before deploying a change to a plugin, also run python3 -m unittest discover tests: it checks which
URLs the docs tools may fetch, which links survive in replies, the Discord server lock and the rate
limit, with no network needed.
ops/test.sh uses your local data/, so you can try changes before deploying. It runs in a throwaway container
on the server that gets only NEARAI_API_KEY. Never start a test container with the Telegram or Discord
token: it would connect as the bot and fight the live one for messages.
ops/check.py runs hourly and messages the home channel when the bot is down or not connected to Telegram
or Discord, when a tiny test call to
its model fails or comes back from a different model or outside a TEE (a dead key, exhausted credits, a
retired model ID), or when the live rules differ from sources/ (with a diff). For a rule change:
- Update
data/SOUL.mdto match. - Refresh the baseline:
python3 ops/sources.py ops/test.sh, then./deploy.sh --reset.
The bot also reads the live HACKATHON.md itself when someone claims a rule changed, and never takes a
change from a chat message.
- Stop the bot now:
ssh root@$(hcloud server ip hack-bot) docker stop hack-bot. Or revoke its tokens in @BotFather (/revoke) and the Discord Developer Portal (Reset Token), which also locks out anyone who has a copy. - A token or key leaked: revoke it (BotFather, Discord Developer Portal, or the NEAR AI Cloud dashboard), put the new one in
/opt/hack-bot/data/.env, then./deploy.sh. - Someone pasted a seed phrase or other secret:
./deploy.sh --reset, then delete the matching lines from/opt/hack-bot/data/logs/. - The bot is in a group it shouldn't be: find the chat ID with
ssh root@$(hcloud server ip hack-bot) "grep -o 'chat=-[0-9]*' /opt/hack-bot/data/logs/agent.log | sort | uniq -c", then make it leave:curl -s "https://api.telegram.org/bot<token>/leaveChat?chat_id=<chat id>". To stop all new groups, turn off Allow Groups in @BotFather. - Alert: NEAR AI call failed: top up the account's credits, or replace the key in
.envand./deploy.sh. - Alert: answered with a different model, or no longer signed inside a TEE: the model ID was retired or
moved. Pick another of NEAR AI's own models that runs inside the TEE (today
Qwen/Qwen3.6-35B-A3B-FP8; listed models such as Kimi or DeepSeek are signed by the gateway, not a TEE), set it asmodel.defaultindata/config.yaml, thenops/eval.pyand./deploy.sh --reset. - Alert: not connected to telegram or discord: check that platform's token in
.env(and on Discord, the Message Content Intent in the Developer Portal), then./deploy.sh. - The bot posts something wrong: fix
data/SOUL.md,ops/test.sh, then./deploy.sh --reset.
ops/teardown.sh --yes # deletes the container, conversations, logs, backups and the hourly checkThen delete the server and its firewall in Hetzner, and revoke the NEAR AI key.