llm.rb 12.6.0 → 13.1.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 (88) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +571 -13
  3. data/LICENSE +21 -93
  4. data/README.md +183 -167
  5. data/bin/llm.rb +124 -0
  6. data/data/deepinfra.json +3 -0
  7. data/data/xai.json +1 -1
  8. data/lib/llm/a2a.rb +1 -1
  9. data/lib/llm/active_record/acts_as_llm.rb +6 -6
  10. data/lib/llm/agent.rb +136 -27
  11. data/lib/llm/buffer.rb +85 -3
  12. data/lib/llm/compactor/null.rb +19 -0
  13. data/lib/llm/compactor/truncate.rb +80 -0
  14. data/lib/llm/compactor.rb +42 -124
  15. data/lib/llm/context.rb +31 -37
  16. data/lib/llm/contract.rb +4 -25
  17. data/lib/llm/function/array.rb +18 -17
  18. data/lib/llm/function/async/group.rb +54 -0
  19. data/lib/llm/function/async/reactor.rb +48 -0
  20. data/lib/llm/function/async/task.rb +83 -0
  21. data/lib/llm/function/fiber/group.rb +46 -0
  22. data/lib/llm/function/fiber/task.rb +62 -0
  23. data/lib/llm/function/{fork_group.rb → fork/group.rb} +14 -5
  24. data/lib/llm/function/fork/job.rb +2 -2
  25. data/lib/llm/function/fork/task.rb +19 -10
  26. data/lib/llm/function/group.rb +40 -0
  27. data/lib/llm/function/{ractor_group.rb → ractor/group.rb} +13 -5
  28. data/lib/llm/function/ractor/job.rb +9 -3
  29. data/lib/llm/function/ractor/mailbox.rb +2 -0
  30. data/lib/llm/function/ractor/task.rb +23 -15
  31. data/lib/llm/function/{call_group.rb → sequential/group.rb} +12 -8
  32. data/lib/llm/function/sequential/task.rb +49 -0
  33. data/lib/llm/function/task.rb +25 -48
  34. data/lib/llm/function/thread/group.rb +46 -0
  35. data/lib/llm/function/thread/task.rb +60 -0
  36. data/lib/llm/function.rb +54 -65
  37. data/lib/llm/loop_guard.rb +1 -2
  38. data/lib/llm/mcp.rb +22 -0
  39. data/lib/llm/object.rb +2 -1
  40. data/lib/llm/provider.rb +6 -3
  41. data/lib/llm/providers/anthropic.rb +1 -1
  42. data/lib/llm/providers/bedrock/request_adapter.rb +1 -1
  43. data/lib/llm/providers/google.rb +2 -2
  44. data/lib/llm/providers/mistral.rb +1 -1
  45. data/lib/llm/providers/ollama.rb +1 -1
  46. data/lib/llm/providers/openai/responses.rb +1 -1
  47. data/lib/llm/providers/openai.rb +1 -1
  48. data/lib/llm/repl/{transcript.rb → buffer.rb} +34 -21
  49. data/lib/llm/repl/command.rb +47 -13
  50. data/lib/llm/repl/commands/compact.rb +33 -0
  51. data/lib/llm/repl/commands/help.rb +3 -5
  52. data/lib/llm/repl/input.rb +80 -15
  53. data/lib/llm/repl/markdown/table.rb +80 -0
  54. data/lib/llm/repl/markdown.rb +33 -3
  55. data/lib/llm/repl/node.rb +37 -0
  56. data/lib/llm/repl/status.rb +4 -4
  57. data/lib/llm/repl/stream.rb +12 -5
  58. data/lib/llm/repl/walker.rb +46 -0
  59. data/lib/llm/repl/window.rb +31 -32
  60. data/lib/llm/repl.rb +70 -38
  61. data/lib/llm/response.rb +10 -0
  62. data/lib/llm/schema/leaf.rb +5 -0
  63. data/lib/llm/schema/object.rb +11 -5
  64. data/lib/llm/sequel/plugin.rb +6 -6
  65. data/lib/llm/skill.rb +20 -4
  66. data/lib/llm/stream.rb +24 -17
  67. data/lib/llm/tool.rb +20 -4
  68. data/lib/llm/tools/chdir.rb +0 -2
  69. data/lib/llm/tools/{swap_text.rb → edit-file.rb} +3 -3
  70. data/lib/llm/tools/git.rb +11 -4
  71. data/lib/llm/tools/mkdir.rb +4 -1
  72. data/lib/llm/tools/pwd.rb +0 -2
  73. data/lib/llm/tools/read_file.rb +0 -2
  74. data/lib/llm/tools/rg.rb +11 -4
  75. data/lib/llm/tools/ruby.rb +46 -0
  76. data/lib/llm/tools/shell.rb +11 -4
  77. data/lib/llm/tools/utils.rb +31 -0
  78. data/lib/llm/tracer/pretty_logger.rb +127 -0
  79. data/lib/llm/tracer.rb +1 -0
  80. data/lib/llm/version.rb +1 -1
  81. data/lib/llm.rb +25 -5
  82. data/llm.gemspec +11 -5
  83. data/resources/deepdive.md +45 -1198
  84. metadata +39 -17
  85. data/lib/llm/function/call_task.rb +0 -46
  86. data/lib/llm/function/fiber_group.rb +0 -105
  87. data/lib/llm/function/task_group.rb +0 -97
  88. data/lib/llm/function/thread_group.rb +0 -102
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1660ea34a112d4c31eb8c9a51000ecd244b0f5629a4f763eceecd734d96f765b
4
- data.tar.gz: 8040eefe13ffc48a40181642dad749d6abd7d16950f5d9e379ef006a0bb5af0c
3
+ metadata.gz: bb5c6136080e0136309987a4e042e6f646c26de39e38d6422bb3904fc665bf5f
4
+ data.tar.gz: d832b9c19f4173ba6e5631ffdc57f495eb4a9a8fb0b657801de185707bc4085e
5
5
  SHA512:
6
- metadata.gz: cc1821c04eb0f996ca3e5d86d1722565e1fe2bc13351f3771c57de556d7ee9e97d930b3516ec70dea9686fad6443498c40d50584c6065a95242318f054a98d83
7
- data.tar.gz: 3cc468ef1332c6f39d6b34a6a11fafe91eaf459ed80d5e79709f8106278fcb6fa1ba4f67b701a38539c3cba4d6967ad88858cccb4bb6b5b12690290036ad2afb
6
+ metadata.gz: 7ba4417a8c5b48a28b2baf04a2960374eae28c44dc2ec19e8f79da9a95f75fdf8cb5f1c749e6bfa1b1f6d1c790723e2005cb576f94f118997bb5e8b0f5d53a63
7
+ data.tar.gz: 0105af6bcaaec50930546e3a5a7207b8cf65e30bbea33359784061159748a3e2f3afb6e2b599eaad904e0c477d9827bb444c698315e56d43572be6ba86fe98b8
data/CHANGELOG.md CHANGED
@@ -15,11 +15,569 @@
15
15
 
16
16
  ## What's next
17
17
 
18
+ *No unreleased changes yet. Check back after the next release.*
19
+
20
+ ## v13.1.0
21
+
22
+ Changes since `v13.0.0`.
23
+
24
+ This release adds `LLM::Agent` class DSL attributes (`path`, `description`),
25
+ extends skills with file-path loading and the `tools: all` directive, adds new
26
+ built-in tools (`LLM::Tool::Ruby`, `LLM::Tool::EditFile`), introduces the
27
+ `LLM::Tracer::PrettyLogger` for human-readable tracing, renames `Transcript`
28
+ to `Buffer` across the REPL, ships a `bin/llm.rb` CLI entry point, and fixes
29
+ several agent and tool bugs around persistence, interruption, and naming.
30
+
31
+ ### Core
32
+
33
+ * **add post install message with deepdive link** <br>
34
+ The gemspec now includes a `post_install_message` that points users to
35
+ the deepdive guide at `https://r.uby.dev/llm/deepdive` after installation,
36
+ making it easier for new users to discover the project documentation.
37
+
38
+ ### Agent
39
+
40
+ * **add `description` class DSL and instance method** <br>
41
+ [`LLM::Agent`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html) now
42
+ has a `description` class DSL (`description "release engineer"`) and a
43
+ corresponding `#description` instance method. The description is an
44
+ optional self-documenting string that serves as a brief summary of the
45
+ agent's purpose. It can be set via the class DSL,
46
+ `LLM::Agent.set(description: ...)`, or `LLM::Agent.new(description: ...)`.
47
+
48
+ * **add `path` class DSL and instance method** <br>
49
+ [`LLM::Agent`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html) now
50
+ has a `path` class DSL (`path "contexts/admin.json"`) and a
51
+ corresponding `#path` instance method. When a path is set, the agent
52
+ automatically restores its conversation history from that file on
53
+ initialization and saves it back after each `talk` or `ask` turn,
54
+ making session persistence across process restarts transparent.
55
+
56
+ ### Skills
57
+
58
+ * **accept a path to a markdown file** <br>
59
+ [`LLM::Skill.load`](https://r.uby.dev/api-docs/llm.rb/LLM/Skill.html#load-class_method)
60
+ now accepts a path to a markdown file in addition to a directory path.
61
+ When given a file path, the file is read directly instead of looking for
62
+ a `SKILL.md` inside a directory. This makes it possible to load a single
63
+ markdown file as a skill without placing it in a dedicated directory.
64
+
65
+ * **extend with `all` keyword for loading the full tool registry** <br>
66
+ `LLM::Skill` now supports `tools: all` (or `tools: "*"`) in the frontmatter
67
+ to load all tools from the global
68
+ [`LLM::Tool.registry`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool.html#registry-class_method).
69
+ Previously, the `tools:` frontmatter only accepted `inherit`, an array of tool
70
+ names, or nothing. The new `all` keyword makes it possible to give a skill
71
+ access to every registered tool without listing them individually.
72
+
73
+ ### Tools
74
+
75
+ * **add `LLM::Tool::Ruby` for executing Ruby code in a subprocess** <br>
76
+ [`LLM::Tool::Ruby`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool/Ruby.html)
77
+ is a new built-in tool that runs a string of Ruby code in a separate
78
+ Ruby process with a configurable timeout (default 15s). The code runs
79
+ in an isolated address space unaware of its parent, making it useful
80
+ for safe(ish) dynamic code execution. It must be required explicitly
81
+ with `require "llm/tools/ruby"` and requires the `test-cmd.rb` gem.
82
+
83
+ * **rename `LLM::Tool::SwapText` to `LLM::Tool::EditFile`** <br>
84
+ The `SwapText` tool has been renamed to
85
+ [`LLM::Tool::EditFile`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool/EditFile.html)
86
+ to better match the naming of sibling tools (`ReadFile`, `WriteFile`).
87
+ The old `require "llm/tools/swap_text"` path no longer exists; use
88
+ `require "llm/tools/edit-file"` instead.
89
+
90
+ ### Tracer
91
+
92
+ * **add `LLM::Tracer::PrettyLogger` for human-readable tracing** <br>
93
+ [`LLM::Tracer::PrettyLogger`](https://r.uby.dev/api-docs/llm.rb/LLM/Tracer/PrettyLogger.html)
94
+ is a new tracer that writes human-readable request and tool-call logs to a
95
+ console or file. Unlike the structured JSON output of
96
+ `LLM::Tracer::Logger`, the pretty logger emits single-line entries with
97
+ inline context, making it easier to follow agent activity at a glance.
98
+ It writes to `$stderr` by default and accepts an `io:` option for file
99
+ output.
100
+
101
+ ### Repl
102
+
103
+ * **rename `LLM::Repl::Transcript` to `LLM::Repl::Buffer`** <br>
104
+ `LLM::Repl::Transcript` has been renamed to
105
+ [`LLM::Repl::Buffer`](https://r.uby.dev/api-docs/llm.rb/LLM/Repl/Buffer.html)
106
+ to better reflect its role as a conversation state manager. The old
107
+ `start` and `finish` methods have been renamed to `open` and `close`
108
+ respectively. The public accessor on
109
+ [`LLM::Repl`](https://r.uby.dev/api-docs/llm.rb/LLM/Repl.html) has been
110
+ renamed from `transcript` to `buffer`.
111
+
112
+ * **add `write_message` for formatted message writing** <br>
113
+ [`LLM::Repl::Buffer#write_message`](https://r.uby.dev/api-docs/llm.rb/LLM/Repl/Buffer.html#write_message-instance_method)
114
+ and
115
+ [`LLM::Repl#write_message`](https://r.uby.dev/api-docs/llm.rb/LLM/Repl.html#write_message-instance_method)
116
+ provide a convenience method that takes a username and content string,
117
+ formatting the output with a bold `user:` label and a trailing newline.
118
+ This is simpler than the equivalent sequence of `write` calls.
119
+
120
+ * **add `Command#write_message` and refactor `Command#write`** <br>
121
+ [`LLM::Command#write_message`](https://r.uby.dev/api-docs/llm.rb/LLM/Repl/Command.html#write_message-instance_method)
122
+ provides a convenience method that takes a username and content string,
123
+ matching the same interface on `LLM::Repl` and `LLM::Buffer`. The
124
+ `Command#write` method is now implemented on top of `write_message`,
125
+ always prefixing output with `command(<name>): `. The `who:` keyword
126
+ argument previously accepted by `write` has been removed; use
127
+ `write_message` instead.
128
+
129
+ * **display pre-existing agent messages when the repl starts** <br>
130
+ When
131
+ [`LLM::Agent#repl`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html#repl-instance_method)
132
+ starts, any messages already in the agent's buffer are now rendered
133
+ in the REPL window. Previously the REPL started with an empty
134
+ transcript even when the agent carried prior conversation history,
135
+ making it harder to resume a session. Tool-call and tool-return
136
+ messages are skipped to avoid visual noise.
137
+
138
+ ### CLI
139
+
140
+ * **add `bin/llm.rb` for launching the REPL from the command line** <br>
141
+ A new executable script (`bin/llm.rb`) provides a convenient way to start
142
+ an interactive REPL session directly from the terminal. It auto-detects
143
+ the provider from environment variables like `OPENAI_API_KEY`, supports
144
+ a `-p PROVIDER` flag for explicit provider selection, a `-t` flag for
145
+ temporary (non-persistent) sessions, and `-h` for help. Sessions are
146
+ automatically saved to `~/.llm.rb/` by default.
147
+
148
+ ### Fix
149
+
150
+ * **agent: fix `path` restore on first run** <br>
151
+ Fix a bug where [`LLM::Agent`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html)
152
+ called `@ctx.restore(path:)` even when the path's file did not exist.
153
+ The fix checks `File.readable?(@path)` before attempting to restore,
154
+ so the agent starts with a blank conversation on first use instead of
155
+ failing with a file-not-found error.
156
+
157
+ * **tools: re-raise `LLM::Interrupt` to abort the turn** <br>
158
+ [`LLM::Tool::Git`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool/Git.html),
159
+ [`LLM::Tool::Mkdir`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool/Mkdir.html),
160
+ [`LLM::Tool::Rg`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool/Rg.html),
161
+ [`LLM::Tool::Ruby`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool/Ruby.html),
162
+ and
163
+ [`LLM::Tool::Shell`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool/Shell.html)
164
+ now re-raise `LLM::Interrupt` after killing their running command. The
165
+ previous behavior rescued the interrupt and killed the child process but
166
+ let the turn continue, which meant a cancelled tool call did not abort
167
+ the conversation turn. Re-raising ensures the entire turn is interrupted.
168
+
169
+ * **tools: rescue `LLM::Interrupt` in shell-based tools** <br>
170
+ [`LLM::Tool::Shell`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool/Shell.html),
171
+ [`LLM::Tool::Git`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool/Git.html),
172
+ [`LLM::Tool::Mkdir`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool/Mkdir.html),
173
+ and
174
+ [`LLM::Tool::Rg`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool/Rg.html)
175
+ now rescue `LLM::Interrupt` and kill their running command, preventing
176
+ orphaned child processes when a tool is interrupted during execution.
177
+
178
+ * **agent: fix default name derivation** <br>
179
+ Fix a bug where `LLM::Agent` used without a subclass derived its default
180
+ name as `"l-lm-agent"` instead of `"agent"`. The fix replaces the
181
+ regex-based parameterization with a pattern that correctly handles
182
+ single-word class names and multi-word namespaced names.
183
+
184
+ * **function: `#params` always returns an `LLM::Object`** <br>
185
+ [`LLM::Function#params`](https://r.uby.dev/api-docs/llm.rb/LLM/Function.html#params-instance_method)
186
+ now always returns an `LLM::Object` representing the function's parameter
187
+ schema. Previously it returned `nil` when a function defined no parameters,
188
+ forcing every caller to guard against `nil`. All provider adapters now use
189
+ `fn.params.to_h` instead of `fn.params || {type: "object", properties: {}}`.
190
+
191
+ ## v13.0.0
192
+
193
+ v13.0.0 relicenses the project under the MIT license, replacing
194
+ the Business Source License that was introduced in v12.0.0. No
195
+ commercial license is needed. Commercial, personal, educational, and
196
+ all other uses are now permitted under the standard MIT terms.
197
+
198
+ Seven breaking changes. Concurrency strategies have been renamed
199
+ (`:call` → `:sequential`, `:task` → `:async`), `spawn` is now
200
+ `task`, and the `:async` strategy has been rebuilt from the ground
201
+ up. It no longer blocks and now supports interruption. The compactor
202
+ has been refactored into pluggable strategies. Interruption is now
203
+ reliable across all six concurrency backends. The `functions` and
204
+ `functions?` methods have been renamed to `pending_functions` and
205
+ `pending_functions?`.
206
+
207
+ ### Migration from v12.6.0
208
+
209
+ | Old | New |
210
+ |-----|-----|
211
+ | `fn.spawn(:call)` | `fn.task(:sequential)` |
212
+ | `fn.spawn(:task)` | `fn.task(:async)` |
213
+ | `ctx.wait(:call)` | `ctx.wait(:sequential)` |
214
+ | `agent.concurrency :task` | `agent.concurrency :async` |
215
+ | `LLM::Function::FiberGroup` | `LLM::Function::Fiber::Group` |
216
+ | `LLM::Function::CallGroup` | `LLM::Function::Sequential::Group` |
217
+ | `LLM::Function::TaskGroup` | `LLM::Function::Async::Group` |
218
+ | `Compactor.new(model:, token_threshold:)` | `Compactor::Truncate.new(ctx)` |
219
+ | `on_compaction(ctx, compactor)` | `on_compaction(compactor)` |
220
+ | `ctx.functions` / `ctx.functions?` | `ctx.pending_functions` / `ctx.pending_functions?` |
221
+ | `agent.functions` / `agent.functions?` | `agent.pending_functions` / `agent.pending_functions?` |
222
+
223
+ ### Breaking
224
+
225
+ * **rename `LLM::Function#spawn` as `LLM::Function#task`** <br>
226
+ [`LLM::Function#task`](https://r.uby.dev/api-docs/llm.rb/LLM/Function.html#task-instance_method)
227
+ (previously `spawn`) now consistently returns a
228
+ [`LLM::Function::Task`](https://r.uby.dev/api-docs/llm.rb/LLM/Function/Task.html)
229
+ object that can be spawned, waited on, and passed to
230
+ [`LLM::Function::Group`](https://r.uby.dev/api-docs/llm.rb/LLM/Function/Group.html).
231
+ The old implementation alternated between spawning immediately or
232
+ returning a raw thread or fiber.
233
+
234
+ * **rename concurrency strategies (`:call` → `:sequential`,**
235
+ **`:task` → `:async`)** <br>
236
+ The `:call` concurrency strategy is now `:sequential`, and the `:task`
237
+ strategy is now `:async`.
238
+ [`LLM::Agent.concurrency`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html#concurrency-class_method),
239
+ [`LLM::Context#wait`](https://r.uby.dev/api-docs/llm.rb/LLM/Context.html#wait-instance_method),
240
+ `LLM::Function::Array#task`, and
241
+ [`LLM::Function#task`](https://r.uby.dev/api-docs/llm.rb/LLM/Function.html#task-instance_method)
242
+ all accept the new names. The old names raise `ArgumentError`.
243
+
244
+ * **rename group classes** <br>
245
+ Group classes have been moved into their strategy's namespace:
246
+ `FiberGroup` → `Fiber::Group`, `ThreadGroup` → `Thread::Group`,
247
+ `CallGroup` → `Sequential::Group`, `TaskGroup` → `Async::Group`,
248
+ `Fork::Group` → `Fork::Group`, `Ractor::Group` → `Ractor::Group`.
249
+
250
+ * **repurpose `LLM::Function::Task` as a task interface superclass** <br>
251
+ `LLM::Function::Task` has been repurposed from a general-purpose class
252
+ that tried to support multiple concurrency strategies into an abstract
253
+ base class that defines the task interface. Individual strategies
254
+ (`Sequential::Task`, `Thread::Task`, `Fiber::Task`, `Async::Task`,
255
+ `Fork::Task`, `Ractor::Task`) now subclass it and implement
256
+ `spawn`, `alive?`, `interrupt!`, and `wait`.
257
+
258
+ * **fix `:async` concurrency (now backed by a managed**
259
+ **`LLM::Function::Async::Reactor` on a background thread)** <br>
260
+ The `:async` strategy previously used `Async {}` which blocked the
261
+ caller until all tasks completed and did not support interruption.
262
+ The fix replaces it with a per-turn
263
+ `LLM::Function::Async::Reactor` on a background thread. Work is
264
+ submitted via `submit(&block)` and consumed by the reactor's event
265
+ loop through a thread-safe `Queue`. `Async::Group` manages the
266
+ reactor lifecycle and spawns tasks lazily on `wait`.
267
+ <br><br>
268
+ Interruption pushes [`LLM::Interrupt`](https://r.uby.dev/api-docs/llm.rb/LLM/Interrupt.html)
269
+ to the task's result queue instead of using `Fiber#raise`, and
270
+ results are bridged back to the caller through a second `Queue`.
271
+ This work drove the broader refactor of strategy naming, the
272
+ spawn/wait split, and the `Task` superclass. The `:async` strategy
273
+ needed the same interface the other strategies already had.
274
+
275
+ * **compactor: refactor to strategy-based interface** <br>
276
+ [`LLM::Compactor`](https://r.uby.dev/api-docs/llm.rb/LLM/Compactor.html)
277
+ has been refactored from a single class that performed LLM-based
278
+ summarization into a strategy-based superclass. Each subclass
279
+ implements a different compaction strategy via `call(**opts)`. The old
280
+ summarization approach (using `model:`, `token_threshold:`,
281
+ `message_threshold:`, and `retention_window:` options) has been removed.
282
+ The built-in
283
+ [`LLM::Compactor::Truncate`](https://r.uby.dev/api-docs/llm.rb/LLM/Compactor/Truncate.html)
284
+ strategy drops the oldest messages when the conversation exceeds a
285
+ configured size.
286
+
287
+ * **rename `LLM::Context#{functions,functions?}` and `LLM::Agent#{functions,functions?}`** <br>
288
+ [`LLM::Context#functions`](https://r.uby.dev/api-docs/llm.rb/LLM/Context.html#functions-instance_method)
289
+ and
290
+ [`LLM::Context#functions?`](https://r.uby.dev/api-docs/llm.rb/LLM/Context.html#functions%3F-instance_method)
291
+ have been renamed to
292
+ [`LLM::Context#pending_functions`](https://r.uby.dev/api-docs/llm.rb/LLM/Context.html#pending_functions-instance_method)
293
+ and
294
+ [`LLM::Context#pending_functions?`](https://r.uby.dev/api-docs/llm.rb/LLM/Context.html#pending_functions%3F-instance_method)
295
+ respectively. The same rename applies to
296
+ [`LLM::Agent#functions`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html#functions-instance_method)
297
+ (now
298
+ [`LLM::Agent#pending_functions`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html#pending_functions-instance_method)).
299
+ The `pending_functions` name was already available as an alias in
300
+ v12.5.0; this change removes the old `functions` name entirely.
301
+
302
+ ### Core
303
+
304
+ * **extend `LLM.require` with an optional version argument** <br>
305
+ `LLM.require` now accepts a second `version` parameter that is passed
306
+ to `Kernel#gem` before loading, enabling version constraints for
307
+ optional runtime dependencies. For example,
308
+ `LLM.require "test-cmd.rb", "~> 1.1"` ensures a minimum gem version
309
+ is available. This is used internally by the `Git`, `Rg`, `Mkdir`,
310
+ and `Shell` tools to enforce compatibility with the `test-cmd.rb` gem.
311
+
312
+ ### Compactor
313
+
314
+ * **add `Truncate` strategy for dropping oldest messages** <br>
315
+ [`LLM::Compactor::Truncate`](https://r.uby.dev/api-docs/llm.rb/LLM/Compactor/Truncate.html)
316
+ is a new built-in compaction strategy that drops the oldest messages
317
+ when the conversation exceeds a configured size. It preserves tool
318
+ call/return pairs so the algorithm never breaks in the middle of a
319
+ sequence. Configured with `keep:` (default 64), it emits the standard
320
+ `on_compaction` and `on_compaction_finish`
321
+ [`LLM::Stream`](https://r.uby.dev/api-docs/llm.rb/LLM/Stream.html)
322
+ lifecycle callbacks. No LLM call is made; the strategy is purely
323
+ lossy but fast and requires no network.
324
+
325
+ * **raise when given an unparseable `keep:` value** <br>
326
+ `LLM::Compactor::Truncate` now raises `ArgumentError` when the `keep:`
327
+ parameter cannot be parsed as an integer or percentage string, instead
328
+ of failing with an obscure error later during execution.
329
+
330
+ * **accept percentage string for the `keep:` parameter** <br>
331
+ `LLM::Compactor::Truncate#call` now accepts a percentage string such
332
+ as `"80%"` for the `keep:` parameter, which keeps approximately 80%
333
+ of the most recent messages. Integer values continue to work as
334
+ before. This makes it easy to trim proportionally rather than to an
335
+ absolute number of messages.
336
+
337
+ * **add `Null` strategy for no-op compaction** <br>
338
+ [`LLM::Compactor::Null`](https://r.uby.dev/api-docs/llm.rb/LLM/Compactor/Null.html)
339
+ is a new built-in compaction strategy that does nothing. It is used as
340
+ the default compactor when no strategy is configured on a context,
341
+ ensuring the compactor interface is always present without requiring a
342
+ separate nil check.
343
+
344
+ * **accept both `LLM::Agent` and `LLM::Context`** <br>
345
+ [`LLM::Compactor#initialize`](https://r.uby.dev/api-docs/llm.rb/LLM/Compactor.html#initialize-instance_method)
346
+ now accepts both `LLM::Agent` and `LLM::Context` instances. When given
347
+ an agent, the internal context is unwrapped automatically, making the
348
+ compactor API more flexible when working with agents.
349
+
350
+ #### Context integration
351
+
352
+ * **accept `compactor` and `compactor_options` parameters** <br>
353
+ [`LLM::Context`](https://r.uby.dev/api-docs/llm.rb/LLM/Context.html)
354
+ now accepts `compactor:` (a compactor class defaulting to
355
+ [`LLM::Compactor::Null`](https://r.uby.dev/api-docs/llm.rb/LLM/Compactor/Null.html))
356
+ and `compactor_options:` (a hash of options forwarded to the
357
+ compactor's `call` method) parameters. The compactor is automatically
358
+ invoked at the beginning of each `talk` turn. The previous `compactor=`
359
+ setter has been removed in favour of constructor-driven configuration.
360
+
361
+ * **`on_compaction` and `on_compaction_finish` receive a single argument** <br>
362
+ [`LLM::Stream#on_compaction`](https://r.uby.dev/api-docs/llm.rb/LLM/Stream.html#on_compaction-instance_method)
363
+ and
364
+ [`LLM::Stream#on_compaction_finish`](https://r.uby.dev/api-docs/llm.rb/LLM/Stream.html#on_compaction_finish-instance_method)
365
+ now accept a single argument (the compactor instance) instead of two
366
+ arguments (context and compactor). The context is still available via
367
+ `LLM::Compactor#ctx`, so access to the context is not lost. This
368
+ simplifies the callback interface for compaction lifecycle observers.
369
+
370
+ ### Tools
371
+
372
+ * **add `LLM::Tool::Utils` module for shared command execution logic** <br>
373
+ A new
374
+ [`LLM::Tool::Utils`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool/Utils.html)
375
+ module provides shared `wait(command:, timeout:)` and `now` helper
376
+ methods for tools that execute commands. Tools that include `Utils` can
377
+ wait on a running command and automatically kill it when it exceeds the
378
+ configured timeout, using `Process.clock_gettime` with `CLOCK_MONOTONIC`
379
+ for precise timing. The module is used by both the `Shell` and `Rg`
380
+ tools internally.
381
+
382
+ * **shell: add `timeout` parameter for command execution deadlines** <br>
383
+ The
384
+ [`LLM::Tool::Shell`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool/Shell.html)
385
+ tool now accepts a `timeout` parameter (default 60s) that automatically
386
+ kills commands exceeding the specified time limit, preventing hung
387
+ processes from blocking the agent indefinitely.
388
+
389
+ * **rg: add `timeout` parameter for search execution deadlines** <br>
390
+ The
391
+ [`LLM::Tool::Rg`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool/Rg.html)
392
+ tool now accepts a `timeout` parameter (default 5s) that automatically
393
+ kills search commands exceeding the specified time limit, preventing
394
+ long-running searches from blocking the agent indefinitely.
395
+
396
+ * **git: add `timeout` parameter for command execution deadlines** <br>
397
+ The
398
+ [`LLM::Tool::Git`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool/Git.html)
399
+ tool now accepts a `timeout` parameter (default 5s) that automatically
400
+ kills git commands exceeding the specified time limit, preventing hung
401
+ processes from blocking the agent indefinitely.
402
+
403
+ ### Schema
404
+
405
+ * **properties are now ordered and support indifferent access** <br>
406
+ [`LLM::Schema::Leaf`](https://r.uby.dev/api-docs/llm.rb/LLM/Schema/Leaf.html)
407
+ tracks property definition order in a new `index` attribute, matching
408
+ the convention already used by
409
+ [`LLM::Command::Parameter`](https://r.uby.dev/api-docs/llm.rb/LLM/Command/Parameter.html).
410
+ Internally, `@properties` is stored as an
411
+ [`LLM::Object`](https://r.uby.dev/api-docs/llm.rb/LLM/Object.html)
412
+ instead of a plain `Hash`, so lookups with both string and symbol keys
413
+ work.
414
+
415
+ ### Buffer
416
+
417
+ * **more array-like message management** <br>
418
+ [`LLM::Buffer`](https://r.uby.dev/api-docs/llm.rb/LLM/Buffer.html)
419
+ now exposes `first`, `reject!`, `select!`, `shift`, `clear`, `drop`,
420
+ `take`, and `reverse`, making it easier to query and mutate
421
+ `LLM::Context#messages` like an ordinary Array. `reject!` is aliased
422
+ as `delete_if` for familiarity.
423
+
424
+ * **`last(nil)` no longer returns the last message** <br>
425
+ `LLM::Buffer#last` now uses an internal `UNDEFINED` sentinel to
426
+ distinguish between no argument (`last` returns the last message)
427
+ and `nil` (`last(nil)` is treated as an argument). Previously `nil`
428
+ was indistinguishable from no argument.
429
+
430
+ ### Function
431
+
432
+ * **consolidate `call` and `call!` into one method** <br>
433
+ The private `call!` method has been merged into the public
434
+ [`LLM::Function#call`](https://r.uby.dev/api-docs/llm.rb/LLM/Function.html#call-instance_method).
435
+ The separate `call!` method existed for tracer-scoping logic now
436
+ handled directly inside `call`. All internal call sites now use
437
+ `function.call` instead of `function.call!`.
438
+
439
+ * **add `LLM::Function::Group` as an abstract base class** <br>
440
+ A new abstract base class
441
+ ([`LLM::Function::Group`](https://r.uby.dev/api-docs/llm.rb/LLM/Function/Group.html))
442
+ defines the interface that all concurrency strategy groups must
443
+ implement: `alive?`, `interrupt!`, and `wait`. Each strategy group
444
+ (`Sequential::Group`, `Thread::Group`, `Fiber::Group`,
445
+ `Async::Group`, `Fork::Group`, `Ractor::Group`) now subclasses
446
+ this base.
447
+
448
+ * **split `spawn` and `wait` across all strategies** <br>
449
+ `spawn` now starts execution without blocking, and `wait` collects
450
+ the result. `on_tool_start` moved into each task's `spawn` so the
451
+ tracer span covers execution rather than construction. Each task and
452
+ group now exposes a public `spawn` method alongside the existing
453
+ `wait`/`value` methods.
454
+
455
+ * **spawn tasks lazily in `Group#wait`** <br>
456
+ All concurrency strategy groups (Fiber::Group, Fork::Group,
457
+ Ractor::Group, Thread::Group) now automatically spawn their tasks
458
+ when `wait` is called if they haven't been spawned yet, matching
459
+ the existing `Async::Group` behavior. This makes the spawn/wait
460
+ contract consistent across all six concurrency backends.
461
+
462
+ ### Agent
463
+
464
+ * **add `name` class DSL and instance method** <br>
465
+ [`LLM::Agent`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html) now
466
+ has a `name` class DSL (`name "admin"`) and a corresponding `#name`
467
+ instance method. The name is resolved through the same lazy-resolution
468
+ path as other agent attributes. It can be set via
469
+ `LLM::Agent.set(name: ...)`, `LLM::Agent.new(name: ...)`, or the class
470
+ DSL. When no name is given, a default is derived from the class name
471
+ (e.g., `SystemAdmin` becomes `system-admin`). The REPL uses the name
472
+ as the prompt label and transcript prefix, making it easier to
473
+ distinguish multiple sessions.
474
+
475
+ ### REPL
476
+
477
+ * **agent identity in the prompt** <br>
478
+ [`LLM::Agent#repl`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html#repl-instance_method)
479
+ and
480
+ [`LLM::Repl.new`](https://r.uby.dev/api-docs/llm.rb/LLM/Repl.html#initialize-instance_method)
481
+ accept a `name:` parameter (defaulting to `LLM::Agent#name`) that sets
482
+ the input prompt to `provider(name)> ` and labels transcript messages
483
+ with the agent's name instead of a hardcoded `agent:`. Useful when
484
+ running multiple sessions.
485
+
486
+ * **`/compact` command** <br>
487
+ New built-in `/compact` command frees context window space by dropping
488
+ the oldest messages via
489
+ [`LLM::Compactor::Truncate`](https://r.uby.dev/api-docs/llm.rb/LLM/Compactor/Truncate.html).
490
+ Supports both integer (`/compact 32`) and percentage (`/compact 75%`)
491
+ arguments. Defaults to keeping the last 128 messages.
492
+
493
+ * **tab-completion for `/` commands** <br>
494
+ Pressing Tab on an input line starting with `/` autocompletes the
495
+ command name. Repeated Tab presses cycle through matching commands.
496
+ Powered by
497
+ [`LLM::Command.complete(str)`](https://r.uby.dev/api-docs/llm.rb/LLM/Command.html#complete-class_method)
498
+ which is available outside the REPL too.
499
+
500
+ * **command system enhancements** <br>
501
+ Commands can set parameter defaults in their `call` method signature
502
+ (e.g., `def call(n: 128)`). Aliases like `/quit` now inherit their
503
+ parent's description and parameters. Commands also have access to
504
+ the active `agent` and `repl` via public readers.
505
+
506
+ * **tool argument sorting** <br>
507
+ Tool parameters in the status bar are now displayed in definition
508
+ order (using the new `index` attribute), regardless of the order
509
+ the model returns them.
510
+
511
+ * **expanded markdown rendering** <br>
512
+ The curses-based markdown renderer now handles lists (`<ul>`, `<ol>`),
513
+ blockquotes, horizontal rules, hyperlinks (underline), images
514
+ (`[image: alt text]`), and tables (aligned columns).
515
+
516
+ * **input improvements** <br>
517
+ Ctrl+P and Ctrl+N walk through conversation history (user messages
518
+ only, managed by
519
+ [`LLM::Repl::Walker`](https://r.uby.dev/api-docs/llm.rb/LLM/Repl/Walker.html)).
520
+ Page Up/Down scroll the transcript by a page. ENTER and BACKSPACE are
521
+ now mapped to raw character codes from `Curses.getch` instead of
522
+ `Curses::Key` constants.
523
+
524
+ ### Object
525
+
526
+ * **preserve the original key name in `KeyError` messages** <br>
527
+ `LLM::Object#fetch` now preserves the original key name when a key
528
+ is not found, instead of raising `KeyError` with `key not found: nil`.
529
+ The previous behavior occurred when the given key was not found in
530
+ the stored hash, causing internal lookup to return `nil` and lose
531
+ the original key reference.
532
+
533
+ ### Registry
534
+
535
+ * **refresh model metadata across providers** <br>
536
+ Update `data/*.json` files with current provider model listings and
537
+ pricing. Mark several DeepInfra models as deprecated (`meta-llama/
538
+ Meta-Llama-3.1-8B-Instruct`, `Qwen/Qwen1.5-110B-Chat`, and
539
+ `mistralai/Mixtral-8x7B-Instruct-v0.1`). Correct xAI cache-read
540
+ pricing from $0.50 to $0.30 per million input tokens.
541
+
542
+ ### Fix
543
+
544
+ * **agent: fix default name resolution when name is not explicitly set** <br>
545
+ Fix a bug where `LLM::Agent` derived its default name from
546
+ `self.class` instead of `self`, causing the name to be `"class"`
547
+ instead of a parameterized version of the actual class name
548
+ (e.g., `"system-admin"` for `SystemAdmin`). The fix uses `self`
549
+ directly, which correctly resolves the class name at the instance
550
+ level.
551
+
552
+ * **function: make Fork::Task and Ractor::Task inherit LLM::Function::Task** <br>
553
+ `LLM::Function::Fork::Task` and `LLM::Function::Ractor::Task` now
554
+ explicitly subclass `LLM::Function::Task` and accept an options hash
555
+ as their second argument, matching the constructor signature used
556
+ by the other four task classes. The interface was already compatible
557
+ but the inheritance was missing by mistake. It is now consistent across
558
+ all six concurrency backends.
559
+
560
+ * **google: fix `stream` parameter leakage that broke the provider** <br>
561
+ Fix a bug in the Google provider where `stream: stream.enabled?` was
562
+ being merged into request parameters, causing API-level errors. The
563
+ Google provider does not use a `stream` parameter. Streaming is
564
+ controlled via the URL path (`streamGenerateContent` vs
565
+ `generateContent`). The fix removes the leaked parameter and correctly
566
+ routes streaming requests through the appropriate path.
567
+
568
+ * **fork: fix deadlock on xchan.rb channel** <br>
569
+ Fix a deadlock in the `:fork` concurrency strategy where both the
570
+ writer and reader could get stuck on the xchan channel, preventing the
571
+ reader from draining the channel. The deadlock surfaced as an errno
572
+ failure, especially with large tool returns. The fix requires xchan.rb
573
+ v0.22.0 and uses the `SOCK_STREAM` socket type for communicating a
574
+ tool's return value.
575
+
18
576
  ## v12.6.0
19
577
 
20
578
  Changes since `v12.5.1`.
21
579
 
22
- This release adds bulk defaults for tools and agents `LLM::Tool.defaults`
580
+ This release adds bulk defaults for tools and agents: `LLM::Tool.defaults`
23
581
  for setting parameter defaults and `LLM::Agent.set` for mass-assigning
24
582
  class-level defaults, both mirrored on ActiveRecord and Sequel agent models.
25
583
 
@@ -55,7 +613,7 @@ prior call.
55
613
  On cancel, `LLM::Interrupt` is now raised on the thread that is
56
614
  running a tool. The tool can rescue `LLM::Interrupt` and gracefully
57
615
  terminate (e.g., clean up resources). The previous approach used
58
- `Thread#interrupt` which was less reliable it did not interrupt a
616
+ `Thread#interrupt` which was less reliable. It did not interrupt a
59
617
  sleeping thread.
60
618
 
61
619
  * **function: suppress thread exception reporting in `:thread` concurrency** <br>
@@ -81,15 +639,15 @@ prior call.
81
639
  `LLM::Interrupt` is now raised on the active fiber via `Fiber#raise`
82
640
  when interrupting `:fiber`-concurrency tools.
83
641
  <br><br>
84
- `Task#interrupt!` now dispatches by task type `Thread#raise` for
85
- threads, `Fiber#raise` for fibers making interruption reliable
642
+ `Task#interrupt!` now dispatches by task type: `Thread#raise` for
643
+ threads, `Fiber#raise` for fibers. Making interruption reliable
86
644
  across all concurrency strategies.
87
645
 
88
646
  * **function: raise `LLM::Interrupt` on fork-backed tool tasks** <br>
89
647
  `LLM::Interrupt` is now raised on the main thread of a fork child
90
648
  process via `Thread.main.raise(LLM::Interrupt)` when interrupting
91
649
  `:fork`-concurrency tools, and the fork `Task#wait` re-raises the
92
- interrupt on the parent side making interruption reliable across
650
+ interrupt on the parent side. Making interruption reliable across
93
651
  all concurrency strategies including `:fork`.
94
652
 
95
653
  * **function: raise `LLM::Interrupt` on `Async::Task`-backed tool tasks** <br>
@@ -107,7 +665,7 @@ prior call.
107
665
  raises `LLM::Interrupt` on the ractor's main thread.
108
666
  <br><br>
109
667
  `Task#interrupt!` delegates to the mailbox to send the interrupt
110
- message extending reliable interruption to the `:ractor`
668
+ message. Extending reliable interruption to the `:ractor`
111
669
  concurrency strategy.
112
670
 
113
671
  ## v12.5.1
@@ -254,7 +812,7 @@ OpenAI, Google, DeepInfra, DeepSeek, and xAI model entries.
254
812
 
255
813
  * **repl: display command errors in the curses UI** <br>
256
814
  Commands invoked with too few arguments now display an error
257
- message `command(<name>): too few arguments` directly in
815
+ message: `command(<name>): too few arguments`. Displayed directly in
258
816
  the curses transcript area, giving immediate feedback instead
259
817
  of silently failing.
260
818
 
@@ -378,7 +936,7 @@ a command system foundation with the `/exit` command, and several new
378
936
  keybindings (Ctrl+F, Ctrl+K, Ctrl+Y). Tool calls are rendered with a
379
937
  compact function-call syntax in the status bar.
380
938
 
381
- Two new built-in tools `LLM::Tool::Ls` and `LLM::Tool::Which` are
939
+ Two new built-in tools: `LLM::Tool::Ls` and `LLM::Tool::Which` are
382
940
  available as opt-in additions for file listing and executable lookup.
383
941
 
384
942
  Model metadata has been refreshed across providers, the REPL loop
@@ -445,7 +1003,7 @@ area.
445
1003
  The curses-based REPL input now detects paste operations by tracking
446
1004
  the rate at which characters arrive. A paste rate of ≤50ms is
447
1005
  assumed to be a burst of characters that could only be explained by
448
- a paste no human types that fast. Multiline pastes are supported
1006
+ a paste. No human types that fast. Multiline pastes are supported
449
1007
  through internal refactoring of the input handling logic.
450
1008
 
451
1009
  * **repl: optimize paste mode rendering** <br>
@@ -468,7 +1026,7 @@ area.
468
1026
 
469
1027
  * **repl: render tool calls in a function-call syntax** <br>
470
1028
  The curses-based REPL status bar now renders tool calls with a
471
- compact function-call syntax `tool(key: value)` instead of
1029
+ compact function-call syntax: `tool(key: value)` instead of
472
1030
  `tool: name`. Strings are quoted and truncated, arrays show their
473
1031
  first two elements, and hashes collapse to `{…}`, making it easier
474
1032
  to see what arguments the model is passing. The `tool done` status
@@ -596,8 +1154,8 @@ tracer logger instances with less verbosity.
596
1154
  streamed content via `#<<`. `LLM::Stream::Disabled` represents an explicitly
597
1155
  disabled stream with no-op callbacks.
598
1156
 
599
- This is part of an internal refactoring that lets all stream values IO
600
- objects, `true`, `false`, `nil`, and `LLM::Stream` instances themselves
1157
+ This is part of an internal refactoring that lets all stream values: IO
1158
+ objects, `true`, `false`, `nil`, and `LLM::Stream` instances themselves
601
1159
  be represented by the same `LLM::Stream` interface via the new
602
1160
  `LLM::Stream.try` factory method.
603
1161
 
@@ -666,7 +1224,7 @@ tracer logger instances with less verbosity.
666
1224
 
667
1225
  * **repl: add extra padding between markdown nodes** <br>
668
1226
  The curses-based REPL markdown renderer now adds extra vertical spacing
669
- between certain markdown elements paragraphs, headers, and codeblocks
1227
+ between certain markdown elements: paragraphs, headers, and codeblocks
670
1228
  for improved readability of model responses.
671
1229
 
672
1230
  * **repl: add a visual divider between transcript and the rows below it** <br>