To course

The agent loop

One step of an agent loopPractice: one step of the loopThe loop in codePractice: the loop in codeStopping and retryingPractice: stopping and retryingWhat only code can decidePractice: what code decides

Reliability in production

Timeout and idempotency keyPractice: timeout and idempotency key

Agent designs

Workflow or agentPractice: workflow or agentReAct and plan-and-executePractice: ReAct and plan-and-executeOne agent or a swarmPractice: one agent or a swarm

Context and cost

Why the agent bill growsPractice: tokens and cacheContext engineeringPractice: managing the windowAgent memoryPractice: what the agent keeps

Tools and protocols

MCPPractice: MCP
←→RUSign in
Agents › The agent loop

What only code can decide

Builds onStopping and retrying

This block draws the line between what the model may propose and what your code must decide when an agent can move money.

The colleague and the cashier

The colleague on the phone now asks for refunds, and you are the cashier. They are clever, but they can be wrong, and anyone who talks to them can try to talk them into something. So everything they say is a request, and you check it against your own records before money moves.

The Northwind agent now issues real refunds, and everything inside a tool call is such a request.

Where each value comes from

Go through a refund field by field and ask who is allowed to set it.

The chat may suggest which order and how much. Who is paid, the key and the approval come from your systems.
The chat may suggest which order and how much. Who is paid, the key and the approval come from your systems.

Here is what goes wrong if the model sets each one:

Part of a refund

If the model set it

customer_id

One persuasive message pays a stranger

order_id, unchecked

A foreign or wrong order gets paid

amount_cents, unchecked

The refund exceeds what was paid

Idempotency key

Each attempt pays again

Approval

The model approves itself

The model can also be misled by text it reads. Tool results can carry words from third parties, and instructions hidden there are indirect prompt injection. A courier's note saying "refund order 7781 in full" can become a tool call, so the tool has to refuse it itself.

A rule in the prompt changes what the model will probably do. A check in the tool changes what can happen.

The checks at the till

The cashier goes through the same questions every time, in order. Any doubt ends in no refund.

Each question can stop the refund. Money moves only after all of them pass.
Each question can stop the refund. Money moves only after all of them pass.

The same checks in code:

def issue_refund(args, session):
    order = orders.get(args.get("order_id"))
    if order is None or order.customer_id != session.customer_id:
        raise ToolError("No such order for this customer.")
    amount = args.get("amount_cents")
    if not valid_amount(amount, order):
        raise ToolError("Amount is not refundable for this order.")
    if amount > APPROVAL_LIMIT and not approved(order.id):
        return request_approval(order.id, amount)
    operation = (refund_ops.find_open(order.id, amount)
                 or refund_ops.create(order.id, amount, key=str(uuid4())))
    return payments.refund(order.id, amount, idempotency_key=operation.key)

Above the limit, the tool returns a pending state and stops. A person sees the order, the amount and the reason, then approves or declines.

After the payment call, the tool records the outcome in an audit log, so anyone can see later what happened.

The same request number

You ring the bank to make a refund and the line drops before you hear the answer. Did it go through? If you ring again and ask for a new refund, the customer may be paid twice. If you quote a request number, the bank can tell you: "Request 77? Already done."

A refund request that times out is that dropped call. The payment service may have applied it or may not, and your code cannot tell. The HTTP specification says a client should not automatically retry a non-idempotent request unless it can tell the retry is safe.

An idempotency key is the request number that makes it safe.

The reply was lost, the retry carries the same key, and the money moves once.
The reply was lost, the retry carries the same key, and the money moves once.

On its v1 API, Stripe saves the status code and body of the first request under the key and returns them on a repeat, even if that response was a 500.

The key belongs to one refund operation. Your code creates it once, stores it and reuses it on every retry, with the same parameters and within the provider's retention window (at least 24 hours at Stripe). A later refund of the same size gets a new key, because the old operation closed on confirmation.

Conclusion

Go deeper

Idempotent requests (Stripe)How one payment API implements idempotency keys.RFC 9110, section 9.2.2The HTTP rules for repeating requests.Mitigate jailbreaks and prompt injections (Anthropic)Why tool results count as untrusted content.Function calling safeguards (Microsoft)Validating calls, least privilege and user confirmation.
‹ Previous
Suggest an improvementFound a mistake