riffer 0.30.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 (214) 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 +25 -0
  7. data/docs/08_MESSAGES.md +1 -1
  8. data/docs/14_MCP.md +50 -5
  9. data/docs/15_SERIALIZATION.md +23 -12
  10. data/docs/providers/02_AMAZON_BEDROCK.md +14 -0
  11. data/lib/riffer/agent/config.rb +42 -47
  12. data/lib/riffer/agent/context.rb +70 -50
  13. data/lib/riffer/agent/response.rb +4 -20
  14. data/lib/riffer/agent/run.rb +28 -67
  15. data/lib/riffer/agent/serializer.rb +36 -85
  16. data/lib/riffer/agent/session/repair.rb +14 -40
  17. data/lib/riffer/agent/session.rb +25 -67
  18. data/lib/riffer/agent/structured_output/result.rb +3 -11
  19. data/lib/riffer/agent/structured_output.rb +5 -13
  20. data/lib/riffer/agent.rb +81 -199
  21. data/lib/riffer/config.rb +34 -101
  22. data/lib/riffer/evals/evaluator.rb +7 -27
  23. data/lib/riffer/evals/evaluator_runner.rb +11 -19
  24. data/lib/riffer/evals/judge.rb +4 -25
  25. data/lib/riffer/evals/result.rb +1 -18
  26. data/lib/riffer/evals/run_result.rb +0 -11
  27. data/lib/riffer/evals/scenario_result.rb +0 -14
  28. data/lib/riffer/evals.rb +0 -6
  29. data/lib/riffer/guardrail.rb +4 -27
  30. data/lib/riffer/guardrails/modification.rb +0 -10
  31. data/lib/riffer/guardrails/result.rb +3 -30
  32. data/lib/riffer/guardrails/runner.rb +5 -22
  33. data/lib/riffer/guardrails/tripwire.rb +1 -19
  34. data/lib/riffer/guardrails.rb +2 -4
  35. data/lib/riffer/helpers/call_or_value.rb +4 -3
  36. data/lib/riffer/helpers/class_name_converter.rb +3 -1
  37. data/lib/riffer/helpers/dependencies.rb +5 -7
  38. data/lib/riffer/helpers.rb +0 -5
  39. data/lib/riffer/mcp/authenticated_tool.rb +9 -9
  40. data/lib/riffer/mcp/client.rb +12 -17
  41. data/lib/riffer/mcp/manifest.rb +13 -10
  42. data/lib/riffer/mcp/registration.rb +2 -11
  43. data/lib/riffer/mcp/registry.rb +44 -52
  44. data/lib/riffer/mcp/search_tool.rb +53 -0
  45. data/lib/riffer/mcp/tool_factory.rb +13 -18
  46. data/lib/riffer/mcp.rb +12 -17
  47. data/lib/riffer/messages/assistant.rb +2 -9
  48. data/lib/riffer/messages/base.rb +46 -16
  49. data/lib/riffer/messages/file_part.rb +32 -24
  50. data/lib/riffer/messages/system.rb +0 -5
  51. data/lib/riffer/messages/tool.rb +0 -10
  52. data/lib/riffer/messages/user.rb +0 -10
  53. data/lib/riffer/messages.rb +0 -7
  54. data/lib/riffer/params/boolean.rb +2 -4
  55. data/lib/riffer/params/param.rb +28 -39
  56. data/lib/riffer/params.rb +9 -21
  57. data/lib/riffer/providers/amazon_bedrock.rb +42 -28
  58. data/lib/riffer/providers/anthropic.rb +4 -9
  59. data/lib/riffer/providers/azure_open_ai.rb +3 -19
  60. data/lib/riffer/providers/base.rb +13 -26
  61. data/lib/riffer/providers/gemini.rb +4 -4
  62. data/lib/riffer/providers/mock.rb +6 -26
  63. data/lib/riffer/providers/open_ai.rb +6 -8
  64. data/lib/riffer/providers/open_router.rb +4 -10
  65. data/lib/riffer/providers/repository.rb +4 -3
  66. data/lib/riffer/providers/token_usage.rb +9 -20
  67. data/lib/riffer/providers.rb +0 -8
  68. data/lib/riffer/runner/fibers.rb +10 -16
  69. data/lib/riffer/runner/sequential.rb +1 -4
  70. data/lib/riffer/runner/threaded.rb +3 -14
  71. data/lib/riffer/runner.rb +2 -15
  72. data/lib/riffer/skills/activate_tool.rb +2 -11
  73. data/lib/riffer/skills/adapter.rb +4 -22
  74. data/lib/riffer/skills/backend.rb +7 -21
  75. data/lib/riffer/skills/config.rb +10 -31
  76. data/lib/riffer/skills/context.rb +5 -20
  77. data/lib/riffer/skills/filesystem_backend.rb +7 -25
  78. data/lib/riffer/skills/frontmatter.rb +10 -28
  79. data/lib/riffer/skills/markdown_adapter.rb +2 -9
  80. data/lib/riffer/skills/xml_adapter.rb +2 -8
  81. data/lib/riffer/stream_events/base.rb +1 -6
  82. data/lib/riffer/stream_events/guardrail_modification.rb +1 -8
  83. data/lib/riffer/stream_events/guardrail_tripwire.rb +1 -8
  84. data/lib/riffer/stream_events/interrupt.rb +4 -7
  85. data/lib/riffer/stream_events/reasoning_delta.rb +2 -4
  86. data/lib/riffer/stream_events/reasoning_done.rb +2 -4
  87. data/lib/riffer/stream_events/skill_activation.rb +2 -4
  88. data/lib/riffer/stream_events/text_delta.rb +0 -2
  89. data/lib/riffer/stream_events/text_done.rb +1 -3
  90. data/lib/riffer/stream_events/token_usage_done.rb +1 -8
  91. data/lib/riffer/stream_events/tool_call_delta.rb +2 -3
  92. data/lib/riffer/stream_events/tool_call_done.rb +1 -3
  93. data/lib/riffer/stream_events/web_search_done.rb +1 -3
  94. data/lib/riffer/stream_events/web_search_status.rb +2 -3
  95. data/lib/riffer/stream_events.rb +0 -10
  96. data/lib/riffer/tool.rb +6 -13
  97. data/lib/riffer/tools/response.rb +8 -4
  98. data/lib/riffer/tools/runtime/fibers.rb +0 -3
  99. data/lib/riffer/tools/runtime/inline.rb +1 -4
  100. data/lib/riffer/tools/runtime/threaded.rb +0 -2
  101. data/lib/riffer/tools/runtime.rb +5 -38
  102. data/lib/riffer/tools/toolable.rb +5 -16
  103. data/lib/riffer/tools.rb +0 -4
  104. data/lib/riffer/version.rb +1 -1
  105. data/lib/riffer.rb +7 -8
  106. data/sig/generated/riffer/agent/config.rbs +29 -46
  107. data/sig/generated/riffer/agent/context.rbs +40 -48
  108. data/sig/generated/riffer/agent/response.rbs +4 -20
  109. data/sig/generated/riffer/agent/run.rbs +12 -61
  110. data/sig/generated/riffer/agent/serializer.rbs +28 -81
  111. data/sig/generated/riffer/agent/session/repair.rbs +12 -40
  112. data/sig/generated/riffer/agent/session.rbs +25 -67
  113. data/sig/generated/riffer/agent/structured_output/result.rbs +2 -10
  114. data/sig/generated/riffer/agent/structured_output.rbs +5 -12
  115. data/sig/generated/riffer/agent.rbs +62 -191
  116. data/sig/generated/riffer/config.rbs +34 -100
  117. data/sig/generated/riffer/evals/evaluator.rbs +7 -27
  118. data/sig/generated/riffer/evals/evaluator_runner.rbs +9 -19
  119. data/sig/generated/riffer/evals/judge.rbs +4 -24
  120. data/sig/generated/riffer/evals/result.rbs +1 -17
  121. data/sig/generated/riffer/evals/run_result.rbs +0 -10
  122. data/sig/generated/riffer/evals/scenario_result.rbs +0 -13
  123. data/sig/generated/riffer/evals.rbs +0 -6
  124. data/sig/generated/riffer/guardrail.rbs +4 -27
  125. data/sig/generated/riffer/guardrails/modification.rbs +0 -10
  126. data/sig/generated/riffer/guardrails/result.rbs +3 -30
  127. data/sig/generated/riffer/guardrails/runner.rbs +5 -22
  128. data/sig/generated/riffer/guardrails/tripwire.rbs +1 -19
  129. data/sig/generated/riffer/guardrails.rbs +2 -4
  130. data/sig/generated/riffer/helpers/call_or_value.rbs +4 -3
  131. data/sig/generated/riffer/helpers/class_name_converter.rbs +1 -1
  132. data/sig/generated/riffer/helpers/dependencies.rbs +3 -7
  133. data/sig/generated/riffer/helpers.rbs +0 -5
  134. data/sig/generated/riffer/mcp/authenticated_tool.rbs +5 -4
  135. data/sig/generated/riffer/mcp/client.rbs +10 -16
  136. data/sig/generated/riffer/mcp/manifest.rbs +9 -9
  137. data/sig/generated/riffer/mcp/registration.rbs +2 -10
  138. data/sig/generated/riffer/mcp/registry.rbs +11 -18
  139. data/sig/generated/riffer/mcp/search_tool.rbs +26 -0
  140. data/sig/generated/riffer/mcp/tool_factory.rbs +10 -15
  141. data/sig/generated/riffer/mcp.rbs +10 -17
  142. data/sig/generated/riffer/messages/assistant.rbs +2 -8
  143. data/sig/generated/riffer/messages/base.rbs +11 -16
  144. data/sig/generated/riffer/messages/file_part.rbs +13 -23
  145. data/sig/generated/riffer/messages/system.rbs +0 -4
  146. data/sig/generated/riffer/messages/tool.rbs +0 -9
  147. data/sig/generated/riffer/messages/user.rbs +0 -9
  148. data/sig/generated/riffer/messages.rbs +0 -7
  149. data/sig/generated/riffer/params/boolean.rbs +2 -4
  150. data/sig/generated/riffer/params/param.rbs +21 -39
  151. data/sig/generated/riffer/params.rbs +9 -21
  152. data/sig/generated/riffer/providers/amazon_bedrock.rbs +21 -25
  153. data/sig/generated/riffer/providers/anthropic.rbs +2 -7
  154. data/sig/generated/riffer/providers/azure_open_ai.rbs +3 -18
  155. data/sig/generated/riffer/providers/base.rbs +9 -25
  156. data/sig/generated/riffer/providers/gemini.rbs +0 -2
  157. data/sig/generated/riffer/providers/mock.rbs +6 -26
  158. data/sig/generated/riffer/providers/open_ai.rbs +1 -5
  159. data/sig/generated/riffer/providers/open_router.rbs +4 -10
  160. data/sig/generated/riffer/providers/repository.rbs +2 -3
  161. data/sig/generated/riffer/providers/token_usage.rbs +6 -16
  162. data/sig/generated/riffer/providers.rbs +0 -8
  163. data/sig/generated/riffer/runner/fibers.rbs +8 -15
  164. data/sig/generated/riffer/runner/sequential.rbs +1 -3
  165. data/sig/generated/riffer/runner/threaded.rbs +3 -13
  166. data/sig/generated/riffer/runner.rbs +2 -14
  167. data/sig/generated/riffer/skills/activate_tool.rbs +2 -11
  168. data/sig/generated/riffer/skills/adapter.rbs +4 -22
  169. data/sig/generated/riffer/skills/backend.rbs +7 -21
  170. data/sig/generated/riffer/skills/config.rbs +10 -31
  171. data/sig/generated/riffer/skills/context.rbs +5 -20
  172. data/sig/generated/riffer/skills/filesystem_backend.rbs +7 -24
  173. data/sig/generated/riffer/skills/frontmatter.rbs +10 -27
  174. data/sig/generated/riffer/skills/markdown_adapter.rbs +2 -9
  175. data/sig/generated/riffer/skills/xml_adapter.rbs +2 -8
  176. data/sig/generated/riffer/stream_events/base.rbs +1 -6
  177. data/sig/generated/riffer/stream_events/guardrail_modification.rbs +1 -8
  178. data/sig/generated/riffer/stream_events/guardrail_tripwire.rbs +1 -8
  179. data/sig/generated/riffer/stream_events/interrupt.rbs +4 -7
  180. data/sig/generated/riffer/stream_events/reasoning_delta.rbs +2 -4
  181. data/sig/generated/riffer/stream_events/reasoning_done.rbs +2 -4
  182. data/sig/generated/riffer/stream_events/skill_activation.rbs +2 -4
  183. data/sig/generated/riffer/stream_events/text_delta.rbs +0 -2
  184. data/sig/generated/riffer/stream_events/text_done.rbs +1 -3
  185. data/sig/generated/riffer/stream_events/token_usage_done.rbs +1 -7
  186. data/sig/generated/riffer/stream_events/tool_call_delta.rbs +2 -3
  187. data/sig/generated/riffer/stream_events/tool_call_done.rbs +1 -3
  188. data/sig/generated/riffer/stream_events/web_search_done.rbs +1 -3
  189. data/sig/generated/riffer/stream_events/web_search_status.rbs +2 -3
  190. data/sig/generated/riffer/stream_events.rbs +0 -10
  191. data/sig/generated/riffer/tool.rbs +5 -12
  192. data/sig/generated/riffer/tools/response.rbs +6 -4
  193. data/sig/generated/riffer/tools/runtime/fibers.rbs +0 -3
  194. data/sig/generated/riffer/tools/runtime/inline.rbs +1 -3
  195. data/sig/generated/riffer/tools/runtime/threaded.rbs +0 -2
  196. data/sig/generated/riffer/tools/runtime.rbs +5 -37
  197. data/sig/generated/riffer/tools/toolable.rbs +4 -14
  198. data/sig/generated/riffer/tools.rbs +0 -4
  199. data/sig/generated/riffer.rbs +5 -4
  200. data/sig/manual/riffer/agent/session/repair.rbs +5 -0
  201. data/sig/manual/riffer/evals/evaluator_runner.rbs +5 -0
  202. data/sig/manual/riffer/helpers/class_name_converter.rbs +5 -0
  203. data/sig/manual/riffer/helpers/dependencies.rbs +5 -0
  204. data/sig/manual/riffer/mcp/authenticated_tool.rbs +5 -0
  205. data/sig/manual/riffer/mcp/registry.rbs +5 -0
  206. data/sig/manual/riffer/mcp/tool_factory.rbs +5 -0
  207. data/sig/manual/riffer/mcp.rbs +5 -0
  208. data/sig/manual/riffer/providers/repository.rbs +5 -0
  209. data/sig/manual/riffer.rbs +5 -0
  210. metadata +17 -9
  211. data/.agents/rdoc.md +0 -69
  212. data/lib/riffer/messages/converter.rb +0 -90
  213. data/sig/generated/riffer/messages/converter.rbs +0 -33
  214. data/sig/manual/riffer/tools/toolable.rbs +0 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7557c63ca04fa006d66a61a53bef6b1f7069ebab67439f00104fbb877d1aceda
