active_harness 0.2.18 → 0.2.20

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: 2e8e3cbcdf3101e80ac7872c4249e271e847ce2d925cf63d869e34bf5f08fc3d
4
- data.tar.gz: 73f663a4e25f23c431b35c53a464c440dcccc793fb71844e9df5428c11283c68
3
+ metadata.gz: d1188ca468f31f46c40d118b7e719a849925aa11ddab3a4a87ea4df0fd9bdb84
4
+ data.tar.gz: 1ed36314efd943e6f4e62bbc43b2ec00d0b0486bdbf015e6b590341d240ffcdc
5
5
  SHA512:
6
- metadata.gz: 46a7fd94cc3d98427e466a30daff7baae44a8683ad6fe6235988cad5dbc33bdf8f4170a89d3108f51eb84aeafdfe596e04490a556c01ea8d7bc7603d18894383
7
- data.tar.gz: 669ee179cc1f1e2eae8a5b9a0f0e026fa2752a601d7a9388d5956f065e07ba93b3edc03b770017dd3ba9445c02f2623ae700e18921c67de6534b75883bfb6717
6
+ metadata.gz: 37e82033bddd4069f26619d2bfc5faec831c32618216bedb6cb9ce19ca7fbbaac1057e829809ee15f1d88d1a6fc3b3832cd2c02d1f112c850a974a26205d3727
7
+ data.tar.gz: 3a4fafd8184d55f116689e28c4f411d102f7279d290093c9cc32dafabfc9eafe9aa55abde59a04cec37985e74da94a8ddb14153febce35aa41c2eb0404eed2c6
@@ -71,5 +71,15 @@ module ActiveHarness
71
71
  instance_eval(&hooks[event])
72
72
  end
73
73
  end
74
+
75
+ # Unified internal method: fires the DSL hook AND the external event_stream lambda.
76
+ # Consistent with Tribunal#fire and Pipeline#fire.
77
+ def fire(event, *args)
78
+ result = run_hook(event, *args)
79
+ @event_stream&.call(event, *args)
80
+ result
81
+ rescue IOError, ActionController::Live::ClientDisconnected
82
+ result
83
+ end
74
84
  end
75
85
  end
@@ -49,7 +49,7 @@ module ActiveHarness
49
49
  # puts "Parse failed: #{error.message}"
50
50
  # { "result" => nil, "reason" => "parse error" } # fallback
51
51
  # end
52
- fallback = run_hook(:parse_error, raw, e)
52
+ fallback = fire(:parse_error, raw, e)
53
53
  fallback.nil? ? raise : fallback
54
54
  end
55
55
  end
@@ -22,7 +22,7 @@ module ActiveHarness
22
22
  public :system_prompt
23
23
 
24
24
  def resolve_system_prompt
25
- run_hook(:before_system_prompt)
25
+ fire(:before_system_prompt)
26
26
 
27
27
  sp = @config[:system_prompt]
28
28
 
@@ -45,7 +45,7 @@ module ActiveHarness
45
45
  end
46
46
  end
47
47
 
48
- run_hook(:after_system_prompt, prompt)
48
+ fire(:after_system_prompt, prompt)
49
49
  prompt
50
50
  end
51
51
 
@@ -11,8 +11,8 @@ module ActiveHarness
11
11
  # SupportAgent.call(input: "Hi")
12
12
  # SupportAgent.call(input: "Hi", context: { user_id: 42 })
13
13
  # SupportAgent.call(input: "Hi", memory: memory)
14
- def call(input: nil, context: {}, models: nil, memory: nil, stream: nil, token_stream: nil, event_stream: nil)
15
- new(input: input, context: context, models: models, memory: memory, stream: stream, token_stream: token_stream, event_stream: event_stream).call
14
+ def call(input: nil, context: {}, models: nil, memory: nil, streams: {})
15
+ new(input: input, context: context, models: models, memory: memory, streams: streams).call
16
16
  end
17
17
 
