Class: TurnKit::Agent

Inherits:
Object
  • Object
show all
Defined in:
lib/turnkit/agent.rb

Constant Summary collapse

ORCHESTRATOR_PREAMBLE =
<<~TEXT.strip
  You are an autonomous task orchestrator. Navigate from the application
  request to a final output without asking the user follow-up questions.

  Use the available tools to gather context, inspect sources, take actions,
  persist outputs, and verify work. Use loaded skills as reusable workflow
  patterns. Iterate when work needs missing context, critique, revision, or
  verification.

  When multiple independent items need the same kind of fetch or read, and
  an available batch tool can handle them in one call, prefer the batch tool
  over repeated one-item tool calls.

  Stop when the task is complete, when the available context and tools are
  sufficient for the best possible answer, or when further iteration would
  not materially improve the result. Respect runtime, cost, and iteration
  limits.
TEXT

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name:, description: "", model: nil, instructions: "", orchestrator: false, tools: [], skills: [], available_skills: [], sub_agents: [], system_prompt: nil, prompt_sections: nil, prompt_mode: nil, client: nil, store: nil, max_iterations: nil, timeout: nil, max_spend: nil, max_depth: nil, max_tool_executions: nil, max_tool_executions_by_name: nil, thinking: nil, compaction: nil, output_schema: nil, input_schema: nil, output_policy: nil, output_policy_mode: nil, output_policy_model: nil, output_policy_thinking: nil, output_retries: 0, on_event: nil) ⇒ Agent

Returns a new instance of Agent.

Raises:

  • (ArgumentError)


29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/turnkit/agent.rb', line 29

def initialize(name:, description: "", model: nil, instructions: "", orchestrator: false, tools: [], skills: [], available_skills: [], sub_agents: [],
  system_prompt: nil, prompt_sections: nil, prompt_mode: nil, client: nil, store: nil,
  max_iterations: nil, timeout: nil, max_spend: nil, max_depth: nil, max_tool_executions: nil, max_tool_executions_by_name: nil, thinking: nil, compaction: nil,
  output_schema: nil, input_schema: nil, output_policy: nil, output_policy_mode: nil, output_policy_model: nil, output_policy_thinking: nil, output_retries: 0, on_event: nil)
  @name = name.to_s
  @description = description.to_s
  @model = model
  @orchestrator = orchestrator ? true : false
  @instructions = compose_instructions(instructions)
  @tools = Array(tools)
  @skills = Array(skills)
  @available_skills = Array(available_skills)
  @sub_agents = Array(sub_agents)
  @system_prompt = system_prompt
  @prompt_sections = prompt_sections
  @prompt_mode = prompt_mode&.to_sym || (:task if @orchestrator)
  @client = client
  @store = store
  @max_iterations = max_iterations
  @timeout = timeout
  @max_spend = max_spend
  @max_depth = max_depth
  @max_tool_executions = max_tool_executions
  @max_tool_executions_by_name = max_tool_executions_by_name
  @thinking = self.class.normalize_thinking(thinking)
  @compaction = compaction
  @output_schema = output_schema
  @input_schema = input_schema
  @output_policy_model = output_policy_model
  @output_policy = normalize_output_policy(output_policy, model: output_policy_model, thinking: output_policy_thinking)
  @output_policy_mode = normalize_output_policy_mode(output_policy_mode)
  @output_retries = Integer(output_retries || 0)
  @on_event = on_event
  raise ArgumentError, "name is required" if @name.empty?
  validate_tools!
end

Instance Attribute Details

#available_skillsObject (readonly)

Returns the value of attribute available_skills.



24
25
26
# File 'lib/turnkit/agent.rb', line 24

def available_skills
  @available_skills
end

#clientObject (readonly)

Returns the value of attribute client.



25
26
27
# File 'lib/turnkit/agent.rb', line 25

def client
  @client
end

#compactionObject (readonly)

Returns the value of attribute compaction.



26
27
28
# File 'lib/turnkit/agent.rb', line 26

def compaction
  @compaction
end

#descriptionObject (readonly)

Returns the value of attribute description.



24
25
26
# File 'lib/turnkit/agent.rb', line 24

def description
  @description
end

#input_schemaObject (readonly)

Returns the value of attribute input_schema.



26
27
28
# File 'lib/turnkit/agent.rb', line 26

def input_schema
  @input_schema
end

#instructionsObject (readonly)

Returns the value of attribute instructions.



24
25
26
# File 'lib/turnkit/agent.rb', line 24

def instructions
  @instructions
end

#max_depthObject (readonly)

Returns the value of attribute max_depth.



25
26
27
# File 'lib/turnkit/agent.rb', line 25

def max_depth
  @max_depth
end

#max_iterationsObject (readonly)

Returns the value of attribute max_iterations.



25
26
27
# File 'lib/turnkit/agent.rb', line 25

def max_iterations
  @max_iterations
end

#max_spendObject (readonly)

Returns the value of attribute max_spend.



25
26
27
# File 'lib/turnkit/agent.rb', line 25

