Cookwala / SDK and scenarios / 032

The smart oven and its core probe: the kofta recipe, the ladder, the limit

The kofta recipe ends its bake when the core probe reads 71 degC. The oven preset has an oven thermometer and a core probe. See which rung the dry run records, what a probe alone would verify, and the limit the number comes from.

Audience: appliance maker · Goal: Make people healthier, Robots for people · Level: beginner

Read first: /docs/CORE/ /docs/QUICKSTART/

Steps

  1. The bake step's end condition is the core probe at 71 degC or more, marked as a critical control point. getRecipe

  2. The oven preset lists both sensors. getDevices

  3. The dry run records the first rung of the bake ladder, the oven thermometer; the end condition and the limit are the probe's job during the run. The preheat is a logged estimate because cw.op.heat's ladder names a pan thermometer, not an oven one. dryRun

  4. An oven with only a core probe would still verify baking: the probe is the second rung. ladder

  5. The 71 degC in the recipe is the default limit for ground meat; the device asks a person when it is not reached. safetyLimits

  6. The probe-only rung.

Outcome

You know how a recipe, a sensor list and a limits pack meet on one number. You also saw a vocabulary edge: an oven preheat written as cw.op.heat is verified by estimate because that operation's ladder is pan-centred.

Command line

curl -s http://localhost:7878/v1/tools/recipes/kofta-oven
cookwala dryrun examples/kofta-oven.cookwala.json --device examples/capabilities/demo-oven.json --human-present
curl -s -X POST http://localhost:7878/v1/tools/ladder -H 'Content-Type: application/json' -d '{"op": "cw.op.bake", "sensors": ["cw.sense.core_temp"], "allowModel": true, "humanPresent": false}'

Recorded output

Status: ok

