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 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().

Manage secrets per environment via the CLI. Use secretDefaults in golem.yaml only for development placeholders; never commit real credentials.

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 config-backed secret field to reveal the current plaintext. Each config-backed read resolves a fresh handle, so live reads observe rotation; retry/replay uses the recorded revision. A received RPC handle instead stays pinned to its transferred revision. .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.

A transferred handle is pinned to one secret revision. Repeated .Get() calls on that received handle do not observe rotation. To give a recipient the rotated value, send a newly resolved handle from the config-backed secret; the recipient must use the new handle.

For local development, set placeholder defaults under secretDefaults.<environment>. Keys are camelCase and follow the same path flattening as config; nested structs are nested maps. The CLI selects defaults by environment name, with no production exclusion. Keep defaults development-only and never commit real credentials:

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. Config-backed live reads resolve fresh handles and observe rotation without restarting the agent; retry/replay uses the recorded revision. Received handles stay pinned to their transferred revision and require a new handle to observe rotation.
  • Use the CLI for production secret values. secretDefaults applies by environment name, even in production; restrict it to development placeholders and never commit real credentials.
  • Deployment can succeed with an unset secret; a missing required secret fails agent creation, not 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