turnkit 0.4.1 → 0.4.2

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 (38) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +18 -0
  3. data/README.md +111 -50
  4. data/UPGRADE.md +29 -0
  5. data/UPGRADE_TO_0_4_2.md +425 -0
  6. data/lib/{turnkit/generators → generators}/turnkit/install/templates/initializer.rb +7 -6
  7. data/lib/turnkit/{stores/active_record_store.rb → active_record_store.rb} +18 -4
  8. data/lib/turnkit/adapters/codex.rb +8 -11
  9. data/lib/turnkit/adapters/ruby_llm.rb +30 -46
  10. data/lib/turnkit/agent.rb +43 -10
  11. data/lib/turnkit/budget.rb +1 -1
  12. data/lib/turnkit/client.rb +5 -1
  13. data/lib/turnkit/compaction.rb +46 -46
  14. data/lib/turnkit/cost.rb +29 -31
  15. data/lib/turnkit/image_result.rb +1 -0
  16. data/lib/turnkit/image_tool.rb +2 -2
  17. data/lib/turnkit/media_analysis_result.rb +0 -2
  18. data/lib/turnkit/model_request.rb +4 -2
  19. data/lib/turnkit/output_policy.rb +1 -15
  20. data/lib/turnkit/run.rb +0 -4
  21. data/lib/turnkit/store.rb +4 -6
  22. data/lib/turnkit/sub_agent_tool.rb +9 -14
  23. data/lib/turnkit/system_prompt.rb +32 -96
  24. data/lib/turnkit/tool.rb +5 -16
  25. data/lib/turnkit/turn.rb +73 -89
  26. data/lib/turnkit/version.rb +1 -1
  27. data/lib/turnkit/view_media_tool.rb +2 -2
  28. data/lib/turnkit.rb +1 -18
  29. metadata +15 -30
  30. data/lib/turnkit/prompt_contribution.rb +0 -13
  31. data/lib/turnkit/rails/railtie.rb +0 -9
  32. data/lib/turnkit/workflow.rb +0 -58
  33. /data/lib/{turnkit/generators → generators}/turnkit/install/templates/conversation.rb +0 -0
  34. /data/lib/{turnkit/generators → generators}/turnkit/install/templates/create_turnkit_tables.rb +0 -0
  35. /data/lib/{turnkit/generators → generators}/turnkit/install/templates/message.rb +0 -0
  36. /data/lib/{turnkit/generators → generators}/turnkit/install/templates/tool_execution.rb +0 -0
  37. /data/lib/{turnkit/generators → generators}/turnkit/install/templates/turn.rb +0 -0
  38. /data/lib/{turnkit/generators → generators}/turnkit/install_generator.rb +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 67553a737fbce38e2402167aeb2cc799c55390c5c7c9740a8d73d056b0679a06
4
- data.tar.gz: fc395c09f05e8ba640ec9dda4907419f0a76a47e8a45607435c5dde0630c3562
3
+ metadata.gz: 78b1d7eae5414daf8116c999392a6d726e50360c643578d35ff7154559c525f3
4
+ data.tar.gz: f6e0f434323bf93ac97af5839cf08ede8d89b9aa50fe96eb0c0ed93992fd4d42
5
5
  SHA512:
