Side-Effect Tracking
Mark which tools change the real world so retries and rollback know what to undo. Without this distinction, you either retry everything (duplicate writes) or retry nothing (lost progress).
Marking side-effects
// Read, safe to retry
const search = guard(searchFn)
// Changes the world, needs special handling
const sendInvoice = guard(invoiceFn, {
sideEffect: true,
compensate: cancelInvoice,
})Why it matters
When an agent run fails at step 5, Fuze needs to know:
- Steps 1-3 were reads, safe to ignore on rollback
- Step 4 sent an invoice, must call compensation function
- Step 5 failed, this is where we are
Without side-effect tracking, you either retry everything (duplicate invoices) or retry nothing (lost progress).
Compensation functions
A compensation function undoes a side-effect:
const sendInvoice = guard(
async function sendInvoice(customerId: string, amount: number) {
return await stripe.createInvoice(customerId, amount)
},
{
sideEffect: true,
compensate: async (result) => {
// result is the return value of the original call
await stripe.voidInvoice(result.invoiceId)
},
}
)On rollback, Fuze calls compensation functions in reverse order, last side-effect first.
Compensation timestamp accuracy
When a compensation handler runs, Fuze captures compensationEndedAt after the handler completes (not before). This ensures the timestamp accurately reflects when the compensation finished, which is important for audit trails and SLA tracking.
Compensation hash chain verification
Every compensation record is included in the audit hash chain. You can verify the integrity of the full chain, including compensation entries, by calling verifyHashChain(). This ensures that no records have been tampered with or inserted after the fact.
Idempotency keys
Fuze generates idempotency keys for side-effect calls. If the same call is retried with the same arguments, the key ensures no duplication.
Idempotency keys are automatically cleaned up when you call purgeOlderThan() as part of data retention. This prevents stale keys from accumulating over time.
Rollback flow
Fuze walks back from step 4 to step 3, calling each compensation function in reverse.
Non-compensable side-effects
If a side-effect has no compensation function, Fuze logs it with status no_compensation and sets escalated: true. The incident is recorded in the audit trail for human review.
Data retention
The purgeOlderThan() method cleans up old records including side-effect entries, compensation records, and idempotency keys. Both compensation records and side-effect records are included in verifyHashChain(), so you should verify chain integrity before purging if you need a final audit check.