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) #

stable0.9.27
{
  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.