twenty-four: AI training plans - implementation details

This post covers the technical implementation of the AI training plan generator. If you want to understand how it works under the hood - the API, validation system, prompt engineering, and storage architecture - read on.

API

POST /plans - Create a new training plan:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
curl -X POST https://training.twenty-four.life/plans \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Spring Half Marathon",
    "race_name": "Seattle Half Marathon",
    "race_date": "2026-03-07",
    "race_distance": 13.1,
    "sport": "Run",
    "start_date": "2026-01-01",
    "import_icu_workouts": true
  }'

Response (202 Accepted):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
{
  "success": true,
  "plan_id": "PLAN_Seattle_Half_Marathon_2026-01-01",
  "name": "Spring Half Marathon",
  "method": "week-by-week",
  "generation_status": "generating",
  "weeks_generated": 0,
  "total_weeks": 10,
  "poll_url": "/plans/PLAN_Seattle_Half_Marathon_2026-01-01",
  "message": "Plan generation started..."
}

The API returns immediately - generation continues in the background.

GET /plans/:id - View the plan in a calendar UI, or add ?format=json for data.

POST /plans/:id/feedback - Regenerate with adjustments:

1
2
3
curl -X POST https://training.twenty-four.life/plans/PLAN_ID/feedback \
  -H "Content-Type: application/json" \
  -d '{"feedback": "Make the plan less aggressive. Add more easy days in weeks 2-4."}'

POST /plans/:id/week/3/feedback - Fix just one week:

1
2
3
curl -X POST https://training.twenty-four.life/plans/PLAN_ID/week/3/feedback \
  -H "Content-Type: application/json" \
  -d '{"feedback": "Move the long run to Sunday instead of Saturday."}'

POST /plans/:id/sync - Push to Intervals.icu when satisfied:

1
2
3
curl -X POST https://training.twenty-four.life/plans/PLAN_ID/sync \
  -H "Content-Type: application/json" \
  -d '{"folder_name": "Half Marathon Training (v2)"}'

Generation Methods

The system supports two approaches:

Method Parameter Speed Quality
Week-by-week (default) use_full_plan: false ~1-2 min/week Higher - each week generated with context from previous weeks
Full-plan use_full_plan: true ~30 seconds Good - entire plan generated in one pass

Progressive Rendering

A 10-week plan takes 10-20 minutes to generate with week-by-week mode. Rather than waiting with no feedback, the system shows weeks as they’re produced.

  1. Immediate response - POST returns 202 with generation_status: "generating"
  2. Background processing - Each week generates and saves to S3 independently
  3. Live UI updates - The plan page polls every 2 seconds and displays new weeks
  4. Progress indicator - Animated progress bar shows “Generating week N of M”
1
2
3
+---------------------------------------------------------+
|  Generating week 3 of 10...  ========..............  30% |
+---------------------------------------------------------+

Poll for status programmatically:

1
2
3
4
5
6
curl https://training.twenty-four.life/plans/PLAN_ID?format=json | jq '{
  generation_status,
  weeks_generated,
  total_weeks,
  workout_count: (.workouts | length)
}'

Response while generating:

1
2
3
4
5
6
{
  "generation_status": "generating",
  "weeks_generated": 3,
  "total_weeks": 10,
  "workout_count": 15
}

Week-by-Week Generation

The default approach generates each week sequentially with full context:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
================================================================================
WEEK 3 of 9
================================================================================

Generating Week 3...
   Phase: Build
   Target load: 450
   Days to race: 49

Validating Week 3 (attempt 1)...
Week 3 validation passed

Saving checkpoint after Week 3...
Checkpoint saved to S3

Creating 5 workouts in Intervals.icu...
Created 5 workouts in Intervals.icu

Progress: 33.3% (3/9 weeks)

Each week’s prompt includes:

  • Prior weeks - What was generated before (load, duration, workout types)
  • Current fitness - ATL, CTL, TSB, ramp rate from Intervals.icu
  • Plan progress - Completed load, miles, hours, rest days used
  • Holidays - Fetched from ICU calendar, enforced as rest days
  • Imported workouts - Existing ICU workouts to work around

This context awareness means Week 5 knows exactly what happened in Weeks 1-4 and can make intelligent adjustments.

Two-Pass Validation

Before accepting each week, the system runs validation:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// Check for violations BEFORE auto-fixing
requiredFixes := DetectRequiredFixes(week, plan)
if len(requiredFixes) > 0 {
    log.Printf("Week %d has %d constraint violations, attempting feedback retry...", weekNum, len(requiredFixes))

    // Build feedback and retry with it
    feedback := BuildFeedbackSection(requiredFixes)
    retryWeek, retryErr := GenerateWeeklyPlanWithFeedback(plan, weekNum, feedback)

    if retryErr == nil && len(DetectRequiredFixes(retryWeek, plan)) == 0 {
        log.Printf("Feedback retry produced clean output!")
        week = retryWeek
    }
}

Example violations detected and fed back:

1
2
3
4
5
6
7
8
bike_weekend: You scheduled a 120min Ride Long on Jan 15 (Wednesday).
   Long bikes (>90min) MUST be on Saturday or Sunday ONLY.

pool_closure: You scheduled a Technique swim on Jan 17 (Friday).
   The pool is CLOSED on Friday. Put swims on Mon-Thu or Sat-Sun.

pool_swim_duration: You scheduled a 90-minute pool swim.
   Pool swims are limited to 60 minutes (1 hour lane reservation).

The model gets one chance to fix violations. If that fails, auto-fixes are applied (moving workouts, capping durations).

Sport-Specific Templates

The system uses different prompt templates based on race type:

Running:

  • weekly_prompt_v1_half_marathon.txt - Half marathon plans
  • weekly_prompt_v1_50k.txt - Ultramarathon plans

Triathlon:

  • weekly_prompt_v1_him.txt - Half Ironman (70.3) plans
  • weekly_prompt_v1_im.txt - Full Ironman plans

Each template includes sport-specific rules:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
HALF IRONMAN TRAINING RULES:
1. Race distance: 1.2 mile swim + 56 mile bike + 13.1 mile run
2. Max long bike: 56 miles / 3.5 hours (race distance)
3. Max long run: 13.1 miles / 2 hours (race distance)
4. Max swim: 2200 yards (race distance)
5. Long rides (>2 hours) MUST be on Saturday or Sunday
6. Include brick workouts: bike followed immediately by run
7. Rest days: 1-2 per week (Monday or Friday recommended)
8. Multi-sport balance: ~3 swims, ~3 bikes, ~3-4 runs per week

CRITICAL POOL CLOSURE RULE
The pool is CLOSED on Tuesday, Thursday, and Friday.
Swim workouts are ONLY ALLOWED on: Mon, Wed, Sat, Sun

Open water swims are PREFERRED on weekends during May-October.

The validation system enforces these rules and rejects weeks that violate them.

Importing Existing Workouts

Setting import_icu_workouts: true fetches existing calendar entries and includes them in the prompt:

1
2
3
4
5
6
7
8
EXISTING ICU WORKOUTS THIS WEEK (ALREADY SCHEDULED):
- Tuesday (Jan 7): HIIT - Gym HIIT Class (45min)
- Thursday (Jan 9): Strength - Gym Strength (60min)
- Friday (Jan 10): PT - Personal Training (60min)

IMPORTANT: These workouts are ALREADY on the calendar. DO NOT replace them.
Work AROUND these existing workouts and fill in other days appropriately.
Count their duration toward your weekly volume planning.

This lets you keep gym classes, personal training, and other commitments while generating endurance training around them.

Holiday Awareness

The system fetches holidays from Intervals.icu and enforces rest:

1
2
3
HOLIDAYS THIS WEEK:
- Spring Break: 2026-03-07 to 2026-03-14
NO WORKOUTS during holidays!

If Claude accidentally schedules a workout during a holiday, validation catches it and either:

  1. Asks Claude to regenerate with feedback
  2. Auto-removes the workout

Structured Workout Format

Every workout uses Intervals.icu structured format:

1
2
3
4
5
6
7
8
9
Warmup
- 10m Ramp 60-75% pace, Z1-Z2 easy build

Tempo Intervals 3x
- 8m 88-92% pace, Z4 threshold effort, focus on steady breathing
- 3m 70% pace, Z2 active recovery

Cooldown
- 10m 60-70% pace, Z1 easy jog

Key format rules:

  • Steps start with -
  • Capitalize Ramp for progressive efforts
  • Include zone notation (Z1-Z5) alongside percentages
  • Add descriptive section headers (not just “Main Set”)

Storage & Checkpoints

Plans are stored in S3 with progressive saves after each week. This enables both the live UI updates and failure recovery.

1
2
3
4
5
6
7
// After each week completes, save immediately
plan.WeeksGenerated = weekNum + 1
plan.UpdatedAt = time.Now()
if err := SaveStoredPlan(plan); err != nil {
    log.Printf("Failed to save progress after week %d: %v", weekNum+1, err)
}
log.Printf("Progress: %.0f%% (%d/%d weeks) - saved to S3", progress, weekNum+1, totalWeeks)

Generation states:

Status Meaning
generating Plan creation in progress, weeks appearing progressively
complete All weeks generated successfully
failed Generation failed (error message in generation_error field)

If generation fails mid-plan, the partial plan remains in S3. You can view completed weeks and decide whether to delete and retry, or provide feedback to regenerate from that point.

Prompt Templates

Download the Claude prompt templates used for each race type:

These are Go text templates with placeholders (e.g., {{.RaceName}}, {{.WeekNumber}}) that get filled in with context about your race, fitness level, and prior weeks before being sent to Claude.


See also: Part 0: The Platform | Part 1: Building with Claude | Part 2: Calendar Service | Part 3: Gym Service | Part 4: Strava Service | Part 5: Workout Generator | Part 6: AI Recommendations | Part 7: Service Consolidation | Part 8: System Architecture

Co-Authored-By: Claude noreply@anthropic.com