active_harness 0.2.35 → 0.2.36

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f08d4dd9d2254cb4895919e690fd485cab05d2b89c53786383b0da353aeb5f82
4
- data.tar.gz: 1aa9a3e3a7cd8a2e83179b88a9f3623f39b4fa59e7386d43094de2739f44d50a
3
+ metadata.gz: efbaa37cda4e529d17ba8e6e4757b68f93e08d9f6ffa96b807d29e5f43e7e303
4
+ data.tar.gz: 7bce0b41461aac9449d4fc31eea1d1f0f0869b31b6304efb8e520707f6ce084e
5
5
  SHA512:
6
- metadata.gz: 1aa38c722c75fbc04d6d389ebe6d260322eddd1a4d6c5bb9a43117b2b341447bf1883f90beacdcf5c2bc99adc6867a2dc7ee820f3dcd85d44538c5d148953a92
7
- data.tar.gz: 146bf3d9516d70dcc45c2b092855b2b6b01543dd3b76e45de250b2b91fecce700cbabf7f492ebbf2e7417dfca22a48c21b2247a361b83eb28a81b001a3ce5999
6
+ metadata.gz: 810a655970645b7b564fa3d1a8ada69512fc12251861e8f340505d6264cc473d95596c0b466aafa8b08d31fbbb977023d2515fadb9a7e7e4775d1aacffd24b07
7
+ data.tar.gz: 0f6300b25ec6d4448cde85185f553bd2c8a9ccbbb386f6b6136d8c4a40f23291b734f331003734a1237c74d9bb506e1a1d434f8a15c006bf1080359cd55e486d
@@ -54,8 +54,8 @@ module ActiveHarness
54
54
  chat = backend.call(params)
55
55
  chat.with_instructions(system_prompt) if system_prompt
56
56
 
57
- if @token_stream
58
- response = chat.ask(@input) { |chunk| @token_stream.call(chunk.content) if chunk.content }
57
+ if @token
58
+ response = chat.ask(@input) { |chunk| @token.call(chunk.content) if chunk.content }
59
59
  else
60
60
  response = chat.ask(@input)
61
61
  end
@@ -67,11 +67,9 @@ module ActiveHarness
67
67
  run_hooks(@config[:hooks] || {}, event, *args)
68
68
  end
69
69
 
70
- # Unified internal method: fires the DSL hook AND the external event_stream lambda.
71
- # Consistent with Tribunal#fire and Pipeline#fire.
72
70
  def fire(event, *args)
73
71
  run_hook(event, *args)
74
- @event_stream&.call(event, *args)
72
+ @stream&.call(:agent, event, *args)
75
73
  rescue IOError, ActionController::Live::ClientDisconnected
76
74
  end
77
75
  end
@@ -53,7 +53,7 @@ module ActiveHarness
53
53
  messages = build_messages(system_prompt, @input)
54
54
  opts = { model: entry[:model], messages: messages }
55
55
  opts[:temperature] = entry[:temperature] if entry[:temperature]
56
- opts[:stream] = @token_stream if @token_stream
56
+ opts[:stream] = @token if @token
57
57
  opts[:name] = entry[:name] if entry[:name]
58
58
  provider.call(**opts)
59
59
  end
@@ -16,7 +16,8 @@ module ActiveHarness
16
16
  params: {},
17
17
  memory: nil,
18
18
  models: nil,
19
- streams: {}
19
+ token: nil,
20
+ stream: nil
20
21
  )
21
22
  new(
22
23
  input: input,
@@ -24,7 +25,8 @@ module ActiveHarness
24
25
  params: params,
25
26
  memory: memory,
26
27
  models: models,
27
- streams: streams
28
+ token: token,
29
+ stream: stream
28
30
  ).call
29
31
  end
30
32
 
@@ -54,8 +56,8 @@ module ActiveHarness
54
56
  :params,
55
57
  :memory
