riffer 0.48.0 → 0.50.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 (262) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/rules/comments.md +2 -4
  3. data/.claude/rules/rbs-inline.md +2 -12
  4. data/.release-please-manifest.json +1 -1
  5. data/.rubocop.yml +5 -0
  6. data/CHANGELOG.md +32 -0
  7. data/docs/AGENTS.md +3 -5
  8. data/docs/AGENT_LIFECYCLE.md +13 -16
  9. data/docs/CONFIGURATION.md +22 -33
  10. data/docs/STREAM_EVENTS.md +3 -3
  11. data/docs/TOOL_ADVANCED.md +1 -3
  12. data/docs/TRACING.md +1 -1
  13. data/docs/providers/AMAZON_BEDROCK.md +31 -0
  14. data/docs/providers/ANTHROPIC.md +10 -0
  15. data/docs/providers/AZURE_OPENAI.md +2 -0
  16. data/docs/providers/OPENAI.md +22 -0
  17. data/docs/providers/OPENROUTER.md +18 -1
  18. data/docs-site/build.rb +0 -6
  19. data/docs-site/check.rb +1 -5
  20. data/lib/riffer/agent/config.rb +4 -45
  21. data/lib/riffer/agent/context.rb +2 -26
  22. data/lib/riffer/agent/outcome.rb +0 -20
  23. data/lib/riffer/agent/response.rb +0 -38
  24. data/lib/riffer/agent/run.rb +11 -43
  25. data/lib/riffer/agent/serializer.rb +10 -39
  26. data/lib/riffer/agent/session/repair.rb +3 -15
  27. data/lib/riffer/agent/session.rb +25 -38
  28. data/lib/riffer/agent/structured_output/result.rb +0 -8
  29. data/lib/riffer/agent/structured_output.rb +0 -7
  30. data/lib/riffer/agent.rb +10 -130
  31. data/lib/riffer/config/amazon_bedrock.rb +30 -0
  32. data/lib/riffer/config/anthropic.rb +21 -0
  33. data/lib/riffer/config/azure_open_ai.rb +30 -0
  34. data/lib/riffer/config/evals.rb +18 -0
  35. data/lib/riffer/config/files.rb +61 -0
  36. data/lib/riffer/config/gemini.rb +21 -0
  37. data/lib/riffer/config/mcp.rb +31 -0
  38. data/lib/riffer/config/open_ai.rb +30 -0
  39. data/lib/riffer/config/open_router.rb +21 -0
  40. data/lib/riffer/config/pricing/rates.rb +36 -0
  41. data/lib/riffer/config/pricing.rb +64 -0
  42. data/lib/riffer/config/skills.rb +38 -0
  43. data/lib/riffer/config/tracing.rb +43 -0
  44. data/lib/riffer/config.rb +3 -362
  45. data/lib/riffer/evals/evaluator.rb +1 -29
  46. data/lib/riffer/evals/evaluator_runner.rb +0 -15
  47. data/lib/riffer/evals/judge.rb +0 -8
  48. data/lib/riffer/evals/result.rb +1 -11
  49. data/lib/riffer/evals/run_result.rb +0 -12
  50. data/lib/riffer/evals/scenario_result.rb +0 -19
  51. data/lib/riffer/files/downloader.rb +2 -3
  52. data/lib/riffer/files/resolver.rb +2 -7
  53. data/lib/riffer/guardrail.rb +2 -22
  54. data/lib/riffer/guardrails/modification.rb +0 -8
  55. data/lib/riffer/guardrails/result.rb +0 -17
  56. data/lib/riffer/guardrails/runner.rb +2 -11
  57. data/lib/riffer/guardrails/tripwire.rb +0 -8
  58. data/lib/riffer/guardrails.rb +0 -2
  59. data/lib/riffer/helpers/boolean.rb +0 -4
  60. data/lib/riffer/helpers/call_or_value.rb +0 -3
  61. data/lib/riffer/helpers/deep_dup.rb +3 -8
  62. data/lib/riffer/helpers/dependencies.rb +0 -4
  63. data/lib/riffer/helpers/identifier.rb +3 -12
  64. data/lib/riffer/helpers/validate.rb +42 -0
  65. data/lib/riffer/mcp/authenticated_tool.rb +4 -12
  66. data/lib/riffer/mcp/client.rb +0 -7
  67. data/lib/riffer/mcp/manifest.rb +3 -7
  68. data/lib/riffer/mcp/registration.rb +0 -10
  69. data/lib/riffer/mcp/registry.rb +0 -9
  70. data/lib/riffer/mcp/search_tool.rb +0 -4
  71. data/lib/riffer/mcp/tool.rb +1 -4
  72. data/lib/riffer/mcp/tool_factory.rb +3 -6
  73. data/lib/riffer/mcp.rb +2 -23
  74. data/lib/riffer/messages/assistant/reasoning_part.rb +4 -21
  75. data/lib/riffer/messages/assistant/tool_call.rb +1 -9
  76. data/lib/riffer/messages/assistant.rb +1 -21
  77. data/lib/riffer/messages/base.rb +0 -12
  78. data/lib/riffer/messages/system.rb +0 -3
  79. data/lib/riffer/messages/tool.rb +0 -15
  80. data/lib/riffer/messages/user/file_part.rb +2 -30
  81. data/lib/riffer/messages/user.rb +0 -4
  82. data/lib/riffer/params/boolean.rb +1 -5
  83. data/lib/riffer/params/param.rb +3 -31
  84. data/lib/riffer/params.rb +8 -41
  85. data/lib/riffer/providers/amazon_bedrock.rb +98 -46
  86. data/lib/riffer/providers/anthropic.rb +65 -65
  87. data/lib/riffer/providers/azure_open_ai.rb +10 -8
  88. data/lib/riffer/providers/base.rb +7 -28
  89. data/lib/riffer/providers/finish_reason.rb +0 -6
  90. data/lib/riffer/providers/gemini/client.rb +4 -17
  91. data/lib/riffer/providers/gemini.rb +4 -10
  92. data/lib/riffer/providers/mock.rb +1 -26
  93. data/lib/riffer/providers/open_ai.rb +94 -46
  94. data/lib/riffer/providers/open_router.rb +105 -30
  95. data/lib/riffer/providers/repository.rb +2 -14
  96. data/lib/riffer/providers/token_usage.rb +5 -14
  97. data/lib/riffer/registrable.rb +11 -45
  98. data/lib/riffer/runner/fibers.rb +1 -6
  99. data/lib/riffer/runner/sequential.rb +0 -1
  100. data/lib/riffer/runner/threaded.rb +0 -3
  101. data/lib/riffer/runner.rb +0 -3
  102. data/lib/riffer/skills/activate_tool.rb +0 -3
  103. data/lib/riffer/skills/adapter.rb +0 -8
  104. data/lib/riffer/skills/backend.rb +2 -7
  105. data/lib/riffer/skills/config.rb +4 -20
  106. data/lib/riffer/skills/context.rb +0 -29
  107. data/lib/riffer/skills/filesystem_backend.rb +0 -7
  108. data/lib/riffer/skills/frontmatter.rb +2 -16
  109. data/lib/riffer/skills/markdown_adapter.rb +3 -6
  110. data/lib/riffer/skills/xml_adapter.rb +0 -3
  111. data/lib/riffer/stream_events/base.rb +0 -3
  112. data/lib/riffer/stream_events/finish_reason_done.rb +1 -6
  113. data/lib/riffer/stream_events/guardrail_modification.rb +0 -10
  114. data/lib/riffer/stream_events/guardrail_tripwire.rb +0 -10
  115. data/lib/riffer/stream_events/interrupt.rb +2 -12
  116. data/lib/riffer/stream_events/reasoning_delta.rb +0 -3
  117. data/lib/riffer/stream_events/reasoning_done.rb +1 -5
  118. data/lib/riffer/stream_events/skill_activation.rb +0 -3
  119. data/lib/riffer/stream_events/text_delta.rb +0 -2
  120. data/lib/riffer/stream_events/text_done.rb +0 -2
  121. data/lib/riffer/stream_events/token_usage_done.rb +0 -2
  122. data/lib/riffer/stream_events/tool_call_delta.rb +1 -5
  123. data/lib/riffer/stream_events/tool_call_done.rb +0 -5
  124. data/lib/riffer/stream_events/web_search_done.rb +0 -3
  125. data/lib/riffer/stream_events/web_search_status.rb +1 -5
  126. data/lib/riffer/testing/minitest.rb +4 -5
  127. data/lib/riffer/testing.rb +5 -38
  128. data/lib/riffer/tool.rb +2 -28
  129. data/lib/riffer/tools/response.rb +3 -32
  130. data/lib/riffer/tools/runtime/fibers.rb +0 -6
  131. data/lib/riffer/tools/runtime/inline.rb +0 -1
  132. data/lib/riffer/tools/runtime/threaded.rb +0 -6
  133. data/lib/riffer/tools/runtime.rb +5 -26
  134. data/lib/riffer/tools/toolable.rb +0 -37
  135. data/lib/riffer/tracing/capture.rb +3 -5
  136. data/lib/riffer/tracing/no_op.rb +0 -7
  137. data/lib/riffer/tracing/otel.rb +7 -16
  138. data/lib/riffer/tracing/stream_recorder.rb +0 -7
  139. data/lib/riffer/tracing.rb +4 -27
  140. data/lib/riffer/version.rb +1 -1
  141. data/lib/riffer.rb +2 -30
  142. data/sig/generated/riffer/agent/config.rbs +12 -48
  143. data/sig/generated/riffer/agent/context.rbs +2 -26
  144. data/sig/generated/riffer/agent/outcome.rbs +0 -20
  145. data/sig/generated/riffer/agent/response.rbs +1 -29
  146. data/sig/generated/riffer/agent/run.rbs +1 -27
  147. data/sig/generated/riffer/agent/serializer.rbs +2 -27
  148. data/sig/generated/riffer/agent/session/repair.rbs +2 -10
  149. data/sig/generated/riffer/agent/session.rbs +10 -36
  150. data/sig/generated/riffer/agent/structured_output/result.rbs +0 -7
  151. data/sig/generated/riffer/agent/structured_output.rbs +0 -7
  152. data/sig/generated/riffer/agent.rbs +5 -121
  153. data/sig/generated/riffer/config/amazon_bedrock.rbs +21 -0
  154. data/sig/generated/riffer/config/anthropic.rbs +15 -0
  155. data/sig/generated/riffer/config/azure_open_ai.rbs +21 -0
  156. data/sig/generated/riffer/config/evals.rbs +13 -0
  157. data/sig/generated/riffer/config/files.rbs +43 -0
  158. data/sig/generated/riffer/config/gemini.rbs +15 -0
  159. data/sig/generated/riffer/config/mcp.rbs +19 -0
  160. data/sig/generated/riffer/config/open_ai.rbs +21 -0
  161. data/sig/generated/riffer/config/open_router.rbs +15 -0
  162. data/sig/generated/riffer/config/pricing/rates.rbs +19 -0
  163. data/sig/generated/riffer/config/pricing.rbs +31 -0
  164. data/sig/generated/riffer/config/skills.rbs +19 -0
  165. data/sig/generated/riffer/config/tracing.rbs +25 -0
  166. data/sig/generated/riffer/config.rbs +2 -307
  167. data/sig/generated/riffer/evals/evaluator.rbs +0 -28
  168. data/sig/generated/riffer/evals/evaluator_runner.rbs +0 -14
  169. data/sig/generated/riffer/evals/judge.rbs +0 -7
  170. data/sig/generated/riffer/evals/result.rbs +1 -11
  171. data/sig/generated/riffer/evals/run_result.rbs +0 -12
  172. data/sig/generated/riffer/evals/scenario_result.rbs +0 -14
  173. data/sig/generated/riffer/files/resolver.rbs +0 -7
  174. data/sig/generated/riffer/guardrail.rbs +2 -22
  175. data/sig/generated/riffer/guardrails/modification.rbs +0 -6
  176. data/sig/generated/riffer/guardrails/result.rbs +0 -15
  177. data/sig/generated/riffer/guardrails/runner.rbs +0 -11
  178. data/sig/generated/riffer/guardrails/tripwire.rbs +0 -8
  179. data/sig/generated/riffer/guardrails.rbs +0 -2
  180. data/sig/generated/riffer/helpers/boolean.rbs +0 -4
  181. data/sig/generated/riffer/helpers/call_or_value.rbs +0 -3
  182. data/sig/generated/riffer/helpers/deep_dup.rbs +0 -8
  183. data/sig/generated/riffer/helpers/dependencies.rbs +0 -4
  184. data/sig/generated/riffer/helpers/identifier.rbs +0 -12
  185. data/sig/generated/riffer/helpers/validate.rbs +21 -0
  186. data/sig/generated/riffer/mcp/authenticated_tool.rbs +0 -5
  187. data/sig/generated/riffer/mcp/client.rbs +0 -7
  188. data/sig/generated/riffer/mcp/manifest.rbs +3 -7
  189. data/sig/generated/riffer/mcp/registration.rbs +0 -10
  190. data/sig/generated/riffer/mcp/registry.rbs +0 -9
  191. data/sig/generated/riffer/mcp/search_tool.rbs +0 -4
  192. data/sig/generated/riffer/mcp/tool.rbs +0 -4
  193. data/sig/generated/riffer/mcp/tool_factory.rbs +0 -6
  194. data/sig/generated/riffer/mcp.rbs +2 -22
  195. data/sig/generated/riffer/messages/assistant/reasoning_part.rbs +4 -17
  196. data/sig/generated/riffer/messages/assistant/tool_call.rbs +1 -9
  197. data/sig/generated/riffer/messages/assistant.rbs +10 -30
  198. data/sig/generated/riffer/messages/base.rbs +0 -12
  199. data/sig/generated/riffer/messages/system.rbs +0 -3
  200. data/sig/generated/riffer/messages/tool.rbs +0 -12
  201. data/sig/generated/riffer/messages/user/file_part.rbs +0 -30
  202. data/sig/generated/riffer/messages/user.rbs +0 -4
  203. data/sig/generated/riffer/params/boolean.rbs +1 -4
  204. data/sig/generated/riffer/params/param.rbs +0 -31
  205. data/sig/generated/riffer/params.rbs +4 -40
  206. data/sig/generated/riffer/providers/amazon_bedrock.rbs +26 -26
  207. data/sig/generated/riffer/providers/anthropic.rbs +23 -22
  208. data/sig/generated/riffer/providers/azure_open_ai.rbs +5 -8
  209. data/sig/generated/riffer/providers/base.rbs +0 -28
  210. data/sig/generated/riffer/providers/finish_reason.rbs +0 -6
  211. data/sig/generated/riffer/providers/gemini/client.rbs +4 -17
  212. data/sig/generated/riffer/providers/gemini.rbs +0 -6
  213. data/sig/generated/riffer/providers/mock.rbs +0 -26
  214. data/sig/generated/riffer/providers/open_ai.rbs +36 -19
  215. data/sig/generated/riffer/providers/open_router.rbs +33 -14
  216. data/sig/generated/riffer/providers/repository.rbs +2 -14
  217. data/sig/generated/riffer/providers/token_usage.rbs +5 -14
  218. data/sig/generated/riffer/registrable.rbs +0 -45
  219. data/sig/generated/riffer/runner/fibers.rbs +0 -6
  220. data/sig/generated/riffer/runner/sequential.rbs +0 -1
  221. data/sig/generated/riffer/runner/threaded.rbs +0 -3
  222. data/sig/generated/riffer/runner.rbs +0 -3
  223. data/sig/generated/riffer/skills/activate_tool.rbs +0 -3
  224. data/sig/generated/riffer/skills/adapter.rbs +0 -8
  225. data/sig/generated/riffer/skills/backend.rbs +2 -7
  226. data/sig/generated/riffer/skills/config.rbs +0 -20
  227. data/sig/generated/riffer/skills/context.rbs +0 -29
  228. data/sig/generated/riffer/skills/filesystem_backend.rbs +0 -7
  229. data/sig/generated/riffer/skills/frontmatter.rbs +2 -16
  230. data/sig/generated/riffer/skills/markdown_adapter.rbs +0 -6
  231. data/sig/generated/riffer/skills/xml_adapter.rbs +0 -3
  232. data/sig/generated/riffer/stream_events/base.rbs +0 -3
  233. data/sig/generated/riffer/stream_events/finish_reason_done.rbs +1 -6
  234. data/sig/generated/riffer/stream_events/guardrail_modification.rbs +0 -10
  235. data/sig/generated/riffer/stream_events/guardrail_tripwire.rbs +0 -10
  236. data/sig/generated/riffer/stream_events/interrupt.rbs +2 -10
  237. data/sig/generated/riffer/stream_events/reasoning_delta.rbs +0 -3
  238. data/sig/generated/riffer/stream_events/reasoning_done.rbs +1 -5
  239. data/sig/generated/riffer/stream_events/skill_activation.rbs +0 -3
  240. data/sig/generated/riffer/stream_events/text_delta.rbs +0 -2
  241. data/sig/generated/riffer/stream_events/text_done.rbs +0 -2
  242. data/sig/generated/riffer/stream_events/token_usage_done.rbs +0 -2
  243. data/sig/generated/riffer/stream_events/tool_call_delta.rbs +1 -5
  244. data/sig/generated/riffer/stream_events/tool_call_done.rbs +0 -5
  245. data/sig/generated/riffer/stream_events/web_search_done.rbs +0 -3
  246. data/sig/generated/riffer/stream_events/web_search_status.rbs +1 -5
  247. data/sig/generated/riffer/testing.rbs +0 -38
  248. data/sig/generated/riffer/tool.rbs +0 -27
  249. data/sig/generated/riffer/tools/response.rbs +3 -29
  250. data/sig/generated/riffer/tools/runtime/fibers.rbs +0 -5
  251. data/sig/generated/riffer/tools/runtime/inline.rbs +0 -1
  252. data/sig/generated/riffer/tools/runtime/threaded.rbs +0 -5
  253. data/sig/generated/riffer/tools/runtime.rbs +2 -24
  254. data/sig/generated/riffer/tools/toolable.rbs +0 -37
  255. data/sig/generated/riffer/tracing/capture.rbs +2 -5
  256. data/sig/generated/riffer/tracing/no_op.rbs +0 -7
  257. data/sig/generated/riffer/tracing/otel.rbs +7 -16
  258. data/sig/generated/riffer/tracing/stream_recorder.rbs +0 -2
  259. data/sig/generated/riffer/tracing.rbs +4 -23
  260. data/sig/generated/riffer.rbs +2 -29
  261. data/sig/manual/riffer/helpers/validate.rbs +5 -0
  262. metadata +30 -1
