golemcloud/golem/golem-add-agent-go

Trust tier 3
  • community
Source check pending

Checking that the SKILL.md file is still in the repository.

Adding a new Go agent to a Golem component. Use when the user asks to create, add, or define a new agent type, split an agent into definition/implementation, or add agent methods in a Go Golem project.

Install

npx vskill@latest install golemcloud/golem/golem-add-agent-go

SKILL.md

Adding a New Agent to a Go Golem Component

Overview

An agent is a durable, stateful unit of computation in Golem. In Go each agent is split across two packages:

  • a state-free definition (package <name>) — the agent's identity, its method descriptors, and the input/output types. Other agents import this package to call it.
  • an implementation (package impl, in a nested impl/ subpackage) — the private state and the method handlers. Importing this package registers the agent with the SDK.

This split lets agents call one another without a Go import cycle, and keeps state private to the implementation.

Steps

  1. Create the definition — add agents/<name>/<name>.go with package <name>.
  2. Declare identity + types — an ID struct (its fields are the constructor parameters), plus any input/output structs.
  3. Define the agent and its methods — golem.DefineAgent + one Agent.Method descriptor per method.
  4. Create the implementation — add agents/<name>/impl/impl.go with package impl: a private state, Agent.Implement, and one impl.Handle per method.
  5. Wire it in — blank-import the impl package in main.go.
  6. Build — run golem build.

Definition (agents/counter/counter.go)

go
// Package counter is the DEFINITION of the counter agent.
package counter
import "github.com/golemcloud/golem/sdks/go/golem"
// ID holds the constructor parameters, and is the type-level identity used by
// cross-agent calls. Without a phantom UUID, the same ID names the same durable agent.
type ID struct{ Name string }
// AddIn is a method's parameter list: one WIT parameter per exported field.
type AddIn struct{ By int64 }
// Agent is the state-free definition. Mode defaults to Durable.
var Agent = golem.DefineAgent[ID](golem.Spec{
Name: "CounterAgent",
Description: "A simple durable counter",
})
// Method descriptors are package-level vars: the same value drives the published
// schema, the implementation binding, and calls from other agents.
var (
Increment = Agent.Method[golem.Unit, int64]("increment", golem.Description("Increase the count by one"))
Add = Agent.Method[AddIn, int64]("add", golem.Description("Add to the count"))
Value = Agent.Method[golem.Unit, int64]("value", golem.Description("Return the current value"))
)

golem.Unit is the empty parameter/result placeholder (a method with no input or no output).

golem.Description(...) describes a method and golem.PromptHint(...) tells AI callers when to use it; Spec.PromptHint does the same for creating the agent.

Spec.Dependencies lists other agents of the same component this one calls (Dependencies: []golem.AgentDependency{billing.Agent}); they are published with the agent's metadata.

Implementation (agents/counter/impl/impl.go)

go
// Package impl is the IMPLEMENTATION of the counter agent.
package impl
import (
"myapp/agents/counter"
"github.com/golemcloud/golem/sdks/go/golem"
)
// state is the agent's private, durable state — invisible to callers.
type state struct{ value int64 }
func (s *state) current() int64 { return s.value }
// Implement binds the constructor and returns a handle; Handle registers each
// method on it.
var agent = counter.Agent.Implement(func(counter.ID) *state { return &state{} })
func init() {
agent.Handle(counter.Increment, func(ctx *golem.Context[state], _ golem.Unit) int64 {
ctx.State.value++
return ctx.State.value
})
agent.Handle(counter.Add, func(ctx *golem.Context[state], in counter.AddIn) int64 {
ctx.State.value += in.By
return ctx.State.value
})
// A handler can also be an ordinary Go method, bound with a method expression.
agent.Handle(counter.Value, golem.Adapt0((*state).current))
}

The handler receives *golem.Context[state]; ctx.State is your *state. golem.Adapt0 adapts a no-input (*state) method into a handler; golem.Adapt adapts a one-input method.

Wiring: main.go

main.go (package main) blank-imports each agent's impl package so its registration runs on import, plus the SDK runtime:

go
package main
import (
_ "myapp/agents/counter/impl"
_ "github.com/golemcloud/golem/sdks/go/golem"
)
func main() {}

Adding another agent = another agents/<name>/ folder + one more blank import here.

Custom Types

Parameter and result types are Go structs; each exported field becomes one WIT field, named by the Go name in lowerCamelCase with each initialism as one word (AmountCents → amountCents, UserID → userId). Two fields whose names map to the same schema name are a definition error.

  • Use sized integers (int64, uint64, …). Bare int/uint are rejected — their width is platform-dependent.
  • time.Time, time.Duration and golem.UUID are datetime, duration and uuid; golem.ParseUUID reads one from text.
  • A *T field means option<T>. A nil slice/map is an empty list/map, never "absent" — use *[]T if you must distinguish.
  • Variants/enums are declared with golem.DefineVariant / golem.DefineEnum. A variant case type is its payload; a case with no payload is an empty struct (type Cash struct{}).
  • When the payload cannot carry the variant's marker method without changing what it is — a time.Time, a golem.Text, an Option, another variant — wrap it in a one-field struct and register it with golem.WrappedCase. type At time.Time would publish a different schema; type At struct{ Value time.Time } with WrappedCase[At]("at") publishes at(datetime). Unions have golem.WrappedBranch for the same reason.
Unstructured and Multimodal Content

