Class: LittleGhost::RunContext

Inherits:
Object
  • Object
show all
Defined in:
lib/little_ghost/run_context.rb

Overview

RunContext gives tools and workflows one place for shared state, cancellation, deadlines, checkpoints, and accumulated usage. It travels with work inside a run without becoming global process state.

Tools and workflows use it to share JSON-like state, check cancellation and deadlines, checkpoint messages, and accumulate usage. Framework-managed fields remain safe when calls overlap on threads or fibers.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(state: {}, cancellation_token: Support::CancellationToken.new, deadline: nil, metadata: {}, checkpoint: nil, conversation_id: nil, interjection_metadata: nil, interjection_ids: []) ⇒ RunContext

Creates a context with optional checkpoint and interjection state.



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/little_ghost/run_context.rb', line 32

def initialize(
  state: {},
  cancellation_token: Support::CancellationToken.new,
  deadline: nil,
  metadata: {},
  checkpoint: nil,
  conversation_id: nil,
  interjection_metadata: nil,
  interjection_ids: []
)
  if conversation_id
    conversation_id = String(conversation_id)
    raise ArgumentError, "conversation_id cannot be empty" if conversation_id.empty?
    conversation_id = conversation_id.dup.freeze
  end
  @state = DataMap.new(state)
  @cancellation_token = cancellation_token
  @deadline = deadline
  @metadata = .freeze
  @checkpoint = checkpoint
  @conversation_id = conversation_id
  @usage = Usage.new
  @usage_mutex = Mutex.new
  @tool_call_count = 0
  @tool_call_count_mutex = Mutex.new
  @structured_result = nil
  @structured_result_mutex = Mutex.new
  @agent_operation_id = nil
  @agent_operation_id_mutex = Mutex.new
  @interjection_mutex = Mutex.new
  @interjection_metadata = &.to_h
  @interjection_ids = Array(interjection_ids).map { |id| String(id).dup.freeze }.freeze
end

Instance Attribute Details

#agent_operation_idObject (readonly)

Active Agent operation identifier, after the context is bound.



27
28
29
# File 'lib/little_ghost/run_context.rb', line 27

def agent_operation_id
  @agent_operation_id
end

#cancellation_tokenObject (readonly)

Token used to cooperatively stop the current work.



21
22
23
# File 'lib/little_ghost/run_context.rb', line 21

def cancellation_token
  @cancellation_token
end

#conversation_idObject (readonly)

Durable subagent conversation identifier, when present.



29
30
31
# File 'lib/little_ghost/run_context.rb', line 29

def conversation_id
  @conversation_id
end

#deadlineObject (readonly)

Wall-clock deadline for the current work, when present.



23
24
25
# File 'lib/little_ghost/run_context.rb', line 23

def deadline
  @deadline
end

#metadataObject (readonly)

Framework metadata attached to this context.



25
26
27
# File 'lib/little_ghost/run_context.rb', line 25

def 
  @metadata
end

#stateObject (readonly)

Mutable DataMap state supplied to this invocation. A top-level Run starts with restored Session state merged with current Invocation context; child Assemblies may receive copied, mapped, or empty state. Application code must synchronize mutations when parallel Tools share this map, or use exclusive Tools. String and Symbol keys address the same value; persisted snapshots use canonical String keys.



19
20
21
# File 'lib/little_ghost/run_context.rb', line 19

def state
  @state
end

Instance Method Details

#activate_interjection(metadata:, ids:) ⇒ Object

:nodoc:



133
134
135
136
137
138
139
140
# File 'lib/little_ghost/run_context.rb', line 133

def activate_interjection(metadata:, ids:) # :nodoc:
  value = &.to_h
  values = Array(ids).map { |id| String(id).dup.freeze }.freeze
  @interjection_mutex.synchronize do
    @interjection_metadata = value
    @interjection_ids = values
  end
end

#bind_agent_operation_id(operation_id) ⇒ Object

:nodoc:



142
143
144
145
146
147
148
149
150
# File 'lib/little_ghost/run_context.rb', line 142

def bind_agent_operation_id(operation_id) # :nodoc:
  @agent_operation_id_mutex.synchronize do
    if @agent_operation_id && @agent_operation_id != operation_id
      raise Error, "run context is already bound to an agent operation"
    end

    @agent_operation_id ||= operation_id
  end
end

#check!Object

Raises LittleGhost::CancelledError or LittleGhost::DeadlineExceededError when execution should stop.



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

def check!
  cancellation_token.raise_if_cancelled!
  raise DeadlineExceededError, "The run deadline was reached" if deadline && Time.now >= deadline
end

#checkpoint(messages) ⇒ Object

Sends messages and current state to the configured checkpoint callback. With no checkpoint callback, this method does nothing and returns nil.



75
76
77
78
79
80
81
82
83
# File 'lib/little_ghost/run_context.rb', line 75

def checkpoint(messages)
  return unless @checkpoint

  if agent_operation_id
    @checkpoint.call(messages:, state:, parent_operation_id: agent_operation_id)
  else
    @checkpoint.call(messages:, state:)
  end
end

#interjection_idsObject

:nodoc:



129
130
131
# File 'lib/little_ghost/run_context.rb', line 129

def interjection_ids # :nodoc:
  @interjection_mutex.synchronize { @interjection_ids }
end

#interjection_metadataObject

:nodoc:



125
126
127
# File 'lib/little_ghost/run_context.rb', line 125

def  # :nodoc:
  @interjection_mutex.synchronize { @interjection_metadata }
end

#record_tool_calls!(count, maximum:) ⇒ Object

:nodoc:



95
96
97
98
99
100
# File 'lib/little_ghost/run_context.rb', line 95

def record_tool_calls!(count, maximum:) # :nodoc:
  @tool_call_count_mutex.synchronize do
    @tool_call_count += Integer(count)
    raise ProtocolError, "The agent reached its maximum tool calls" if @tool_call_count > Integer(maximum)
  end
end

#record_usage(value) ⇒ Object

Adds value to accumulated model usage.



86
87
88
# File 'lib/little_ghost/run_context.rb', line 86

def record_usage(value)
  @usage_mutex.synchronize { @usage += value }
end

#remaining_time(maximum = nil) ⇒ Object

Calculates seconds remaining before the deadline.

When maximum is provided, the result is capped at that value. With no deadline, returns maximum.



106
107
108
109
110
111
112
# File 'lib/little_ghost/run_context.rb', line 106

def remaining_time(maximum = nil)
  check!
  return maximum unless deadline

  remaining = deadline - Time.now
  maximum ? [remaining, maximum].min : remaining
end

#structured_resultObject

Finds the latest validated structured result, if any.



121
122
123
# File 'lib/little_ghost/run_context.rb', line 121

def structured_result
  @structured_result_mutex.synchronize { @structured_result }
end

#submit_structured_result(result) ⇒ Object

Stores a validated LittleGhost::StructuredResult and returns it.



115
116
117
118
# File 'lib/little_ghost/run_context.rb', line 115

def submit_structured_result(result)
  @structured_result_mutex.synchronize { @structured_result = result }
  result
end

#usageObject

Takes a snapshot of accumulated usage.



91
92
93
# File 'lib/little_ghost/run_context.rb', line 91

def usage
  @usage_mutex.synchronize { @usage }
end