@@ -1,46 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Typed configuration object holding every class-level DSL setting on a
5
- # Riffer::Agent subclass. Procs are stored unresolved and resolved per-instance
6
- # later.
7
4
  class Riffer::Agent::Config
8
5
  DEFAULT_MAX_STEPS = 16 #: Integer
9
6
 
10
7
  # @rbs @tool_runtime: (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)?
11
8
 
12
- # The configured agent identifier.
13
9
  attr_reader :identifier #: String? # @dynamic identifier
14
-
15
- # The configured model.
16
10
  attr_reader :model #: (String | Proc)? # @dynamic model
17
-
18
- # The configured instructions.
19
11
  attr_reader :instructions #: (String | Proc)? # @dynamic instructions
20
-
21
- # Options passed to generate_text/stream_text.
22
12
  attr_accessor :model_options #: Hash[Symbol, untyped] # @dynamic model_options, model_options=
23
-
24
- # The configured structured-output schema.
25
13
  attr_reader :structured_output #: Riffer::Params? # @dynamic structured_output
26
-
27
- # The maximum number of LLM call steps in the tool-use loop.
28
14
  attr_accessor :max_steps #: Numeric? # @dynamic max_steps, max_steps=
29
-
30
- # The configured tools.
31
15
  attr_accessor :tools_config #: (Array[singleton(Riffer::Tool)] | Proc)? # @dynamic tools_config, tools_config=