For content a model or a person reads, use the content types. Tools and UIs recognise them by their role:

go
type EnOrDe struct{}
func (EnOrDe) Languages() []string { return []string{"en", "de"} }
type SummarizeIn struct {
Doc golem.UnstructuredText[EnOrDe] // golem.AnyLanguage for any
Photo golem.UnstructuredBinary[golem.AnyMimeType] // or a marker with MimeTypes()
Media golem.Multimodal // text and binary items
}
doc := golem.UnstructuredText[EnOrDe]{Text: "hallo", Language: "de"} // inline
ref := golem.UnstructuredBinary[golem.AnyMimeType]{URL: "https://example.com/a.png"} // by URL
media := golem.Multimodal{
golem.TextModality{Value: golem.UnstructuredText[golem.AnyLanguage]{Text: "hello", Language: "en"}},
golem.BinaryModality{Value: ref},
}
  • A non-empty URL refers to the content; otherwise Text or Data carries it inline.
  • golem.MultimodalOf[T] is a list of your own modalities, where T is a variant registered with golem.DefineVariant.
Restricting Values

A golem struct tag restricts a field, on a method's parameters or on a record. The restriction is published in the schema, and the platform rejects a value outside it before the method runs:

go
type ResizeIn struct {
Width uint32 `golem:"min=1,max=4096,unit=px"`
Name golem.Text `golem:"languages=en|de,maxLength=40,regex=^[a-z][a-z0-9-]*$"`
Photo golem.Binary `golem:"mime=image/png|image/jpeg,maxBytes=1048576"`
Out golem.Path `golem:"direction=output,kind=file,extensions=png"`
Source golem.URL `golem:"schemes=https"`
Limit golem.Option[uint8] `golem:"max=9"` // an option or a list is restricted through to its values
}
  • Numbers take min, max and unit. A quantity takes min and max, such as 0.5 in its base unit or 20kg.
  • golem.Text takes languages, minLength, maxLength and regex. A plain string carries no restrictions.
  • golem.Binary takes mime, minBytes and maxBytes. golem.Path takes direction, kind, mime and extensions. golem.URL takes schemes and hosts.
  • Lists are separated by |. A regex runs to the end of the tag, so put it last.
  • A tag that does not fit the field's type is reported as a definition error.

Returning Failures

Handlers return only their output value. Signal failure two ways:

  • Uncaught errors — a panic (e.g. from golem.Must(...) on an unexpected error) traps the component, as in Rust: the invocation fails without retry and the caller gets an error, not a result.
  • Domain errors the caller should observe as a value — model them in the method's output type with golem.Result[Ok, Err]:
go
// Add to agents/counter/counter.go (package counter).
type RecordIn struct{ Amount int64 }
var Record = Agent.Method[RecordIn, golem.Result[int64, string]]("record")

In agents/counter/impl/impl.go, using the counter import, agent and state defined above, register the handler in init:

go
func init() {
agent.Handle(counter.Record, func(ctx *golem.Context[state], in counter.RecordIn) golem.Result[int64, string] {
if in.Amount < 0 {
return golem.Err[int64, string]("cannot record a negative amount")
}
ctx.State.value += in.Amount
return golem.Ok[int64, string](ctx.State.value)
})
}

Returning golem.Err(...) completes the invocation successfully — the caller receives the error as a value (and can unwrap with .MustOk()). Panicking instead fails the whole agent.

Key Constraints

  • Method parameters/results are passed by value; wrap the parameter list in a struct (one field per WIT parameter).
  • A golem.Principal field in a method's input struct (or the ID) is filled by the host with the invocation's principal and is not a parameter callers pass; ctx.Principal() is the principal the agent was initialized with (see golem-add-http-auth-go).
  • ID must be a struct; its fields plus an optional phantom UUID define identity. Durable agents are created implicitly on first invocation. An empty ID (type ID struct{}) is a singleton only for non-phantom durable addressing. NewPhantom adds a fresh UUID without changing the mode; ephemeral targets require phantom addressing. Use a new NewPhantom target for each independent ephemeral call; do not reuse a completed ephemeral target (see golem-multi-instance-agent-go).
  • An agent admits invocations sequentially, but goroutines within one invocation can interleave at yield points (for example, while waiting for RPC, HTTP, streams or promises). This is cooperative concurrency, not a promise of CPU-thread parallelism. Synchronize shared mutable state when goroutines can interleave; sequential invocation admission does not remove that need.
  • Method descriptors must be package-level vars in the definition package (the same value is used by the schema, the binding, and callers).
  • State lives only in the impl package (unexported) — callers never see it.
  • Target is WASM only: no raw sockets; outgoing HTTP goes through the SDK's WASI transport (see golem-make-http-request-go).
Related Skills
SkillWhen to Load
golem-call-another-agent-goThis agent needs to call another agent (RPC)
golem-add-http-endpoint-goExpose this agent's methods over HTTP
golem-multi-instance-agent-goAddress many instances of an agent by ID
golem-custom-snapshot-goCustomize how the agent's state is snapshotted

Security

  • Security scanPassed, 90 of 100Pattern checks on the skill's files.
  • Model reviewPassed

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

7-day trend score
0

Not refreshed yet.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/golemcloud/golem/golem-add-agent-go/badge)](https://verified-skill.com/skills/golemcloud/golem/golem-add-agent-go)

Something wrong with this skill or its page? Tell us.