riffer 0.41.0 → 0.42.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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/{AGENTS.md → .claude/CLAUDE.md} +0 -8
  3. data/.claude/rules/comments.md +13 -0
  4. data/{.agents → .claude/rules}/rbs-inline.md +28 -104
  5. data/.release-please-manifest.json +1 -1
  6. data/CHANGELOG.md +11 -0
  7. data/docs/AGENTS.md +24 -3
  8. data/docs/AGENT_LIFECYCLE.md +2 -2
  9. data/docs/TOOLS.md +18 -1
  10. data/lib/riffer/agent/run.rb +2 -4
  11. data/lib/riffer/agent.rb +3 -17
  12. data/lib/riffer/guardrail.rb +1 -1
  13. data/lib/riffer/helpers/identifier.rb +41 -0
  14. data/lib/riffer/providers/anthropic.rb +4 -0
  15. data/lib/riffer/providers/base.rb +4 -1
  16. data/lib/riffer/registrable.rb +81 -0
  17. data/lib/riffer/tool.rb +1 -0
  18. data/lib/riffer/tools/toolable.rb +2 -3
  19. data/lib/riffer/version.rb +1 -1
  20. data/lib/riffer.rb +3 -0
  21. data/sig/generated/riffer/agent.rbs +2 -12
  22. data/sig/generated/riffer/helpers/identifier.rbs +19 -0
  23. data/sig/generated/riffer/providers/base.rbs +2 -0
  24. data/sig/generated/riffer/registrable.rbs +51 -0
  25. data/sig/generated/riffer/tool.rbs +2 -0
  26. data/sig/generated/riffer/tools/toolable.rbs +3 -1
  27. data/sig/generated/riffer.rbs +4 -0
  28. data/sig/manual/riffer/agent.rbs +7 -0
  29. data/sig/manual/riffer/helpers/identifier.rbs +5 -0
  30. data/sig/manual/riffer/tool.rbs +7 -0
  31. metadata +11 -11
  32. data/.agents/architecture.md +0 -265
  33. data/.agents/code-style.md +0 -110
  34. data/.agents/providers.md +0 -54
  35. data/.agents/testing.md +0 -60
  36. data/CLAUDE.md +0 -1
  37. data/lib/riffer/helpers/class_name_converter.rb +0 -22
  38. data/sig/generated/riffer/helpers/class_name_converter.rbs +0 -12
  39. data/sig/manual/riffer/helpers/class_name_converter.rbs +0 -5
@@ -0,0 +1,51 @@
1
+ # Generated from lib/riffer/registrable.rb with RBS::Inline
2
+
3
+ # Registry of a class's named direct subclasses, keyed by identifier. Extend it
4
+ # onto a base class to look up subclasses in constant time via +find+ and +all+.
5
+ #
6
+ # class Riffer::Tool
7
+ # extend Riffer::Registrable
8
+ # end
9
+ #
10
+ # Riffer::Tool.find("weather_tool") # => WeatherTool
11
+ #
12
+ # @rbs module-self Class
13
+ module Riffer::Registrable : Class
14
+ @identifier_registry: Hash[String, Class]?
15
+
16
+ # Finds a registered subclass by identifier, or +nil+ when none matches.
17
+ # Only *named direct* subclasses are registered: grandchildren are not
18
+ # visible to a grandparent's +find+ (call +find+ on their direct parent
19
+ # instead), anonymous classes are never registered, and duplicate identifiers
20
+ # raise Riffer::DuplicateIdentifierError at first lookup.
21
+ #
22
+ # --
23
+ # : (String | Symbol) -> Class?
24
+ def find: (String | Symbol) -> Class?
25
+
26
+ # Returns all registered subclasses. Only *named direct* subclasses are
27
+ # registered: grandchildren are not included (call +all+ on their direct
28
+ # parent instead), anonymous classes are never registered, and duplicate
29
+ # identifiers raise Riffer::DuplicateIdentifierError at first lookup.
30
+ #
31
+ # --
32
+ # : () -> Array[Class]
33
+ def all: () -> Array[Class]
34
+
35
+ private
36
+
37
+ # Ruby invokes +inherited+ with +self+ bound to the direct superclass — the
38
+ # only registry the new subclass joins — so busting self's memo is exactly
39
+ # sufficient.
40
+ # --
41
+ # : (Class) -> void
42
+ def inherited: (Class) -> void
43
+
44
+ # --
45
+ # : () -> Hash[String, Class]
46
+ def identifier_registry: () -> Hash[String, Class]
47
+
48
+ # --
49
+ # : () -> Hash[String, Class]
50
+ def build_identifier_registry: () -> Hash[String, Class]
51
+ end
@@ -18,6 +18,8 @@
18
18
  class Riffer::Tool