32
-
33
- # The accumulated +use_mcp+ tag configurations.
34
16
  attr_reader :mcp_configs #: Array[Hash[Symbol, untyped]] # @dynamic mcp_configs
35
-
36
- # The configured skills.
37
17
  attr_accessor :skills_config #: Riffer::Skills::Config? # @dynamic skills_config, skills_config=
38
-
39
- # Registered guardrail entries keyed by phase.
40
18
  attr_reader :guardrails #: Hash[Symbol, Array[Hash[Symbol, untyped]]] # @dynamic guardrails
41
19
 
42
- # Builds a new Config. Raises Riffer::ArgumentError if +model+ or
43
- # +instructions+ is invalid (e.g. an empty string).
44
20
  #--
45
21
  #: (
46
22
  # ?identifier: String?,
@@ -81,14 +57,12 @@ class Riffer::Agent::Config
81
57
  self.tool_runtime = tool_runtime if tool_runtime
82
58
  end
83
59
 
84
- # Sets +identifier+, coercing the value to String.
85
60
  #--
86
61
  #: (untyped) -> String?
87
62
  def identifier=(value)
88
63
  @identifier = value&.to_s
89
64
  end
90
65
 
91
- # Sets +structured_output+. Raises Riffer::ArgumentError on an invalid value.
92
66
  #--
