AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified Apache-2.0 Self-run

Resonate External System Of Record Pattern Go

skill-resonatehq-resonate-skills-resonate-external-system-of-record-pattern-go · by resonatehq

Maintain consistency between a Resonate Go workflow and an external system that owns the truth — a database, ledger, or third-party API — without distributed transactions, by wrapping every interaction with that system in its own idempotent ctx.Run step so the durable promise records the result and replay never re-fires the external call. Pre-release caveat: the Go SDK has no semver-tagged releas…

— No reviews yet
0 installs
29 views
0.0% view→install

Install

$ agentstack add skill-resonatehq-resonate-skills-resonate-external-system-of-record-pattern-go

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • ✓ Prompt-injection patterns
  • ✓ Secret / credential exfiltration
  • ✓ Dangerous shell & filesystem operations
  • ✓ Untrusted network calls
  • ✓ Known-malicious package signatures

What it can access

  • ✓ Network access No
  • ✓ Filesystem access No
  • ✓ Shell / process execution No
  • ✓ Environment & secrets No
  • ✓ Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-resonatehq-resonate-skills-resonate-external-system-of-record-pattern-go)

Reliability & compatibility

✓ Security review passed
0 installs to date
— no reviews yet
● 2mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Resonate External System Of Record Pattern Go? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Resonate External System of Record Pattern — Go

> Pre-release caveat. The Go SDK has no semver-tagged release yet — go get …@latest resolves to a pseudo-version; pin a commit for stability. APIs may change before the first tag is cut. Every code block here is verified against develop/go.mdx; there is no Go system-of-record example repo yet, so all snippets use only documented SDK surface at commit 22076134651f.

Overview

For the language-agnostic framing and the "Write Last, Read First" mental model, see resonate-external-system-of-record-pattern-typescript. This skill translates that model to idiomatic Go: explicit (T, error) returns, option structs, and ctx.Run-based checkpointing without a separate dependency-injection mechanism.

The core contract is simple: one system owns the truth. Resonate coordinates the workflow and guarantees at-least-once execution; the external system enforces consistency through its own primitives — idempotency keys, upserts, conditional writes, ledger deduplication.

When to use

  • A Resonate workflow must create, update, or read from a database, payment processor, ledger, or any external store that has its own durability.
  • You need multi-step operations (reserve → charge → record) to be safe to retry without double-writes.
  • You cannot use distributed transactions across Resonate's promise store and the external system.

Do not use this pattern when all writes live inside a single ACID transaction scope — just use the transaction.

The replay principle — wrap every SoR call in ctx.Run

This is the entire reason the pattern exists.

Whenever a workflow suspends and resumes (after a ctx.Sleep, a remote ctx.RPC, or a pending ctx.Promise), the entire workflow body re-executes from the top. Durable child promises short-circuit steps that already settled — the external call is not re-fired, and the stored result is returned directly. But any external call that is NOT wrapped in a ctx.Run will execute again on every replay, producing double-writes.

Rule: every read from or write to the external SoR must be inside its own ctx.Run leaf. A bare HTTP call or DB write in workflow code will fire once per replay.

// BAD — fires on every replay, may charge the card twice
func placeOrder(ctx *resonate.Context, args OrderArgs) (string, error) {
    chargeID, err := stripe.Charge(args.AmountCents, args.CardToken) // replay fires this again
    if err != nil {
        return "", err
    }
    // ...
}

// GOOD — checkpointed; on replay the stored chargeID is returned, Stripe is not called again
func placeOrder(ctx *resonate.Context, args OrderArgs) (string, error) {
    f, err := ctx.Run(chargeCard, args)
    if err != nil {
        return "", err
    }
    var chargeID string
    if err := f.Await(&chargeID); err != nil {
        return "", err
    }
    // ...
}

Idempotency keys from ctx.ID()

A ctx.Run step can re-execute on retry before it settles (Resonate delivers at-least-once). The SoR must be addressed idempotently so a duplicate call is harmless.

Derive stable keys from ctx.ID(), ctx.OriginID(), or stable workflow args — never from time.Now() or rand, which produce different values across replays and produce different keys, defeating deduplication.

type ChargeArgs struct {
    OrderID     string `json:"order_id"`
    AmountCents int64  `json:"amount_cents"`
    CardToken   string `json:"card_token"`
}

// chargeCard is called via ctx.Run — it may execute more than once before settling.
// The idempotency key is derived from the stable order ID, not wall-clock time.
func chargeCard(args ChargeArgs) (string, error) {
    idempotencyKey := "charge:" + args.OrderID // stable across retries

    req, err := http.NewRequest("POST", "https://api.stripe.com/v1/charges", encodeForm(map[string]string{
        "amount":   fmt.Sprintf("%d", args.AmountCents),
        "currency": "usd",
        "source":   args.CardToken,
    }))
    if err != nil {
        return "", err
    }
    req.Header.Set("Idempotency-Key", idempotencyKey)
    // ... execute request, parse response
    return chargeID, nil
}

Full order workflow: reserve → charge → record

Three sequential SoR interactions, each in its own ctx.Run. Any step that has already settled short-circuits on replay; no external system is hit twice.

type OrderArgs struct {
    OrderID     string `json:"order_id"`
    SKU         string `json:"sku"`
    Quantity    int    `json:"quantity"`
    AmountCents int64  `json:"amount_cents"`
    CardToken   string `json:"card_token"`
}

type ReserveArgs struct {
    OrderID  string `json:"order_id"`
    SKU      string `json:"sku"`
    Quantity int    `json:"quantity"`
}

type ChargeArgs struct {
    OrderID     string `json:"order_id"`
    AmountCents int64  `json:"amount_cents"`
    CardToken   string `json:"card_token"`
}