56
58
  attr_reader :result,
57
- :token_stream,
58
- :event_stream
59
+ :token,
60
+ :stream
59
61
 
60
62
  def models=(list)
61
63
  @models_override = Array(list)
@@ -68,7 +70,8 @@ module ActiveHarness
68
70
  params: {},
69
71
  memory: nil,
70
72
  models: nil,
71
- streams: {}
73
+ token: nil,
74
+ stream: nil
72
75
  )
73
76
  @input = input
74
77
  @config = self.class.agent_config
@@ -77,8 +80,8 @@ module ActiveHarness
77
80
  @params = params
78
81
  @memory = memory
79
82
  @models_override = Array(models) if models
80
- @token_stream = streams[:token]
81
- @event_stream = streams[:agent]
83
+ @token = token
84
+ @stream = stream
82
85
  fire(:setup)
83
86
  end
84
87
 
@@ -88,15 +91,13 @@ module ActiveHarness
88
91
  # Optionally accepts input and stream callback inline:
89
92
  # agent.call("What is the capital of Japan?")
90
93
  # agent.call("...", stream: ->(token) { print token })
91
- def call(input = nil, streams: nil)
94
+ def call(input = nil, token: nil, stream: nil)
92
95
  if input
93
96
  @input = input
94
97
  normalize_input!
95
98
  end
96
- if streams
97
- @token_stream = streams[:token] if streams.key?(:token)
98
- @event_stream = streams[:agent] if streams.key?(:agent)
99
- end
99
+ @token = token if token
100
+ @stream = stream if stream
100
101
  fire(:before_call)
101
102
  @system_prompt = resolve_system_prompt
102
103
  attempts = []
@@ -60,15 +60,14 @@ module ActiveHarness
60
60
 
61
61
  private
62
62
 
63
- # Fires global hook AND pipeline_event_stream. Consistent with Agent#fire and Tribunal#fire.
64
63
  def fire(event, step_name, data, config)
65
64
  run_hooks(config[:hooks], event, step_name, data)
66
- @pipeline_event_stream&.call(event, step_name, data)
65
+ @stream&.call(:pipeline, event, step_name, data)
67
66
  rescue IOError, ActionController::Live::ClientDisconnected
68
67
  nil
69
68
  end
70
69
 
71
- # Per-step hook: receives (data) only — not forwarded to pipeline_event_stream
70
+ # Per-step hook: receives (data) only — not forwarded to stream
72
71
  # (global fire already covers the step event with step_name context).
73
72
  def fire_step(event, step_name, data, config)
74
73
  run_hooks(config[:step_hooks][step_name] || {}, event, data)
@@ -66,8 +66,7 @@ module ActiveHarness
66
66
  # any agent or tribunal executed within this pipeline (including agents
67
67
  # running inside tribunals). Multiple blocks can be registered; all fire.
68
68
  #
69
- # The handler receives the same (event, *args) signature that the runtime
70
- # streams: { agent: lambda } would receive.
69
+ # The handler receives (event, *args) already scoped to the source.
71
70
  #
72
71
  # on_agent_event do |event, result|
73
72
  # Rails.logger.info "[Agent #{event}] #{result.model}" if event == :after_call
@@ -116,24 +115,23 @@ module ActiveHarness
116
115
  context: {},
117
116
  params: {},
118
117
  memory: nil,
119
- streams: {}
118
+ token: nil,
119
+ stream: nil
120
120
  )