93
67
  #: (Riffer::Params?) -> Riffer::Params?
94
68
  def structured_output=(value)
@@ -99,16 +73,14 @@ class Riffer::Agent::Config
99
73
  @structured_output = value
100
74
  end
101
75
 
102
- # Returns the declared tool runtime, or +Riffer.config.tool_runtime+ when none
103
- # was declared. Resolving the global here rather than at construction is what
104
- # lets a copy tell an inherited runtime from a defaulted one.
105
76
  #--
106
77
  #: () -> (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)
107
78
  def tool_runtime
79
+ # Resolved here rather than at construction so a copy can tell an inherited
80
+ # runtime from a defaulted one.
108
81
  @tool_runtime || Riffer.config.tool_runtime
109
82
  end
110
83
 
111
- # Sets +tool_runtime+. Raises Riffer::ArgumentError on an invalid value.
112
84
  #--
113
85
  #: ((singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)) -> (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)
114
86
  def tool_runtime=(value)
@@ -122,8 +94,6 @@ class Riffer::Agent::Config
122
94
  @tool_runtime = value
123
95
  end
124
96
 
125
- # Sets +model+. Raises Riffer::ArgumentError on an invalid value (e.g. an
126
- # empty string).
127
97
  #--
128
98
  #: ((String | Proc)?) -> (String | Proc)?
129
99
  def model=(value)
@@ -131,8 +101,6 @@ class Riffer::Agent::Config
131
101
  @model = value
132
102
  end
133
103
 
134
- # Sets +instructions+. Raises Riffer::ArgumentError on an invalid value (e.g.
135
- # an empty string).
136
104
  #--
137
105
  #: ((String | Proc)?) -> (String | Proc)?
138
106
  def instructions=(value)
@@ -140,8 +108,6 @@ class Riffer::Agent::Config
140
108
  @instructions = value
141
109
  end
142
110
 
143
- # Appends an MCP tag entry to +mcp_configs+.
144
- #
145
111
  #--
146
112
  #: (String | Symbol, ?progressive: bool) -> Array[Hash[Symbol, untyped]]
147
113
  def add_mcp(tag, progressive: true)
@@ -150,9 +116,6 @@ class Riffer::Agent::Config
150
116
  @mcp_configs << { tags: [tag.to_sym], progressive: progressive }
151
117
  end
152
118
 
153
- # Appends a guardrail entry to +guardrails+ for the given phase; +:around+
154
- # appends to both +:before+ and +:after+. Raises Riffer::ArgumentError unless
155
- # +phase+ is :before, :after, or :around.
156
119
  #--
157
120
  #: (Symbol, klass: singleton(Riffer::Guardrail), ?options: Hash[Symbol, untyped]) -> void
158
121
  def add_guardrail(phase, klass:, options: {})
@@ -175,8 +138,6 @@ class Riffer::Agent::Config
175
138
  end
176
139
  end
177
140
 
178
- # Returns the guardrail entries for the given phase, or +[]+ if none.
179
- #
180
141
  #--
181
142
  #: (Symbol) -> Array[Hash[Symbol, untyped]]
182
143
  def guardrails_for(phase)
@@ -185,14 +146,12 @@ class Riffer::Agent::Config
185
146
 
186
147
  private
187
148
 
188
- # +dup+ would leave the copy sharing every collection with this one, so a
189
- # declaration on either would reach the other. The nesting runs deeper than one
190
- # level: an mcp entry holds its own +:tags+ array, a guardrail entry its own
191
- # +:options+ hash, and +model_options+ is arbitrary.
192
149
  #--
193
150
  #: (Riffer::Agent::Config) -> void
194
151
  def initialize_copy(source)
195
152
  super
153
+ # A shallow copy would share collections, letting a declaration on either
154
+ # config reach the other; entries nest (mcp +:tags+, guardrail +:options+).
196
155
  @model_options = Riffer::Helpers::DeepDup.call(source.model_options)
197
156
  @mcp_configs = Riffer::Helpers::DeepDup.call(source.mcp_configs)
198
157
  @guardrails = Riffer::Helpers::DeepDup.call(source.guardrails)
@@ -1,16 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Typed value object wrapping the runtime context Hash held by a Riffer::Agent.
5
- # Exposes typed +skills+, +token_usage+, +mcp_progressive_tools+, and
6
- # +discovered_tools+ accessors while preserving +#[]+ / +#dig+ for caller-provided keys.
7
4
  class Riffer::Agent::Context
