Basic checks
What's broken?
Agent's def_delegators list covers six of the seven Chat callback
registrations — before_message, after_message, before_tool_call,
after_tool_result, before_fallback, after_fallback — but not
before_request, so agent.before_request { ... } raises NoMethodError while
its six siblings work.
This isn't blocking anything: an agent keeps the chat you hand it, so configuring
the chat before constructing the agent works fine (workaround under "What actually
happened"). It's the asymmetry I'm reporting, since the class documentation
describes the delegating behaviour without an exception:
- "Agent instances delegate the Chat API (#ask, #complete, #with_tools, and so
on) to the wrapped chat, which is available via #chat."
- On
GUARDED_OPERATIONS: "The chat operations an agent instance runs through
its ::rescue_from handlers. Every other Chat method is delegated untouched."
Calling each callback on an agent, on main:
| callback |
respond_to? |
returns the wrapped chat |
before_message |
true |
yes |
after_message |
true |
yes |
before_tool_call |
true |
yes |
after_tool_result |
true |
yes |
before_fallback |
true |
yes |
after_fallback |
true |
yes |
before_request |
false |
— |
It reads like an oversight rather than a decision: the commit that added
before_request touched chat.rb and the protocols but not agent.rb, and the
existing 'delegates callback hooks to the underlying chat' example enumerates
the six callbacks that existed at the time.
The argument against delegating it, in case it was deliberate: before_request
edits the provider's wire format, and agents support with_fallbacks across
providers, so a hook written for one payload shape could misfire after a
fallback. If that's the reason, a line in the Agent docs would settle it and I'm
happy to send that instead.
Either way I have a patch ready — one word in agent.rb plus a test that extends
the existing delegation example rather than adding a spec file (two files, eight
lines). Glad to open the PR, or to close this if the omission is intended.
How to reproduce
No network needed — Chat#render applies the hooks without sending.
require 'ruby_llm'
RubyLLM.configure { |c| c.anthropic_api_key = ENV['ANTHROPIC_API_KEY'] }
class SupportAgent < RubyLLM::Agent
model 'claude-haiku-4-5'
end
agent = SupportAgent.new
agent.respond_to?(:before_request)
# => false
agent.before_request { |payload| payload[:metadata] = { user_id: 'u-1' } }
# raises NoMethodError
# The six siblings all work on the same object:
agent.before_message { :ok }
# => #<RubyLLM::Chat model: "claude-haiku-4-5" ...>
Expected behavior
agent.before_request { |payload| ... } registers the hook on the wrapped chat
and returns it, like the other six callbacks.
What actually happened
NoMethodError: undefined method `before_request' for
#<SupportAgent:0x00007df7805c7358 @chat=#<RubyLLM::Chat model: "claude-haiku-4-5",
provider: "anthropic", messages: 0>>
Did you mean? before_message
Ruby's own suggestion is before_message — one of the callbacks that is
delegated.
Workaround, which reads fine and is what I'm using: configure the chat first and
hand it to the agent.
chat = RubyLLM.chat(model: 'claude-haiku-4-5')
chat.before_request { |payload| payload[:metadata] = { user_id: 'u-1' } }
agent = SupportAgent.new(chat: chat)
agent.ask_later('Hello')
agent.chat.equal?(chat) # => true, the agent keeps this chat
agent.chat.render[:metadata] # => {:user_id=>"u-1"}
Environment
- Ruby version: 3.1.4
- RubyLLM version: main @ be80b6f (2.0 development; VERSION still reads 1.16.0)
- Provider (OpenAI, Anthropic, etc.): Anthropic — provider-independent, the payload
never leaves the process in the repro
- OS: Linux
Basic checks
What's broken?
Agent'sdef_delegatorslist covers six of the seven Chat callbackregistrations —
before_message,after_message,before_tool_call,after_tool_result,before_fallback,after_fallback— but notbefore_request, soagent.before_request { ... }raisesNoMethodErrorwhileits six siblings work.
This isn't blocking anything: an agent keeps the chat you hand it, so configuring
the chat before constructing the agent works fine (workaround under "What actually
happened"). It's the asymmetry I'm reporting, since the class documentation
describes the delegating behaviour without an exception:
on) to the wrapped chat, which is available via #chat."
GUARDED_OPERATIONS: "The chat operations an agent instance runs throughits ::rescue_from handlers. Every other Chat method is delegated untouched."
Calling each callback on an agent, on
main:respond_to?before_messageafter_messagebefore_tool_callafter_tool_resultbefore_fallbackafter_fallbackbefore_requestIt reads like an oversight rather than a decision: the commit that added
before_requesttouchedchat.rband the protocols but notagent.rb, and theexisting
'delegates callback hooks to the underlying chat'example enumeratesthe six callbacks that existed at the time.
The argument against delegating it, in case it was deliberate:
before_requestedits the provider's wire format, and agents support
with_fallbacksacrossproviders, so a hook written for one payload shape could misfire after a
fallback. If that's the reason, a line in the Agent docs would settle it and I'm
happy to send that instead.
Either way I have a patch ready — one word in
agent.rbplus a test that extendsthe existing delegation example rather than adding a spec file (two files, eight
lines). Glad to open the PR, or to close this if the omission is intended.
How to reproduce
No network needed —
Chat#renderapplies the hooks without sending.Expected behavior
agent.before_request { |payload| ... }registers the hook on the wrapped chatand returns it, like the other six callbacks.
What actually happened
Ruby's own suggestion is
before_message— one of the callbacks that isdelegated.
Workaround, which reads fine and is what I'm using: configure the chat first and
hand it to the agent.
Environment