19
19
  extend Riffer::Tools::Toolable
20
20
 
21
+ extend Riffer::Registrable
22
+
21
23
  # Executes the tool with the given arguments.
22
24
  # --
23
25
  # : (context: Riffer::Agent::Context?, **untyped) -> Riffer::Tools::Response
@@ -13,7 +13,9 @@
13
13
  # required :input, String
14
14
  # end
15
15
  # end
16
- module Riffer::Tools::Toolable
16
+ #
17
+ # @rbs module-self Module
18
+ module Riffer::Tools::Toolable : Module
17
19
  @kind: Symbol?
18
20
 
19
21
  @params_builder: Riffer::Params?
@@ -23,6 +23,10 @@ module Riffer
23
23
  class ToolExecutionError < Error
24
24
  end
25
25
 
26
+ # Raised when two registered subclasses share the same identifier.
27
+ class DuplicateIdentifierError < Error
28
+ end
29
+
26
30
  # Returns the Riffer configuration.
27
31
  #
28
32
  # --
@@ -0,0 +1,7 @@
1
+ # `Riffer::Agent` extends `Riffer::Registrable`, whose generic signatures return
2
+ # `Class`. Narrow them here so callers get the agent singleton type back.
3
+ class Riffer::Agent
4
+ def self.find: (String | Symbol) -> singleton(Riffer::Agent)?
5
+
6
+ def self.all: () -> Array[singleton(Riffer::Agent)]
7
+ end
@@ -0,0 +1,5 @@
1
+ # `Riffer::Helpers::Identifier` uses `extend self`; rbs-inline doesn't emit
2
+ # that, so re-extend here to expose its instance methods as singleton methods.
3
+ module Riffer::Helpers::Identifier
4
+ extend ::Riffer::Helpers::Identifier
5
+ end
@@ -0,0 +1,7 @@
1
+ # `Riffer::Tool` extends `Riffer::Registrable`, whose generic signatures return
2
+ # `Class`. Narrow them here so callers get the tool singleton type back.
3
+ class Riffer::Tool
4
+ def self.find: (String | Symbol) -> singleton(Riffer::Tool)?
5
+
6
+ def self.all: () -> Array[singleton(Riffer::Tool)]
7
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: riffer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.41.0
4
+ version: 0.42.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jake Bottrall
@@ -41,19 +41,15 @@ extra_rdoc_files:
41
41
  - LICENSE.txt
42
42
  - README.md
43
43
  files:
44
- - ".agents/architecture.md"
45
- - ".agents/code-style.md"
46
- - ".agents/providers.md"
47
- - ".agents/rbs-inline.md"
48
- - ".agents/testing.md"
49
44
  - ".bundle/config"
45
+ - ".claude/CLAUDE.md"
46
+ - ".claude/rules/comments.md"
47
+ - ".claude/rules/rbs-inline.md"
50
48
  - ".release-please-config.json"
51
49
  - ".release-please-manifest.json"
52
50
  - ".rubocop.yml"
53
51
  - ".ruby-version"
54
- - AGENTS.md
55
52
  - CHANGELOG.md
56
- - CLAUDE.md
57
53
  - CODE_OF_CONDUCT.md
58
54
  - Guardfile
59
55
  - LICENSE.txt
@@ -123,8 +119,8 @@ files:
123
119
  - lib/riffer/helpers.rb
124
120
  - lib/riffer/helpers/boolean.rb
