Messages and custom types¶
A run operates on AgentMessage values, not raw llm messages. This lets an
application keep its own UI-only entries in the transcript — notices, separators,
status banners — alongside the messages the model actually exchanges.
Two kinds of message¶
AgentMessage is a sealed interface with two implementations:
- Adapted
llmmessages.FromLLMwraps a standardllm.UserMessage,llm.AssistantMessage, orllm.ToolResultMessage. This is the common path, since the agent package cannot add methods to types owned byllm. - Your own types. A struct that embeds
agent.CustomsatisfiesAgentMessagewithout referencing the interface's unexported marker.
agent.UserMessage is a shortcut for the frequent text-plus-images case:
msg := agent.UserMessage("What is in this picture?",
llm.ImageContent{Data: base64PNG, MIMEType: "image/png"})
UI-only messages¶
Embed agent.Custom to define a message that lives in the transcript and the
event stream but is not part of the model conversation:
type Notice struct {
agent.Custom
Text string
}
assistant := agent.New(agent.Options{
Model: model,
Messages: []agent.AgentMessage{Notice{Text: "session resumed"}}, // kept, not sent
})
A Notice appears in Snapshot().Messages and flows through MessageStart /
MessageEnd events, so your UI can render it — but the default projection drops it
before the model sees the conversation.
Projecting to the model¶
ConvertToLLM projects the transcript into llm.Message values for one request.
The default unwraps FromLLM messages and drops every other AgentMessage, so
custom messages stay in history but never reach the model.
Provide your own ConvertToLLM to project custom messages yourself — for example,
to render a Notice as a system note the model should see:
assistant := agent.New(agent.Options{
Model: model,
ConvertToLLM: func(messages []agent.AgentMessage) []llm.Message {
out := make([]llm.Message, 0, len(messages))
for _, m := range messages {
switch v := m.(type) {
case Notice:
out = append(out, llm.UserText("[system] "+v.Text))
default:
if std, ok := agent.ToLLM(m); ok { // unwrap FromLLM messages
out = append(out, std)
}
}
}
return out
},
})
The projection runs at the request boundary on every turn, after
TransformContext, so it always sees the current transcript.
Persisting a transcript¶
FromLLM-wrapped messages hold standard llm messages, which serialize to
self-describing JSON and can be replayed against any model. Custom messages are
your own types: to persist and restore them, give them a type discriminator and
register a decoder in your application's storage layer — the agent package keeps
no persistence of its own.