turnkit 0.2.5 → 0.2.7
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/README.md +155 -321
- data/lib/turnkit/adapters/ruby_llm.rb +69 -5
- data/lib/turnkit/agent.rb +20 -2
- data/lib/turnkit/client.rb +5 -1
- data/lib/turnkit/compaction.rb +406 -0
- data/lib/turnkit/conversation.rb +11 -4
- data/lib/turnkit/error.rb +3 -0
- data/lib/turnkit/event.rb +25 -0
- data/lib/turnkit/generators/turnkit/install/templates/create_turnkit_tables.rb +1 -0
- data/lib/turnkit/generators/turnkit/install/templates/initializer.rb +6 -0
- data/lib/turnkit/generators/turnkit/install_generator.rb +6 -0
- data/lib/turnkit/message.rb +21 -1
- data/lib/turnkit/message_projection.rb +28 -1
- data/lib/turnkit/model_request.rb +35 -0
- data/lib/turnkit/record.rb +2 -1
- data/lib/turnkit/result.rb +3 -2
- data/lib/turnkit/stores/active_record_store.rb +14 -4
- data/lib/turnkit/sub_agent_tool.rb +13 -4
- data/lib/turnkit/tool.rb +117 -4
- data/lib/turnkit/tool_call.rb +3 -1
- data/lib/turnkit/tool_runner.rb +8 -1
- data/lib/turnkit/turn.rb +101 -16
- data/lib/turnkit/version.rb +1 -1
- data/lib/turnkit.rb +7 -0
- metadata +8 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1c205e6b6c72785350419adfb6515f9b2d213c3eef06c9516a038667bae24842
|
|
4
|
+
data.tar.gz: 74c72004e3334cafa69071034aaa98c14f98262fe56bee9a69ac058bd70499af
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6df2331b9e594e1c4925113fb39996ace94860181037397e67855afebf479cb128ad83cbc2d76dcb8c2fe85d55ca042624d3f5b5ff3b33ba7cd7b4fdf1dbf62c
|
|
7
|
+
data.tar.gz: 640c1fdfdbdb08610ba75885e8fb6903c81ecfd90dec5dcf2eeb4462e13ab17de357af6adcc1e5cf18c9fb4622d769151382278481a7b3d178462b80e2e1bfc2
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.6 - 2026-06-07
|
|
4
|
+
|
|
5
|
+
- Add automatic context compaction for long conversations. TurnKit now stores append-only `context_summary` messages and projects compacted history into future model calls while keeping the full transcript durable.
|
|
6
|
+
|
|
3
7
|
## 0.2.5 - 2026-06-06
|
|
4
8
|
|
|
5
9
|
- Add per-agent and per-turn provider thinking configuration.
|
data/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
[](https://www.ruby-lang.org)
|
|
5
5
|
[](LICENSE.md)
|
|
6
6
|
|
|
7
|
-
Build durable Ruby
|
|
7
|
+
Build durable Ruby and Rails agents with tools, skills, sub-agents, and persistence.
|
|
8
8
|
|
|
9
9
|
## Installation
|
|
10
10
|
|
|
@@ -22,21 +22,12 @@ bundle install
|
|
|
22
22
|
|
|
23
23
|
## Quick Start
|
|
24
24
|
|
|
25
|
-
Set
|
|
25
|
+
Set an API key:
|
|
26
26
|
|
|
27
27
|
```sh
|
|
28
28
|
export ANTHROPIC_API_KEY=...
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
-
| Provider | Env var | Example model |
|
|
32
|
-
| --- | --- | --- |
|
|
33
|
-
| Anthropic | `ANTHROPIC_API_KEY` | `claude-sonnet-4-5` |
|
|
34
|
-
| OpenAI | `OPENAI_API_KEY` | `gpt-4.1-mini` |
|
|
35
|
-
| Gemini | `GEMINI_API_KEY` | `gemini-2.5-flash` |
|
|
36
|
-
|
|
37
|
-
> [!WARNING]
|
|
38
|
-
> TurnKit defaults to `claude-sonnet-4-5`. If `ANTHROPIC_API_KEY` is unset or blank, set `TurnKit.default_model` to a provider you have configured.
|
|
39
|
-
|
|
40
31
|
Create an agent:
|
|
41
32
|
|
|
42
33
|
```ruby
|
|
@@ -59,93 +50,78 @@ puts turn.output_text
|
|
|
59
50
|
|
|
60
51
|
### Models
|
|
61
52
|
|
|
62
|
-
Set
|
|
53
|
+
Set a model:
|
|
63
54
|
|
|
64
55
|
```ruby
|
|
65
|
-
TurnKit.default_model = "
|
|
56
|
+
TurnKit.default_model = "gpt-4.1-mini"
|
|
66
57
|
```
|
|
67
58
|
|
|
68
|
-
|
|
59
|
+
Set the matching key:
|
|
69
60
|
|
|
70
61
|
```sh
|
|
71
62
|
export OPENAI_API_KEY=...
|
|
72
63
|
```
|
|
73
64
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
```ruby
|
|
77
|
-
TurnKit.default_model = "gpt-4.1-mini"
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
Use Gemini:
|
|
65
|
+
Use these common providers:
|
|
81
66
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
67
|
+
| Provider | Key | Model |
|
|
68
|
+
| --- | --- | --- |
|
|
69
|
+
| Anthropic | `ANTHROPIC_API_KEY` | `claude-sonnet-4-5` |
|
|
70
|
+
| OpenAI | `OPENAI_API_KEY` | `gpt-4.1-mini` |
|
|
71
|
+
| Gemini | `GEMINI_API_KEY` | `gemini-2.5-flash` |
|
|
72
|
+
| OpenRouter | `OPENROUTER_API_KEY` | `openrouter/...` |
|
|
87
73
|
|
|
88
|
-
|
|
89
|
-
TurnKit.default_model = "gemini-2.5-flash"
|
|
90
|
-
```
|
|
74
|
+
Expect `TurnKit::ModelAccessError` for obvious key mistakes.
|
|
91
75
|
|
|
92
|
-
###
|
|
76
|
+
### Conversations
|
|
93
77
|
|
|
94
|
-
|
|
78
|
+
Create a conversation:
|
|
95
79
|
|
|
96
80
|
```ruby
|
|
97
81
|
agent = TurnKit::Agent.new(
|
|
98
|
-
name: "
|
|
99
|
-
|
|
100
|
-
thinking: { budget: 4_000 }
|
|
82
|
+
name: "writer",
|
|
83
|
+
instructions: "Write clear release notes."
|
|
101
84
|
)
|
|
85
|
+
|
|
86
|
+
conversation = agent.conversation(subject: "v1 launch")
|
|
102
87
|
```
|
|
103
88
|
|
|
104
|
-
|
|
89
|
+
Add context:
|
|
105
90
|
|
|
106
91
|
```ruby
|
|
107
|
-
|
|
108
|
-
name: "reasoner",
|
|
109
|
-
model: "gemini-2.5-flash",
|
|
110
|
-
thinking: { effort: :high }
|
|
111
|
-
)
|
|
92
|
+
conversation.say("Mention faster tool execution.")
|
|
112
93
|
```
|
|
113
94
|
|
|
114
|
-
|
|
95
|
+
Run the agent:
|
|
115
96
|
|
|
116
97
|
```ruby
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
conversation.ask("Answer quickly.", thinking: nil)
|
|
98
|
+
turn = conversation.run!
|
|
99
|
+
puts turn.output_text
|
|
120
100
|
```
|
|
121
101
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
When the provider reports reasoning usage, TurnKit records it as `thinking_tokens` and includes it in usage totals and cost calculation.
|
|
102
|
+
### Prompt Preview
|
|
125
103
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
Create a conversation:
|
|
104
|
+
Preview a pending turn:
|
|
129
105
|
|
|
130
106
|
```ruby
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
instructions: "Write clear release notes."
|
|
134
|
-
)
|
|
107
|
+
turn = conversation.ask("Draft the launch email.", async: true)
|
|
108
|
+
request = turn.preview
|
|
135
109
|
```
|
|
136
110
|
|
|
137
|
-
|
|
111
|
+
Inspect the request:
|
|
138
112
|
|
|
139
113
|
```ruby
|
|
140
|
-
|
|
141
|
-
|
|
114
|
+
request.model
|
|
115
|
+
request.messages
|
|
116
|
+
request.tool_names
|
|
117
|
+
request.instructions
|
|
118
|
+
request.report
|
|
142
119
|
```
|
|
143
120
|
|
|
144
|
-
Run the
|
|
121
|
+
Run the reviewed turn:
|
|
145
122
|
|
|
146
123
|
```ruby
|
|
147
|
-
turn
|
|
148
|
-
puts turn.output_text
|
|
124
|
+
turn.run!
|
|
149
125
|
```
|
|
150
126
|
|
|
151
127
|
### Tools
|
|
@@ -156,11 +132,15 @@ Create a tool:
|
|
|
156
132
|
class SaveReport < TurnKit::Tool
|
|
157
133
|
description "Save a report."
|
|
158
134
|
usage_hint "Use when the user asks to persist a report."
|
|
135
|
+
|
|
159
136
|
parameter :title, :string, required: true
|
|
160
137
|
parameter :body, :string, required: true
|
|
161
138
|
|
|
162
139
|
def self.ends_turn? = true
|
|
163
|
-
|
|
140
|
+
|
|
141
|
+
def self.completion_message(result)
|
|
142
|
+
"Saved #{result.fetch("report_id")}."
|
|
143
|
+
end
|
|
164
144
|
|
|
165
145
|
def call(title:, body:, context:)
|
|
166
146
|
{ report_id: "rep_1", title: title, body: body }
|
|
@@ -168,7 +148,7 @@ class SaveReport < TurnKit::Tool
|
|
|
168
148
|
end
|
|
169
149
|
```
|
|
170
150
|
|
|
171
|
-
|
|
151
|
+
Register the tool:
|
|
172
152
|
|
|
173
153
|
```ruby
|
|
174
154
|
agent = TurnKit::Agent.new(
|
|
@@ -178,82 +158,87 @@ agent = TurnKit::Agent.new(
|
|
|
178
158
|
)
|
|
179
159
|
```
|
|
180
160
|
|
|
181
|
-
|
|
161
|
+
Run the tool loop:
|
|
182
162
|
|
|
183
163
|
```ruby
|
|
184
164
|
turn = agent.conversation.ask("Save a short status report.")
|
|
185
165
|
puts turn.output_text
|
|
186
166
|
```
|
|
187
167
|
|
|
188
|
-
|
|
168
|
+
Rely on TurnKit to validate tools and model-provided arguments.
|
|
169
|
+
|
|
170
|
+
### Structured Output
|
|
189
171
|
|
|
190
|
-
|
|
172
|
+
Define a schema:
|
|
191
173
|
|
|
192
174
|
```ruby
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
175
|
+
schema = {
|
|
176
|
+
type: "object",
|
|
177
|
+
properties: {
|
|
178
|
+
title: { type: "string" },
|
|
179
|
+
bullets: {
|
|
180
|
+
type: "array",
|
|
181
|
+
items: { type: "string" }
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
required: ["title", "bullets"]
|
|
185
|
+
}
|
|
186
|
+
```
|
|
198
187
|
|
|
199
|
-
|
|
200
|
-
parameter :search_queries, :array, required: false
|
|
188
|
+
Use structured output:
|
|
201
189
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
end
|
|
190
|
+
```ruby
|
|
191
|
+
agent = TurnKit::Agent.new(
|
|
192
|
+
name: "writer",
|
|
193
|
+
output_schema: schema
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
turn = agent.conversation.ask("Summarize the launch plan.")
|
|
197
|
+
puts turn.output_data
|
|
211
198
|
```
|
|
212
199
|
|
|
213
|
-
|
|
200
|
+
Override the schema per turn:
|
|
214
201
|
|
|
215
202
|
```ruby
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
203
|
+
conversation.ask(
|
|
204
|
+
"Return one decision.",
|
|
205
|
+
output_schema: {
|
|
206
|
+
type: "object",
|
|
207
|
+
properties: {
|
|
208
|
+
decision: { type: "string" }
|
|
209
|
+
}
|
|
210
|
+
}
|
|
222
211
|
)
|
|
223
212
|
```
|
|
224
213
|
|
|
225
|
-
|
|
214
|
+
### Events
|
|
226
215
|
|
|
227
|
-
|
|
216
|
+
Subscribe globally:
|
|
228
217
|
|
|
229
218
|
```ruby
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
context.execution # The TurnKit::ToolExecution for this tool call
|
|
233
|
-
|
|
234
|
-
{ query: query }
|
|
219
|
+
TurnKit.on_event = ->(event) do
|
|
220
|
+
Rails.logger.info("turnkit.#{event.type}")
|
|
235
221
|
end
|
|
236
222
|
```
|
|
237
223
|
|
|
238
|
-
|
|
224
|
+
Subscribe per agent:
|
|
239
225
|
|
|
240
226
|
```ruby
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
227
|
+
agent = TurnKit::Agent.new(
|
|
228
|
+
name: "helper",
|
|
229
|
+
on_event: ->(event) { puts event.type }
|
|
230
|
+
)
|
|
244
231
|
```
|
|
245
232
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
Prefer returning a `Hash`. TurnKit serializes the normalized value as the tool result:
|
|
233
|
+
Subscribe per turn:
|
|
249
234
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
235
|
+
```ruby
|
|
236
|
+
turn.run! do |event|
|
|
237
|
+
puts event.type
|
|
238
|
+
end
|
|
239
|
+
```
|
|
255
240
|
|
|
256
|
-
|
|
241
|
+
Use events for turns, model calls, messages, and tool calls.
|
|
257
242
|
|
|
258
243
|
### Skills
|
|
259
244
|
|
|
@@ -283,7 +268,7 @@ writer = TurnKit::Agent.new(
|
|
|
283
268
|
)
|
|
284
269
|
```
|
|
285
270
|
|
|
286
|
-
|
|
271
|
+
Register the sub-agent:
|
|
287
272
|
|
|
288
273
|
```ruby
|
|
289
274
|
editor = TurnKit::Agent.new(
|
|
@@ -299,117 +284,36 @@ turn = editor.conversation.ask("Ask the writer for three headlines.")
|
|
|
299
284
|
puts turn.output_text
|
|
300
285
|
```
|
|
301
286
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
Inspect token usage:
|
|
287
|
+
Use sub-agents for isolated child conversations.
|
|
305
288
|
|
|
306
|
-
|
|
307
|
-
turn.usage.total_tokens
|
|
308
|
-
conversation.usage.total_tokens
|
|
309
|
-
agent.usage.total_tokens
|
|
310
|
-
```
|
|
289
|
+
### Context Compaction
|
|
311
290
|
|
|
312
|
-
|
|
291
|
+
Disable compaction:
|
|
313
292
|
|
|
314
293
|
```ruby
|
|
315
|
-
|
|
316
|
-
conversation.cost.total
|
|
317
|
-
agent.cost.total
|
|
294
|
+
TurnKit.compaction = false
|
|
318
295
|
```
|
|
319
296
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
Override model rates:
|
|
297
|
+
Configure compaction:
|
|
323
298
|
|
|
324
299
|
```ruby
|
|
325
|
-
TurnKit.
|
|
326
|
-
"
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
cached_input: 0.05,
|
|
330
|
-
cache_creation: 0.25
|
|
331
|
-
}
|
|
300
|
+
TurnKit.compaction = {
|
|
301
|
+
model: "gpt-4.1-mini",
|
|
302
|
+
threshold: 0.75,
|
|
303
|
+
context_limit: 128_000
|
|
332
304
|
}
|
|
333
305
|
```
|
|
334
306
|
|
|
335
|
-
|
|
307
|
+
Compact manually:
|
|
336
308
|
|
|
337
309
|
```ruby
|
|
338
|
-
|
|
339
|
-
{
|
|
340
|
-
input: usage.input_tokens * 0.25 / 1_000_000.0,
|
|
341
|
-
output: usage.output_tokens * 1.00 / 1_000_000.0
|
|
342
|
-
}
|
|
343
|
-
end
|
|
310
|
+
conversation.compact!(focus: "billing migration")
|
|
344
311
|
```
|
|
345
312
|
|
|
346
|
-
|
|
313
|
+
Run the local smoke test:
|
|
347
314
|
|
|
348
|
-
```
|
|
349
|
-
|
|
350
|
-
name: "analyst",
|
|
351
|
-
cost_limit: 0.25
|
|
352
|
-
)
|
|
353
|
-
```
|
|
354
|
-
|
|
355
|
-
### Prompt caching
|
|
356
|
-
|
|
357
|
-
Enable prompt caching:
|
|
358
|
-
|
|
359
|
-
```ruby
|
|
360
|
-
TurnKit.prompt_cache = :auto
|
|
361
|
-
```
|
|
362
|
-
|
|
363
|
-
Disable prompt caching:
|
|
364
|
-
|
|
365
|
-
```ruby
|
|
366
|
-
TurnKit.prompt_cache = :off
|
|
367
|
-
```
|
|
368
|
-
|
|
369
|
-
Split custom prompts:
|
|
370
|
-
|
|
371
|
-
```ruby
|
|
372
|
-
agent = TurnKit::Agent.new(
|
|
373
|
-
name: "cached",
|
|
374
|
-
system_prompt: [
|
|
375
|
-
"Stable instructions and tool guidance.",
|
|
376
|
-
TurnKit::SystemPrompt::CACHE_BOUNDARY,
|
|
377
|
-
"Dynamic subject and live context."
|
|
378
|
-
].join("\n")
|
|
379
|
-
)
|
|
380
|
-
```
|
|
381
|
-
|
|
382
|
-
### Custom clients
|
|
383
|
-
|
|
384
|
-
Create a client:
|
|
385
|
-
|
|
386
|
-
```ruby
|
|
387
|
-
class MyClient < TurnKit::Client
|
|
388
|
-
def chat(model:, messages:, tools:, instructions:, temperature: nil, thinking: nil, metadata: nil)
|
|
389
|
-
TurnKit::Result.new(
|
|
390
|
-
text: "provider response",
|
|
391
|
-
model: model,
|
|
392
|
-
usage: TurnKit::Usage.new(
|
|
393
|
-
input_tokens: 100,
|
|
394
|
-
output_tokens: 20,
|
|
395
|
-
cached_tokens: 80,
|
|
396
|
-
cache_write_tokens: 100
|
|
397
|
-
)
|
|
398
|
-
)
|
|
399
|
-
end
|
|
400
|
-
end
|
|
401
|
-
```
|
|
402
|
-
|
|
403
|
-
Use the client:
|
|
404
|
-
|
|
405
|
-
```ruby
|
|
406
|
-
TurnKit.client = MyClient.new
|
|
407
|
-
```
|
|
408
|
-
|
|
409
|
-
Split cache sections:
|
|
410
|
-
|
|
411
|
-
```ruby
|
|
412
|
-
stable, dynamic = TurnKit::SystemPrompt.split_cache_boundary(instructions)
|
|
315
|
+
```sh
|
|
316
|
+
ruby script/manual_compaction.rb
|
|
413
317
|
```
|
|
414
318
|
|
|
415
319
|
### Rails
|
|
@@ -420,47 +324,18 @@ Install Rails persistence:
|
|
|
420
324
|
bin/rails generate turnkit:install
|
|
421
325
|
```
|
|
422
326
|
|
|
423
|
-
The installer creates:
|
|
424
|
-
|
|
425
|
-
- `config/initializers/turnkit.rb`
|
|
426
|
-
- `app/models/turnkit/conversation.rb`
|
|
427
|
-
- `app/models/turnkit/turn.rb`
|
|
428
|
-
- `app/models/turnkit/message.rb`
|
|
429
|
-
- `app/models/turnkit/tool_execution.rb`
|
|
430
|
-
- a migration for TurnKit persistence
|
|
431
|
-
|
|
432
|
-
The generated migration currently uses `ActiveRecord::Migration[7.1]`. In a newer Rails app, update that version if your app requires it, for example `ActiveRecord::Migration[8.1]`.
|
|
433
|
-
|
|
434
327
|
Run migrations:
|
|
435
328
|
|
|
436
329
|
```sh
|
|
437
330
|
bin/rails db:migrate
|
|
438
331
|
```
|
|
439
332
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
```ruby
|
|
443
|
-
TurnKit.store = TurnKit::ActiveRecordStore.new
|
|
444
|
-
```
|
|
445
|
-
|
|
446
|
-
Suggested Rails file layout for your application AI code:
|
|
333
|
+
Use this layout:
|
|
447
334
|
|
|
448
335
|
```text
|
|
449
|
-
app/
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
read_web_page.rb
|
|
453
|
-
skills/
|
|
454
|
-
prompts/
|
|
455
|
-
```
|
|
456
|
-
|
|
457
|
-
If you prefer to keep AI infrastructure out of `app/models`, add an autoloaded directory such as:
|
|
458
|
-
|
|
459
|
-
```text
|
|
460
|
-
app/ai/
|
|
461
|
-
tools/
|
|
462
|
-
skills/
|
|
463
|
-
prompts/
|
|
336
|
+
app/ai/agents/
|
|
337
|
+
app/ai/tools/
|
|
338
|
+
app/ai/skills/
|
|
464
339
|
```
|
|
465
340
|
|
|
466
341
|
Reconcile stale turns:
|
|
@@ -469,112 +344,63 @@ Reconcile stale turns:
|
|
|
469
344
|
TurnKit.reconcile_stale!
|
|
470
345
|
```
|
|
471
346
|
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
Inspect the latest persisted turn in a Rails console:
|
|
347
|
+
## Options
|
|
475
348
|
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
349
|
+
| Option | Description |
|
|
350
|
+
| --- | --- |
|
|
351
|
+
| `TurnKit.default_model` | Set the default model. |
|
|
352
|
+
| `TurnKit.client` | Set the model client. |
|
|
353
|
+
| `TurnKit.store` | Set the persistence store. |
|
|
354
|
+
| `TurnKit.max_iterations` | Limit model loop iterations. |
|
|
355
|
+
| `TurnKit.max_depth` | Limit sub-agent depth. |
|
|
356
|
+
| `TurnKit.max_tool_executions` | Limit tool calls per turn. |
|
|
357
|
+
| `TurnKit.timeout` | Limit turn runtime. |
|
|
358
|
+
| `TurnKit.cost_limit` | Limit estimated turn cost. |
|
|
359
|
+
| `TurnKit.compaction` | Configure context compaction. |
|
|
360
|
+
| `TurnKit.on_event` | Subscribe to lifecycle events. |
|
|
482
361
|
|
|
483
|
-
|
|
362
|
+
Set options globally:
|
|
484
363
|
|
|
485
364
|
```ruby
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
.map { |execution|
|
|
490
|
-
{
|
|
491
|
-
name: execution.tool_name,
|
|
492
|
-
status: execution.status,
|
|
493
|
-
arguments: execution.arguments,
|
|
494
|
-
result_keys: execution.result&.keys,
|
|
495
|
-
error: execution.error
|
|
496
|
-
}
|
|
497
|
-
}
|
|
365
|
+
TurnKit.default_model = "gpt-4.1-mini"
|
|
366
|
+
TurnKit.max_iterations = 25
|
|
367
|
+
TurnKit.timeout = 300
|
|
498
368
|
```
|
|
499
369
|
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
Use a model whose provider key is configured, then run a real tool-using turn:
|
|
370
|
+
Set options per agent:
|
|
503
371
|
|
|
504
372
|
```ruby
|
|
505
|
-
TurnKit.default_model = "gpt-4.1-mini"
|
|
506
|
-
|
|
507
373
|
agent = TurnKit::Agent.new(
|
|
508
|
-
name: "
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
Assistant::Tools::ReadWebPage
|
|
513
|
-
]
|
|
514
|
-
)
|
|
515
|
-
|
|
516
|
-
turn = agent.conversation.ask(
|
|
517
|
-
"Search for the TurnKit Ruby gem, read the first useful result, then summarize it."
|
|
374
|
+
name: "engineer",
|
|
375
|
+
model: "gpt-4.1-mini",
|
|
376
|
+
max_iterations: 10,
|
|
377
|
+
max_depth: 2
|
|
518
378
|
)
|
|
519
|
-
|
|
520
|
-
puts turn.output_text
|
|
521
|
-
|
|
522
|
-
pp Turnkit::ToolExecution
|
|
523
|
-
.where(turn_uid: turn.id)
|
|
524
|
-
.order(:created_at)
|
|
525
|
-
.pluck(:tool_name, :status, :error)
|
|
526
379
|
```
|
|
527
380
|
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
Configure defaults:
|
|
381
|
+
Enable thinking:
|
|
531
382
|
|
|
532
383
|
```ruby
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
TurnKit.cost_limit = nil
|
|
539
|
-
TurnKit.cost_rates = {}
|
|
540
|
-
TurnKit.cost_calculator = nil
|
|
541
|
-
TurnKit.prompt_cache = :auto
|
|
384
|
+
agent = TurnKit::Agent.new(
|
|
385
|
+
name: "reasoner",
|
|
386
|
+
model: "claude-sonnet-4-5",
|
|
387
|
+
thinking: { budget: 4_000 }
|
|
388
|
+
)
|
|
542
389
|
```
|
|
543
390
|
|
|
544
|
-
|
|
391
|
+
## Upgrading
|
|
392
|
+
|
|
393
|
+
Add `output_data` for structured output persistence.
|
|
545
394
|
|
|
546
395
|
```ruby
|
|
547
|
-
|
|
548
|
-
name: "analyst",
|
|
549
|
-
model: "gpt-4.1-mini",
|
|
550
|
-
max_iterations: 10,
|
|
551
|
-
timeout: 60,
|
|
552
|
-
cost_limit: 0.25,
|
|
553
|
-
thinking: { effort: :low }
|
|
554
|
-
)
|
|
396
|
+
add_column :turnkit_turns, :output_data, :json
|
|
555
397
|
```
|
|
556
398
|
|
|
557
|
-
|
|
558
|
-
| --- | --- |
|
|
559
|
-
| `default_model` | Set the default RubyLLM model. |
|
|
560
|
-
| `client` | Set the model client. |
|
|
561
|
-
| `store` | Set the conversation store. |
|
|
562
|
-
| `max_iterations` | Limit model calls per turn. |
|
|
563
|
-
| `timeout` | Limit seconds per root turn. |
|
|
564
|
-
| `max_tool_executions` | Limit tool calls per root turn. |
|
|
565
|
-
| `cost_limit` | Limit cost per root turn. |
|
|
566
|
-
| `thinking` | Configure provider reasoning or extended thinking per agent. |
|
|
567
|
-
| `cost_rates` | Override prices by model. |
|
|
568
|
-
| `cost_calculator` | Override cost calculation. |
|
|
569
|
-
| `prompt_cache` | Use provider prompt caching. |
|
|
399
|
+
Skip this step for new installs.
|
|
570
400
|
|
|
571
401
|
## Contributing
|
|
572
402
|
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
```text
|
|
576
|
-
https://github.com/samuelcouch/turnkit
|
|
577
|
-
```
|
|
403
|
+
Fork the project.
|
|
578
404
|
|
|
579
405
|
Run tests:
|
|
580
406
|
|
|
@@ -582,6 +408,14 @@ Run tests:
|
|
|
582
408
|
bundle exec rake test
|
|
583
409
|
```
|
|
584
410
|
|
|
411
|
+
Run syntax checks:
|
|
412
|
+
|
|
413
|
+
```sh
|
|
414
|
+
find lib test examples -type f -name '*.rb' -print0 | xargs -0 ruby -c
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
Open a pull request.
|
|
418
|
+
|
|
585
419
|
## License
|
|
586
420
|
|
|
587
|
-
|
|
421
|
+
Use this gem under the MIT License.
|