riffer 0.31.0 → 0.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (213) hide show
  1. checksums.yaml +4 -4
  2. data/.agents/code-style.md +63 -4
  3. data/.agents/rbs-inline.md +1 -6
  4. data/.release-please-manifest.json +1 -1
  5. data/AGENTS.md +1 -2
  6. data/CHANGELOG.md +18 -0
  7. data/docs/08_MESSAGES.md +1 -1
  8. data/docs/14_MCP.md +50 -5
  9. data/docs/providers/02_AMAZON_BEDROCK.md +14 -0
  10. data/lib/riffer/agent/config.rb +42 -47
  11. data/lib/riffer/agent/context.rb +70 -50
  12. data/lib/riffer/agent/response.rb +4 -20
  13. data/lib/riffer/agent/run.rb +28 -67
  14. data/lib/riffer/agent/serializer.rb +22 -81
  15. data/lib/riffer/agent/session/repair.rb +14 -40
  16. data/lib/riffer/agent/session.rb +25 -67
  17. data/lib/riffer/agent/structured_output/result.rb +3 -11
  18. data/lib/riffer/agent/structured_output.rb +5 -13
  19. data/lib/riffer/agent.rb +74 -192
  20. data/lib/riffer/config.rb +34 -101
  21. data/lib/riffer/evals/evaluator.rb +7 -27
  22. data/lib/riffer/evals/evaluator_runner.rb +11 -19
  23. data/lib/riffer/evals/judge.rb +4 -25
  24. data/lib/riffer/evals/result.rb +1 -18
  25. data/lib/riffer/evals/run_result.rb +0 -11
  26. data/lib/riffer/evals/scenario_result.rb +0 -14
  27. data/lib/riffer/evals.rb +0 -6
  28. data/lib/riffer/guardrail.rb +4 -27
  29. data/lib/riffer/guardrails/modification.rb +0 -10
  30. data/lib/riffer/guardrails/result.rb +3 -30
  31. data/lib/riffer/guardrails/runner.rb +5 -22
  32. data/lib/riffer/guardrails/tripwire.rb +1 -19
  33. data/lib/riffer/guardrails.rb +2 -4
  34. data/lib/riffer/helpers/call_or_value.rb +4 -3
  35. data/lib/riffer/helpers/class_name_converter.rb +3 -1
  36. data/lib/riffer/helpers/dependencies.rb +5 -7
  37. data/lib/riffer/helpers.rb +0 -5
  38. data/lib/riffer/mcp/authenticated_tool.rb +9 -9
  39. data/lib/riffer/mcp/client.rb +12 -17
  40. data/lib/riffer/mcp/manifest.rb +13 -10
  41. data/lib/riffer/mcp/registration.rb +2 -11
  42. data/lib/riffer/mcp/registry.rb +44 -52
  43. data/lib/riffer/mcp/search_tool.rb +53 -0
  44. data/lib/riffer/mcp/tool_factory.rb +13 -18
  45. data/lib/riffer/mcp.rb +12 -17
  46. data/lib/riffer/messages/assistant.rb +2 -9
  47. data/lib/riffer/messages/base.rb +46 -16
  48. data/lib/riffer/messages/file_part.rb +32 -24
  49. data/lib/riffer/messages/system.rb +0 -5
  50. data/lib/riffer/messages/tool.rb +0 -10
  51. data/lib/riffer/messages/user.rb +0 -10
  52. data/lib/riffer/messages.rb +0 -7
  53. data/lib/riffer/params/boolean.rb +2 -4
  54. data/lib/riffer/params/param.rb +28 -39
  55. data/lib/riffer/params.rb +9 -21
  56. data/lib/riffer/providers/amazon_bedrock.rb +42 -28
  57. data/lib/riffer/providers/anthropic.rb +4 -9
  58. data/lib/riffer/providers/azure_open_ai.rb +3 -19
  59. data/lib/riffer/providers/base.rb +13 -26
  60. data/lib/riffer/providers/gemini.rb +4 -4
  61. data/lib/riffer/providers/mock.rb +6 -26
  62. data/lib/riffer/providers/open_ai.rb +6 -8
  63. data/lib/riffer/providers/open_router.rb +4 -10
  64. data/lib/riffer/providers/repository.rb +4 -3
  65. data/lib/riffer/providers/token_usage.rb +9 -20
  66. data/lib/riffer/providers.rb +0 -8
  67. data/lib/riffer/runner/fibers.rb +10 -16
  68. data/lib/riffer/runner/sequential.rb +1 -4
  69. data/lib/riffer/runner/threaded.rb +3 -14
  70. data/lib/riffer/runner.rb +2 -15
  71. data/lib/riffer/skills/activate_tool.rb +2 -11
  72. data/lib/riffer/skills/adapter.rb +4 -22
  73. data/lib/riffer/skills/backend.rb +7 -21
  74. data/lib/riffer/skills/config.rb +10 -31
  75. data/lib/riffer/skills/context.rb +5 -20
  76. data/lib/riffer/skills/filesystem_backend.rb +7 -25
  77. data/lib/riffer/skills/frontmatter.rb +10 -28
  78. data/lib/riffer/skills/markdown_adapter.rb +2 -9
  79. data/lib/riffer/skills/xml_adapter.rb +2 -8
  80. data/lib/riffer/stream_events/base.rb +1 -6
  81. data/lib/riffer/stream_events/guardrail_modification.rb +1 -8
  82. data/lib/riffer/stream_events/guardrail_tripwire.rb +1 -8
  83. data/lib/riffer/stream_events/interrupt.rb +4 -7
  84. data/lib/riffer/stream_events/reasoning_delta.rb +2 -4
  85. data/lib/riffer/stream_events/reasoning_done.rb +2 -4
  86. data/lib/riffer/stream_events/skill_activation.rb +2 -4
  87. data/lib/riffer/stream_events/text_delta.rb +0 -2
  88. data/lib/riffer/stream_events/text_done.rb +1 -3
  89. data/lib/riffer/stream_events/token_usage_done.rb +1 -8
  90. data/lib/riffer/stream_events/tool_call_delta.rb +2 -3
  91. data/lib/riffer/stream_events/tool_call_done.rb +1 -3
  92. data/lib/riffer/stream_events/web_search_done.rb +1 -3
  93. data/lib/riffer/stream_events/web_search_status.rb +2 -3
  94. data/lib/riffer/stream_events.rb +0 -10
  95. data/lib/riffer/tool.rb +6 -13
  96. data/lib/riffer/tools/response.rb +8 -4
  97. data/lib/riffer/tools/runtime/fibers.rb +0 -3
  98. data/lib/riffer/tools/runtime/inline.rb +1 -4
  99. data/lib/riffer/tools/runtime/threaded.rb +0 -2
  100. data/lib/riffer/tools/runtime.rb +5 -38
  101. data/lib/riffer/tools/toolable.rb +5 -16
  102. data/lib/riffer/tools.rb +0 -4
  103. data/lib/riffer/version.rb +1 -1
  104. data/lib/riffer.rb +7 -8
  105. data/sig/generated/riffer/agent/config.rbs +29 -46
  106. data/sig/generated/riffer/agent/context.rbs +40 -48
  107. data/sig/generated/riffer/agent/response.rbs +4 -20
  108. data/sig/generated/riffer/agent/run.rbs +12 -61
  109. data/sig/generated/riffer/agent/serializer.rbs +21 -80
  110. data/sig/generated/riffer/agent/session/repair.rbs +12 -40
  111. data/sig/generated/riffer/agent/session.rbs +25 -67
  112. data/sig/generated/riffer/agent/structured_output/result.rbs +2 -10
  113. data/sig/generated/riffer/agent/structured_output.rbs +5 -12
  114. data/sig/generated/riffer/agent.rbs +57 -186
  115. data/sig/generated/riffer/config.rbs +34 -100
  116. data/sig/generated/riffer/evals/evaluator.rbs +7 -27
  117. data/sig/generated/riffer/evals/evaluator_runner.rbs +9 -19
  118. data/sig/generated/riffer/evals/judge.rbs +4 -24
  119. data/sig/generated/riffer/evals/result.rbs +1 -17
  120. data/sig/generated/riffer/evals/run_result.rbs +0 -10
  121. data/sig/generated/riffer/evals/scenario_result.rbs +0 -13
  122. data/sig/generated/riffer/evals.rbs +0 -6
  123. data/sig/generated/riffer/guardrail.rbs +4 -27
  124. data/sig/generated/riffer/guardrails/modification.rbs +0 -10
  125. data/sig/generated/riffer/guardrails/result.rbs +3 -30
  126. data/sig/generated/riffer/guardrails/runner.rbs +5 -22
  127. data/sig/generated/riffer/guardrails/tripwire.rbs +1 -19
  128. data/sig/generated/riffer/guardrails.rbs +2 -4
  129. data/sig/generated/riffer/helpers/call_or_value.rbs +4 -3
  130. data/sig/generated/riffer/helpers/class_name_converter.rbs +1 -1
  131. data/sig/generated/riffer/helpers/dependencies.rbs +3 -7
  132. data/sig/generated/riffer/helpers.rbs +0 -5
  133. data/sig/generated/riffer/mcp/authenticated_tool.rbs +5 -4
  134. data/sig/generated/riffer/mcp/client.rbs +10 -16
  135. data/sig/generated/riffer/mcp/manifest.rbs +9 -9
  136. data/sig/generated/riffer/mcp/registration.rbs +2 -10
  137. data/sig/generated/riffer/mcp/registry.rbs +11 -18
  138. data/sig/generated/riffer/mcp/search_tool.rbs +26 -0
  139. data/sig/generated/riffer/mcp/tool_factory.rbs +10 -15
  140. data/sig/generated/riffer/mcp.rbs +10 -17
  141. data/sig/generated/riffer/messages/assistant.rbs +2 -8
  142. data/sig/generated/riffer/messages/base.rbs +11 -16
  143. data/sig/generated/riffer/messages/file_part.rbs +13 -23
  144. data/sig/generated/riffer/messages/system.rbs +0 -4
  145. data/sig/generated/riffer/messages/tool.rbs +0 -9
  146. data/sig/generated/riffer/messages/user.rbs +0 -9
  147. data/sig/generated/riffer/messages.rbs +0 -7
  148. data/sig/generated/riffer/params/boolean.rbs +2 -4
  149. data/sig/generated/riffer/params/param.rbs +21 -39
  150. data/sig/generated/riffer/params.rbs +9 -21
  151. data/sig/generated/riffer/providers/amazon_bedrock.rbs +21 -25
  152. data/sig/generated/riffer/providers/anthropic.rbs +2 -7
  153. data/sig/generated/riffer/providers/azure_open_ai.rbs +3 -18
  154. data/sig/generated/riffer/providers/base.rbs +9 -25
  155. data/sig/generated/riffer/providers/gemini.rbs +0 -2
  156. data/sig/generated/riffer/providers/mock.rbs +6 -26
  157. data/sig/generated/riffer/providers/open_ai.rbs +1 -5
  158. data/sig/generated/riffer/providers/open_router.rbs +4 -10
  159. data/sig/generated/riffer/providers/repository.rbs +2 -3
  160. data/sig/generated/riffer/providers/token_usage.rbs +6 -16
  161. data/sig/generated/riffer/providers.rbs +0 -8
  162. data/sig/generated/riffer/runner/fibers.rbs +8 -15
  163. data/sig/generated/riffer/runner/sequential.rbs +1 -3
  164. data/sig/generated/riffer/runner/threaded.rbs +3 -13
  165. data/sig/generated/riffer/runner.rbs +2 -14
  166. data/sig/generated/riffer/skills/activate_tool.rbs +2 -11
  167. data/sig/generated/riffer/skills/adapter.rbs +4 -22
  168. data/sig/generated/riffer/skills/backend.rbs +7 -21
  169. data/sig/generated/riffer/skills/config.rbs +10 -31
  170. data/sig/generated/riffer/skills/context.rbs +5 -20
  171. data/sig/generated/riffer/skills/filesystem_backend.rbs +7 -24
  172. data/sig/generated/riffer/skills/frontmatter.rbs +10 -27
  173. data/sig/generated/riffer/skills/markdown_adapter.rbs +2 -9
  174. data/sig/generated/riffer/skills/xml_adapter.rbs +2 -8
  175. data/sig/generated/riffer/stream_events/base.rbs +1 -6
  176. data/sig/generated/riffer/stream_events/guardrail_modification.rbs +1 -8
  177. data/sig/generated/riffer/stream_events/guardrail_tripwire.rbs +1 -8
  178. data/sig/generated/riffer/stream_events/interrupt.rbs +4 -7
  179. data/sig/generated/riffer/stream_events/reasoning_delta.rbs +2 -4
  180. data/sig/generated/riffer/stream_events/reasoning_done.rbs +2 -4
  181. data/sig/generated/riffer/stream_events/skill_activation.rbs +2 -4
  182. data/sig/generated/riffer/stream_events/text_delta.rbs +0 -2
  183. data/sig/generated/riffer/stream_events/text_done.rbs +1 -3
  184. data/sig/generated/riffer/stream_events/token_usage_done.rbs +1 -7
  185. data/sig/generated/riffer/stream_events/tool_call_delta.rbs +2 -3
  186. data/sig/generated/riffer/stream_events/tool_call_done.rbs +1 -3
  187. data/sig/generated/riffer/stream_events/web_search_done.rbs +1 -3
  188. data/sig/generated/riffer/stream_events/web_search_status.rbs +2 -3
  189. data/sig/generated/riffer/stream_events.rbs +0 -10
  190. data/sig/generated/riffer/tool.rbs +5 -12
  191. data/sig/generated/riffer/tools/response.rbs +6 -4
  192. data/sig/generated/riffer/tools/runtime/fibers.rbs +0 -3
  193. data/sig/generated/riffer/tools/runtime/inline.rbs +1 -3
  194. data/sig/generated/riffer/tools/runtime/threaded.rbs +0 -2
  195. data/sig/generated/riffer/tools/runtime.rbs +5 -37
  196. data/sig/generated/riffer/tools/toolable.rbs +4 -14
  197. data/sig/generated/riffer/tools.rbs +0 -4
  198. data/sig/generated/riffer.rbs +5 -4
  199. data/sig/manual/riffer/agent/session/repair.rbs +5 -0
  200. data/sig/manual/riffer/evals/evaluator_runner.rbs +5 -0
  201. data/sig/manual/riffer/helpers/class_name_converter.rbs +5 -0
  202. data/sig/manual/riffer/helpers/dependencies.rbs +5 -0
  203. data/sig/manual/riffer/mcp/authenticated_tool.rbs +5 -0
  204. data/sig/manual/riffer/mcp/registry.rbs +5 -0
  205. data/sig/manual/riffer/mcp/tool_factory.rbs +5 -0
  206. data/sig/manual/riffer/mcp.rbs +5 -0
  207. data/sig/manual/riffer/providers/repository.rbs +5 -0
  208. data/sig/manual/riffer.rbs +5 -0
  209. metadata +17 -9
  210. data/.agents/rdoc.md +0 -69
  211. data/lib/riffer/messages/converter.rb +0 -90
  212. data/sig/generated/riffer/messages/converter.rbs +0 -33
  213. data/sig/manual/riffer/tools/toolable.rbs +0 -6
