Agent Saga
Multi-step coordination with compensation cascade. When a saga's step fails mid-way, the framework fires every previously-completed step's compensate in reverse order so the operator-side state doesn't end up half-written.
Substrate for v0.9.34 submission (DKIM-sign → ARC-sign → outbox- enqueue → SMTP-deliver → store-move-to-Sent), regulated export, journal compaction, every future multi-step write.
var sendSaga = b.agent.saga.create({
name: "mail.send",
audit: b.audit,
steps: [
{
name: "dkim-sign",
run: async function (ctx, state) { state.signed = sign(state.message); },
compensate: async function (ctx, state) { /* sign is pure, nothing to undo *\/ },
},
{
name: "store-draft",
run: async function (ctx, state) { state.draftId = store.append("Drafts", state.signed); },
compensate: async function (ctx, state) { if (state.draftId) store.delete(state.draftId); },
},
{
name: "smtp-deliver",
run: async function (ctx, state) { await smtp.deliver(state.signed); },
compensate: async function (ctx, state) { /* idempotent: SMTP delivery doesn't have a recall *\/ },
},
],
});
var result = await sendSaga.run({ store, smtp }, { message: bytes });
## Compensation order
If step i throws, the framework calls step[i-1].compensate, step[i-2].compensate, ..., step[0].compensate in reverse order. Each compensate receives the SAME state object that the corresponding run mutated — operator inspects what got written and undoes it.
Compensations that throw emit agent.saga.compensation_failed audit at CRITICAL severity and halt further compensations (operator alert; manual intervention needed). On step failure the saga REJECTS (throws) rather than resolving — the thrown error carries failedStep, cause (the originating step error), compensationCause, and failedCompStepName.
## No saga-level retry
Saga's value-add is compensation, not retry. If a step needs retry-with-backoff, the operator wraps step.run with b.retry inside the step body. With v0.9.22 idempotency available, internal retry inside step.run is side-effect-safe.
b.agent.saga.create(config) #
{
name: string, // required (audit label)
steps: Array<{ name, run, compensate? }>, // required, non-empty
audit: b.audit namespace, // optional
}
Create a saga definition. Returns an instance whose run(ctx, initialState, opts) resolves to { status: "completed", sagaId, state } on success and rejects (throws) on step failure with an error carrying the failed-step + compensation detail (see the intro).
var saga = b.agent.saga.create({
name: "my.workflow",
steps: [{ name: "step1", run: async (ctx, s) => { s.x = 1; } }],
});
var final = await saga.run({}, {});
Last updated 2026-08-08T16:39:49.652Z by seeder.