$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.