18
18
  # Each subclass gets its own isolated config hash.
@@ -36,8 +36,8 @@ module ActiveHarness
36
36
  # -------------------------------------------------------------------------
37
37
  # Instance API
38
38
  # -------------------------------------------------------------------------
39
- attr_accessor :input, :context, :stream, :token_stream, :event_stream
40
- attr_reader :result
39
+ attr_accessor :input, :context
40
+ attr_reader :result, :token_stream, :event_stream
41
41
 
42
42
  def models=(list)
43
43
  @models_override = Array(list)
@@ -48,18 +48,17 @@ module ActiveHarness
48
48
  @memory = obj
49
49
  end
50
50
 
51
- def initialize(input: nil, context: {}, models: nil, memory: nil, stream: nil, token_stream: nil, event_stream: nil)
51
+ def initialize(input: nil, context: {}, models: nil, memory: nil, streams: {})
52
52
  @input = input
53
53
  @config = self.class.agent_config
54
54
  normalize_input!
55
55
  @context = context
56
56
  @models_override = Array(models) if models
57
- @stream = stream
58
- @token_stream = token_stream
59
- @event_stream = event_stream
57
+ @token_stream = streams[:token]
58
+ @event_stream = streams[:agent]
60
59
  # memory: can be passed directly or via context[:memory]
61
60
  @memory = memory || @context[:memory]
62
- run_hook(:setup)
61
+ fire(:setup)
63
62
  end
64
63
 
65
64
  # Attempts each model in order, returns the first successful Result.
@@ -68,15 +67,18 @@ module ActiveHarness
68
67
  # Optionally accepts input and stream callback inline:
69
68
  # agent.call("What is the capital of Japan?")
70
69
  # agent.call("...", stream: ->(token) { print token })
71
- def call(input = nil, token_stream: nil)
70
+ def call(input = nil, streams: nil)
72
71
  if input
73
72
  @input = input
74
73
  normalize_input!
75
74
  end
76
- @token_stream = token_stream if token_stream
75
+ if streams
76
+ @token_stream = streams[:token] if streams.key?(:token)
77
+ @event_stream = streams[:agent] if streams.key?(:agent)
78
+ end
77
79
  @memory&.load
78
80
  @system_prompt = resolve_system_prompt
79
- run_hook(:before_call)
81
+ fire(:before_call)
80
82
  attempts = []
81
83
 
82
84
  cfg = ActiveHarness.config
@@ -91,22 +93,22 @@ module ActiveHarness
91
93
  elapsed = (Process.clock_gettime(Process::CLOCK_MONOTONIC) - t0).round(3)
92
94
  result = build_result(response, entry, attempts, elapsed)
93
95
  save_to_memory(result)
94
- run_hook(:after_call, result)
96
+ fire(:after_call, result)
95
97
  @result = result
96
98
  return self
97
99
  rescue *RETRYABLE_ERRORS => e
98
100
  elapsed = (Process.clock_gettime(Process::CLOCK_MONOTONIC) - t0).round(3)
99
101
  attempts << attempt_entry(entry, e, elapsed)
100
- run_hook(:retry, entry, e)
102
+ fire(:retry, entry, e)
101
103
  next
102
104
  rescue *STOP_ERRORS => e
103
105
  elapsed = (Process.clock_gettime(Process::CLOCK_MONOTONIC) - t0).round(3)
104
106
  attempts << attempt_entry(entry, e, elapsed)
105
- run_hook(:retry, entry, e)
107
+ fire(:retry, entry, e)
106
108
  raise
107
109
  end
108
110
 
109
- run_hook(:failure, attempts)
111
+ fire(:failure, attempts)
110
112
  raise Errors::AllModelsFailed, "All models failed. Attempts: #{attempts.inspect}"
111
113
  end
112
114
 
@@ -129,23 +129,21 @@ module ActiveHarness
129
129
  @payload = value
130
130
  end
131
131
 
