我的 AI 跑步教练是一个 Git 仓库加一个 Claude 智能体
跑完一趟,手表自动同步,三分钟后分析已经写进我的仓库,本周计划被重新调整过,教练还在 Strava 活动下留了一条评论。没有做 App,没有写服务器,没有按 token 计费的账单:一个 Claude 订阅、AgentsRoom,加上一堆 Markdown 文件。下面是完整的搭建过程,可以照着复现。
我跑完这一趟。手表照例自己同步到 Strava。我去洗澡。
等我洗完出来,有三件事已经在我什么都没碰的情况下发生了。这次训练的分析写进了我的训练仓库。本周计划被调整过,旁边记着改动的理由。而在 Strava 活动下面,有一条我教练的评论,告诉我这次训练值多少分,以及它让周五的安排变成什么样。
这位教练不是我做出来的应用。它是一个装满 Markdown 文件的 Git 仓库、一个 Claude 订阅,加上把这一切串起来的 AgentsRoom。没有写服务器,没有按 token 计费的账单,组装大概花了一个周末。
整套东西已经作为模板公开:github.com/AgentsRoomDev/running-performance-coach。你可以克隆下来,把留空的位置填上,它就是你的了。这篇文章一块一块讲它怎么运作,前提只假设你听说过 API 这个词,但从没写过 webhook。
先交代背景:我跑了很多年,马拉松 2 小时 47 分,半马 1 小时 13 分 59 秒,10 公里 33 分 45 秒。当前这个周期的目标是把 10 公里重新跑回 34 分以内。这一点跟后面有关:一个每次都要重新解释什么叫乳酸阈课的通用教练对我毫无用处,而这正是这套搭建要解决的问题。
从跑完到评论出现之间发生了什么
整条链路是六步:
- 手表把活动发给 Strava。这一步对所有人来说都已经在发生。
- 每 15 分钟,一个小 Python 脚本问 Strava 有没有新东西。
- 一旦发现新训练,它在我的仓库里生成一份 Markdown 训练卡:分段、配速、里程、心率。只放测出来的数据。
- 它同时改写 Strava 上活动的标题和描述,好让我的动态流不再显示"下午跑步"。
- 然后它给 AgentsRoom 发一条签名过的消息,打开一个 Claude 智能体,这次训练已经在它手上。
- 这个智能体干教练的活:读、比较、写分析、调整本周、提交、推送、在 Strava 上评论、把长报告用邮件发给我。
前五步是水管工程。第六步才是这篇文章要讲的。
训练日志是一个 Git 仓库,不是数据库
这是改变了一切的决定,也是最让人意外的那个。
一次训练 = 一个文件,journal/2026/2026-09-03.md。一周 = 一个文件,plan/weeks/2026-W36.md。一次计划变更 = 一次提交,理由写在提交信息里。没有数据库,没有表结构,没有迁移,没有界面。
按重要性排,有三个后果。
教练可以重读自己的历史。 它知道自己三周前开了什么处方,也能检查那到底管不管用。一个你把训练讲给它听的聊天机器人,每次对话都从零开始。一个拥有仓库的智能体有记忆,而且这份记忆人也读得懂。
我在手机上用 GitHub App 看计划。 仓库里的 README.md 不是一个介绍页:它是我的仪表盘。写在 CLAUDE.md 里的契约对此说得很明白,只要 README 还没反映出来,任何排课都不算做完。结果是:我没有任何界面要维护,却有一块屏幕告诉我今天该干什么。
没有什么是不可撤销的。 智能体写的每一样东西都是一次提交。我可以读它、反驳它、revert 它。这跟一个应用自顾自做决定完全不是一回事。
第 1 步:Strava 唤醒一个小脚本
Strava 提供了 API:一种让程序开口问"把这位运动员最近的活动给我"的方式。strava_sync.py 这个脚本做的正是这件事,然后把返回结果变成一份训练卡。
有意思的不是那次网络调用,而是重建。手表记录的是原始分段。脚本必须推断出那到底是一次什么课:
Lap 1 : 4.40 km in 26'07 (5:56/km) ← 热身
Lap 2 : 1.00 km in 3'41 (3:41/km) ← 第 1 组
Lap 3 : 0.20 km in 1'59 (9:55/km) ← 间歇休息
... → "5 x 1000m r' 2'"
它把"最快的 k 个分段就是重复段"这种形式的切分方式全试一遍,留下站得住脚的那个最优解。听起来很简单,其实不是:按速度做朴素聚类,只要热身比间歇休息更快就会被骗过去。
尤其要注意,训练的形状是从手表重建的,绝不从计划重建。反着做很诱人(计划上写着 5 x 1000m,照抄就是了),而那恰好是错误:整件事的意义就在于识别出我做了别的内容的那些日子。当两者对不上时,这个对不上本身就是结论,教练看得见:
计划 3 x 8' → 实际连续跑
动手之前,有两个提醒。
Strava 的 API 从 2026 年 6 月起需要付费的开发者订阅。 没有它,每次调用都会返回 403 Application Status Inactive。退路是有的,模板里也内置了:从手表导出一个 TCX 文件,交给 import_tcx.py。导入之后的所有环节运作方式完全相同。
配额很宽松,但确实存在。 在我的应用上,读取是每 15 分钟 300 次请求、每天 3,000 次。稳定运行时脚本每轮只用一次,也就是每天 96 次。离上限差得很远,但这种东西是事前就该核对的,不是事后。

