Skip to Content
How-To GuidesGoAdding Secrets to a Go Agent

Adding Secrets to a Go Agent

Overview

Secrets are sensitive configuration values (API keys, passwords, tokens) read at runtime through golem.Secret[T]. A secret uses the same config mechanism as regular typed config — it is just a field whose type is golem.Secret[T] inside the agent’s config struct (see golem-add-config-go). The config value carries an opaque secret handle; the plaintext is revealed only when your code calls .Get() or .MustGet().

Secrets are not stored in golem.yaml (which is source-controlled) except through secretDefaults for local development. In real environments they are managed per-environment via the CLI.

Steps

  1. Add a golem.Secret[T] field to the agent’s config struct.
  2. Define / implement the agent as a configured agent (DefineConfiguredAgent + ImplementConfigured).
  3. Reveal the value at runtime with .MustGet(), or .Get() to handle a read failure.
  4. Provide values via secretDefaults (local) or the CLI (per environment).

Declaring a Secret Field

A secret is any field of type golem.Secret[T], at any depth of the config struct:

// Package client is the DEFINITION of an API-client agent. package client import "github.com/golemcloud/golem/sdks/go/golem" type ID struct{ Name string } type Config struct { Endpoint string APIKey golem.Secret[string] // a secret leaf DB DBConfig } type DBConfig struct { Host string Password golem.Secret[string] // secret at path db.password } var Agent = golem.DefineConfiguredAgent[ID, Config](golem.Spec{ Name: "ClientAgent", Description: "Calls an external API using a secret key", }) var Connect = Agent.Method[golem.Unit, string]("connect", golem.Description("Describe the connection using secret values"))

Reading a Secret at Runtime

Read the config, then call .MustGet() on the secret field to reveal the current plaintext. Because it re-reads the host on every call, a rotated secret is observed rather than a stale snapshot. .Get() returns (T, error) instead, for a secret that may be unavailable:

// Package impl is the IMPLEMENTATION of the API-client agent. package impl import ( "fmt" "myapp/agents/client" "github.com/golemcloud/golem/sdks/go/golem" ) type state struct{} var agent = client.Agent.ImplementConfigured(func(_ *golem.InitContext[client.ID, state, client.Config]) *state { return &state{} }) func init() { agent.Handle(client.Connect, func(ctx *golem.Context[state], _ golem.Unit) string { cfg := ctx.Config(client.Agent) apiKey := cfg.APIKey.MustGet() // reveals the current plaintext dbPass := cfg.DB.Password.MustGet() // secret at any depth return fmt.Sprintf("connecting to %s (key len=%d, db host=%s, pw len=%d)", cfg.Endpoint, len(apiKey), cfg.DB.Host, len(dbPass)) }) }

A golem.Secret[T] cannot be constructed from a plaintext: it comes from the agent’s config, or from another agent. Its String()/GoString() render as golem.Secret(redacted), so it stays out of logs formatted with %v/%s.

Passing a Secret to Another Agent

A golem.Secret[T] can be a field of a method’s input or its result. It travels as a secret handle, never as plaintext, and the receiver reveals it with .Get() / .MustGet():

var Share = Agent.Method[golem.Unit, golem.Secret[string]]("share") agent.Handle(Share, func(ctx *golem.Context[state], _ golem.Unit) golem.Secret[string] { return ctx.Config(Agent).APIKey }) // in another agent key := client.Share.MustCall(client.Agent.Get(client.ID{Name: "keeper"}), golem.Unit{}).MustGet()

A config secret can be sent again; a secret you received moves when you send it on, and the copy that was sent is unusable afterwards (golem.ErrSecretMoved). External clients cannot carry secrets.

Secret Defaults in golem.yaml (local only)

For local development, set defaults under secretDefaults.<environment>. Keys are camelCase and follow the same path flattening as config; nested structs are nested maps. These are not used in production:

secretDefaults: local: apiKey: "dev-key-123" db: password: "dev-password"

Managing Secrets via CLI (per environment)

Outside local defaults, secrets are environment-scoped — each deployment environment has its own values, set through the platform CLI (paths use the same camelCase as the config keys):

golem secret create apiKey --type string --value "sk-abc123" golem secret create db.password --type string --value "s3cret" # Without a value option the value is prompted for (hidden input); or pipe it in printenv DB_PASSWORD | golem secret create db.password --type string --value-stdin # Create, or update the value if the secret already exists golem secret create apiKey --type string --value "sk-abc123" --update-existing # List, update, clear, and delete (delete asks for confirmation; --yes skips it) golem secret list golem secret update apiKey --value "new-value" golem secret update apiKey --unset golem secret delete apiKey

Key Constraints

  • A secret is a config field of type golem.Secret[T] — the agent must be a configured agent (DefineConfiguredAgent / ImplementConfigured); see golem-add-config-go.
  • Only .Get() / .MustGet() reveal the plaintext, and it re-reads the host each call, so a rotated value is observed without restarting the agent. Each reveal pins the resolved revision for deterministic retry/replay.
  • Secret values are never written to golem.yaml except via secretDefaults (local dev). Production values come from the CLI, per environment.
  • A missing required secret fails agent creation/deployment. Read a secret from within an invocation only — .Get() calls the host. It returns an error when the secret is unavailable, its version is gone, or it was moved; .MustGet() panics instead, which traps the component (fails the agent), as in Rust.
  • Over RPC, secret fields are always platform-provisioned; a definition’s WithConfig overrides only local config, never secrets.
GuideDescription
golem-add-config-goThe non-secret typed-config mechanism secrets build on
golem-add-agent-goDefine the base agent the config attaches to
golem-call-another-agent-goUnderstand why secrets are not overridable over RPC