@@ -3,16 +3,10 @@
3
3
 
4
4
  require "json"
5
5
 
6
- # Riffer::Agent::StructuredOutput provides parse/validate for structured JSON
7
- # responses from LLM providers.
8
- #
9
- # params = Riffer::Params.new
10
- # params.required(:sentiment, String)
11
- # so = Riffer::Agent::StructuredOutput.new(params)
12
- # result = so.parse_and_validate('{"sentiment":"positive","score":0.9}')
13
- # result.object #=> {sentiment: "positive", score: 0.9}
14
- #
6
+ # Parses and validates structured JSON responses against a Riffer::Params
7
+ # schema.
15
8
  class Riffer::Agent::StructuredOutput
9
+ # The schema parameters.
16
10
  attr_reader :params #: Riffer::Params
17
11
 
18
12
  #--
@@ -29,10 +23,8 @@ class Riffer::Agent::StructuredOutput
29
23
  @params.to_json_schema(strict: strict)
30
24
  end
31
25
 
32
- # Parses a JSON string and validates it against the schema.
33
- #
34
- # Returns a Result with the validated object on success, or an error message on failure.
35
- #
26
+ # Parses a JSON string and validates it against the schema, returning a
27
+ # Result carrying either the validated object or an error message.
36
28
  #--
