turnkit 0.4.1 → 0.5.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 (42) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +34 -0
  3. data/README.md +126 -51
  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} +34 -10
  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/memory_store.rb +11 -6
  19. data/lib/turnkit/model_request.rb +4 -2
  20. data/lib/turnkit/output_policy.rb +1 -15
  21. data/lib/turnkit/reconciliation.rb +72 -0
  22. data/lib/turnkit/record.rb +1 -1
  23. data/lib/turnkit/run.rb +0 -4
  24. data/lib/turnkit/store.rb +17 -8
  25. data/lib/turnkit/sub_agent_tool.rb +9 -14
  26. data/lib/turnkit/system_prompt.rb +32 -96
  27. data/lib/turnkit/tool.rb +5 -16
  28. data/lib/turnkit/tool_runner.rb +27 -3
  29. data/lib/turnkit/turn.rb +73 -89
  30. data/lib/turnkit/version.rb +1 -1
  31. data/lib/turnkit/view_media_tool.rb +2 -2
  32. data/lib/turnkit.rb +3 -21
  33. metadata +16 -30
  34. data/lib/turnkit/prompt_contribution.rb +0 -13
  35. data/lib/turnkit/rails/railtie.rb +0 -9
  36. data/lib/turnkit/workflow.rb +0 -58
  37. /data/lib/{turnkit/generators → generators}/turnkit/install/templates/conversation.rb +0 -0
  38. /data/lib/{turnkit/generators → generators}/turnkit/install/templates/create_turnkit_tables.rb +0 -0
  39. /data/lib/{turnkit/generators → generators}/turnkit/install/templates/message.rb +0 -0
  40. /data/lib/{turnkit/generators → generators}/turnkit/install/templates/tool_execution.rb +0 -0
  41. /data/lib/{turnkit/generators → generators}/turnkit/install/templates/turn.rb +0 -0
  42. /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: 3db08f0653a468f22daa74070c7b1a290f02b44beaf6d35c950f3d36d86dc224
4
+ data.tar.gz: 18d3b2df593865ba53e0863d0e2b061f8744bdbbf78409b40d525c3ba47b5070
5
5
  SHA512:
6
- metadata.gz: 45864840f7bc24d3626e0e3bb849f2ea3d71c1fdb8a2faa7ff19725ab0b6932d205962208c2ea75be7014d8f0befa7c6ebd754e0f5dfedd7db6875fff90254a9
7
- data.tar.gz: 7dc83b0922078e52fb220438097514c0efba0d10740a9cec40e810f316d42c6e50fdc67f92db2cb9e9564fdcab376a1e01286d8821effa4a9246e51f8baf6c1c
6
+ metadata.gz: dd2216c9e2275dd737d6e1cf7dadd97053ca5afb6992939791669f29e047f3e44eb45f492a09b4494222117428baf07ffd9aa60352f9363b0a8b3eedb9c6ab54
7
+ data.tar.gz: 3b8f1a45bc98506dc09a9a6703aacf1f614fb7649c3c64942c98e902aff1db9232f986aad33c077be51fec1623d82abf4506d32698b34e3079ff775b9a1eb38b
data/CHANGELOG.md CHANGED
@@ -1,5 +1,39 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.5.0 - 2026-07-22
4
+
5
+ ### Breaking
6
+
7
+ - Custom stores: `find_stale_turns` is replaced by `reconcile_stale_turns(before:)`, which must atomically transition eligible turns to `stale` and return the reconciled records. `update_tool_execution` is replaced by `claim_tool_execution(id, from:, to:, **attributes)`, an atomic compare-and-set mirroring `claim_turn`.
8
+
9
+ ### Fixed
10
+
11
+ - Make stale-turn reconciliation atomic, so `TurnKit.reconcile_stale!` can no longer overwrite a turn that was concurrently claimed, heartbeated, or completed (#1).
12
+ - Heartbeat while a tool executes, so tools slower than `TurnKit.timeout` are not falsely reconciled.
13
+
14
+ ### Added
15
+
16
+ - Add an `interrupted` tool-execution status. Reconciliation marks a stale turn's unfinished tool executions `interrupted`, appends a synthetic error tool result for unresolved tool calls (keeping the transcript continuable), and emits `turn.stale` and `tool_call.interrupted` events. Late tool results arriving after interruption are dropped, never overwriting the reconciled state.
17
+ - `TurnKit.reconcile_stale!` returns the reconciled turn records. `stale` is provisional: a worker whose heartbeats were merely late finishes its turn normally, replacing `stale` with the actual outcome.
18
+
19
+ ## 0.4.2 - 2026-07-02
20
+
21
+ ### Breaking
22
+
23
+ - Remove `TurnKit::Workflow`; use `TurnKit::Agent.new(orchestrator: true, ...)` for reusable autonomous task runners.
24
+ - Make `Agent#run` task positional, remove `TurnKit.model` aliases, and rename run accessors to `output_text` and `tool_executions`.
25
+ - Simplify tool context injection to `context:`, reserve `context` as a tool parameter name, and reduce `SubAgentTool` input to `task`.
26
+ - Replace prompt contributor APIs with per-agent `system_prompt:` customization and stable/dynamic `TurnKit::SystemPrompt` accessors.
27
+ - Strictly validate custom cost-rate keys, remove `MediaAnalysisResult#structured?`, remove Rails record-class globals, and require atomic custom store `claim_turn` implementations.
28
+ - Remove `ruby_llm` as a runtime dependency; add `gem "ruby_llm", "~> 1.16"` when using the default RubyLLM adapter.
29
+ - Call custom clients with the full `TurnKit::Client` keyword contracts, including `dynamic_instructions:` for split stable/dynamic prompts.
30
+
31
+ ### Changed
32
+
33
+ - Keep the Rails install generator under the conventional `lib/generators` path without a Railtie.
34
+ - Document compaction config with symbol keys while continuing to accept string keys.
35
+ - Persist turn runtime state under `options["state"]` with backward-compatible reads.
36
+
3
37
  ## 0.4.1 - 2026-06-19
4
38
 
5
39
  - 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,12 +773,37 @@ app/ai/tools/
745
773
  app/ai/skills/
746
774
  ```
747
775
 
748
- Reconcile stale turns:
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
+
787
+ Reconcile turns abandoned by a dead worker (for example after a hard-killed
788
+ process). Run this periodically:
749
789
 
750
790
  ```ruby
751
791
  TurnKit.reconcile_stale!
752
792
  ```
753
793
 
794
+ Reconciliation atomically marks pending and running turns whose last heartbeat
795
+ is older than `TurnKit.timeout` as `stale`, so it never overwrites a turn that
796
+ was concurrently claimed, heartbeated, or completed. Each stale turn's
797
+ unfinished tool executions become `interrupted`, and a synthetic error tool
798
+ result is appended for any unresolved tool call so the conversation can be
799
+ continued. TurnKit never reruns an interrupted tool — whether its side effect
800
+ happened is unknown, so the continued model is told not to assume either way.
801
+
802
+ Reconciliation does not cancel or reassign the underlying process. If the
803
+ original worker is still alive (its heartbeats were merely late), it continues
804
+ over the synthetic interrupted result and later replaces `stale` with its
805
+ actual `completed` or `failed` outcome — treat `turn.stale` as provisional.
806
+
754
807
  ## Options
755
808
 
756
809
  | Option | Description |
@@ -782,6 +835,25 @@ TurnKit.timeout = 300
782
835
 
783
836
  `max_spend` is the only spend-limit name in the public API.
784
837
 
838
+
839
+ Customize cost rates with USD-per-million-token component keys:
840
+
841
+ ```ruby
842
+ TurnKit.cost_rates["custom-model"] = {
843
+ input: 0.15,
844
+ output: 0.60,
845
+ cache_read: 0.03,
846
+ cache_write: 0.18,
847
+ thinking: 0.60
848
+ }
849
+ ```
850
+
851
+ Custom clients should subclass `TurnKit::Client` or accept the full `#chat`
852
+ keyword contract, including `dynamic_instructions:`. Adapters that support prompt
853
+ caching should cache `instructions` and append `dynamic_instructions` per turn.
854
+
855
+ Custom stores must implement `claim_turn` atomically.
856
+
785
857
  Set options per agent:
786
858
 
787
859
  ```ruby
@@ -805,7 +877,10 @@ agent = TurnKit::Agent.new(
805
877
 
806
878
  ## Upgrading
807
879
 
808
- Add `output_data` for structured output persistence.
880
+ See the [0.4.2 Upgrade Guide](UPGRADE_TO_0_4_2.md) for the full API migration checklist.
881
+
882
+ Rails installs from older versions may need `output_data` for structured output,
883
+ image, and media-analysis persistence.
809
884
 
810
885
  ```ruby
811
886
  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