8
5
  # @rbs @data: Hash[Symbol, untyped]
9
6
 
10
7
  RESERVED_KEYS = %i[skills token_usage mcp_progressive_tools discovered_tools].freeze #: Array[Symbol]
11
8
 
12
- # Builds a new context. The caller Hash is duped so later caller mutations
13
- # don't leak in. Raises Riffer::ArgumentError if it contains a reserved key.
9
+ # Raises Riffer::ArgumentError if +data+ contains a reserved key.
14
10
  #--
15
11
  #: (?Hash[Symbol, untyped]) -> void
16
12
  def initialize(data = {})
@@ -27,17 +23,12 @@ class Riffer::Agent::Context
27
23
  @data[:discovered_tools] = nil
28
24
  end
29
25
 
30
- # The agent's resolved +Riffer::Skills::Context+, or +nil+ when skills
31
- # are not configured.
32
- #
33
26
  #--
34
27
  #: () -> Riffer::Skills::Context?
35
28
  def skills
36
29
  @data[:skills]
37
30
  end
38
31
 
39
- # Sets the resolved skills context. Raises Riffer::ArgumentError on an
40
- # invalid value.
41
32
  #--
42
33
  #: (Riffer::Skills::Context?) -> Riffer::Skills::Context?
43
34
  def skills=(value)
@@ -48,17 +39,13 @@ class Riffer::Agent::Context
48
39
  @data[:skills] = value
49
40
  end
50
41
 
51
- # The cumulative +Riffer::Providers::TokenUsage+ across every Run on this agent,
52
- # or +nil+ before the first response is recorded.
53
- #
42
+ # Cumulative across every run on this agent.
54
43
  #--
55
44
  #: () -> Riffer::Providers::TokenUsage?
56
45
  def token_usage
57
46
  @data[:token_usage]
58
47
  end
59
48
 
60
- # Sets the cumulative token usage. Raises Riffer::ArgumentError on an invalid
61
- # value.
62
49
  #--
63
50
  #: (Riffer::Providers::TokenUsage?) -> Riffer::Providers::TokenUsage?
64
51
  def token_usage=(value)
@@ -69,22 +56,18 @@ class Riffer::Agent::Context
69
56
  @data[:token_usage] = value
70
57
  end
71
58
 
72
- # Hash-style read, preserved so tools can pull caller-provided keys via
73
- # <tt>context[:agent]</tt>.
74
59
  #--
75
60
  #: (Symbol) -> untyped
76
61
  def [](key)
77
62
  @data[key]
78
63
  end
79
64
 
80
- # Auth-wrapped MCP tool classes for progressive discovery, or +nil+.
81
65
  #--
82
66
  #: () -> Array[singleton(Riffer::Tool)]?
83
67
  def mcp_progressive_tools
84
68
  @data[:mcp_progressive_tools]
85
69
  end
86
70
 
87
- # Sets progressive MCP tools. Raises Riffer::ArgumentError on an invalid value.
88
71
  #--
89
72
  #: (Array[singleton(Riffer::Tool)]?) -> Array[singleton(Riffer::Tool)]?
90
73
  def mcp_progressive_tools=(value)
@@ -99,15 +82,12 @@ class Riffer::Agent::Context
99
82
  @data[:mcp_progressive_tools] = value
100
83
  end
101
84
 
102
- # MCP tool classes discovered during progressive search. Accumulates across
103
- # +generate+ calls and is merged into the active tool list on every LLM call.
104
85
  #--
105
86
  #: () -> Array[singleton(Riffer::Tool)]?
106
87
  def discovered_tools
107
88
  @data[:discovered_tools]
108
89
  end
109
90
 
110
- # Sets the discovered tools array. Raises Riffer::ArgumentError on an invalid value.
111
91
  #--
112
92
  #: (Array[singleton(Riffer::Tool)]?) -> Array[singleton(Riffer::Tool)]?
113
93
  def discovered_tools=(value)
@@ -122,8 +102,6 @@ class Riffer::Agent::Context
122
102
  @data[:discovered_tools] = value
123
103
  end
124
104
 
125
- # Accumulates newly discovered MCP tool classes, deduplicating by name.
126
- # Each call extends the existing set; calling multiple times is safe.
127
105
  #--
128
106
  #: (Array[singleton(Riffer::Tool)]) -> Array[singleton(Riffer::Tool)]
129
107
  def discover_tools(tools)
@@ -137,8 +115,6 @@ class Riffer::Agent::Context
137
115
  @data.dig(*keys)
138
116
  end
139
117
 
140
- # Returns a copy of the underlying Hash; mutating it does not affect this
141
- # context.
142
118
  #--
143
119
  #: () -> Hash[Symbol, untyped]
144
120
  def to_h
@@ -1,36 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # How a run ended — the single place to read whether the agent completed
5
- # normally and, if not, why. +detail+ carries the specifics when there are any:
6
- # the tripwire reason, the interrupt reason, the provider's raw finish value,
7
- # or the structured output parse/validation error.
8
- #
9
- # response = agent.generate("Analyze this")
10
- # case response.outcome.reason
11
- # when :completed then puts response.structured_output
12
- # when :invalid_structured_output then warn response.outcome.detail
13
- # end
14
4
  class Riffer::Agent::Outcome
15
- # Finish reasons that end a turn normally; every other finish reason means the
16
- # provider cut the turn short and surfaces as the run's outcome verbatim.
17
5
  NORMAL_FINISH_REASONS = %i[stop tool_calls].freeze #: Array[Symbol]
18
6
 
19
- # Derived from the provider vocabulary so a new finish reason becomes an
20
- # outcome without a second list to update.
21
7
  PROVIDER_STOP_REASONS = (Riffer::Providers::FinishReason::VALUES - NORMAL_FINISH_REASONS).freeze #: Array[Symbol]
22
8
 
23
- # The vocabulary every run ends in.
24
9
  VALUES = (%i[completed guardrail_blocked interrupted max_steps invalid_structured_output] +
25
10
  PROVIDER_STOP_REASONS).freeze #: Array[Symbol]
26
11
 
27
- # Why the run ended.
28
12
  attr_reader :reason #: Symbol # @dynamic reason
29
13
 
30
- # Human-readable specifics for +reason+, when there are any.
31
14
  attr_reader :detail #: String? # @dynamic detail
32
15
 
33
- # Raises Riffer::ArgumentError when +reason+ is outside VALUES.
34
16
  #--
35
17
  #: (reason: Symbol, ?detail: String?) -> void
