Class: TurnKit::SystemPrompt

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

Constant Summary collapse

DEFAULT_SECTIONS =
%i[agent instructions behavior loaded_skills available_skills tools subject environment].freeze
SECTION_METHODS =
{
  agent: :agent_section,
  instructions: :instructions_section,
  behavior: :behavior_section,
  loaded_skills: :loaded_skills_section,
  available_skills: :available_skills_section,
  tools: :tools_section,
  subject: :subject_section,
  environment: :environment_section
}.freeze
DEFAULT_BEHAVIOR =
<<~TEXT.strip
  Treat each user message as a constraint on the current task. Follow the
  agent instructions and loaded skills first, then use tools when they are
  available and needed.

  Use the provided environment as the source of truth for the current date
  and time. Do not guess relative dates like "today", "tomorrow", or
  "yesterday" when the environment gives an exact calendar anchor.

  Only use tools listed in <tools_available>. If a tool you want is not
  listed, it is unavailable for this turn; adjust your answer instead of
  pretending to call it.

  If a tool returns an error, read the error and fix your inputs before
  trying again. Do not retry the identical failing call blindly.

  Report outcomes honestly. If you cannot verify something, say so or omit
  the claim instead of inventing details.
TEXT

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(agent:, turn:, conversation:, sections: nil) ⇒ SystemPrompt

Returns a new instance of SystemPrompt.



39
40
41
42
43
44
# File 'lib/turnkit/system_prompt.rb', line 39

def initialize(agent:, turn:, conversation:, sections: nil)
  @agent = agent
  @turn = turn
  @conversation = conversation
  @sections = Array(sections || agent.effective_prompt_sections)
end

Instance Attribute Details

#agentObject (readonly)

Returns the value of attribute agent.



37
38
39
# File 'lib/turnkit/system_prompt.rb', line 37

def agent
  @agent
end

#conversationObject (readonly)

Returns the value of attribute conversation.



37
38
39
# File 'lib/turnkit/system_prompt.rb', line 37

def conversation
  @conversation
end

#sectionsObject (readonly)

Returns the value of attribute sections.



37
38
39
# File 'lib/turnkit/system_prompt.rb', line 37

def sections
  @sections
end

#turnObject (readonly)

Returns the value of attribute turn.



37
38
39
# File 'lib/turnkit/system_prompt.rb', line 37

def turn
  @turn
end

Class Method Details

.loaded_skills_text(skills) ⇒ Object



86
87
88
# File 'lib/turnkit/system_prompt.rb', line 86

def self.loaded_skills_text(skills)
  skills.map { |skill| "## Skill: #{skill.key}\n\n#{skill.content}" }.join("\n\n")
end

Instance Method Details

#agent_sectionObject



57
58
59
60
61
62
63
64
65
# File 'lib/turnkit/system_prompt.rb', line 57

def agent_section
  lines = [
    "- Name: #{agent.name}",
    agent.description.empty? ? nil : "- Description: #{agent.description}",
    "- Model: #{turn.model || agent.effective_model}"
  ].compact

  tagged("agent", lines.join("\n"))
end

#available_skills_sectionObject



90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/turnkit/system_prompt.rb', line 90

def available_skills_section
  skills = agent.effective_available_skills
  return nil if skills.empty?

  entries = skills.map do |skill|
    description = skill.description.empty? ? nil : "#{skill.description}"
    "- #{skill.key}: #{skill.name}#{description}"
  end

  tagged(
    "skills_available",
    "Load or follow a skill when the task matches its description.\n\n#{entries.join("\n")}"
  )
end

#behavior_sectionObject



73
74
75
# File 'lib/turnkit/system_prompt.rb', line 73

def behavior_section
  tagged("behavior", TurnKit.prompt_behavior || DEFAULT_BEHAVIOR)
end

#environment_sectionObject



124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/turnkit/system_prompt.rb', line 124

def environment_section
  anchor = turn.started_at || Clock.now
  today = anchor.to_date
  yesterday = today - 1
  tomorrow = today + 1

  tagged(
    "environment",
    [
      "- Today: #{today.strftime('%A, %B %-d, %Y')} (#{today.iso8601})",
      "- Current time: #{anchor.strftime('%-I:%M %Z')}",
      "- Yesterday: #{yesterday.strftime('%A, %B %-d, %Y')} (#{yesterday.iso8601})",
      "- Tomorrow: #{tomorrow.strftime('%A, %B %-d, %Y')} (#{tomorrow.iso8601})"
    ].join("\n")
  )
end

#instructions_sectionObject



67
68
69
70
71
# File 'lib/turnkit/system_prompt.rb', line 67

def instructions_section
  return nil if agent.instructions.empty?

  tagged("instructions", agent.instructions)
end

#loaded_skills_sectionObject



77
78
79
80
81
82
83
84
# File 'lib/turnkit/system_prompt.rb', line 77

def loaded_skills_section
  return nil if agent.skills.empty?

  tagged(
    "skills_loaded",
    self.class.loaded_skills_text(agent.skills)
  )
end

#render(section) ⇒ Object

Raises:

  • (ArgumentError)


50
51
52
53
54
55
# File 'lib/turnkit/system_prompt.rb', line 50

def render(section)
  method = SECTION_METHODS[section.to_sym]
  raise ArgumentError, "unknown prompt section: #{section}" unless method

  public_send(method)
end

#subject_sectionObject



115
116
117
118
119
120
121
122
# File 'lib/turnkit/system_prompt.rb', line 115

def subject_section
  return nil unless conversation.subject&.respond_to?(:to_prompt)

  value = conversation.subject.to_prompt.to_s.strip
  return nil if value.empty?

  tagged("subject_context", value)
end

#to_sObject



46
47
48
# File 'lib/turnkit/system_prompt.rb', line 46

def to_s
  sections.map { |section| render(section) }.compact.reject { |value| value.strip.empty? }.join("\n\n")
end

#tools_sectionObject



105
106
107
108
109
110
111
112
113
# File 'lib/turnkit/system_prompt.rb', line 105

def tools_section
  tools = agent.effective_tools

  if tools.empty?
    tagged("tools_available", "(none)\n\nNo tools are available for this turn.")
  else
    tagged("tools_available", tools.map { |tool| tool_line(tool) }.join("\n"))
  end
end