The “Static Plan” Problem
In Episode 4, we successfully built our “Zero-UI” warehouse: an automated pipeline that takes an AI-generated JSON workout from Google Calendar and pushes it directly to my Garmin watch via Intervals.icu.
But there was still one glaring flaw in the system. The pipeline was robust, but the data was static.
What happens if I wake up with a fever? What if my Heart Rate Variability (HRV) has been tanking for three straight days due to work stress? A static plan—even one generated by the smartest LLM on Sunday—will blindly tell me to go out and do 6x3min VO2 Max intervals on Wednesday. Doing intense intervals when your central nervous system is fried is a one-way ticket to overtraining and injury.
Initially, I handled this feedback loop manually. Every morning, I would check my wellness metrics on Intervals.icu, feed them into the Gemini mobile app alongside my requirements, and ask it to adapt my scheduled workout. Thanks to Gemini’s native Google Workspace integration, it could “reach out” and replace the JSON in my calendar for me. From there, the synchronization script from Episode 4 would pick up the change and ship it to my watch.
It worked, but it still required a manual morning intervention. I wanted to eliminate that “manual chat” entirely—letting the system detect my fatigue and rewrite my plan autonomously while I was still asleep. I needed Full Auto Mode.
The Architecture of Adaptation
To achieve this, we had to close the feedback loop. The script couldn’t just push workouts blindly; it needed to read my physiological state, evaluate the day’s training load against that state, and dynamically rewrite the workout if necessary, all before shipping it to the Garmin.
Here is the logic flow of our new adapt_daily_plan() orchestrator:
- Pull Wellness Data: Fetch the last 10 days of metrics (HRV, RHR, Sleep) via the Intervals.icu API.
- The “Day View” Aggregation: Instead of treating workouts in isolation, we fetch every coach event scheduled for the entire day from Google Calendar (from midnight to midnight).
- Contextual LLM Interception: We bundle all of today’s workouts into a single JSON array and send it to Gemini 3.1-Flash along with the wellness history. This is crucial: if a day has both a run and a strength session, the AI needs to see both to decide if the total daily load is sustainable given my current recovery.
- The Collective Rewrite: Gemini returns a matching array of adapted workouts. If I’m fried, it might keep the easy swim but scrap the heavy lifting, or turn everything into a rest day. It adds a
descriptionto each session explaining the coaching decision. - Patching the Calendar: The Python script rewrites the event in Google Calendar with the newly adapted JSON. Crucially, it nests the original workout inside an
original_workoutproperty so we never lose the macro-plan history. - Sync to Watch: The standard sync function runs, picks up the adapted workout, and ships it to the devices.
Building the Brains: Gemini 3.1-Flash
To make this reliable without human supervision, we needed speed and strict structured outputs. We use Gemini 3.1-Flash as our primary reasoning engine.
The most critical engineering aspect here isn’t the code itself (you can check the open-source repository for the exact implementation), but the strict enforcement of “JSON Mode”. By configuring the LLM to exclusively reply in a strict application/json format, we eliminate the need to parse messy conversational text. The AI doesn’t reply with “Hello, here is your adapted workout…”, it acts purely as a functional endpoint returning a structured workout dictionary.
The “Coach’s Instructions”: Prompt Engineering
To ensure the AI makes sound decisions, we separated the logic into modular prompt templates. This allows us to refine the “coaching philosophy” independently of the Python orchestration logic.
- System Instructions: Defines the coach’s personality and strict rules (e.g., “If HRV is crashing, prioritize rest over ego”).
- Context Injection: Dynamically injects the 10-day wellness trend and the day’s workouts.
- Order Preservation: Forces the AI to return the adapted sessions in the exact same chronological order to ensure correct mapping back to Google Calendar.
The Catch: Losing the Conversation Context
There is a subtle engineering trade-off here. By moving to a stateless API call for my daily automation, I lose the continuous “thread” of a long-form conversation that I would have in the Gemini web or mobile app.
In the app, Gemini “remembers” everything we discussed weeks ago. With its massive context window, it can draw connections between a comment I made in February and its impact on a workout in March. My automated script, however, is stateless: every morning at 6:00 AM, it’s a “fresh start”. While this approach is cleaner and faster, it doesn’t yet leverage the full power of Gemini’s deep context memory as well as a single, month-long chat thread might.
Beyond context, there is also a pragmatic matter of Token Economics. Running a multi-month coaching conversation through an API would eventually become prohibitively expensive as the history grows. By using the Gemini Web App for my primary preparation, I benefit from its massive context for “free” (within the consumer subscription), reserving the paid API credits for what they do best: precise, high-value daily automation.
However, implementing this second “stateless” path is also a deliberate engineering test. It creates a perfect opportunity to benchmark both approaches: comparing how a highly conversational coach (the manual chat) and a strict, data-driven orchestrator (the automated API) handle the same fatigue spikes.
For my actual preparation for the 160km in Chiang Mai, I’ll be sticking with the manual conversational app as my primary guide—preserving that vital long-term context—while using this automation project to explore the boundaries of autonomous AI coaching.
Beyond the Calendar: A “Brain-First” Refactor
During this phase, we also made a significant architectural shift. Initially, the system was “Calendar-centric”—it was designed specifically to sync Google Calendar events. But as we added wellness adaptation, we realized the core of the project wasn’t the calendar; it was the decision-making engine.
We refactored the codebase to move away from hardcoded calendar dependencies toward a more abstract “Brain” architecture:
- Source Agnosticism: We renamed core tracking fields from
calendar_event_idtosource_id. Today it’s Google Calendar, but tomorrow the “source” could be a local JSON file, a Notion database, or a direct feed from another coach. - The Orchestration Layer: The
CoachServicenow acts as a central synthesizer. It separates the “Source” (Calendar), the “Context” (Wellness metrics), and the “Reasoning Engine” (Gemini API). - Decoupling for Testability: By isolating the coaching logic from the external APIs, we can now test the brain’s decisions in a vacuum. We don’t need a real Google Calendar account or a live Intervals.icu feed to verify if the AI correctly prescribes a rest day when HRV crashes. This structural rigor is what allowed us to increase our test coverage and precision significantly during this update.
This shift transforms the project from a simple “sync tool” into a modular AI coaching platform. In the age of LLMs, where code generation is becoming a commodity, the real value lies in the architecture—designing a system that is robust, testable, and vendor-independent.
The CLI Experience
As an engineer, I love the command line. Exposing this orchestration as a simple command makes the whole pipeline incredibly elegant.
# Every morning at 6:00 AM (via cron)
python -m project_chiang_m_ai adapt
python -m project_chiang_m_ai sync --todayIf I sleep poorly, I wake up, check my watch, and the brutal threshold workout is magically gone, replaced by a gentle recovery jog.
Conclusion: The Ultimate Synthetic Coach
We have successfully built a closed-loop, data-driven, adversarial AI architecture. We generate macro-plans using diverse LLMs (Episode 2), handle weekly chaos with Agile principles (Episode 3), pipe it seamlessly to our devices (Episode 4), and now, dynamically adapt the daily grind based on live physiological metrics (Episode 5).
The tech stack is ready. The automation is running in the background. The only thing left to do is lace up the shoes and actually run the 160 kilometers in Chiang Mai.
See you on the trails!