Class: LittleGhost::Run

Inherits:
Object
  • Object
show all
Includes:
Enumerable
Defined in:
lib/little_ghost/run.rb

Overview

Observe one top-level agent or workflow execution from start to finish. A run records its response, outcome, usage, error, and owned resources.

run = CustomerSupportAgent.ask("Why is transfer 481 pending?")

run.completed? # => true
run.outcome    # => "completed"
run.response   # => "Transfer 481 is waiting for the receiving bank."

The class-level ask helper or standalone ask method consumes the event stream and returns the Run. For a live interface, the class-level streaming helper or standalone streaming method yields StreamEvent objects and returns the same run after enumeration. A run can execute only once.

Completion, failure, deadline, and cancellation become the completed, failed, partial, and cancelled outcomes. Ordinary execution failures are available through error and the terminal stream event; cleanup, event delivery, or instrumentation failures may still raise because the framework cannot safely report a clean stop.

The run opens its workspace, sandbox, session, and entrypoint, then closes registered resources in reverse order. register extends that lifecycle for application resources. Interruption is available only while an agent entrypoint is active and unambiguous.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(invocation:, agent_class:, runtime:, entrypoint_class: agent_class, cancellation_token: Support::CancellationToken.new, workspace: nil, sandbox: nil) ⇒ Run

Creates a dormant run for invocation.



41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/little_ghost/run.rb', line 41

def initialize(invocation:, agent_class:, runtime:, entrypoint_class: agent_class,
  cancellation_token: Support::CancellationToken.new, workspace: nil, sandbox: nil)
  @runtime = runtime
  @agent_class = agent_class
  @entrypoint_class = entrypoint_class
  @invocation = invocation
  @cancellation_token = cancellation_token
  @workspace = workspace
  @sandbox = sandbox
  @operation_id = SecureRandom.uuid
  @resources = []
  @closed = false
  @started = false
  @mutex = Mutex.new
  @event_mutex = Mutex.new
  @subagent_instrumentation_mutex = Mutex.new
  @subagent_instrumentation = {}
  @exclusive_tools_mutex = Mutex.new
  @once_mutex = Mutex.new
  @once_keys = {}
  @interruption_mutex = Mutex.new
  @interruption_state = :not_started
  @entrypoint = nil
  @usage = Usage.new
end

Instance Attribute Details

#agent_classObject (readonly)

Runtime and declarations used to execute the run; its request, cancellation token, resources, terminal outcome, response, result, usage, and error.



37
38
39
# File 'lib/little_ghost/run.rb', line 37

def agent_class
  @agent_class
end

#cancellation_tokenObject (readonly)

Runtime and declarations used to execute the run; its request, cancellation token, resources, terminal outcome, response, result, usage, and error.



37
38
39
# File 'lib/little_ghost/run.rb', line 37

def cancellation_token
  @cancellation_token
end

#entrypoint_classObject (readonly)

Runtime and declarations used to execute the run; its request, cancellation token, resources, terminal outcome, response, result, usage, and error.



37
38
39
# File 'lib/little_ghost/run.rb', line 37

def entrypoint_class
  @entrypoint_class
end

#errorObject (readonly)

Runtime and declarations used to execute the run; its request, cancellation token, resources, terminal outcome, response, result, usage, and error.



37
38
39
# File 'lib/little_ghost/run.rb', line 37

def error
  @error
end

#invocationObject (readonly)

Runtime and declarations used to execute the run; its request, cancellation token, resources, terminal outcome, response, result, usage, and error.



37
38
39
# File 'lib/little_ghost/run.rb', line 37

def invocation
  @invocation
end

#operation_idObject (readonly)

Runtime and declarations used to execute the run; its request, cancellation token, resources, terminal outcome, response, result, usage, and error.



37
38
39
# File 'lib/little_ghost/run.rb', line 37

def operation_id
  @operation_id
end

#outcomeObject (readonly)

Runtime and declarations used to execute the run; its request, cancellation token, resources, terminal outcome, response, result, usage, and error.



37
38
39
# File 'lib/little_ghost/run.rb', line 37

def outcome
  @outcome
end

#responseObject (readonly)

Runtime and declarations used to execute the run; its request, cancellation token, resources, terminal outcome, response, result, usage, and error.



37
38
39
# File 'lib/little_ghost/run.rb', line 37

def response
  @response
end

#resultObject (readonly)

Runtime and declarations used to execute the run; its request, cancellation token, resources, terminal outcome, response, result, usage, and error.



37
38
39
# File 'lib/little_ghost/run.rb', line 37

def result
  @result
end

#runtimeObject (readonly)

Runtime and declarations used to execute the run; its request, cancellation token, resources, terminal outcome, response, result, usage, and error.



37
38
39
# File 'lib/little_ghost/run.rb', line 37

def runtime
  @runtime
end

#sandboxObject (readonly)

Runtime and declarations used to execute the run; its request, cancellation token, resources, terminal outcome, response, result, usage, and error.



37
38
39
# File 'lib/little_ghost/run.rb', line 37

def sandbox
  @sandbox
end

#sessionObject (readonly)

Runtime and declarations used to execute the run; its request, cancellation token, resources, terminal outcome, response, result, usage, and error.



37
38
39
# File 'lib/little_ghost/run.rb', line 37

def session
  @session
end

#usageObject (readonly)

Runtime and declarations used to execute the run; its request, cancellation token, resources, terminal outcome, response, result, usage, and error.



37
38
39
# File 'lib/little_ghost/run.rb', line 37

def usage
  @usage
end

#workspaceObject (readonly)

Runtime and declarations used to execute the run; its request, cancellation token, resources, terminal outcome, response, result, usage, and error.



