让家里的电脑变成一个 Telegram 机器人:带记忆的私人 AI 助手


上一篇搭好了一套跑在家里旧台式机上的本地 AI:35B 模型常驻、网页版对话、编程 agent。但有一个缺口 —— 手机上不好用。

网页版能用,可要打开浏览器、输一串 IP;而编程 agent 干脆用不了,因为它拒绝监听非本机地址(理由很正当:它能执行命令,谁访问到谁就控制了这台机器)。

这篇解决它:接一个 Telegram 机器人,在微信一样的聊天窗口里和自己的模型说话。而且它记得住 —— 上次说过的事,这次还在。

为什么 Telegram 比隧道好

我原本给编程 agent 搭了 SSH 隧道,笔记本上够用。但手机上做隧道很别扭。

Telegram 这条路完全绕开了这个问题:

传统做法    手机 → 公网 → 你家路由器端口映射 → 服务    要开端口,有暴露面
Telegram    手机 → Telegram 服务器 ← 你家电脑主动连出   零端口暴露

你的电脑是主动连出去的,不需要任何入站端口,不用碰路由器,不用动态域名。这在安全上比自己开端口干净得多。

选哪个 agent 框架

我已经装了 DeepSeek Harness(上一篇里的编程 agent)。这次用的是 Nous Research 的 Hermes Agent,因为两者定位不同:

DeepSeek Harness Hermes Agent
像什么 可拆解的编程工具 常驻的个人助手
入口 Web UI + CLI 15+ 平台:Telegram / Discord / Slack / WhatsApp / 邮件 / Home Assistant…
记忆 会话内 跨会话累积,写进 MEMORY.md / USER.md
定时任务 无 内置 cron
架构 一切皆插件(135 个) 三层:接口 / agent 逻辑 / 执行后端

简单说:dsh 是坐在电脑前写代码时用的,Hermes 是随时从手机戳一下的。 两个可以共存,我现在两个都留着。

安装前:先读那个安装脚本

官方的装法是这样:

iex (irm https://hermes-agent.nousresearch.com/install.ps1)

这行命令的意思是「把远程服务器上的脚本直接交给系统执行」。 这台机器上有我所有的实验数据和 SSH 密钥,所以我没有直接跑,而是先下载下来读一遍:

curl -fsSL -o hermes-install.ps1 https://hermes-agent.nousresearch.com/install.ps1
# 243 KB,读它做了什么
grep -nE "github.com|git clone|InstallDir" hermes-install.ps1
grep -niE "setx|schtasks|New-Service|HKLM|Set-NetFirewall|RunAs" hermes-install.ps1

审计结果是干净的:

仓库      github.com/NousResearch/hermes-agent    官方
装到      %LOCALAPPDATA%\hermes                   用户级,不动系统目录
schtasks  只停/启它自己的任务                      不创建别的持久化项
防火墙    无任何操作
提权      无

然后用我读过的这一份来装,而不是再从网上拉一次(避免下载到的和执行的是两个版本)。

这个习惯值得保留。 不是因为这个项目可疑 —— 恰恰相反,它的安装脚本写得相当克制。而是因为 curl | bash 这个模式本身把「信任」交给了一次网络请求,而读一遍只花五分钟。

接本地模型

Hermes 支持 18+ 家模型服务商,也支持任何 OpenAI 兼容端点。指向本机:

hermes config set model.base_url http://127.0.0.1:11435/v1
hermes config set model.provider custom
hermes config set model.default custom/qwen3.6:35b

(11435 是我上一篇里那个剥离思考模式的小代理,直连 Ollama 就是 11434。)

这里有个坑:文档里写的是 hermes config set CUSTOM_BASE_URL ...,但它会提示:

'CUSTOM_BASE_URL' is not a recognized config key — it was saved anyway,
but Hermes may not read it.

“存了但可能读不到” —— 这种半成功最麻烦。正确的键要去 config.yaml 里找,是 model.base_url。凡是工具提示“不认识这个键”,就别当它成功了。

验证:

hermes -z "Reply with exactly: HELLO" --cli

回了 HELLO,而且我的代理日志里出现了对应请求 —— 证明确实走的本地模型,不是云端。

申请机器人

两分钟的事,在 Telegram 里:

  1. 搜 @BotFather,发 /newbot,给机器人起个名字和一个以 bot 结尾的用户名
  2. 它给你一串 token,形如 8877670322:AAxxxxx
  3. 再搜 @userinfobot,发任意消息,拿到你自己的数字 ID

第三步很重要 —— 用来锁定只有你能用这个机器人。

访问控制:默认拒绝,但你得配对地方

这一步我差点搞砸,值得展开说。

先看它的授权逻辑(源码里写得很清楚):

"""
Checks in order:
1. Per-platform allow-all flag
2. Environment variable allowlists (TELEGRAM_ALLOWED_USERS, etc.)
3. DM pairing approved list
4. Global allow-all (GATEWAY_ALLOW_ALL_USERS=true)
5. Default: deny
"""

默认拒绝,这是好的默认值。但我一开始这样配:

hermes config set TELEGRAM_ALLOWED_USERS 7622580257

它同样提示“不是识别的键,但还是存了” —— 存进了 config.yaml。而代码里是用 os.getenv() 读的,只认环境变量。

正确位置是 .env 文件:

TELEGRAM_BOT_TOKEN=8877670322:AAxxxxx
TELEGRAM_ALLOWED_USERS=7622580257
TELEGRAM_ALLOW_ALL_USERS=false

这次的错误方向是“更安全”的(白名单没生效 = 谁都用不了,包括我自己),但如果哪天配的是 ALLOW_ALL_USERS=false 却写错了地方,方向就反过来了。安全相关的配置,一定要验证它真的生效,不能只看命令有没有报错。

启动,然后手机上就能用了

hermes gateway run

第一条消息发过去,机器人回了。同时我在两处日志里确认了链路完整:

gateway 日志    session agent:main:telegram:dm:762258...   ← 我的 user id,白名单生效
代理日志        RESULT /v1/chat/completions reasoning=0    ← 请求到了本地 35B

这时它会提示一句:

No home channel is set. Type /sethome to make this chat your home channel.

发个 /sethome 就好 —— 定时任务的结果会送到这个聊天。

记忆:它真的记得住吗

这是 Hermes 相比普通聊天界面最大的区别。测一下:

我:  Remember this: my winpc runs qwen3.6:35b with a 196608 token context.
它:  Done. Saved to memory.

但我没信这句话。 去找文件,MEMORY.md 不存在。

文档说记忆在 MEMORY.md / USER.md,而实际写在了 hermes\memories\USER.md:

User's Windows PC runs qwen3.6:35b with a 196608 token context window.

然后开一个全新的进程问它:

我:  What context length does my winpc model run with? Answer from memory only.
它:  Your Windows PC model (qwen3.6:35b) runs with a 196,608 token context window.

跨会话记忆确认可用。 但如果我信了那句 “Done. Saved to memory” 就不会去查,也就不会知道文件在哪、更不会知道它到底存没存。

这是整个过程里我最想强调的一点:程序说“完成了”,和事情真的完成了,是两件需要分别验证的事。

开机自启:四个服务,三个第一次都没起来

这台机器上现在有四个服务要开机自启:

OllamaServe       模型服务 + 自动预热
OpenWebUIServe    网页版 + 代理
DshServe          编程 agent
HermesGateway     Telegram 机器人

模式都一样:一个守护脚本(崩了自动重启)+ 一个 schtasks 开机任务:

schtasks /create /tn HermesGateway ^
  /tr "powershell.exe -NoProfile -ExecutionPolicy Bypass -File F:\hermes\serve-hermes.ps1" ^
  /sc onstart /ru SYSTEM /rl HIGHEST /f

但配置正确不等于能跑起来。 我每个都实拉验证了一次(停掉手动实例,用计划任务启动),四个里有三个第一次失败:

失败一:SYSTEM 找不到命令

'dsh' 不是内部或外部命令

因为 dsh 是 npm 用户级全局安装,在 C:\Users\<用户>\AppData\Roaming\npm。计划任务以 SYSTEM 身份跑,读不到那个用户的 PATH。写绝对路径解决。

这个错误很好查 —— 它明确报错了。

失败二:进程活着,但什么都没启用

这个阴险得多。Hermes 的 gateway 启动了、进程在跑、任务状态 Running,但手机上发消息没反应。日志里只有两行 WARNING:

No env user allowlists configured
No messaging platforms enabled

它读到了一份空配置,于是“正常地”什么都不做。

根因同样是 SYSTEM 身份:Hermes 用 HERMES_HOME(默认是调用账户的 %LOCALAPPDATA%\hermes)定位 config.yaml 和 .env。SYSTEM 下那个路径解析到别处,读到空的,没有 token 也没有白名单。

解法是在守护脚本里把路径钉死:

$env:HERMES_HOME  = "C:\Users\syang\AppData\Local\hermes"
$env:LOCALAPPDATA = "C:\Users\syang\AppData\Local"
$env:USERPROFILE  = "C:\Users\syang"

如果我只检查“进程在不在”,会得出部署成功的结论。

排查过程里我还犯了个错值得记:为了确认 SYSTEM 的 %LOCALAPPDATA% 是什么,我起了个进程去 echo 它,得到的结果和用户账户一样 —— 让我一度排除了路径的嫌疑。但那个探测进程是从用户会话派生的、继承了用户环境,探测方法本身就是无效的。真正的证据是日志里那两行 WARNING,而不是我构造的测试。

失败三:多个实例抢同一个端口

编程 agent 那次:我以为自己重启了三次服务,实际每次新实例都因端口被占而立刻崩溃、守护脚本再拉起、再崩溃,而浏览器全程连着那个老进程 —— 所以我改的配置“看起来没生效”。

原因是我按命令行字符串匹配进程来杀,而真正持有端口的子进程命令行和我以为的不一样。正确做法是按端口占用者杀:

Get-NetTCPConnection -LocalPort $p -State Listen |
    ForEach-Object { Stop-Process -Id $_.OwningProcess -Force }

现在的样子

winpc(RTX 3060 12GB / 32GB RAM)
├─ Ollama            qwen3.6:35b 常驻,196608 上下文
├─ thinkoff 代理     剥离思考 + 锁定上下文 + 请求日志
├─ Open WebUI :3000  浏览器,经 Tailscale
├─ dsh :3080         编程 agent,仅本机 + SSH 隧道
└─ Hermes gateway    Telegram 机器人,跨会话记忆

四个服务全部开机自启,崩溃自动重启,每一个都实拉验证过

日常用法变成了:在 Telegram 里给自己发消息。问问题、让它读文件、记住点什么,下次接着聊。数据不出家门,不花 API 费用,手机在任何地方都能用。

如果你也要装

按顺序:

  1. 先读安装脚本,curl | bash 之前花五分钟看看它做什么
  2. 配置项提示“不认识这个键”就当它失败了,去源码或 config 文件里找正确的键名
  3. 安全配置要验证生效,不能只看命令有没有报错
  4. 开机任务必须实拉一次,配置正确和能跑起来是两回事
  5. 服务在跑 ≠ 服务在工作,查它的功能日志而不只是进程列表
  6. 杀进程按端口,不要匹配命令行

第 5 条是这次最大的收获。前一篇我总结说「最花时间的是搞清楚为什么它没按预期工作」,这次要补一句:其中最难的一类,是它看起来正在工作。