36
18
  def initialize(reason:, detail: nil)
@@ -42,8 +24,6 @@ class Riffer::Agent::Outcome
42
24
  @detail = detail
43
25
  end
44
26
 
45
- # Returns true when the run completed normally.
46
- #
47
27
  #--
48
28
  #: () -> bool
49
29
  def success?
@@ -1,50 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # Wraps an agent generation response. +outcome+ says how the run ended; when a
5
- # guardrail blocks execution, +content+ is empty and +tripwire+ carries the
6
- # block details.
7
- #
8
- # response = agent.generate("Hello")
9
- # if response.outcome.success?
10
- # puts response.content
11
- # else
12
- # puts "#{response.outcome.reason}: #{response.outcome.detail}"
13
- # end
14
4
  class Riffer::Agent::Response
15
- # The response content.
16
5
  attr_reader :content #: String # @dynamic content
17
-
18
- # How the run ended.
19
6
  attr_reader :outcome #: Riffer::Agent::Outcome # @dynamic outcome
20
-
21
- # The tripwire if execution was blocked.
22
7
  attr_reader :tripwire #: Riffer::Guardrails::Tripwire? # @dynamic tripwire
23
-
24
- # The modifications made by guardrails during processing.
25
8
  attr_reader :modifications #: Array[Riffer::Guardrails::Modification] # @dynamic modifications
26
-
27
- # The reasoning parts on the final assistant message, if the provider
28
- # produced any.
29
9
  attr_reader :reasoning #: Array[Riffer::Messages::Assistant::ReasoningPart] # @dynamic reasoning
30
-
31
- # The parsed structured output, if structured output was configured.
32
10
  attr_reader :structured_output #: Hash[Symbol, untyped]? # @dynamic structured_output
33
-
34
- # The aggregate token usage across this run's LLM calls, if any was reported.
35
11
  attr_reader :token_usage #: Riffer::Providers::TokenUsage? # @dynamic token_usage
36
-
37
- # The number of LLM calls made during this run (0 when a before-guardrail
38
- # blocks before any call). Distinct from the session's cumulative step count.
39
12
  attr_reader :steps #: Integer # @dynamic steps
40
-
41
- # The full message history from the agent conversation.
42
13
  attr_reader :messages #: Array[Riffer::Messages::Base] # @dynamic messages
43
14
 
44
- # Call ids of tool_use blocks riffer filled with placeholder results this
45
- # turn (when an interrupt left them unanswered and history healing is on).
46
- attr_reader :healed_tool_call_ids #: Array[String] # @dynamic healed_tool_call_ids
47
-
48
15
  #--
49
16
  #: (
50
17
  # String,
@@ -54,7 +21,6 @@ class Riffer::Agent::Response
54
21
  # ?reasoning: Array[Riffer::Messages::Assistant::ReasoningPart],
55
22
  # ?structured_output: Hash[Symbol, untyped]?,
56
23
  # ?messages: Array[Riffer::Messages::Base],
57
- # ?healed_tool_call_ids: Array[String],
58
24
  # ?token_usage: Riffer::Providers::TokenUsage?,
59
25
  # ?steps: Integer
60
26
  # ) -> void
@@ -66,7 +32,6 @@ class Riffer::Agent::Response
66
32
  reasoning: [],
67
33
  structured_output: nil,
68
34
  messages: [],
69
- healed_tool_call_ids: [],
70
35
  token_usage: nil,
71
36
  steps: 0
72
37
  )
@@ -77,13 +42,10 @@ class Riffer::Agent::Response
77
42
  @reasoning = reasoning
78
43
  @structured_output = structured_output
79
44
  @messages = messages
80
- @healed_tool_call_ids = healed_tool_call_ids
81
45
  @token_usage = token_usage
82
46
  @steps = steps
83
47
  end
84
48
 
85
- # Returns true if any guardrail modified data during processing.
86
- #
87
49
  #--
88
50
  #: () -> bool
89
51
  def modified?
@@ -1,14 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
  # rbs_inline: enabled
3
3
 
4
- # The generation loop — a pure module of functions over an +agent+, which owns
5
- # every per-call value; Run just orchestrates.
6
4
  module Riffer::Agent::Run
7
5
  extend self
8
6
 
9
- # Runs the generate loop for the given agent. See Riffer::Agent#generate
10
- # for prompt/files semantics.
11
- #
12
7
  #--
13
8
  #: (agent: Riffer::Agent, ?prompt: String?, ?files: Array[Hash[Symbol, untyped] | Riffer::Messages::User::FilePart]?, ?tags: Hash[(String | Symbol), untyped]) -> Riffer::Agent::Response
14
9
  def generate(agent:, prompt: nil, files: nil, tags: {})
@@ -16,17 +11,13 @@ module Riffer::Agent::Run
16
11
  run_loop(agent, tags: tags)
17
12
  end
18
13
 
19
- # Runs the streaming loop for the given agent. See Riffer::Agent#stream
20
- # for prompt/files semantics.
21
- #
22
14
  #--
23
15
  #: (agent: Riffer::Agent, ?prompt: String?, ?files: Array[Hash[Symbol, untyped] | Riffer::Messages::User::FilePart]?, ?tags: Hash[(String | Symbol), untyped]) -> Enumerator[Riffer::StreamEvents::Base, Riffer::Agent::Response]
24
16
  def stream(agent:, prompt: nil, files: nil, tags: {})
25
17
  append_user_message(agent, prompt, files: files)
26
18
  # The enumerator body runs in its own fiber, where the fiber-local OTEL
27
19
  # context is empty — capture here so the run span parents to the caller's
28
- # trace. tags ride as an ordinary argument captured in the closure, so they
29
- # cross the fiber boundary without any re-propagation.
20
+ # trace.
30
21
  trace_context = Riffer::Tracing.current_context
31
22
  Enumerator.new do |stream_yielder|
32
23
  Riffer::Tracing.with_context(trace_context) { run_loop(agent, tags: tags, stream_yielder: stream_yielder) }
@@ -35,12 +26,6 @@ module Riffer::Agent::Run
35
26
 
36
27
  private
37
28
 
38
- # Both +generate+ and +stream+ funnel here, so this is the single place raw
39
- # +tags+ are normalized and merged over the default tags (a caller tag wins
40
- # on a shared key). The clean <tt>String => String</tt> map is then
41
- # threaded to every span builder in the run as +riffer.tag.*+ and to each
42
- # provider call (via +merged_model_options+) for native request-metadata
43
- # mapping.
44
29
  #--