4
- data.tar.gz: 39bbe45b7e111ae343e32c54b9769db17dd7d8d9b98cd5cb7d42338aa5e4179c
3
+ metadata.gz: 0cddafa66f3f24980037c23be5e8584ec888b93c40b2b780c99e7205558d2593
4
+ data.tar.gz: 5aea2edfeb9d47a4246b15d3f4e16a7a3c1d7941a473355495267b9eb93bc3cc
5
5
  SHA512:
6
- metadata.gz: 61694198a05d63d831dca9b4b37e38b6cacbd7b67214fa47c2027a0a7e1636ee07e5f85641f9fe00ee597d66dfdb0a6fccd0e494b6ec670a84a2020907cfdd0b
7
- data.tar.gz: fbff4457d39da36a1c77e9cb7ea86ccb5e96f0887ba982f6a8a960557ee4beceda3a604011fcfd20951f9baedd59eadbf188832cb6bcc8fcf71e256d93e8cdc3
6
+ metadata.gz: fad79dcfd8036c3249ea17be785cb6acb193e3a1677f23ee052c89daf39fe97840369a4718e65ebb515bbddeb866608a532b8076a89e2f6c724ddd74d2131e85
7
+ data.tar.gz: 0c7b1bdb7383c6f9a2b053d2d811a7c4aa29142f8a44aa3fd07071d88b0dac722268f036c9f45887bbf3b10591cb3067193540c2cff766d6ab91aa4644f0dcb8
@@ -24,11 +24,66 @@ class MyCustomError < Riffer::Error
24
24
  end
