Call a Golem Tool from Go
A Go agent calls a tool’s command with the command’s own argument struct: Call starts from the declared defaults, lets you set the rest, and returns the typed result.
Grant the Tool
The calling agent must be bound to the tool in golem.yaml:
tools:
greeter: {}
agents:
AssistantAgent:
tools:
greeter: {}A binding on a component applies to all of its agents (see golem-edit-manifest).
A Tool of the Same Component
Call the command declared with tool.DefineTool (see golem-define-tool-go):
greeting, err := greeter.Greet.Call(func(a *greeter.GreetArgs) {
a.Name = "ada"
a.Loud = true
})
if nf, ok := greeter.ErrNotFound.Match(err); ok {
return "no such person: " + nf.Name
}
if err != nil {
return "", err
}fill may be nil when the defaults are enough. The call goes through the runtime like any other, so the tool’s middleware applies, and the handler’s principal is the one the calling agent’s invocation runs on behalf of.
A Tool of Another Component
Depend on the tool, and golem build generates a typed client for it:
components:
example:caller:
dir: caller
templates: go
dependencies:
tools:
- example:provider/greeterThe client is golem-temp/bridge-sdk/go/internal/greeter-tool-guest-client, a Go module named golem.local/bridge/greeter-tool-guest-client. Point the calling component’s go.mod at it:
require golem.local/bridge/greeter-tool-guest-client v0.0.0
replace golem.local/bridge/greeter-tool-guest-client => ../golem-temp/bridge-sdk/go/internal/greeter-tool-guest-clientIt declares the tool with tool.DefineToolClient and each command the same way, so calls look identical:
import greeter "golem.local/bridge/greeter-tool-guest-client"
greeting, err := greeter.Greet.Call(func(a *greeter.GreetArgs) { a.Name = "ada" })Names follow the command path: Tool (with its identity type, e.g. GreeterTool), Root for the tool’s own body, Greet, StockShow for stock show, with <Command>Args structs, <Node>Globals structs embedded in them, and Err<Case> error cases (Err<Command><Case> when two commands give one case different payloads). Defaults a Go literal can spell are filled in; set any other field yourself.
An existing built-in tool or MCP import
Load golem-generating-tool-clients to select a built-in release or an MCP import. Declare it, add it to the Go component’s dependencies.tools, and bind it separately; golem build generates the Go client without an explicit bridge selection. Do not copy the tool’s schema into a tool.DefineTool of your own. For the built-in bash release 0.2.1:
tools:
bash:
release:
account: builtin-tool-owner@golem.cloud
name: bash
version: "0.2.1"
components:
example:caller:
dir: caller
templates: go
dependencies:
tools: [bash]
tools:
bash:
filesystemAccess: allowedPoint go.mod at golem.local/bridge/bash-tool-guest-client as above, then:
import bash "golem.local/bridge/bash-tool-guest-client"
result, err := bash.Run.Call(func(a *bash.RunArgs) { a.Script = "printf 'hello\\n'" })
if err != nil {
return golem.Err[string, string](err.Error())
}
if result.ExitCode != 0 {
return golem.Err[string, string](result.Stderr)
}
return golem.Ok[string, string](result.Stdout)cwd and timeout keep their declared defaults ("" and 600) unless the call sets a.Cwd or a.Timeout. The script’s exit code is part of the result, not an error: check ExitCode when a nonzero exit should fail the method. For an MCP import, use the generated result type rather than assuming the call returns a string.
A Tool Without Its Definition
tool.DefineToolClient[T](name) declares a tool’s shape for calling only, with the same commands, specs and error cases a definition has, but no handlers — what a generated client contains, and what to write by hand for a subset of a tool:
type Search struct{}
var (
Tool = tool.DefineToolClient[Search]("document-search")
Query = Tool.Command[QueryArgs, QueryResult]("query", func(a *QueryArgs, s *tool.CommandSpec) {
s.Positional(&a.Text)
})
)Calls go to the definition’s name. When a deployment registers the same tool under another name, target it with Bind and On:
archive := Tool.Bind("archive-search")
res, err := Query.On(archive).Call(func(a *QueryArgs) { a.Text = "golem" })Stdin, Stdout and Stderr
A field bound with Stdin is an io.Reader; set it to feed the command. A required one left nil is refused before anything is sent. A command declared with OutputCommand (it writes standard output, standard error or both) returns a running invocation instead of the result:
inv, err := tools.Upper.Call(func(a *tools.UpperArgs) { a.In = strings.NewReader("hello") })
if err != nil {
return err
}
out := inv.Collect() // out.Result/out.Err, out.Stdout/out.StdoutErr, out.Stderr/out.StderrErrinv.Stdout()andinv.Stderr()take an output (anio.ReadCloser) to read while the command runs; theninv.Wait()returns the result. Bytes on stderr do not mean the command failed.Waitdrains and discards every output not taken before it, so a command never stalls on output nobody reads; taking one afterwards readstool.ErrOutputDrained. An output you took is yours: read it while waiting, or a command writing more than the stream buffers stalls.Collectreads both outputs concurrently while awaiting the result. Each keeps its own outcome:Errfor the result,StdoutErrandStderrErrfor the outputs (a*tool.OutputErrorwhen the command failed one), so a failed output does not hide the result and a failed result does not hide what the outputs carried.Stdout/Stderrare nil for a command without that output.inv.Cancel()asks the runtime to cancel the call. To stop reading only one output,Closeit: the command sees that output closed by its consumer, and the call, its result and the other output go on.
Errors
A failed call returns a *tool.CallError (errors.As): its Kind separates the tool’s declared errors (tool.CallDeclaredError, matched with ErrX.Match) from rejected input, constraint violations, denials, cancellation and runtime failures. Message carries the detail.
Discovered Tools
A tool with no Go declaration is called through discovery: reflection.DiscoverTool returns a snapshot of the deployed tool, and the client bound from it validates and packs every call against it, with canonical JSON arguments and results.
greeter, found := reflection.DiscoverTool("greeter")
if !found {
return fmt.Errorf("the greeter tool is not available to this agent")
}
client := greeter.Bind()
out, err := client.Call([]string{"greet"}, map[string]any{
"name": "ada", "loud": false, "times": 2, "title": nil,
})
var ce *tool.CallError
if errors.As(err, &ce) && ce.Kind == tool.CallDeclaredError {
fmt.Println(ce.ErrorName, golem.Must(ce.Payload().JSON()))
}- Supply every field of the command’s input record by its wire name: inherited globals, positionals, the tail (a list), options and flags. Nothing is filled in from declared defaults.
client.Start(path, args, stdin)runs a command that reads standard input or has outputs, and returns the running invocation with every output the command declares;client.Calldiscards them.cmd.Input()is the input record of a callable command (cmd.Callable()) andcmd.Output()the result type,golem.Nonewhen there is none, asschema.Refs that pack, unpack and render JSON Schema;tool.Command(path),tool.Commands()andcmd.Errors()describe the rest. Arguments that do not match fail with*schema.ValidationErrorbefore anything is sent. A snapshot never refreshes itself.reflection.BindTool(name)(no error; nothing is checked until a call) gives a dynamic client that forwards already packedgolem.TypedValues unchecked, with the sameCallandStart; itsStarttakestool.Streams{Stdout: true, Stderr: true}to request outputs, which must match what the command declares.
Related Guides
| Guide | Description |
|---|---|
golem-define-tool-go | Defining the tool being called |
golem-agent-reflection-go | Discovering and calling agents the same way |
golem-tools-middleware-go | Intercepting tool calls |