37
29
  #: (String) -> Riffer::Agent::StructuredOutput::Result
38
30
  def parse_and_validate(json_string)
data/lib/riffer/agent.rb CHANGED
@@ -3,12 +3,8 @@
3
3
 
4
4
  require "json"
5
5
 
6
- # Riffer::Agent is the base class for all agents in the Riffer framework.
7
- #
8
- # Provides orchestration for LLM calls, tool use, and message management.
9
- # Subclass this to create your own agents.
10
- #
11
- # See Riffer::Messages and Riffer::Providers.
6
+ # Base class for all agents in the Riffer framework. Subclass it to define an
7
+ # agent's model, instructions, tools, and guardrails.
12
8
  #
13
9
  # class MyAgent < Riffer::Agent
14
10
  # model 'openai/gpt-4o'
@@ -21,14 +17,9 @@ require "json"
21
17
  class Riffer::Agent
22
18
  # @rbs self.@config: Riffer::Agent::Config?
23
19
 
24
- include Riffer::Messages::Converter
25
- extend Riffer::Helpers::ClassNameConverter
26
-
27
20
  INTERRUPT_MAX_STEPS = :max_steps #: Symbol
28
21
 
29
- # Returns the per-class Riffer::Agent::Config value object holding every
30
- # DSL setting. Lazily initialized on first read; each subclass has its own.
31
- #
22
+ # Returns the per-class Riffer::Agent::Config holding every DSL setting.
32
23
  #--
