Invariants: rules checked at decision time
An invariant is evaluated before each tool call runs, against the world as it was when the agent decided. That timing matters:- Attempting is the bug. If the agent tries to book a carrier whose insurance has expired, the trial fails, even if a
checkthen rejects the booking. An agent that relies on the API to stop it will eventually meet an API that doesn’t. - Order is visible.
beforeinvariants see the call history, so “verify the carrier before booking it” fails when the verification comes after the booking, or was for a different carrier. - The domain states the rule once. It then holds in every task, including tasks written later, and tasks a drafting step adds.
The spec has the exact syntax.
Soft invariants: measure what the policy allows
Some behaviour is allowed but worth measuring. The freight policy lets the agent offer a load’smax_rate as a counter-offer, but an agent that always concedes to the ceiling leaves margin on the table. Mark such a rule soft: true: it is checked like any other, but never fails a trial. Runs report how often it was broken, out of the trials where it applied:
Expectations: what success looks like
Expectations belong to one task and describe the outcome its conversation should reach:stateis the main signal: the records the conversation should have created or changed, or left alone (count: 0, orkeywith the originalfields).calledandnot_calledcover actions that leave no record, such as a lookup that must happen.reply_includesmatches text in what the agent said. Wording varies between runs and models, so prefer state.
Which one to use
- If it should hold in every conversation, it is an invariant: “never book above the ceiling”, “verify before booking”.
- If it is the outcome of one scenario, it is an expectation: “this caller ends up with L-1004 booked”, “nothing is booked when the caller insists on $1,600”.
- If you find yourself adding the same
not_calledto many tasks, it is probably an invariant (never, orneverwithwhen). - Keep invariants as strict as the SOP and no stricter. A rule like “never transfer to a human” written as an invariant fails correct handoffs everywhere.