- diet.protein_per_kg 2 -> 1.7 g/kg (evidence ceiling for trained/resistance-adapted adults) daily target 190-200 -> 165-175 g/day, ~80-90 g/meal - weekly non-whey protein floor 1330 -> 1190 g (170 g/day x 7), still excl whey - SKILL.md: RATIONAL PORTIONS hard rule (per-item max_per_meal caps, no single-ingredient overload, honest shortfall reporting); WEEKLY PROTEIN FLOOR (>=3 sources, none >50%) - inventory.json: per-item max_per_meal caps + rational_portions defaults table - scripts/verify_portions.py: regression probe for overload/cap violations - README + references synced to 1.7 g/kg; .idea/.agentbridge gitignored
Meal Suggestion — Kitchen Inventory + High-Protein Meals
An automated, research-grounded meal system for a single user (jp): a recovering burns victim on a high-protein diet (~165–175 g/day) who is also losing fat, and who wants to avoid buying red meat. It does two things on a schedule:
- Daily meal suggestions — two meals (lunch + dinner) built only from what is actually in the kitchen inventory, emailed every day at 09:00.
- Weekly shopping list — a £30 Lidl basket, themed around a randomly chosen world cuisine that week, emailed Mondays at 08:00.
Sender: hpm6@txt3.com (the agent's Gmail, via smtp.gmail.com:587).
Recipient: jp@txt3.com.
General guidance, not medical advice — defer to the user's clinical / dietitian team; keep hydrated on a high-protein intake.
Table of contents
- How it works
- Repository layout
- Core rules
- Egg limit (hard rule)
- Weekly cuisine rotation
- Inventory & meal history
- Email format
- Automation (cron)
- Scripts
- Where to shop
- On-demand usage
- Testing & regression guards
- Git & sync
How it works
The system is driven by a skill (SKILL.md) plus two cron jobs that load it.
On each run the agent:
- Reads the kitchen
inventory.json(current stock). - Daily: searches the web for easy, tasty, high-protein budget recipes for
inspiration, then builds two meals strictly from in-stock items, records
them to
meal-history.json, and emails them. - Weekly: picks a cuisine, builds a £30 Lidl basket themed around it, adds a per-store price comparison and a health-coverage note, and emails it.
All generated meals obey the strict inventory rule: no ingredient may appear
that is not already in inventory.json. Recipe ideas are only used as
inspiration; if a recipe needs something not in stock, it is substituted with an
in-stock item or dropped.
Repository layout
meal-suggestion/
├── SKILL.md # The operative spec: rules, format, automation
├── README.md # This document
├── .gitignore # Excludes live data (inventory/meal-history/cuisine-rotation)
├── inventory.example.json # Schema example for inventory.json
├── meal-history.example.json # Schema example for meal-history.json
├── references/ # Knowledge base the agent reads when generating
│ ├── email-template.md # HTML email scaffold (inline CSS, mobile-friendly)
│ ├── food-health.md # Healing + 46yo health + easy/tasty meal research
│ ├── protein-sources.md # Allowed proteins + example pairings
│ ├── protein-targets.md # Protein math (1.7 g/kg)
│ ├── meal-history.md # 7-day logger spec + reconcile-from-inventory logic
│ └── eastbourne-shops.md # Shop price table (Lidl preferred)
├── scripts/
│ ├── pick_cuisine.py # Picks this week's world cuisine (seeded, varied)
│ └── verify_eggs.py # Regression guard for the EGG LIMIT hard rule
└── (live, git-ignored)
├── inventory.json # CURRENT stock — drifts as the user eats/buys
├── meal-history.json # 7-day rolling log of suggested meals
└── cuisine-rotation.json # Current + recent weekly cuisine picks
The live skill directory is /home/jp/.hermes/skills/meal-suggestion/; this repo
is a synced mirror under ~/IdeaProjects/meal-suggestion/. The two stay in step
(see Git & sync).
Core rules
- High-protein target: ~165–175 g/day (≈1.7 g/kg at ~95–100 kg, for burns recovery + lean-mass preservation during ~15 kg fat loss). Aim ~90–100 g per meal and state the per-meal and daily protein totals in the email.
- Weight-loss framing: mild calorie deficit via bulking with veg and going easy on chips/bread/oil — not by cutting protein.
- Red meat: already in stock is usable (use it up, deprioritised), but the shopping list must never suggest buying red meat (beef, lamb, pork, venison, bacon, gammon).
- Strict inventory rule: both daily meals are built ONLY from items in
inventory.json. Nothing invented, assumed, or added. - Tasty & varied: the agent does a 1–2 query web search for easy, high-protein budget meals and draws on real recipes so meals aren't the same plate repeated. Prefers no-cook / one-pan / 10–15 min methods.
- Health coverage: meals/items favour wound-healing nutrients (Vit C, Zinc, Vit A, Copper, Vit K, iron+VitC) and 46-year-old health (Vit D, B12, Magnesium, Omega-3) plus mental + visual acuity (leafy greens, eggs/choline, berries, walnuts, olive oil, lutein/zeaxanthin, omega-3 DHA). Noted as gentle "health boost" lines, not medical advice.
Egg limit (hard rule)
Eggs were previously overloaded (a day suggested 8 eggs across two egg-bearing meals). This is now a hard cap, enforced three ways:
- In SKILL.md: max 4 eggs per meal; only ONE of the two daily meals may contain eggs — the other must be egg-free. If a meal needs more protein than 4 eggs supply, close the gap with lentils/beans/halloumi/cheese/tinned fish/ whey — never a 5th+ egg. This overrides the "lead with eggs" preference.
- In inventory.json:
diet.egg_rulescarriesmax_per_meal: 4andmax_meals_per_day_with_eggs: 1; the Eggs item note states the cap. Every run reads the limit from the data itself. - In scripts/verify_eggs.py: a regression guard that checks the persisted
history against
diet.egg_rulesand exits non-zero on violation. Run it after any change to inventory/meal-history or the daily job, and before sending a regenerated day's email.
Weekly cuisine rotation
To keep shops and meals varied over time, each week's list is themed around a
randomly chosen world cuisine, driven by scripts/pick_cuisine.py.
- Stable within a week: the pick is seeded by the week's Monday ISO date, so re-runs / retries of the Monday job never flip the cuisine mid-week.
- Varied over time: the last 4 chosen cuisines are excluded from the pool (no back-to-back repeats).
- Persisted: writes the choice to
cuisine-rotation.json(week_monday,cuisine,theme_items) so the daily job can read it and cook on-theme meals from the same week's buys. - Cuisine pool (all Lidl-achievable on ~£30, high-protein, red-meat-free by design): Mexican/Tex-Mex, Greek/Mediterranean, Indian/South Asian, Thai, East Asian/Japanese, Middle Eastern/Levantine, Italian, Korean.
- Themes without breaking rules: the cuisine leads protein/veg/condiment buys
with its
theme_items, but still respects £30 budget, protein-first, healing/ 46yo coverage, Lidl-default, and never-buy-red-meat. Pure flavour buys (spices/sauces/lime/herbs) are the optional low-price "interest" items.
The weekly email subject/header shows Cuisine: <name>.
Inventory & meal history
inventory.json — current stock. Schema:
{
"updated": "YYYY-MM-DD",
"diet": {
"goal": "high-protein",
"avoid_purchase": ["red meat"],
"egg_rules": { "max_per_meal": 4, "max_meals_per_day_with_eggs": 1,
"note": "..." },
"age_years": 46, "bodyweight_kg": "95-100",
"protein_per_kg": 2, "protein_target_g_per_day": "190-200",
"weight_loss_goal_kg": 15
},
"items": [
{ "name": "Eggs", "qty": 10, "unit": "count", "category": "protein",
"notes": "Hard cap: max 4 eggs per meal; only ONE of the two daily meals may use eggs." }
]
}
category∈protein, veg, fruit, dairy, grain, tinned, frozen, condiment, other.- You own the inventory. When the user says they used/consumed ingredients,
decrement/remove them (via the
patchtool) and bumpupdated. When they bought items, add/increase them. The daily email only proposes meals — it does not auto-consume.
meal-history.json — rolling 7-day log. The daily job UPSERTS today's two
entries (replaces same date+meal, so re-runs don't duplicate) and prunes
entries older than 7 days. Each entry:
{ "date": "YYYY-MM-DD", "weekday": "Thursday",
"meal": "lunch", "name": "Halloumi & Red Lentil Power Bowl (egg-free)",
"items": [{ "name": "Halloumi", "qty": 100, "unit": "g" }] }
Only substantive food items are logged — pure condiments and the optional "third hit" snack are not. This log is what the on-demand inventory-removal feature reconciles against.
Email format
Sent via python3 /home/jp/.hermes/scripts/meal/send_meal_email.py (reads SMTP
creds from ~/.hermes/.env). Both HTML (inline CSS, mobile-friendly, dark-on-light)
and a plain-text alternative are produced so the plan survives HTML-stripping
clients.
- Daily: header (date +
high-protein · ~165–175g/day · no red meat bought), two meal cards (name, ingredients, 2–3 step method, protein total, short "health boost" note), footer (Reply to tell me what you used and I'll update the inventory). - Weekly: shopping table (item, est. price, running total ≤ £30, Total row),
header with
Cuisine: <name>, a "health coverage" note (healing + brain + eyes- 46yo + cuisine theme), a "Same basket — where else?" per-store comparison table, and a "Same basket — where else?" plain-text mirror.
Automation (cron)
Two cron jobs (created via the cronjob tool), both loading the
meal-suggestion skill:
| Job | Schedule | Purpose |
|---|---|---|
| Daily meal suggestions | 0 9 * * * |
2 in-stock meal suggestions → email |
| Weekly shopping list | 0 8 * * 1 |
£30 cuisine-themed Lidl basket → email |
Delivery is local (the email is the deliverable); check cronjob action=list /
logs if a send seems missing. The weekly job runs pick_cuisine.py first,
then themes the basket; the daily job reads cuisine-rotation.json and prefers
on-theme cooking while still obeying the strict inventory rule and the egg cap.
Scripts
scripts/pick_cuisine.py
Picks this week's cuisine.
python3 scripts/pick_cuisine.py— pick (or reuse this week's) and persist.python3 scripts/pick_cuisine.py --dry-run— print only, do not write.- Prints JSON:
{"week_monday", "cuisine", "theme_items", "reused"}. - Seeded by the week's Monday → stable within a week; excludes the last 4 cuisines → no back-to-back repeats.
scripts/verify_eggs.py
Regression guard for the EGG LIMIT contract. Reads inventory.json
(diet.egg_rules) and meal-history.json, checks no meal exceeds
max_per_meal and no day exceeds max_meals_per_day_with_eggs. Exits non-zero
on violation. Run after any change to the egg logic or before sending a
regenerated day.
Where to shop (Eastbourne)
User preference (confirmed): Lidl is the preferred shop — a weekly trip, so the extra distance is fine; best value and leaves budget for essential micronutrients and the occasional treat. Others (Tesco Express, Sainsbury's, Co-op, Londis) are comparison/fallback only.
Grounded in Which? 2026 (93-item basket; Lidl ≈ £160.70 baseline):
- Lidl ≈ 1.00x — cheapest, preferred
- Tesco superstore w/ Clubcard ≈ 1.17x; Tesco Express ≈ 1.22x
- Sainsbury's w/ Nectar ≈ 1.17x
- Co-op ≈ 1.30x (membership = annual dividend)
- Londis ≈ 1.25–1.35x (milk 2L £1.40 is a confirmed cheaper line)
The weekly email shows a "Same basket — where else?" comparison (Lidl / Tesco Express Clubcard / Sainsbury's Nectar / Co-op member / Londis) as estimates from the Which? 2026 ratios + known local prices — no live web search. Default is a single Lidl shop; split only when a specific item's local price is definitely known lower.
On-demand usage
- "What should I eat today?" — read
inventory.json, suggest now, offer to email. - "Remove Wednesday's dinner from inventory" — resolve the entry, decrement
each logged item from
inventory.json(remove if ~0), skip items not present, bumpupdated, confirm. Works per-item and partial-consumption overrides too. - Adding stock — list items in chat; they're added to
inventory.jsonand an immediate 2-meal suggestion can follow.
Testing & regression guards
python3 scripts/verify_eggs.py— asserts the EGG LIMIT holds acrossmeal-history.jsonusing the persisteddiet.egg_rules. Should printPASS: egg limit OK (max 4/meal, max 1 egg-meal/day).- The cuisine picker is deterministic per week (Monday-seeded) and was verified to produce no consecutive repeats across 8 simulated weeks.
Git & sync
This repo (~/IdeaProjects/meal-suggestion/) is the version-controlled mirror of
the live skill (/home/jp/.hermes/skills/meal-suggestion/).
- Live data is git-ignored:
inventory.json,meal-history.json,cuisine-rotation.json, and__pycache__/. Only the spec, references, examples, and scripts are committed. - Sync workflow: copy the changed source files (currently
SKILL.mdandscripts/) from the live skill dir into this repo, then commit. Keep.gitignoreexcluding live data in both places.
# from /home/jp/.hermes/skills/meal-suggestion
cp SKILL.md /home/jp/IdeaProjects/meal-suggestion/SKILL.md
cp scripts/pick_cuisine.py scripts/verify_eggs.py \
/home/jp/IdeaProjects/meal-suggestion/scripts/
# commit the mirror
cd /home/jp/IdeaProjects/meal-suggestion
git add -A && git commit -m "Sync: egg-limit hard rule + weekly cuisine rotation"
git push
Version history of the mirror:
72ce56eInitial meal-suggestion skill (logic + template; live data git-ignored)c5f4fb1Sync: food-health research, mackerel, easy/tasty + acuity, weekly health-coverage9e44876Sync: strict inventory-only rule for daily meals- (current) Sync: EGG LIMIT hard rule + weekly cuisine rotation + scripts