第 2 步:脚本带着签名唤醒智能体
从这里开始有意思了。
webhook 是提问的反面。与其每五分钟问一次有没有新东西,你把一个网址交给某个程序,事情发生时由它给你发消息。什么都没发生的时候,你什么都不用付。
AgentsRoom 提供的正是这个:一个 webhook 触发器。你在应用里创建一个触发器,它给你一个 URL 和一个密钥。任何人往这个 URL 发一条 JSON 消息,都会打开一个智能体,带着你写好的提示词,消息内容已经注入其中。

我的脚本发出的消息故意做得极小:
{
"type": "created",
"title": "03/09 · 5 x 1000m r' 2'",
"body": "03/09/2026 的训练已从 Strava 导入。\n\n质量课:5 x 1000m r' 2'\n分段:3'41 - 3'40 - 3'38 - 3'40 - 3'35\n\n总里程:12.51 km,用时 1h07'42 (5:25/km),累计爬升 56 m\n计划中的课:RP10-5x1000\n\n训练卡:journal/2026/2026-09-03.md\n周计划卡:plan/weeks/2026-W36.md"
}
请注意这里没有什么:计划的正文。webhook 携带的是计划课的代号和几张卡片的路径,从不携带它们的内容。拥有仓库的智能体会自己去读;没有仓库的智能体,没有资格收到我的内部指令。这和发布到 Strava 上的描述遵循同一条规则。
签名,以及随之而来的那个坑
一个能打开智能体的公开 URL,不能对任何撞见它的人都敞开。所以触发器是带签名的:脚本用共享密钥算出消息的指纹(如果这个词对你有意义,那就是 HMAC-SHA256),放在 X-AgentsRoom-Signature 请求头里发出去。服务端在自己那边重算同一个指纹;对不上就拒绝。
没有签名时,回答很干脆:
{"error":"REJECTED","message":"Signature missing."}
而这就是那个坑,它花掉了我一个晚上。签名覆盖的是真正发到线路上的那串字节,不是内存里的对象。如果你对磁盘上的文件签名,然后让另一层重新序列化这个对象(多一个空格、键的顺序不同、重音符号换了一种转义方式),你就得到了一个完全有效的签名,对应的却是服务端永远收不到的那条消息。这种拒绝无从调试:两边看起来都完全正确。
修法一句话讲得完:在同一个地方序列化,在同一个地方签名。模板里由 post_json 函数同时负责这两件事,别的任何代码都无权碰消息体。
第 3 步:三个层次告诉教练它是谁、这里怎么运作、现在该做什么
一个做教练的智能体,不是一大坨提示词。它是三段分开的文本,这个分离很重要。
第 1 层,人设:它是谁
一段在 AgentsRoom 里挂到智能体上的系统提示词。它承载训练哲学,而且刻意做成对项目通用的:换谁它都能带。
你的工作不只是生成训练计划。你要通过分析运动员的训练、理解他当前的状态、调整接下来的课,持续地为他做教练。[…]说话要像一个有经验的教练,不要像一个打鸡血的聊天机器人。
它也写明了自己不做什么:不把一次训练仅仅按"目标配速有没有守住"来评判,对成绩预测的不确定性要说清楚,不因为运动员想要就认可一个目标。最后这一条才是让教练真正有用的地方。
你不必自己写:这个人设已经发布在 AgentsRoom 的智能体目录里,名字是跑步表现教练。一键安装,装完即用。
第 2 层,CLAUDE.md:这里怎么运作
这是契约,每次会话开始时都会先读一遍。它包含文件结构、维持结构一致的规则、约束所有建议的训练原则,以及最重要的仪式:一次训练被上报时要走的确切流程。
摘一段,因为它能体现精细程度:
一周乱掉时的牺牲顺序: 先砍轻松跑上多出来的分钟数,然后是力量训练,然后是长距离的长度,最后才是一节质量课。永远不要砍掉整周。
就是在这里,教练不再是一个聊天机器人。它不是每次现场即兴发挥一套流程,而是照着我一次性写好的那套来。如果你只打算读模板仓库里的一个文件,就读这个。
第 3 层,触发器提示词:现在该做什么
这是一次训练落地时交给智能体的消息。它通过模板变量接收活动信息:{{event.title}}、{{event.body}}、{{event.url}}。所以智能体一开始就已经握着这次训练,不用再去找。

