active_harness 0.2.16 → 0.2.18

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: 26824b777c1b0a68676671e07725a4ec171bb1491cf306b098aa97c6d4cf5510
4
- data.tar.gz: bd0ca2f2fb3fa813eeea21bd48e49435babd989b761e5ea4a6dda194218a27ae
3
+ metadata.gz: 2e8e3cbcdf3101e80ac7872c4249e271e847ce2d925cf63d869e34bf5f08fc3d
4
+ data.tar.gz: 73f663a4e25f23c431b35c53a464c440dcccc793fb71844e9df5428c11283c68
5
5
  SHA512:
6
- metadata.gz: b51494c59af3664360e3a0cdaa650a309499bada4df3a10f46bb1c4f547e577276f27bcdfa2c5ebbe2e88a220245feca1c51aba018ecee495cb15d72c5a076bb
7
- data.tar.gz: e770331ab6e5621fc5f55257bd736d24eb972dbcd800075113971665c652e3d553505488ae8dec58d184c1f8cc5fc971146861d7e264cd4e291c4d18ccd694ae
6
+ metadata.gz: 46a7fd94cc3d98427e466a30daff7baae44a8683ad6fe6235988cad5dbc33bdf8f4170a89d3108f51eb84aeafdfe596e04490a556c01ea8d7bc7603d18894383
7
+ data.tar.gz: 669ee179cc1f1e2eae8a5b9a0f0e026fa2752a601d7a9388d5956f065e07ba93b3edc03b770017dd3ba9445c02f2623ae700e18921c67de6534b75883bfb6717
@@ -1,14 +1,14 @@
1
1
  module ActiveHarness
2
2
  class Agent
3
3
  # -------------------------------------------------------------------------
4
- # RubyLLM backend DSL
4
+ # Custom LLM backend DSL
5
5
  #
6
- # Allows an agent to delegate HTTP calls to the `ruby_llm` gem instead of
7
- # ActiveHarness's built-in Net::HTTP providers.
6
+ # Allows an agent to delegate HTTP calls to any LLM client (e.g. `ruby_llm`)
7
+ # instead of ActiveHarness's built-in Net::HTTP providers.
8
8
  #
9
9
  # Usage:
10
10
  #
11
- # ruby_llm_backend do |params|
11
+ # custom_llm_backend do |params|
12
12
  # RubyLLM.chat(
13
13
  # model: params.model,
14
14
  # provider: params.provider,
@@ -27,23 +27,23 @@ module ActiveHarness
27
27
  # - streaming via stream: lambda
28
28
  # -------------------------------------------------------------------------
29
29
 
30
- # Passed to the ruby_llm_backend block for each model attempt.
30
+ # Passed to the custom_llm_backend block for each model attempt.
31
31
  BackendParams = Struct.new(:model, :provider, :temperature, keyword_init: true)
32
32
 
33
33
  class << self
34
- # Define the RubyLLM backend block for this agent class.
35
- def ruby_llm_backend(&block)
36
- agent_config[:ruby_llm_backend] = block
34
+ # Define the custom LLM backend block for this agent class.
35
+ def custom_llm_backend(&block)
36
+ agent_config[:custom_llm_backend] = block
37
37
  end
38
38
  end
39
39
 
40
40
  private
41
41
 
42
- # Called from attempt_model when ruby_llm_backend is configured.
43
- def attempt_via_ruby_llm(entry, system_prompt)
42
+ # Called from attempt_model when custom_llm_backend is configured.
43
+ def attempt_via_custom_llm(entry, system_prompt)
44
44
  require "ruby_llm"
45
45
 
46
- backend = @config[:ruby_llm_backend]
46
+ backend = @config[:custom_llm_backend]
47
47
 