45
30
  #: (Riffer::Agent, ?tags: Hash[(String | Symbol), untyped]?, ?stream_yielder: Enumerator::Yielder?) -> Riffer::Agent::Response
46
31
  def run_loop(agent, tags: {}, stream_yielder: nil)
@@ -125,20 +110,12 @@ module Riffer::Agent::Run
125
110
  return final_response(agent, all_modifications, token_usage: run_usage, steps: run_steps)
126
111
  end
127
112
 
128
- new_messages, filled = Riffer::Agent::Session::Repair.fill_orphans(agent.session.messages)
129
- agent.session.set(new_messages)
130
- if stream_yielder
131
- stream_yielder << Riffer::StreamEvents::Interrupt.new(
132
- reason: reason,
133
- healed_tool_call_ids: filled,
134
- )
135
- end
113
+ stream_yielder << Riffer::StreamEvents::Interrupt.new(reason: reason) if stream_yielder
136
114
  final_response(
137
115
  agent,
138
116
  all_modifications,
139
117
  interrupted: true,
140
118
  interrupt_reason: reason,
141
- healed_tool_call_ids: filled,
142
119
  token_usage: run_usage,
143
120
  steps: run_steps,
144
121
  )
@@ -164,12 +141,12 @@ module Riffer::Agent::Run
164
141
 
165
142
  case event
166
143
  when Riffer::StreamEvents::TextDelta
167
- # Append in place rather than += (which reallocates and copies the whole
168
- # buffer per delta, O(n^2) over a stream). accumulated_content stays an
169
- # owned buffer; replace (not =) on TextDone keeps it that way so a later
170
- # delta's << can never mutate the string held by a TextDone event.
144
+ # << rather than +=, which copies the whole buffer per delta (O(n^2)
145
+ # over a stream).
171
146
  accumulated_content << event.content
172
147
  when Riffer::StreamEvents::TextDone
148
+ # replace, not =, so a later delta's << can never mutate the string
149
+ # this event holds.
173
150
  accumulated_content.replace(event.content)
174
151
  when Riffer::StreamEvents::ToolCallDone
175
152
  accumulated_tool_calls << Riffer::Messages::Assistant::ToolCall.new(
@@ -239,14 +216,12 @@ module Riffer::Agent::Run
239
216
  )
240
217
  end
241
218
 
242
- # Checked in the order things happened. The loop being stopped (max_steps or
243
- # an interrupt) beats the provider's finish reason, which beats riffer's own
244
- # validation of the content. A truncated response that also fails the schema
245
- # therefore reports :length, not :invalid_structured_output.
246
219
  #--
247
220
  #: (Riffer::Messages::Assistant?, Riffer::Agent::StructuredOutput::Result?, interrupted: bool, interrupt_reason: (String | Symbol)?) -> Riffer::Agent::Outcome
248
221
  def final_outcome(message, result, interrupted:, interrupt_reason:)
249
222
  finish_reason = message&.finish_reason
223
+ # Precedence follows the order things happened: a stopped loop beats the
224
+ # provider's finish reason, which beats schema validation of the content.
250
225
  if interrupted && interrupt_reason == Riffer::Agent::INTERRUPT_MAX_STEPS
251
226
  Riffer::Agent::Outcome.new(reason: :max_steps)
252
227
  elsif interrupted
@@ -374,20 +349,17 @@ module Riffer::Agent::Run
374
349
  discovered.empty? ? agent.tools : agent.tools + discovered
375
350
  end
376
351
 
377
- # +tags+ rides in the options hash as a curated key the providers extract for
378
- # native request-metadata mapping (alongside +:structured_output+); it never
379
- # reaches an SDK call verbatim. Span tagging is threaded separately to each
380
- # builder.
381
352
  #--
382
353
  #: (Riffer::Agent, ?Hash[String, String]) -> Hash[Symbol, untyped]
383
354
  def merged_model_options(agent, tags = {})
384
355
  opts = agent.config.model_options.dup
385
356
  opts[:structured_output] = agent.structured_output if agent.structured_output
357
+ # Providers extract :tags for native request metadata; it never reaches an
358
+ # SDK call verbatim.
386
359
  opts[:tags] = tags unless tags.empty?
387
360
  opts
388
361
  end
389
362
 
390
- # The tags riffer adds to every run, identifying the agent it's on behalf of.
391
363
  #--
392
364
  #: (Riffer::Agent) -> Hash[String, String]
393
365
  def default_tags(agent)
@@ -403,7 +375,6 @@ module Riffer::Agent::Run
403
375
  # ?modifications: Array[Riffer::Guardrails::Modification],
404
376
  # ?reasoning: Array[Riffer::Messages::Assistant::ReasoningPart],
405
377
  # ?structured_output: Hash[Symbol, untyped]?,
406
- # ?healed_tool_call_ids: Array[String],
407
378
  # ?token_usage: Riffer::Providers::TokenUsage?,
408
379
  # ?steps: Integer
409
380
  # ) -> Riffer::Agent::Response
@@ -415,7 +386,6 @@ module Riffer::Agent::Run
415
386
  modifications: [],
416
387
  reasoning: [],
417
388
  structured_output: nil,
418
- healed_tool_call_ids: [],
419
389
  token_usage: nil,
420
390
  steps: 0
421
391
  )
@@ -428,17 +398,15 @@ module Riffer::Agent::Run
428
398
  reasoning: reasoning,
429
399
  structured_output: structured_output,
430
400
  messages: messages.frozen? ? messages : messages.dup.freeze,
431
- healed_tool_call_ids: healed_tool_call_ids,
432
401
  token_usage: token_usage,
433
402
  steps: steps,
434
403
  )
435
404
  end
436
405
 
437
- # Raises when +files+ are supplied without a +prompt+ — the provider needs
438
- # text to anchor the attachments.
439
406
  #--
440
407
  #: (Riffer::Agent, String?, ?files: Array[Hash[Symbol, untyped] | Riffer::Messages::User::FilePart]?) -> void
441
408
  def append_user_message(agent, prompt, files: nil)
409
+ # The provider needs text to anchor the attachments.
442
410
  raise Riffer::ArgumentError, "files: requires a prompt" if files && !files.empty? && prompt.nil?
443
411
  return unless prompt
444
412
 
@@ -3,32 +3,20 @@
3
3
 
4
4
  require "json"
5
5
 