type RecordArgs struct {
    OrderID  string `json:"order_id"`
    ChargeID string `json:"charge_id"`
}

// recordOrder writes the finalized order to the SoR. Idempotent via ON CONFLICT
// DO NOTHING keyed on OrderID, so a replay-retry never double-records.
func recordOrder(_ *resonate.Context, args RecordArgs) (struct{}, error) {
    _ = args // INSERT ... ON CONFLICT (order_id) DO NOTHING
    return struct{}{}, nil
}

// createOrder orchestrates three idempotent SoR steps.
// Each ctx.Run is checkpointed: on replay, settled steps return their stored
// result without touching the external system.
func createOrder(ctx *resonate.Context, args OrderArgs) (string, error) {
    // Step 1 — reserve inventory; idempotent via ON CONFLICT in the DB
    fReserve, err := ctx.Run(reserveInventory, ReserveArgs{
        OrderID:  args.OrderID,
        SKU:      args.SKU,
        Quantity: args.Quantity,
    })
    if err != nil {
        return "", err
    }
    var reservationID string
    if err := fReserve.Await(&reservationID); err != nil {
        return "", err
    }

    // Step 2 — charge payment; idempotent via Stripe idempotency key
    fCharge, err := ctx.Run(chargeCard, ChargeArgs{
        OrderID:     args.OrderID,
        AmountCents: args.AmountCents,
        CardToken:   args.CardToken,
    }, resonate.RunOpts{
        RetryPolicy: resonate.ExponentialRetry{
            MaxAttempts: 5,
            Base:        100 * time.Millisecond,
            Max:         30 * time.Second,
            Jitter:      true,
        },
    })
    if err != nil {
        return "", err
    }
    var chargeID string
    if err := fCharge.Await(&chargeID); err != nil {
        return "", err
    }

    // Step 3 — record the order in the SoR DB; idempotent via ON CONFLICT DO NOTHING
    fRecord, err := ctx.Run(recordOrder, RecordArgs{OrderID: args.OrderID, ChargeID: chargeID})
    if err != nil {
        return "", err
    }
    if err := fRecord.Await(nil); err != nil {
        return "", err
    }

    return args.OrderID, nil
}

Check-then-act made replay-safe

A common SoR pattern is: read current state, branch on it, then apply a mutation. Both the read and the write must be in separate ctx.Run calls. Reading the SoR in bare workflow code is unsafe — on replay the bare read fires again and may observe a different value (e.g., another process updated the account between the first run and the replay).

type AccountArgs struct {
    AccountID string `json:"account_id"`
    Delta     int64  `json:"delta"`
}

type AccountState struct {
    Balance int64  `json:"balance"`
    Status  string `json:"status"`
}

// adjustBalance reads account state as a checkpointed step, branches in plain
// Go code (no SoR access here), then applies the mutation in another step.
func adjustBalance(ctx *resonate.Context, args AccountArgs) (int64, error) {
    // Checkpointed read — on replay returns the stored snapshot, not a fresh DB hit
    fState, err := ctx.Run(getAccountState, args.AccountID)
    if err != nil {
        return 0, err
    }
    var state AccountState
    if err := fState.Await(&state); err != nil {
        return 0, err
    }

    // Branch in plain workflow code — no external calls here
    if state.Status == "frozen" {
        return 0, resonate.NewNonRetryable(fmt.Errorf("account %s is frozen", args.AccountID))
    }
    if state.Balance+args.Delta ` enum or `?` operator. Error paths are explicit `if err != nil` chains.
- **Checkpointed reads via `ctx.Run`.** Go has no dependency-injection mechanism like Rust's `ctx.get_dependency::()`. Pass clients/pools as closed-over values or construct them outside the workflow and reference them in the leaf closure. The checkpoint is the same — the leaf's return value is stored in the durable promise and returned on replay without re-executing.
- **Stable idempotency-key derivation.** Use `ctx.ID()` or a deterministic hash of stable args. `time.Now()` and `math/rand` produce different values across replays — never use them to derive keys passed into a SoR.
- **`resonate.NewNonRetryable` for 4xx SoR errors.** Stops the step retry loop; the caller's `Await` returns the wrapped error immediately.
- **Option structs are the last positional arg.** `ctx.Run(fn, args, resonate.RunOpts{...})` — the options struct must be last; misplacing it passes it as the function argument.

## Avoid

- **Bare external calls in workflow code.** Any DB query, HTTP call, or file write outside a `ctx.Run` re-executes on every replay. Always wrap SoR interactions in a leaf.
- **`time.Now()` or `rand` as idempotency key inputs.** These differ across replays; the SoR sees a different key each time and cannot deduplicate. Derive keys from `ctx.ID()` or stable args.
- **Reading the SoR in bare workflow code.** The read fires again on replay and may observe a changed value. Checkpoint SoR reads in their own `ctx.Run`.
- **Writing to two systems without a clear SoR.** If both fail partially, neither wins. Designate one SoR; make the other a compensatable side-effect. For multi-system compensation, see `resonate-saga-pattern-go`.

## Related skills

- `resonate-basic-durable-world-usage-go` — `ctx.Run`, `ctx.RPC`, `ctx.Sleep`, option structs, `RegisteredFunc.Run`
- `resonate-saga-pattern-go` — when the SoR doesn't cover all steps and you need compensation across multiple services
- `durable-execution` — foundational replay semantics; this pattern is checkpoint-centric
- `resonate-external-system-of-record-pattern-typescript` — same pattern, TS SDK; good for mental model
- `resonate-external-system-of-record-pattern-rust` — same pattern, Rust SDK; type-dispatched DI contrast

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [resonatehq](https://github.com/resonatehq)
- **Source:** [resonatehq/resonate-skills](https://github.com/resonatehq/resonate-skills)
- **License:** Apache-2.0

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.