33
24
  #: () -> Riffer::Agent::Config
34
25
  def self.config
@@ -40,10 +31,10 @@ class Riffer::Agent
40
31
  #--
41
32
  #: (?String?) -> String
42
33
  def self.identifier(value = nil)
43
- value.nil? ? (config.identifier || class_name_to_path(name)) : (config.identifier = value)
34
+ value.nil? ? (config.identifier || Riffer::Helpers::ClassNameConverter.convert(name)) : (config.identifier = value)
44
35
  end
45
36
 
46
- # Gets or sets the model string (e.g., "openai/gpt-4o") or Proc.
37
+ # Gets or sets the model string (e.g., "openai/gpt-4o").
47
38
  #
48
39
  #--
49
40
  #: (?(String | Proc)?) -> (String | Proc)?
@@ -51,10 +42,7 @@ class Riffer::Agent
51
42
  value.nil? ? config.model : (config.model = value)
52
43
  end
53
44
 
54
- # Gets or sets the agent instructions.
55
- #
56
- # Accepts a static string or a Proc for dynamic instructions.
57
- # When a Proc is given, it is called at generate time and receives
45
+ # Gets or sets the agent instructions. A Proc is called at generate time with
58
46
  # the +context+ hash (which may be +nil+).
59
47
  #
60
48
  # instructions "You are a helpful assistant."
@@ -86,9 +74,6 @@ class Riffer::Agent
86
74
  end
87
75
 
88
76
  # Gets or sets the structured output schema for this agent.
89
- #
90
- # Accepts a Riffer::Params instance or a block evaluated against a new Params.
91
- #
92
77
  #--
93
78
  #: (?Riffer::Params?) ?{ (Riffer::Params) [self: Riffer::Params] -> void } -> Riffer::Params?
94
79
  def self.structured_output(params = nil, &block)
@@ -101,10 +86,8 @@ class Riffer::Agent
101
86
  end
102
87
 
103
88
  # Gets or sets the maximum number of LLM call steps in the tool-use loop.
104
- #
105
- # Defaults to Riffer::Agent::Config::DEFAULT_MAX_STEPS (16). Set to +nil+
106
- # for unlimited steps. The splat distinguishes a getter call (no argument)
107
- # from setting the limit to +nil+.
89
+ # The splat distinguishes a getter (no argument) from setting the limit to
90
+ # +nil+ (unlimited); it defaults to Riffer::Agent::Config::DEFAULT_MAX_STEPS.
108
91
  #
109
92
  # max_steps # reads the current limit
110
93
  # max_steps 8 # cap the loop at 8 steps
@@ -125,14 +108,12 @@ class Riffer::Agent
125
108
  value.nil? ? config.tools_config : (config.tools_config = value)
126
109
  end
127
110
 
128
- # Opts this agent into tools from all MCP registrations that share any of
129
- # the given tag(s).
130
- #
131
- # +tag+ - a String or Symbol; matched against registration manifest tags.
111
+ # Opts this agent into MCP tools from registrations matching the given tag.
112
+ # Progressive registrations expose +mcp_search+ instead of every schema up front.
132
113
  #
133
- #: (String | Symbol) -> void
134
- def self.use_mcp(tag)
135
- config.add_mcp(tag)
114
+ #: (String | Symbol, ?progressive: bool) -> void
115
+ def self.use_mcp(tag, progressive: true)
116
+ config.add_mcp(tag, progressive: progressive)
136
117
  end
137
118
 
138
119
  # Returns the accumulated +use_mcp+ configurations for this agent class.
@@ -142,20 +123,16 @@ class Riffer::Agent
142
123
  config.mcp_configs
143
124
  end
144
125
 