125
121
  - lib/riffer/helpers/call_or_value.rb
126
- - lib/riffer/helpers/class_name_converter.rb
127
122
  - lib/riffer/helpers/dependencies.rb
123
+ - lib/riffer/helpers/identifier.rb
128
124
  - lib/riffer/mcp.rb
129
125
  - lib/riffer/mcp/authenticated_tool.rb
130
126
  - lib/riffer/mcp/client.rb
@@ -157,6 +153,7 @@ files:
157
153
  - lib/riffer/providers/open_router.rb
158
154
  - lib/riffer/providers/repository.rb
159
155
  - lib/riffer/providers/token_usage.rb
156
+ - lib/riffer/registrable.rb
160
157
  - lib/riffer/runner.rb
161
158
  - lib/riffer/runner/fibers.rb
162
159
  - lib/riffer/runner/sequential.rb
@@ -242,8 +239,8 @@ files:
242
239
  - sig/generated/riffer/helpers.rbs
243
240
  - sig/generated/riffer/helpers/boolean.rbs
244
241
  - sig/generated/riffer/helpers/call_or_value.rbs
245
- - sig/generated/riffer/helpers/class_name_converter.rbs
246
242
  - sig/generated/riffer/helpers/dependencies.rbs
243
+ - sig/generated/riffer/helpers/identifier.rbs
247
244
  - sig/generated/riffer/mcp.rbs
248
245
  - sig/generated/riffer/mcp/authenticated_tool.rbs
249
246
  - sig/generated/riffer/mcp/client.rbs
@@ -276,6 +273,7 @@ files:
276
273
  - sig/generated/riffer/providers/open_router.rbs
277
274
  - sig/generated/riffer/providers/repository.rbs
278
275
  - sig/generated/riffer/providers/token_usage.rbs
276
+ - sig/generated/riffer/registrable.rbs
279
277
  - sig/generated/riffer/runner.rbs
280
278
  - sig/generated/riffer/runner/fibers.rbs
281
279
  - sig/generated/riffer/runner/sequential.rbs
@@ -322,20 +320,22 @@ files:
322
320
  - sig/generated/riffer/version.rbs
323
321
  - sig/manifest.yaml
324
322
  - sig/manual/riffer.rbs
323
+ - sig/manual/riffer/agent.rbs
325
324
  - sig/manual/riffer/agent/run.rbs
326
325
  - sig/manual/riffer/agent/serializer.rbs
327
326
  - sig/manual/riffer/agent/session/repair.rbs
328
327
  - sig/manual/riffer/evals/evaluator_runner.rbs
329
328
  - sig/manual/riffer/helpers/boolean.rbs
330
329
  - sig/manual/riffer/helpers/call_or_value.rbs
331
- - sig/manual/riffer/helpers/class_name_converter.rbs
332
330
  - sig/manual/riffer/helpers/dependencies.rbs
331
+ - sig/manual/riffer/helpers/identifier.rbs
333
332
  - sig/manual/riffer/mcp.rbs
334
333
  - sig/manual/riffer/mcp/authenticated_tool.rbs
335
334
  - sig/manual/riffer/mcp/registry.rbs
336
335
  - sig/manual/riffer/mcp/tool_factory.rbs
337
336
  - sig/manual/riffer/providers.rbs
338
337
  - sig/manual/riffer/providers/repository.rbs
338
+ - sig/manual/riffer/tool.rbs
339
339
  - sig/manual/riffer/tracing.rbs
340
340
  - sig/manual/riffer/tracing/capture.rbs
341
341
  - sig/manual/riffer/tracing/no_op.rbs
