Skip to main content
Laravel, thinking fast.
$provisioning->update(['response' => $result['data']]);
$lead->update(['status' => LeadStatus::Customer->value]);

The status changes after the upstream call succeeded, not when it was attempted. The row created at the start was a claim; this is the confirmation. A claim that never gets confirmed is identifiable and reapable — a status that was set optimistically is indistinguishable from a real one, forever.

Idempotency Is a Per-Tool Decision

Chapter 6 set tries = 1 on the job, and this is why. Retry policy cannot live at the job level once a turn has side effects, because a turn is a mix of tools with completely different retry semantics.

Checking a URL is idempotent — call it fifty times, nothing changes. Suggesting palettes is idempotent. Creating a site is not, and neither is sending an email.

So the loop never retries, and each tool takes responsibility for its own repeat-safety:

  • Naturally idempotent — read-only tools. Nothing to do.
  • Made idempotent by a constraint — creation guarded by a unique index on the natural key.
  • Made idempotent by a single-use token — the token is the permission, spent once.
  • Genuinely unsafe to repeat — should not be a tool the model can call freely, which is Chapter 11.

Ask of every tool: what happens if this runs twice? If the answer is not "nothing", the constraint that makes it safe belongs in the tool — not in a description, and not in the loop.

Tell the User What Happened

return Response::json([
    'site_id' => $siteId,
    'status' => $provisioning->status,
    'message' => 'Site provisioning has started.',
    'expected_url' => 'https://'.$url.'.example',
    'site_details' => $siteDetails,
    'account_url' => $accountActionUrl ?: null,
    'subscription_deadline' => 'Users must subscribe before the trial window ends
        to keep the site active. Use the window stated in your instructions —
        never state a duration of your own.',
]);

An irreversible action's result should be maximally informative, because it is the user's receipt. Not "done" — what was created, where it will be, what they must do next, and by when.

That last field is worth noting: it tells the model to defer to its instructions rather than invent a number. Chapter 2's rule about facts the model cannot know, applied where getting it wrong would be a false commercial promise.

What This Chapter Argued

  • An instruction describes intent; a constraint enforces it. Uniqueness cannot be achieved by asking nicely.
  • Persist a claim first and let a unique index on the natural key reject the duplicate.
  • Catch the constraint violation and translate it into something the model can act on, rather than a 500 it may retry.
  • A single-use token needs an owner for its whole lifetime. Pull it, hold it, and give it back on every failure path.
  • Never hold a transaction across a slow network call. One hung upstream should not exhaust your connection pool.
  • Roll back before you report. Observability can fail; compensating actions must run first.
  • Confirm state only when the work is real, so unconfirmed claims stay identifiable.
  • Retry policy belongs per tool, not per job. Ask what happens if each one runs twice.
  • Make an irreversible result a receipt — what was made, where, what to do next, by when.