下面是它在触发器里的骨架:
新训练已从 Strava 导入。
**{{event.title}}** · 活动 {{event.id}}
{{event.url}}
{{event.body}}
---
你在 `training-plan` 仓库里。先读 `CLAUDE.md`:它就是法律。
你用我的语言写,全程直接对我说话(§3)。
§6 的仪式适用,但它的**第 1 步已经做完了**:`strava_publish.py`
已经创建了训练卡,也已经提交。你从第 2 步接着往下,一直做到底。
三项交付物,按这个顺序:**仓库里的分析**、
**Strava 活动下的评论**、**邮件**。
⚠️ **你是无人值守运行的:没有人会读到问题。** 绝不要请求裁决:
你自己决定,自己动手,然后在报告里说明你定了什么、为什么。
## 1 · 分析与调整计划(§6 仪式,第 2 步到第 6 步)
1. 先 `git pull --rebase`:训练卡可能来自服务器。
2. 按这个顺序读:今天的卡、本周的卡、
`athlete/zones-and-paces.md`,以及**最近 3 张训练卡**:
一次训练从来不能孤立评判。
3. 写 `## Analysis` 一节:**结论在前**,然后是支撑它的信号,
最后是它改变了什么。
⛔ 如果 `## Analysis` 已经填过,不要重写。
4. 更新本周的卡,把**每一处**计划改动连同理由记在
`## Adjustments` 下面。
5. **重新生成 `README.md`**:这是我在手机上看的那块屏。
6. 提交与推送,路径写明确,⛔ 绝不使用 `git add -A`。
## 2 · 在 Strava 上点赞和评论
⛔ Strava 评论是公开的:不写目标心率,不写小伤小痛,
不写内部取舍,不写预测成绩。
## 3 · 完整报告发邮件
出力最多的那一行在中间:"没有人会读到问题"。一个在屏幕前没人看着的情况下运行、却要请求裁决的智能体,它不是在犯错,它只是停住了,而你第二天才发现。
用哪个模型,以及为什么一百万 token 不是虚荣
| 设置 | 取值 |
|---|---|
| 模型 | Claude Opus,1M 上下文 |
| 推理强度 | 高 |
| 权限模式 | 自主 |
| 浏览器访问 | 开启 |