25
25
  ```
26
26
 
27
- ## Comments
27
+ ## Comments & Documentation
28
28
 
29
- - Only add comments when the code is ambiguous or not semantically obvious
30
- - Explain **why** something is done, not **what** is being done
31
- - Comments should add value beyond what the code already expresses
29
+ A comment exists to explain a **why** the code itself cannot never a **how**, and never a restatement of what the code already says. This bar governs all prose, from inline `#` comments to RDoc descriptions on the public API. Types are not prose's job: parameters, return values, and attribute/constant types live in rbs-inline `#:` annotations (see [rbs-inline.md](rbs-inline.md)).
30
+
31
+ **What's a comment (in scope):** RDoc descriptions and inline `#` explanations. **Not comments (never touched):** rbs-inline `#:` annotations, magic comments (`frozen_string_literal`, `rbs_inline`), and RDoc directives (`:nodoc:`, `:nocov:`).
32
+
33
+ ### Public surface
34
+
35
+ Everything public — classes, modules, constants, attributes, public methods, and `protected` subclass-contract methods (e.g. the `pass` / `transform` / `block` helpers a custom `Riffer::Guardrail` calls) — gets **at minimum a very brief description**:
36
+
37
+ - **One verb-first sentence, one line.** `Creates a new agent.`, `Serializes the definition to JSON.`
38
+ - An optional **second sentence is reserved strictly for a "why"** — a non-obvious constraint or rationale the code can't convey. Never a second sentence of "how".
39
+ - If a description needs more than one sentence to say _what_ it does, that's a smell the method does too much.
40
+ - **Exempt:** a constant whose name and value already carry the full meaning (`VERSION = "0.30.0"`, `PHASES = %i[before after]`) — describe a constant only when its name doesn't; an empty namespace module (a Zeitwerk placeholder with no usable members of its own, e.g. `module Riffer::Messages; end`) — its children are documented individually; and a constructor (`initialize`) — the class doc and RDoc's `::new` already cover plain construction, so describe it only when it carries a contract or non-obvious construction behavior (a raise, a dup guard).
41
+
42
+ Do not document parameters or return values in prose — the `#:` line is the single source of truth for types. Attributes and constants still carry a brief description on the line above their inline `#:`.
43
+
44
+ ```ruby
45
+ # Serializes the agent definition to a transferable JSON payload.
46
+ #--
47
+ #: (Riffer::Agent) -> String
48
+ def serialize(agent)
49
+
50
+ # The agent's display name.
51
+ attr_reader :name #: String
52
+ ```
53
+
54
+ ### Private methods
55
+
56
+ A comment survives on a private method **only** if it explains a why a competent reader cannot recover from the code and names alone — a non-local constraint, an external-system quirk, a deliberate non-obvious tradeoff. A description of what it does, or a why that's evident from the code, gets cut.
57
+
58
+ ### Inline comments
59
+
60
+ Same bar: kept only to explain the why of something genuinely ambiguous. `TODO` / `FIXME` / `HACK` markers are tracked work and stay; `NOTE` / `REVIEW` are subject to the why-rule.
61
+
62
+ ### No history
63
+
64
+ A comment describes the present, never how the code got there. Change narration — "was X, now Y", "previously used Z" — has no place; the reader cares about what is, not what was. The one thing worth stating is a still-true constraint, and it belongs in the present tense ("the API returns null for empty results — guard"), never told as the story of the bug that revealed it.
65
+
66
+ ### RDoc mechanics
67
+
68
+ **The `#--` stop directive.** Place `#--` on the line immediately before a **standalone** `#:` type annotation. Without it, RDoc treats `#:` as a label-list marker and corrupts the preceding description into a `<pre>` block. Inline `#:` on the same line as code (attributes, constants) does not need it.
69
+
70
+ **Raises.** Document a raise **only when it's part of the caller's contract** — something a caller should reasonably anticipate and handle. Skip programmer-error guards and "should never happen" assertions. Reserved for public methods. When the raise condition merely restates the declared `#:` type, phrase it by intent ("Raises Riffer::ArgumentError on an invalid value") rather than re-listing the type union — but keep the runtime constraints a type can't express (enum value sets, coercion rules, validation failure).
71
+
72
+ ```ruby
73
+ # Builds a param from a schema hash.
74
+ # Raises Riffer::ArgumentError if the schema is missing a +type+.
75
+ ```
76
+
77
+ **Examples.** Include an example only when a **consumer is likely to use the thing themselves** — a public entry point they construct, subclass, or call (an `Agent` subclass, `Riffer::Mcp.register`, the `params` DSL). Skip examples on framework-internal types even though they're technically public (value objects the framework constructs and hands back, internal engines). When included, keep them sparing — only when they teach something the signature can't — and write them as indented code blocks (2 extra spaces of indent). Usage walkthroughs belong in `docs/`.
78
+
79
+ **Inline code formatting.** Use `+word+` for single-word inline code; for multi-word expressions (spaces, colons, brackets) use `<tt>multi word expression</tt>`, e.g. `Equivalent to <tt>throw :riffer_interrupt, reason</tt>`.
80
+
81
+ **Internal APIs.** Mark with `:nodoc:` to exclude from generated documentation:
82
+
83
+ ```ruby
84
+ def internal_method # :nodoc:
85
+ end
86
+ ```
32
87
 
