Esta página foi traduzida por máquina e ainda não foi revisada por uma pessoa. O inglês é a referência; correções são bem-vindas no GitHub. GitHub

Cookwala / SDK e cenários / 044

Retrying a start with the same Idempotency-Key never starts a second pot

Networks drop answers. An agent that retries POST /v1/executions with the same Idempotency-Key gets the execution already started, in its current state, instead of a second run of the same recipe.

Público: agent builder · Objetivo: Robôs para pessoas · Nível: beginner

Leia primeiro: /docs/CORE/ /docs/HUB/

Passos

  1. Start a shakshuka run with the key ex-044-idem-key. startExecution

  2. The run advances to the first step that needs a person.

  3. Send the identical request with the identical key, as a client would after a lost answer. The hub returns the same execution at its current state, not a new one. startExecution

  4. Reading the status confirms there is one execution and it is the one both answers described. getExecution

  5. The retry carried the plan of the original start.

Resultado

You can retry a start safely. Next: scenario 050 shows what happens when the key differs but the execution id repeats.

Linha de comando

cookwala hub --recipes examples --speed 200
jq '. + {"x-hub-human-present": true}' examples/core/execute-request.json | curl -s -X POST http://localhost:7878/v1/executions -H 'Content-Type: application/json' -H 'Idempotency-Key: ex-2026-10-04-001-a' -d @-
jq '. + {"x-hub-human-present": true}' examples/core/execute-request.json | curl -s -X POST http://localhost:7878/v1/executions -H 'Content-Type: application/json' -H 'Idempotency-Key: ex-2026-10-04-001-a' -d @-

Saída gravada

Status: ok

[1] startExecution: {"core": "0.2.0", "kind": "ExecutionStatus", "id": "ex-044-idem", "seq": 0, "state": "accepted", "request": "ex-044-idem", "updatedAt": "2026-10-07T00:54:27Z", "x-hub-plan": [{"node": "n1", "op": "cw.op.cut", "by": "device", "verifiedBy": "sensor", "rung": "cw.sense.vision"}, {"node": "n2", "op": "cw.op.cut", "by": "device", "verifiedBy": "sensor", "rung": "cw.sense.vision"}, {"node": "n3", "op": "cw.op.cut", "by": "device", "verifiedBy": "sensor", "rung": "cw.sense.vision"}, {"node": "n4", "op": "cw.op.heat", "by": "human", "verifiedBy": "human"}, {"node": "n5", "op": "cw.op.saute", "by": "hu…
[3] startExecution: {"core": "0.2.0", "kind": "ExecutionStatus", "id": "ex-044-idem", "seq": 6, "state": "needs_human", "request": "ex-044-idem", "updatedAt": "2026-10-07T00:54:29Z", "x-hub-plan": [{"node": "n1", "op": "cw.op.cut", "by": "device", "verifiedBy": "sensor", "rung": "cw.sense.vision"}, {"node": "n2", "op": "cw.op.cut", "by": "device", "verifiedBy": "sensor", "rung": "cw.sense.vision"}, {"node": "n3", "op": "cw.op.cut", "by": "device", "verifiedBy": "sensor", "rung": "cw.sense.vision"}, {"node": "n4", "op": "cw.op.heat", "by": "human", "verifiedBy": "human"}, {"node": "n5", "op": "cw.op.saute", "by": …
[4] getExecution: {"core": "0.2.0", "kind": "ExecutionStatus", "id": "ex-044-idem", "seq": 6, "state": "needs_human", "request": "ex-044-idem", "updatedAt": "2026-10-07T00:54:29Z", "x-hub-plan": [{"node": "n1", "op": "cw.op.cut", "by": "device", "verifiedBy": "sensor", "rung": "cw.sense.vision"}, {"node": "n2", "op": "cw.op.cut", "by": "device", "verifiedBy": "sensor", "rung": "cw.sense.vision"}, {"node": "n3", "op": "cw.op.cut", "by": "device", "verifiedBy": "sensor", "rung": "cw.sense.vision"}, {"node": "n4", "op": "cw.op.heat", "by": "human", "verifiedBy": "human"}, {"node": "n5", "op": "cw.op.saute", "by": …

Código

Executar: python scenarios/out/044-idempotent-retry-returns-the-same-execution/python.py

# Scenario 044: Retrying a start with the same Idempotency-Key never starts a second pot
# Networks drop answers. An agent that retries POST /v1/executions with the same Idempotency-Key gets the execution already started, in its current state, instead of a second run of the same recipe.
# 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: Start a shakshuka run with the key ex-044-idem-key.
try:
    first = c.start_execution({"core": "0.2.0", "kind": "ExecuteRequest", "id": "ex-044-idem", "recipe": "cw:cookwala.ai:example-shakshuka", "recipeHash": "sha256:2ea6fab93cc673fc6784f9bc9fe401d2387bb7779dc537df97286b73807c8841", "servings": 4, "requestedBy": "agent:planner-1", "idempotencyKey": "ex-044-idem-a"}, "ex-044-idem-key", True)
except CookwalaProblem as p:
    first = p.body  # a refusal is a result, not a crash
    print("refused:", p.refusal)
assert pick(first, "state") == "accepted", pick(first, "state")
print("ok", "state", "=", "accepted")
assert pick(first, "seq") == 0, pick(first, "seq")
print("ok", "seq", "=", 0)
assert pick(first, "id") == "ex-044-idem", pick(first, "id")
print("ok", "id", "=", "ex-044-idem")

# Step 2: The run advances to the first step that needs a person.
import time; time.sleep(4)

# Step 3: Send the identical request with the identical key, as a client would after a lost answer. The hub returns the same execution at its current state, not a new one.
try:
    retry = c.start_execution({"core": "0.2.0", "kind": "ExecuteRequest", "id": "ex-044-idem", "recipe": "cw:cookwala.ai:example-shakshuka", "recipeHash": "sha256:2ea6fab93cc673fc6784f9bc9fe401d2387bb7779dc537df97286b73807c8841", "servings": 4, "requestedBy": "agent:planner-1", "idempotencyKey": "ex-044-idem-a"}, "ex-044-idem-key", True)
except CookwalaProblem as p:
    retry = p.body  # a refusal is a result, not a crash
    print("refused:", p.refusal)
assert pick(retry, "id") == "ex-044-idem", pick(retry, "id")
print("ok", "id", "=", "ex-044-idem")
assert pick(retry, "state") == "needs_human", pick(retry, "state")
print("ok", "state", "=", "needs_human")
assert pick(retry, "seq") == 6, pick(retry, "seq")
print("ok", "seq", "=", 6)

# Step 4: Reading the status confirms there is one execution and it is the one both answers described.
status = c.get_execution("ex-044-idem")
assert pick(status, "seq") == 6, pick(status, "seq")
print("ok", "seq", "=", 6)
assert pick(status, "state") == "needs_human", pick(status, "state")
print("ok", "state", "=", "needs_human")

# Step 5: The retry carried the plan of the original start.
assert pick(retry, "x-hub-plan.length") == 13, pick(retry, "x-hub-plan.length")
print("ok", "x-hub-plan.length", "=", 13)

print("scenario complete")

scenarios/044-idempotent-retry-returns-the-same-execution.json

Anterior: 043 Próximo: 045