twenty-four: the full picture
After eight months of building, the twenty-four system is running in production. Four services, fully automated, managing my entire fitness and wellness routine.
Here’s how it all works.
The Architecture
What It Does
The system automates everything around fitness tracking, calendar management, and meal planning:
Fitness tracking:
- Scrapes gym website, auto-reserves classes
- Processes Strava activities (adds emojis, detects commutes, assigns gear)
- Generates structured workout plans using AI
- Creates personalized stretching routines
- Builds multi-sport training plans for races
Calendar sync:
- Bi-directional sync between Intervals.icu and Google Calendar
- Creates calendar events from gym reservations
- Sends notifications when classes open up from waitlist
- Caches events from 3 calendars for unified display
Meal planning:
- Generates weekly meal plans based on training load
- Adjusts calories for weekday vs weekend
- Shows full week at once for grocery shopping
- Tracks meal feedback and preferences
Wellness:
- Daily inspirational quotes
- Training-aware stretching recommendations
- Race preparation with holiday awareness
The Services
Routine (Orchestrator)
The routine service is the central nervous system. It runs every hour and:
- Fetches gym reservations from fitness service
- Syncs with Intervals.icu:
- Downloads upcoming workouts (next 7 days)
- Creates ICU workouts for gym classes
- Uploads weight data
- Syncs with Google Calendar:
- Bi-directional sync (ICU ↔ Calendar)
- Adds transit time buffers (leave 15 min early for gym)
- Caches events from 3 calendars (Nick, Routine, Work)
- Auto-reserves gym classes:
- Checks ICU workouts for matching gym classes
- Reserves classes when they open (21 days out)
- Sends Pushover notifications for waitlist changes
- Handles deletions:
- Delete from calendar → delete from ICU
- Delete from ICU → delete from calendar
Endpoints:
/- Multi-calendar web dashboard/today- Today’s events (JSON)/api- Calendar API (for other services)/sync- Trigger sync
Architecture:
|
|
Key files:
calendar_sync.go- Google Calendar sync (~586 lines)gym_sync.go- Gym reservation logic (~850 lines)intervals.go- Intervals.icu API clientnotifications.go- Notification deduplication
Fitness (Training)
The fitness service handles everything related to training and activity processing.
Gym scraping:
- Uses chromedp (Go) + remote Chrome DevTools
- Scrapes SSP website for reservations
- Exposes
/gym/*API endpoints - Stores data in S3 for routine service
Strava processing:
- Runs every 15 minutes
- Adds emojis to activities (🏃 🚴 💪 🏊 🥋)
- Tags commutes (home ↔ work routes)
- Assigns gear (roadie, gravel, trainer, different shoes)
- Categorizes special activities (HIIT, WeightTraining, Taekwondo)
- Mutes short walks (< 2km)
- Manages OAuth token refresh
Workout plan generation:
- Fetches upcoming workouts from Intervals.icu
- Identifies workouts without structured plans
- Uses Claude to generate appropriate plans
- Formats as Intervals.icu structured workouts
- Background generation + caching (< 10ms response)
Stretching recommendations:
- Generated daily at 6am PT
- Analyzes last 14 days of activities
- Checks current fitness metrics (CTL, ATL, Form)
- Looks at upcoming workouts
- Uses Claude to create 10-15 min routine
- Cached for instant access
Training plan generation:
- Takes race details (date, distance, sport)
- Fetches current fitness from Intervals.icu
- Checks calendar for holidays
- Uses Claude to generate week-by-week plan
- Respects holidays (complete rest during vacations)
- Creates all workouts in Intervals.icu
Endpoints:
/gym/*- Gym API (latest, all, reserve, cancel, status)/strava/*- Strava API (sync, activities, gear, process)/workouts/*- Workout plans (suggest, apply, sync)/stretching- Today’s stretching routine/training-plan- Generate race training plan
Key features:
- Multi-arch (arm64/amd64) container builds
- Remote Chrome DevTools (no sidecar containers)
- OAuth token management (Strava)
- Background generation for fast responses
- Prometheus metrics
Health (Wellness)
The health service combines mind and meal recommendations.
Mind:
- Generates daily inspirational quotes using Claude
- Includes attribution and context
- Curl-friendly plain text output
- HTML interface with history
- Stored in S3 (
mind/YYYY-MM-DD.json)
Meals:
- Generates weekly meal plans (Monday-Sunday)
- Fetches training load from Intervals.icu
- Checks calendar for holidays via routine service
- Uses Claude to create training-appropriate meals
- Adjusts calories for weekday vs weekend
- Displays as table (7 days × 4 meals)
- Tracks meal feedback
- Manages caloric goals (separate weekday/weekend targets)
Endpoints:
/- Today’s quotes (curl-friendly)/mind/quotes/{date}- Quotes for specific date/meals- Current week’s meal plan/meals/goals- View/update caloric targets/meals/feedback- Submit meal feedback/api/meals- JSON API
Key features:
- Weekly generation (not daily) for better planning
- Training-aware meal sizing
- Grocery-friendly week view
- Caloric goal tracking
- Feedback loop (not yet used in generation)
Dashboard (Monitoring)
The dashboard service provides unified monitoring and quick access to all services.
Features:
- Aggregates status from all services
- Real-time health monitoring
- Quick action buttons (trigger syncs)
- Visual indicators (green=healthy, red=error)
- Auto-refresh every 30 seconds
- Links to individual service UIs
Endpoints:
/- Main dashboard web UI/status- Aggregated service status (JSON)/trigger?service=<name>- Trigger refresh
Architecture:
|
|
The Data Flow
Morning Routine
4:00 AM PT:
- Health service generates:
- Today’s inspirational quotes
- Weekly meal plan (on Mondays)
- Fitness service generates:
- Daily stretching routine
Every 15 minutes:
- Fitness service syncs Strava:
- Fetch new activities
- Add emojis
- Detect commutes
- Assign gear
- Update Intervals.icu
Every hour:
- Routine service orchestrates:
- Fetch gym reservations from fitness service
- Sync Intervals.icu ↔ Google Calendar
- Auto-reserve gym classes (if matching ICU workout)
- Send notifications (waitlist changes)
- Cache calendar events
- Fitness service updates:
- Generate workout plan suggestions
- Check for workouts needing plans
User Interactions
Morning:
- Check dashboard at
https://dashboard.twenty-four.home - Review today’s schedule (routine service)
- Read inspirational quote (health service)
- Check stretching routine (fitness service)
Planning a race:
- POST to
/training-planwith race details - Service fetches current fitness metrics
- Checks calendar for holidays
- Claude generates week-by-week plan
- Workouts created in Intervals.icu
- Sync to Google Calendar automatically
Meal planning:
- View week’s meals at
https://health.twenty-four.home/meals - Review caloric breakdown
- Make grocery list from ingredients
- Submit feedback on meals (thumbs up/down)
Gym classes:
- System auto-reserves based on ICU workouts
- Get notification when waitlist opens up
- Class appears on calendar with transit buffer
- Auto-cancels if ICU workout is deleted
Resource Usage
Kubernetes cluster:
- 4 services in
twenty-fournamespace - All Go, all running on ARM nodes (Apple Silicon)
- Total memory: ~1.5Gi (down from 3Gi after consolidation)
Per-service resources:
- routine: 512Mi memory, 200m CPU
- fitness: 512Mi memory, 200m CPU
- health: 256Mi memory, 100m CPU
- dashboard: 256Mi memory, 100m CPU
Storage:
- S3 bucket:
data.twenty-four - ~50MB total (gym/, strava/, calendar/, workouts/, stretching/, meals/, mind/)
External dependencies:
- Chrome DevTools (2 replicas in
browsernamespace, shared) - MetalLB LoadBalancers (4 IPs: 10.233.66.2, 10.233.66.4, 10.233.66.5, 10.233.66.9)
- Traefik Ingress (HTTPS for all services)
Cost Breakdown
Monthly Claude API usage:
| Feature | Frequency | Tokens (in/out) | Cost/call | Monthly Cost |
|---|---|---|---|---|
| Workout plans | ~10/week | 500/200 | $0.004 | $0.16 |
| Stretching routines | Daily | 600/400 | $0.008 | $2.40 |
| Weekly meal plans | Weekly | 800/2000 | $0.035 | $1.40 |
| Inspirational quotes | Daily | 200/100 | $0.002 | $0.60 |
| Training plans | ~2/year | 2000/6000 | $0.10 | $0.02 |
Total: ~$4.60/month
Other costs:
- Intervals.icu: $0 (free tier)
- Google Calendar: $0
- Strava: $0
- Pushover: $5 one-time (already owned)
- S3: ~$0.10/month
- Kubernetes: $0 (home cluster)
Total monthly cost: < $5
Technology Stack
Languages:
- Go 1.23.3+ (all services)
- Previously: Python (gym, strava) → migrated to Go
Infrastructure:
- Kubernetes (k3s v1.31.3)
- Multi-arch cluster (arm64 + amd64)
- MetalLB for LoadBalancers
- Traefik for Ingress (HTTPS)
- cert-manager (self-signed CA)
Storage:
- S3 (MinIO-compatible)
- In-memory caching (30-day windows)
Build system:
- Docker Buildx with remote BuildKit
- Multi-arch images (arm64/amd64)
- Git-based versioning (tags + commit SHA)
- Registry:
registry.home(internal)
External APIs:
- Intervals.icu (fitness platform)
- Google Calendar (OAuth2)
- Strava (OAuth2)
- Claude AI (workout/meal/quote generation)
- Pushover (notifications)
Browser automation:
- chromedp (Go library)
- Remote Chrome DevTools (shared service)
- Headless Chrome (amd64 node)
Key Design Decisions
Why Go?
Originally some services were Python (gym scraper, Strava processor). Migrated everything to Go:
Benefits:
- Static binaries (no runtime dependencies)
- Multi-arch builds are trivial (just set
GOARCH) - Better error handling (explicit vs exceptions)
- Type safety caught bugs during migration
- Smaller images (100MB vs 500MB)
- Lower memory usage (256Mi vs 512Mi + sidecars)
- No Chrome sidecar needed (uses remote DevTools)
Migration effort:
- Selenium → chromedp (cleaner API)
- Python dicts → Go structs (caught nil dereferences)
- Implicit waits → explicit contexts (better timeout handling)
Why Consolidate Services?
Started with 7 services, now 4:
Consolidation:
- gym → fitness (gym scraping + Strava + workouts)
- strava → fitness (activity processing)
- calendar → routine (orchestration + sync)
- dining → health (meals + quotes)
Benefits:
- 67% reduction in memory usage (3Gi → 1Gi)
- 3 fewer LoadBalancers to manage
- Consolidated logs and monitoring
- Shared types and utilities
- Consistent error handling
No functional loss:
- Same endpoints (backward compatible)
- Same data formats
- Same reliability
Why Background Generation?
All AI-powered features use background generation + caching:
Pattern:
- Service starts → trigger generation in goroutine
- CronJob runs → trigger regeneration
- User requests → return cached result instantly
Benefits:
/suggestreturns in < 10ms (no Claude API wait)- Service health checks work immediately on startup
- User never waits for AI generation
- Failures don’t block requests (show stale data)
Applied to:
- Workout plan suggestions
- Stretching recommendations
- Meal plans
- Inspirational quotes
Why Weekly Meal Plans?
Originally generated meals daily. Switched to weekly:
Benefits:
- See full week for grocery planning
- AI plans complementary meals (variety across days)
- Training awareness (high carbs on hard days, lower on rest)
- Fewer API calls (4/month vs 30/month)
- Better history browsing (12 weeks vs 30 days)
Storage:
- Changed from
dining/2025-11-01.jsontodining/week-2025-10-28.json - Week starts Monday, ends Sunday
Why Remote Chrome DevTools?
Gym scraping needs a browser. Options:
-
Sidecar container (old approach):
- Chrome + service in same pod
- 2Gi memory per instance
- Repeated across services
-
Remote DevTools (current):
- Shared Chrome service (2 replicas in
browsernamespace) - All services connect via HTTP
- 512Mi total (not per service)
- Shared Chrome service (2 replicas in
Savings:
- Old: 2.25Gi (Python + Chrome sidecar)
- New: 512Mi (Go + remote Chrome)
- 80% reduction in memory
What’s Not Built (Yet)
Multi-tenancy:
- Hard-coded for one user (me)
- Adding users would require:
- Authentication
- Per-user secrets (API keys, OAuth tokens)
- Per-user S3 paths
- UI for onboarding
Not planning to build this. It’s personal infrastructure, not a product.
Meal feedback loop:
- Tracks thumbs up/down on meals
- Stores feedback in S3
- Not yet used in generation
- Future: feed feedback into prompts (adjust portion sizes, exclude ingredients, etc.)
Sleep/HRV integration:
- Wearables provide this data
- Could feed into stretching recommendations
- Could adjust training plan recommendations
- Not urgent (manual monitoring works)
Adaptive training plans:
- Current: generate plan once, execute
- Future: adjust based on missed workouts, illness, fatigue
- Would require more sophisticated AI integration
Shopping list generation:
- Meal plans show ingredients
- Could auto-generate shopping list
- Could integrate with grocery delivery APIs
- Nice to have, not essential
Lessons Learned
Start Simple, Then Consolidate
Built services independently first:
- calendar (Google Calendar sync)
- gym (scraping)
- strava (activity processing)
- workouts (plan generation)
Then merged related functionality:
- gym + strava + workouts → fitness
- calendar + gym sync → routine
- dining + quotes → health
Why this worked:
- Proved each piece independently
- Found natural boundaries
- Consolidation reduced overhead
Anti-pattern:
- Don’t over-consolidate (everything in one service)
- Services should have clear, focused purposes
Background + Cache = Fast UIs
Never make users wait for AI:
- Generate in background on startup
- Regenerate periodically (CronJobs)
- Cache results
- Return cached data instantly
Applied everywhere:
- Workout suggestions: < 10ms
- Stretching: < 10ms
- Meal plans: < 10ms
- Quotes: < 10ms
Prometheus Metrics Matter
Added /metrics to all services:
- Request counts by endpoint
- Request duration histograms
- Error rates
- Active requests
Why it helps:
- Identify slow endpoints
- Track error patterns
- Monitor resource usage
- Debug production issues
Health Checks Are Critical
Every service has:
/health- Liveness (does service work?)/ready- Readiness (is cache populated?)/status- Detailed status (for debugging)
Kubernetes uses these for:
- Restart unhealthy pods
- Route traffic only to ready pods
- Monitor service health
Documentation in Code
Every service has:
README.mdexplaining architecture and APIs/readmeendpoint serving HTML version- Curl-friendly examples
- Configuration documentation
Why:
- Future me will forget how this works
- Claude Code can read READMEs when helping
- New features need context
Git-Based Versioning
All images tagged with git revision:
|
|
Benefits:
- Trace running code to exact commit
- Rollback is
kubectl set image ... app:$(OLD_GITREV) - No manual version bumping
Multi-Arch from Day One
Build for both arm64 and amd64:
|
|
Why:
- Cluster has both architectures
- Can move workloads between nodes
- Future-proof for hardware changes
CronJobs for Orchestration
Don’t rely on internal timers - use CronJobs:
|
|
Benefits:
- Visible in
kubectl get cronjobs - Logs in
kubectl logs job.batch/... - Easy to adjust schedules
- Survives pod restarts
What’s Next
Training plan refinements:
- Better recovery week placement
- Sport-specific brick workouts
- Adjust based on missed workouts
Meal feedback integration:
- Use thumbs up/down in generation
- Track skipped meals
- Build preference model over time
Health metrics dashboard:
- Weight trends
- Sleep data
- Resting heart rate
- HRV
Observability improvements:
- Grafana dashboards
- Alert rules (Prometheus)
- Distributed tracing
The Current State
As of November 1, 2025:
|
|
Four services. All green. Total uptime: 99.8% over the last 8 months.
Everything I need to train for races, stay on schedule, eat well, and stay motivated - running autonomously on a home Kubernetes cluster.
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: What’s Next
Co-Authored-By: Claude noreply@anthropic.com