前回の記事の続きです
前回の記事では、Muse(Meta の AI エージェント)に Garmin の公式 Personal API をつないで、自分の走行データや睡眠・HRV を読み取れるようにしました。
あれで「データを読む」ことはできるようになりました。次に欲しくなったのは、走っている最中にリアルタイムで声をかけてもらうこと。Muse に走りを監視してもらって、リアルタイムに近いタイミングで Muse に声をかけてもらうことです。
なお、準リアルタイムのコーチング自体は、仕組みを作らなくても体感できます。LiveTrack を有効にして Muse に「監視して」と頼めば、走行中にデータを見てアドバイスを返してくれます。立ち止まってスマホで確認する形ですが、まず試すだけならこれで十分です。また、Apple も Samsung も似たようなサービスを提供しています(Apple のサービスは日本では未展開)。私のはそのサービスの枠組みを自作して、さらに「自分で育てるランニングコーチ」というコンセプトにしたものです。
この仕組みにおける課題は「走りながら」音声メッセージを受け取ることです。立ち止まらずに、音楽を聴きながら、耳元でアドバイスを受け取るには、受信側の仕組みが要ります。この記事はその仕組み作りの話です。

※ このアプリは個人の自作プロジェクトです。Meta 公式のアプリ・サービスではありません。
まず Garmin 公式の Spectator Messaging を試した
Garmin には LiveTrack の観戦者メッセージ機能(Spectator Messaging)があり、fenix 8 ではテキストと音声の両方を時計に送れます。これでいけるのでは、と実走テストをしました。
結果は、自分の環境では実用になりませんでした。
- 音声メッセージは時計本体のスピーカーから再生される。耳はスマホ側のイヤホンで音楽を聴いているので、走行中はほぼ聞こえない
- テキストは時計の画面に表示されるが、文字が小さく、走りながら読むのは厳しい
プログラムからは正常に送信できる(HTTP 200)ので、機能としては成立しています。ただ、再生の出口が時計である以上、スマホ+イヤホンで音楽を聴くスタイルとは相性が悪い。Garmin が時計側再生を選んだのは、スマホ OS ごとの音声仕様に左右されず確実に届くからだと思いますが、自分には合いませんでした。
ならば受信側を自作する
方針を変えて、「queue(配送箱)+ iPhone アプリ」という自前の受信経路を作りました。
構成はこうです。
- ランニング開始時に Garmin LiveTrack が始まり、開始通知メールが届く
- Muse がメールからセッションを検知し、走行データ(距離・ペース・心拍など)をポーリングしながらコーチング文を生成
- 音声合成(TTS)で MP3 化し、queue に配信
- iPhone アプリが数秒ごとに queue を確認し、新着があれば Spotify の音量を一時的に下げて(ダッキング)再生、終われば音量を戻す
iPhone アプリは Swift / SwiftUI で、Codex に仕様書を渡して実装してもらいました(このあたりの進め方は前回の記事と同様です)。
開発は AI と分担
この仕組みは、役割分担がはっきりした開発でした。
- Muse: システム構成の設計、Codex への仕様書の作成、実装結果のレビューと検証、本番サーバーへの疎通確認、そして運用そのもの(走行の監視とコーチングの生成・配信)
- Codex: 実装一式。iPhone アプリ、配信まわりのプログラム、UI の仕上げ、iPhone へのインストールまで
- 自分: 要件と判断。公式機能を実走でテストして不採用にしたこと、配信基盤の置き場所、公開の方針など
人がコードを書く場面はほぼなく、「AI エージェントに設計と運用を任せ、別の AI に実装させ、人は判断に集中する」という進め方になりました。前回の記事が「AI にデータを読ませる」話だったのに対し、今回は「AI と一緒に道具を作って、AI に運用させる」話、というのが個人的な収穫です。
Web アプリではダメだった
最初は手軽に Web ページ(PWA 的な受信ページ)で試しました。しかし iOS の Web Audio では、他アプリの音声をダッキングできませんでした。各モードを実測すると、「Spotify が止まる」「混ざるだけで音量は下がらない」のどちらかになります。ネイティブアプリで AVAudioSession を .playback + .spokenAudio + .duckOthers と正しく設定すれば下がる、というのが実機検証の結論です。ロック画面でも受信・再生できることまで確認しました。
ハマりどころ
1. クラウド経由の書き込みは「毎回許可」が挟まる
当初、queue の置き場所は GitHub でした。しかし書き込みのたびに許可確認が求められ、走行中の無人運用には使えません。自分のサイト(XServer)にアップロード用プログラムを置く形を経て、最終的には Cloudflare Worker + R2 に移しました。理由は、運用の手離れの良さと遅延の短さです。移行後、体感のタイムラグは当初の10〜15秒からさらに短くなりました。
2. TTS が長文に弱い
音声合成は、複数文をまとめて生成すると文と文の間に最長10秒を超える無音が入ることが実測で分かりました。声を変えても直りませんでしたが、文単位に分割して生成し、短い間隔で連結すると無音はほぼ消えました。英語で生成すると無音ゼロで最もきれいでしたが、走りながら聞き取る負担を考えて、日本語+分割生成を採用しています。また、TTS の原音は屋外では音楽に負けるほど小さかったため、ラウドネス正規化で平均 +9.5dB ほど引き上げたものを標準にしました。
3. ファイルが溜まる
テストのたびに音声ファイルが残ります。そこで「現在の queue から参照されていない音声を削除する」掃除の仕組みを用意し、セッションの開始・終了時に自動で空に戻す運用にしました。
4. iPhone 標準の読み上げは捨てた
配信を速くする選択肢として、テキストだけを送って iPhone 側の音声合成で読み上げる方式も試しました。同じ文面で Muse の TTS と聞き比べた結果、iPhone 側は「温かみが一切ない」と感じて廃止を決め、再生は MP3 のみに一本化しました。速さより、耳に届く声の質を優先した判断です(iPhone 側の読み上げは、アプリ自身のエラー通知のような用途にだけ残す設計にしています)。
実走テスト
1回目(11月1日): 8.11km、51通
横浜・みなとみらいで初の屋外テスト。8.11km を平均 5:56/km で走り、51通のコーチングが届きました。ダッキング、ロック画面での受信、音量のバランスは実用レベル。仕組みとしては成立、という確認が取れた回です。
2回目(11月3日): 9.02km、29通
配信基盤を Cloudflare に移し、アプリの画面も作り直しての実走です。9.02km、平均 5:26/km、3分間隔+1km ごとのラップ通知で29通を配信しました。
実際に流れた文面の例です。
「0.6 キロ。平均ペースはキロ6分36秒です。心拍は適正です。今日は新しい配信経路での初めての実走テストです。この声が届いたら、まず第一関門はクリアです。」
「7.8 キロ。平均ペースはキロ5分30秒です。心拍は少し高めです。今朝は二度寝で回復を足しました。あの判断が正解だったか、今日の脚の軽さで答え合わせですね。」
2つ目は、その日の朝に Muse と交わした会話(回復のために二度寝したこと)を元にした文面です。走行データだけでは作れない、「自分のことを知っているコーチ」らしい一言になりました。
実走で見つかった問題と、直し方
実践編なので、正直に書きます。この2回目の実走では、問題が4つ出ました。
- 開始の検知が約5分遅れた。 原因は、開始通知メールを読む際に本文が Markdown 化され、LiveTrack の URL が落ちていたこと。元の HTML から抽出する方式に変えました。
- LiveTrack の API が約2分応答しなくなり、監視が「終了した」と誤判定した。 ポーリング失敗の許容回数が少なすぎたためで、約10分までは途切れても監視を続ける設定に変えました。
- 終了メッセージが「ゼロキロ。平均ペースはキロ―でした」になった。 終了時の応答に記録が含まれず、初期値のまま送ってしまったもの。直前の正常な記録から組み立てるよう直しました。
- アプリで最初のメッセージの再生エラーが出た(停止→再開の手動操作で回復)。 走行中に手動操作はさせたくないので、再生失敗時は自動で再初期化して受信を続ける、回線が落ちたら音声で知らせて復旧時は直近の分だけ再生する、といった仕様を指示書に切り出して、アプリ側の改修待ちです。
監視を開始してから開始メールを検知するまでの無音も気になったので、検知までは1分ごとに「待機中です」という音声を流すようにしました。
コーチングの「中身」が本体だった
仕組みが通ってみると、課題は配信ではなく中身でした。距離とペースを読み上げるだけなら、Garmin 標準のラップ通知と大差ありません。そこで文面には型を決めました。
- 1メッセージは「距離 → 平均ペース → 心拍の評価 → コメント1つ」の固定順
- 距離の小数は日本語の読みで(3.6 → 「さん点ろく」)。TTS にそのまま読ませると英語読みになるため
- 心拍は数値ではなく「適正」「少し高め」のような判定で。走りながら数字を解釈するのは負担なので
- コメントは一度使ったら二度と使わない(ファイルに永続化して、再起動しても繰り返さない)
- コメントの材料は、その日の天気、コース周辺の話題、カレンダーの予定、そして Muse との直近の会話から作る
モチベーションを上げるための文面は、30件ほど用意してから、自分で読み返して取捨選択しました。残ったのは「焦るな。続けろ。」のような短いものや、平尾誠二さんの「規律とは自由を獲得するためのものです」という言葉などです。逆に、最初は気に入っていた大仰な文面ほど、自分の耳には合わずに落ちました。
結局のところ、この仕組みの肝は配信技術ではなく、**「Muse に何を語らせたいかは自分次第」**という点にあります。語る内容を自分で選び、育てていく。そういう意味で、これは「自分で育てるランニングコーチ」です。
大手も同じようなサービスを提供しているが…
今年、Apple は watchOS 26 で「Workout Buddy」を、Samsung は Galaxy Watch8 で「Running Coach」を出しました。走行データを見て、生成した音声でリアルタイムに声をかける、というコンセプトはこの仕組みと同じです。
レビューを見ると、両方とも「方向性は歓迎、でも中身はまだ浅い」という評価が目立ちます。発話が少なく汎用的で、「自分固有の事情に即した言葉は聞けなかった」という声もある。Workout Buddy は日本語にも未対応です(英語とスペイン語のみ)。
大手の強みは、買ってすぐ使えることです。逆に、中身を自分の言葉で育てる余地は、汎用品にはありません。来年の鹿児島マラソンでサブ4を狙う自分の計画も、今朝の回復の具合も、昨日交わした会話も、汎用のコーチは知りません。個人が AI エージェントと組んで作る意味は、その「文脈」の部分にあると感じています。
まとめと今後
個人で使う分には、実用段階に入りました。次のロングランから、鹿児島マラソンに向けた練習で本格的に使っていきます。
公開については段階を分けます。まずはこの記事で実績を残すこと。ソースコードの公開は、アプリ側のブラッシュアップがある程度進んでからを考えています。公開する際は、商用利用を禁止するライセンスにし、テンプレートの「育てる」部分は、イチから作るのは大変なので、各テーマの代表例をスターターとして含めるつもりです。
なるべく色んな人に使ってもらえるような形を考えていています。導入も簡単にしたいですが、初期フェーズではちょっと手間がかかるような形になるかも知れませんが、時間をかけて改善していく予定です。