121
- @original_input = input
122
- @payload = input
123
- @context = context.dup
124
- @params = params
125
- @memory = memory
126
- @token_stream = streams[:token]
127
- class_streams = self.class.pipeline_config[:streams] || {}
128
- @agent_event_stream = merge_stream(streams[:agent], class_streams[:agent])
129
- @tribunal_event_stream = merge_stream(streams[:tribunal], class_streams[:tribunal])
130
- @pipeline_event_stream = merge_stream(streams[:pipeline], class_streams[:pipeline])
131
- @step_results = {}
132
- @stopped = false
133
- @stopped_at = nil
134
- @stop_reason = nil
135
- @execution_time = nil
136
- @output = nil
121
+ @original_input = input
122
+ @payload = input
123
+ @context = context.dup
124
+ @params = params
125
+ @memory = memory
126
+ @token = token
127
+ class_streams = self.class.pipeline_config[:streams] || {}
128
+ @stream = merge_stream(stream, class_streams)
129
+ @step_results = {}
130
+ @stopped = false
131
+ @stopped_at = nil
132
+ @stop_reason = nil
133
+ @execution_time = nil
134
+ @output = nil
137
135
  end
138
136
 
139
137
  def stopped?
@@ -179,7 +177,7 @@ module ActiveHarness
179
177
  @stopped_at = step.name
180
178
  @stop_reason = result
181
179
  run_hooks(config[:hooks], :stopped, step.name, result)
182
- @pipeline_event_stream&.call(:stopped, step.name, result)
180
+ @stream&.call(:pipeline, :stopped, step.name, result)
183
181
  break
184
182
  end
185
183
  end
@@ -196,7 +194,7 @@ module ActiveHarness
196
194
 
197
195
  last_result = @step_results[@step_results.keys.last]
198
196
  run_hooks(config[:hooks], :complete, last_result)
199
- @pipeline_event_stream&.call(:complete, last_result)
197
+ @stream&.call(:pipeline, :complete, last_result)
200
198
  end
201
199
 
202
200
  self
@@ -204,37 +202,41 @@ module ActiveHarness
204
202
 
205
203
  private
206
204
 
207
- # Combines a runtime-passed stream lambda with zero or more class-level handler
208
- # blocks registered via on_agent_event / on_tribunal_event / on_pipeline_event.
209
- # Returns nil when there are no handlers at all, preserving the existing
210
- # "no stream" fast path in agents and tribunals.
205
+ # Combines a runtime-passed stream lambda with class-level handler blocks
206
+ # registered via on_agent_event / on_tribunal_event / on_pipeline_event.
207
+ # Returns nil when there are no handlers at all.
211
208
  #
212
- # Each class-level handler is evaluated via instance_exec so that blocks
213
- # written in the pipeline class body can access pipeline instance variables
214
- # (e.g. @otel_pipeline_span, @params) and call pipeline instance methods.
209
+ # Class-level handlers receive (event, *args) already scoped to source.
210
+ # Runtime lambda receives (source, event, *args).
211
+ # instance_exec lets class-level blocks access pipeline instance variables.
215
212
  def merge_stream(passed_in, class_handlers)
216
- class_handlers = Array(class_handlers).compact
217
- return passed_in if class_handlers.empty?
213
+ agent_handlers = Array(class_handlers[:agent]).compact
214
+ tribunal_handlers = Array(class_handlers[:tribunal]).compact
215
+ pipeline_handlers = Array(class_handlers[:pipeline]).compact
216
+
217
+ has_class_handlers = agent_handlers.any? || tribunal_handlers.any? || pipeline_handlers.any?
218
+ return passed_in unless has_class_handlers
218
219
 
219
220
  pipeline_instance = self