6
- # Turns a resolved agent into a self-contained, provider-neutral data hash and
7
- # back into a runnable agent, behind the +Riffer::Agent#to_h+ /
8
- # +Riffer::Agent.from_h+ delegators.
9
- #
10
- # hash = Riffer::Agent::Serializer.to_h(agent: agent)
11
- # rebuilt = Riffer::Agent::Serializer.from_h(hash, context: {tenant: "acme"})
12
6
  module Riffer::Agent::Serializer
13
7
  extend self
14
8
 
15
- # The wire format version, bumped only on an incompatible change to the hash
16
- # shape; +from_h+ refuses any other version.
9
+ # Bump only on an incompatible change to the hash shape.
17
10
  SCHEMA_VERSION = 1 #: Integer
18
11
 
19
- # Raised by +from_h+ when the hash's +schema_version+ is unsupported.
20
12
  class VersionError < Riffer::ArgumentError; end
21
13
 
22
- # The default +tool_resolver+: synthesizes a body-less tool shell from a
23
- # descriptor. Its +#call+ raises — route shells through a remote runtime.
24
14
  DEFAULT_TOOL_RESOLVER = ->(descriptor) { build_tool_shell(descriptor) } #: ^(Hash[Symbol, untyped]) -> singleton(Riffer::Tool)
25
15
 
26
- # Snapshots a resolved agent into a self-contained wire hash. Proc-based
27
- # settings are already evaluated against the agent's context, so the hash
28
- # carries plain data, never Procs.
29
16
  #--
30
17
  #: (agent: Riffer::Agent) -> Hash[Symbol, untyped]
31
18
  def to_h(agent:)
19
+ # Already resolved against the agent's context, so the hash carries plain data, never Procs.
32
20
  config = agent.config
33
21
  {
34
22
  schema_version: SCHEMA_VERSION,
@@ -43,17 +31,11 @@ module Riffer::Agent::Serializer
43
31
  }
44
32
  end
45
33
 
46
- # Reconstructs a runnable agent from a wire hash. +context+ is threaded into
47
- # tool dispatch (not used to re-resolve the already-resolved config);
48
- # +session+ seeds conversation history (the hash carries the agent definition,
49
- # not its history). Raises Riffer::Agent::Serializer::VersionError on an
50
- # unsupported +schema_version+.
51
- #
34
+ # Raises Riffer::Agent::Serializer::VersionError on an unsupported +schema_version+.
52
35
  #--
53
36
  #: (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
54
37
  def from_h(hash, context: nil, session: nil, tool_resolver: DEFAULT_TOOL_RESOLVER, tool_runtime: nil)
55
- # Version -> decoder dispatch. Adding a +when 2+ arm (a backwards-compatible
56
- # decoder) is how a future breaking change keeps older hashes readable.
38
+ # One arm per supported version keeps older hashes decodable after a breaking change.
57
39
  case hash[:schema_version]
58
40
  when SCHEMA_VERSION
59
41
  decode_v1(hash, context: context, session: session, tool_resolver: tool_resolver, tool_runtime: tool_runtime)
@@ -63,15 +45,12 @@ module Riffer::Agent::Serializer
63
45
  end
64
46
  end
65
47
 
66
- # Snapshots a resolved agent to a JSON string.
67
48
  #--
68
49
  #: (agent: Riffer::Agent) -> String
69
50
  def to_json(agent:)
70
51
  JSON.generate(to_h(agent: agent))
71
52
  end
72
53
 
73
- # Reconstructs a runnable agent from a JSON string produced by +to_json+. See
74
- # +from_h+ for the arguments.
75
54
  #--
76
55
  #: (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
77
56
  def from_json(json, context: nil, session: nil, tool_resolver: DEFAULT_TOOL_RESOLVER, tool_runtime: nil)
@@ -100,14 +79,11 @@ module Riffer::Agent::Serializer
100
79
  max_steps: decode_max_steps(hash),
101
80
  tools_config: tools,
102
81
  } #: Hash[Symbol, untyped]
103
- # tool_runtime= rejects nil, so only inject when supplied; otherwise the
104
- # Config default (Riffer.config.tool_runtime) applies.
82
+ # Config#tool_runtime= rejects nil.
105
83
  config_args[:tool_runtime] = tool_runtime if tool_runtime
106
84
 
107
- # +session+ is forwarded verbatim: when nil, Agent.new seeds a fresh session
108
- # from the decoded instructions; when supplied, Agent.new uses it as-is to
109
- # resume persisted history. The hash never carries history (see "What does
110
- # not transfer"), so this is the only seam for rehydrating a conversation.
85
+ # The hash never carries history, so +session+ is the only seam for rehydrating a conversation.
86
+ # +context+ feeds tool dispatch only; the config was resolved before serialization.
111
87
  Riffer::Agent.new(config: Riffer::Agent::Config.new(**config_args), context: context, session: session)
112
88
  end
113
89
 
@@ -119,19 +95,17 @@ module Riffer::Agent::Serializer
119
95
  Riffer::Params.from_json_schema(schema)
120
96
  end
121
97
 
122
- # Encodes unlimited steps (+nil+ in the DSL) as +-1+ on the wire, where a
123
- # JSON +null+ is awkward across transports (e.g. proto3).
124
98
  #--
125
99
  #: (Numeric?) -> Numeric
126
100
  def encode_max_steps(value)
101
+ # A JSON null is awkward across transports (e.g. proto3), so unlimited travels as -1.
127
102
  value.nil? ? -1 : value
128
103
  end
129
104
 
130
- # Reverses +encode_max_steps+; a missing key falls back to the default so a
131
- # partial hash can't become an unbounded loop.
132
105
  #--
133
106
  #: (Hash[Symbol, untyped]) -> Numeric?
134
107
  def decode_max_steps(hash)
108
+ # A partial hash must not become an unbounded loop.
135
109
  return Riffer::Agent::Config::DEFAULT_MAX_STEPS unless hash.key?(:max_steps)
136
110
 
137
111
  hash[:max_steps] == -1 ? nil : hash[:max_steps]
@@ -151,10 +125,7 @@ module Riffer::Agent::Serializer
151
125
  schema = descriptor[:parameters_schema]
152
126
  tool_timeout = descriptor[:timeout]
153
127
 
154
- # An anonymous Riffer::Tool subclass is the idiom for synthesizing a tool
155
- # from data — the tool DSL is class-level, so there is no value-level
156
- # builder to type against. Same approach as Riffer::Mcp::ToolFactory;
157
- # steep can't type the dynamic class body, hence the ignore block.
128
+ # The tool DSL is class-level, so there is no value-level builder to synthesize a tool from data.
158
129
  Class.new(Riffer::Tool) do
159
130
  # steep:ignore:start
160
131
  identifier tool_name