Skip to content

Question: Is omitting tool messages from conversation history restoration intentional? #38

@sergiobayona

Description

@sergiobayona

I noticed that Runner#restore_conversation_history only restores user and assistant messages, but completely ignores tool messages. I'm trying to understand if this is an intentional design decision or a potential bug.

Current Behavior

When a conversation is saved and then restored (for continuation across sessions or process boundaries), the system:

  1. Saves all message types including tool messages via MessageExtractor.extract_messages (message_extractor.rb:43-54)
  2. Restores only user and assistant messages, skipping tool messages (runner.rb:273-276)

Code Reference

Saving (includes tool messages):

# lib/agents/helpers/message_extractor.rb
def extract_messages(chat, current_agent)
  chat.messages.filter_map do |msg|
    case msg.role
    when :user, :assistant
      extract_user_or_assistant_message(msg, current_agent)
    when :tool
      extract_tool_message(msg)  # ✅ Tool messages ARE saved
    end
  end
end

Restoring (skips tool messages):

# lib/agents/runner.rb:270-288
def restore_conversation_history(chat, context_wrapper)
  history = context_wrapper.context[:conversation_history] || []

  history.each do |msg|
    # Only restore user and assistant messages with content
    next unless %i[user assistant].include?(msg[:role].to_sym)  # ⚠️ Tool messages skipped
    next unless msg[:content] && !Helpers::MessageExtractor.content_empty?(msg[:content])

    content = RubyLLM::Content.new(msg[:content])
    message = RubyLLM::Message.new(role: msg[:role].to_sym, content: content)
    chat.add_message(message)
  end
end

Example Scenario

# Turn 1: User asks for account information
runner = Agents::Runner.with_agents(support_agent)
result1 = runner.run("What's my account balance?")
# Agent calls lookup_account_tool, gets balance: $500
# Conversation history saved:
#   - user: "What's my account balance?"
#   - assistant: [tool_calls: lookup_account]
#   - tool: {result: "Balance: $500"}
#   - assistant: "Your balance is $500"

# Turn 2: Continue conversation in new process/session
result2 = runner.run("Can I afford a $400 purchase?", context: result1.context)
# Conversation history restored:
#   - user: "What's my account balance?"
#   - assistant: "Your balance is $500"
#   ⚠️ Tool call and tool result messages NOT restored!

# Potential issue: LLM has no memory that it looked up the balance via a tool.
# It only sees it said "$500" but doesn't know how it got that information.

Potential Impacts

If this is unintentional, it could lead to:

  1. Repeated tool calls: LLM doesn't "remember" it already fetched data, may call the same tool again
  2. Lost context: LLM loses the reasoning chain of how it obtained information
  3. Inconsistent behavior: First conversation turn has full context, subsequent turns don't
  4. Wasted resources: Redundant API calls and tool executions

Questions for Maintainers

  1. Is this intentional? Are there specific reasons why tool messages should not be restored?

    • Performance considerations?
    • RubyLLM compatibility constraints?
    • Context window management?
  2. If intentional, what's the recommended pattern for maintaining tool execution context across conversation turns?

  3. If unintentional, I'm happy to submit a PR to restore tool messages along with the necessary tests.

Environment

  • Gem Version: 0.7.0
  • RubyLLM Version: ~> 1.8.2
  • Ruby Version: 3.1+

Additional Context

This asymmetry (saving tool messages but not restoring them) suggests it might be unintentional, but I wanted to check before assuming it's a bug. The SDK's emphasis on "Shared Context: Maintaining state and conversation history across agent interactions with full persistence support" made me think complete message history restoration would be expected.

Thank you for clarifying!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions