Adding Typed Configuration to a Go Agent
Overview
A Go agent’s configuration is a single config struct attached to the agent’s type. Each exported field becomes a config key; the platform provisions the values (from golem.yaml, env, or the CLI) and the agent reads them back at runtime as a typed struct. Config is materialized once per worker and cached, so reads on a hot method are near-free.
Two things change versus a plain agent:
- define with
golem.DefineConfiguredAgent[ID, Cfg]instead ofgolem.DefineAgent[ID], - implement with
Agent.ImplementConfigured(its constructor receives an*golem.InitContext[ID, state, Cfg]),
then read the config with ctx.Config(Agent) inside a method, or ctx.Config() inside the constructor.
Steps
- Declare the config struct — one exported field per config key.
- Attach it with
golem.DefineConfiguredAgent[ID, Cfg]in the definition. - Implement with
Agent.ImplementConfigured; read config from theInitContextin the constructor. - Read at method time with
ctx.Config(Agent). - Provide values under
agents.<Name>.configingolem.yaml.
Definition (agents/shop/shop.go)
// Package shop is the DEFINITION of the shop agent.
package shop
import "github.com/golemcloud/golem/sdks/go/golem"
type ID struct{ Name string }
// Config is the agent's whole config surface. Each exported field is a config
// key; a nested struct flattens into multi-segment paths; a pointer field is
// optional; list/map fields decode as their WIT shapes.
type Config struct {
Greeting string
Tax TaxConfig // nested → paths tax.ratePct
Timezone *string // optional (read back as nil when unset)
Features []string // list
Limits map[string]int64 // map
}
type TaxConfig struct{ RatePct int64 }
var Agent = golem.DefineConfiguredAgent[ID, Config](golem.Spec{
Name: "ShopAgent",
Description: "A shop whose behaviour is driven by config",
})
var (
Greet = Agent.Method[golem.Unit, string]("greet", golem.Description("Greeting, from config"))
Settings = Agent.Method[golem.Unit, []string]("settings", golem.Description("Return the configured features"))
)Field-name conventions match plain agent types: use sized integers (int64, not bare int), a *T field is option<T>, and nested structs recurse. Field names are lowerCamelCase for the wire, with each initialism as one word (RatePct → ratePct, APIKey → apiKey, AccountID → accountId).
Implementation (agents/shop/impl/impl.go)
// Package impl is the IMPLEMENTATION of the shop agent.
package impl
import (
"myapp/agents/shop"
"github.com/golemcloud/golem/sdks/go/golem"
)
type state struct{ greeting string }
// ImplementConfigured binds the constructor; its InitContext exposes Config().
var agent = shop.Agent.ImplementConfigured(func(ctx *golem.InitContext[shop.ID, state, shop.Config]) *state {
// Constructor-time config read: seed state once from config.
return &state{greeting: ctx.Config().Greeting}
})
func init() {
agent.Handle(shop.Greet, func(ctx *golem.Context[state], _ golem.Unit) string {
return ctx.State.greeting
})
agent.Handle(shop.Settings, func(ctx *golem.Context[state], _ golem.Unit) []string {
cfg := ctx.Config(shop.Agent) // method-time config read
return cfg.Features
})
}ctx.Config(shop.Agent) returns the whole shop.Config value; ctx is the running method’s *golem.Context[state] (requiring it means config is only readable from inside a running method). The constructor reads the same config via ctx.Config() on its InitContext.
A config field takes the same golem restriction tag as a method parameter (see golem-add-agent-go), which publishes it in the config’s declared type:
type Config struct {
Retries uint32 `golem:"max=5"`
}Providing Values in golem.yaml
Set defaults under agents.<Name>.config. Keys are camelCase — they match the Go field names in lowerCamelCase, with each initialism as one word (AccountID → accountId); nested structs are nested maps, and a map[string]V is a list of [key, value] pairs:
agents:
ShopAgent:
config:
greeting: "Welcome to ACME"
tax:
ratePct: 20
features: ["fast-checkout", "gift-wrap"]
limits: [["maxItems", 100]]
# timezone is omitted → the optional *string field reads back as nilOverriding Config Over RPC
When one agent calls another, the caller can override the callee’s local (non-secret) config at client creation with the callee definition’s WithConfig, which checks the config type at compile time:
client := shop.Agent.Get(
shop.ID{Name: "acme"},
shop.Agent.WithConfig(shop.Config{Greeting: "Hi from the caller"}),
)Only declared local keys can be overridden; secret fields are always platform-provisioned and are skipped. WithConfig replaces every local value: it sends every local field of the struct, zero values included. To set only some, or to call an agent in another component, use the generated guest client’s With<Agent>Config option (see golem-call-another-agent-go). It sends only the fields you set, through golem.WithConfigEntries(golem.ConfigEntryOf(path, value)…).
Key Constraints
- Config is agent-attached and instance-bound — read only from within that agent’s own constructor or methods, never from free-floating code.
- Reading an undeclared key, or an unset required key, traps. Declare a field as a pointer (
*T) to read an absent value back asnilinstead of trapping. - Config is materialized once per worker and cached; local fields are read on first access and reused for the worker’s life. (Secret fields stay live — see
golem-add-secret-go.) - Use
golem.DefineAgent[ID](no config) for agents that need none;golem.NoConfigis the empty config type and never needs naming.
Related Guides
| Guide | Description |
|---|---|
golem-add-agent-go | Define the base agent this config attaches to |
golem-add-secret-go | Add golem.Secret[T] fields for sensitive values |
golem-call-another-agent-go | Override a callee’s config with the definition’s WithConfig over RPC |