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 GuidesGoCalling Go Agents from Outside Golem

Calling Go Agents from Outside Golem

Overview

Agents are reached from outside the platform in four ways:

  1. A generated Go bridge client — a typed Go module per agent type, calling the Golem REST API. The usual choice for a Go program.
  2. The golem CLI — golem agent invoke calls a method and waits for the result.
  3. HTTP endpoints — if the agent declares an HTTP mount, any HTTP client calls its methods over plain HTTP.
  4. The worker REST API — invoke methods and complete promises directly over HTTP against the Golem server.

The bridge client’s language is independent of the agent’s, so a Go program can call agents written in any language.

Throughout, the example is a CounterAgent whose constructor takes a name string, with methods increment (returns a count), add, and value.

Approach 1: a generated Go bridge client

Step 1: enable bridge generation

Add a bridge section to golem.yaml:

bridge: go: external: agents: "*" # Generate for all agents # Or list specific agents: # agents: # - CounterAgent # - my-app:billing outputDir: ./bridge-sdk/go # Optional custom output directory

golem build --yes then writes one Go module per agent type — e.g. counter-agent-client/ — into the output directory (default golem-temp/bridge-sdk/go/), and keeps it in sync as agents change. Prefer this over running golem generate-bridge by hand.

Step 2: require the generated module

Each client is its own module, named golem.local/bridge/<client-dir>. That path can never resolve on a module proxy, so the consuming program points a replace at the generated directory:

require golem.local/bridge/counter-agent-client v0.0.0 replace golem.local/bridge/counter-agent-client => ../golem-temp/bridge-sdk/go/counter-agent-client

Then go mod tidy. The client depends only on github.com/golemcloud/golem/sdks/go/bridge (the REST runtime) and github.com/golemcloud/golem/sdks/go/core (the shared value types) — not on the guest SDK, so it builds as an ordinary native Go program.

Step 3: call the agent

