Cookwala / SDK and scenarios / 034

The fryer robot plans koshari with every heat step verified by a sensor

With estimates forbidden, a robot that has a pan thermometer, a liquid thermometer and an oil thermometer still plans all twenty steps of koshari, none by estimate and none by a person. Then check the recipe's own frying target against the envelope.

Audience: robot maker · Goal: Help end hunger, Robots for people · Level: beginner

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

Steps

  1. Estimates forbidden: boiling by liquid thermometer, heating by pan thermometer, deep frying by oil thermometer. dryRun

  2. The recipe asks for oil at 175 plus or minus 10 degC and limits each batch to 130 g. getRecipe

  3. A trace that holds the recipe target passes the envelope and the target. checkEnvelope

  4. Twenty steps, the deep fry by the device.

Outcome

You have the sensor list a koshari robot needs to run with no estimates and no person doing a step, and the target it must hold. Next: scenario 022 shows the limit above that target.

Command line

cookwala dryrun examples/koshari.cookwala.json --device examples/capabilities/demo-fryer-robot.json --human-present --no-model
curl -s http://localhost:7878/v1/tools/recipes/koshari
printf '[{"t": 0, "tempC": 160}, {"t": 30, "tempC": 172}, {"t": 60, "tempC": 176}, {"t": 90, "tempC": 174}]' > trace.json && cookwala envelope cw.op.deep_fry trace.json --target 175 --tolerance 10

Recorded output

Status: ok

[1] dryRun: {"state": "accepted", "plan": [{"node": "n1", "op": "cw.op.boil", "by": "device", "verifiedBy": "sensor", "rung": "cw.sense.liquid_temp"}, {"node": "n2", "op": "cw.op.simmer", "by": "device", "verifiedBy": "sensor", "rung": "cw.sense.liquid_temp"}, {"node": "n3", "op": "cw.op.drain", "by": "device", "verifiedBy": "time", "rung": "time"}, {"node": "n4", "op": "cw.op.boil", "by": "device", "verifiedBy": "sensor", "rung": "cw.sense.liquid_temp"}, {"node": "n5", "op": "cw.op.boil", "by": "device", "verifiedBy": "sensor", "rung": "cw.sense.liquid_temp"}, {"node": "n6", "op": "cw.op.drain", "by": "d…
[2] getRecipe: {"$schema": "https://cookwala.ai/v1/schemas/recipe.schema.json", "cookwala": "0.2.0", "id": "example-koshari", "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": "Koshari", "ar": "كشري"}, "cuisine"…
[3] checkEnvelope: {"envelopeOk": true, "targetOk": true, "reason": "ok"}

Code

Run: python scenarios/out/034-fryer-robot-without-estimates/python.py

# Scenario 034: The fryer robot plans koshari with every heat step verified by a sensor
# With estimates forbidden, a robot that has a pan thermometer, a liquid thermometer and an oil thermometer still plans all twenty steps of koshari, none by estimate and none by a person. Then check the recipe's own frying target against the envelope.
# 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: Estimates forbidden: boiling by liquid thermometer, heating by pan thermometer, deep frying by oil thermometer.
plan = c.dry_run(recipe_id="koshari", device_id="demo-fryer-robot", human_present=True, allow_model=False)
assert pick(plan, "state") == "accepted", pick(plan, "state")
print("ok", "state", "=", "accepted")
assert pick(plan, "plan.length") == 20, pick(plan, "plan.length")
print("ok", "plan.length", "=", 20)
assert pick(plan, "plan[0].rung") == "cw.sense.liquid_temp", pick(plan, "plan[0].rung")
print("ok", "plan[0].rung", "=", "cw.sense.liquid_temp")
assert pick(plan, "plan[6].rung") == "cw.sense.pan_surface_temp", pick(plan, "plan[6].rung")
print("ok", "plan[6].rung", "=", "cw.sense.pan_surface_temp")
assert pick(plan, "plan[13].rung") == "cw.sense.oil_temp", pick(plan, "plan[13].rung")
print("ok", "plan[13].rung", "=", "cw.sense.oil_temp")
assert pick(plan, "plan[10].rung") == "cw.sense.vision", pick(plan, "plan[10].rung")
print("ok", "plan[10].rung", "=", "cw.sense.vision")

# Step 2: The recipe asks for oil at 175 plus or minus 10 degC and limits each batch to 130 g.
koshari = c.get_recipe("koshari")
assert pick(koshari, "process.nodes[13].op") == "cw.op.deep_fry", pick(koshari, "process.nodes[13].op")
print("ok", "process.nodes[13].op", "=", "cw.op.deep_fry")
assert pick(koshari, "process.nodes[13].params.target.value") == 175, pick(koshari, "process.nodes[13].params.target.value")
print("ok", "process.nodes[13].params.target.value", "=", 175)
assert pick(koshari, "process.nodes[13].params.target.tolerance") == 10, pick(koshari, "process.nodes[13].params.target.tolerance")
print("ok", "process.nodes[13].params.target.tolerance", "=", 10)
assert pick(koshari, "process.nodes[13].params.batchMaxG") == 130, pick(koshari, "process.nodes[13].params.batchMaxG")
print("ok", "process.nodes[13].params.batchMaxG", "=", 130)

# Step 3: A trace that holds the recipe target passes the envelope and the target.
trace = c.check_envelope("cw.op.deep_fry", [{"t": 0, "tempC": 160}, {"t": 60, "tempC": 172}, {"t": 120, "tempC": 176}, {"t": 180, "tempC": 174}], {"value": 175, "tolerance": 10})
assert pick(trace, "envelopeOk") == True, pick(trace, "envelopeOk")
print("ok", "envelopeOk", "=", True)
assert pick(trace, "targetOk") == True, pick(trace, "targetOk")
print("ok", "targetOk", "=", True)
assert pick(trace, "reason") == "ok", pick(trace, "reason")
print("ok", "reason", "=", "ok")

# Step 4: Twenty steps, the deep fry by the device.
assert pick(plan, "plan[13].by") == "device", pick(plan, "plan[13].by")
print("ok", "plan[13].by", "=", "device")
assert pick(plan, "plan[13].verifiedBy") == "sensor", pick(plan, "plan[13].verifiedBy")
print("ok", "plan[13].verifiedBy", "=", "sensor")

print("scenario complete")

scenarios/034-fryer-robot-without-estimates.json

Previous: 033 Next: 035