132
- def initialize(input:, context: {}, memory: nil,
133
- stream: nil, agent_event_stream: nil,
134
- tribunal_event_stream: nil, pipeline_event_stream: nil)
135
- @original_input = input
136
- @payload = input
137
- @context = context.dup
138
- @memory = memory
139
- @stream = stream
140
- @agent_event_stream = agent_event_stream
141
- @tribunal_event_stream = tribunal_event_stream
142
- @pipeline_event_stream = pipeline_event_stream
143
- @step_results = {}
144
- @stopped = false
145
- @stopped_at = nil
146
- @stop_reason = nil
147
- @execution_time = nil
148
- @output = nil
132
+ def initialize(input:, context: {}, memory: nil, streams: {})
133
+ @original_input = input
134
+ @payload = input
135
+ @context = context.dup
136
+ @memory = memory
137
+ @token_stream = streams[:token]
138
+ @agent_event_stream = streams[:agent]
139
+ @tribunal_event_stream = streams[:tribunal]
140
+ @pipeline_event_stream = streams[:pipeline]
141
+ @step_results = {}
142
+ @stopped = false
143
+ @stopped_at = nil
144
+ @stop_reason = nil
145
+ @execution_time = nil
146
+ @output = nil
149
147
  end
150
148
 
151
149
  def stopped?
@@ -160,7 +158,7 @@ module ActiveHarness
160
158
  @memory&.load
161
159
 
162
160
  config[:steps].each do |step|
163
- fire_global(:before_step, step.name, @payload, config)
161
+ fire(:before_step, step.name, @payload, config)
164
162
  fire_step(:before_step, step.name, @payload, config)
165
163
 
166
164
  result = execute_step(step)
@@ -169,7 +167,7 @@ module ActiveHarness
169
167
  @context[step.name] = result
170
168
  @payload = result.output if step.transform?
171
169
 
172
- fire_global(:after_step, step.name, result, config)
170
+ fire(:after_step, step.name, result, config)
173
171
  fire_step(:after_step, step.name, result, config)
174
172
 
175
173
  if step.stop_if && step.stop_if.call(result)
@@ -178,6 +176,7 @@ module ActiveHarness
178
176
  @stop_reason = result
179
177
  blk = config[:hooks][:stopped]
180
178
  instance_exec(step.name, result, &blk) if blk
179
+ @pipeline_event_stream&.call(:stopped, step.name, result)
181
180
  break
182
181
  end
183
182
  end
@@ -195,6 +194,7 @@ module ActiveHarness
195
194
  last_result = @step_results[@step_results.keys.last]
196
195
  blk = config[:hooks][:complete]
197
196
  instance_exec(last_result, &blk) if blk
197
+ @pipeline_event_stream&.call(:complete, last_result)
198
198
  end
199
199
 
200
200
  self
@@ -204,32 +204,33 @@ module ActiveHarness
204
204
 
205
205
  def execute_step(step)
206
206
  if step.tribunal?
207
- agent = step.agent_class.new(
208
- input: @payload,
209
- context: @context.dup,
210
- stream: @stream,
211
- agent_event_stream: @agent_event_stream,
212
- tribunal_event_stream: @tribunal_event_stream
213
- )
214
- agent.call
207
+ agent_streams = { token: @token_stream, agent: @agent_event_stream, tribunal: @tribunal_event_stream }.compact
208
+ step.agent_class.new(
209
+ input: @payload,
210
+ context: @context.dup,
211
+ streams: agent_streams
212
+ ).call
215
213
  else
216
- agent = step.agent_class.new(
217
- input: @payload,
218
- context: @context.dup,
219
- stream: @stream,
220
- event_stream: @agent_event_stream
221
- )
222
- agent.call.result
214
+ agent_streams = { token: @token_stream, agent: @agent_event_stream }.compact
215
+ step.agent_class.new(
216
+ input: @payload,
217
+ context: @context.dup,
218
+ streams: agent_streams
219
+ ).call.result
223
220
  end
224
221
  end
225
222
 
