turnkit 0.2.9 → 0.3.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.
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TurnKit
4
- VERSION = "0.2.9"
4
+ VERSION = "0.3.0"
5
5
  end
@@ -4,11 +4,7 @@ require_relative "agent"
4
4
 
5
5
  module TurnKit
6
6
  class Workflow
7
- attr_reader :name, :description, :instructions, :tools, :skills, :available_skills
8
- attr_reader :model, :client, :store, :prompt_mode, :thinking, :compaction, :output_schema
9
- attr_reader :max_iterations, :timeout, :cost_limit, :max_depth, :max_tool_executions
10
-
11
- DEFAULT_INSTRUCTIONS = <<~TEXT.strip
7
+ ORCHESTRATOR_PREAMBLE = <<~TEXT.strip
12
8
  You are an autonomous task orchestrator. Navigate from the application
13
9
  request to a final output without asking the user follow-up questions.
14
10
 
@@ -17,87 +13,46 @@ module TurnKit
17
13
  patterns. Iterate when work needs missing context, critique, revision, or
18
14
  verification.
19
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
20
  Stop when the task is complete, when the available context and tools are
21
21
  sufficient for the best possible answer, or when further iteration would
22
22
  not materially improve the result. Respect runtime, cost, and iteration
23
23
  limits.
24
24
  TEXT
25
25
 
26
- def initialize(name: "workflow", description: "", instructions: nil,
27
- tools: [], skills: [], available_skills: [], model: nil, client: nil,
28
- store: nil, prompt_mode: :task, thinking: nil, compaction: nil,
29
- output_schema: nil, max_iterations: nil, timeout: nil, max_spend: nil,
30
- cost_limit: nil, max_depth: nil, max_tool_executions: nil)
26
+ DEFAULT_INSTRUCTIONS = ORCHESTRATOR_PREAMBLE
27
+
28
+ attr_reader :name, :options
31
29
 
30
+ def initialize(name: "workflow", instructions: nil, preamble: true, **options)
32
31
  @name = name.to_s
33
- @description = description.to_s
34
- @instructions = instructions || DEFAULT_INSTRUCTIONS
35
- @tools = Array(tools)
36
- @skills = Array(skills)
37
- @available_skills = Array(available_skills)
38
- @model = model
39
- @client = client
40
- @store = store
41
- @prompt_mode = prompt_mode
42
- @thinking = thinking
43
- @compaction = compaction
44
- @output_schema = output_schema
45
- @max_iterations = max_iterations
46
- @timeout = timeout
47
- @cost_limit = cost_limit || max_spend
48
- @max_depth = max_depth
49
- @max_tool_executions = max_tool_executions
50
32
  raise ArgumentError, "name is required" if @name.empty?
51
- build_agent
52
- end
53
-
54
- def run(prompt = nil, task: nil, input: nil, async: false, subject: nil, metadata: {},
55
- max_spend: nil, cost_limit: nil, **options)
56
-
57
- task = task || prompt
58
- raise ArgumentError, "task is required" if task.to_s.empty?
59
33
 
60
- build_agent(cost_limit: cost_limit || max_spend, **options).run(
61
- task,
62
- input: input,
63
- async: async,
64
- subject: subject,
65
- metadata: metadata
66
- )
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)
67
40
  end
68
41
 
69
- def agent(**options)
70
- build_agent(**options)
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)
71
44
  end
72
45
 
73
- def max_spend
74
- cost_limit
46
+ def agent(**overrides)
47
+ overrides.empty? ? @agent : Agent.new(**@options.merge(overrides.compact))
75
48
  end
76
49
 
77
50
  private
78
- def build_agent(**overrides)
79
- attrs = {
80
- name: name,
81
- description: description,
82
- instructions: instructions,
83
- tools: tools,
84
- skills: skills,
85
- available_skills: available_skills,
86
- model: model,
87
- client: client,
88
- store: store,
89
- prompt_mode: prompt_mode,
90
- thinking: thinking,
91
- compaction: compaction,
92
- output_schema: output_schema,
93
- max_iterations: max_iterations,
94
- timeout: timeout,
95
- cost_limit: cost_limit,
96
- max_depth: max_depth,
97
- max_tool_executions: max_tool_executions
98
- }
99
- attrs.merge!(overrides.compact)
100
- Agent.new(**attrs)
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")
101
56
  end
102
57
  end
103
58
  end
data/lib/turnkit.rb CHANGED
@@ -5,6 +5,7 @@ require "digest"
5
5
  require "securerandom"
6
6
  require "time"
7
7
  require "date"
8
+ require "pathname"
8
9
 
9
10
  require_relative "turnkit/version"
10
11
  require_relative "turnkit/error"
@@ -14,6 +15,7 @@ require_relative "turnkit/cost"
14
15
  require_relative "turnkit/budget"