145
- # Gets or sets the tool runtime for this agent.
146
- #
147
- # Accepts a Riffer::Tools::Runtime subclass, a Riffer::Tools::Runtime instance,
148
- # or a Proc. Defaults to <tt>Riffer.config.tool_runtime</tt> when unset.
149
- #
126
+ # Gets or sets the tool runtime for this agent; defaults to
127
+ # <tt>Riffer.config.tool_runtime</tt> when unset.
150
128
  #--
151
129
  #: (?(singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)?) -> (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)
152
130
  def self.tool_runtime(value = nil)
153
131
  value.nil? ? config.tool_runtime : (config.tool_runtime = value)
154
132
  end
155
133
 
156
- # Configures skills for this agent via a block DSL.
157
- #
158
- # Returns the current Riffer::Skills::Config when called without a block.
134
+ # Configures skills for this agent via a block DSL, or returns the current
135
+ # Riffer::Skills::Config when called without a block.
159
136
  #
160
137
  # skills do
161
138
  # backend Riffer::Skills::FilesystemBackend.new(".skills")
@@ -191,10 +168,6 @@ class Riffer::Agent
191
168
  end
192
169
 
193
170
  # Generates a response using a new agent instance.
194
- #
195
- # +context:+ is threaded into +new+; +prompt+ and +files:+ are forwarded
196
- # to +#generate+.
197
- #
198
171
  #--
199
172
  #: (?String?, ?files: Array[Hash[Symbol, untyped] | Riffer::Messages::FilePart]?, ?context: Hash[Symbol, untyped]?) -> Riffer::Agent::Response
200
173
  def self.generate(prompt = nil, files: nil, context: nil)
@@ -202,10 +175,6 @@ class Riffer::Agent
202
175
  end
203
176
 
204
177
  # Streams a response using a new agent instance.
205
- #
206
- # +context:+ is threaded into +new+; +prompt+ and +files:+ are forwarded
207
- # to +#stream+.
208
- #
209
178
  #--
210
179
  #: (?String?, ?files: Array[Hash[Symbol, untyped] | Riffer::Messages::FilePart]?, ?context: Hash[Symbol, untyped]?) -> Enumerator[Riffer::StreamEvents::Base, void]
211
180
  def self.stream(prompt = nil, files: nil, context: nil)
@@ -213,11 +182,6 @@ class Riffer::Agent
213
182
  end
214
183
 
215
184
  # Reconstructs a runnable agent from a wire hash produced by +#to_h+.
216
- #
217
- # Delegates to Riffer::Agent::Serializer.from_h. See it for the +session+
218
- # seed, the +tool_resolver+ / +tool_runtime+ injection points, and what does
219
- # not transfer.
220
- #
221
185
  #--
222
186
  #: (Hash[Symbol, untyped], ?context: Hash[Symbol, untyped]?, ?session: Riffer::Agent::Session?, ?tool_resolver: ^(Hash[Symbol, untyped]) -> singleton(Riffer::Tool), ?tool_runtime: (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)?) -> Riffer::Agent
223
187
  def self.from_h(hash, context: nil, session: nil, tool_resolver: Riffer::Agent::Serializer::DEFAULT_TOOL_RESOLVER, tool_runtime: nil)
@@ -225,24 +189,14 @@ class Riffer::Agent
225
189
  end
226
190
 
227
191
  # Reconstructs a runnable agent from a JSON string produced by +#to_json+.
228
- #
229
- # Delegates to Riffer::Agent::Serializer.from_json, which parses the JSON
230
- # (with symbol keys) for you. See Riffer::Agent::Serializer.from_h for the
231
- # +session+ seed and the +tool_resolver+ / +tool_runtime+ injection points.
232
- #
233
192
  #--
234
193
  #: (String, ?context: Hash[Symbol, untyped]?, ?session: Riffer::Agent::Session?, ?tool_resolver: ^(Hash[Symbol, untyped]) -> singleton(Riffer::Tool), ?tool_runtime: (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)?) -> Riffer::Agent
235
194
  def self.from_json(json, context: nil, session: nil, tool_resolver: Riffer::Agent::Serializer::DEFAULT_TOOL_RESOLVER, tool_runtime: nil)
236
195
  Riffer::Agent::Serializer.from_json(json, context: context, session: session, tool_resolver: tool_resolver, tool_runtime: tool_runtime)
237
196
  end
238
197
 
239
- # Registers a guardrail for input, output, or both phases.
240
- #
241
- # [phase] :before, :after, or :around.
242
- # [with] the guardrail class (must be subclass of Riffer::Guardrail).
243
- # [options] additional options passed to the guardrail.
244
- #
245
- # Raises Riffer::ArgumentError if phase is invalid or guardrail is not a Guardrail class.
198
+ # Registers a guardrail for input, output, or both phases. Raises
199
+ # Riffer::ArgumentError unless +phase+ is :before, :after, or :around.
246
200
  #--
247
201
  #: (Symbol, with: singleton(Riffer::Guardrail), **untyped) -> void
248
202
  def self.guardrail(phase, with:, **options)
@@ -250,92 +204,60 @@ class Riffer::Agent
250
204
  end
251
205
 
252
206
  # Returns the registered guardrail configs for a given phase.
253
- #
254
- # [phase] :before or :after.
255
- #
256
207
  #--
257
208
  #: (Symbol) -> Array[Hash[Symbol, untyped]]
258
209
  def self.guardrails_for(phase)
259
210
  config.guardrails_for(phase)
260
211
  end
261
212
 
