Skip to content

GymOS - Coaching OS for Gyms

Coaching-first operating system for gyms: one TypeScript codebase for web and mobile, a deterministic nutrition engine, and an AI that names meals but never invents calories. Built AI-native and gated by CI.

Creator & Full-Stack Engineer (solo, AI-native)·web
GymOS - Coaching OS for Gyms

Overview

What it is. GymOS is a coaching-first operating system for gyms and independent coaches. It runs the weekly coaching loop (client roster, vitals, goals, check-ins and meal plans) without letting a language model invent calories, macros or allergens. The product is coach-only today: a Next.js 16 PWA that is live as a free-tier pilot, with the same screens being packaged for iOS and Android through Expo. The source is public under Apache-2.0.

Background. It started at home. My wife coaches clients, and the slow part of her week was never the coaching. It was onboarding, assembling meal plans and tracking how clients were really doing, across notes and spreadsheets. I said I would build her a small app for those three things. It grew into a multi-tenant platform: role-based access control was in the domain core on day one, and tenant isolation with row-level security followed in the first hardening phase.

The problem. Independent and boutique coaches lose hours to meal plan assembly (spreadsheet macros, copy-pasted weeks, allergen mistakes), to weekly adjustments (noisy weight data, no clear rule for changing calories versus fixing adherence, no record of why a plan changed) and to tool sprawl across chat apps, spreadsheets and generic calorie trackers. Generic AI meal planners fail this buyer: the model invents numbers, publishes automatically, sends client data to third-party models and leaves the coach no override.

Core idea. Numbers are code, language is optional, publishing is human. Nutrition runs in four layers. Layer 1 computes targets from physiology (Mifflin-St Jeor or Katch-McArdle) with hard safety floors: 1,200 kcal for women and 1,500 for men, a maximum deficit of 25% of TDEE, a surplus ceiling of 15%, and weekly weight change capped at 1% of body weight. Layer 2 is a deterministic, seeded solver that builds meals from a food catalog. Restrictions (14 allergen codes plus religious and ethical codes such as halal and vegan) are filtered in SQL first and checked again independently after generation, and the same inputs plus seed reproduce the same plan. Layer 3 is an optional local model (a Qwen3 instruct model served by llama.cpp) that only names meals and writes prep notes behind schema, numeric-claim and groundedness guardrails. Template names are the fallback, and also the default until a model is provisioned. Layer 4 learns from coach edits through a nightly food-ranking job that feeds the solver; it does not retrain the model.

Weekly check-ins. The adaptive engine is pure code too. It smooths weight with an exponential moving average over a 21-day window, trusts a verdict only after at least four weigh-ins spread over ten days, separates "fix adherence" from "change calories", and damps every adjustment (half of the observed energy gap, at most 200 kcal a day per step). It flags plateaus, weight loss faster than 1.5% of body weight per week, resting heart rate spikes of 15 bpm and high blood pressure. The model may narrate a recommendation. It never computes one.

Coach in control. A plan moves from DRAFT to PUBLISHED to SUPERSEDED, and nothing publishes without an explicit review. One published plan per client is enforced by a partial unique index. A dietary change can force NEEDS_REVIEW with a high-priority notification. Coaches can swap foods, edit portions, regenerate a single meal, set standing meal instructions and override calories (warn, do not block, with an absolute minimum of 800 kcal), and an audited override unlocks plans that a safety gate has blocked. The API also renders client credentials and diet plans as PDFs.

Architecture. A pnpm and Turborepo monorepo with four shells (a Next.js 16 PWA, an Expo SDK 57 mobile app, a Hono modular-monolith API and a pg-boss worker) over eight packages (app, ui, platform, contracts, core, modules, db, ai). Feature screens are written once in packages/app with Tamagui, React Native primitives, Solito navigation and TanStack Query, then render on the web through React Native Web and on phones through Expo Router. The API is contract-first: Zod route schemas generate an OpenAPI 3.1 spec (38 operations across 27 paths) that is committed, diffed in CI and consumed by a typed client, so the app and the API cannot drift. Domain writes go through public module barrels, enforced by eslint-plugin-boundaries.