220
- ->(event, *args) {
221
- class_handlers.each { |h| pipeline_instance.instance_exec(event, *args, &h) }
222
- passed_in&.call(event, *args)
221
+ ->(source, event, *args) {
222
+ handlers = case source
223
+ when :agent then agent_handlers
224
+ when :tribunal then tribunal_handlers
225
+ when :pipeline then pipeline_handlers
226
+ else []
227
+ end
228
+ handlers.each { |h| pipeline_instance.instance_exec(event, *args, &h) }
229
+ passed_in&.call(source, event, *args)
223
230
  }
224
231
  end
225
232
 
226
233
  def execute_step(step)
227
- streams = {
228
- token: @token_stream,
229
- agent: @agent_event_stream,
230
- tribunal: @tribunal_event_stream,
231
- pipeline: @pipeline_event_stream
232
- }.compact
233
234
  step.agent_class.new(
234
235
  input: @payload,
235
236
  context: @context.dup,
236
237
  params: @params,
237
- streams: streams
238
+ token: @token,
239
+ stream: @stream
238
240
  ).call.result
239
241
  end
240
242
  end
@@ -72,10 +72,9 @@ module ActiveHarness
72
72
  run_hooks(@hooks, event, *args)
73
73
  end
74
74
 
75
- # Fire the DSL-registered hook AND the external tribunal_event_stream lambda (if set).
76
75
  def fire(event, *args)
77
76
  run_hook(event, *args)
78
- @tribunal_event_stream&.call(event, *args)
77
+ @stream&.call(:tribunal, event, *args)
79
78
  rescue IOError, ActionController::Live::ClientDisconnected
80
79
  nil
81
80
  end
@@ -55,9 +55,8 @@ module ActiveHarness
55
55
  :verdict,
56
56
  :execution_time,
57
57
  :agent_execution_times,
58
- :token_stream,
59
- :agent_event_stream,
60
- :tribunal_event_stream
58
+ :token,
59
+ :stream
61
60
 
62
61
  def initialize(
63
62
  input: nil,
@@ -66,7 +65,8 @@ module ActiveHarness
66
65
  memory: nil,
67
66
  agents: nil,
68
67
  timeout: 7,
69
- streams: {},
68
+ token: nil,
69
+ stream: nil,
70
70
  may_fail: :_unset
71
71
  )
72
72
  config = self.class.tribunal_config
@@ -82,9 +82,8 @@ module ActiveHarness
82
82
  @evaluate_block = config[:evaluate_block]
83
83
  @may_fail = may_fail == :_unset ? config[:may_fail] : may_fail
84
84
  @hooks = config[:hooks].transform_values { |v| Array(v).dup }
85
- @token_stream = streams[:token]
86
- @agent_event_stream = streams[:agent]
87
- @tribunal_event_stream = streams[:tribunal]
85
+ @token = token
86
+ @stream = stream
88
87
  @results = []
89
88
  @errors = []
90
89
  @verdict = nil
@@ -181,14 +180,13 @@ module ActiveHarness
181
180
  end
182
181
 
183
182
  def resolve_agents
184
- agent_streams = { token: @token_stream, agent: @agent_event_stream }.compact
185
183
  @agents.map do |agent|
186
184
  if agent.is_a?(Class)
187
- agent.new(input: @input, context: @context.dup, params: @params, streams: agent_streams)
185
+ agent.new(input: @input, context: @context.dup, params: @params, token: @token, stream: @stream)
188
186
  else
189
187
  agent.input = @input if @input
190
- agent.instance_variable_set(:@token_stream, @token_stream) if @token_stream
191
- agent.instance_variable_set(:@event_stream, @agent_event_stream) if @agent_event_stream
188
+ agent.instance_variable_set(:@token, @token) if @token
189
+ agent.instance_variable_set(:@stream, @stream) if @stream
192
190
  agent
193
191
  end
194
192
  end
@@ -34,7 +34,7 @@ require_relative "active_harness/pipeline"
34
34
  require_relative "active_harness/railtie" if defined?(Rails::Railtie)
35
35
 
36
36
  module ActiveHarness
37
- VERSION = "0.2.35"
37
+ VERSION = "0.2.36"
38
38
 
39
39
  class << self
40
40
  # Configure ActiveHarness.
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_harness
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.35
4
+ version: 0.2.36
5
5
  platform: ruby
6
6
  authors:
7
7
  - the-teacher
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-06-13 00:00:00.000000000 Z
11
+ date: 2026-06-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby