Saga-Pattern Transactions (MoonBit)
SkillAI & modelsSaga-pattern transactions with compensation in MoonBit Golem agents. Use when the user asks about transactions, compensating actions, rollback, or saga patterns.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Saga-Pattern Transactions (MoonBit) skill
What this skill tells your AI
The instructions your AI receives, as published by golemcloud/golem in golem-skills/skills/moonbit/golem-add-transactions-moonbit/SKILL.md and read by ahel’s review.
Overview
Golem supports the saga pattern for multi-step operations where each step has a compensation (undo) action. If a step fails, previously completed steps are automatically compensated in reverse order.
SDK Limitation: The MoonBit SDK does not yet provide a high-level transaction/saga API like Rust's
fallible_transaction/infallible_transactionmacros. A future SDK release may add dedicated transaction support. In the meantime, the saga pattern can be implemented manually using the oplog and atomic operation APIs.
Concept
A saga transaction is a sequence of execute + compensate pairs:
- Execute — perform a step (e.g., reserve inventory, charge payment)
- Compensate — undo that step (e.g., cancel reservation, refund payment)
If step N fails, compensations for steps N-1 through 1 run in reverse order. Compensation logic must be idempotent — it may be called more than once during retries.
Manual Implementation
Use Golem's oplog and atomic operation APIs from @golem_sdk/api to build saga-style transactions manually.
Available APIs
| API | Purpose |
|---|---|
@api.mark_begin_operation() | Start an atomic region; returns an oplog index |
@api.mark_end_operation(idx) | Commit an atomic region |
@api.with_atomic_operation(f) | Run f inside an atomic region (auto-committed) |
@api.set_oplog_index(idx) | Roll back execution to a previous oplog position |
@api.get_oplog_index() | Get the current oplog position |
Defining Operations
Model each step as a pair of functions — one to execute and one to compensate:
fn reserve_inventory(sku : String) -> String {
// Call inventory API, return reservation_id
let reservation_id = call_inventory_api(sku)
reservation_id
}
fn cancel_reservation(reservation_id : String) -> Unit {
// Compensate: cancel the reservation (must be idempotent)
call_cancel_reservation_api(reservation_id)
}
fn charge_payment(amount : UInt) -> String {
// Call payment API, return charge_id
let charge_id = call_payment_api(amount)
charge_id
}
fn refund_payment(charge_id : String) -> Unit {
// Compensate: refund the payment (must be idempotent)
call_refund_api(charge_id)
}
Fallible Transaction (Manual)
On failure, compensate completed steps in reverse order and return an error:
struct CompletedStep {
compensate : () -> Unit
}
fn fallible_saga() -> Result[String, String] {
let completed : Array[CompletedStep] = []
// Step 1: Reserve inventory
let reservation_id = try {
@api.with_atomic_operation(fn() { reserve_inventory("SKU-123") })
} catch {
e => {
compensate_all(completed)
return Err("Reserve failed: \{e}")
}
}
completed.push({ compensate: fn() { cancel_reservation(reservation_id) } })
// Step 2: Charge payment
let charge_id = try {
@api.with_atomic_operation(fn() { charge_payment(4999) })
} catch {
e => {
compensate_all(completed)
return Err("Payment failed: \{e}")
}
}
completed.push({ compensate: fn() { refund_payment(charge_id) } })
Ok("reservation=\{reservation_id}, charge=\{charge_id}")
}
fn compensate_all(steps : Array[CompletedStep]) -> Unit {
// Compensate in reverse order
for i = steps.length() - 1; i >= 0; i = i - 1 {
(steps[i].compensate)()
}
}
Infallible Transaction (Manual)
On failure, compensate completed steps and retry the entire transaction using set_oplog_index:
fn infallible_saga() -> String {
let checkpoint = @api.get_oplog_index()
let completed : Array[CompletedStep] = []
// Step 1: Reserve inventory
let reservation_id = try {
@api.with_atomic_operation(fn() { reserve_inventory("SKU-123") })
} catch {
_ => {
compensate_all(completed)
@api.set_oplog_index(checkpoint) // retry from the beginning
panic() // unreachable — set_oplog_index rewinds execution
}
}
completed.push({ compensate: fn() { cancel_reservation(reservation_id) } })
// Step 2: Charge payment
let charge_id = try {
@api.with_atomic_operation(fn() { charge_payment(4999) })
} catch {
_ => {
compensate_all(completed)
@api.set_oplog_index(checkpoint) // retry from the beginning
panic() // unreachable
}
}
completed.push({ compensate: fn() { refund_payment(charge_id) } })
"reservation=\{reservation_id}, charge=\{charge_id}"
}
When set_oplog_index is called, Golem rewinds execution to the saved checkpoint. The side-effecting calls will be re-executed on retry, potentially with different results.
Guidelines
- No high-level API yet — implement sagas manually using oplog primitives as shown above
- Wrap each step in
with_atomic_operationso partial steps are retried as a unit on failure - Keep compensation logic idempotent — it may run more than once
- Compensate in reverse order of execution
- Use
set_oplog_indexfor infallible (auto-retry) semantics; useResultfor fallible semantics - Side-effecting calls (HTTP, database) should be wrapped in durable function patterns for replay safety
- A future MoonBit SDK release may add
fallible_transaction/infallible_transactionhelpers — check the SDK changelog for updates
Signals
- GitHub stars
- 2k
- Forks
- 212
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
golem-add-transactions-moonbit- Source
- github.com/golemcloud/golem