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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +34 -0
- data/README.md +126 -51
- data/UPGRADE.md +29 -0
- data/UPGRADE_TO_0_4_2.md +425 -0
- data/lib/{turnkit/generators → generators}/turnkit/install/templates/initializer.rb +7 -6
- data/lib/turnkit/{stores/active_record_store.rb → active_record_store.rb} +34 -10
- data/lib/turnkit/adapters/codex.rb +8 -11
- data/lib/turnkit/adapters/ruby_llm.rb +30 -46
- data/lib/turnkit/agent.rb +43 -10
- data/lib/turnkit/budget.rb +1 -1
- data/lib/turnkit/client.rb +5 -1
- data/lib/turnkit/compaction.rb +46 -46
- data/lib/turnkit/cost.rb +29 -31
- data/lib/turnkit/image_result.rb +1 -0
- data/lib/turnkit/image_tool.rb +2 -2
- data/lib/turnkit/media_analysis_result.rb +0 -2
- data/lib/turnkit/memory_store.rb +11 -6
- data/lib/turnkit/model_request.rb +4 -2
- data/lib/turnkit/output_policy.rb +1 -15
- data/lib/turnkit/reconciliation.rb +72 -0
- data/lib/turnkit/record.rb +1 -1
- data/lib/turnkit/run.rb +0 -4
- data/lib/turnkit/store.rb +17 -8
- data/lib/turnkit/sub_agent_tool.rb +9 -14
- data/lib/turnkit/system_prompt.rb +32 -96
- data/lib/turnkit/tool.rb +5 -16
- data/lib/turnkit/tool_runner.rb +27 -3
- data/lib/turnkit/turn.rb +73 -89
- data/lib/turnkit/version.rb +1 -1
- data/lib/turnkit/view_media_tool.rb +2 -2
- data/lib/turnkit.rb +3 -21
- metadata +16 -30
- data/lib/turnkit/prompt_contribution.rb +0 -13
- data/lib/turnkit/rails/railtie.rb +0 -9
- data/lib/turnkit/workflow.rb +0 -58
- /data/lib/{turnkit/generators → generators}/turnkit/install/templates/conversation.rb +0 -0
- /data/lib/{turnkit/generators → generators}/turnkit/install/templates/create_turnkit_tables.rb +0 -0
- /data/lib/{turnkit/generators → generators}/turnkit/install/templates/message.rb +0 -0
- /data/lib/{turnkit/generators → generators}/turnkit/install/templates/tool_execution.rb +0 -0
- /data/lib/{turnkit/generators → generators}/turnkit/install/templates/turn.rb +0 -0
- /data/lib/{turnkit/generators → generators}/turnkit/install_generator.rb +0 -0
data/lib/turnkit/turn.rb
CHANGED
|
@@ -43,7 +43,7 @@ module TurnKit
|
|
|
43
43
|
@started_at = @record["started_at"]
|
|
44
44
|
emit("turn.started", status: status, model: model)
|
|
45
45
|
agent.effective_client.validate!(model: model)
|
|
46
|
-
@budget = Budget.resume(store: store, root_turn_id: root_turn_id, limits: budget_limits)
|
|
46
|
+
@budget = Budget.resume(store: store, root_turn_id: root_turn_id, limits: agent.budget_limits)
|
|
47
47
|
revisions_used = 0
|
|
48
48
|
loop do
|
|
49
49
|
budget.check!(depth: depth)
|
|
@@ -113,7 +113,15 @@ module TurnKit
|
|
|
113
113
|
end
|
|
114
114
|
|
|
115
115
|
def policy_audit
|
|
116
|
-
|
|
116
|
+
options = @record["options"] || {}
|
|
117
|
+
options.dig("state", "policy_audit") || options["policy_audit"]
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Reads iterations from options["state"], falling back to the legacy
|
|
121
|
+
# top-level key for turns persisted before the state split.
|
|
122
|
+
def self.iterations_for(record)
|
|
123
|
+
options = record["options"] || {}
|
|
124
|
+
(options.dig("state", "iterations") || options["iterations"]).to_i
|
|
117
125
|
end
|
|
118
126
|
|
|
119
127
|
def usage
|
|
@@ -168,23 +176,13 @@ module TurnKit
|
|
|
168
176
|
end
|
|
169
177
|
|
|
170
178
|
def paint(prompt, model:, provider: nil, size: nil, assume_model_exists: nil, input_images: nil, mask: nil, params: {}, metadata: {}, client: nil)
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
when "pending"
|
|
174
|
-
claimed = store.claim_turn(id, from: "pending", to: "running", started_at: Clock.now, heartbeat_at: Clock.now)
|
|
175
|
-
raise Error, "turn is already running" unless claimed
|
|
176
|
-
|
|
179
|
+
claimed = claim_standalone!("paint")
|
|
180
|
+
if claimed
|
|
177
181
|
@record = claimed
|
|
178
182
|
@started_at = @record["started_at"]
|
|
179
|
-
@budget = Budget.resume(store: store, root_turn_id: root_turn_id, limits: budget_limits)
|
|
180
|
-
claimed_standalone = true
|
|
181
183
|
emit("turn.started", status: status, model: model)
|
|
182
|
-
|
|
183
|
-
# Image tools call this while their parent turn is running.
|
|
184
|
-
else
|
|
185
|
-
raise Error, "cannot paint for #{status} turn"
|
|
184
|
+
@budget = Budget.resume(store: store, root_turn_id: root_turn_id, limits: agent.budget_limits)
|
|
186
185
|
end
|
|
187
|
-
|
|
188
186
|
image_client = client || agent.effective_client
|
|
189
187
|
request = {
|
|
190
188
|
prompt: prompt,
|
|
@@ -201,43 +199,28 @@ module TurnKit
|
|
|
201
199
|
image_client.validate!(model: model)
|
|
202
200
|
emit("image.requested", request.except(:input_images, :mask))
|
|
203
201
|
result = call_image_client(image_client, request)
|
|
204
|
-
result_cost =
|
|
205
|
-
add_usage!(result.usage, cost: result_cost)
|
|
206
|
-
budget.add_cost!(result_cost.total)
|
|
202
|
+
result_cost = apply_result_cost(result, model: model)
|
|
207
203
|
image = result.images.first
|
|
208
204
|
raise Error, "image client returned no image" unless image
|
|
209
205
|
raise Error, "image client returned image without url or data" if image.url.to_s.empty? && image.data.to_s.empty?
|
|
210
206
|
|
|
211
207
|
persist_image_message(image)
|
|
212
208
|
emit("image.completed", image: image.to_h, model: image.model || model, provider: image.provider || provider&.to_s, mime_type: image.mime_type, usage: result.usage.to_h, cost: result_cost.to_h, metadata: metadata || {})
|
|
213
|
-
complete_with_output(image.url.to_s, output_data: { "type" => "image", "images" => [ image.to_h ] }, audit: check_policy(image.url.to_s, output_data: { "type" => "image", "images" => [ image.to_h ] })) if
|
|
209
|
+
complete_with_output(image.url.to_s, output_data: { "type" => "image", "images" => [ image.to_h ] }, audit: check_policy(image.url.to_s, output_data: { "type" => "image", "images" => [ image.to_h ] })) if claimed
|
|
214
210
|
image
|
|
215
211
|
rescue StandardError => error
|
|
216
|
-
if
|
|
217
|
-
update!(status: "failed", error: { "class" => error.class.name, "message" => error.message }, completed_at: Clock.now)
|
|
218
|
-
emit("turn.failed", error: { "class" => error.class.name, "message" => error.message })
|
|
219
|
-
end
|
|
212
|
+
fail_standalone!(error) if claimed
|
|
220
213
|
raise
|
|
221
214
|
end
|
|
222
215
|
|
|
223
216
|
def view_media(media, objective:, model:, provider: nil, output_schema: nil, params: {}, metadata: {}, client: nil)
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
when "pending"
|
|
227
|
-
claimed = store.claim_turn(id, from: "pending", to: "running", started_at: Clock.now, heartbeat_at: Clock.now)
|
|
228
|
-
raise Error, "turn is already running" unless claimed
|
|
229
|
-
|
|
217
|
+
claimed = claim_standalone!("view media")
|
|
218
|
+
if claimed
|
|
230
219
|
@record = claimed
|
|
231
220
|
@started_at = @record["started_at"]
|
|
232
|
-
@budget = Budget.resume(store: store, root_turn_id: root_turn_id, limits: budget_limits)
|
|
233
|
-
claimed_standalone = true
|
|
234
221
|
emit("turn.started", status: status, model: model)
|
|
235
|
-
|
|
236
|
-
# Media tools call this while their parent turn is running.
|
|
237
|
-
else
|
|
238
|
-
raise Error, "cannot view media for #{status} turn"
|
|
222
|
+
@budget = Budget.resume(store: store, root_turn_id: root_turn_id, limits: agent.budget_limits)
|
|
239
223
|
end
|
|
240
|
-
|
|
241
224
|
media_input = MediaInput.wrap(media)
|
|
242
225
|
media_client = client || agent.effective_client
|
|
243
226
|
request = {
|
|
@@ -253,42 +236,38 @@ module TurnKit
|
|
|
253
236
|
media_client.validate!(model: model)
|
|
254
237
|
emit("media.requested", request.except(:media).merge(media: media_input.to_h))
|
|
255
238
|
result = call_media_client(media_client, request)
|
|
256
|
-
result_cost =
|
|
257
|
-
add_usage!(result.usage, cost: result_cost)
|
|
258
|
-
budget.add_cost!(result_cost.total)
|
|
239
|
+
result_cost = apply_result_cost(result, model: model)
|
|
259
240
|
analysis = result.media_analyses.first
|
|
260
241
|
raise Error, "media client returned no media analysis" unless analysis
|
|
261
242
|
|
|
262
243
|
persist_media_analysis_message(analysis)
|
|
263
244
|
output_data = { "type" => "media_analysis", "media_analyses" => [ analysis.to_h ] }
|
|
264
245
|
emit("media.completed", analysis: analysis.to_h, model: analysis.model || model, provider: analysis.provider || provider&.to_s, media: media_input.to_h, usage: result.usage.to_h, cost: result_cost.to_h, metadata: metadata || {})
|
|
265
|
-
complete_with_output(analysis.text, output_data: output_data, audit: check_policy(analysis.text, output_data: output_data)) if
|
|
246
|
+
complete_with_output(analysis.text, output_data: output_data, audit: check_policy(analysis.text, output_data: output_data)) if claimed
|
|
266
247
|
analysis
|
|
267
248
|
rescue StandardError => error
|
|
268
|
-
emit("media.failed", error: { "class" => error.class.name, "message" => error.message }, metadata: metadata || {}) if status == "running" ||
|
|
269
|
-
if
|
|
270
|
-
update!(status: "failed", error: { "class" => error.class.name, "message" => error.message }, completed_at: Clock.now)
|
|
271
|
-
emit("turn.failed", error: { "class" => error.class.name, "message" => error.message })
|
|
272
|
-
end
|
|
249
|
+
emit("media.failed", error: { "class" => error.class.name, "message" => error.message }, metadata: metadata || {}) if status == "running" || claimed
|
|
250
|
+
fail_standalone!(error) if claimed
|
|
273
251
|
raise
|
|
274
252
|
end
|
|
275
253
|
|
|
276
254
|
private
|
|
277
255
|
def model_request
|
|
278
256
|
prompt = SystemPrompt.new(agent: agent, turn: self, conversation: conversation, mode: prompt_mode || agent.effective_prompt_mode(turn: self))
|
|
279
|
-
instructions = case agent.system_prompt
|
|
257
|
+
instructions, dynamic_instructions = case agent.system_prompt
|
|
280
258
|
when nil
|
|
281
|
-
prompt.
|
|
259
|
+
[ prompt.stable, prompt.dynamic ]
|
|
282
260
|
when String
|
|
283
|
-
agent.system_prompt
|
|
261
|
+
[ agent.system_prompt, nil ]
|
|
284
262
|
else
|
|
285
|
-
agent.system_prompt.call(prompt).to_s
|
|
263
|
+
[ agent.system_prompt.call(prompt).to_s, nil ]
|
|
286
264
|
end
|
|
287
265
|
ModelRequest.new(
|
|
288
266
|
model: model,
|
|
289
267
|
messages: llm_messages,
|
|
290
268
|
tools: agent.effective_tools,
|
|
291
269
|
instructions: instructions,
|
|
270
|
+
dynamic_instructions: dynamic_instructions,
|
|
292
271
|
thinking: thinking,
|
|
293
272
|
output_schema: output_schema,
|
|
294
273
|
metadata: { turn_id: id, conversation_id: conversation.id },
|
|
@@ -296,48 +275,27 @@ module TurnKit
|
|
|
296
275
|
)
|
|
297
276
|
end
|
|
298
277
|
|
|
278
|
+
# Clients implement the TurnKit::Client keyword contract. See client.rb.
|
|
299
279
|
def call_client(request, client: agent.effective_client)
|
|
300
|
-
|
|
280
|
+
client.chat(
|
|
301
281
|
model: request.model,
|
|
302
282
|
messages: request.messages,
|
|
303
283
|
tools: request.tools,
|
|
304
284
|
instructions: request.instructions,
|
|
285
|
+
dynamic_instructions: request.dynamic_instructions,
|
|
305
286
|
thinking: request.thinking,
|
|
306
287
|
output_schema: request.output_schema,
|
|
307
288
|
metadata: request.metadata,
|
|
308
289
|
on_event: ->(event) { emit_event(event) }
|
|
309
|
-
|
|
310
|
-
accepted = chat_keyword_names(client)
|
|
311
|
-
kwargs = kwargs.slice(*accepted) unless accepted.include?(:keyrest)
|
|
312
|
-
client.chat(**kwargs)
|
|
313
|
-
end
|
|
314
|
-
|
|
315
|
-
def chat_keyword_names(client)
|
|
316
|
-
client.method(:chat).parameters.filter_map do |kind, name|
|
|
317
|
-
return [ :keyrest ] if kind == :keyrest
|
|
318
|
-
|
|
319
|
-
name if %i[key keyreq].include?(kind)
|
|
320
|
-
end
|
|
290
|
+
)
|
|
321
291
|
end
|
|
322
292
|
|
|
323
293
|
def call_image_client(client, request)
|
|
324
|
-
|
|
325
|
-
accepted = client.method(:paint).parameters.filter_map do |kind, name|
|
|
326
|
-
return client.paint(**kwargs) if kind == :keyrest
|
|
327
|
-
|
|
328
|
-
name if %i[key keyreq].include?(kind)
|
|
329
|
-
end
|
|
330
|
-
client.paint(**kwargs.slice(*accepted))
|
|
294
|
+
client.paint(**request, on_event: ->(event) { emit_event(event) })
|
|
331
295
|
end
|
|
332
296
|
|
|
333
297
|
def call_media_client(client, request)
|
|
334
|
-
|
|
335
|
-
accepted = client.method(:view_media).parameters.filter_map do |kind, name|
|
|
336
|
-
return client.view_media(**kwargs) if kind == :keyrest
|
|
337
|
-
|
|
338
|
-
name if %i[key keyreq].include?(kind)
|
|
339
|
-
end
|
|
340
|
-
client.view_media(**kwargs.slice(*accepted))
|
|
298
|
+
client.view_media(**request, on_event: ->(event) { emit_event(event) })
|
|
341
299
|
end
|
|
342
300
|
|
|
343
301
|
def llm_messages
|
|
@@ -453,8 +411,7 @@ module TurnKit
|
|
|
453
411
|
end
|
|
454
412
|
|
|
455
413
|
def persist_policy_audit(audit)
|
|
456
|
-
|
|
457
|
-
update!(options: options)
|
|
414
|
+
update_state!("policy_audit" => audit.to_h)
|
|
458
415
|
emit("output_policy.completed", clean: audit.clean?, violation_count: audit.violations.length)
|
|
459
416
|
end
|
|
460
417
|
|
|
@@ -506,23 +463,50 @@ module TurnKit
|
|
|
506
463
|
|
|
507
464
|
def count_iteration!
|
|
508
465
|
budget.count_iteration!
|
|
509
|
-
|
|
510
|
-
|
|
466
|
+
update_state!("iterations" => Turn.iterations_for(@record) + 1)
|
|
467
|
+
end
|
|
468
|
+
|
|
469
|
+
# Runtime state lives under options["state"]; the rest of options is
|
|
470
|
+
# write-once turn configuration. Reads fall back to the legacy top-level
|
|
471
|
+
# keys for turns persisted before the split.
|
|
472
|
+
def update_state!(changes)
|
|
473
|
+
options = @record["options"] || {}
|
|
474
|
+
update!(options: options.merge("state" => (options["state"] || {}).merge(changes)))
|
|
511
475
|
end
|
|
512
476
|
|
|
513
477
|
def heartbeat!
|
|
514
478
|
update!(heartbeat_at: Clock.now)
|
|
515
479
|
end
|
|
516
480
|
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
481
|
+
# Claims a pending turn for a standalone media call. Returns the claimed
|
|
482
|
+
# record when this call owns turn completion, nil when running inside a parent
|
|
483
|
+
# turn (media tools call paint/view_media while their turn is running).
|
|
484
|
+
# Callers perform any post-claim setup after assignment, so later errors
|
|
485
|
+
# still fail the claimed turn.
|
|
486
|
+
def claim_standalone!(action)
|
|
487
|
+
case status
|
|
488
|
+
when "pending"
|
|
489
|
+
claimed = store.claim_turn(id, from: "pending", to: "running", started_at: Clock.now, heartbeat_at: Clock.now)
|
|
490
|
+
raise Error, "turn is already running" unless claimed
|
|
491
|
+
|
|
492
|
+
claimed
|
|
493
|
+
when "running"
|
|
494
|
+
nil
|
|
495
|
+
else
|
|
496
|
+
raise Error, "cannot #{action} for #{status} turn"
|
|
497
|
+
end
|
|
498
|
+
end
|
|
499
|
+
|
|
500
|
+
def fail_standalone!(error)
|
|
501
|
+
update!(status: "failed", error: { "class" => error.class.name, "message" => error.message }, completed_at: Clock.now)
|
|
502
|
+
emit("turn.failed", error: { "class" => error.class.name, "message" => error.message })
|
|
503
|
+
end
|
|
504
|
+
|
|
505
|
+
def apply_result_cost(result, model:)
|
|
506
|
+
cost = Cost.from_usage(result.usage, model: result.model || model)
|
|
507
|
+
add_usage!(result.usage, cost: cost)
|
|
508
|
+
budget.add_cost!(cost.total)
|
|
509
|
+
cost
|
|
526
510
|
end
|
|
527
511
|
|
|
528
512
|
def aggregate_cost(current, cost)
|
data/lib/turnkit/version.rb
CHANGED
|
@@ -11,8 +11,8 @@ module TurnKit
|
|
|
11
11
|
end
|
|
12
12
|
end
|
|
13
13
|
|
|
14
|
-
def call(
|
|
15
|
-
|
|
14
|
+
def call(context:, **arguments)
|
|
15
|
+
context.turn.view_media(
|
|
16
16
|
media(**arguments),
|
|
17
17
|
objective: objective(**arguments),
|
|
18
18
|
model: self.class.model,
|
data/lib/turnkit.rb
CHANGED
|
@@ -17,7 +17,6 @@ require_relative "turnkit/event"
|
|
|
17
17
|
require_relative "turnkit/model_request"
|
|
18
18
|
require_relative "turnkit/schema_check"
|
|
19
19
|
require_relative "turnkit/agent"
|
|
20
|
-
require_relative "turnkit/workflow"
|
|
21
20
|
require_relative "turnkit/client"
|
|
22
21
|
require_relative "turnkit/conversation"
|
|
23
22
|
require_relative "turnkit/message"
|
|
@@ -31,10 +30,10 @@ require_relative "turnkit/output_audit"
|
|
|
31
30
|
require_relative "turnkit/output_policy"
|
|
32
31
|
require_relative "turnkit/prompt_data"
|
|
33
32
|
require_relative "turnkit/prompt_context"
|
|
34
|
-
require_relative "turnkit/prompt_contribution"
|
|
35
33
|
require_relative "turnkit/system_prompt"
|
|
36
34
|
require_relative "turnkit/store"
|
|
37
35
|
require_relative "turnkit/memory_store"
|
|
36
|
+
require_relative "turnkit/reconciliation"
|
|
38
37
|
require_relative "turnkit/compaction"
|
|
39
38
|
require_relative "turnkit/tool"
|
|
40
39
|
require_relative "turnkit/image_tool"
|
|
@@ -50,9 +49,7 @@ require_relative "turnkit/usage"
|
|
|
50
49
|
require_relative "turnkit/run"
|
|
51
50
|
require_relative "turnkit/adapters/codex"
|
|
52
51
|
require_relative "turnkit/adapters/ruby_llm"
|
|
53
|
-
require_relative "turnkit/
|
|
54
|
-
|
|
55
|
-
require_relative "turnkit/rails/railtie" if defined?(Rails)
|
|
52
|
+
require_relative "turnkit/active_record_store"
|
|
56
53
|
|
|
57
54
|
module TurnKit
|
|
58
55
|
class << self
|
|
@@ -65,10 +62,7 @@ module TurnKit
|
|
|
65
62
|
attr_accessor :cost_rates, :cost_calculator
|
|
66
63
|
attr_accessor :prompt_sections, :prompt_behavior, :available_skills
|
|
67
64
|
attr_accessor :prompt_data_max_chars, :context_contributors
|
|
68
|
-
attr_accessor :system_prompt_contributors, :model_prompt_contributors
|
|
69
65
|
attr_accessor :on_event
|
|
70
|
-
attr_accessor :conversation_record_class, :turn_record_class
|
|
71
|
-
attr_accessor :message_record_class, :tool_execution_record_class
|
|
72
66
|
end
|
|
73
67
|
|
|
74
68
|
self.default_model = "claude-sonnet-4-5"
|
|
@@ -87,8 +81,6 @@ module TurnKit
|
|
|
87
81
|
self.prompt_data_max_chars = 20_000
|
|
88
82
|
self.available_skills = []
|
|
89
83
|
self.context_contributors = []
|
|
90
|
-
self.system_prompt_contributors = []
|
|
91
|
-
self.model_prompt_contributors = {}
|
|
92
84
|
self.on_event = nil
|
|
93
85
|
self.output_policy_model = nil
|
|
94
86
|
self.output_policy_thinking = { effort: :low }
|
|
@@ -97,18 +89,8 @@ module TurnKit
|
|
|
97
89
|
yield self
|
|
98
90
|
end
|
|
99
91
|
|
|
100
|
-
def self.model
|
|
101
|
-
default_model
|
|
102
|
-
end
|
|
103
|
-
|
|
104
|
-
def self.model=(value)
|
|
105
|
-
self.default_model = value
|
|
106
|
-
end
|
|
107
|
-
|
|
108
92
|
def self.reconcile_stale!(before: Clock.now - (timeout || 300))
|
|
109
|
-
|
|
110
|
-
store.update_turn(turn.fetch("id"), "status" => "stale", "completed_at" => Clock.now)
|
|
111
|
-
end
|
|
93
|
+
Reconciliation.reconcile!(before: before)
|
|
112
94
|
end
|
|
113
95
|
|
|
114
96
|
def self.check_output_policy(output, constraints: [], context: {})
|
metadata
CHANGED
|
@@ -1,31 +1,17 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: turnkit
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Sam Couch
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
12
|
-
dependencies:
|
|
13
|
-
- !ruby/object:Gem::Dependency
|
|
14
|
-
name: ruby_llm
|
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
|
16
|
-
requirements:
|
|
17
|
-
- - "~>"
|
|
18
|
-
- !ruby/object:Gem::Version
|
|
19
|
-
version: '1.14'
|
|
20
|
-
type: :runtime
|
|
21
|
-
prerelease: false
|
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
-
requirements:
|
|
24
|
-
- - "~>"
|
|
25
|
-
- !ruby/object:Gem::Version
|
|
26
|
-
version: '1.14'
|
|
11
|
+
date: 2026-07-22 00:00:00.000000000 Z
|
|
12
|
+
dependencies: []
|
|
27
13
|
description: TurnKit is a Ruby/Rails agent runtime for durable AI conversations, application
|
|
28
|
-
runs,
|
|
14
|
+
runs, orchestrator agents, tool calling, skills, sub-agents, context compaction,
|
|
29
15
|
and persistence.
|
|
30
16
|
email:
|
|
31
17
|
- sam@samcouch.com
|
|
@@ -37,7 +23,16 @@ files:
|
|
|
37
23
|
- LICENSE.md
|
|
38
24
|
- README.md
|
|
39
25
|
- UPGRADE.md
|
|
26
|
+
- UPGRADE_TO_0_4_2.md
|
|
27
|
+
- lib/generators/turnkit/install/templates/conversation.rb
|
|
28
|
+
- lib/generators/turnkit/install/templates/create_turnkit_tables.rb
|
|
29
|
+
- lib/generators/turnkit/install/templates/initializer.rb
|
|
30
|
+
- lib/generators/turnkit/install/templates/message.rb
|
|
31
|
+
- lib/generators/turnkit/install/templates/tool_execution.rb
|
|
32
|
+
- lib/generators/turnkit/install/templates/turn.rb
|
|
33
|
+
- lib/generators/turnkit/install_generator.rb
|
|
40
34
|
- lib/turnkit.rb
|
|
35
|
+
- lib/turnkit/active_record_store.rb
|
|
41
36
|
- lib/turnkit/adapters/codex.rb
|
|
42
37
|
- lib/turnkit/adapters/ruby_llm.rb
|
|
43
38
|
- lib/turnkit/agent.rb
|
|
@@ -49,13 +44,6 @@ files:
|
|
|
49
44
|
- lib/turnkit/cost.rb
|
|
50
45
|
- lib/turnkit/error.rb
|
|
51
46
|
- lib/turnkit/event.rb
|
|
52
|
-
- lib/turnkit/generators/turnkit/install/templates/conversation.rb
|
|
53
|
-
- lib/turnkit/generators/turnkit/install/templates/create_turnkit_tables.rb
|
|
54
|
-
- lib/turnkit/generators/turnkit/install/templates/initializer.rb
|
|
55
|
-
- lib/turnkit/generators/turnkit/install/templates/message.rb
|
|
56
|
-
- lib/turnkit/generators/turnkit/install/templates/tool_execution.rb
|
|
57
|
-
- lib/turnkit/generators/turnkit/install/templates/turn.rb
|
|
58
|
-
- lib/turnkit/generators/turnkit/install_generator.rb
|
|
59
47
|
- lib/turnkit/id.rb
|
|
60
48
|
- lib/turnkit/image_result.rb
|
|
61
49
|
- lib/turnkit/image_tool.rb
|
|
@@ -69,16 +57,14 @@ files:
|
|
|
69
57
|
- lib/turnkit/output_audit.rb
|
|
70
58
|
- lib/turnkit/output_policy.rb
|
|
71
59
|
- lib/turnkit/prompt_context.rb
|
|
72
|
-
- lib/turnkit/prompt_contribution.rb
|
|
73
60
|
- lib/turnkit/prompt_data.rb
|
|
74
|
-
- lib/turnkit/
|
|
61
|
+
- lib/turnkit/reconciliation.rb
|
|
75
62
|
- lib/turnkit/record.rb
|
|
76
63
|
- lib/turnkit/result.rb
|
|
77
64
|
- lib/turnkit/run.rb
|
|
78
65
|
- lib/turnkit/schema_check.rb
|
|
79
66
|
- lib/turnkit/skill.rb
|
|
80
67
|
- lib/turnkit/store.rb
|
|
81
|
-
- lib/turnkit/stores/active_record_store.rb
|
|
82
68
|
- lib/turnkit/sub_agent_tool.rb
|
|
83
69
|
- lib/turnkit/system_prompt.rb
|
|
84
70
|
- lib/turnkit/tool.rb
|
|
@@ -89,7 +75,6 @@ files:
|
|
|
89
75
|
- lib/turnkit/usage.rb
|
|
90
76
|
- lib/turnkit/version.rb
|
|
91
77
|
- lib/turnkit/view_media_tool.rb
|
|
92
|
-
- lib/turnkit/workflow.rb
|
|
93
78
|
homepage: https://github.com/samuelcouch/turnkit
|
|
94
79
|
licenses:
|
|
95
80
|
- MIT
|
|
@@ -117,5 +102,6 @@ requirements: []
|
|
|
117
102
|
rubygems_version: 3.5.22
|
|
118
103
|
signing_key:
|
|
119
104
|
specification_version: 4
|
|
120
|
-
summary: Ruby/Rails agent runtime for durable AI conversations, runs, and
|
|
105
|
+
summary: Ruby/Rails agent runtime for durable AI conversations, runs, and orchestrator
|
|
106
|
+
agents.
|
|
121
107
|
test_files: []
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module TurnKit
|
|
4
|
-
class PromptContribution
|
|
5
|
-
attr_accessor :stable_prefix, :dynamic_suffix, :section_overrides
|
|
6
|
-
|
|
7
|
-
def initialize(stable_prefix: nil, dynamic_suffix: nil, section_overrides: nil)
|
|
8
|
-
@stable_prefix = stable_prefix.to_s
|
|
9
|
-
@dynamic_suffix = dynamic_suffix.to_s
|
|
10
|
-
@section_overrides = (section_overrides || {}).transform_keys(&:to_sym)
|
|
11
|
-
end
|
|
12
|
-
end
|
|
13
|
-
end
|
data/lib/turnkit/workflow.rb
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative "agent"
|
|
4
|
-
|
|
5
|
-
module TurnKit
|
|
6
|
-
class Workflow
|
|
7
|
-
ORCHESTRATOR_PREAMBLE = <<~TEXT.strip
|
|
8
|
-
You are an autonomous task orchestrator. Navigate from the application
|
|
9
|
-
request to a final output without asking the user follow-up questions.
|
|
10
|
-
|
|
11
|
-
Use the available tools to gather context, inspect sources, take actions,
|
|
12
|
-
persist outputs, and verify work. Use loaded skills as reusable workflow
|
|
13
|
-
patterns. Iterate when work needs missing context, critique, revision, or
|
|
14
|
-
verification.
|
|
15
|
-
|
|
16
|
-
When multiple independent items need the same kind of fetch or read, and
|
|
17
|
-
an available batch tool can handle them in one call, prefer the batch tool
|
|
18
|
-
over repeated one-item tool calls.
|
|
19
|
-
|
|
20
|
-
Stop when the task is complete, when the available context and tools are
|
|
21
|
-
sufficient for the best possible answer, or when further iteration would
|
|
22
|
-
not materially improve the result. Respect runtime, cost, and iteration
|
|
23
|
-
limits.
|
|
24
|
-
TEXT
|
|
25
|
-
|
|
26
|
-
DEFAULT_INSTRUCTIONS = ORCHESTRATOR_PREAMBLE
|
|
27
|
-
|
|
28
|
-
attr_reader :name, :options
|
|
29
|
-
|
|
30
|
-
def initialize(name: "workflow", instructions: nil, preamble: true, **options)
|
|
31
|
-
@name = name.to_s
|
|
32
|
-
raise ArgumentError, "name is required" if @name.empty?
|
|
33
|
-
|
|
34
|
-
@options = options.merge(
|
|
35
|
-
name: @name,
|
|
36
|
-
prompt_mode: options.fetch(:prompt_mode, :task),
|
|
37
|
-
instructions: compose_instructions(instructions, preamble: preamble)
|
|
38
|
-
).freeze
|
|
39
|
-
@agent = Agent.new(**@options)
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
def run(prompt = nil, task: nil, input: nil, async: false, subject: nil, metadata: {}, **overrides)
|
|
43
|
-
agent(**overrides).run(task || prompt, input: input, async: async, subject: subject, metadata: metadata)
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
def agent(**overrides)
|
|
47
|
-
overrides.empty? ? @agent : Agent.new(**@options.merge(overrides.compact))
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
private
|
|
51
|
-
def compose_instructions(instructions, preamble:)
|
|
52
|
-
parts = []
|
|
53
|
-
parts << ORCHESTRATOR_PREAMBLE if preamble
|
|
54
|
-
parts << instructions.to_s.strip unless instructions.to_s.strip.empty?
|
|
55
|
-
parts.join("\n\n")
|
|
56
|
-
end
|
|
57
|
-
end
|
|
58
|
-
end
|
|
File without changes
|
/data/lib/{turnkit/generators → generators}/turnkit/install/templates/create_turnkit_tables.rb
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|