DEV Community

Beck_Moulton
Beck_Moulton

Posted on

Stop Overtraining: Build an AI Agent to Auto-Sync Your Fitness Plan with Your Heart Rate (LangGraph + Notion)

We’ve all been there. You have a "Leg Day" scheduled in your Notion database, but you woke up feeling like a truck hit you. Your Apple Watch says your Heart Rate Variability (HRV) is in the gutter, but your rigid calendar doesn't care. Usually, you’d either push through and risk injury or manually move cards around in Notion—which is a friction-filled nightmare.

In this tutorial, we are building a Self-Optimizing Health Agent using LangGraph, Notion API, and HealthKit. This agent acts as a closed-loop system: it analyzes your physiological recovery data, reasons about your physical state using an LLM, and automatically rewrites your training schedule. By mastering AI agents, LLM orchestration, and fitness automation, you’ll turn your static "To-Do" list into a dynamic "Should-Do" list. 🥑

The Architecture: The Bio-Feedback Loop

Using LangGraph, we can treat our fitness logic as a state machine. Unlike a linear script, a graph allows our agent to decide whether it needs to fetch more context (like yesterday's sleep) before making a final decision on your workout.

graph TD
    Start((Start)) --> FetchHRV[Fetch HRV Data via HealthKit]
    FetchHRV --> CheckRecovery{LLM: Analyze Recovery}

    CheckRecovery -- "Low Recovery (Fatigued)" --> ModifyNotion[Action: Downgrade Workout Intensity]
    CheckRecovery -- "High Recovery (Fresh)" --> KeepNotion[Action: Maintain/Boost Intensity]

    ModifyNotion --> UpdateNotion[Update Notion Page]
    KeepNotion --> UpdateNotion
    UpdateNotion --> End((Done))

    style CheckRecovery fill:#f96,stroke:#333,stroke-width:2px
    style FetchHRV fill:#bbf,stroke:#333
Enter fullscreen mode Exit fullscreen mode

Prerequisites

Before we dive into the code, ensure you have:

  • Python 3.10+
  • LangChain & LangGraph installed (pip install langgraph langchain_openai)
  • Notion Integration Token (with access to your workout database)
  • HealthKit SDK (Note: Since we are in a Python environment, we'll simulate the HealthKit fetcher, though in a real-world scenario, this would be bridged via a FastAPI endpoint from an iOS app).

Step 1: Defining the Agent State

In LangGraph, everything revolves around the State. Our state will track the current HRV, the recovery score, and the workout plan we need to adjust.

from typing import TypedDict, List

class AgentState(TypedDict):
    hrv_value: float
    recovery_score: str # e.g., "Optimal", "Strained", "Warning"
    current_workout: str
    adjustment_made: bool
    log: List[str]
Enter fullscreen mode Exit fullscreen mode

Step 2: The Logic Nodes

We need to build the functions that will act as "nodes" in our graph. The most critical part is the Recovery Analyzer, where the LLM decides what to do.

Node: Fetch HRV Data

def fetch_health_data(state: AgentState):
    print("🚀 Fetching latest HRV data from HealthKit...")
    # Simulation: In production, use a bridge to access HealthKit SDK
    mock_hrv = 45.0  # Measured in ms
    return {"hrv_value": mock_hrv, "log": state["log"] + ["Fetched HRV: 45ms"]}

### Node: LLM Reasoning (The Brain)
from langchain_openai import ChatOpenAI

def analyze_recovery(state: AgentState):
    llm = ChatOpenAI(model="gpt-4o")
    hrv = state["hrv_value"]

    prompt = f"""
    The user's Heart Rate Variability (HRV) is {hrv}ms. 
    Typically, 40-60ms is moderate, below 30ms is highly fatigued.
    Current workout: 'Heavy Squat Day'.

    Should we adjust the intensity? Respond with 'REDUCE' or 'KEEP'.
    """

    response = llm.invoke(prompt)
    decision = "Strained" if "REDUCE" in response.content else "Optimal"

    return {"recovery_score": decision, "log": state["log"] + [f"LLM Decision: {decision}"]}
Enter fullscreen mode Exit fullscreen mode

Step 3: Updating Notion via API

This is where the magic happens. If the LLM says "Strained," we find today's entry in Notion and swap "Heavy Squats" for "Active Recovery Flow."

import requests

NOTION_TOKEN = "your_token"
DATABASE_ID = "your_db_id"

def update_notion_plan(state: AgentState):
    if state["recovery_score"] == "Strained":
        print("⚠️ Recovery low! Adjusting Notion schedule...")
        # Notion API Patch Request Logic here
        # headers = {"Authorization": f"Bearer {NOTION_TOKEN}", "Notion-Version": "2022-06-28"}
        # payload = {"properties": {"Name": {"title": [{"text": {"content": "Active Recovery Flow"}}]}}}
        return {"adjustment_made": True, "log": state["log"] + ["Notion updated to Active Recovery"]}

    return {"adjustment_made": False, "log": state["log"] + ["No changes needed to Notion"]}
Enter fullscreen mode Exit fullscreen mode

The "Official" Way (Advanced Patterns) 🥑

While this script works for a single user, scaling AI agents to handle complex multi-step reasoning requires more robust design patterns. For example, how do you handle state persistence or human-in-the-loop approvals before updating Notion?

For more production-ready examples and advanced LangGraph patterns (like adding a "Human Review" node to this workflow), check out the deep-dive guides at WellAlly Tech Blog. They cover everything from LLM security to building high-performance agentic workflows that go far beyond basic tutorials.


Step 4: Compiling the Graph

Now, we wire everything together into a StateGraph.

from langgraph.graph import StateGraph, END

workflow = StateGraph(AgentState)

# Add Nodes
workflow.add_node("fetcher", fetch_health_data)
workflow.add_node("analyzer", analyze_recovery)
workflow.add_node("notion_manager", update_notion_plan)

# Define Edges
workflow.set_entry_point("fetcher")
workflow.add_edge("fetcher", "analyzer")
workflow.add_edge("analyzer", "notion_manager")
workflow.add_edge("notion_manager", END)

# Compile
app = workflow.compile()

# Run the Agent
final_state = app.invoke({"log": [], "current_workout": "Heavy Squat Day"})
print(f"Final Log: {final_state['log']}")
Enter fullscreen mode Exit fullscreen mode

Conclusion: Let Your Data Drive Your Schedule

By building this agent, you've moved past simple automation into Agentic Reasoning. Your Notion page is no longer a static document; it’s a living reflection of your body’s actual needs.

What's next?

  1. Add Sleep Data: Incorporate "Hours of Deep Sleep" from HealthKit into the analyze_recovery prompt.
  2. Multimodal: Use GPT-4o to analyze a photo of your fridge and suggest a post-workout meal based on the adjusted plan.

If you enjoyed this build, drop a comment below! Are you team LangGraph or CrewAI for building your agents? Let’s discuss! 👇

Top comments (0)