agentsclimarketplace

Resonate basic ephemeral world usage go

Skill resonatehq/resonate-skills/resonate-basic-ephemeral-world-usage-go

Agent skills for building with Resonate — durable execution for long-running, crash-safe workflows.

Install
npx -y skills add resonatehq/resonate-skills --skill resonate-basic-ephemeral-world-usage-go

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 5 stars5 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Core patterns for using the Resonate Go SDK's Client APIs from the ephemeral world — initializing a Resonate instance, registering durable functions with the package-level generic resonate.Register, invoking them top-level via RegisteredFunc.Run, dispatching remotely with Resonate.RPC, reconnecting to an existing execution with Resonate.Get, reading typed and untyped handle results, RunOptions, and Stop semantics. Pre-release caveat: the Go SDK has no semver-tagged release yet; APIs may change before the first tag is cut.

The file declares its own license as Apache-2.0. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.

SKILL.md

11.6 KB, as published. Nobody here has run it

Resonate Basic Ephemeral World Usage — 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 and the resonatehq-examples/*-go repos at SDK commit 22076134651f.

Overview

The ephemeral world is wherever your Go program starts: main(), an HTTP handler, a CLI entry point, a background goroutine. You use a *resonate.Resonate instance to register durable functions and invoke them top-level. Once an invocation starts, it crosses into the durable world and Resonate guarantees its completion — retries on failure, resumes after crashes, continues across process restarts.

This skill covers the Client API surface only. The Durable World (Context APIs inside workflow functions) is covered in resonate-basic-durable-world-usage-go.

Install

go get github.com/resonatehq/resonate-sdk-go@latest

Pin a specific commit for reproducible builds until a semver tag is published:

go get github.com/resonatehq/resonate-sdk-go@22076134651f

Core imports:

import (
    resonate "github.com/resonatehq/resonate-sdk-go"
    "github.com/resonatehq/resonate-sdk-go/localnet"  // zero-dependency dev
    "github.com/resonatehq/resonate-sdk-go/httpnet"   // named worker groups
)

Basic Shape

A complete one-file program: register, run, read, stop.

package main

import (
    "context"
    "fmt"
    "log"
    "time"

    resonate "github.com/resonatehq/resonate-sdk-go"
)

type GreetArgs struct {
    Name string `json:"name"`
}

func greet(_ *resonate.Context, args GreetArgs) (string, error) {
    return fmt.Sprintf("hello, %s!", args.Name), nil
}

func main() {
    r, err := resonate.New(resonate.Config{URL: "http://localhost:8001"})
    if err != nil {
        log.Fatalf("resonate.New: %v", err)
    }
    defer func() { _ = r.Stop() }()

    greetFn, err := resonate.Register(r, "greet", greet)
    if err != nil {
        log.Fatalf("Register: %v", err)
    }

    ctx := context.Background()
    id := fmt.Sprintf("hello-%d", time.Now().UnixNano())

    h, err := greetFn.Run(ctx, id, GreetArgs{Name: "world"})
    if err != nil {
        log.Fatalf("Run: %v", err)
    }

    out, err := h.Result(ctx)
    if err != nil {
        log.Fatalf("Result: %v", err)
    }
    fmt.Println(out) // hello, world!
}

Initialization

Remote server

r, err := resonate.New(resonate.Config{URL: "http://localhost:8001"})

Config fields:

FieldTypeDefaultPurpose
URLstringunsetShorthand HTTP transport at this address (default group "default").
NetworkNetworknilExplicit transport — use when URL is empty.
HeartbeatHeartbeatAsyncHeartbeat at TTL/2Keeps acquired task leases alive.
EncryptorEncryptorNoopEncryptorCodec encryption at the durability boundary.
TTLtime.Duration60sPer-task lease duration.
PrefixstringemptyPrepended (with :) to every promise/task ID.
TokenstringunsetSent as Bearer auth on every protocol request.

Network precedence: Config.URLConfig.NetworkRESONATE_URL env var → resonate.ErrNetworkRequired. The only env var the constructor reads is RESONATE_URL.

Zero-dependency development (localnet)

pid := "dev-worker"
r, err := resonate.New(resonate.Config{
    Network:   localnet.NewLocal("default", &pid),
    Heartbeat: resonate.NoopHeartbeat{}, // required — localnet has no lease endpoint
})

Named worker group

There is no Group field on Config. Pass the transport explicitly:

r, err := resonate.New(resonate.Config{
    Network: httpnet.NewHTTP("http://localhost:8001", httpnet.HTTPOptions{
        Group: "worker-group-a",
    }),
})

Authentication

r, err := resonate.New(resonate.Config{
    URL:   "https://resonate.example.com",
    Token: os.Getenv("RESONATE_TOKEN"),
})

resonate.Register

Register is a package-level generic function (Go has no method-level generics). It takes the instance, a name, and the function; returns a typed *RegisteredFunc[A, R] and an error.

greetFn, err := resonate.Register(r, "greet", greet)
if err != nil {
    log.Fatalf("Register: %v", err)
}

Registered functions must have the canonical signature func(*resonate.Context, A) (R, error). Use struct{} for A or R when the function takes no input or produces no meaningful result.

RegisteredFunc.Run

Invokes a registered function in the same process and returns a typed *TypedHandle[R]. The returned handle is durable — the function will complete even if the process crashes and another worker picks it up.

h, err := greetFn.Run(ctx, "greet-1", GreetArgs{Name: "world"})
if err != nil {
    log.Fatalf("Run: %v", err)
}

result, err := h.Result(ctx) // returns (string, error) — typed
if err != nil {
    log.Fatalf("Result: %v", err)
}

RunOptions

Pass as a trailing argument to Run:

h, err := greetFn.Run(ctx, "greet-1", GreetArgs{Name: "world"}, resonate.RunOptions{
    Timeout:     60 * time.Second,
    Target:      "poll://any@workers",
    Tags:        map[string]string{"team": "checkout"},
    RetryPolicy: resonate.ConstantRetry{MaxAttempts: 3, Delay: time.Second},
})
FieldPurpose
TimeoutCaps the root promise deadline. Zero uses DefaultTopLevelTimeout (24h).
TargetLogical routing address (resonate:target tag). Empty falls back to the configured group.
TagsMerged into the root promise's tag set.
RetryPolicyRe-execution policy on error. Nil applies DefaultRetryPolicy (exponential, 3 attempts). Only takes effect when this worker wins the create-and-acquire race and runs the function locally; a task picked up by another worker via server push uses DefaultRetryPolicy.
VersionReserved — declared but not yet consumed (issue #5).

Resonate.RPC

Invokes a registered function in a remote process by name. Returns an untyped *Handle; the target function does not need to be registered locally.

h, err := r.RPC(ctx, "greet-1", "greet", GreetArgs{Name: "world"})
if err != nil {
    log.Fatalf("RPC: %v", err)
}

var result string
if err := h.Result(ctx, &result); err != nil {
    log.Fatalf("Result: %v", err)
}

RPC accepts an optional resonate.RPCOptions{Timeout, Target, Tags, Version} as a trailing argument.

Resonate.Get

Gets a handle to an existing execution by promise ID. Returns *resonate.ServerError with Code: 404 when the promise does not exist.

h, err := r.Get(ctx, "greet-1")
if err != nil {
    log.Fatalf("Get: %v", err)
}

var result string
if err := h.Result(ctx, &result); err != nil {
    log.Fatalf("Result: %v", err)
}

Reading Handle Results

  • Typed handleRegisteredFunc.Run returns *TypedHandle[R]; call h.Result(ctx) to get (R, error) directly.
  • Untyped handleRPC and Get return *Handle; call h.Result(ctx, &out) and pass a pointer to the target variable.
  • Generic helper for untyped handles:
result, err := resonate.ResultOf[string](ctx, h)

A rejected promise surfaces as *resonate.ApplicationError (or the deserialized concrete error where available).

Stop Semantics

defer func() { _ = r.Stop() }()

Stop closes the network connection, stops the heartbeat loop, and cancels the background subscription-refresh goroutine. It is idempotent.

Call Stop from processes that should exit after their work finishes: demo binaries, one-shot jobs, CI tasks, examples. Without it, background goroutines keep the process alive after main would otherwise return.

Do not call Stop on a long-running worker. Calling it tears down the channels a worker uses to receive and hold work:

  • The connection to the Resonate Server closes — the worker stops receiving dispatched tasks.
  • The heartbeat loop stops — the server-side TTL on in-flight tasks expires, and the server reassigns them.
  • The subscription-refresh goroutine is cancelled — listeners on awaited promises are no longer re-registered.

The worker process keeps running but silently stops processing. Let SIGINT / SIGTERM end a worker's lifecycle instead.

Distinct Go Idioms

  • if err != nil everywhere — both resonate.New, resonate.Register, Run, RPC, Get, and Result return errors. Use explicit checks; no method chaining.
  • Option structs, not buildersRunOptions, RPCOptions, RunOpts, RPCOpts are plain structs with exported fields. Pass them as trailing arguments; zero values mean "use the default."
  • Package-level generic resonate.Register — Go has no method-level generics, so registration is a free function, not a method on the instance. This is why greetFn carries the type parameters, not r.
  • Exported struct fields with json tags for args — args are JSON-serialized into the durable promise so any worker that resumes the function can decode them. Unexported fields are silently dropped.
  • context.Context is ctx passed to Run/RPC/Get/Result — this is the standard context.Context (cancellation, deadlines), not the *resonate.Context inside a workflow.
  • defer func() { _ = r.Stop() }() — the closure-wrapped defer is idiomatic Go for deferred calls where you want to discard the error without a naked _ assignment at the call site.
  • time.Duration for timeouts60 * time.Second, not raw integers. Durations are native time.Duration throughout the SDK.

Avoid

  • Calling resonate.New with neither URL nor Network and without RESONATE_URL set — returns ErrNetworkRequired at startup.
  • Using localnet without Heartbeat: resonate.NoopHeartbeat{} — the default AsyncHeartbeat issues HTTP keep-alive requests that localnet cannot serve; the heartbeat loop errors.
  • Passing a Group on Config — there is no Group field. Use httpnet.NewHTTP(..., httpnet.HTTPOptions{Group: "..."}) via Config.Network.
  • Using Client APIs (r.RPC, greetFn.Run) inside a durable function — those belong to the Ephemeral World. Use ctx.RPC and ctx.Run inside workflows.
  • Calling Stop on a long-running worker (see Stop Semantics above).
  • Relying on Config.Version in RunOptions / RPCOptions — the field is declared but not yet consumed.

Related Skills

  • resonate-basic-durable-world-usage-go — Context APIs: ctx.Run, ctx.RPC, ctx.Sleep, ctx.Promise, ctx.Detached, fan-out patterns, retry policies, context accessors
  • durable-execution — foundational concepts; read this first if new to Resonate
  • resonate-defaults — SDK-wide defaults reference (TTL, retry policy, top-level timeout, child timeout)
  • resonate-basic-ephemeral-world-usage-typescript — TypeScript sibling for comparison
  • resonate-basic-ephemeral-world-usage-rust — Rust sibling for comparison

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.