Additional Runtime APIs
Generate an idempotency key
Golem provides a function to generate an idempotency key (a UUID) which can be passed to external systems to ensure that the same request is not processed multiple times.
It is guaranteed that this idempotency key will always be the same (per occurrence) even if the agent is restarted due to a crash.
To generate an idempotency key:
TypeScript
import { generateIdempotencyKey } from "@golemcloud/golem-ts-sdk";
const key = generateIdempotencyKey();Get agent metadata
It is possible to query metadata for Golem agents. This metadata is defined by the AgentMetadata interface:
TypeScript
/**
* Metadata about an agent
*/
export type AgentMetadata = {
/** The agent ID, consists of the component ID, agent type and agent parameters */
agentId: AgentId;
/** Command line arguments seen by the agent */
args: string[];
/** Environment variables seen by the agent */
env: [string, string][];
/** Configuration variables seen by the agent */
config: [string, string][];
/** The current agent status */
status: AgentStatus;
/** The component revision the agent is running with */
componentRevision: bigint;
/** The agent's current retry count */
retryCount: bigint;
/** The environment the agent belongs to */
environmentId: EnvironmentId;
};There are two exported functions to query agent metadata:
getSelfMetadata()returns the metadata for the current agentgetAgentMetadata(agentId: AgentId)returns the metadata for a specific agent given by itsAgentId, orundefinedif it does not exist
Enumerate agents
Agent enumeration is a feature of Golem available both through the public HTTP API and using the SDK.
Enumerating agents of a component is a slow operation and should not be used as part of the application logic.
The following example demonstrates how to use the agent enumeration API:
TypeScript
import {
ComponentId,
GetAgents,
AgentAnyFilter,
AgentMetadata,
AgentStatusFilter,
} from "golem:api/host@1.5.0"
const filter: AgentAnyFilter = {
filters: [
{
filters: [
{
tag: "status",
val: {
comparator: "equal",
value: "idle",
} satisfies AgentStatusFilter,
},
],
},
],
}
const componentId: ComponentId = {
/* ... */
}
const agents: AgentMetadata[] = []
const getter = new GetAgents(componentId, filter, true)
let batch: AgentMetadata[] | undefined
while ((batch = getter.getNext()) !== undefined) {
agents.push(...batch)
}The precise parameter or option enables precise mode. In this mode, Golem calculates the latest metadata for each returned agent; otherwise, it uses only the last cached values.
Update an agent
TypeScript
To trigger an update for a given agent from one component version to another, use the updateAgent function:
import {
getSelfMetadata,
updateAgent,
type AgentId,
type ComponentRevision,
} from "golem:api/host@1.5.0"
const agentId: AgentId = getSelfMetadata().agentId
const targetRevision: ComponentRevision = 1n
updateAgent(agentId, targetRevision, "automatic")To learn more about updating agents, see the Agent Update section of the agents page.
Oplog search and query
The oplog interface in golem:api provides functions to search and query the agent’s persisted oplog.
The interface defines a big variant data type called oplog-entry, and two resources for querying an agent’s oplog.
- the
get-oplogresource enumerates through all entries of the oplog - the
search-oplogresource accepts a search expression and only returns the matching entries
Both resources, once constructed, provide a get-next function that returns a chunk of oplog entries. Repeatedly calling this function goes through the whole data set, and eventually returns none.
Custom durability for library authors
The low-level golem:durability@1.6.0 interface is for SDK and library authors who need to wrap raw host effects as one durable operation. Application code should use the normal high-level SDK APIs and Golem’s built-in durability instead.
A library opens the operation with begin-custom-durable-invocation, passing a stable function name, a typed request, and a durable function type. The executor synchronously allocates a replay-stable UUID before the host call can yield. The UUID identifies the logical call; the request remains its independently recorded and validated semantic input. This separation means a unit/no-input request remains correct for generators while concurrent calls still have distinct identities. The result selects one of two paths:
live(live-invocation): perform the complete operation while the returned resource is active, then consume the resource by calling its staticfinishfunction with the typed result.replayed(persisted-invocation): do not perform the physical body. Decode and return the recorded response.
Only the live result contains a resource. Host calls initiated while it is active remain available as replay-inert observational oplog entries owned by the custom operation. Keep the resource alive until every synchronous or asynchronous host effect has completed.
invocation = begin-custom-durable-invocation(name, request, function-type)
match invocation:
live(live-invocation):
result = perform-entire-physical-operation()
live-invocation.finish(encode(result), forced-commit)
return result
replayed(recorded):
return decode(recorded.response)Golem derives the UUID from the top-level idempotency key, the parent custom invocation when nested, and a deterministic ordinal in that logical parent’s namespace. Child operations therefore do not shift later root identities when a completed parent is skipped during replay. The identity does not depend on the request payload.
An unfinished live invocation is retried as a whole operation. Individual physical effects are not independently resumed, so the library must make a repeated attempt safe by selecting the correct function type and, where appropriate, using external idempotency keys or transactions.
The resource is affine: finish consumes it, and it cannot be completed twice. Dropping it unfinished removes observational ownership without recording an End, so recovery recognizes the operation as incomplete and retries its body.
The request, response, operation name, and observational host calls are intended to support observability. They may appear in oplog inspection, logs, and metrics. Use useful schemas and names, but redact credentials and personal data, and avoid recording unnecessary or unbounded payloads.
Use an SDK combinator instead of handling the live resource manually. Rust provides golem_rust::durability::Durability::run, run_async, run_infallible, and run_infallible_async; TypeScript provides durable; Effect TypeScript provides Durability.wrap and wrapInfallible; Scala provides DurabilityApi.durable and durableAsync; MoonBit provides @api.durable and durable_async. These combinators own begin/replay/finish/drop and evaluate the operation body only for a live invocation.
Invocation context
Golem associates an invocation context with each invocation, which contains various information depending on how the exported function was called. This context gets inherited when making further invocations via agent-to-agent communication, and it is also possible to define custom spans and associate custom attributes to it.
The spans are not automatically sent to any tracing system but they can be reconstructed from the oplog, for example using oplog processor plugins, to provide real-time tracing information.
TypeScript
To get the current invocation context, use the currentContext host function, imported from golem:api/context@1.5.0:
/**
* Invocation context support
*/
declare module 'golem:api/context@1.5.0' {
/**
* Gets the current invocation context
* The function call captures the current context; if new spans are started, the returned `invocation-context` instance will not
* reflect that.
*/
export function currentContext(): InvocationContext;
}The InvocationContext itself is a class with various methods for querying attributes of the invocation context:
| method | description |
|---|---|
traceId | Returns the trace ID associated with the context, coming from either an external trace header or generated at the edge of Golem |
spanId | Returns the span ID associated with the context |
parent | Returns the parent invocation context, if any |
getAttribute | Gets an attribute from the current span by key, or searches the full parent chain when inherited is true |
getAttributes | Gets all attributes from the current span, or the merged attribute set from the full parent chain when inherited is true |
getAttributeChain | Gets all values of a given attribute from the current and parent contexts |
getAttributeChains | Get all attributes and their previous values |
traceContextHeaders | Gets the W3C Trace Context headers associated with the current invocation context |
Custom attributes can only be set on custom spans. First start a new span using startSpan
/**
* Starts a new `span` with the given name, as a child of the current invocation context
*/
export function startSpan(name: string): Span;and then use the Span class’s methods:
| method | description |
|---|---|
startedAt | Returns the timestamp when the span was started |
setAttribute | Sets an attribute on the span |
setAttributes | Sets multiple attributes on the span |
finish | Ends the current span |
If finish is not explicitly called on the span, it is going to be finished when the runtime drops the span object.
The custom spans are pushed onto the invocation context stack, so whenever an RPC call or HTTP call is made, their parent span(s) will include the user-defined custom spans as well as the rest of the invocation context.
To export traces, logs, and metrics to an external system, see the Observability page for setting up the OpenTelemetry plugin.
The WASI Key-Value store interface
Although Golem agents can store their state completely in their own memory, it is possible to use the wasi:keyvalue interface to store key-value pairs in a Golem managed key value storage.
This can be useful if state needs to be shared between different agents or if the size of this state is too large to be stored in memory. Storage is shared across agents and components within the same environment.
TypeScript
There are two primary modules for using the key-value store:
wasi:keyvalue/eventual@0.1.0defines an API for an eventually consistent key-value storewasi:keyvalue/eventual-batch@0.1.0defines an API with batch operations working on multiple keys
The primary interface to work with the key-value pairs consists of the four basic operations:
/**
* Get the value associated with the key in the bucket.
* The value is returned as an option. If the key-value pair exists in the
* bucket, it returns `Ok(value)`. If the key does not exist in the
* bucket, it returns `Ok(none)`.
* If any other error occurs, it returns an `Err(error)`.
* @throws Error
*/
export function get(bucket: Bucket, key: Key): IncomingValue | undefined;
/**
* Set the value associated with the key in the bucket. If the key already
* exists in the bucket, it overwrites the value.
* If the key does not exist in the bucket, it creates a new key-value pair.
* If any other error occurs, it returns an `Err(error)`.
* @throws Error
*/
export function set(bucket: Bucket, key: Key, outgoingValue: OutgoingValue): void;
/**
* Delete the key-value pair associated with the key in the bucket.
* If the key does not exist in the bucket, it does nothing.
* If any other error occurs, it returns an `Err(error)`.
* @throws Error
*/
export function delete_(bucket: Bucket, key: Key): void;
/**
* Check if the key exists in the bucket.
* If the key exists in the bucket, it returns `Ok(true)`. If the key does
* not exist in the bucket, it returns `Ok(false)`.
* If any other error occurs, it returns an `Err(error)`.
* @throws Error
*/
export function exists(bucket: Bucket, key: Key): boolean;The batch API defines similar functions such as getMany and setMany, but working on multiple keys at once.
The IncomingValue and OutgoingValue types are defined as follows:
export class OutgoingValue {
static newOutgoingValue(): OutgoingValue;
outgoingValueWriteBodyAsync(data: AsyncIterable<number>): void;
outgoingValueWriteBodySync(value: Uint8Array): void;
}
export class IncomingValue {
incomingValueConsumeSync(): Uint8Array;
incomingValueConsumeAsync(): AsyncIterable<number>;
incomingValueSize(): bigint;
}The streaming variants of setting and consuming the values may be used by the underlying implementation to directly
stream the data to the key-value store. For small values, the sync variants are more convenient, directly taking and returning
a Uint8Array.
The WASI Blob Store interface
The wasi:blobstore interface provides a way to store and retrieve large binary data. This can be useful for storing large files or other binary data that is too large to be stored in the agent’s memory. Storage is shared across agents and components within the same environment.
TypeScript
The Blob Store API organizes blobs identified by object names into containers. The wasi:blobstore/blobstore module exports functions to create, get and delete these containers by name:
declare module 'wasi:blobstore/blobstore' {
/**
* creates a new empty container
* @throws Error
*/
export function createContainer(name: ContainerName): Container;
/**
* retrieves a container by name
* @throws Error
*/
export function getContainer(name: ContainerName): Container;
/**
* deletes a container and all objects within it
* @throws Error
*/
export function deleteContainer(name: ContainerName): void;
/**
* returns true if the container exists
* @throws Error
*/
export function containerExists(name: ContainerName): boolean;
/**
* copies (duplicates) an object, to the same or a different container.
* returns an error if the target container does not exist.
* overwrites destination object if it already existed.
* @throws Error
*/
export function copyObject(src: ObjectId, dest: ObjectId): void;
/**
* moves or renames an object, to the same or a different container
* returns an error if the destination container does not exist.
* overwrites destination object if it already existed.
* @throws Error
*/
export function moveObject(src: ObjectId, dest: ObjectId): void;
// ...
}A Container is a class providing read-write access for the blobs in it:
export class Container {
/**
* returns container name
* @throws Error
*/
name(): string;
/**
* returns container metadata
* @throws Error
*/
info(): ContainerMetadata;
/**
* retrieves an object or portion of an object, as a resource.
* Start and end offsets are inclusive.
* Once a data-blob resource has been created, the underlying bytes are held by the blobstore service for the lifetime
* of the data-blob resource, even if the object they came from is later deleted.
* @throws Error
*/
getData(name: ObjectName, start: bigint, end: bigint): IncomingValue;
/**
* creates or replaces an object with the data blob.
* @throws Error
*/
writeData(name: ObjectName, data: OutgoingValue): void;
/**
* returns list of objects in the container. Order is undefined.
* @throws Error
*/
listObjects(): AsyncIterable<ObjectName>;
/**
* deletes object.
* does not return error if object did not exist.
* @throws Error
*/
deleteObject(name: ObjectName): void;
/**
* deletes multiple objects in the container
* @throws Error
*/
deleteObjects(names: ObjectName[]): void;
/**
* returns true if the object exists in this container
* @throws Error
*/
hasObject(name: ObjectName): boolean;
/**
* returns metadata for the object
* @throws Error
*/
objectInfo(name: ObjectName): ObjectMetadata;
/**
* removes all objects within the container, leaving the container empty.
* @throws Error
*/
clear(): void;
}IncomingValue can consume blob data either synchronously as a Uint8Array or asynchronously as an AsyncIterable<number>. To write blob data asynchronously, pass an AsyncIterable<number> to outgoingValueWriteBody(data).