Skip to Content
Next — You're reading the unreleased preview docs. APIs and pages may change before the next release.Switch to the current docs →
How-To GuidesMoonBitWaiting for External Input with Golem Promises (MoonBit)

Waiting for External Input with Golem Promises (MoonBit)

Overview

A Golem promise lets an agent suspend its execution until an external event completes it. The agent creates a promise, passes the promise ID to an external system (another agent, a webhook, a UI, an HTTP API call), and then awaits the result. Creation does not suspend the agent. For durable agents awaiting a pending promise, suspension occurs after the suspension policy delay and frees guest execution resources; persisted state and other resources remain.

API

All functions are in the @api package of the Golem MoonBit SDK:

FunctionSignatureDescription
@api.create_promise() -> PromiseIdCreates a new promise and returns its ID
@api.await_promiseasync (PromiseId) -> BytesSuspends until the promise is completed
@api.complete_promise(PromiseId, Bytes) -> BoolCompletes a promise with a byte payload
@api.get_promise(PromiseId) -> PromiseResultGets an owned handle for async result retrieval

Usage Pattern

1. Create a Promise and Wait

Run this fragment inside an async fn. Any helper or agent method that waits, and every caller of that helper, must also be async.

let promise_id = @api.create_promise() // Pass promise_id to an external system... // Awaiting can suspend the durable agent after the suspension policy delay let data : Bytes = @api.await_promise(promise_id)

2. Complete a Promise from Another Agent

let payload = "approved" @api.complete_promise(promise_id, @utf8.encode(payload[:]))

3. Advanced: Own the Result Handle

Run this fragment inside an async fn. There is no subscribe or optional synchronous polling API. Release a directly acquired handle, including when the async operation exits with an error:

let promise_result = @api.get_promise(promise_id) defer promise_result.drop() let data : Bytes = promise_result.get()

PromiseId Structure

A PromiseId contains an agent_id, a fingerprint and an oplog_idx. The fingerprint is a @types.Uuid that identifies the agent incarnation that created the promise: a deleted and re-created agent keeps its agent ID but gets a new fingerprint, so a promise ID of the deleted incarnation does not reach the new one. Golem displays a promise ID as <agent-id>/<fingerprint>/<oplog-idx>.

To let an external system complete the promise via the Golem REST API, the agent must expose the agent ID, the fingerprint and the oplog index. @schema.Uuid::from_wit(promise_id.fingerprint).to_string() gives the fingerprint as a UUID string. The external caller then sends:

POST /v1/components/{component_id}/workers/{agent_name}/complete Content-Type: application/json Authorization: Bearer <token-with-agent-invoke-permission> {"oplogIdx": <oplog_idx>, "fingerprint": "<fingerprint-uuid>", "data": [<bytes>]}

All three body fields are required.

Full Example: Human-in-the-Loop Approval

/// The values an external caller needs to complete the approval promise #derive.golem_schema pub(all) struct ApprovalRequest { oplog_idx : UInt64 fingerprint : String } #derive.agent struct WorkflowAgent { name : String mut pending : @types.PromiseId? mut last_result : String } fn WorkflowAgent::new(name : String) -> WorkflowAgent { { name, pending: None, last_result: "" } } /// Start an approval workflow that waits for external input pub fn WorkflowAgent::start_approval(self : Self) -> ApprovalRequest { let promise_id = @api.create_promise() self.pending = Some(promise_id) // Return the oplog index and the fingerprint so the external caller can complete the promise { oplog_idx: promise_id.oplog_idx, fingerprint: @schema.Uuid::from_wit(promise_id.fingerprint).to_string(), } } /// Wait for the approval to arrive and return the result pub async fn WorkflowAgent::wait_for_approval(self : Self) -> String { guard self.pending is Some(promise_id) else { return "No approval is pending" } let data = @api.await_promise(promise_id) self.pending = None let result = @utf8.decode_lossy(data[:]) self.last_result = result result }

Use Cases

Import "moonbitlang/core/encoding/utf8" @utf8, "golemcloud/golem_sdk/schema" @schema and "golemcloud/golem_sdk/interface/golem/core/types" @types in moon.pkg. The agent keeps the full PromiseId in its state, because the fingerprint cannot be rebuilt from the agent metadata. Call start_approval first and publish its returned oplog index and fingerprint together with the known component and agent IDs to the approver before invoking wait_for_approval. External completion requires agent-invoke permission. Async methods propagate host errors; external callers must handle invocation failure rather than treating it as approval. Lossy decoding replaces invalid UTF-8; validate the approval value before acting on it.

  • Human-in-the-loop: Pause a workflow until a human approves or rejects
  • Webhook callbacks: Wait for an external HTTP callback to arrive
  • Inter-agent synchronization: One agent creates a promise, another completes it
  • External event ingestion: Suspend until an IoT sensor, payment gateway, or third-party API sends a signal