def max_spend
  @max_spend
end

#max_tool_executionsObject (readonly)

Returns the value of attribute max_tool_executions.



25
26
27
# File 'lib/turnkit/agent.rb', line 25

def max_tool_executions
  @max_tool_executions
end

#max_tool_executions_by_nameObject (readonly)

Returns the value of attribute max_tool_executions_by_name.



25
26
27
# File 'lib/turnkit/agent.rb', line 25

def max_tool_executions_by_name
  @max_tool_executions_by_name
end

#modelObject (readonly)

Returns the value of attribute model.



24
25
26
# File 'lib/turnkit/agent.rb', line 24

def model
  @model
end

#nameObject (readonly)

Returns the value of attribute name.



24
25
26
# File 'lib/turnkit/agent.rb', line 24

def name
  @name
end

#on_eventObject (readonly)

Returns the value of attribute on_event.



26
27
28
# File 'lib/turnkit/agent.rb', line 26

def on_event
  @on_event
end

#output_policyObject (readonly)

Returns the value of attribute output_policy.



27
28
29
# File 'lib/turnkit/agent.rb', line 27

def output_policy
  @output_policy
end

#output_policy_modeObject (readonly)

Returns the value of attribute output_policy_mode.



27
28
29
# File 'lib/turnkit/agent.rb', line 27

def output_policy_mode
  @output_policy_mode
end

#output_policy_modelObject (readonly)

Returns the value of attribute output_policy_model.



27
28
29
# File 'lib/turnkit/agent.rb', line 27

def output_policy_model
  @output_policy_model
end

#output_retriesObject (readonly)

Returns the value of attribute output_retries.



27
28
29
# File 'lib/turnkit/agent.rb', line 27

def output_retries
  @output_retries
end

#output_schemaObject (readonly)

Returns the value of attribute output_schema.



26
27
28
# File 'lib/turnkit/agent.rb', line 26

def output_schema
  @output_schema
end

#prompt_modeObject (readonly)

Returns the value of attribute prompt_mode.



26
27
28
# File 'lib/turnkit/agent.rb', line 26

def prompt_mode
  @prompt_mode
end

#prompt_sectionsObject (readonly)

Returns the value of attribute prompt_sections.



26
27
28
# File 'lib/turnkit/agent.rb', line 26

def prompt_sections
  @prompt_sections
end

#skillsObject (readonly)

Returns the value of attribute skills.



24
25
26
# File 'lib/turnkit/agent.rb', line 24

def skills
  @skills
end

#storeObject (readonly)

Returns the value of attribute store.



25
26
27
# File 'lib/turnkit/agent.rb', line 25

def store
  @store
end

#sub_agentsObject (readonly)

Returns the value of attribute sub_agents.



24
25
26
# File 'lib/turnkit/agent.rb', line 24

def sub_agents
  @sub_agents
end

#system_promptObject (readonly)

Returns the value of attribute system_prompt.



26
27
28
# File 'lib/turnkit/agent.rb', line 26

def system_prompt
  @system_prompt
end

#thinkingObject (readonly)

Returns the value of attribute thinking.



26
27
28
# File 'lib/turnkit/agent.rb', line 26

def thinking
  @thinking
end

#timeoutObject (readonly)

Returns the value of attribute timeout.



25
26
27
# File 'lib/turnkit/agent.rb', line 25

def timeout
  @timeout
end

#toolsObject (readonly)

Returns the value of attribute tools.



24
25
26
# File 'lib/turnkit/agent.rb', line 24

def tools
  @tools
end

Class Method Details

.normalize_thinking(value) ⇒ Object

Raises:

  • (ArgumentError)


66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/turnkit/agent.rb', line 66

def self.normalize_thinking(value)
  return nil if value.nil?

  attrs = value.respond_to?(:to_h) ? value.to_h : value
  raise ArgumentError, "thinking must be a hash" unless attrs.is_a?(Hash)

  attrs = attrs.transform_keys(&:to_sym)
  unknown = attrs.keys - %i[effort budget]
  raise ArgumentError, "unknown thinking attributes: #{unknown.join(", ")}" if unknown.any?
  raise ArgumentError, "thinking requires :effort or :budget" if attrs[:effort].nil? && attrs[:budget].nil?
  raise ArgumentError, "thinking budget must be an Integer" if attrs[:budget] && !attrs[:budget].is_a?(Integer)

  attrs.slice(:effort, :budget).compact
end

Instance Method Details

#budget_limitsObject



177
178
179
180
181
182
183
184
185
186
# File 'lib/turnkit/agent.rb', line 177

def budget_limits
  {
    max_iterations: max_iterations || TurnKit.max_iterations,
    timeout: timeout || TurnKit.timeout,
    max_depth: max_depth || TurnKit.max_depth,
    max_tool_executions: max_tool_executions || TurnKit.max_tool_executions,
    max_tool_executions_by_name: max_tool_executions_by_name || TurnKit.max_tool_executions_by_name,
    max_spend: max_spend || TurnKit.max_spend
  }
