turnkit 0.1.0 → 0.2.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 +6 -0
- data/README.md +63 -14
- data/lib/turnkit/agent.rb +29 -5
- data/lib/turnkit/generators/turnkit/install/templates/initializer.rb +5 -0
- data/lib/turnkit/generators/turnkit/install_generator.rb +11 -1
- data/lib/turnkit/skill.rb +4 -0
- data/lib/turnkit/system_prompt.rb +158 -0
- data/lib/turnkit/turn.rb +6 -1
- data/lib/turnkit/version.rb +1 -1
- data/lib/turnkit.rb +5 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4a7dcc3aa1a8456411f915c927657756e2c879919d3d4f931b93674a305baa91
|
|
4
|
+
data.tar.gz: 3ae8bdbc9e66f6c966e6d8cf0b9790a0e432b6f0be93e2b02c71e72b5035c9c0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8ab51bd650b36b7dd6b0ff8e423e64ae53cf98a7c89ce9a789f86cc2bbfa5421019f9860597bfe3a1169cb17d23a58b7d32f7c12cc15a7aaa7b2257a02718bd9
|
|
7
|
+
data.tar.gz: 300c9c80847bcc9022fc2b4339ae12e6bb12953c55f0229e92cb0870cb0431c9939f74af9f68eef1341fada31afeedfd766dc706115f71420bf2eef2616e88b0
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.0 - 2026-06-04
|
|
4
|
+
|
|
5
|
+
- Add configurable system prompt sections and custom system prompt builders.
|
|
6
|
+
- Add globally and per-agent available skills for prompt guidance.
|
|
7
|
+
- Add skill loading from directories.
|
|
8
|
+
|
|
3
9
|
## 0.1.0 - 2026-06-04
|
|
4
10
|
|
|
5
11
|
- Initial release of TurnKit.
|
data/README.md
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
# TurnKit
|
|
2
2
|
|
|
3
3
|
[](https://rubygems.org/gems/turnkit)
|
|
4
|
-
[](https://github.com/samuelcouch/turnkit/actions)
|
|
5
4
|
[](https://www.ruby-lang.org)
|
|
6
5
|
[](LICENSE.md)
|
|
7
6
|
|
|
8
|
-
Ruby AI
|
|
7
|
+
Build durable Ruby AI agents with turns, tools, skills, and Rails persistence.
|
|
9
8
|
|
|
10
9
|
## Installation
|
|
11
10
|
|
|
@@ -23,12 +22,14 @@ bundle install
|
|
|
23
22
|
|
|
24
23
|
## Quick Start
|
|
25
24
|
|
|
26
|
-
Set a provider key
|
|
25
|
+
Set a provider key:
|
|
27
26
|
|
|
28
27
|
```sh
|
|
29
28
|
export ANTHROPIC_API_KEY=...
|
|
30
29
|
```
|
|
31
30
|
|
|
31
|
+
Ask an agent:
|
|
32
|
+
|
|
32
33
|
```ruby
|
|
33
34
|
require "turnkit"
|
|
34
35
|
|
|
@@ -99,6 +100,55 @@ agent = TurnKit::Agent.new(
|
|
|
99
100
|
)
|
|
100
101
|
```
|
|
101
102
|
|
|
103
|
+
List available skills:
|
|
104
|
+
|
|
105
|
+
```ruby
|
|
106
|
+
research = TurnKit::Skill.from_file(
|
|
107
|
+
"skills/research.md",
|
|
108
|
+
description: "Use for source-backed research tasks."
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
agent = TurnKit::Agent.new(
|
|
112
|
+
name: "researcher",
|
|
113
|
+
instructions: "Prefer primary sources.",
|
|
114
|
+
tools: [WebSearch, ReadWebPage],
|
|
115
|
+
available_skills: [research]
|
|
116
|
+
)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Add subject context:
|
|
120
|
+
|
|
121
|
+
```ruby
|
|
122
|
+
article = Article.find(1)
|
|
123
|
+
conversation = agent.conversation(subject: article)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Choose prompt sections:
|
|
127
|
+
|
|
128
|
+
```ruby
|
|
129
|
+
agent = TurnKit::Agent.new(
|
|
130
|
+
name: "writer",
|
|
131
|
+
instructions: "Write plainly.",
|
|
132
|
+
prompt_sections: %i[agent instructions tools environment]
|
|
133
|
+
)
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Build a custom prompt:
|
|
137
|
+
|
|
138
|
+
```ruby
|
|
139
|
+
agent = TurnKit::Agent.new(
|
|
140
|
+
name: "custom",
|
|
141
|
+
instructions: "Answer in JSON.",
|
|
142
|
+
system_prompt: ->(prompt) {
|
|
143
|
+
[
|
|
144
|
+
prompt.agent_section,
|
|
145
|
+
prompt.instructions_section,
|
|
146
|
+
"Return only valid JSON."
|
|
147
|
+
].compact.join("\n\n")
|
|
148
|
+
}
|
|
149
|
+
)
|
|
150
|
+
```
|
|
151
|
+
|
|
102
152
|
Delegate to sub-agents:
|
|
103
153
|
|
|
104
154
|
```ruby
|
|
@@ -126,7 +176,6 @@ bin/rails db:migrate
|
|
|
126
176
|
Configure Rails:
|
|
127
177
|
|
|
128
178
|
```ruby
|
|
129
|
-
# config/initializers/turnkit.rb
|
|
130
179
|
TurnKit.store = TurnKit::ActiveRecordStore.new
|
|
131
180
|
TurnKit.default_model = "claude-sonnet-4-5"
|
|
132
181
|
TurnKit.timeout = 300
|
|
@@ -140,7 +189,7 @@ TurnKit.reconcile_stale!
|
|
|
140
189
|
|
|
141
190
|
## Options
|
|
142
191
|
|
|
143
|
-
Configure defaults
|
|
192
|
+
Configure defaults:
|
|
144
193
|
|
|
145
194
|
```ruby
|
|
146
195
|
TurnKit.default_model = "claude-sonnet-4-5"
|
|
@@ -165,18 +214,18 @@ agent = TurnKit::Agent.new(
|
|
|
165
214
|
|
|
166
215
|
| Option | Description |
|
|
167
216
|
| --- | --- |
|
|
168
|
-
| `default_model` |
|
|
169
|
-
| `client` |
|
|
170
|
-
| `store` |
|
|
171
|
-
| `max_iterations` |
|
|
172
|
-
| `timeout` |
|
|
173
|
-
| `max_depth` |
|
|
174
|
-
| `max_tool_executions` |
|
|
175
|
-
| `cost_limit` |
|
|
217
|
+
| `default_model` | Set the default model. |
|
|
218
|
+
| `client` | Set the model client. |
|
|
219
|
+
| `store` | Set the conversation store. |
|
|
220
|
+
| `max_iterations` | Limit model calls per turn. |
|
|
221
|
+
| `timeout` | Limit seconds per root turn. |
|
|
222
|
+
| `max_depth` | Limit sub-agent nesting. |
|
|
223
|
+
| `max_tool_executions` | Limit tool calls per root turn. |
|
|
224
|
+
| `cost_limit` | Limit cost per root turn. |
|
|
176
225
|
|
|
177
226
|
## Contributing
|
|
178
227
|
|
|
179
|
-
|
|
228
|
+
Report bugs and open pull requests on GitHub:
|
|
180
229
|
|
|
181
230
|
```text
|
|
182
231
|
https://github.com/samuelcouch/turnkit
|
data/lib/turnkit/agent.rb
CHANGED
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
module TurnKit
|
|
4
4
|
class Agent
|
|
5
|
-
attr_reader :name, :description, :model, :instructions, :tools, :skills, :sub_agents
|
|
5
|
+
attr_reader :name, :description, :model, :instructions, :tools, :skills, :available_skills, :sub_agents
|
|
6
6
|
attr_reader :client, :store, :max_iterations, :timeout, :cost_limit, :max_depth, :max_tool_executions
|
|
7
|
+
attr_reader :prompt_sections, :system_prompt
|
|
7
8
|
|
|
8
|
-
def initialize(name:, description: "", model: nil, instructions: "", tools: [], skills: [],
|
|
9
|
+
def initialize(name:, description: "", model: nil, instructions: "", tools: [], skills: [], available_skills: [], sub_agents: [],
|
|
10
|
+
system_prompt: nil, prompt_sections: nil, client: nil, store: nil,
|
|
9
11
|
max_iterations: nil, timeout: nil, cost_limit: nil, max_depth: nil, max_tool_executions: nil)
|
|
10
12
|
@name = name.to_s
|
|
11
13
|
@description = description.to_s
|
|
@@ -13,7 +15,10 @@ module TurnKit
|
|
|
13
15
|
@instructions = instructions.to_s
|
|
14
16
|
@tools = Array(tools)
|
|
15
17
|
@skills = Array(skills)
|
|
18
|
+
@available_skills = Array(available_skills)
|
|
16
19
|
@sub_agents = Array(sub_agents)
|
|
20
|
+
@system_prompt = system_prompt
|
|
21
|
+
@prompt_sections = prompt_sections
|
|
17
22
|
@client = client
|
|
18
23
|
@store = store
|
|
19
24
|
@max_iterations = max_iterations
|
|
@@ -51,6 +56,27 @@ module TurnKit
|
|
|
51
56
|
tools + sub_agents.map { |agent| SubAgentTool.for(agent) }
|
|
52
57
|
end
|
|
53
58
|
|
|
59
|
+
def effective_available_skills
|
|
60
|
+
(Array(TurnKit.available_skills) + available_skills).uniq { |skill| skill.key }
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def effective_prompt_sections
|
|
64
|
+
prompt_sections || TurnKit.prompt_sections
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def system_prompt_for(turn:, conversation:)
|
|
68
|
+
prompt = SystemPrompt.new(agent: self, turn: turn, conversation: conversation)
|
|
69
|
+
|
|
70
|
+
case system_prompt
|
|
71
|
+
when nil
|
|
72
|
+
prompt.to_s
|
|
73
|
+
when String
|
|
74
|
+
system_prompt
|
|
75
|
+
else
|
|
76
|
+
system_prompt.call(prompt).to_s
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
54
80
|
def build_budget(root_started_at: Clock.now)
|
|
55
81
|
Budget.new(
|
|
56
82
|
max_iterations: max_iterations || TurnKit.max_iterations,
|
|
@@ -64,9 +90,7 @@ module TurnKit
|
|
|
64
90
|
|
|
65
91
|
def instructions_with_skills
|
|
66
92
|
parts = [ instructions ]
|
|
67
|
-
|
|
68
|
-
parts << "## Skill: #{skill.name}\n\n#{skill.content}"
|
|
69
|
-
end
|
|
93
|
+
parts << SystemPrompt.loaded_skills_text(skills)
|
|
70
94
|
parts.reject(&:empty?).join("\n\n")
|
|
71
95
|
end
|
|
72
96
|
end
|
|
@@ -12,3 +12,8 @@ TurnKit.tool_execution_record_class = "Turnkit::ToolExecution"
|
|
|
12
12
|
# TurnKit.timeout = 300
|
|
13
13
|
# TurnKit.max_depth = 3
|
|
14
14
|
# TurnKit.max_tool_executions = 100
|
|
15
|
+
|
|
16
|
+
# TurnKit builds each system prompt from these sections by default.
|
|
17
|
+
# TurnKit.prompt_sections = %i[agent instructions behavior loaded_skills available_skills tools subject environment]
|
|
18
|
+
# TurnKit.prompt_behavior = "Custom behavior instructions."
|
|
19
|
+
# TurnKit.available_skills = TurnKit::Skill.from_directory(Rails.root.join("app/ai/skills"))
|
|
@@ -8,6 +8,8 @@ module TurnKit
|
|
|
8
8
|
class InstallGenerator < Rails::Generators::Base
|
|
9
9
|
include Rails::Generators::Migration
|
|
10
10
|
|
|
11
|
+
namespace "turnkit:install"
|
|
12
|
+
|
|
11
13
|
source_root File.expand_path("install/templates", __dir__)
|
|
12
14
|
|
|
13
15
|
class_option :table_prefix, type: :string, default: "turnkit", desc: "Database table prefix."
|
|
@@ -28,13 +30,21 @@ module TurnKit
|
|
|
28
30
|
end
|
|
29
31
|
|
|
30
32
|
def self.next_migration_number(dirname)
|
|
31
|
-
if
|
|
33
|
+
if timestamped_migrations?
|
|
32
34
|
Time.now.utc.strftime("%Y%m%d%H%M%S")
|
|
33
35
|
else
|
|
34
36
|
"%.3d" % (current_migration_number(dirname) + 1)
|
|
35
37
|
end
|
|
36
38
|
end
|
|
37
39
|
|
|
40
|
+
def self.timestamped_migrations?
|
|
41
|
+
if ActiveRecord.respond_to?(:timestamped_migrations)
|
|
42
|
+
ActiveRecord.timestamped_migrations
|
|
43
|
+
else
|
|
44
|
+
ActiveRecord::Base.timestamped_migrations
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
38
48
|
private
|
|
39
49
|
def table_prefix
|
|
40
50
|
options[:table_prefix]
|
data/lib/turnkit/skill.rb
CHANGED
|
@@ -10,6 +10,10 @@ module TurnKit
|
|
|
10
10
|
new(key: key || base, name: name || base.tr("_-", " ").split.map(&:capitalize).join(" "), description: description, content: content)
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
+
def self.from_directory(path, pattern: "*.md")
|
|
14
|
+
Dir.glob(File.join(path, pattern)).sort.map { |file| from_file(file) }
|
|
15
|
+
end
|
|
16
|
+
|
|
13
17
|
def initialize(key:, name:, content:, description: "")
|
|
14
18
|
@key = key.to_s
|
|
15
19
|
@name = name.to_s
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TurnKit
|
|
4
|
+
class SystemPrompt
|
|
5
|
+
DEFAULT_SECTIONS = %i[agent instructions behavior loaded_skills available_skills tools subject environment].freeze
|
|
6
|
+
SECTION_METHODS = {
|
|
7
|
+
agent: :agent_section,
|
|
8
|
+
instructions: :instructions_section,
|
|
9
|
+
behavior: :behavior_section,
|
|
10
|
+
loaded_skills: :loaded_skills_section,
|
|
11
|
+
available_skills: :available_skills_section,
|
|
12
|
+
tools: :tools_section,
|
|
13
|
+
subject: :subject_section,
|
|
14
|
+
environment: :environment_section
|
|
15
|
+
}.freeze
|
|
16
|
+
|
|
17
|
+
DEFAULT_BEHAVIOR = <<~TEXT.strip
|
|
18
|
+
Treat each user message as a constraint on the current task. Follow the
|
|
19
|
+
agent instructions and loaded skills first, then use tools when they are
|
|
20
|
+
available and needed.
|
|
21
|
+
|
|
22
|
+
Use the provided environment as the source of truth for the current date
|
|
23
|
+
and time. Do not guess relative dates like "today", "tomorrow", or
|
|
24
|
+
"yesterday" when the environment gives an exact calendar anchor.
|
|
25
|
+
|
|
26
|
+
Only use tools listed in <tools_available>. If a tool you want is not
|
|
27
|
+
listed, it is unavailable for this turn; adjust your answer instead of
|
|
28
|
+
pretending to call it.
|
|
29
|
+
|
|
30
|
+
If a tool returns an error, read the error and fix your inputs before
|
|
31
|
+
trying again. Do not retry the identical failing call blindly.
|
|
32
|
+
|
|
33
|
+
Report outcomes honestly. If you cannot verify something, say so or omit
|
|
34
|
+
the claim instead of inventing details.
|
|
35
|
+
TEXT
|
|
36
|
+
|
|
37
|
+
attr_reader :agent, :turn, :conversation, :sections
|
|
38
|
+
|
|
39
|
+
def initialize(agent:, turn:, conversation:, sections: nil)
|
|
40
|
+
@agent = agent
|
|
41
|
+
@turn = turn
|
|
42
|
+
@conversation = conversation
|
|
43
|
+
@sections = Array(sections || agent.effective_prompt_sections)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def to_s
|
|
47
|
+
sections.map { |section| render(section) }.compact.reject { |value| value.strip.empty? }.join("\n\n")
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def render(section)
|
|
51
|
+
method = SECTION_METHODS[section.to_sym]
|
|
52
|
+
raise ArgumentError, "unknown prompt section: #{section}" unless method
|
|
53
|
+
|
|
54
|
+
public_send(method)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def agent_section
|
|
58
|
+
lines = [
|
|
59
|
+
"- Name: #{agent.name}",
|
|
60
|
+
agent.description.empty? ? nil : "- Description: #{agent.description}",
|
|
61
|
+
"- Model: #{turn.model || agent.effective_model}"
|
|
62
|
+
].compact
|
|
63
|
+
|
|
64
|
+
tagged("agent", lines.join("\n"))
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def instructions_section
|
|
68
|
+
return nil if agent.instructions.empty?
|
|
69
|
+
|
|
70
|
+
tagged("instructions", agent.instructions)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def behavior_section
|
|
74
|
+
tagged("behavior", TurnKit.prompt_behavior || DEFAULT_BEHAVIOR)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def loaded_skills_section
|
|
78
|
+
return nil if agent.skills.empty?
|
|
79
|
+
|
|
80
|
+
tagged(
|
|
81
|
+
"skills_loaded",
|
|
82
|
+
self.class.loaded_skills_text(agent.skills)
|
|
83
|
+
)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def self.loaded_skills_text(skills)
|
|
87
|
+
skills.map { |skill| "## Skill: #{skill.key}\n\n#{skill.content}" }.join("\n\n")
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def available_skills_section
|
|
91
|
+
skills = agent.effective_available_skills
|
|
92
|
+
return nil if skills.empty?
|
|
93
|
+
|
|
94
|
+
entries = skills.map do |skill|
|
|
95
|
+
description = skill.description.empty? ? nil : " — #{skill.description}"
|
|
96
|
+
"- #{skill.key}: #{skill.name}#{description}"
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
tagged(
|
|
100
|
+
"skills_available",
|
|
101
|
+
"Load or follow a skill when the task matches its description.\n\n#{entries.join("\n")}"
|
|
102
|
+
)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def tools_section
|
|
106
|
+
tools = agent.effective_tools
|
|
107
|
+
|
|
108
|
+
if tools.empty?
|
|
109
|
+
tagged("tools_available", "(none)\n\nNo tools are available for this turn.")
|
|
110
|
+
else
|
|
111
|
+
tagged("tools_available", tools.map { |tool| tool_line(tool) }.join("\n"))
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def subject_section
|
|
116
|
+
return nil unless conversation.subject&.respond_to?(:to_prompt)
|
|
117
|
+
|
|
118
|
+
value = conversation.subject.to_prompt.to_s.strip
|
|
119
|
+
return nil if value.empty?
|
|
120
|
+
|
|
121
|
+
tagged("subject_context", value)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def environment_section
|
|
125
|
+
anchor = turn.started_at || Clock.now
|
|
126
|
+
today = anchor.to_date
|
|
127
|
+
yesterday = today - 1
|
|
128
|
+
tomorrow = today + 1
|
|
129
|
+
|
|
130
|
+
tagged(
|
|
131
|
+
"environment",
|
|
132
|
+
[
|
|
133
|
+
"- Today: #{today.strftime('%A, %B %-d, %Y')} (#{today.iso8601})",
|
|
134
|
+
"- Current time: #{anchor.strftime('%-I:%M %Z')}",
|
|
135
|
+
"- Yesterday: #{yesterday.strftime('%A, %B %-d, %Y')} (#{yesterday.iso8601})",
|
|
136
|
+
"- Tomorrow: #{tomorrow.strftime('%A, %B %-d, %Y')} (#{tomorrow.iso8601})"
|
|
137
|
+
].join("\n")
|
|
138
|
+
)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
private
|
|
142
|
+
def tagged(name, content)
|
|
143
|
+
"<#{name}>\n#{content}\n</#{name}>"
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def tool_line(tool)
|
|
147
|
+
description = tool.description.empty? ? nil : ": #{tool.description}"
|
|
148
|
+
params = tool.parameters.map do |param|
|
|
149
|
+
required = param.fetch(:required) ? " required" : ""
|
|
150
|
+
enum = param[:enum] ? " enum=#{Array(param[:enum]).join('|')}" : ""
|
|
151
|
+
"#{param.fetch(:name)}(#{param.fetch(:type)}#{required}#{enum})"
|
|
152
|
+
end
|
|
153
|
+
suffix = params.empty? ? "" : " Parameters: #{params.join(', ')}."
|
|
154
|
+
terminal = tool.ends_turn? ? " Ends the turn." : ""
|
|
155
|
+
"- #{tool.tool_name}#{description}#{suffix}#{terminal}"
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
data/lib/turnkit/turn.rb
CHANGED
|
@@ -7,6 +7,7 @@ module TurnKit
|
|
|
7
7
|
attr_reader :agent, :conversation, :store, :budget, :depth
|
|
8
8
|
attr_reader :id, :conversation_id, :agent_name, :parent_turn_id, :parent_tool_execution_id
|
|
9
9
|
attr_reader :root_turn_id, :context_message_sequence, :model
|
|
10
|
+
attr_reader :started_at
|
|
10
11
|
|
|
11
12
|
def initialize(agent:, conversation:, record:, store:, budget: nil, depth: 0)
|
|
12
13
|
@agent = agent
|
|
@@ -21,6 +22,7 @@ module TurnKit
|
|
|
21
22
|
@root_turn_id = @record["root_turn_id"] || id
|
|
22
23
|
@context_message_sequence = @record["context_message_sequence"].to_i
|
|
23
24
|
@model = @record["model"] || agent.effective_model
|
|
25
|
+
@started_at = @record["started_at"]
|
|
24
26
|
@budget = budget || agent.build_budget
|
|
25
27
|
@depth = depth
|
|
26
28
|
end
|
|
@@ -37,7 +39,7 @@ module TurnKit
|
|
|
37
39
|
model: model,
|
|
38
40
|
messages: llm_messages,
|
|
39
41
|
tools: agent.effective_tools,
|
|
40
|
-
instructions: agent.
|
|
42
|
+
instructions: agent.system_prompt_for(turn: self, conversation: conversation),
|
|
41
43
|
metadata: { turn_id: id, conversation_id: conversation.id }
|
|
42
44
|
)
|
|
43
45
|
|
|
@@ -128,6 +130,9 @@ module TurnKit
|
|
|
128
130
|
|
|
129
131
|
def update!(attributes)
|
|
130
132
|
@record = store.update_turn(id, attributes)
|
|
133
|
+
@started_at = @record["started_at"]
|
|
134
|
+
@model = @record["model"] || agent.effective_model
|
|
135
|
+
@record
|
|
131
136
|
end
|
|
132
137
|
end
|
|
133
138
|
|
data/lib/turnkit/version.rb
CHANGED
data/lib/turnkit.rb
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require "json"
|
|
4
4
|
require "securerandom"
|
|
5
5
|
require "time"
|
|
6
|
+
require "date"
|
|
6
7
|
|
|
7
8
|
require_relative "turnkit/version"
|
|
8
9
|
require_relative "turnkit/error"
|
|
@@ -16,6 +17,7 @@ require_relative "turnkit/message"
|
|
|
16
17
|
require_relative "turnkit/record"
|
|
17
18
|
require_relative "turnkit/result"
|
|
18
19
|
require_relative "turnkit/skill"
|
|
20
|
+
require_relative "turnkit/system_prompt"
|
|
19
21
|
require_relative "turnkit/store"
|
|
20
22
|
require_relative "turnkit/memory_store"
|
|
21
23
|
require_relative "turnkit/tool"
|
|
@@ -36,6 +38,7 @@ module TurnKit
|
|
|
36
38
|
attr_accessor :default_model, :client, :store, :logger
|
|
37
39
|
attr_accessor :max_iterations, :timeout, :max_depth, :max_tool_executions
|
|
38
40
|
attr_accessor :cost_limit
|
|
41
|
+
attr_accessor :prompt_sections, :prompt_behavior, :available_skills
|
|
39
42
|
attr_accessor :conversation_record_class, :turn_record_class
|
|
40
43
|
attr_accessor :message_record_class, :tool_execution_record_class
|
|
41
44
|
end
|
|
@@ -47,6 +50,8 @@ module TurnKit
|
|
|
47
50
|
self.timeout = 300
|
|
48
51
|
self.max_depth = 3
|
|
49
52
|
self.max_tool_executions = 100
|
|
53
|
+
self.prompt_sections = SystemPrompt::DEFAULT_SECTIONS.dup
|
|
54
|
+
self.available_skills = []
|
|
50
55
|
|
|
51
56
|
def self.reconcile_stale!(before: Clock.now - (timeout || 300))
|
|
52
57
|
store.find_stale_turns(before: before).each do |turn|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: turnkit
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Sam Couch
|
|
@@ -62,6 +62,7 @@ files:
|
|
|
62
62
|
- lib/turnkit/store.rb
|
|
63
63
|
- lib/turnkit/stores/active_record_store.rb
|
|
64
64
|
- lib/turnkit/sub_agent_tool.rb
|
|
65
|
+
- lib/turnkit/system_prompt.rb
|
|
65
66
|
- lib/turnkit/tool.rb
|
|
66
67
|
- lib/turnkit/tool_call.rb
|
|
67
68
|
- lib/turnkit/tool_execution.rb
|