15
16
  require_relative "turnkit/event"
16
17
  require_relative "turnkit/model_request"
18
+ require_relative "turnkit/schema_check"
17
19
  require_relative "turnkit/agent"
18
20
  require_relative "turnkit/workflow"
19
21
  require_relative "turnkit/client"
@@ -22,6 +24,8 @@ require_relative "turnkit/message"
22
24
  require_relative "turnkit/record"
23
25
  require_relative "turnkit/result"
24
26
  require_relative "turnkit/skill"
27
+ require_relative "turnkit/output_audit"
28
+ require_relative "turnkit/output_policy"
25
29
  require_relative "turnkit/prompt_data"
26
30
  require_relative "turnkit/prompt_context"
27
31
  require_relative "turnkit/prompt_contribution"
@@ -33,6 +37,7 @@ require_relative "turnkit/tool"
33
37
  require_relative "turnkit/tool_call"
34
38
  require_relative "turnkit/tool_execution"
35
39
  require_relative "turnkit/sub_agent_tool"
40
+ require_relative "turnkit/load_skill_tool"
36
41
  require_relative "turnkit/message_projection"
37
42
  require_relative "turnkit/tool_runner"
38
43
  require_relative "turnkit/turn"
@@ -48,8 +53,10 @@ module TurnKit
48
53
  class << self
49
54
  attr_accessor :default_model, :client, :store, :logger
50
55
  attr_accessor :max_iterations, :timeout, :max_depth, :max_tool_executions
51
- attr_accessor :cost_limit, :prompt_cache
56
+ attr_accessor :max_tool_executions_by_name
57
+ attr_accessor :max_spend, :prompt_cache
52
58
  attr_accessor :compaction
59
+ attr_accessor :output_policy_model, :output_policy_thinking
53
60
  attr_accessor :cost_rates, :cost_calculator
54
61
  attr_accessor :prompt_sections, :prompt_behavior, :available_skills
55
62
  attr_accessor :prompt_data_max_chars, :context_contributors
@@ -66,6 +73,8 @@ module TurnKit
66
73
  self.timeout = 300
67
74
  self.max_depth = 3
68
75
  self.max_tool_executions = 100
76
+ self.max_tool_executions_by_name = {}
77
+ self.max_spend = nil
69
78
  self.prompt_cache = :auto
70
79
  self.compaction = true
71
80
  self.cost_rates = {}
@@ -76,6 +85,8 @@ module TurnKit
76
85
  self.system_prompt_contributors = []
77
86
  self.model_prompt_contributors = {}
78
87
  self.on_event = nil
88
+ self.output_policy_model = nil
89
+ self.output_policy_thinking = { effort: :low }
79
90
 
80
91
  def self.configure
81
92
  yield self
@@ -89,17 +100,13 @@ module TurnKit
89
100
  self.default_model = value
90
101
  end
91
102
 
92
- def self.max_spend
93
- cost_limit
94
- end
95
-
96
- def self.max_spend=(value)
97
- self.cost_limit = value
98
- end
99
-
100
103
  def self.reconcile_stale!(before: Clock.now - (timeout || 300))
101
104
  store.find_stale_turns(before: before).each do |turn|
102
105
  store.update_turn(turn.fetch("id"), "status" => "stale", "completed_at" => Clock.now)
103
106
  end
104
107
  end
108
+
109
+ def self.check_output_policy(output, constraints: [], context: {})
110
+ OutputAudit.check(output, constraints: constraints, context: context)
111
+ end
105
112
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: turnkit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.9
4
+ version: 0.3.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-06-08 00:00:00.000000000 Z
11
+ date: 2026-06-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ruby_llm
@@ -57,10 +57,13 @@ files:
57
57
  - lib/turnkit/generators/turnkit/install/templates/turn.rb
58
58
  - lib/turnkit/generators/turnkit/install_generator.rb
59
59
  - lib/turnkit/id.rb
60
+ - lib/turnkit/load_skill_tool.rb
60
61
  - lib/turnkit/memory_store.rb
61
62
  - lib/turnkit/message.rb
62
63
  - lib/turnkit/message_projection.rb
63
64
  - lib/turnkit/model_request.rb
65
+ - lib/turnkit/output_audit.rb
66
+ - lib/turnkit/output_policy.rb
64
67
  - lib/turnkit/prompt_context.rb
65
68
  - lib/turnkit/prompt_contribution.rb
66
69
  - lib/turnkit/prompt_data.rb
@@ -68,6 +71,7 @@ files:
68
71
  - lib/turnkit/record.rb
69
72
  - lib/turnkit/result.rb
70
73
  - lib/turnkit/run.rb
74
+ - lib/turnkit/schema_check.rb
71
75
  - lib/turnkit/skill.rb
72
76
  - lib/turnkit/store.rb
73
77
  - lib/turnkit/stores/active_record_store.rb