end

#build_budget(root_started_at: Clock.now) ⇒ Object



188
189
190
# File 'lib/turnkit/agent.rb', line 188

def build_budget(root_started_at: Clock.now)
  Budget.new(**budget_limits, root_started_at: root_started_at)
end

#conversation(model: nil, subject: nil, metadata: {}) ⇒ Object



81
82
83
84
85
86
87
88
89
90
# File 'lib/turnkit/agent.rb', line 81

def conversation(model: nil, subject: nil, metadata: {})
  store = effective_store
  record = store.create_conversation(
    "agent_name" => name,
    "model" => model || effective_model,
    "subject" => subject,
    "metadata" => 
  )
  Conversation.new(agent: self, record: record, store: store, model: model || effective_model, subject: subject, metadata: )
end

#costObject



112
113
114
# File 'lib/turnkit/agent.rb', line 112

def cost
  Cost.from_records(effective_store.list_turns(agent_name: name))
end

#effective_available_skillsObject



150
151
152
# File 'lib/turnkit/agent.rb', line 150

def effective_available_skills
  (Array(TurnKit.available_skills) + available_skills).uniq { |skill| skill.key }
end

#effective_clientObject



132
133
134
# File 'lib/turnkit/agent.rb', line 132

def effective_client
  client || TurnKit.client
end

#effective_modelObject



120
121
122
# File 'lib/turnkit/agent.rb', line 120

def effective_model
  model || TurnKit.default_model
end

#effective_on_eventObject



146
147
148
# File 'lib/turnkit/agent.rb', line 146

def effective_on_event
  on_event || TurnKit.on_event
end

#effective_output_policyObject



128
129
130
# File 'lib/turnkit/agent.rb', line 128

def effective_output_policy
  Array(output_policy).compact
end

#effective_prompt_mode(turn: nil) ⇒ Object



158
159
160
161
162
# File 'lib/turnkit/agent.rb', line 158

def effective_prompt_mode(turn: nil)
  return prompt_mode if prompt_mode

  turn&.depth.to_i.positive? ? :minimal : :full
end

#effective_prompt_sectionsObject



154
155
156
# File 'lib/turnkit/agent.rb', line 154

def effective_prompt_sections
  prompt_sections || TurnKit.prompt_sections
end

#effective_storeObject



136
137
138
# File 'lib/turnkit/agent.rb', line 136

def effective_store
  store || TurnKit.store
end

#effective_thinkingObject



124
125
126
# File 'lib/turnkit/agent.rb', line 124

def effective_thinking
  thinking
end

#effective_toolsObject



140
141
142
143
144
# File 'lib/turnkit/agent.rb', line 140

def effective_tools
  configured = tools + sub_agents.map { |agent| SubAgentTool.for(agent) }
  skills = effective_available_skills
  skills.empty? ? configured : configured + [ LoadSkillTool.for(skills) ]
end

#instructions_with_skillsObject



192
193
194
195
196
# File 'lib/turnkit/agent.rb', line 192

def instructions_with_skills
  parts = [ instructions ]
  parts << SystemPrompt.loaded_skills_text(skills)
  parts.reject(&:empty?).join("\n\n")
end

#orchestrator?Boolean

Returns:

  • (Boolean)


92
93
94
# File 'lib/turnkit/agent.rb', line 92

def orchestrator?
  @orchestrator
end

#run(task, input: nil, async: false, subject: nil, metadata: {}, parent_run: nil, root_turn_id: nil, prompt_mode: :task, **options) ⇒ Object

Raises:

  • (ArgumentError)


96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/turnkit/agent.rb', line 96

def run(task, input: nil, async: false, subject: nil, metadata: {}, parent_run: nil, root_turn_id: nil, prompt_mode: :task, **options)
  raise ArgumentError, "task is required" if task.to_s.empty?
  SchemaCheck.validate!(input, input_schema, error_class: InputError, label: "input") if input_schema

  conversation = self.conversation(subject: subject, metadata: )
  message = conversation.say(task_message(task, input), metadata: { "source" => "application", "task" => true })
  turn = conversation.build_turn(
    trigger_message_id: message.id,
    root_turn_id: root_turn_id || parent_run_root_turn_id(parent_run),
    prompt_mode: prompt_mode,
    **options
  )
  run = Run.new(turn)
  async ? run : run.run!
end

#system_prompt_for(turn:, conversation:) ⇒ Object



164
165
166
167
168
169
170
171
172
173
174
175
# File 'lib/turnkit/agent.rb', line 164

def system_prompt_for(turn:, conversation:)
  prompt = SystemPrompt.new(agent: self, turn: turn, conversation: conversation, mode: effective_prompt_mode(turn: turn))

  case system_prompt
  when nil
    prompt.to_s
  when String
    system_prompt
  else
    system_prompt.call(prompt).to_s
  end
end

#usageObject



116
117
118
# File 'lib/turnkit/agent.rb', line 116

def usage
  Usage.from_records(effective_store.list_turns(agent_name: name))
end