Skip to Content
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: 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.Tuple2[A, B]… from github.com/golemcloud/golem/sdks/go/core/values; 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.

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>, "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, OplogIndex) supplies the component, the agent and the oplog index. 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