我的 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 分以內。這一點跟後面有關:一個每次都要重新解釋什麼叫乳酸閾課的通用教練對我毫無用處,而這正是這套搭建要解決的問題。

從跑完到評論出現之間發生了什麼

整條鏈路是六步:

  1. 手錶把活動發給 Strava。這一步對所有人來說都已經在發生。
  2. 每 15 分鐘,一個小 Python 腳本問 Strava 有沒有新東西。
  3. 一旦發現新訓練,它在我的儲存庫裡生成一份 Markdown 訓練卡:分段、配速、里程、心率。只放測出來的資料。
  4. 它同時改寫 Strava 上活動的標題和描述,好讓我的動態流不再顯示"下午跑步"。
  5. 然後它給 AgentsRoom 發一條簽名過的訊息,開啟一個 Claude 代理,這次訓練已經在它手上。
  6. 這個代理幹教練的活:讀、比較、寫分析、調整本週、提交、推送、在 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 次。離上限差得很遠,但這種東西是事前就該核對的,不是事後。

Strava API 應用的設定頁面:標準開發者層級、client ID、被遮住的 client secret、讀取範圍的 access token 和 refresh token,以及顯示出來的速率限制,整體每 15 分鐘 600 次請求、每天 6,000 次,讀取每 15 分鐘 300 次、每天 3,000 次。

第 2 步:腳本帶著簽名喚醒代理

從這裡開始有意思了。

webhook 是提問的反面。與其每五分鐘問一次有沒有新東西,你把一個網址交給某個程式,事情發生時由它給你發訊息。什麼都沒發生的時候,你什麼都不用付。

AgentsRoom 提供的正是這個:一個 webhook 觸發器。你在應用裡建立一個觸發器,它給你一個 URL 和一個金鑰。任何人往這個 URL 發一條 JSON 訊息,都會開啟一個代理,帶著你寫好的提示詞,訊息內容已經注入其中。

AgentsRoom 的工具選擇器,Triggers 圖示上浮著一條提示"Agent runs on a schedule or a webhook"。

我的腳本發出的訊息故意做得極小:

{
  "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}}。所以代理一開始就已經握著這次訓練,不用再去找。

AgentsRoom 的觸發器編輯器,名稱是"Coach · {{event.title}}",下面是教練提示詞,開頭寫著"新訓練已從 Strava 匯入",接著是 event 變數、先讀 CLAUDE.md 的指令、關於無人值守執行的警告,以及儀式的第一步。

下面是它在觸發器裡的骨架:

新訓練已從 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 上下文
推理強度
權限模式自主
瀏覽器訪問開啟

AgentsRoom 的觸發器列表,其中一行是"Coach · {{event.title}}",webhook 標籤,來源"Any service (JSON)",專案 Running Performance Coach,以及代理設定:Opus 模型、高推理強度、自主模式、瀏覽器開啟。

長上下文不是花架子。為了把一次訓練判斷準確,教練要讀今天的卡、本週的卡、參考配速表,以及前面三次訓練。一次訓練從來不能孤立評判:累積負荷、日子的先後安排、正在盯著的觀察項,會把結論整個翻過來。兩小時長距離第二天跑出的三個 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 APIStrava 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 代理。

免費下載 AgentsRoom

配套應用:隨時隨地監控你的 Agent

使用 Claude、Codex、Antigravity CLI 或其他 AI 提供商。

獲取擴充功能
Chrome Web Store

把 Bug 和需求直接傳送到您的公開待辦清單。

AgentsRoom 實際執行一瞥。

多專案管理
多供應商
多代理執行
即時狀態
檔案差異與提交
行動應用
即時預覽
代理團隊
瀏覽器自動化
Backlog 驅動開發
提示詞庫
技能庫
檢視所有功能

繼續閱讀