6
- metadata.gz: 45864840f7bc24d3626e0e3bb849f2ea3d71c1fdb8a2faa7ff19725ab0b6932d205962208c2ea75be7014d8f0befa7c6ebd754e0f5dfedd7db6875fff90254a9
7
- data.tar.gz: 7dc83b0922078e52fb220438097514c0efba0d10740a9cec40e810f316d42c6e50fdc67f92db2cb9e9564fdcab376a1e01286d8821effa4a9246e51f8baf6c1c
6
+ metadata.gz: 8fdf2b4569248ebc409da2915663864e5891d54049c3696d827e0c2c2242b283c52e2f0cf2bbaee9adc08094d74c839b28dab2f5efaf898a03812393de28cf18
7
+ data.tar.gz: 3283229cf6c2e94d109437b715ee21944fbebaefe1a430670c8dc4410cbb0fbc6e670039a3d60730f4a5fd27ee2669b65eab4431cb362e342bed32dac89d4fec
data/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.2 - 2026-07-02
4
+
5
+ ### Breaking
6
+
7
+ - Remove `TurnKit::Workflow`; use `TurnKit::Agent.new(orchestrator: true, ...)` for reusable autonomous task runners.
8
+ - Make `Agent#run` task positional, remove `TurnKit.model` aliases, and rename run accessors to `output_text` and `tool_executions`.
9
+ - Simplify tool context injection to `context:`, reserve `context` as a tool parameter name, and reduce `SubAgentTool` input to `task`.
10
+ - Replace prompt contributor APIs with per-agent `system_prompt:` customization and stable/dynamic `TurnKit::SystemPrompt` accessors.
11
+ - Strictly validate custom cost-rate keys, remove `MediaAnalysisResult#structured?`, remove Rails record-class globals, and require atomic custom store `claim_turn` implementations.
12
+ - Remove `ruby_llm` as a runtime dependency; add `gem "ruby_llm", "~> 1.16"` when using the default RubyLLM adapter.
13
+ - Call custom clients with the full `TurnKit::Client` keyword contracts, including `dynamic_instructions:` for split stable/dynamic prompts.
14
+
15
+ ### Changed
16
+
17
+ - Keep the Rails install generator under the conventional `lib/generators` path without a Railtie.
18
+ - Document compaction config with symbol keys while continuing to accept string keys.
19
+ - Persist turn runtime state under `options["state"]` with backward-compatible reads.
20
+
3
21
  ## 0.4.1 - 2026-06-19
4
22
 
5
23
  - Add first-class media analysis with `Turn#view_media`, `TurnKit.view_media`, and `TurnKit::ViewMediaTool`.