262
- # The conversation handle. See Riffer::Agent::Session.
213
+ # The conversation handle.
263
214
  attr_reader :session #: Riffer::Agent::Session
264
215
 
265
- # The per-instance Riffer::Agent::Config. Either the class-level default or
266
- # an explicit Config passed to +Agent.new(config:)+.
216
+ # The per-instance Riffer::Agent::Config.
267
217
  attr_reader :config #: Riffer::Agent::Config
268
218
 
269
- # The system message built from the configured +instructions+, or +nil+
270
- # when no instructions are configured. Built once at +Agent.new+ using the
271
- # constructor +context:+ and cached. Useful for persistence flows.
219
+ # The system message built from the configured +instructions+, or +nil+ when
220
+ # none are configured.
272
221
  attr_reader :instruction_message #: Riffer::Messages::System?
273
222
 
274
- # The system message describing the configured skills catalog, or +nil+
275
- # when skills are unconfigured or the catalog is empty. Built once at
276
- # +Agent.new+ and cached.
223
+ # The system message describing the configured skills catalog, or +nil+ when
224
+ # skills are unconfigured or the catalog is empty.
277
225
  attr_reader :skills_message #: Riffer::Messages::System?
278
226
 
279
- # The mutable runtime context, a +Riffer::Agent::Context+ value object
280
- # threaded into every Proc-based DSL setting, guardrail, tool runtime,
281
- # and skills resolution, and shared with every +Riffer::Agent::Run+
282
- # this agent executes. Exposes:
283
- #
284
- # - +context.skills+ — the resolved +Riffer::Skills::Context+ (when
285
- # skills are configured), set at +Agent.new+ time.
286
- # - +context.token_usage+ — the cumulative +Riffer::Providers::TokenUsage+,
287
- # updated by each Run as the loop progresses.
288
- # - +context[:key]+ / <tt>context.dig(:key)</tt> — Hash-style reads for
289
- # caller-provided keys (e.g. <tt>context[:agent]</tt>,
290
- # <tt>context[:tenant]</tt>). +:skills+ and +:token_usage+ are
291
- # reserved and cannot be passed by the caller.
227
+ # The mutable runtime context shared with every +Riffer::Agent::Run+ this
228
+ # agent executes and threaded through all Proc-based settings.
292
229
  attr_reader :context #: Riffer::Agent::Context
293
230
 
294
- # The resolved provider name (the part before "provider/"), e.g. +"openai"+.
295
- # Resolved eagerly at +Agent.new+ alongside +model_name+; together they
296
- # form the provider-neutral model identifier the agent serializes.
231
+ # The resolved provider name (the part before "/" in the model string),
232
+ # e.g. +"openai"+.
297
233
  attr_reader :provider_name #: String
298
234
 
299
- # The resolved model name (the part after "provider/"), used as the model
300
- # argument on every LLM call. Resolved eagerly at +Agent.new+.
235
+ # The resolved model name (the part after "/" in the model string), used as
236
+ # the model argument on every LLM call.
301
237
  attr_reader :model_name #: String
302
238
 
303
- # The provider client. Built eagerly at +Agent.new+ from the configured
304
- # provider class and +Config#provider_options+, then handed to every
305
- # +Riffer::Agent::Run+ this agent executes. Public so tests can pre-queue
306
- # responses on +Riffer::Providers::Mock+ before calling +#generate+.
239
+ # The provider client. Public so tests can pre-queue responses on
240
+ # +Riffer::Providers::Mock+ before calling +#generate+.
307
241
  attr_reader :provider #: Riffer::Providers::Base
308
242
 
309
- # The +Riffer::Agent::StructuredOutput+ wrapping the configured schema, or +nil+
310
- # when structured output is not configured. Resolved eagerly at +Agent.new+.
243
+ # The +Riffer::Agent::StructuredOutput+ wrapping the configured schema, or
244
+ # +nil+ when not configured.
311
245
  attr_reader :structured_output #: Riffer::Agent::StructuredOutput?
312
246
 
313
- # The tool classes the LLM sees on every call this agent makes. Resolved
314
- # eagerly at +Agent.new+ (Proc-form +uses_tools+ is called against
315
- # +context+ once; MCP tools and the skill_activate tool are merged in).
247
+ # The tool classes the LLM sees on every call this agent makes.
316
248
  attr_reader :tools #: Array[singleton(Riffer::Tool)]
317
249
 
318
- # The tool runtime instance used to execute tool calls. Resolved eagerly
319
- # at +Agent.new+ (Proc-form +tool_runtime+ is called against +context+ once).
250
+ # The tool runtime instance used to execute tool calls.
320
251
  attr_reader :tool_runtime #: Riffer::Tools::Runtime
321
252
 
322
253
  # Initializes a new agent.
323
254
  #
324
- # When +session:+ is omitted, a fresh +Riffer::Agent::Session+ is built and seeded
325
- # with the system instruction message and skills catalog (when configured),
326
- # using +context:+. When +session:+ is provided, the agent uses it as-is —
327
- # the caller is responsible for the session's contents (typical use case:
328
- # cross-process resume from persisted history). With
329
- # +Riffer.config.experimental_history_healing+ on, a provided session is
330
- # healed at construction time so the +tool_use+ ↔ +tool_result+ invariant
331
- # holds before the next inference call.
255
+ # A provided +session:+ is used as-is the caller owns its contents (e.g.
256
+ # cross-process resume from persisted history); an omitted one is seeded with
257
+ # the instruction and skills messages.
332
258
  #