48
48
  params = BackendParams.new(
49
49
  model: entry[:model],
@@ -60,7 +60,7 @@ module ActiveHarness
60
60
  response = chat.ask(@input)
61
61
  end
62
62
 
63
- { content: response.content, usage: ruby_llm_usage(response) }
63
+ { content: response.content, usage: custom_llm_usage(response) }
64
64
  rescue ::RubyLLM::UnauthorizedError => e
65
65
  raise Errors::InvalidApiKeyError, e.message
66
66
  rescue ::RubyLLM::RateLimitError, ::RubyLLM::OverloadedError => e
@@ -76,7 +76,7 @@ module ActiveHarness
76
76
  "The `ruby_llm` gem is required. Add `gem \"ruby_llm\"` to your Gemfile."
77
77
  end
78
78
 
79
- def ruby_llm_usage(response)
79
+ def custom_llm_usage(response)
80
80
  t = response.tokens
81
81
  return nil unless t
82
82
 
@@ -41,7 +41,7 @@ module ActiveHarness
41
41
  private
42
42
 
43
43
  def attempt_model(entry, system_prompt)
44
- return attempt_via_ruby_llm(entry, system_prompt) if @config[:ruby_llm_backend]
44
+ return attempt_via_custom_llm(entry, system_prompt) if @config[:custom_llm_backend]
45
45
 
46
46
  provider = resolve_provider(entry[:provider])
47
47
  messages = build_messages(system_prompt, @input)
@@ -170,6 +170,6 @@ require_relative "agent/hooks"
170
170
  require_relative "agent/models"
171
171
  require_relative "agent/providers"
172
172
  require_relative "agent/output_parser"
173
- require_relative "agent/ruby_llm_backend"
173
+ require_relative "agent/custom_llm_backend"
174
174
  require_relative "agent/cost"
175
175
 
@@ -129,17 +129,23 @@ module ActiveHarness
129
129
  @payload = value
130
130
  end
131
131
 
132
- def initialize(input:, context: {}, memory: nil)
133
- @original_input = input
134
- @payload = input
135
- @context = context.dup
136
- @memory = memory
137
- @step_results = {}
138
- @stopped = false
139
- @stopped_at = nil
140
- @stop_reason = nil
141
- @execution_time = nil
142
- @output = nil
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
143
149
  end
144
150
 
145
151
  def stopped?
@@ -170,7 +176,8 @@ module ActiveHarness
170
176
  @stopped = true
171
177
  @stopped_at = step.name
172
178
  @stop_reason = result
173
- config[:hooks][:stopped]&.call(step.name, result)
179
+ blk = config[:hooks][:stopped]
180
+ instance_exec(step.name, result, &blk) if blk
174
181
  break
175
182
  end
176
183
  end
@@ -186,7 +193,8 @@ module ActiveHarness
186
193
  )
187
194
 
188
195
  last_result = @step_results[@step_results.keys.last]
189
- config[:hooks][:complete]&.call(last_result)
196
+ blk = config[:hooks][:complete]
197
+ instance_exec(last_result, &blk) if blk
190
198
  end
191
199
 
192
200
  self
@@ -195,23 +203,36 @@ module ActiveHarness
195
203
  private
196
204
 
197
205
  def execute_step(step)
198
- agent = step.agent_class.new(input: @payload, context: @context.dup)
199
- if agent.is_a?(ActiveHarness::Agent)
200
- agent.call.result
201
- else
202
- # Tribunal — call returns self, expose via .itself
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
+ )
203
214
  agent.call
215
+ 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
204
223
  end
205
224
  end
206
225
 
207
226
  # Global hook: receives (step_name, data)
208
227
  def fire_global(event, step_name, data, config)
209
- config[:hooks][event]&.call(step_name, data)
228
+ blk = config[:hooks][event]
229
+ instance_exec(step_name, data, &blk) if blk
210
230
  end
211
231
 
212
232
  # Per-step hook: receives (data) only
213
233
  def fire_step(event, step_name, data, config)
214
- config[:step_hooks][step_name]&.dig(event)&.call(data)
234
+ blk = config[:step_hooks][step_name]&.dig(event)
235
+ instance_exec(data, &blk) if blk
215
236
  end
216
237
  end
217
238
  end