226
- # Global hook: receives (step_name, data)
227
- def fire_global(event, step_name, data, config)
223
+ # Fires global hook AND pipeline_event_stream. Consistent with Agent#fire and Tribunal#fire.
224
+ def fire(event, step_name, data, config)
228
225
  blk = config[:hooks][event]
229
226
  instance_exec(step_name, data, &blk) if blk
227
+ @pipeline_event_stream&.call(event, step_name, data)
228
+ rescue IOError, ActionController::Live::ClientDisconnected
229
+ nil
230
230
  end
231
231
 
232
- # Per-step hook: receives (data) only
232
+ # Per-step hook: receives (data) only — not forwarded to pipeline_event_stream
233
+ # (global fire already covers the step event with step_name context).
233
234
  def fire_step(event, step_name, data, config)
234
235
  blk = config[:step_hooks][step_name]&.dig(event)
235
236
  instance_exec(data, &blk) if blk
@@ -46,11 +46,12 @@ module ActiveHarness
46
46
  # -------------------------------------------------------------------------
47
47
  # Instance API
48
48
  # -------------------------------------------------------------------------
49
- attr_accessor :input, :context, :stream, :agent_event_stream, :tribunal_event_stream
50
- attr_reader :results, :errors, :verdict, :execution_time, :agent_execution_times
49
+ attr_accessor :input, :context
50
+ attr_reader :results, :errors, :verdict, :execution_time, :agent_execution_times,
51
+ :token_stream, :agent_event_stream, :tribunal_event_stream
51
52
 
52
53
  def initialize(input: nil, context: {}, agents: nil, timeout: 7,
53
- stream: nil, agent_event_stream: nil, tribunal_event_stream: nil,
54
+ streams: {},
54
55
  may_fail: :_unset)
55
56
  config = self.class.tribunal_config
56
57
 
@@ -63,9 +64,9 @@ module ActiveHarness
63
64
  @evaluate_block = config[:evaluate_block]
64
65
  @may_fail = may_fail == :_unset ? config[:may_fail] : may_fail
65
66
  @hooks = config[:hooks].dup
66
- @stream = stream
67
- @agent_event_stream = agent_event_stream
68
- @tribunal_event_stream = tribunal_event_stream
67
+ @token_stream = streams[:token]
68
+ @agent_event_stream = streams[:agent]
69
+ @tribunal_event_stream = streams[:tribunal]
69
70
  @results = []
70
71
  @errors = []
71
72
  @verdict = nil
@@ -151,18 +152,14 @@ module ActiveHarness
151
152
  end
152
153
 
153
154
  def resolve_agents
155
+ agent_streams = { token: @token_stream, agent: @agent_event_stream }.compact
154
156
  @agents.map do |agent|
155
157
  if agent.is_a?(Class)
156
- agent.new(
157
- input: @input,
158
- context: @context.dup,
159
- stream: @stream,
160
- event_stream: @agent_event_stream
161
- )
158
+ agent.new(input: @input, context: @context.dup, streams: agent_streams)
162
159
  else
163
- agent.input = @input if @input
164
- agent.stream = @stream if @stream
165
- agent.event_stream = @agent_event_stream if @agent_event_stream
160
+ agent.input = @input if @input
161
+ agent.instance_variable_set(:@token_stream, @token_stream) if @token_stream
162
+ agent.instance_variable_set(:@event_stream, @agent_event_stream) if @agent_event_stream
166
163
  agent
167
164
  end
168
165
  end
@@ -29,7 +29,7 @@ require_relative "active_harness/pipeline"
29
29
  require_relative "active_harness/railtie" if defined?(Rails::Railtie)
30
30
 
31
31
  module ActiveHarness
32
- VERSION = "0.2.0"
32
+ VERSION = "0.2.20"
33
33
 
34
34
  class << self
35
35
  # Configure ActiveHarness.
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_harness
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.18
4
+ version: 0.2.20
5
5
  platform: ruby
6
6
  authors:
7
7
  - the-teacher