长上下文不是花架子。为了把一次训练判断准确,教练要读今天的卡、本周的卡、参考配速表,以及前面三次训练。一次训练从来不能孤立评判:累积负荷、日子的先后安排、正在盯着的观察项,会把结论整个翻过来。两小时长距离第二天跑出的三个 3'38,跟休息日之后同样的 3'38,讲的不是同一个故事。
自主模式不是马虎,而是必然结果:一次屏幕前没人的运行,没有人能批准 git push。而浏览器访问才让智能体能去 Strava 评论、能把邮件发出去,这两件事在这里都没有趁手的 API。
什么被自动化了,什么是故意不自动化的
这是我最满意的设计决定,也最容易被忽略。
导入任务只记录和发布,从不评判。
| 脚本做什么 | 它不做什么 |
|---|---|
| 拉取新的活动 | 填写 Analysis 一节 |
| 创建训练卡 | 动本周的卡 |
| 在 Strava 上写标题和描述 | 动参考配速 |
| 提交它创建的卡 | 给出任何意见 |
一个开始评判的脚本,会产出没有上下文的结论,逻辑还被冻在没人回头看的代码里。评判意味着同时握住本周的负荷、当下的状态、上一次说过的话:那是教练的活,由把整份材料摊在面前的智能体来做。
实际好处立竿见影:当智能体没有跑起来时(机器关着、API 挂了),训练卡照样存在。什么都没丢,只是少了那条评论,重放一次就补上了。
还有一个方向一致的选择:脚本不维护状态文件来记住自己处理过什么。Strava 上的描述才是事实来源。 描述为空,它就写;带着它自己的签名,它就跳过;非空又没有签名,那是你写的,它不碰。一个本地状态文件根本说不出另一台机器做过什么;这样一来,两台机器可以同时跑而不互相踩脚。
还有两条分离规则,刻在仓库里,不许绕开:
- 发布到 Strava 的描述绝不照抄计划的正文:我的周计划卡里有目标心率和各种取舍,它们不该出现在一次公开活动上;
- 手写的描述绝不被覆盖。
落在活动下面的那条评论
重点不是自我表扬。重点是教练的结论能从我的手机上、在活动下面读到,不用打开仓库,而且它会一直待在那里,跟这次训练绑在一起。
所以这条评论刻意做得很窄:一个表示结论的表情、支撑它的那个数字,以及它对下一次训练意味着什么改动。大约 250 个字符。
✅ 五组平均 3'39,目标是 3'38-3'44,心率在整个组块里都很平。配速表站得住。周五继续轻松跑:本周的余量你已经花掉了。
长版本,也就是带心率、带我标出的观察项、带下周里程取舍的那一份,进仓库和邮件。两条通道,两拨读者,守住这条边界的是提示词。
只有到了生产环境才会坏的三件事
下面每一行的存在,都是因为没有它的时候出过事。它们比这篇文章其余部分更有教益。
1. 把浏览器钉死。 我的 Chrome 里连着两个 Claude 扩展。没有任何机制保证智能体拿到的是哪一个,而只有其中一个持有 Strava 的登录态。结果是:每跑两次就有一次,智能体落在错误的浏览器里,处于登出状态,什么都评论不了。按设备 id 选择浏览器这件事不会跨会话保留:所以它必须写在提示词里,还要明令禁止它去问用户该选哪一个。无人值守时,一个问题就是一次死锁。
2. Strava 的评论输入框没有 maxlength。 浏览器里没有任何东西拦着你写太长:在提交时拒绝的是服务端。一个写出漂亮的 600 字符段落的智能体,会把它整段敲进去,点"发布",然后撞上一个自己看不懂的失败。所以提示词必须在动笔之前就强制简短,还要预备好这种情况:提交失败就缩短后重发,绝不拆成两条评论。
3. 一次活动只留一条教练评论。 当你为了测试重放事件时(一开始你会重放很多次),没有这条规则,智能体会在已经处理过的活动上不断叠评论。所以提示词让它在动笔前先读"评论"标签页,如果自己已经在里面就跳过这一轮。仓库这边同理:如果 ## Analysis 一节已经填过,就不重写。
这套东西要花多少钱
| 部件 | 在哪 | 花费 |
|---|---|---|
| 教练智能体 | 我的机器,通过 AgentsRoom | 我的 Claude 订阅 |
| 每 15 分钟的轮询 | 一台常开的小 Linux 机器 | 约 5 欧元/月,Raspberry Pi 上为零 |
| 日志 | 一个私有 Git 仓库 | 免费 |
| Strava API | Strava Developer Program | 见 Strava 的定价 |
这套搭建里没有按 token 计费的 API key。这是我觉得最被低估的一点:同样的东西如果建在按用量计费的 API 上,每次训练都会有一个计价器在转,我大概不会一直留着它。
这个周末就把它搭起来
按顺序的步骤。如果你已经有 Strava 账号和 Claude 订阅,留一个晚上就够。
1. 克隆模板,把它变成你自己的。
git clone https://github.com/AgentsRoomDev/running-performance-coach.git my-coach
cd my-coach
rm -rf .git && git init
把你的副本设成私有。 训练日志里有健康数据:心率、睡眠、伤病。模板是公开的,你的副本不该是。
然后按这个顺序填:athlete/profile.md(作为跑者的你)、athlete/records.md(你的个人最好成绩)、athlete/constraints.md(你真正能挤出来的时段)、athlete/zones-and-paces.md(你的参考配速)、plan/objective.md(目标赛事和目标成绩),然后是 CLAUDE.md,把里面每一处 {{...}} 占位替换掉。
最后,用你的 Claude 智能体打开这个仓库,对它说:"读一下 CLAUDE.md 和 athlete/,然后给我搭出第一周。"
2. 接上 Strava。
cp .env.example .env && chmod 600 .env
python3 scripts/strava_oauth.py # 在浏览器里点一次,只需一次
python3 scripts/strava_sync.py --dry-run
--dry-run 会打印出将要写入的内容,但什么都不写。这正是检查训练重建结果合不合你心意的时刻。
3. 在 AgentsRoom 里创建触发器。 在 Triggers 下选 New trigger:
| 字段 | 取值 |
|---|---|
| 类型 | Webhook,来源 generic |
| 提示词 | docs/trigger-prompt.md 的内容 |
| 角色 / 人设 | docs/coach-persona.md |
| 权限模式 | 自主 |
| 浏览器访问 | 开启 |
AgentsRoom 会生成一个 URL 和一个签名密钥。把两者都放进你的 .env:
WEBHOOK_URL=https://agentsroom.dev/api/triggers/t_xxxxxxxxxxxx
WEBHOOK_SECRET=whsec_xxxxxxxxxxxxxxxxxxxxxxxx
4. 先测试,再信任它。
python3 scripts/webhook_replay.py scripts/examples/webhook-session.json --dry-run
python3 scripts/webhook_replay.py scripts/examples/webhook-session.json
这会把一次训练重放进触发器,不用等你下次跑步,也不会动到自动任务的状态。你应该看到 ✅ HTTP 202,AgentsRoom 里也应该弹出一个智能体标签页。
5. 让它每 15 分钟跑一次。
bash scripts/systemd/install.sh # 在 Linux 服务器上
一个 oneshot 单元加一个定时器:没有常驻进程,机器关机期间错过的那一轮会在下次开机时补上。
如果你没有常开的机器,跳过这一步:想起来的时候手动跑一下 strava_sync.py,或者干脆在对话里把这次训练讲给智能体听。CLAUDE.md 里的仪式照样有效。你失去的是自动化,不是教练。
抛开跑步,我从中得到的
这套搭建没有任何一处是跑步专有的。它展示的是一个可复用的模式,几乎适用于任何一个你会积累个人数据、又希望有人给出内行意见的领域。
三个部件,就这些。一个装 Markdown 文件的 Git 仓库,作为机器和你都能读的记忆。一个唤醒智能体的事件,取代一个循环轮询、白白烧 token 的智能体。三层配置,干净地分开智能体是谁、它在你这里怎么工作、它此刻该做什么。
把"跑步训练"换成"银行流水""写代码的会话""血糖读数"或者"读书笔记":机制不变。
常见问题
做一个 AI 跑步教练需要会写代码吗
你需要能在终端里跑一条命令,能编辑一个文本文件。模板仓库克隆下来就能用,Python 脚本只用标准库(不需要 pip install),教练那一部分是靠在 Markdown 文件里写普通句子来配置的。真正的工作不是技术性的:是老实描述自己作为跑者是什么水平、目标是什么。
每个月要花多少钱
智能体跑在你已经有的 Claude 订阅上(Pro 或 Max):没有按 token 计费的 API key。除此之外,你可能想要一台常开的小机器,每 15 分钟去问一次 Strava,VPS 上大约每月 5 欧元,Raspberry Pi 上则是零。私有 Git 仓库免费。剩下的是 Strava API,从 2026 年 6 月起需要付费的开发者订阅。
为什么用 Git 仓库而不是数据库
因为历史变成了教练和你都读得懂的东西。每次训练是一个 Markdown 文件,每次计划变更是一次带理由的提交。智能体可以重读自己三周前开出的处方,检查它是否奏效;你则在手机上用 GitHub App 看自己的计划,一行界面代码都不用写。
用大白话说,webhook 是什么
webhook 是一种反过来找你的服务,不是你去找它。与其每五分钟问一次有没有新东西,你把一个网址交给某个程序,事情发生时由它给你发消息。在这里,导入训练的脚本把这条消息发给 AgentsRoom,AgentsRoom 在一秒内打开一个 Claude 智能体。这也是这套搭建省钱的原因:一个循环轮询的智能体每一轮都在烧 token,而一个 webhook 触发器在什么都没发生时不花一分钱。
跑步以外的运动能用吗
能。导入过程重建的是手表的分段,骑车和游泳同样会记录分段。要改的是策略文件和课表清单,它们都是你可以重写的文本。机制(导入、webhook、智能体、仓库)不变。
智能体会不会判断错,把我的计划搞砸
它可能出错,但砸不了什么:它写的一切都是一次 Git 提交,你可以读、可以反驳、可以回退。CLAUDE.md 文件明确禁止它改写历史、编造你没有提供的数据、不记录理由就改计划,以及给出医疗建议。一旦疼痛可疑,它会让你去找专业人士。
模板仓库在这里:AgentsRoomDev/running-performance-coach。克隆它,填上你的配速,你就有了自己的教练。想看看唤醒智能体的那一块,webhook 触发器页面上有说明,而 AgentsRoom 在这里下载。
下载 AgentsRoom
在一个窗口中运行你所有项目的所有 AI 智能体。
配套应用:随时随地监控你的 Agent
使用 Claude、Codex、Antigravity CLI 或其他 AI 提供商。
把 Bug 和需求直接发送到您的公开待办清单。
AgentsRoom 实际运行一瞥。
继续阅读
Git worktree:让多个 AI agent 并行工作而不互相冲突的基础
并行运行时,你的 agent 会互相覆盖对方的工作。Git worktree 用两条命令就能解决。这篇文章告诉你怎么用。
阅读全文Antigravity CLI每台机器只保留一个Google登录,以下才是真正管用的做法
为什么您无法在Antigravity CLI上交替使用两份Google AI Pro订阅、它究竟把您的登录信息存在哪里、那些账户切换工具对您的系统钥匙串到底做了什么、为什么家庭方案并不会让额度翻倍,以及唯一能真正并行运行多个账户的做法。
阅读全文我在Claude里还剩多少令牌?根本没有这个数字
你的Claude订阅套餐不是按令牌计价的,所以没有任何界面会显示令牌余额。这里讲清楚它到底计量什么、去哪里看,以及为什么你没在打字它也在掉,还有2026年9月14日每周周期会发生什么变化。
阅读全文