Configuring Agent Durability (Go)
Overview
Durability is a property of the agent definition — it lives on the golem.Spec
you pass to golem.DefineAgent. There is no attribute or macro; you set two
struct fields:
Mode—golem.Durable(the default) orgolem.Ephemeral.Snapshot— agolem.SnapshotPolicy, defaulting togolem.SnapshotDisabled.
Nothing in the implementation package changes: handlers are written the same way regardless of mode.
Steps
- Decide the mode — durable (default) unless the agent is genuinely stateless.
- Set
Modeon thegolem.Specin the definition package (omit it for durable). - Add a snapshot policy to reduce recovery replay cost, not oplog writes or storage growth.
- Build — run
golem build.
Durable Agents (Default)
By default every Golem agent is durable — Mode defaults to golem.Durable,
so you never write it explicitly:
- State persists across invocations, failures, and restarts.
- Every side effect is recorded in an oplog (operation log).
- On failure, the agent is transparently recovered by replaying the oplog.
- No special code needed — durability is automatic.
You cannot opt out of oplog writes for a durable agent — the oplog is how durability works. Do not try to skip persistence to reduce oplog volume. Snapshots reduce recovery replay cost, not oplog writes or storage growth. Use an explicit snapshot policy for recovery (below).
// Package counter is the DEFINITION of a standard durable agent.
package counter
import "github.com/golemcloud/golem/sdks/go/golem"
type ID struct{ Name string }
// Mode is omitted, so it defaults to golem.Durable.
var Agent = golem.DefineAgent[ID](golem.Spec{
Name: "CounterAgent",
Description: "A simple durable counter",
})
var (
Increment = Agent.Method[golem.Unit, int64]("increment", golem.Description("Increase the count by one"))
Value = Agent.Method[golem.Unit, int64]("value", golem.Description("Return the current value"))
)The implementation is ordinary — the private state is rebuilt by oplog replay
on recovery:
package impl
import (
"myapp/agents/counter"
"github.com/golemcloud/golem/sdks/go/golem"
)
type state struct{ count int64 }
var agent = counter.Agent.Implement(func(counter.ID) *state { return &state{} })
func init() {
agent.Handle(counter.Increment, func(ctx *golem.Context[state], _ golem.Unit) int64 {
ctx.State.count++
return ctx.State.count
})
agent.Handle(counter.Value, func(ctx *golem.Context[state], _ golem.Unit) int64 {
return ctx.State.count
})
}Durable with Periodic Snapshots
Same durability guarantees as the default, but recovery starts from the latest snapshot and replays subsequent oplog entries instead of the whole oplog. This reduces replay cost, not oplog writes or storage growth.
Periodic snapshots run at invocation boundaries or while the agent is idle; they do not interrupt an endless handler on a timer. Split recurring work into bounded invocations if it must reach snapshot points.
For Go, implement a custom golem.Snapshotter as well as setting a policy.
Custom recovery supports state restoration and OnRestore after restart.
Default JSON recovery and manual snapshot-based component updates have separate
runtime limitations. See golem-custom-snapshot-go before enabling snapshots.
Set the Snapshot field on the golem.Spec:
import "time"
// Snapshot every 10 invocations.
var Agent = golem.DefineAgent[ID](golem.Spec{
Name: "CounterAgent",
Snapshot: golem.SnapshotEveryN(10),
})
// Or at most once per interval.
var HeartbeatAgent = golem.DefineAgent[ID](golem.Spec{
Name: "HeartbeatAgent",
Snapshot: golem.SnapshotPeriodic(30 * time.Second),
})
// Or let the platform pick the cadence.
var PipelineAgent = golem.DefineAgent[ID](golem.Spec{
Name: "PipelineAgent",
Snapshot: golem.SnapshotDefault,
})The snapshot policy constructors are:
| Constructor | Meaning |
|---|---|
golem.SnapshotDisabled | The zero value — the platform never snapshots. |
golem.SnapshotDefault | Snapshot at the platform’s default cadence. |
golem.SnapshotEveryN(n uint16) | Snapshot every n invocations. |
golem.SnapshotPeriodic(d time.Duration) | Snapshot when due at an invocation boundary or while idle, not inside a running handler. |
What gets snapshotted
Go reflection cannot see unexported fields, so the idiomatic private state
(e.g. an unexported count) is only captured if the state type implements the
golem.Snapshotter interface:
type Snapshotter interface {
Save() ([]byte, error)
Load([]byte) error
}Without a Snapshotter, the snapshot is the JSON of the state’s exported
fields only. Use Save/Load on the *state for recovery even when all fields
are exported; default JSON serialization does not remove the recovery limitation
described above.
Ephemeral Agents
Use ephemeral mode for stateless, per-invocation agents where persistence is not needed:
- State is discarded after each invocation completes.
- No durable state across invocations — lower overhead.
- Useful for pure functions, request handlers, or adapters.
var Agent = golem.DefineAgent[ID](golem.Spec{
Name: "StatelessHandler",
Mode: golem.Ephemeral,
})When to Choose Which
| Use Case | Mode |
|---|---|
| Counter, shopping cart, workflow orchestrator | Durable (default) |
| Stateless request processor, transformer | Ephemeral |
| Long-running saga or multi-step pipeline | Durable (default) |
| Pure computation, no side effects worth persisting | Ephemeral |
| Agent that calls external APIs with at-least-once semantics | Durable (default) |
| Agent with bounded heartbeat, polling, or recurring invocations | Durable + periodic snapshots |
| Any durable agent whose oplog grows so large that replay is slow | Durable + periodic snapshots |
When in doubt, use the default (durable). Ephemeral is an optimization for agents that genuinely don’t need persistence; add periodic snapshots whenever recovery time matters.
Key Constraints
- Mode and snapshot policy are set on the definition (
golem.Spec), not the implementation — the handler code is identical either way. Modedefaults togolem.Durable; only set it to switch togolem.Ephemeral.Snapshotdefaults togolem.SnapshotDisabled; snapshots do not change durability guarantees, only recovery time.- Read-only methods require a durable agent — marking a method on an
Ephemeralagent read-only is a definition error (seegolem-mark-read-only-go). - Unexported private state is not captured by the default reflective snapshot;
implement
golem.Snapshotterif you snapshot such an agent.
Related Guides
| Guide | Description |
|---|---|
golem-add-agent-go | Define the agent and its methods in the first place |
golem-mark-read-only-go | Add cacheable, side-effect-free query methods (durable only) |
golem-recurring-task-go | Long-running agents that heartbeat or schedule future work |