333
- # +context:+ flows through Proc-based instructions, model, skills resolution,
334
- # tool resolution, guardrails, and tool runtime. It is fixed for the
335
- # lifetime of the agent.
336
- #
337
- # Raises Riffer::ArgumentError if the configured model string is invalid
338
- # (must be "provider/model" format).
259
+ # Raises Riffer::ArgumentError unless the configured model string is
260
+ # "provider/model" format.
339
261
  #
340
262
  #--
341
263
  #: (?session: Riffer::Agent::Session?, ?context: Hash[Symbol, untyped]?, ?config: Riffer::Agent::Config?) -> void
@@ -361,15 +283,10 @@ class Riffer::Agent
361
283
 
362
284
  # Generates a response from the agent.
363
285
  #
364
- # Runs the inference loop via +Riffer::Agent::Run.generate+. When +prompt+
365
- # is given, a new +Riffer::Messages::User+ is appended to the session
366
- # (silently +on_message+ does not fire for user inputs) and then the
367
- # loop runs. When +prompt+ is omitted, the loop runs against the current
368
- # session — useful for resuming a persisted conversation whose last turn
369
- # is already a user message, or for picking up pending tool calls after
370
- # an interrupt.
371
- #
372
- # +files:+ requires +prompt+. Pass files to attach to the new user message.
286
+ # With +prompt+, a new user message is appended (silently — +on_message+ does
287
+ # not fire for user inputs) before the loop runs. Without it, the loop runs
288
+ # against the current session, resuming a persisted conversation or pending
289
+ # tool calls. +files:+ requires +prompt+.
373
290
  #
374
291
  #--
375
292
  #: (?String?, ?files: Array[Hash[Symbol, untyped] | Riffer::Messages::FilePart]?) -> Riffer::Agent::Response
@@ -377,15 +294,11 @@ class Riffer::Agent
377
294
  Riffer::Agent::Run.generate(agent: self, prompt: prompt, files: files)
378
295
  end
379
296
 
380
- # Streams a response from the agent.
381
- #
382
- # Runs the inference loop via +Riffer::Agent::Run.stream+, returning an
383
- # +Enumerator+ of +Riffer::StreamEvents+.
297
+ # Streams a response from the agent, returning an +Enumerator+ of
298
+ # +Riffer::StreamEvents+. See +#generate+ for prompt/files semantics.
384
299
  #
385
300
  # Raises Riffer::ArgumentError if structured output is configured.
386
301
  #
387
- # See +#generate+ for prompt/files semantics.
388
- #
389
302
  #--
390
303
  #: (?String?, ?files: Array[Hash[Symbol, untyped] | Riffer::Messages::FilePart]?) -> Enumerator[Riffer::StreamEvents::Base, void]
391
304
  def stream(prompt = nil, files: nil)
@@ -393,37 +306,24 @@ class Riffer::Agent
393
306
  Riffer::Agent::Run.stream(agent: self, prompt: prompt, files: files)
394
307
  end
395
308
 
396
- # Interrupts the agent loop.
397
- #
398
- # Call from an +on_message+ callback to cleanly interrupt the loop.
399
- # Equivalent to <tt>throw :riffer_interrupt, reason</tt>.
400
- #
401
- # When +Riffer.config.experimental_history_healing+ is enabled, riffer
402
- # fills any orphaned +tool_use+ on the way out with a placeholder
403
- # +Riffer::Messages::Tool+ carrying +error_type: :interrupted+. The
404
- # filled call_ids are exposed on
405
- # +Riffer::Agent::Response#healed_tool_call_ids+ (and the streaming
406
- # +Riffer::StreamEvents::Interrupt+ event).
407
- #
309
+ # Interrupts the agent loop from an +on_message+ callback. Equivalent to
310
+ # <tt>throw :riffer_interrupt, reason</tt>.
408
311
  #--
409
312
  #: (?(String | Symbol)?) -> void
410
313
  def interrupt!(reason = nil)
411
314
  throw :riffer_interrupt, reason
412
315
  end
413
316
 
414
- # Snapshots this resolved agent into a self-contained, provider-neutral
415
- # wire hash. Delegates to Riffer::Agent::Serializer.to_h.
416
- #
317
+ # Snapshots this resolved agent into a self-contained, provider-neutral wire
318
+ # hash.
417
319
  #--
418
320
  #: () -> Hash[Symbol, untyped]
419
321
  def to_h
420
322
  Riffer::Agent::Serializer.to_h(agent: self)
421
323
  end
422
324
 
423
- # Snapshots this resolved agent into a wire JSON string. Delegates to
424
- # Riffer::Agent::Serializer.to_json. The +*+ absorbs the JSON generator
425
- # state argument so <tt>JSON.generate(agent)</tt> works too.
426
- #
325
+ # Snapshots this resolved agent into a wire JSON string. The +*+ absorbs the
326
+ # JSON generator state argument so <tt>JSON.generate(agent)</tt> works too.
427
327
  #--
428
328
  #: (*untyped) -> String
429
329
  def to_json(*)
@@ -448,12 +348,6 @@ class Riffer::Agent
448
348
  Riffer::Messages::System.new(skills.system_prompt)
449
349
  end
450
350
 
451
- # Resolves +Config#model+ to a "provider/model" string (calling the Proc
452
- # form against +@context+) and parses it.
453
- #
454
- # Returns +[provider_name, model_name]+. Raises Riffer::ArgumentError on an
455
- # invalid model string.
456
- #
457
351
  #--
458
352
  #: () -> [String, String]
