Skip to content

Conversations & Messages 💬

In Loom, a conversation is represented as an ordered list of messages. Because Loom is "state-first," these messages are typically stored in your GraphState and passed to LLMs to provide context.

1. Message Roles

Loom defines four core roles, each representing a different participant in the conversation:

Role Description Concrete Type
System High-level instructions for the model (e.g., "You are a helpful assistant"). message.System
User Messages sent by the human user. message.User
Assistant Responses generated by the AI model. message.Assistant
Tool The result of an external tool execution. message.Tool

Creating Messages

import "github.com/masterkeysrd/loom/message"

systemMsg := message.NewSystemText("You are a math tutor.")
userMsg   := message.NewUserText("What is the square root of 144?")

2. Content Blocks

Loom messages are composed of one or more Blocks. This flexible structure supports multimodal inputs and advanced features like "chain-of-thought" thinking.

Text Blocks

The most common block type, used for simple text.

block := &message.TextBlock{Text: "Hello!"}

Multimodal Blocks (Images, Documents)

You can attach images, PDFs, or other media to a user message.

imageMsg := message.NewUserImage(imageData, "image/png")
// OR via URL
imageMsg := message.NewUserImageURL("https://example.com/photo.jpg")

Thinking Blocks

Some models (like Claude 3.5 or Gemini 2.0) can return their internal reasoning. Loom surfaces this as a ThinkingBlock.

// Access the thinking part of an assistant response
thinking := assistantMsg.Content.Thought()

3. Managing History (MessageList)

A MessageList is a specialized slice of messages that provides critical functionality for state management and persistence. Always use MessageList instead of a raw []message.Message when storing history in your Graph State.

type MyState struct {
    History message.MessageList `json:"history"`
}

Why use MessageList?

  1. Polymorphic Serialization: MessageList automatically handles the complex task of serializing different message types (User, Assistant, etc.) into a single JSON array with a "role" discriminator.
  2. Type-Safe Deserialization: When loading a checkpoint from a database (SQL or PG), MessageList ensures that raw JSON is correctly restored into the proper Go structs.
  3. Fluent Helpers: Provides methods like Last() and Text() to easily access content without range loops.
history := message.MessageList{
    message.NewSystemText("..."),
    message.NewUserText("..."),
}

// Add a new response
history = append(history, assistantResponse)

4. Token Metrics

Loom tracks token usage for every assistant response. This information is attached to the message.Assistant struct.

if resp.Metrics != nil {
    fmt.Printf("Input: %d, Output: %d, Total: %d, Estimated Cost: %s\n",
        resp.Metrics.Tokens.Input,
        resp.Metrics.Tokens.Output,
        resp.Metrics.TotalTokens,
        resp.Metrics.TotalCost)
}

5. Message Extensions & Metadata

Loom provides two ways to attach auxiliary information to a message:

Extensions (Type-Safe Hints)

Extensions are provider-specific structs that influence model behavior. Unlike raw metadata, Extensions are type-safe and are automatically restored to their concrete types when loaded from storage.

Loom provides two methods for adding extensions:

  1. WithExtension (Fluent): Available on concrete message types (User, Assistant, etc.). It returns the same concrete type, allowing for method chaining without type erasure.
  2. AddExtension (Polymorphic): Available on the Message interface. Use this when you are working with a generic message object.
// 1. Fluent usage (preserves *message.User type)
msg := message.NewUserText("Analyze this...").
    WithExtension(&loomanthropic.MessageCache{Enabled: true})

// 2. Interface usage
var m message.Message = msg
m.AddExtension(&someother.Extension{})

Metadata (Arbitrary Data)

Metadata is a simple map[string]any for application-level data (IDs, timestamps, custom flags) that doesn't need a formal schema.

msg.Metadata["session_id"] = "xyz-123"

Summary

  • Roles: Use System, User, Assistant, and Tool to distinguish participants.
  • Blocks: Mix and match TextBlock, ImageBlock, and ThinkingBlock for rich interactions.
  • MessageList: Use this type to manage and persist your conversation history in your graph state.