import ( "context" "fmt" "time" "github.com/golemcloud/golem/sdks/go/bridge" counter "golem.local/bridge/counter-agent-client" ) func run(ctx context.Context) error { // Once per process: which server, app and environment to talk to. if err := bridge.Configure(bridge.Configuration{ Server: bridge.Local(), // or bridge.Cloud(token), bridge.Custom(url, token) AppName: "my-app", EnvName: "local", }); err != nil { return err } // The id struct holds the constructor arguments that identify an instance. c, err := counter.GetCounterAgent(counter.CounterAgentID{Name: "my-counter"}) if err != nil { return err } n, err := c.Increment(ctx) // awaits the result if err != nil { return err } fmt.Println("count:", n) // Fire-and-forget, or scheduled for later: both return a bridge.Receipt. if _, err := c.TriggerAdd(ctx, 5); err != nil { return err } _, err = c.ScheduleAdd(ctx, time.Now().Add(time.Hour), 5) return err }

Every method has three forms — M(ctx, …), TriggerM(ctx, …) and ScheduleM(ctx, when, …) — and every call returns an error: an argument the callee’s schema does not accept (checked before anything is sent), a transport failure, an error status (*bridge.Error, carrying the status and body) and a result that does not decode are all reported, never panicked.

GetCounterAgent (and NewPhantom<Agent>) takes options:

OptionEffect
bridge.WithConfiguration(cfg)Use this server/app/env instead of the one set with bridge.Configure
bridge.WithPhantomID(id)Address the phantom instance with this id
bridge.WithNewPhantomID()Address a fresh phantom instance, under a random id
With<Agent>Config(<Agent>Config{…})Override agent configuration, typed: generated when the agent declares local configuration, one values.Option field per config path (["limits", "max-items"] → LimitsMaxItems); only set fields are sent
bridge.WithConfig(bridge.ConfigEntry{Path: []string{"retries"}, Value: 3})Override agent configuration by path; values are plain JSON

An ephemeral agent has no durable instance to get: its client is constructed with NewPhantom<Agent>(id, opts...), every call runs in a fresh instance, and an awaited call returns bridge.InvocationResult[T] (Value, plus the AgentID and IdempotencyKey of the instance that ran) — or a bridge.Receipt for a method without a result.

Generated types

Parameter and return types are generated in the client package, using the same Go spelling a Go agent would: records are structs, enums are uint32 constants (StatusInTransit), flags are structs of bool, and variants and unions are sealed interfaces with one struct per case (EventNote{Value: "hi"}; a case without a payload is an empty struct). Options, results and tuples are values.Option[T], values.Result[T, E] and values.Tuple1[A], values.Tuple2[A, B]… from github.com/golemcloud/golem/sdks/go/core/values, and an option whose value contains the same option again (next: option<Node> inside Node) is a pointer, *Node; a named tuple, list or option of the agent is an alias of that type (type Pair = values.Tuple2[string, bool]); text, char, binary, path, url and uuid are values.Text, values.Char, values.Binary, values.Path, values.URL, values.UUID; datetimes and durations are time.Time and time.Duration.

Unstructured and multimodal content uses the shared content types: values.UnstructuredText[L], values.UnstructuredBinary[M] (with generated language and media-type markers such as LanguagesEnDe, or values.AnyLanguage / values.AnyMimeType), values.Multimodal, and values.MultimodalOf[T] for a custom set of modalities. A quantity is values.Quantity[U], where U is a generated unit marker named after the base unit (values.Quantity[UnitKg]). An empty Unit sends the base unit.

Streaming methods

A method that takes or returns a stream spells each stream bridge.AgentStream[T] and runs over an invocation session, a WebSocket the bridge manages. It has only the awaited form, M(ctx, …), which returns once the result arrives. Streams in the result keep reading from the session after that:

// Send a stream: known values, a producer goroutine, or a writer you feed. out, err := c.Transform(ctx, bridge.StreamOf[uint32](1, 2, 3)) if err != nil { return err } for v, err := range out.All(ctx) { // or out.Next(ctx), out.Collect(ctx) if err != nil { return err // *bridge.StreamError (producer failed), *bridge.StreamCancelled } fmt.Println(v) } upload := bridge.ProduceStream(func(w *bridge.AgentStreamWriter[[]byte]) error { return w.WriteAll(chunks...) // a returned error is the agent's "source unavailable" }) _, err = c.Upload(ctx, upload)
  • A stream is read once. Passing it to a method gives it away; reading it afterwards fails with bridge.ErrStreamConsumed.
  • Close() a returned stream you stop reading early. The agent then knows nobody is listening, and the session can finish.
  • Cancelling ctx before the result arrives cancels the call and the streams it sent.
  • If the connection drops, the session reconnects and resumes on its own: unacknowledged input is resent, and output continues after the last item you read.
  • A rejected call or a failed invocation is a *bridge.SessionError with the protocol’s error code.
  • While the server accepts neither the start nor a resume, the session keeps reconnecting with a growing delay (50 ms up to 3 s) for Configuration.ReconnectWindow (default bridge.DefaultReconnectWindow, 5 minutes; bridge.NoReconnectLimit keeps trying until ctx ends). HTTP 429 and 5xx on the upgrade and retryable rejections are retried too. Past the window the call fails with *bridge.UnreachableError, which names the endpoint and the window and wraps the last cause.

Approach 2: the CLI

golem agent invoke calls a method on a deployed agent and blocks for the result. The agent is created automatically on first invocation. Both golem and golem-cli work for every command.

golem agent invoke <AGENT_ID> <FUNCTION_NAME> [ARGUMENTS...]

Agent ID format

The agent ID is the agent type name plus its constructor parameters, in declaration order (for a Go agent, the fields of its ID struct):

AgentTypeName(param1, param2, ...)

CounterAgent takes one name, so its ID is CounterAgent("my-counter"). A singleton (type ID struct{}) uses empty parentheses: CounterAgent(). IDs may be prefixed with env/, app/env/, or account/app/env/.

Examples

# No-parameter method golem agent invoke 'CounterAgent("my-counter")' increment # add takes one parameter (the By field of AddIn), passed positionally golem agent invoke 'CounterAgent("my-counter")' add 5 # Read the current value golem agent invoke 'CounterAgent("my-counter")' value # In a specific environment golem agent invoke 'staging/CounterAgent("my-counter")' value # With an explicit idempotency key golem agent invoke -i my-unique-key 'CounterAgent("my-counter")' increment # Machine-readable output golem agent invoke --format json 'CounterAgent("my-counter")' value

CLI options

OptionDescription
-t, --triggerOnly trigger the invocation without waiting (fire-and-forget)
-i, --idempotency-key <KEY>Set a specific idempotency key; use "-" for auto-generated
--no-streamDisable live streaming of agent stdout/stderr/log
--schedule-at <DATETIME>Schedule the invocation (requires --trigger; ISO 8601)
--format json / --format yamlMachine-readable output

Method arguments use Go syntax, read against the agent’s schema: strings quoted ("my-counter"), integers bare (5), booleans true/false, structs and lists as composite literals without their type ({zone: "a", level: 2}, {"x", "y"}), maps { "k" => 1 }, options as the value or nil, results Ok(v)/Err(e), enum and variant cases by name. Each exported field of a Go input struct is one positional parameter, named in lowerCamelCase with each initialism as one word (AmountCents → amountCents, UserID → userId, APIKey → apiKey); see golem-invoke-agent-go for the full syntax.

Approach 3: HTTP endpoints

If the agent declares an HTTP mount, its methods are reachable over plain HTTP from any client. Declare the mount on the Spec and per-method routes with golem.HTTP:

var Agent = golem.DefineAgent[ID](golem.Spec{ Name: "CounterAgent", HTTP: &golem.Mount{Path: "/counters/{name}"}, // {name} binds the ID field }) var Add = Agent.Method[AddIn, int64]("add", golem.HTTP(golem.POST("/add?by={by}")))

The agent must also be declared in the manifest’s httpApi deployment. External clients then POST to the deployed route — from a Go program with the standard library:

resp, err := http.Post( "https://my-golem.example.com/counters/my-counter/add?by=5", "application/json", nil, )

See golem-add-http-endpoint-go for the full mount/route model.

Approach 4: the agent REST API

Invoke methods (or complete promises) directly against the Golem server over HTTP — usable from any language, including external Go. Constructor and method parameters are records in the REST wire form ({"kind": ..., "value": ...}; 64-bit integers are strings):

# Invoke CounterAgent("my-counter").add(5) and await the result curl -X POST "$GOLEM_URL/v1/agents/invoke-agent" \ -H "Authorization: Bearer $GOLEM_TOKEN" \ -H 'Content-Type: application/json' \ -d '{ "appName": "my-app", "envName": "local", "agentTypeName": "CounterAgent", "parameters": {"kind": "record", "value": {"fields": [{"kind": "string", "value": "my-counter"}]}}, "methodName": "add", "methodParameters": {"kind": "record", "value": {"fields": [{"kind": "s64", "value": "5"}]}}, "mode": "await" }' # Complete a promise an agent is awaiting (see golem-wait-for-external-input-go) curl -X POST \ "$GOLEM_URL/v1/components/$COMPONENT_ID/workers/$AGENT_ID/complete" \ -H "Authorization: Bearer $GOLEM_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"oplogIdx": <oplog_index>, "fingerprint": "<fingerprint>", "data": [<bytes>]}'

"mode": "schedule" triggers the invocation without awaiting it. For promise completion, $AGENT_ID is the same AgentTypeName(params) string the CLI uses; a promise’s PromiseID (AgentID.ComponentID, AgentID.AgentID, Fingerprint, OplogIndex) supplies the component, the agent, the fingerprint of the agent incarnation that created the promise (as the UUID string) and the oplog index. All three body fields are required. The generated external Go client (Approach 1) builds these requests for you.

Key Constraints

  • golem agent invoke blocks until the agent returns; use -t/--trigger for fire-and-forget.
  • Every invocation carries an idempotency key (auto-generated if not supplied), giving at-most-once execution even if the client retries.
  • Constructor arguments in the agent ID must match the Go ID struct fields in order.
  • A generated Go client is its own module (golem.local/bridge/<client-dir>); the consuming go.mod needs the require and a replace pointing at the generated directory.
  • A stream-bearing method has no Trigger/Schedule form: a trigger could not hand back a stream.
  • HTTP-endpoint access requires both a Spec.HTTP mount and an httpApi deployment declaration.
GuideDescription
golem-invoke-agent-goFull golem agent invoke reference and options
golem-add-http-endpoint-goExpose the agent’s methods over HTTP
golem-wait-for-external-input-goComplete a promise from outside via REST
golem-add-agent-goDefine the agent being called