🍳 Foodlang

Execution IR

.food.yaml says what the recipe is. The execution plan says how to execute it. Targets say how a specific machine understands it.

Foodlang separates recipes from execution. Machine and automation targets do not compile from the recipe text directly — they compile through an internal, S88-inspired execution plan:

.food → FoodRecipeAst → FoodExecutionPlan → machine target
.food.yaml → FoodRecipeAst → FoodExecutionPlan → machine target

Because both formats normalize to the same AST and the same execution plan, equivalent KDL and YAML recipes produce equivalent machine output.

What the plan contains

  • recipe — name and kind (pourover, espresso_milk_drink, …)
  • inputs — ingredients with roles and normalized amounts (oz → ml)
  • requires.capabilities — the capabilities the executor must provide
  • plan — ordered actions (grind, heat_water, dispense_water, wait, …) with parameters, effects, and confirmations
  • s88 — a formula plus unit procedures → operations → phases

A brew_profile is expanded into concrete actions: a bloom becomes a dispense_water + wait, and each pour becomes a dispense_water (+ optional wait).

See it

foodlang compile examples/ethiopia-pourover.food --target food-exec
foodlang compile examples/ethiopia-pourover.food --target s88

Confirmations & honesty

Actions carry confirmations (assumed, timer, sensor, manual). Until real device sensors are mapped, the plan uses assumed/timer confirmations and says so in its warnings. Foodlang does not claim native execution — it produces a plan a machine runtime can validate, adapt, or run if it supports the required capabilities.