data/README.md CHANGED
@@ -4,8 +4,8 @@
4
4
  [![Ruby](https://img.shields.io/badge/ruby-%3E%3D%203.1-red.svg)](https://www.ruby-lang.org)
5
5
  [![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE.md)
6
6
 
7
- Build durable Ruby and Rails agents with conversations, runs, workflows, tools,
8
- skills, output audits, sub-agents, and persistence.
7
+ Build durable Ruby and Rails agents with conversations, runs, orchestrator agents,
8
+ tools, skills, output audits, sub-agents, and persistence.
9
9
 
10
10
  ## Installation
11
11
 
@@ -13,6 +13,9 @@ Add this line to your application's **Gemfile**:
13
13
 
14
14
  ```ruby
15
15
  gem "turnkit"
16
+
17
+ # Required only when using TurnKit's default RubyLLM adapter.
18
+ gem "ruby_llm", "~> 1.16"
16
19
  ```
17
20
 
18
21
  Run:
@@ -21,7 +24,7 @@ Run:
21
24
  bundle install
22
25
  ```
23
26
 
24
- Upgrading from an earlier TurnKit version? See the [Upgrade Guide](UPGRADE.md).
27
+ Upgrading from an earlier TurnKit version? See the [0.4.2 Upgrade Guide](UPGRADE_TO_0_4_2.md).
25
28
 
26
29
  ## Quick Start
27
30
 
@@ -49,11 +52,11 @@ turn = agent.conversation.ask("Explain Ruby blocks in one sentence.")
49
52
  puts turn.output_text
50
53
  ```
51
54
 
52
- Or run a non-interactive application task:
55
+ Or run a non-interactive application task.
53
56
 
54
57
  ```ruby
55
58
  run = agent.run("Explain Ruby blocks in one sentence.")
56
- puts run.output
59
+ puts run.output_text
57
60
  ```
58
61
 
59
62
  ## Usage
@@ -63,9 +66,9 @@ For runnable, API-key-free examples of the three core entry points, see
63
66
 
64
67
  - conversation: durable thread over time;
65
68
  - agent run: one bounded application task;
66
- - workflow: reusable task runner with skills, tools, and limits.
69
+ - orchestrator agent: reusable task runner with skills, tools, and limits.
67
70
 
68
- For fuller workflow examples, see:
71
+ For fuller orchestrator examples, see:
69
72
 
70
73
  - [`examples/workflow_researcher`](examples/workflow_researcher): source-grounded research with web tools, batch reads, per-tool limits, and deep monitoring;
71
74
  - [`examples/amazon_memo_writer`](examples/amazon_memo_writer): strict memo generation with research tools, a structured terminal submit tool, deterministic format checks, and an LLM output policy.
@@ -75,14 +78,14 @@ For fuller workflow examples, see:
75
78
  Set a model:
76
79
 
77
80
  ```ruby
78
- TurnKit.model = "gpt-4.1-mini"
81
+ TurnKit.default_model = "gpt-4.1-mini"
79
82
  ```
80
83
 
81
84
  Or configure TurnKit in one place:
82
85
 
83
86
  ```ruby
84
87
  TurnKit.configure do |config|
85
- config.model = "gpt-4.1-mini"
88
+ config.default_model = "gpt-4.1-mini"
86
89
  config.max_spend = 0.25
87
90
  config.max_iterations = 12
88
91
  end
@@ -116,7 +119,7 @@ Then configure TurnKit:
116
119
  ```ruby
117
120
  TurnKit.configure do |config|
118
121
  config.client = TurnKit::Adapters::Codex.new(sandbox: "read-only")
119
- config.model = "gpt-5.4"
122
+ config.default_model = "gpt-5.4"
120
123
  end
121
124
  ```
122
125
 
@@ -186,26 +189,26 @@ small wrapper over TurnKit's existing conversation and turn engine. Existing
186
189
  Prepare a pending run without calling the model:
187
190
 
188
191
  ```ruby
189
- run = agent.run(task: "Classify later.", async: true)
192
+ run = agent.run("Classify later.", async: true)
190
193
  request = run.preview
191
194
  run.run!
192
195
  ```
193
196
 
194
- ### Workflows
197
+ ### Orchestrator agents
195
198
 
196
- Use a workflow when a run graduates into a reusable production capability: a
197
- named task runner with workflow skills, tools, defaults, guardrails, compaction,
198
- and output policy.
199
+ Use an orchestrator agent when a run graduates into a reusable production
200
+ capability: a named task runner with skills, tools, defaults, guardrails,
201
+ compaction, and output policy.
199
202
 
200
- Workflows fight for their life when the task has a repeatable operating
201
- procedure: inspect app data, gather context, use sources, draft, verify, save,
202
- and stop under budget. They are overkill for simple classification or extraction
203
- runs.
203
+ Orchestrator agents fight for their life when the task has a repeatable
204
+ operating procedure: inspect app data, gather context, use sources, draft,
205
+ verify, save, and stop under budget. They are overkill for simple classification
206
+ or extraction runs.
204
207
 
205
208
  ```ruby
206
209
  source_grounded_brief = TurnKit::Skill.from_file("app/ai/skills/source_grounded_brief.md")
207
210
 
208
- workflow = TurnKit::Workflow.new(
211
+ agent = TurnKit::Agent.new(
209
212
  name: "brief_writer",
210
213
  instructions: "Create source-grounded briefs and verify claims before final output.",
211
214
  skills: [source_grounded_brief],
@@ -220,16 +223,17 @@ workflow = TurnKit::Workflow.new(
220
223
  compaction: {
221
224
  context_limit: 64_000,
222
225
  threshold: 0.75
223
- }
226
+ },
227
+ orchestrator: true
224
228
  )
225
229
 
226
- run = workflow.run(
230
+ run = agent.run(
227
231
  "Create a source-grounded brief.",
228
232
  input: { topic: "Rails 8 Solid Queue" }
229
233
  )
230
234
 
231
- puts run.output
232
- puts run.tool_calls.map(&:tool_name)
235
+ puts run.output_text
236
+ puts run.tool_executions.map(&:tool_name)
233
237
  puts run.cost.total
234
238
  ```
235
239
 
@@ -240,11 +244,11 @@ model-tool loop:
240
244
  model → tool → result → model → tool → result → final
241
245
  ```
242
246
 
243
- For repeated workflows, keep instructions, skills, and tools stable and pass the
247
+ For repeated orchestrator runs, keep instructions, skills, and tools stable and pass the
244
248
  per-run data through `input:`. This gives provider prompt caching the best chance
245
- to reuse the stable workflow prompt while each run supplies dynamic data.
249
+ to reuse the stable agent prompt while each run supplies dynamic data.
246
250
 
247
- ### Choosing runs, conversations, and workflows
251
+ ### Choosing runs, conversations, and orchestrator agents
248
252
 
249
253
  Use the smallest entry point that matches the shape of work:
250
254
 
@@ -252,7 +256,7 @@ Use the smallest entry point that matches the shape of work:
252
256
  | --- | --- | --- |
253
257
  | `Conversation` | A user or app will keep adding messages over time. | Best for durable threads and follow-up steering; history grows, so long threads need compaction. |
254
258
  | `Agent#run` | Your app needs one bounded result now. | Best for simple production tasks; repeated complex policies can sprawl across callers. |
255
- | `TurnKit::Workflow` | A task becomes a named reusable workflow with tools, skills, limits, and observability. | Best cache and packaging story for repeated autonomous work; overkill for one-off/simple tasks. |
259
+ | `Agent.new(orchestrator: true)` | A task becomes a named reusable agent with tools, skills, limits, and observability. | Best cache and packaging story for repeated autonomous work; overkill for one-off/simple tasks. |
256
260
 
257
261
  Prompt caching and compaction solve different problems:
258
262
 
@@ -262,7 +266,7 @@ Prompt caching and compaction solve different problems:
262
266
  - budgets (`max_spend`, `max_iterations`, `max_tool_executions`) keep autonomous
263
267
  loops bounded.
264
268
 
265
- Use `max_tool_executions_by_name` when a workflow needs different budgets for
269
+ Use `max_tool_executions_by_name` when an orchestrator needs different budgets for
266
270
  different tools. For example, allow many cheap reads but only one final submit
267
271
  tool, or cap web searches while allowing a batch page reader.
268
272
 
@@ -270,19 +274,25 @@ Reach for separate agents and `sub_agents` only when the isolation is worth the
270
274
  extra model calls, such as different models, different tool permissions,
271
275
  parallel specialist review, or separate durable child conversations.
272
276
 
273
- Run a workflow with `run`:
277
+ Run an orchestrator agent with `run`:
274
278
 
275
279
  ```ruby
276
- run = workflow.run(
277
- "Create compliant outreach for this account.",
278
- input: lead.attributes,
280
+ outreach_agent = TurnKit::Agent.new(
281
+ name: "outreach_writer",
282
+ instructions: "Create compliant outreach for accounts.",
279
283
  max_spend: 0.25,
280
284
  max_iterations: 8,
281
285
  max_tool_executions: 20,
282
286
  compaction: {
283
287
  context_limit: 64_000,
284
288
  threshold: 0.75
285
- }
289
+ },
290
+ orchestrator: true
291
+ )
292
+
293
+ run = outreach_agent.run(
294
+ "Create compliant outreach for this account.",
295
+ input: lead.attributes
286
296
  )
287
297
  ```
288
298
 
@@ -329,10 +339,11 @@ numbered_lists_only = ->(output) do
329
339
  }
330
340
  end
331
341
 
332
- workflow = TurnKit::Workflow.new(
342
+ agent = TurnKit::Agent.new(
333
343
  name: "memo_writer",
334
344
  output_policy: [no_em_dash, numbered_lists_only],
335
- output_policy_mode: :fail
345
+ output_policy_mode: :fail,
346
+ orchestrator: true
336
347
  )
337
348
  ```
338
349
 
@@ -354,18 +365,19 @@ policy can be a `.md`, `.markdown`, or `.txt` file path, a `TurnKit::Skill`, a
354
365
  `TurnKit::OutputPolicy`, or any object that responds to `#call` or `#check`.
355
366
 
356
367
  ```ruby
357
- workflow = TurnKit::Workflow.new(
368
+ agent = TurnKit::Agent.new(
358
369
  name: "memo_writer",
359
370
  output_policy: "app/ai/policies/amazon_memo.md",
360
371
  output_policy_model: "gpt-4.1-mini",
361
372
  output_policy_thinking: { effort: :low },
362
- output_policy_mode: :report
373
+ output_policy_mode: :report,
374
+ orchestrator: true
363
375
  )
364
376
  ```
365
377
 
366
378
  `output_policy_mode: :report` records violations while allowing the run to
367
379
  complete. `:fail` marks the run failed after recording the output and audit;
368
- `:fail` is the default for contract-driven workflows. Policy model usage and
380
+ `:fail` is the default for contract-driven orchestrator runs. Policy model usage and
369
381
  cost are counted on the parent run.
370
382
 
371
383
  Add `output_retries:` to turn policy failures into bounded revision loops instead
@@ -374,7 +386,7 @@ of dead ends:
374
386
  ```ruby
375
387
  voice = TurnKit::Skill.from_file("app/ai/skills/memo_voice.md")
376
388
 
377
- workflow = TurnKit::Workflow.new(
389
+ agent = TurnKit::Agent.new(
378
390
  name: "memo_writer",
379
391
  skills: [voice],
380
392
  output_policy: [voice, no_em_dash],
@@ -383,7 +395,8 @@ workflow = TurnKit::Workflow.new(
383
395
  "type" => "object",
384
396
  "required" => ["project_id"],
385
397
  "properties" => { "project_id" => { "type" => "string" } }
386
- }
398
+ },
399
+ orchestrator: true
387
400
  )
388
401
  ```
389
402
 
@@ -418,6 +431,27 @@ Run the reviewed turn:
418
431
  turn.run!
419
432
  ```
420
433
 
434
+
435
+ ### Prompt customization
436
+
437
+ Customize generated prompts with `system_prompt:` on an agent. A string replaces
438
+ the generated prompt. A callable receives the built `TurnKit::SystemPrompt` and
439
+ returns the final string:
440
+
441
+ ```ruby
442
+ agent = TurnKit::Agent.new(
443
+ name: "reporter",
444
+ system_prompt: ->(prompt) {
445
+ [prompt.stable, prompt.section(:tools), prompt.dynamic].reject(&:empty?).join("\n\n")
446
+ }
447
+ )
448
+ ```
449
+
450
+ `TurnKit::SystemPrompt` supports `to_s`, `section(:tools)`, `stable`, and
451
+ `dynamic`. `prompt_sections:`, `TurnKit.prompt_sections`,
452
+ `TurnKit.prompt_behavior`, and `TurnKit.context_contributors` remain available
453
+ for generated prompts.
454
+
421
455
  ### Tools
422
456
 
423
457
  Create a tool:
@@ -479,7 +513,7 @@ image.to_blob # generated bytes for base64 responses, or fetched URL bytes
479
513
  image.mime_type # "image/png"
480
514
  ```
481
515
 
482
- For reusable workflow steps, subclass `TurnKit::ImageTool`:
516
+ For reusable agent steps, subclass `TurnKit::ImageTool`:
483
517
 
484
518
  ```ruby
485
519
  class GenerateHeaderImage < TurnKit::ImageTool
@@ -546,7 +580,7 @@ media = TurnKit::MediaInput.bytes(
546
580
  )
547
581
  ```
548
582
 
549
- For reusable workflow steps, subclass `TurnKit::ViewMediaTool`:
583
+ For reusable agent steps, subclass `TurnKit::ViewMediaTool`:
550
584
 
551
585
  ```ruby
552
586
  class ReviewHeaderImage < TurnKit::ViewMediaTool
@@ -717,12 +751,6 @@ Compact manually:
717
751
  conversation.compact!(focus: "billing migration")
718
752
  ```
719
753
 
720
- Run the local smoke test:
721
-
722
- ```sh
723
- ruby script/manual_compaction.rb
724
- ```
725
-
726
754
  ### Rails
727
755
 
728
756
  Install Rails persistence:
@@ -745,6 +773,17 @@ app/ai/tools/
745
773
  app/ai/skills/
746
774
  ```
747
775
 
776
+ Use custom Active Record classes by passing class names to the store:
777
+
778
+ ```ruby
779
+ TurnKit.store = TurnKit::ActiveRecordStore.new(
780
+ conversation_class: "My::Conversation",
781
+ turn_class: "My::Turn",
782
+ message_class: "My::Message",
783
+ tool_execution_class: "My::ToolExecution"
784
+ )
785
+ ```
786
+
748
787
  Reconcile stale turns:
749
788
 
750
789
  ```ruby
@@ -782,6 +821,25 @@ TurnKit.timeout = 300
782
821
 
783
822
  `max_spend` is the only spend-limit name in the public API.
784
823
 
824
+
825
+ Customize cost rates with USD-per-million-token component keys:
826
+
827
+ ```ruby
828
+ TurnKit.cost_rates["custom-model"] = {
829
+ input: 0.15,
830
+ output: 0.60,
831
+ cache_read: 0.03,
832
+ cache_write: 0.18,
833
+ thinking: 0.60
834
+ }
835
+ ```
836
+
837
+ Custom clients should subclass `TurnKit::Client` or accept the full `#chat`
838
+ keyword contract, including `dynamic_instructions:`. Adapters that support prompt
839
+ caching should cache `instructions` and append `dynamic_instructions` per turn.
840
+
841
+ Custom stores must implement `claim_turn` atomically.
842
+
785
843
  Set options per agent:
786
844
 
787
845
  ```ruby
@@ -805,7 +863,10 @@ agent = TurnKit::Agent.new(
805
863
 
806
864
  ## Upgrading
807
865
 
808
- Add `output_data` for structured output persistence.
866
+ See the [0.4.2 Upgrade Guide](UPGRADE_TO_0_4_2.md) for the full API migration checklist.
867
+
868
+ Rails installs from older versions may need `output_data` for structured output,
869
+ image, and media-analysis persistence.
809
870
 
810
871
  ```ruby
811
872
  add_column :turnkit_turns, :output_data, :json
data/UPGRADE.md CHANGED
@@ -1,5 +1,34 @@
1
1
  # Upgrade Guide
2
2
 
3
+ ## 0.4.2
4
+
5
+ TurnKit 0.4.2 is a pre-1.0 surface cleanup. Update these call sites before upgrading:
6
+
7
+ - `TurnKit::Workflow.new(...)` → `TurnKit::Agent.new(orchestrator: true, ...)`. Orchestrator agents prepend the same autonomous-orchestrator preamble and default `prompt_mode` to `:task`.
8
+ - `workflow.run(task, max_spend: ...)` / other per-run workflow overrides → configure those limits on the `Agent`; agents are cheap to construct.
9
+ - `workflow.agent(**overrides)` → construct another `TurnKit::Agent` with the desired options.
10
+ - `agent.run(task: "...")` → `agent.run("...", input: ..., async: ...)`.
11
+ - `TurnKit.model` / `TurnKit.model=` → `TurnKit.default_model` / `TurnKit.default_model=`.
12
+ - `run.output` → `run.output_text`.
13
+ - `run.tool_calls` → `run.tool_executions`.
14
+ - `run.steps` and `run.persisted?` were removed.
15
+ - Tool framework context is always passed as `context:`. The `turnkit_context:` alternative was removed, and `context` is now a reserved tool parameter name.
16
+ - `TurnKit::SubAgentTool` exposes a single model-visible `task` parameter.
17
+ - Prompt extension hooks `TurnKit.system_prompt_contributors`, `TurnKit.model_prompt_contributors`, `TurnKit::PromptContribution`, and prompt section override objects were removed. Pass `system_prompt:` to `TurnKit::Agent` instead: a string replaces the generated prompt; a callable receives `TurnKit::SystemPrompt` and returns the final string.
18
+ - `TurnKit::SystemPrompt` supports `to_s`, `section(:tools)`, `stable`, and `dynamic`. `TurnKit.prompt_sections`, per-agent `prompt_sections:`, `TurnKit.prompt_behavior`, and `TurnKit.context_contributors` remain.
19
+ - `TurnKit.cost_rates` accepts only `input`, `output`, `cache_read`, `cache_write`, and `thinking` keys, expressed as USD per million tokens. Old aliases such as `cached_input`, `cache_creation`, `reasoning`, and `*_per_million` now raise `TurnKit::ConfigError`.
20
+ - `MediaAnalysisResult#structured?` → `MediaAnalysisResult#data?`.
21
+ - Rails record class globals (`TurnKit.conversation_record_class`, turn/message/tool_execution variants) were removed. Pass class names to the store: `TurnKit::ActiveRecordStore.new(conversation_class: "My::Conversation", turn_class: "My::Turn", message_class: "My::Message", tool_execution_class: "My::ToolExecution")`.
22
+ - The Railtie was removed. `rails g turnkit:install` still works through the conventional `lib/generators` path.
23
+ - `ruby_llm` is no longer a runtime dependency. To use the default RubyLLM adapter, add `gem "ruby_llm", "~> 1.16"` to your Gemfile. The Codex adapter and custom `TurnKit::Client` subclasses need no extra gems.
24
+ - Custom clients are called with the full `TurnKit::Client` keyword contracts for `chat`, `paint`, and `view_media`; signature sniffing was removed. Subclass `TurnKit::Client` or accept the full keyword sets.
25
+ - `Client#chat` gained `dynamic_instructions:`. Cache the stable `instructions` string and append `dynamic_instructions` per turn when the provider supports prompt caching.
26
+ - `SystemPrompt::CACHE_BOUNDARY`, `split_cache_boundary`, and the `has_cache_boundary` prompt report field were removed. `SystemPrompt#report` keeps `stable_chars` and `dynamic_chars`.
27
+ - `TurnKit::Store#claim_turn` no longer has a default implementation. Custom stores must implement an atomic compare-and-set claim.
28
+ - Compaction configuration examples now use symbol keys; string keys are still accepted.
29
+
30
+ Non-breaking: turn runtime state such as iterations and policy audit now persists under `options["state"]` with backward-compatible reads.
31
+
3
32
  ## 0.3.0 is a clean break
4
33
 
5
34
  TurnKit 0.3.0 intentionally removes the short-lived legacy names from the 0.2