Skip to content

Meal coaching

Meals are the nutritionist aspect’s domain. The rule behind everything here: the numbers are computed in Go, never by the model. A model asked to add up a training week produces plausible totals that don’t add up, and a quietly wrong nutrition target is worse than none.

Targets follow the training week

Meal targets are derived from your active training plan, so a hard leg day and a rest day don’t carry the same number. You need a training plan and a body mass first:

  • With no training plan: “design a training plan first — meal targets are derived from the training week, so the two agree.”
  • With no body mass (on the request or in your profile): “I need your body mass in kg first — every target is anchored to it, and guessing would give you numbers computed for someone else.”

How a day’s target is computed (internal/nutrition):

PartRule
Rest-day maintenanceEstimated as about 31 kcal per kg. If your scale has recorded a measured BMR, it is BMR × 1.35 instead, and the plan says which one it used (baseline_source).
Direction“lose” is about a 15% deficit, “gain” about a 10% surplus, and “maintain” (the default) adds nothing.
Training daysAbout 9 kcal per hard set in that day’s session is added.
Protein2.0 g per kg, on every day and in every direction
Fat0.8 g per kg, as a floor
CarbohydrateWhatever calories remain
Safety floorNever below 22 kcal per kg, whatever the arithmetic says

A saved meal plan records the body mass it was computed from (body_mass_kg_used), so the app can say “computed at 76.4 kg” if you have changed since.

Getting a meal plan

  • In chat: “meal plan, 82 kg”. The deterministic meal action reads the body mass and a direction word (lose/cut, gain/bulk) from the text.
  • From the plan console: ⋯ → Training plan → Meals, via DraftMealPlan. Options are direction, preferences (allergies, dislikes, diet), meals per day, and which day to build example meals for. The default is the heaviest day, the hardest one to hit.

DraftMealPlan streams progress, because choosing foods takes one model call per meal on a slow shared plane. The model proposes foods only. Go then rescales the portions so each meal’s ingredients add up exactly to that meal’s calories and macros. The food choices are judged. On a dissent the foods are withheld and the targets stand. Nothing is live until you save the plan (SaveMealPlan). As with training, you can keep several meal plans and switch between them (SetActiveMealPlan).

Unlocked nutritionist skills

  • Meal Timing: each day’s target is split into per-meal portions that add up exactly to the day.
  • Fuel Cycling: training days get a fourth “training fuel” meal; rest days stay lean.

The meal checklist

The Meals card opens “Today’s plan — N kcal” as a checklist of meal slots. Tick what you actually ate. The card shows the percentage checked, and 80% checked keeps the streak. Each tick journals the day’s checked fraction, and the server keeps the day’s highest value. Ticks are remembered on the device for that calendar day, but the journal on the server is the source of truth for streaks and score.

Meal photos

Sparkle button → Divine a meal’s macros. This sends a JPEG or PNG (optionally with a note like “lunch”) to AnalyzeMeal. The reply is a photo-based estimate: prose plus a per-ingredient breakdown and a total. If the model’s structured output doesn’t parse, you still get the prose and the breakdown is empty. The estimate is journaled, so it shows up in later “how did I eat?” conversations. If it couldn’t be journaled, the result says so.

Photo estimates are usually “not verified”, because the judge is text-only and can’t see the image. Treat them as estimates.

The pantry

The pantry holds the handful of specific products you buy again and again, with the macros printed on the label: the protein tub, the creatine, a particular yoghurt. When a proposed ingredient matches a pantry item, the label’s numbers replace the model’s guess and the portion is scaled from there.

  • Open it from the Pantry link on the plan console’s Meals view.
  • Each item is stored exactly as its label states it: a serving note (“1 scoop”), serving grams, calories, protein, carbs and fat. The name you give it is what gets matched.
  • The API has a photo_derived flag for numbers a vision model read off a label, and the pantry list marks such items.
  • Luna keeps up to 60 items: “your pantry holds 60 items, which is as many as Luna keeps — remove one you no longer buy”.
  • DeletePantryItem removes an item from the list. Because the store is append-only, it writes a tombstone rather than erasing the earlier record.

The pantry is deliberately not a food database. No nutrient table is bundled.

The nutritionist is not a clinician. The prompt rules out diagnosis, supplement or medication advice, extreme restriction and rapid-loss protocols, and assisting disordered eating, and points you to a dietitian or doctor instead. These are prompt guardrails, not medical safeguards.