Class: PromptObjects::PromptObject

Inherits:
Capability show all
Defined in:
lib/prompt_objects/prompt_object.rb

Overview

An immutable-at-execution-time LLM capability definition. Conversation state belongs to runtime threads, runs, messages, and events; this object only owns the editable definition loaded from markdown.

Instance Attribute Summary collapse

Attributes inherited from Capability

#state

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Capability

#descriptor, execution_policy, execution_policy_definition, #execution_policy_definition, #execution_policy_signature, #receive, #resolved_execution_policy

Constructor Details

#initialize(config:, body:, env:, llm:, path: nil) ⇒ PromptObject

Returns a new instance of PromptObject.



12
13
14
15
16
17
18
19
# File 'lib/prompt_objects/prompt_object.rb', line 12

def initialize(config:, body:, env:, llm:, path: nil)
  super()
  @config = config
  @body = body
  @env = env
  @llm = llm
  @path = path
end

Instance Attribute Details

#bodyObject (readonly)

Returns the value of attribute body.



10
11
12
# File 'lib/prompt_objects/prompt_object.rb', line 10

def body
  @body
end

#configObject (readonly)

Returns the value of attribute config.



10
11
12
# File 'lib/prompt_objects/prompt_object.rb', line 10

def config
  @config
end

#pathObject (readonly)

Returns the value of attribute path.



10
11
12
# File 'lib/prompt_objects/prompt_object.rb', line 10

def path
  @path
end

Class Method Details

.serialize_message(message) ⇒ Object

Transitional wire serializers for repository records. They do not read or mutate PromptObject state and can be removed with the older endpoint shape.



165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
# File 'lib/prompt_objects/prompt_object.rb', line 165

def self.serialize_message(message)
  case message[:role]
  when :user
    { role: "user", content: message[:content], from: message[:from] || message[:from_po] }
  when :assistant
    hash = { role: "assistant", content: message[:content] }
    if message[:tool_calls]
      hash[:tool_calls] = message[:tool_calls].map do |tool_call|
        if tool_call.respond_to?(:id)
          { id: tool_call.id, name: tool_call.name, arguments: tool_call.arguments }
        else
          {
            id: tool_call[:id] || tool_call["id"],
            name: tool_call[:name] || tool_call["name"],
            arguments: tool_call[:arguments] || tool_call["arguments"] || {}
          }
        end
      end
    end
    hash
  when :tool
    { role: "tool", results: message[:results] || message[:tool_results] }
  else
    { role: message[:role].to_s, content: message[:content] }
  end
end

.serialize_session(session) ⇒ Object



192
193
194
195
196
197
198
199
200
201
202
# File 'lib/prompt_objects/prompt_object.rb', line 192

def self.serialize_session(session)
  {
    id: session[:id],
    name: session[:name],
    message_count: session[:message_count] || 0,
    updated_at: session[:updated_at]&.iso8601,
    parent_session_id: session[:parent_session_id],
    parent_po: session[:parent_po],
    thread_type: session[:thread_type] || "root"
  }
end

Instance Method Details

#autonomous?Boolean

Returns:

  • (Boolean)


29
30
31
# File 'lib/prompt_objects/prompt_object.rb', line 29

def autonomous?
  !!@config["autonomous"]
end

#autonomous_configObject



33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/prompt_objects/prompt_object.rb', line 33

def autonomous_config
  return nil unless autonomous?

  auto = @config["autonomous"]
  auto = {} unless auto.is_a?(Hash)
  {
    "interval" => auto["interval"] || 30,
    "initial_delay" => auto["initial_delay"] || auto["interval"] || 30,
    "message" => auto["message"] || "Check your environment and act.",
    "start_on_boot" => auto.fetch("start_on_boot", true),
    "wait_for_human" => auto.fetch("wait_for_human", false)
  }
end

#descriptionObject



25
26
27
# File 'lib/prompt_objects/prompt_object.rb', line 25

def description
  @config["description"] || "A prompt object"
end

#execution_definition(provider: nil, model: nil, capability_policy_version: "1") ⇒ Object

Capture the exact definition used by a run. Hot reload or definition editing cannot alter this deeply frozen snapshot after enqueue.



206
207
208
209
210
211
212
213
214
215
216
217
218
# File 'lib/prompt_objects/prompt_object.rb', line 206

def execution_definition(provider: nil, model: nil, capability_policy_version: "1")
  snapshot_config = deep_freeze(Marshal.load(Marshal.dump(@config)))
  version_payload = JSON.generate(canonicalize({ "prompt" => @body, "config" => snapshot_config }))
  Domain::PromptObjectDefinition.new(
    po_name: name,
    version: Digest::SHA256.hexdigest(version_payload),
    prompt: @body.dup.freeze,
    config: snapshot_config,
    provider: provider,
    model: model,
    capability_policy_version: capability_policy_version
  )