Data, auth and tenancy. PostgreSQL 17 on Neon through Drizzle ORM: 31 tables and 19 migrations. Multi-tenancy is a shared schema with organisation and outlet scoping, and Row-Level Security as a backstop (ADR-0003). Authorization is one function, can(actor, action, resource), over a 10-role matrix. Money is integer minor units. Sessions are short-lived JWTs with rotating refresh tokens and an active-session check, so logout revokes access immediately (ADR-0002). Sign-up uses an email OTP with hashed, peppered codes (ADR-0006), passwords use scrypt, and API rate limits and plan-generation idempotency keys live in the database.

Privacy by design. The language layer sees only de-identified food names and grams. assertDeidentified guards that boundary, the model container has no network egress in the intended deployment, and raw model output and cache are purged after 90 days.

Background jobs. A worker runs four nightly pg-boss jobs (check-in roll, attention refresh, food-ranking refresh and cleanup) on a separate ephemeral queue database, so a polling worker never keeps Neon's free-tier compute awake.

Deployment. The hosted pilot is a free-tier PaaS stack defined as code: Neon for Postgres, Vercel for the Next.js app (with /v1 rewritten to the API) and Render for the API (Docker, health-checked, auto-deployed from main), provisioned with OpenTofu and a Render Blueprint. To stay near $0 a month, the pilot runs the model layer in template-fallback mode and leaves the nightly worker off. A second path is codified for a single always-free VM (Docker Compose, Caddy, a separate queue database and an optional local llama.cpp service, with images built in CI and pushed to GHCR) but is not deployed yet. Mobile builds are configured through Expo EAS (switched off for now), and CI bundles the iOS app on every pull request.

Quality gates. Every pull request must pass required checks before it can merge: a full-history secret scan, formatting, lint with module-boundary rules, typecheck, an OpenAPI drift check, unit and API integration tests, a production web build, a Playwright login smoke test, a production dependency audit that fails on high severity, and an iOS Metro bundle check. The suite has 409 unit and integration tests plus 16 tests for the agent hooks, and 11 golden fixtures hold the language layer to a 100% guardrail pass rate. That is about 35,000 lines of TypeScript backed by about 7,000 lines of tests. GitHub rules require pull requests and block force-pushes, and secret scanning with push protection, Dependabot alerts and private vulnerability reporting are on.

Built AI-native. Cursor and Claude Code read one short AGENTS.md, and the rules that matter are enforced mechanically instead of advised. ESLint bans raw fetch, raw div elements, Platform checks and raw Date in persistence code. A guard hook blocks secret reads, force-pushes, pushes to main and package installs. A stop hook sends failing checks back to the agent. A reviewer subagent reads diffs in a fresh context, and CI plus GitHub rules are the last wall. I also budget the context: cutting my always-on rules from 15 to 3 removed, by my estimate, about 70% of the default context. Since August 2026, 48 of the repository's 72 pull requests were opened from Cursor cloud-agent branches. The workflow, its measurements and its known gaps are written up in docs/ai-native-workflow.md in the repo.

Status and roadmap. Phase 0 (the single-coach pilot) and the core of Phase 1 (per-user auth, tenant isolation with RLS, per-org configuration) are in the codebase, and the Expo coach app is in progress. There is no client-facing app yet, by design: the backend and the contracts come first. Coach self-signup with an email OTP is already in the codebase. Next on the roadmap are a coach portfolio and plan catalog with a client app, then a gym-admin app, payments and multi-region.

Tech Stack

Next.jsReact 19TypeScriptReact NativeExpoTamaguiTanStack QueryHonoZodOpenAPIPostgreSQLDrizzle ORMpg-bossllama.cppTurborepoPlaywrightGitHub ActionsVercelCursorClaude Code

Related projects