Saga-Pattern Transactions (Scala)

SkillAI & models

Adds transaction support with automatic undo steps to your Scala Golem agent.

Available today. Use it from your connected AI after setup.

Add ahel to your AI once: Claude, ChatGPT, Cursor, Claude Code or Codex. Then ask it to use this.

Then ask your AI: use the Saga-Pattern Transactions (Scala) skill

About this skill

Adding saga-pattern transactions with compensation to a Scala Golem agent. Use when the user asks about transactions, sagas, compensation, rollback, or multi-step operations that need undo logic.

What this skill tells your AI

The instructions your AI receives, as published by golemcloud/golem in golem-skills/skills/scala/golem-add-transactions-scala/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.

Defining Operations

Each operation has an async execute function and an async compensate function that return Future[Either[Err, Out]].

Critical: Both execute and compensate must return a Future that completes only after the underlying work finishes. If the work involves a JS Promise (e.g., fetch), convert it to a Future via FutureInterop.fromPromise and chain with flatMap/map. Never fire a Promise and ignore the result — doing so makes operation ordering non-deterministic, which breaks compensation ordering.

import golem.{Transactions, FutureInterop}
import scala.concurrent.Future
import scala.scalajs.js

// Correct: awaits the fetch Promise before completing the Future
val reserveInventory = Transactions.operation[String, Unit, String](
  orderId => {
    val promise = js.Dynamic.global.fetch(
      s"http://example.com/orders/$orderId/reserve",
      js.Dynamic.literal(method = "POST")
    ).asInstanceOf[js.Promise[js.Dynamic]]
    FutureInterop.fromPromise(promise).map(_ => Right(()))
  }
)(
  (orderId, _) => {
    val promise = js.Dynamic.global.fetch(
      s"http://example.com/orders/$orderId/reserve",
      js.Dynamic.literal(method = "DELETE")
    ).asInstanceOf[js.Promise[js.Dynamic]]
    FutureInterop.fromPromise(promise).map(_ => Right(()))
  }
)

For synchronous operations, Future.successful is fine:

val incrementCounter = Transactions.operation[Unit, Int, String](
  _ => { counter += 1; Future.successful(Right(counter)) }
)(
  (_, oldValue) => { counter = oldValue - 1; Future.successful(Right(())) }
)

Fallible Transactions

On failure, compensates completed steps and returns the error:

import golem.Transactions

val result: Future[Either[Transactions.TransactionFailure[String], (String, String)]] =
  Transactions.fallibleTransaction[(String, String), String] { tx =>
    for {
      reservation <- tx.execute(reserveInventory, "SKU-123")
      charge      <- reservation match {
        case Right(r) => tx.execute(chargePayment, 4999L).map(_.map(c => (r, c)))
        case Left(e)  => Future.successful(Left(e))
      }
    } yield charge
  }

Infallible Transactions

On failure, compensates completed steps and retries the entire transaction:

import golem.Transactions

val result: Future[(String, String)] =
  Transactions.infallibleTransaction { tx =>
    for {
      reservation <- tx.execute(reserveInventory, "SKU-123")
      charge      <- tx.execute(chargePayment, 4999L)
    } yield (reservation, charge)
  }
// Always succeeds eventually

Guidelines

  • Keep compensation logic idempotent — it may be called more than once
  • Compensation runs in reverse order of execution
  • Use fallibleTransaction when failure is an acceptable outcome
  • Use infallibleTransaction when the operation must eventually succeed

Signals

GitHub stars
2k
Forks
210
Last commit
Sep 2026
Advanced
Item type
skill
Key
golem-add-transactions-scala
Source
github.com/golemcloud/golem