[1] getRecipe: {"$schema": "https://cookwala.ai/v1/schemas/recipe.schema.json", "cookwala": "0.2.0", "id": "example-kofta-oven", "revision": 1, "updated": "2026-10-04", "verification": {"level": "V1", "evidence": {"simulatorRuns": 0, "humanReviews": 1, "fieldReports": 0}, "reviewedBy": "cookwala.ai editorial (structure only)", "notes": "Example recipe written to exercise the standard: structured steps, end conditions, hazards and critical control points. Not simulated and not field-verified; values are typical home-cooking values, not measurements."}, "dish": {"names": {"en": "Oven-baked beef kofta", "ar": "…
[2] getDevices: {"devices": {"demo-arm": {"cookwala": "0.1.0", "actor": {"kind": "robot", "id": "demo:arm", "vendor": "Cookwala demo presets", "model": "Counter robot arm (cuts, cracks, mixes, moves)"}, "roles": ["executor"], "location": {"zone": "kitchen", "mobile": false}, "capabilities": {"ops": [{"op": "cw.op.cut"}, {"op": "cw.op.crack"}, {"op": "cw.op.transfer"}, {"op": "cw.op.mix"}, {"op": "cw.op.whisk"}, {"op": "cw.op.wash"}, {"op": "cw.op.drain"}, {"op": "cw.op.crush"}, {"op": "cw.op.grate"}, {"op": "cw.op.layer"}, {"op": "cw.op.garnish"}, {"op": "cw.op.form"}], "sensors": [{"sensor": "cw.sense.vision…
[3] dryRun: {"state": "accepted", "plan": [{"node": "n1", "op": "cw.op.grate", "by": "human", "verifiedBy": "human"}, {"node": "n2", "op": "cw.op.cut", "by": "human", "verifiedBy": "human"}, {"node": "n3", "op": "cw.op.crush", "by": "human", "verifiedBy": "human"}, {"node": "n4", "op": "cw.op.mix", "by": "human", "verifiedBy": "human"}, {"node": "n5", "op": "cw.op.form", "by": "human", "verifiedBy": "human"}, {"node": "n6", "op": "cw.op.chill", "by": "human", "verifiedBy": "human"}, {"node": "n7", "op": "cw.op.heat", "by": "device", "verifiedBy": "model", "rung": "model"}, {"node": "n8", "op": "cw.op.bake…
[4] ladder: "cw.sense.core_temp"
[5] safetyLimits: {"core": "0.2.0", "kind": "SafetyLimits", "id": "cookwala-default", "version": "0.2.0", "status": "draft", "limits": [{"id": "oil.max_temp", "kind": "max_temp", "action": "cut_heat", "message": "Cut heat above 200 °C: most cooking oils smoke and fire risk rises.", "source": "Common frying-oil smoke points; fire-safety practice", "appliesTo": {"medium": "oil", "ops": ["cw.op.fry", "cw.op.deep_fry"]}, "max": 200, "unit": "degC"}, {"id": "pan.max_surface", "kind": "max_temp", "action": "cut_heat", "message": "Cut heat above 280 °C pan surface (coatings degrade, oil ignites).", "source": "Cookware…

Code

Run: python scenarios/out/032-oven-with-core-probe-for-kofta/python.py

# Scenario 032: The smart oven and its core probe: the kofta recipe, the ladder, the limit
# The kofta recipe ends its bake when the core probe reads 71 degC. The oven preset has an oven thermometer and a core probe. See which rung the dry run records, what a probe alone would verify, and the limit the number comes from.
# Run a hub first: python hub/cookwala_hub.py --recipes examples
import json, os
from cookwala.client import CookwalaClient, CookwalaProblem

def load(path):
    with open(path, encoding="utf-8") as f:
        return json.load(f)

def pick(obj, path):
    for part in path.replace("]", "").replace("[", ".").split("."):
        obj = obj[int(part)] if part.isdigit() else (len(obj) if part == "length" else obj.get(part))
    return obj

c = CookwalaClient(os.environ.get("COOKWALA_HUB", "http://localhost:7878"))


# Step 1: The bake step's end condition is the core probe at 71 degC or more, marked as a critical control point.
kofta = c.get_recipe("kofta-oven")
assert pick(kofta, "process.nodes[7].until.all[0].sensor") == "cw.sense.core_temp", pick(kofta, "process.nodes[7].until.all[0].sensor")
print("ok", "process.nodes[7].until.all[0].sensor", "=", "cw.sense.core_temp")
assert pick(kofta, "process.nodes[7].until.all[0].gte") == 71, pick(kofta, "process.nodes[7].until.all[0].gte")
print("ok", "process.nodes[7].until.all[0].gte", "=", 71)
assert pick(kofta, "process.nodes[7].ccp") == "ccp_ground_meat", pick(kofta, "process.nodes[7].ccp")
print("ok", "process.nodes[7].ccp", "=", "ccp_ground_meat")

# Step 2: The oven preset lists both sensors.
devices = c.get_devices()
assert pick(devices, "devices.demo-oven.capabilities.sensors[0].sensor") == "cw.sense.oven_temp", pick(devices, "devices.demo-oven.capabilities.sensors[0].sensor")
print("ok", "devices.demo-oven.capabilities.sensors[0].sensor", "=", "cw.sense.oven_temp")
assert pick(devices, "devices.demo-oven.capabilities.sensors[1].sensor") == "cw.sense.core_temp", pick(devices, "devices.demo-oven.capabilities.sensors[1].sensor")
print("ok", "devices.demo-oven.capabilities.sensors[1].sensor", "=", "cw.sense.core_temp")

# Step 3: The dry run records the first rung of the bake ladder, the oven thermometer; the end condition and the limit are the probe's job during the run. The preheat is a logged estimate because cw.op.heat's ladder names a pan thermometer, not an oven one.
plan = c.dry_run(recipe_id="kofta-oven", device_id="demo-oven", human_present=True)
assert pick(plan, "state") == "accepted", pick(plan, "state")
print("ok", "state", "=", "accepted")
assert pick(plan, "plan[7].op") == "cw.op.bake", pick(plan, "plan[7].op")
print("ok", "plan[7].op", "=", "cw.op.bake")
assert pick(plan, "plan[7].rung") == "cw.sense.oven_temp", pick(plan, "plan[7].rung")
print("ok", "plan[7].rung", "=", "cw.sense.oven_temp")
assert pick(plan, "plan[6].op") == "cw.op.heat", pick(plan, "plan[6].op")
print("ok", "plan[6].op", "=", "cw.op.heat")
assert pick(plan, "plan[6].rung") == "model", pick(plan, "plan[6].rung")
print("ok", "plan[6].rung", "=", "model")

# Step 4: An oven with only a core probe would still verify baking: the probe is the second rung.
probeOnly = c.ladder("cw.op.bake", ["cw.sense.core_temp"], True, False)

# Step 5: The 71 degC in the recipe is the default limit for ground meat; the device asks a person when it is not reached.
limits = c.safety_limits()
assert pick(limits, "limits[4].id") == "core.ground_meat", pick(limits, "limits[4].id")
print("ok", "limits[4].id", "=", "core.ground_meat")
assert pick(limits, "limits[4].min") == 71, pick(limits, "limits[4].min")
print("ok", "limits[4].min", "=", 71)
assert pick(limits, "limits[4].action") == "ask_human", pick(limits, "limits[4].action")
print("ok", "limits[4].action", "=", "ask_human")

# Step 6: The probe-only rung.
assert probeOnly == "cw.sense.core_temp", probeOnly
print("ok", "probeOnly", "=", "cw.sense.core_temp")

print("scenario complete")

scenarios/032-oven-with-core-probe-for-kofta.json

Previous: 031 Next: 033