MealMind
Snowflake-native multi-agent nutrition planning
A meal-planning application built entirely inside Snowflake, with two halves that share almost nothing. Underneath is a medallion pipeline: USDA nutrition data scraped with Selenium, staged through S3, then transformed by dbt from four raw food sets into three marts that Cortex Search indexes. On top is a LangGraph graph where a router dispatches to retrieval, adjustment, recipe and calorie agents, all reaching the warehouse through one MCP tool. A scheduled evaluation DAG scores outputs with an LLM judge and stamps each run with a run ID and model version.
Verified: each of these is checkable in the repository
- 3
- medallion layers, bronze through goldenImages/Basic System Architecture.png
- 5
- Cortex models compared in a judged arenautils/model_arena.py
- 10
- node LangGraph routing graphutils/meal_router_agent.py
- 2
- daily Airflow DAGs, planning and evaluationairflow_docker/dags/
A nutrition assistant that invents food data is worse than useless. Generated meal plans need to be grounded in real nutritional values, and the quality of a generated plan is not something you can check by reading one example. It needs measuring repeatedly, because model behaviour drifts.
Bronze: Selenium pulls seven USDA extracts (the food sets plus Food, Nutrient and Food_Nutrient) into S3, and an external stage copies them into RAW_SCHEMA, joining each food set against the nutrient tables on load. Silver: four staging views normalise them, then intermediate views union, clean, score branded quality, deduplicate and assign tiers, with dbt tests and macros producing a completeness score. Golden: three marts materialise as tables, and the search catalog is what Cortex Search indexes. Airflow orchestrates the path. Above it, a LangGraph router loads durable preferences, dispatches to the specialist agents, and routes every tool call through one MCP server; a feedback agent writes corrections back so the next run starts from them.
One router, four specialists, one tool boundary
Preferences load from the warehouse before anything is planned, every specialist reaches Snowflake through the same MCP server rather than querying it directly, and a feedback agent writes corrections back so the next run starts from them.
Why it is built this way
01Retrieval as an MCP tool, not an embedded call
A JSON-RPC MCP server exposes search as a tool the agents call. It keeps retrieval behind a contract instead of scattering warehouse queries through agent code.
02Generate a week in two batches
Seven days at once produced drift and repetition. Splitting into four days then three, with a consolidation pass, kept plans coherent across the week.
03Join at load, deduplicate in the middle layer
Each USDA food set is joined against Food, Nutrient and Food_Nutrient as it lands, so staging reads one shape per source. Deduplication and tier assignment then happen once in the intermediate layer rather than per mart, the marts differ in what they expose, not in how clean they are.
04Evaluation on a schedule, with history
Scores are appended to a warehouse table with a run ID, execution date and model version, so regressions are visible as a trend rather than discovered by a user.
05Compare models rather than assume one
Five Cortex models were run against each other with a judge picking winners. Model choice became a measurement instead of a default.
06Two memory horizons
Durable preferences and feedback live in warehouse tables; conversational state lives in thread checkpoints. Conflating them makes an assistant that either forgets everything or never lets a correction go.
The evaluation DAG was the piece that changed how the team worked. Once scores landed in a table with a run ID, a regression became a row you could point at in a standup instead of an argument about whether the output felt worse.
Next project
CareIQ
A clinical AI assistant that shows its work and its limits