@@ -76,6 +76,14 @@ module ActiveHarness
76
76
  end
77
77
  end
78
78
 
79
+ # Fire the DSL-registered hook AND the external tribunal_event_stream lambda (if set).
80
+ def fire(event, *args)
81
+ run_hook(event, *args)
82
+ @tribunal_event_stream&.call(event, *args)
83
+ rescue IOError, ActionController::Live::ClientDisconnected
84
+ nil
85
+ end
86
+
79
87
  # Like run_hook but uses the return value to replace the passed value.
80
88
  # Used by :before_verdict to allow results transformation before verdict computation.
81
89
  def transform_hook(event, value)
@@ -82,12 +82,12 @@ module ActiveHarness
82
82
  # - If ALL agents fail/timeout, raises Errors::AllAgentsFailed.
83
83
  def call
84
84
  agents = resolve_agents
85
- run_hook(:before_call)
85
+ fire(:before_call)
86
86
 
87
87
  started_at = Process.clock_gettime(Process::CLOCK_MONOTONIC)
88
88
 
89
89
  futures = agents.each_with_index.map do |agent, index|
90
- run_hook(:before_agent, agent, index)
90
+ fire(:before_agent, agent, index)
91
91
  t0 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
92
92
  future = Concurrent::Future.execute { agent.call }
93
93
  [future, t0]
@@ -106,22 +106,22 @@ module ActiveHarness
106
106
  value = future.value
107
107
  result = value.is_a?(ActiveHarness::Agent) ? value.result : value
108
108
  @results << result
109
- run_hook(:after_agent, result, index)
109
+ fire(:after_agent, result, index)
110
110
  elsif future.incomplete?
111
111
  error = Errors::TimeoutError.new(
112
112
  "Agent #{agents[index].class.name} timed out after #{@timeout}s"
113
113
  )
114
114
  @errors << { agent: agents[index].class.name, error: error }
115
- run_hook(:agent_error, agents[index].class.name, error, index)
115
+ fire(:agent_error, agents[index].class.name, error, index)
116
116
  else
117
117
  @errors << { agent: agents[index].class.name, error: future.reason }
118
- run_hook(:agent_error, agents[index].class.name, future.reason, index)
118
+ fire(:agent_error, agents[index].class.name, future.reason, index)
119
119
  end
120
120
  end
121
121
 
122
122
  @execution_time = (Process.clock_gettime(Process::CLOCK_MONOTONIC) - started_at).round(3)
123
123
 
124
- run_hook(:after_call, @results, @errors)
124
+ fire(:after_call, @results, @errors)
125
125
 
126
126
  # If all agents failed, raise an exception.
127
127
  # Otherwise, compute the verdict based on successful results.
@@ -129,8 +129,8 @@ module ActiveHarness
129
129
 
130
130
  verdict_input = transform_hook(:before_verdict, @results)
131
131
  @verdict = compute_verdict(verdict_input)
132
-
133
- run_hook(:after_verdict, @verdict)
132
+
133
+ fire(:after_verdict, @verdict)
134
134
 
135
135
  self
136
136
  end
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.16
4
+ version: 0.2.18
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-05-25 00:00:00.000000000 Z
11
+ date: 2026-05-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
@@ -34,12 +34,12 @@ files:
34
34
  - lib/active_harness.rb
35
35
  - lib/active_harness/agent.rb
36
36
  - lib/active_harness/agent/cost.rb
37
+ - lib/active_harness/agent/custom_llm_backend.rb
37
38
  - lib/active_harness/agent/hooks.rb
38
39
  - lib/active_harness/agent/models.rb
39
40
  - lib/active_harness/agent/output_parser.rb
40
41
  - lib/active_harness/agent/prompt.rb
41
42
  - lib/active_harness/agent/providers.rb
42
- - lib/active_harness/agent/ruby_llm_backend.rb
43
43
  - lib/active_harness/configuration.rb
44
44
  - lib/active_harness/core/errors.rb
45
45
  - lib/active_harness/costs.rb