33
88
  ## Hash Key Convention
34
89
 
@@ -37,6 +92,10 @@ end
37
92
  - String keys are only used at serialization boundaries (JSON Schema output, external API payloads)
38
93
  - Do not write dual-access patterns like `hash[:key] || hash["key"]` — normalize to symbol keys at the boundary instead
39
94
 
95
+ ## Reserved Tool Identifiers
96
+
97
+ - Internal-use-only tools use plain descriptive names without a prefix (e.g. `mcp_search`, `mcp_call`, `evaluation`, `skill_activate`)
98
+
40
99
  ## Module Structure
41
100
 
42
101
  ```ruby
@@ -4,12 +4,7 @@ Type annotations are added directly in Ruby source files using [rbs-inline](http
4
4
 
5
5
  ## Magic Comment
6
6
 
7
- Every `lib/**/*.rb` file must include the `rbs_inline: enabled` comment on line 2:
8
-
9
- ```ruby
10
- # frozen_string_literal: true
11
- # rbs_inline: enabled
12
- ```
7
+ rbs-inline only processes a file when `rbs_inline: enabled` is present on line 2. It ships as part of the required header on every `lib/**/*.rb` file — see [Required Header](code-style.md#required-header).
13
8
 
14
9
  ## Annotation Syntax
15
10
 
@@ -1,3 +1,3 @@
1
1
  {
2
- ".": "0.30.0"
2
+ ".": "0.32.0"
3
3
  }
data/AGENTS.md CHANGED
@@ -14,8 +14,7 @@ Ruby gem framework for building AI-powered agents with LLM provider adapters.
14
14
 
15
15
  - [Architecture](.agents/architecture.md) - Core components and project structure
16
16
  - [Testing](.agents/testing.md) - Minitest spec DSL and VCR cassettes
17
- - [Code Style](.agents/code-style.md) - StandardRB and comment conventions
18
- - [RDoc](.agents/rdoc.md) - Documentation format for public APIs
17
+ - [Code Style](.agents/code-style.md) - StandardRB, comment, and RDoc conventions
19
18
  - [Providers](.agents/providers.md) - Adding new LLM provider adapters
20
19
  - [RBS Inline](.agents/rbs-inline.md) - Type annotations with rbs-inline
21
20
 
data/CHANGELOG.md CHANGED
@@ -5,6 +5,31 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.32.0](https://github.com/janeapp/riffer/compare/riffer/v0.31.0...riffer/v0.32.0) (2026-06-08)
9
+
10
+
11
+ ### ⚠ BREAKING CHANGES
12
+
13
+ * `TokenUsage#cache_creation_tokens` is renamed to `cache_write_tokens` (also reflected in `TokenUsage#to_h`). Update any code reading that attribute or hash key.
14
+ * apply module/class conventions consistently ([#298](https://github.com/janeapp/riffer/issues/298))
15
+
16
+ ### Features
17
+
18
+ * add Bedrock prompt caching and surface cached tokens ([#300](https://github.com/janeapp/riffer/issues/300)) ([407390b](https://github.com/janeapp/riffer/commit/407390b505a3af6b881c5204d856a8e9ceddbed9))
19
+ * add progressive discovery for MCP tools by default ([0a37485](https://github.com/janeapp/riffer/commit/0a37485dd59e08a0ba0ca8d1046e3313914f72a5))
20
+
21
+
22
+ ### Code Refactoring
23
+
24
+ * apply module/class conventions consistently ([#298](https://github.com/janeapp/riffer/issues/298)) ([76a3131](https://github.com/janeapp/riffer/commit/76a3131f800ba026c07cc194ab2879279134e865))
25
+
26
+ ## [0.31.0](https://github.com/janeapp/riffer/compare/riffer/v0.30.0...riffer/v0.31.0) (2026-06-03)
27
+
28
+
29
+ ### Features
30
+
31
+ * forward session: through Agent.from_h/from_json for history seeding ([#295](https://github.com/janeapp/riffer/issues/295)) ([0b2eaa2](https://github.com/janeapp/riffer/commit/0b2eaa27f5154bfd61891d4e93b8938f7dce2ce6))
32
+
8
33
  ## [0.30.0](https://github.com/janeapp/riffer/compare/riffer/v0.29.1...riffer/v0.30.0) (2026-06-03)
9
34
 
10
35
 
data/docs/08_MESSAGES.md CHANGED
@@ -196,7 +196,7 @@ agent = MyAgent.new(session: session)
196
196
  response = agent.generate # session already carries the last user turn
197
197
  ```
198
198
 
199
- `Riffer::Agent::Session.new(messages:)` accepts `Riffer::Messages::Base` objects. If your persistence layer hands back hashes, normalize them first via `Riffer::Messages::Converter#convert_to_message_object` or your own adapter (e.g. jane's `to_riffer`).
199
+ `Riffer::Agent::Session.new(messages:)` accepts `Riffer::Messages::Base` objects. If your persistence layer hands back hashes, normalize them first via `Riffer::Messages::Base.from_hash` or your own adapter.
200
200
 
201
201
  ### Accessing Message History
202
202
 
data/docs/14_MCP.md CHANGED
@@ -36,8 +36,9 @@ When **`Riffer.config.mcp.credentials`** is set to a Proc, each MCP `tools/call`
36
36
  ```ruby
37
37
  Riffer.configure do |config|
38
38
  config.mcp.credentials = lambda do |manifest:, matched_tags:, context:|
39
- # return nil to omit this server's tools for this agent run (at resolve time)
40
- # return Hash<String,String> headers for tools/call (e.g. Authorization)
39
+ # when caller does not have an integration, return nil to omit this server's tools for this run
40
+ # when server is unauthenticated or local, return {} to include this server's tools with no extra headers
41
+ # when authorization headers are required, return Hash<String,String> headers
41
42
  end
42
43
  end
43
44
  ```
@@ -46,9 +47,9 @@ end
46
47
  - **`matched_tags`** — intersection of the agent's `use_mcp` tags and `manifest.tags` for this registration (unioned across multiple `use_mcp` lines).
47
48
  - **`context`** — the same hash passed to `generate` / `stream` for this run.
48
49
 
49
- **Resolve time:** Before tools are exposed to the model, the proc is invoked once per matching registration. If it returns **`nil`**, that server's tools are omitted for this run (e.g. tenant has no integration).
50
+ **Resolve time:** The proc is invoked once per matching registration before tools are exposed to the model.
50
51
 
51
- **Call time:** Authenticated tool wrappers invoke the proc again for each execution. If it returns **`nil`**, `Riffer::Mcp::CredentialsDeniedError` is raised.
52
+ **Call time:** Authenticated tool wrappers invoke the proc again for each execution. If it returns `nil`, then `Riffer::Mcp::CredentialsDeniedError` is raised.
52
53
 
53
54
  If **`credentials` is unset**, discovery and `tools/call` share one client built from `discovery_headers` (same behaviour as a single static token for both list and call).
54
55
 
@@ -71,6 +72,8 @@ class ResearchAgent < Riffer::Agent
71
72
  end
72
73
  ```
73
74
 
75
+ By default, `use_mcp` uses **progressive discovery**. The agent receives `mcp_search` rather than every schema up front. See [Progressive Tool Discovery](#progressive-tool-discovery).
76
+
74
77
  `use_mcp` accepts any tag registered via `Riffer::Mcp.register`. Multiple calls accumulate — the agent receives tools from all matching servers:
75
78
 
76
79
  ```ruby
@@ -90,6 +93,48 @@ Tool names must be unique across `uses_tools` and all included MCP servers; dupl
90
93
 
91
94
  Like [`uses_tools`](03_AGENTS.md#uses_tools), **`use_mcp` is not inherited** from the superclass. Declare `use_mcp` on each agent class that should load MCP tools.
92
95
 
96
+ ## Progressive Tool Discovery
97
+
98
+ Progressive discovery is the default. The `use_mcp` instruction exposes **`mcp_search`** instead of flooding the context with every tool schema up front.
99
+
100
+ ```ruby
101
+ class ResearchAgent < Riffer::Agent
102
+ model "openai/gpt-4o"
103
+
104
+ use_mcp :github # default: tools discoverable on demand
105
+ use_mcp :jira, progressive: false # opt-out: all Jira tools injected directly
106
+ end
107
+ ```
108
+
109
+ Only use `progressive: false` when the server has a small, stable set of tools you always want available.
110
+
111
+ **`mcp_search`** — Search for available tools by name or description.
112
+ - `query` (required, non-empty) — filter by name or description substring.
113
+
114
+ On a successful search, matching tools are injected into the agent's active tool list. The model calls them natively on the next turn — no proxy or JSON-encoded arguments.
115
+
116
+ **Example flow:**
117
+
118
+ 1. Agent starts — only `mcp_search` appears in the tool list.
119
+ 2. LLM calls `mcp_search` with `query: "create pull request"`.
120
+ 3. Riffer injects `github__create_pr` into the active tool list and returns an acknowledgment.
121
+ 4. LLM calls `github__create_pr` directly with its real schema (e.g. `title:`, `body:`, `base:`).
122
+ 5. The provider validates the arguments; the tool executes with all credential handling intact.
123
+
124
+ Injected tools accumulate across turns — tools discovered in one search remain available for subsequent calls without re-searching.
125
+
126
+ **Multiple progressive `use_mcp` calls:** All matching registrations are combined into one `mcp_search`:
127
+
128
+ ```ruby
129
+ use_mcp :connectors_a # both default to progressive
130
+ use_mcp :connectors_b
131
+ # → one mcp_search exposing tools from both registrations
132
+ ```
133
+
134
+ **Credential handling:** Progressive tools follow the same credential rules as regular tools — see [Session credentials callback](#session-credentials-callback).
135
+
136
+ **Tool access in tools:** The full discovery pool is available via `context[:mcp_progressive_tools]` and injected tools via `context[:injected_tools]` (both `Array[Riffer::Tool subclass]`). These keys are framework-managed — treat them as read-only from application code.
137
+
93
138
  ## Unregistering a Server
94
139
 
95
140
  ```ruby
@@ -130,7 +175,7 @@ All inherit from `Riffer::Mcp::Error < Riffer::Error`.
130
175
 
131
176
  - **Tool results:** `tools/call` responses are reduced to joined **text** content from MCP `content` items. Non-text parts (e.g. images, embedded resources) are not surfaced in this release.
132
177
  - **Session credentials:** When `Riffer.config.mcp.credentials` is set, authenticated tool wrappers may build a **new HTTP client per tool invocation** so headers stay fresh; there is no connection pooling in this release.
133
- - **Context window / progressive disclosure:** Discovery registers **all** tools from each server; matching agents see the full set in one shot. There is no built-in lazy listing or prompt-size budgeting yet. Tighter control may require application-level filtering, MCP server design, or future Riffer APIs.
178
+ - **Context window / progressive disclosure:** `use_mcp` defaults to progressive mode tools are discovered via `mcp_search` and injected natively for subsequent calls. Use `progressive: false` to inject all schemas directly. See [Progressive Tool Discovery](#progressive-tool-discovery).
134
179
 
135
180
  ## Requirements
136
181
 
@@ -1,28 +1,39 @@
1
1
  # Serialization
2
2
 
3
- `Riffer::Agent::Serializer` turns a **resolved agent** into a self-contained, provider-neutral data dict (`to_h`) and reconstructs a **runnable agent** from that dict (`from_h`). Use it to persist agent definitions outside of code, or to transfer them across a process/service boundary.
3
+ `Riffer::Agent::Serializer` turns a **resolved agent** into a self-contained, provider-neutral data hash (`to_h`) and reconstructs a **runnable agent** from that hash (`from_h`). Use it to persist agent definitions outside of code, or to transfer them across a process/service boundary.
4
4
 
5
5
  You normally reach it through the delegators on `Riffer::Agent`:
6
6
 
7
7
  ```ruby
8
- dict = agent.to_h # snapshot
9
- rebuilt = Riffer::Agent.from_h(dict) # reconstruct
8
+ data = agent.to_h # snapshot
9
+ rebuilt = Riffer::Agent.from_h(data) # reconstruct
10
10
  ```
11
11
 
12
- The dict is plain data — symbol-keyed, JSON-safe. For the wire, use the JSON helpers, which handle generating and parsing for you:
12
+ The hash is plain data — symbol-keyed, JSON-safe. For the wire, use the JSON helpers, which handle generating and parsing for you:
13
13
 
14
14
  ```ruby
15
15
  json = agent.to_json # or Riffer::Agent::Serializer.to_json(agent:)
16
16
  rebuilt = Riffer::Agent.from_json(json)
17
17
  ```
18
18
 
19
- The hash forms (`to_h` / `from_h`) are public too, if you want to embed the dict in a larger payload. `from_h` expects symbol keys, so parse with `JSON.parse(str, symbolize_names: true)` — or just use `from_json`, which does that for you.
19
+ The hash forms (`to_h` / `from_h`) are public too, if you want to embed the hash in a larger payload. `from_h` expects symbol keys, so parse with `JSON.parse(str, symbolize_names: true)` — or just use `from_json`, which does that for you.
20
20
 
21
21
  ### Runtime context
22
22
 
23
23
  `from_h` / `from_json` accept an optional `context:` — the rebuilt agent's **runtime** context, exactly the value you'd pass to `Agent.new(context:)`. It is **not** used to re-resolve the serialized definition (that's already resolved); it's threaded into tool dispatch and read by tools/runtimes at call time. Pass it when a tool or a remote runtime needs per-call data — e.g. `context: { tenant: "acme" }` for multi-tenant dispatch, or Maestro passing `context: { agent: self }` so its runtime can call back. Omit it (defaults to empty) when nothing downstream reads context.
24
24
 
25
- ## What the dict carries
25
+ ### Seeding conversation history
26
+
27
+ The hash carries the agent **definition**, not its conversation history (see [What does not transfer](#what-does-not-transfer)). To resume a persisted conversation, pass a `session:` — exactly the value you'd pass to `Agent.new(session:)`:
28
+
29
+ ```ruby
30
+ rebuilt = Riffer::Agent.from_h(data, session: persisted_session)
31
+ rebuilt.generate # continues from the seeded history
32
+ ```
33
+
34
+ The session is used **as-is**: the rebuilt agent does not prepend anything to it, so the caller owns its full contents — **including the system instruction message**. Omit `session:` and the rebuilt agent builds a fresh session, seeded with the hash's `instructions` and an empty history. Because a supplied session is authoritative, the hash's `instructions` are not re-injected into it — make sure your persisted session already contains the system message.
35
+
36
+ ## What the hash carries
26
37
 
27
38
  ```ruby
28
39
  {
@@ -41,7 +52,7 @@ The hash forms (`to_h` / `from_h`) are public too, if you want to embed the dict
41
52
 
42
53
  ### Resolved snapshot
43
54
 
44
- `to_h` reads the agent's **resolved** state, not its raw configuration. By the time you call it, `Agent.new` has already evaluated any `Proc`-based `model`, `instructions`, or `uses_tools` against the agent's own context — so the dict carries plain strings and data, never Procs. The receiver's `context:` drives runtime behavior (tool dispatch); it does **not** re-evaluate baked-in fields.
55
+ `to_h` reads the agent's **resolved** state, not its raw configuration. By the time you call it, `Agent.new` has already evaluated any `Proc`-based `model`, `instructions`, or `uses_tools` against the agent's own context — so the hash carries plain strings and data, never Procs. The receiver's `context:` drives runtime behavior (tool dispatch); it does **not** re-evaluate baked-in fields.
45
56
 
46
57
  ### Structured output
47
58
 
@@ -56,7 +67,7 @@ Tools cross as `{name, description, parameters_schema, timeout}` descriptors —
56
67
  When the rebuilt agent runs in the **same** codebase that defined the tools (e.g. persisting an agent definition and rehydrating it later), resolve each descriptor back to its real class:
57
68
 
58
69
  ```ruby
59
- rebuilt = Riffer::Agent.from_h(dict,
70
+ rebuilt = Riffer::Agent.from_h(data,
60
71
  tool_resolver: ->(descriptor) { MyToolRegistry.fetch(descriptor[:name]) })
61
72
  ```
62
73
 
@@ -67,7 +78,7 @@ The real classes carry their `#call` bodies, so the agent runs on the default `I
67
78
  When the receiver holds **only the Riffer gem**, the default `tool_resolver` synthesizes body-less **tool shells**. A shell advertises the tool's schema to the LLM but has no `#call` — invoking it in-process raises. Pair the default resolver with a remote `Riffer::Tools::Runtime` that forwards each call back to the origin:
68
79
 
69
80
  ```ruby
70
- rebuilt = Riffer::Agent.from_h(dict,
81
+ rebuilt = Riffer::Agent.from_h(data,
71
82
  tool_runtime: MyRemoteToolRuntime.new(client: rpc_client))
72
83
  ```
73
84
 
@@ -77,12 +88,12 @@ You own what a resolved tool does: a resolver may return real in-process classes
77
88
 
78
89
  ## `max_steps`
79
90
 
80
- Unlimited steps are `nil` at the agent level — set it with `max_steps nil`. On the wire, the serializer encodes that as **`-1`** (and decodes `-1` back to `nil`), so the dict stays portable across transports where JSON `null` is awkward — proto3, for one, can't distinguish `null` from an absent field. The `-1` is purely a wire detail: the DSL and your code only ever see `nil`, and the encode/decode handles the translation at the boundary.
91
+ Unlimited steps are `nil` at the agent level — set it with `max_steps nil`. On the wire, the serializer encodes that as **`-1`** (and decodes `-1` back to `nil`), so the hash stays portable across transports where JSON `null` is awkward — proto3, for one, can't distinguish `null` from an absent field. The `-1` is purely a wire detail: the DSL and your code only ever see `nil`, and the encode/decode handles the translation at the boundary.
81
92
 
82
93
  - **DSL** — integer = bounded, `nil` = unlimited, omitted = `Config`'s default (16).
83
94
  - **Wire** — integer = bounded, `-1` = unlimited, omitted = default (16).
84
95
 
85
- A finite integer round-trips as-is; a dict missing the key falls back to the default rather than running unbounded.
96
+ A finite integer round-trips as-is; a hash missing the key falls back to the default rather than running unbounded.
86
97
 
87
98
  ## Versioning
88
99
 
@@ -90,7 +101,7 @@ A finite integer round-trips as-is; a dict missing the key falls back to the def
90
101
 
91
102
  ## Secrets
92
103
 
93
- `provider_options` and `model_options` **ride on the wire as plain data** — they are part of the dict and _will_ transfer. Prefer configuring API keys via environment/global provider configuration rather than `provider_options`. **Never serialize an agent whose options carry sensitive values** — and if a serialized definition ever does, handle it as a secret (encrypt it, keep it out of logs).
104
+ `provider_options` and `model_options` **ride on the wire as plain data** — they are part of the hash and _will_ transfer. Prefer configuring API keys via environment/global provider configuration rather than `provider_options`. **Never serialize an agent whose options carry sensitive values** — and if a serialized definition ever does, handle it as a secret (encrypt it, keep it out of logs).
94
105
 
95
106
  ## What does **not** transfer
96
107
 
@@ -83,6 +83,20 @@ model_options additional_model_request_fields: {
83
83
  }
84
84
  ```
85
85
 
86
+ ### cache_control
87
+
88
+ Enable prompt caching for models that support it (Claude, Nova). Riffer appends a single Converse `cachePoint` to the stable prefix — after the system array, or after the tools when there is no system prompt — so system instructions and tool definitions are reused across the calls in an agent loop and across conversation turns. The volatile message tail is never cached.
89
+
90
+ ```ruby
91
+ # 5-minute TTL (default)
92
+ model_options cache_control: {type: "ephemeral"}
93
+
94
+ # 1-hour TTL (model-dependent; Bedrock validates support)
95
+ model_options cache_control: {type: "ephemeral", ttl: "1h"}
96
+ ```
97
+
98
+ Caching is opt-in: omit `cache_control` and no cachePoint is sent. The breakpoint is only honored once the prefix clears the model's minimum token count; on models that don't support `cachePoint`, the Converse request errors. Verify hits via `response.token_usage.cache_read_tokens`.
99
+
86
100
  ## Example
87
101
 
88
102
  ```ruby
@@ -2,38 +2,49 @@
2
2
  # rbs_inline: enabled
3
3
 
4
4
  # Typed configuration object holding every class-level DSL setting on a
5
- # Riffer::Agent subclass.
6
- #
7
- # Each subclass of Riffer::Agent owns one Config, accessible via the class
8
- # method <tt>config</tt>. The class-level DSL (+model+, +instructions+, +uses_tools+,
9
- # etc.) reads and mutates this Config in place. Append-style DSL methods
10
- # (+use_mcp+, +guardrail+) are handled by the +add_mcp+ and +add_guardrail+
11
- # helpers below.
12
- #
13
- # Config stores Procs unresolved. Per-instance resolution happens elsewhere
14
- # (instructions, model, tools, tool runtime, skills).
5
+ # Riffer::Agent subclass. Procs are stored unresolved and resolved per-instance
6
+ # later.
15
7
  class Riffer::Agent::Config
16
8
  DEFAULT_MAX_STEPS = 16 #: Integer
17
9
 
10
+ # The configured agent identifier.
18
11
  attr_reader :identifier #: String?
12
+
13
+ # The configured model.
19
14
  attr_reader :model #: (String | Proc)?
15
+
16
+ # The configured instructions.
20
17
  attr_reader :instructions #: (String | Proc)?
18
+
19
+ # Options passed to the provider client.
21
20
  attr_accessor :provider_options #: Hash[Symbol, untyped]
21
+
22
+ # Options passed to generate_text/stream_text.
22
23
  attr_accessor :model_options #: Hash[Symbol, untyped]
24
+
25
+ # The configured structured-output schema.
23
26
  attr_reader :structured_output #: Riffer::Params?
27
+
28
+ # The maximum number of LLM call steps in the tool-use loop.
24
29
  attr_accessor :max_steps #: Numeric?
30
+
31
+ # The configured tools.
25
32
  attr_accessor :tools_config #: (Array[singleton(Riffer::Tool)] | Proc)?
33
+
34
+ # The accumulated +use_mcp+ tag configurations.
26
35
  attr_reader :mcp_configs #: Array[Hash[Symbol, untyped]]
36
+
37
+ # The configured tool runtime.
27
38
  attr_reader :tool_runtime #: (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)
39
+
40
+ # The configured skills.
28
41
  attr_accessor :skills_config #: Riffer::Skills::Config?
42
+
43
+ # Registered guardrail entries keyed by phase.
29
44
  attr_reader :guardrails #: Hash[Symbol, Array[Hash[Symbol, untyped]]]
30
45
 
31
- # Builds a new Config. All fields are optional; unset fields take the
32
- # documented defaults.
33
- #
34
- # Raises Riffer::ArgumentError if +model+ or +instructions+ is provided
35
- # as a non-String, non-Proc value (or as an empty String).
36
- #
46
+ # Builds a new Config. Raises Riffer::ArgumentError if +model+ or
47
+ # +instructions+ is invalid (e.g. an empty string).
37
48
  #--
38
49
  #: (?identifier: String?, ?model: (String | Proc)?, ?instructions: (String | Proc)?, ?provider_options: Hash[Symbol, untyped], ?model_options: Hash[Symbol, untyped], ?structured_output: Riffer::Params?, ?max_steps: Numeric?, ?tools_config: (Array[singleton(Riffer::Tool)] | Proc)?, ?mcp_configs: Array[Hash[Symbol, untyped]], ?tool_runtime: (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc), ?skills_config: Riffer::Skills::Config?, ?guardrails: Hash[Symbol, Array[Hash[Symbol, untyped]]]) -> void
39
50
  def initialize(
@@ -64,18 +75,14 @@ class Riffer::Agent::Config
64
75
  self.tool_runtime = tool_runtime
65
76
  end
66
77
 
67
- # Sets +identifier+. Accepts +nil+ or any value, coerced to String.
68
- #
78
+ # Sets +identifier+, coercing the value to String.
69
79
  #--
70
80
  #: (untyped) -> String?
71
81
  def identifier=(value)
72
82
  @identifier = value&.to_s
73
83
  end
74
84
 
75
- # Sets +structured_output+. Accepts a Riffer::Params instance or +nil+.
76
- #
77
- # Raises Riffer::ArgumentError on any other type.
78
- #
85
+ # Sets +structured_output+. Raises Riffer::ArgumentError on an invalid value.
79
86
  #--
80
87
  #: (Riffer::Params?) -> Riffer::Params?
81
88
  def structured_output=(value)
@@ -83,11 +90,7 @@ class Riffer::Agent::Config
83
90
  @structured_output = value
84
91
  end
85
92
 
86
- # Sets +tool_runtime+. Accepts a Riffer::Tools::Runtime subclass, a
87
- # Riffer::Tools::Runtime instance, or a Proc.
88
- #
89
- # Raises Riffer::ArgumentError on any other type.
90
- #
93
+ # Sets +tool_runtime+. Raises Riffer::ArgumentError on an invalid value.
91
94
  #--
92
95
  #: ((singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)) -> (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)
93
96
  def tool_runtime=(value)
@@ -96,10 +99,8 @@ class Riffer::Agent::Config
96
99
  @tool_runtime = value
97
100
  end
98
101
 
99
- # Sets +model+. Accepts a String ("provider/model"), a Proc, or +nil+.
100
- #
101
- # Raises Riffer::ArgumentError on non-String, non-Proc, or empty-String values.
102
- #
102
+ # Sets +model+. Raises Riffer::ArgumentError on an invalid value (e.g. an
103
+ # empty string).
103
104
  #--
104
105
  #: ((String | Proc)?) -> (String | Proc)?
105
106
  def model=(value)
@@ -107,10 +108,8 @@ class Riffer::Agent::Config
107
108
  @model = value
108
109
  end
109
110
 
110
- # Sets +instructions+. Accepts a String, a Proc, or +nil+.
111
- #
112
- # Raises Riffer::ArgumentError on non-String, non-Proc, or empty-String values.
113
- #
111
+ # Sets +instructions+. Raises Riffer::ArgumentError on an invalid value (e.g.
112
+ # an empty string).
114
113
  #--
115
114
  #: ((String | Proc)?) -> (String | Proc)?
116
115
  def instructions=(value)
@@ -121,20 +120,15 @@ class Riffer::Agent::Config
121
120
  # Appends an MCP tag entry to +mcp_configs+.
122
121
  #
123
122
  #--
124
- #: (String | Symbol) -> Array[Hash[Symbol, untyped]]
125
- def add_mcp(tag)
126
- @mcp_configs << {tags: [tag.to_sym]}
123
+ #: (String | Symbol, ?progressive: bool) -> Array[Hash[Symbol, untyped]]
124
+ def add_mcp(tag, progressive: true)
125
+ raise Riffer::ArgumentError, "progressive must be a boolean" unless progressive == true || progressive == false
126
+ @mcp_configs << {tags: [tag.to_sym], progressive: progressive}
127
127
  end
128
128
 
129
- # Appends a guardrail entry to +guardrails+ for the given phase.
130
- #
131
- # [phase] +:before+, +:after+, or +:around+. +:around+ appends to both
132
- # +:before+ and +:after+.
133
- # [klass] the Riffer::Guardrail subclass to register.
134
- # [options] options forwarded to the guardrail at runtime.
135
- #
136
- # Raises Riffer::ArgumentError on an invalid phase or non-Guardrail class.
137
- #
129
+ # Appends a guardrail entry to +guardrails+ for the given phase; +:around+
130
+ # appends to both +:before+ and +:after+. Raises Riffer::ArgumentError unless
131
+ # +phase+ is :before, :after, or :around.
138
132
  #--
139
133
  #: (Symbol, klass: singleton(Riffer::Guardrail), ?options: Hash[Symbol, untyped]) -> void
140
134
  def add_guardrail(phase, klass:, options: {})
@@ -164,6 +158,7 @@ class Riffer::Agent::Config
164
158
 
165
159
  private
166
160
 
161
+ #--
167
162
  #: (untyped, String) -> void
168
163
  def validate_string_or_proc!(value, name)
169
164
  return if value.nil? || value.is_a?(Proc)