@@ -1,265 +0,0 @@
1
- # Architecture
2
-
3
- ## Core Components
4
-
5
- ### Agent (`lib/riffer/agent.rb`)
6
-
7
- Base class for AI agents. Subclass and use DSL methods `model`, `instructions`, `structured_output`, and `skills` to configure. Orchestrates message flow, LLM calls, tool execution, structured output parsing, and skill activation via a generate/stream loop.
8
-
9
- Class-level DSL settings live on a per-class `Riffer::Agent::Config` object accessible via `MyAgent.config`. DSL methods read and mutate this Config in place. Instances read from `@config`, which defaults to `self.class.config` but can be replaced via `Agent.new(config: ...)`.
10
-
11
- ```ruby
12
- class EchoAgent < Riffer::Agent
13
- model 'openai/gpt-5-mini' # provider/model
14
- instructions 'You are an assistant that repeats what the user says.'
15
- end
16
-
17
- agent = EchoAgent.new
18
- puts agent.generate('Hello world')
19
- ```
20
-
21
- `instructions` also accepts a Proc for dynamic instructions resolved at generate time. The Proc receives the `context` hash:
22
-
23
- ```ruby
24
- class PersonalAgent < Riffer::Agent
25
- model 'openai/gpt-5-mini'
26
- instructions ->(context) { "You are assisting #{context[:name]}" }
27
- end
28
-
29
- PersonalAgent.generate('Hello!', context: { name: 'Jane' })
30
- ```
31
-
32
- ### Providers (`lib/riffer/providers/`)
33
-
34
- Adapters for LLM APIs. The base class uses a template-method pattern — `generate_text` and `stream_text` orchestrate the flow, delegating to five hook methods each provider implements:
35
-
36
- - `build_request_params(messages, model, options)` — convert messages, tools, and options into SDK params
37
- - `execute_generate(params)` — call the SDK and return the raw response
38
- - `execute_stream(params, yielder)` — call the streaming SDK, mapping events to the yielder
39
- - `extract_token_usage(response)` — pull token counts from the SDK response
40
- - `extract_content(response)` — extract text content from the SDK response
41
- - `extract_tool_calls(response)` — extract tool calls from the SDK response
42
-
43
- Providers are registered in `Riffer::Providers::Repository::REPO` with identifiers (e.g., `openai`, `amazon_bedrock`).
44
-
45
- Each provider declares a preferred skill adapter via `self.skills_adapter(model = nil)`. Default is Markdown; Anthropic returns XML; Amazon Bedrock returns XML when the model identifier matches an Anthropic model (e.g. `anthropic.claude-…` or `us.anthropic.claude-…`); Mock returns XML when the model name contains `claude`. The agent passes the resolved model identifier so proxy providers (Bedrock, Mock) can pick the right adapter without per-agent overrides.
46
-
47
- ### Skills (`lib/riffer/skills/`)
48
-
49
- Support for the [Agent Skills spec](https://agentskills.io/). Skills are packaged as directories containing `SKILL.md` files with YAML frontmatter. The framework discovers skills through a pluggable backend, injects metadata into the system prompt, and provides a tool (`skill_activate`) for the LLM to load full skill instructions on demand.
50
-
51
- - `Config` - DSL configuration object (`backend`, `adapter`, `activate`)
52
- - `Backend` - base class interface (`list_skills`, `read_skill`)
53
- - `FilesystemBackend` - built-in filesystem scanner
54
- - `Frontmatter` - parsed YAML frontmatter value object with `.parse(raw)` class method
55
- - `Context` - coordinates discovery, activation, caching, and prompt rendering for a generation cycle
56
- - `Adapter` - base class for skill adapters (`render_catalog`, `activate_tool`)
57
- - `MarkdownAdapter` - default Markdown skill adapter
58
- - `XmlAdapter` - XML skill adapter for Anthropic/Claude
59
- - `ActivateTool` - default tool the LLM calls to activate a skill
60
-
61
- ### Messages (`lib/riffer/messages/`)
62
-
63
- Typed message objects that extend `Riffer::Messages::Base`:
64
-
65
- - `System` - system instructions
66
- - `User` - user input (supports file attachments via `Riffer::Messages::FilePart`)
67
- - `Assistant` - AI responses
68
- - `Tool` - tool execution results
69
-
70
- `Riffer::Messages::FilePart` represents file attachments (images and documents) that can be included with User messages. Supports file paths, URLs, and raw base64 data.
71
-
72
- The `Converter` module handles hash-to-object conversion, including file hash-to-`FilePart` conversion.
73
-
74
- ### StreamEvents (`lib/riffer/stream_events/`)
75
-
76
- Structured events for streaming responses:
77
-
78
- - `TextDelta` - incremental text chunks
79
- - `TextDone` - completion signals
80
- - `ReasoningDelta` - reasoning process chunks
81
- - `ReasoningDone` - reasoning completion
82
- - `WebSearchStatus` - web search status updates
83
- - `WebSearchDone` - web search completion with query and sources
84
- - `Interrupt` - callback interrupted the agent loop
85
-
86
- ### Per-Call State Reset
87
-
88
- Each call to `generate` or `stream` resets `context`, tools, tool runtime, model, skills state, and the interrupted flag via `prepare_run`. Only the message history and cumulative `token_usage` persist across calls. This means `context:` must be passed on every call.
89
-
90
- ### Stopping the Loop Early
91
-
92
- Two mechanisms can stop the agent loop before the LLM finishes naturally:
93
-
94
- **Guardrail tripwires** — declarative policy enforcement registered at class level. A `:before` guardrail can block the request before the LLM is called; an `:after` guardrail can block the response. Tripwires are not resumable — the caller must change the input and start over. `Response#blocked?` returns `true`.
95
-
96
- **Callback interrupts** — imperative flow control via `on_message` callbacks. Use `throw :riffer_interrupt` to pause the loop at any point. `Response#interrupted?` returns `true`. In streaming, yields an `Interrupt` event.
97
-
98
- ### Resuming After an Interrupt
99
-
100
- Two resume paths:
101
-
102
- - **In-memory** — call `generate` or `stream` again with a string on the same agent instance. The message history is preserved and the new user message is appended.
103
- - **Cross-process** — pass persisted messages as an array to a new agent instance. Array input uses messages as-is (no system message prepend). Passing an array to an agent that already has messages raises `Riffer::ArgumentError`.
104
-
105
- ```ruby
106
- agent.generate('Continue') # in-memory resume
107
- MyAgent.new.stream(persisted_messages) # cross-process resume
108
- ```
109
-
110
- On resume, `execute_pending_tool_calls` detects tool calls from the last assistant message that lack corresponding tool result messages and executes them before entering the LLM loop. This handles the case where an interrupt fired mid-way through tool execution.
111
-
112
- ### Runner (`lib/riffer/runner.rb`)
113
-
114
- Concurrency primitive for batch execution. Subclasses implement `#map(items, context: nil, &block)` to control how items are processed. The `context` keyword carries the agent's context hash, enabling runners that need it for job serialization or routing.
115
-
116
- Built-in runners:
117
-
118
- - `Sequential` — processes items in the current thread via `Array#map`
119
- - `Threaded` — processes items concurrently using a thread pool with configurable `max_concurrency`
120
-
121
- ```ruby
122
- runner = Riffer::Runner::Threaded.new(max_concurrency: 3)
123
- runner.map(items, context: ctx) { |item| process(item) }
124
- ```
125
-
126
- ### Tools::Runtime (`lib/riffer/tools/runtime.rb`)
127
-
128
- Composes with a Runner to execute tool calls. Provides `#execute` as the public entry point and `#around_tool_call` as a hook for instrumentation. Passes the agent context through to the runner.
129
-
130
- Built-in runtimes:
131
-
132
- - `Inline` — uses `Runner::Sequential` (default)
133
- - `Threaded` — uses `Runner::Threaded`
134
-
135
- Context flow: `Agent#execute_tool_calls` → `Tools::Runtime#execute(tool_calls, tools:, context:)` → `Runner#map(tool_calls, context:) { dispatch }` → `Tool#call(context:, **args)`
136
-
137
- ### MCP Integration (`lib/riffer/mcp/`)
138
-
139
- Register third-party MCP servers globally; agents opt-in by tag via `use_mcp`. Tags are application-defined (manifests may list several; any overlap with `use_mcp` opts in—see `docs/MCP.md`).
140
-
141
- ```ruby
142
- Riffer::Mcp.register(
143
- name: "github",
144
- tags: [:github],
145
- endpoint: "https://mcp.github.com",
146
- discovery_headers: -> { {"Authorization" => "Bearer #{ENV['GITHUB_TOKEN']}"} }
147
- )
148
-
149
- # Optional: per-run tools/call headers (see docs/MCP.md)
150
- Riffer.configure { |c| c.mcp.credentials = ->(manifest:, matched_tags:, context:) { ... } }
151
-
152
- class ResearchAgent < Riffer::Agent
153
- model "openai/gpt-5-mini"
154
- use_mcp :github # picks up any :github-tagged registration
155
- use_mcp :search, on_pending: :wait # per-call override
156
- end
157
- ```
158
-
159
- Key types: `Manifest` (`discovery_headers`, optional `credentials_scope` hint), `Registry` (thread-safe store), `Registration` (spawns discovery thread → `ToolFactory`), `Client` (wraps `mcp` gem), `AuthenticatedTool` (wraps MCP tools when `credentials` proc is set).
160
-
161
- **on_pending strategies** (global default `:ignore`): `:ignore` skips the server; `:wait` blocks until ready, re-raises failed discovery immediately, or times out; `:raise` re-raises failed discovery or `NotReadyError` while still pending.
162
-
163
- ## Key Patterns
164
-
165
- - Model config accepts a `provider/model` string (e.g., `openai/gpt-5-mini`) or a Proc/lambda that returns one
166
- - Configuration via `Riffer.configure { |c| c.openai.api_key = "..." }`
167
- - Providers use `depends_on` helper for runtime dependency checking
168
- - Zeitwerk for autoloading - file structure must match module/class names
169
-
170
- ## Project Structure
171
-
172
- ```
173
- examples/
174
- evaluators/ # Reference evaluator implementations (copy-paste)
175
- guardrails/ # Reference guardrail implementations (copy-paste)
176
- lib/
177
- riffer.rb # Main entry point, uses Zeitwerk for autoloading
178
- riffer/
179
- version.rb # VERSION constant
180
- config.rb # Configuration class
181
- agent.rb # Agent class
182
- agent/
183
- config.rb # Per-class DSL configuration value object
184
- session.rb # Conversation handle (message array + invariants)
185
- session/
186
- repair.rb # tool_use ↔ tool_result invariant repair
187
- structured_output.rb # Structured output schema wrapper
188
- structured_output/
189
- result.rb # Parse/validation result object
190
- messages.rb # Messages namespace/module
191
- providers.rb # Providers namespace/module
192
- params.rb # Parameter collection with DSL and validation
193
- params/
194
- param.rb # Single parameter definition (shared by tools and structured output)
195
- boolean.rb # Boolean sentinel type
196
- stream_events.rb # Stream events namespace/module
197
- skills.rb # Skills namespace/module
198
- skills/
199
- config.rb # DSL configuration object
200
- adapter.rb # Adapter base class (render_catalog, activate_tool)
201
- markdown_adapter.rb # Default Markdown skill adapter
202
- xml_adapter.rb # XML skill adapter for Anthropic/Claude
203
- backend.rb # Backend base class (interface)
204
- filesystem_backend.rb # Built-in filesystem backend
205
- frontmatter.rb # Parsed YAML frontmatter value object with .parse
206
- context.rb # Skills context for a generation cycle
207
- activate_tool.rb # Default skill_activate tool
208
- helpers/
209
- class_name_converter.rb # Class name conversion utilities
210
- dependencies.rb # Dependency management
211
- messages/
212
- base.rb # Base message class
213
- assistant.rb # Assistant message
214
- converter.rb # Message converter
215
- system.rb # System message
216
- user.rb # User message
217
- tool.rb # Tool message
218
- file_part.rb # File attachment (images and documents)
219
- providers/
220
- base.rb # Base provider class
221
- open_ai.rb # OpenAI provider
222
- amazon_bedrock.rb # Amazon Bedrock provider
223
- anthropic.rb # Anthropic provider
224
- repository.rb # Provider registry
225
- mock.rb # Mock provider
226
- stream_events/
227
- base.rb # Base stream event
228
- interrupt.rb # Interrupt event
229
- text_delta.rb # Text delta event
230
- text_done.rb # Text done event
231
- reasoning_delta.rb # Reasoning delta event
232
- reasoning_done.rb # Reasoning done event
233
- web_search_status.rb # Web search status event
234
- web_search_done.rb # Web search done event
235
- mcp.rb # MCP public API + error classes
236
- mcp/
237
- manifest.rb # Server config value object
238
- registry.rb # Thread-safe global store
239
- registration.rb # Per-server state + discovery thread
240
- client.rb # Thin mcp gem wrapper
241
- authenticated_tool.rb # Wraps MCP tools when credentials proc is configured
242
- tool_factory.rb # Generates Riffer::Tool subclasses from MCP tools
243
- test/
244
- test_helper.rb # Minitest configuration with VCR
245
- riffer_test.rb # Main module tests
246
- riffer/
247
- [feature]_test.rb # Feature tests mirror lib/riffer/ structure
248
- ```
249
-
250
- ## Configuration Example
251
-
252
- ```ruby
253
- Riffer.configure do |config|
254
- config.openai.api_key = ENV['OPENAI_API_KEY']
255
- end
256
- ```
257
-
258
- ## Streaming Example
259
-
260
- ```ruby
261
- agent = EchoAgent.new
262
- agent.stream('Tell me a story').each do |event|
263
- print event.content
264
- end
265
- ```
@@ -1,110 +0,0 @@
1
- # Code Style
2
-
3
- ## Formatting
4
-
5
- - Use RuboCop for linting and formatting
6
- - Config lives in `.rubocop.yml`, with cops sorted alphabetically
7
- - Run `bin/lint` to check, `bin/lint -a` to auto-fix (safe autocorrect), `bin/lint -A` for unsafe autocorrect
8
- - Never silence violations with inline `rubocop:disable` comments — fix them, or when an exception is genuinely warranted add a narrowly scoped override in `.rubocop.yml` with a comment explaining why
9
-
10
- ## Required Header
11
-
12
- All Ruby files in `lib/` must include:
13
-
14
- ```ruby
15
- # frozen_string_literal: true
16
- # rbs_inline: enabled
17
- ```
18
-
19
- ## Error Handling
20
-
21
- Define custom errors as subclasses of `Riffer::Error`:
22
-
23
- ```ruby
24
- class MyCustomError < Riffer::Error
25
- end
26
- ```
27
-
28
- ## Comments & Documentation
29
-
30
- 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)).
31
-
32
- **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:`).
33
-
34
- ### Public surface
35
-
36
- 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**:
37
-
38
- - **One verb-first sentence, one line.** `Creates a new agent.`, `Serializes the definition to JSON.`
39
- - 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".
40
- - If a description needs more than one sentence to say _what_ it does, that's a smell the method does too much.
41
- - **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).
42
-
43
- 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 `#:`.
44
-
45
- ```ruby
46
- # Serializes the agent definition to a transferable JSON payload.
47
- #--
48
- #: (Riffer::Agent) -> String
49
- def serialize(agent)
50
-
51
- # The agent's display name.
52
- attr_reader :name #: String
53
- ```
54
-
55
- ### Private methods
56
-
57
- 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.
58
-
59
- ### Inline comments
60
-
61
- 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.
62
-
63
- ### No history
64
-
65
- 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.
66
-
67
- ### RDoc mechanics
68
-
69
- **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.
70
-
71
- **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).
72
-
73
- ```ruby
74
- # Builds a param from a schema hash.
75
- # Raises Riffer::ArgumentError if the schema is missing a +type+.
76
- ```
77
-
78
- **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/`.
79
-
80
- **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>`.
81
-
82
- **Internal APIs.** Mark with `:nodoc:` to exclude from generated documentation:
83
-
84
- ```ruby
85
- def internal_method # :nodoc:
86
- end
87
- ```
88
-
89
- ## Hash Key Convention
90
-
91
- - Use **symbol keys** for all internal hashes
92
- - Use `JSON.parse(str, symbolize_names: true)` at parse boundaries — never `JSON.parse` followed by `transform_keys(&:to_sym)`
93
- - String keys are only used at serialization boundaries (JSON Schema output, external API payloads)
94
- - Do not write dual-access patterns like `hash[:key] || hash["key"]` — normalize to symbol keys at the boundary instead
95
-
96
- ## Reserved Tool Identifiers
97
-
98
- - Internal-use-only tools use plain descriptive names without a prefix (e.g. `mcp_search`, `mcp_call`, `evaluation`, `skill_activate`)
99
-
100
- ## Module Structure
101
-
102
- ```ruby
103
- # frozen_string_literal: true
104
-
105
- module Riffer::Feature
106
- class MyClass
107
- # Implementation
108
- end
109
- end
110
- ```
data/.agents/providers.md DELETED
@@ -1,54 +0,0 @@
1
- # Adding a New Provider
2
-
3
- ## Steps
4
-
5
- 1. Create `lib/riffer/providers/your_provider.rb` extending `Riffer::Providers::Base`
6
- 2. Implement the required hook methods (see [Custom Providers](../docs/providers/CUSTOM_PROVIDERS.md) for the full API)
7
- 3. Register in `Riffer::Providers::Repository::REPO`
8
- 4. Add provider config to `Riffer::Config` if needed — a `Struct` with credential members plus a `client` member
9
- 5. Create tests in `test/riffer/providers/your_provider_test.rb`
10
-
11
- ## Constructor and client contract
12
-
13
- - Constructors take **no arguments** — define one only when the provider needs `depends_on`, and give it no parameters. Credentials live in config; never accept them as kwargs or hold them in ivars.
14
- - Never hold a client ivar; call the private `client` method from `execute_generate`/`execute_stream`. Base resolves: `global_client` (a client instance, or a no-argument Proc resolved on every call) → memoized `build_client`.
15
- - Providers hold no agent state — no context, no reference to the owning agent. Client selection is process-global by design, so a configured Proc takes no arguments.
16
- - Implement `build_client` (build the SDK client by reading `Riffer.config.<provider>.<credential>` directly) and override `global_client` to return `Riffer.config.<provider>.client`.
17
- - **Never pass an SDK an explicit nil credential.** Build the kwargs as a hash and `.compact` it, so an unset value stays _absent_: SDKs distinguish absent from nil to decide whether to read their own env vars, and an explicit nil suppresses that. Passing `base_url: nil` skips `OPENAI_BASE_URL` and pins requests to api.openai.com; passing `region: nil` makes the AWS SDK raise `MissingRegionError` even with `AWS_REGION` exported.
18
- - **Exception — a provider borrowing another vendor's SDK** (`OpenRouter` and `AzureOpenAI` reuse `::OpenAI::Client`) must keep its credential and endpoint concrete, nil included. Compacting there would let the OpenAI SDK fall back to `OPENAI_API_KEY` / `OPENAI_BASE_URL` and send one vendor's credential to another's endpoint. Compact a key only when the SDK's env fallback for it names the same service the provider talks to.
19
-
20
- ## Architecture
21
-
22
- The base class uses the **template method** pattern. The public methods `generate_text` and `stream_text` orchestrate the flow, delegating to hook methods that each provider implements:
23
-
24
- ```
25
- generate_text
26
- ├─ build_request_params
27
- ├─ execute_generate
28
- ├─ extract_content
29
- ├─ extract_tool_calls
30
- └─ extract_token_usage
31
-
32
- stream_text
33
- ├─ build_request_params
34
- └─ execute_stream
35
- ```
36
-
37
- ## Registration
38
-
39
- Add to `Riffer::Providers::Repository::REPO`:
40
-
41
- ```ruby
42
- REPO = {
43
- # ... existing providers
44
- your_provider: -> { YourProvider }
45
- }.freeze
46
- ```
47
-
48
- ## Dependencies
49
-
50
- Use `depends_on` helper for runtime dependency checking if your provider requires external gems.
51
-
52
- ## Reference
53
-
54
- For hook method signatures, structured output handling, file handling, and complete examples, see [Custom Providers](../docs/providers/CUSTOM_PROVIDERS.md).