459
353
  def resolve_provider_and_model
@@ -469,11 +363,6 @@ class Riffer::Agent
469
363
  [provider_name, model_name]
470
364
  end
471
365
 
472
- # Builds the provider client from the resolved +@provider_name+ and the
473
- # configured +provider_options+.
474
- #
475
- # Raises Riffer::ArgumentError on an unregistered provider.
476
- #
477
366
  #--
478
367
  #: () -> Riffer::Providers::Base
479
368
  def build_provider
@@ -482,9 +371,6 @@ class Riffer::Agent
482
371
  provider_class.new(**@config.provider_options)
483
372
  end
484
373
 
485
- # Resolves the skills backend, lists skills, and selects an adapter.
486
- # Returns nil if skills are unconfigured or the backend is empty.
487
- #
488
374
  #--
489
375
  #: () -> Riffer::Skills::Context?
490
376
  def resolve_skills
@@ -522,19 +408,6 @@ class Riffer::Agent
522
408
  params ? Riffer::Agent::StructuredOutput.new(params) : nil
523
409
  end
524
410
 
525
- # Resolves the full tool catalog for the agent:
526
- #
527
- # - The configured +uses_tools+ value (Proc-form resolved against +context+).
528
- # - The skill activation tool, when a +skills+ block is configured. The
529
- # activation tool class comes from the per-agent +skills do; activate_tool ...; end+
530
- # override when set, otherwise from +Riffer.config.skills.default_activate_tool+.
531
- # - All MCP tools matching any +use_mcp+ tag, optionally wrapped in
532
- # AuthenticatedTool when +Riffer.config.mcp.credentials+ is configured.
533
- #
534
- # Raises Riffer::ArgumentError on tool name conflicts with the skill
535
- # activation tool, on duplicate tool names across sources, or on tool
536
- # classes missing required metadata (description, params).
537
- #
538
411
  #--
539
412
  #: () -> Array[singleton(Riffer::Tool)]
540
413
  def resolve_tools
@@ -573,25 +446,35 @@ class Riffer::Agent
573
446
 
574
447
  cred = Riffer.config.mcp.credentials
575
448
  ctx = @context
576
- gather_mcp_registrations_with_tags(configs).flat_map do |reg, tag_accum|
577
- matched_tags = tag_accum.uniq
578
- mcp_tools_for_registration(reg, matched_tags, cred, ctx)
449
+
450
+ regular_reg_tags, progressive_reg_tags = gather_mcp_registrations_with_tags(configs)
451
+
452
+ regular_tools = regular_reg_tags.flat_map { |reg, tag_accum| mcp_tools_for_registration(reg, tag_accum.uniq, cred, ctx) }
453
+ progressive_tools = progressive_reg_tags.flat_map { |reg, tag_accum| mcp_tools_for_registration(reg, tag_accum.uniq, cred, ctx) }
454
+
455
+ if progressive_tools.any?
456
+ @context.mcp_progressive_tools = progressive_tools.freeze
457
+ regular_tools + [Riffer::Mcp::SearchTool]
458
+ else
459
+ regular_tools
579
460
  end
580
461
  end
581
462
 
582
- # Each matching MCP registration once, with tag symbols unioned across +use_mcp+ rows.
583
- #
584
- #: (Array[Hash[Symbol, untyped]]) -> Hash[Riffer::Mcp::Registration, Array[Symbol]]
463
+ #--
464
+ #: (Array[Hash[Symbol, untyped]]) -> [Hash[Riffer::Mcp::Registration, Array[Symbol]], Hash[Riffer::Mcp::Registration, Array[Symbol]]]
585
465
  def gather_mcp_registrations_with_tags(configs)
586
- by_reg = {} #: Hash[Riffer::Mcp::Registration, Array[Symbol]]
466
+ regular = {} #: Hash[Riffer::Mcp::Registration, Array[Symbol]]
467
+ progressive = {} #: Hash[Riffer::Mcp::Registration, Array[Symbol]]
587
468
  configs.each do |cfg|
469
+ target = cfg[:progressive] ? progressive : regular
588
470
  Riffer::Mcp::Registry.find_by_tags(cfg[:tags]).each do |reg|
589
- (by_reg[reg] ||= []).concat(cfg[:tags] & reg.manifest.tags)
471
+ (target[reg] ||= []).concat(cfg[:tags] & reg.manifest.tags)
590
472
  end
591
473
  end
592
- by_reg
474
+ [regular, progressive]
593
475
  end
594
476
 
477
+ #--
595
478
  #: (Riffer::Mcp::Registration, Array[Symbol], (^(manifest: Riffer::Mcp::Manifest, matched_tags: Array[Symbol], context: Riffer::Agent::Context) -> Hash[Symbol, untyped]?)?, Riffer::Agent::Context) -> Array[singleton(Riffer::Tool)]
596
479
  def mcp_tools_for_registration(reg, matched_tags, cred, ctx)
597
480
  return reg.tools unless cred
@@ -599,8 +482,7 @@ class Riffer::Agent
599
482
  Riffer::Mcp::AuthenticatedTool.wrap_all(reg.tools, reg.manifest, matched_tags)
600
483
  end
601
484
 
602
- # Raises if two or more tool classes share the same +.name+ (ambiguous dispatch).
603
- #
485
+ #--
604
486
  #: (Array[singleton(Riffer::Tool)]) -> void
605
487
  def assert_distinct_tool_names!(tool_classes)
606
488
  tally = Hash.new(0) #: Hash[String, Integer]