end

#nameObject



21
22
23
# File 'lib/prompt_objects/prompt_object.rb', line 21

def name
  @config["name"] || "unnamed"
end

#parametersObject



47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/prompt_objects/prompt_object.rb', line 47

def parameters
  {
    type: "object",
    properties: {
      message: {
        type: "string",
        description: "Natural language message to send"
      }
    },
    required: ["message"]
  }
end

#relocate(new_path) ⇒ Object



86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/prompt_objects/prompt_object.rb', line 86

def relocate(new_path)
  return false unless @path && File.exist?(@path)
  return false if new_path == @path
  return false if File.exist?(new_path)

  File.rename(@path, new_path)
  @path = new_path
  true
rescue => e
  puts "Error relocating PO #{name}: #{e.message}" if ENV["DEBUG"]
  false
end

#saveObject



99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/prompt_objects/prompt_object.rb', line 99

def save
  return false unless @path

  content = "#{save_frontmatter_yaml}---\n\n#{@body}\n"
  @env.suppress_next_reload(@path) if @env.respond_to?(:suppress_next_reload)
  File.write(@path, content, encoding: "UTF-8")
  @source = content
  @source_loaded = true
  true
rescue => e
  puts "Error saving PO #{name} to #{@path}: #{e.message}" if ENV["DEBUG"]
  false
end

#save_frontmatter_yamlObject



113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/prompt_objects/prompt_object.rb', line 113

def save_frontmatter_yaml
  frontmatter = @config.dup
  capabilities = frontmatter.delete("capabilities") || []

  if capabilities.any?
    registry = @env.respond_to?(:registry) ? @env.registry : nil
    delegates, tools = capabilities.partition { |capability_name| registry&.get(capability_name).is_a?(PromptObject) }
    frontmatter["delegates_to"] = delegates if delegates.any?
    frontmatter["tools"] = tools if tools.any?
  end

  frontmatter.to_yaml
end

#sourceObject

The raw markdown source for definition editing. File watcher reloads replace instances, while direct edits update this memo in place.



62
63
64
65
66
67
68
# File 'lib/prompt_objects/prompt_object.rb', line 62

def source
  unless @source_loaded
    @source_loaded = true
    @source = (@path && File.exist?(@path)) ? File.read(@path, encoding: "UTF-8") : nil
  end
  @source
end

#system_prompt_for(definition) ⇒ Object



220
221
222
223
224
225
226
# File 'lib/prompt_objects/prompt_object.rb', line 220

def system_prompt_for(definition)
  build_system_prompt_from(
    prompt: definition.prompt,
    config: definition.config,
    object_name: definition.po_name
  )
end

#to_inspect_hash(registry: nil) ⇒ Object



150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/prompt_objects/prompt_object.rb', line 150

def to_inspect_hash(registry: nil)
  {
    name: name,
    description: description,
    status: "idle",
    capabilities: resolve_capabilities(registry: registry),
    universal_capabilities: resolve_universal_capabilities(registry: registry),
    prompt: body,
    config: config,
    source: source
  }
end

#to_state_hash(registry: nil) ⇒ Object

Definition state for WebSocket and REST consumers. Runtime activity is delivered separately from authoritative repository snapshots/events.



129
130
131
132
133
134
135
136
137
138
139
# File 'lib/prompt_objects/prompt_object.rb', line 129

def to_state_hash(registry: nil)
  {
    status: "idle",
    description: description,
    capabilities: resolve_capabilities(registry: registry),
    universal_capabilities: resolve_universal_capabilities(registry: registry),
    prompt: body,
    config: config,
    source: source
  }
end

#to_summary_hash(registry: nil) ⇒ Object



141
142
143
144
145
146
147
148
# File 'lib/prompt_objects/prompt_object.rb', line 141

def to_summary_hash(registry: nil)
  {
    name: name,
    description: description,
    status: "idle",
    capabilities: resolve_capabilities(registry: registry)
  }
end

#tool_descriptors_for(definition) ⇒ Object



228
229
230
# File 'lib/prompt_objects/prompt_object.rb', line 228

def tool_descriptors_for(definition)
  descriptors_for_config(definition.config)
end

#update_source(raw) ⇒ Object



70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/prompt_objects/prompt_object.rb', line 70

def update_source(raw)
  return false unless @path

  data = Loader.parse(raw, path: @path)
  @env.suppress_next_reload(@path) if @env.respond_to?(:suppress_next_reload)
  File.write(@path, raw, encoding: "UTF-8")
  @config = data[:config]
  @body = data[:body]
  @source = raw
  @source_loaded = true
  true
rescue => e
  puts "Error updating source for PO #{name}: #{e.message}" if ENV["DEBUG"]
  false
end