Skip to Content
How-To GuidesMoonBitCustom Snapshots in MoonBit

Custom Snapshots in MoonBit

Golem agents can implement the Snapshottable trait to support manual (snapshot-based) updates and snapshot-based recovery.

Automatic JSON Snapshotting (Default)

When the agent struct derives ToJson and @json.FromJson, the SDK’s code generation automatically provides JSON-based snapshotting. Enable it via the snapshotting attribute on #derive.agent:

#derive.agent(snapshotting="every_n(1)") struct Counter { name : String mut value : UInt64 } derive(ToJson, @json.FromJson) fn Counter::new(name : String) -> Counter { { name, value: 0 } } pub fn Counter::increment(self : Self) -> Unit { self.value += 1 } pub fn Counter::get_value(self : Self) -> UInt64 { self.value }

The code generation tool detects ToJson and @json.FromJson derives and generates a Snapshottable implementation that serializes the agent as JSON.

Snapshotting Modes

The snapshotting attribute accepts these values:

ModeExampleDescription
(omitted)#derive.agentSnapshotting disabled
Every N#derive.agent(snapshotting="every_n(1)")Snapshot every N successful invocations

Custom Snapshotting

For custom binary serialization or cross-version migration, implement the Snapshottable trait manually:

pub(open) trait Snapshottable { save_snapshot(Self) -> Bytes load_snapshot(Self, Bytes) -> Result[Unit, String] }

Example

#derive.agent(snapshotting="every_n(1)") struct Counter { name : String mut value : UInt64 } fn Counter::new(name : String) -> Counter { { name, value: 0 } } pub fn Counter::increment(self : Self) -> Unit { self.value += 1 } pub fn Counter::get_value(self : Self) -> UInt64 { self.value } ///| pub impl @agents.Snapshottable for Counter with save_snapshot(self) { // Serialize value as 8 big-endian bytes let bytes = Bytes::new(8) let v = self.value for i in 0..<8 { bytes[i] = ((v >> ((7 - i).to_uint64() * 8)).to_int() & 0xff).to_byte() } bytes } ///| pub impl @agents.Snapshottable for Counter with load_snapshot(self, bytes) { if bytes.length() != 8 { return Err("Expected an 8-byte long snapshot") } let mut v : UInt64 = 0 for i in 0..<8 { v = v | (bytes[i].to_uint64() << ((7 - i).to_uint64() * 8)) } self.value = v Ok(()) }

Method Signatures

// Save: serialize the agent's current state to bytes save_snapshot(Self) -> Bytes // Load: restore the agent's state from previously saved bytes // Return Err to signal the update should fail and the agent should revert load_snapshot(Self, Bytes) -> Result[Unit, String]

How the SDK Wires Snapshots

The code generation tool (golem_sdk_tools agents) produces a ConstructedAgent struct for each agent. When snapshotting is enabled:

  1. If the agent has ToJson + @json.FromJson derives, the generated code automatically provides a Snapshottable implementation using JSON serialization.
  2. If the agent has a manual impl Snapshottable, the custom implementation is used instead.
  3. The ConstructedAgent records the snapshottable interface reference and snapshot_format (Json or Binary).
  4. The SDK’s save-snapshot and load-snapshot WIT exports delegate to these implementations.

Best Practices

  1. Prefer automatic (JSON) snapshotting — derive ToJson and @json.FromJson on the agent struct for zero-effort persistence.
  2. Keep snapshots small — large snapshots impact recovery and update time.
  3. Version your snapshot format — include a version byte so load_snapshot can handle snapshots from older versions.
  4. Test round-trips — verify that save_snapshotload_snapshot produces equivalent state.
  5. Handle migration — when the state schema changes between versions, load_snapshot in the new version should be able to parse snapshots from the old version.
  6. Return Err to reject incompatible snapshotsload_snapshot returning Err causes the update to fail gracefully, reverting the agent to the old version.
Last updated on