37
38
39
# File 'lib/little_ghost/run.rb', line 37

def workspace
  @workspace
end

Instance Method Details

#callObject

Consumes the event stream and returns self.



68
69
70
71
# File 'lib/little_ghost/run.rb', line 68

def call
  each { |_event| }
  self
end

#cancelled?Boolean

True when cancellation stopped the run without a response.

Returns:

  • (Boolean)


99
# File 'lib/little_ghost/run.rb', line 99

def cancelled? = outcome == "cancelled"

#closeObject

Closes registered resources in reverse order.

The operation is idempotent. It attempts every closer and then raises the first LittleGhost::CleanupError, or otherwise the first cleanup exception.



194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
# File 'lib/little_ghost/run.rb', line 194

def close
  callbacks = @mutex.synchronize do
    return if @closed
    @closed = true
    @resources.reverse
  end
  errors = []
  callbacks.each do |callback|
    callback.call
  rescue => error
    errors << error
  end
  cleanup_error = errors.find { |caught| caught.is_a?(CleanupError) } || errors.first
  begin
    finish_remaining_subagent_instrumentation(
      outcome: cleanup_error ? :error : :cancelled,
      error_type: cleanup_error&.class&.name
    )
  rescue => error
    errors << error
  end
  error = errors.find { |caught| caught.is_a?(CleanupError) } || errors.first
  raise error if error
end

#completed?Boolean

True after successful completion.

Returns:

  • (Boolean)


90
# File 'lib/little_ghost/run.rb', line 90

def completed? = outcome == "completed"

#context(state: {}, metadata: {}) ⇒ Object

Creates a RunContext with this run's cancellation token and deadline.



138
139
140
141
142
143
144
145
# File 'lib/little_ghost/run.rb', line 138

def context(state: {}, metadata: {})
  RunContext.new(
    state:,
    cancellation_token:,
    deadline: invocation.deadline_at,
    metadata:
  )
end

#eachObject

Yields events and returns self after the terminal event.

Without a block, returns an Enumerator. A second execution raises Error.



76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/little_ghost/run.rb', line 76

def each
  return enum_for(__method__) unless block_given?

  begin_execution!
  @emitter = ->(event) { yield_event(event) { |value| yield value } }
  Instrumentation.with_context(correlation_attributes.except(:operation_id)) do
    execute { |event| yield event }
  end
  self
ensure
  @emitter = nil
end

#failed?Boolean

True after execution or cleanup failed.

Returns:

  • (Boolean)


93
# File 'lib/little_ghost/run.rb', line 93

def failed? = outcome == "failed"

#interrupt_response(message, interruption_id: nil, batch_key: nil, metadata: {}, cancellation_token: Support::CancellationToken.new, deadline: nil) ⇒ Object

Adds an interruption to the active entrypoint and waits for its response.

Raises LittleGhost::AgentInterruptError before the entrypoint is ready, after it finishes, or when the entrypoint does not support interruptions.



105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/little_ghost/run.rb', line 105

def interrupt_response(
  message,
  interruption_id: nil,
  batch_key: nil,
  metadata: {},
  cancellation_token: Support::CancellationToken.new,
  deadline: nil
)
  entrypoint = @interruption_mutex.synchronize do
    case @interruption_state
    when :not_started, :starting
      raise AgentInterruptError, "Run entrypoint is not ready for interruptions"
    when :terminal
      raise AgentInterruptError, "Run has already finished"
    end

    @entrypoint
  end
  unless entrypoint.is_a?(Agent)
    raise AgentInterruptError, "Run entrypoint does not support interruptions"
  end

  entrypoint.interrupt_response(
    message,
    interruption_id:,
    batch_key:,
    metadata:,
    cancellation_token:,
    deadline:
  )
end

#once(key) ⇒ Object

Performs the block at most once successfully for key during this run.

Concurrent callers are serialized. The caller that performs the block receives its value; later callers receive nil. If the block raises, the key is not recorded and a later call may retry it.



176
177
178
179
180
181
182
183
184
# File 'lib/little_ghost/run.rb', line 176

def once(key)
  @once_mutex.synchronize do
    return if @once_keys.key?(key)

    value = yield
    @once_keys[key] = true
    value
  end
end

#partial?Boolean

True when the deadline preserved a partial response.

Returns:

  • (Boolean)


96
# File 'lib/little_ghost/run.rb', line 96

def partial? = outcome == "partial"

#prepare_interruption(payload) ⇒ Object

:nodoc:



186
187
188
# File 'lib/little_ghost/run.rb', line 186

def prepare_interruption(payload) # :nodoc:
  runtime.prepare_interruption(self, payload)
end

#publish(type, **data) ⇒ Object

:nodoc:



147
148
149
150
151
152
# File 'lib/little_ghost/run.rb', line 147

def publish(type, **data) # :nodoc:
  event = StreamEvent.build(type, **data)
  @event_mutex.synchronize { @emitter&.call(event) }
  instrument_event(type, data)
  event
end

#register(resource = nil, &closer) ⇒ Object

Adds a resource or closer to reverse-order cleanup and returns the resource.

A resource must respond to close unless a block supplies the cleanup operation. Registering after the run has closed raises Error.



158
159
160
161
162
163
164
165
# File 'lib/little_ghost/run.rb', line 158

def register(resource = nil, &closer)
  callback = closer || close_callback(resource)
  @mutex.synchronize do
    raise Error, "run is already closed" if @closed
    @resources << callback
  end
  resource
end

#synchronize_exclusive_tools(&block) ⇒ Object

:nodoc:



167
168
169
# File 'lib/little_ghost/run.rb', line 167

def synchronize_exclusive_tools(&block) # :nodoc:
  @exclusive_tools_mutex.synchronize(&block)
end