Skip to content

Context & memory

Sundae can look things up, but she starts every reply blind — she doesn't know who's writing or what they've asked before. In this chapter we fix that with resolvers: the pieces that assemble the context the model sees before inference.

What a resolver does

A resolver is a subclass of Protege::Resolver that contributes messages to the model's context — a system prompt, a database record, the conversation so far. An agent's resolvers run in order and their contributions are concatenated into the prompt. Where tools are the agent's hands, resolvers are its briefing.

The CustomerServicePersona class already has a chain, from the generator:

ruby
# app/personas/customer_service_persona.rb
message_resolvers do |chain|
  chain.use(Protege::LoadTextResolver, role: :system) { |ctx| ctx.persona.instructions }
  chain.use Protege::ThreadHistoryResolver
end

That's two built-ins already earning their keep: LoadTextResolver puts Sundae's editable instructions in as the system prompt, and ThreadHistoryResolver gives her memory (more on that below). Now let's add who she's talking to.

Load the customer

Scaffold a resolver:

bash
bin/rails g protege:resolver customer_context

Fill it in — it looks up the Customer who emailed and hands the model a short brief:

ruby
# app/resolvers/customer_context_resolver.rb
class CustomerContextResolver < Protege::Resolver
  def resolve(context:)
    customer = Customer.find_by(email: context.message.from_address)
    return nil unless customer   # unknown sender → contribute nothing

    message(role: :system, content: <<~TXT)
      You're speaking with #{customer.name} (#{customer.email}).
      Recent orders: #{customer.orders.recent.map(&:number).join(', ').presence || 'none'}.
    TXT
  end
end
  • resolve(context:) returns a message, an array of messages, or nil. Returning nil is normal — a first-time emailer isn't a customer yet, so there's simply nothing to add.
  • Build messages with the message(role:, content:) helper. role: :system folds this into the prompt.
  • The context is the same one tools get: context.message.from_address is who wrote in.

Add the menu, then wire the chain

Resolvers do nothing until you add them to a persona's chain — order matters, and different roles want different context. Let's also drop in today's menu using the built-in LoadRecordResolver, which renders records to text:

ruby
# app/personas/customer_service_persona.rb
message_resolvers do |chain|
  chain.use(Protege::LoadTextResolver, role: :system) { |ctx| ctx.persona.instructions }
  chain.use CustomerContextResolver
  chain.use(Protege::LoadRecordResolver) { Flavor.available }   # today's menu
  chain.use Protege::ThreadHistoryResolver
end

The chain runs top to bottom: Sundae's persona prompt, then who she's talking to, then what's in stock, then the conversation history — and finally the harness appends the customer's new message as the closing turn. Now when a customer asks "is pistachio back yet?", Sundae answers from the actual menu, by name.

Memory comes from the thread

The last resolver, ThreadHistoryResolver, is what makes Sundae feel like she remembers. On each turn it replays the thread's earlier messages — inbound as the customer, outbound as Sundae — and, crucially, her earlier tool use too: if she looked up an order three replies ago, that result is replayed, so she doesn't look it up again or contradict herself.

It's bounded by default (a recent window) so long threads don't blow up the prompt; pass limit: nil to replay everything, or limit: 20 for a specific window. See Resolvers for the details.

Memory is per-thread today

Sundae remembers within a conversation. She does not yet remember a customer across separate threads — persona-level cross-thread memory is planned but not built. For durable facts (like the customer's record), load them explicitly with a resolver, exactly as CustomerContextResolver does.

Next

Sundae knows her customer and the menu, and remembers the conversation. Now let's widen what she can do — refunds, stock checks, new orders — and teach her to handle photos.

More tools & attachments.