active_harness 0.2.38 → 0.2.39

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: '064709a78ef949aadd741237f4cdd5a2f36056d211fa2038048a2911fc45a5be'
4
- data.tar.gz: 298318704ce083cf46b87dc5dbaebbae15df127a313685ea32d5c82b03191072
3
+ metadata.gz: 656a10b468592b8e0c53c2c93a8fbf7b8536420f94bd0cb7a61255d432ee78ed
4
+ data.tar.gz: 29de2c821b23ba363ef1596fb545035fbd1191ca6487161f6b9f30b39fe960db
5
5
  SHA512:
6
- metadata.gz: b7c4e5a06e8f8a66e89a0f0b2289c7f461e76cd942a374abb5e6a3845f35e348b8b5fc63182e384d825863ef4c8698c59b64d606d11b5eb27bcada47b1d57642
7
- data.tar.gz: 3528828f40f1a11a0b62b45ff193b49e05cc202e09e754a578e94c372279cdb760622d5e33de3f61aeef62d7cf15166197a4e0d2a5637d0c4cb8e5d5cf332112
6
+ metadata.gz: 9d0d4c4114ed028a603a783841c5205163f1f02203e7c010d5c806a8e673644b391cbfb636bbb1c4d77eb40a807907e63380213b087ab3c5515491c1c2c64fcf
7
+ data.tar.gz: 84b154cb18717e6183337d728a6eb3ec9d34753856d6acc8fbf96c90e06a6e0db799b961b3f8d5bd7dc3984797bed401429a3110573243379eb471fcd84f160b
@@ -1,18 +1,18 @@
1
1
  module ActiveHarness
2
2
  class Pipeline
3
3
  class Step
4
- attr_reader :name, :agent_class
4
+ attr_reader :name, :executor
5
5
 
6
- def initialize(name, agent_class = nil, &block)
7
- @name = name
8
- @agent_class = agent_class
9
- @stop_if = nil
6
+ def initialize(name, executor = nil, &block)
7
+ @name = name
8
+ @executor = executor
9
+ @stop_if = nil
10
10
  instance_eval(&block) if block_given?
11
11
  end
12
12
 
13
- # DSL: use TranslationAgent
13
+ # DSL: use TranslationAgent / SafetyTribunal / NestedPipeline
14
14
  def use(klass)
15
- @agent_class = klass
15
+ @executor = klass
16
16
  end
17
17
 
18
18
  # DSL (inside block): stop_if ->(result) { ... }
@@ -35,9 +35,13 @@ module ActiveHarness
35
35
  @transform_block
36
36
  end
37
37
 
38
- # True if agent_class is a Tribunal subclass — tribunal steps do not update payload.
38
+ def lambda?
39
+ @executor.is_a?(Proc)
40
+ end
41
+
42
+ # True if the executor is a Tribunal subclass — tribunal steps do not update payload.
39
43
  def tribunal?
40
- @agent_class.is_a?(Class) && @agent_class <= ActiveHarness::Tribunal
44
+ @executor.is_a?(Class) && @executor <= ActiveHarness::Tribunal
41
45
  end
42
46
 
43
47
  # Returns true when this step should update the pipeline payload after execution.
@@ -29,7 +29,7 @@ module ActiveHarness
29
29
  # pipeline.call
30
30
  # pipeline.output # => final payload string (nil if stopped)
31
31
  # pipeline.stopped? # => false
32
- # pipeline.step_results # => { translate: <Result>, ... }
32
+ # pipeline.steps { |name, result| ... } # iterate over completed steps
33
33
  #
34
34
  class Pipeline
35
35
  # -------------------------------------------------------------------------
@@ -46,8 +46,8 @@ module ActiveHarness
46
46
  # use InjectionGuardAgent
47
47
  # stop_if ->(result) { result.processed["detected"] == true }
48
48
  # end
49
- def step(name, agent_class = nil, &block)
50
- pipeline_config[:steps] << Pipeline::Step.new(name, agent_class, &block)
49
+ def step(name, executor = nil, &block)
50
+ pipeline_config[:steps] << Pipeline::Step.new(name, executor, &block)
51
51
  end
52
52
 
53
53
  def pipeline_config
@@ -100,7 +100,6 @@ module ActiveHarness
100
100
  :stopped_at,
101
101
  :stop_reason,
102
102
  :execution_time,
103
- :step_results,
104
103
  :context
105
104
  attr_writer :context
106
105
  attr_accessor :params
@@ -126,6 +125,7 @@ module ActiveHarness
126
125
  @token = token
127
126
  class_streams = self.class.pipeline_config[:streams] || {}
128
127
  @stream = merge_stream(stream, class_streams)
128
+ @executors = build_executors
129
129
  @step_results = {}
130
130
  @stopped = false
131
131
  @stopped_at = nil
@@ -134,10 +134,44 @@ module ActiveHarness
134
134
  @output = nil
135
135
  end
136
136
 
137
+ # Returns a hash of pre-created executor instances keyed by step name.
138
+ # Available immediately after new — before call — so instances can be
139
+ # configured (model lists, params, etc.) before execution begins.
140
+ #
141
+ # pipeline = SupportPipeline.new(input: "...")
142
+ # pipeline.executors[:translate].models.prepend(provider: :openai, model: "gpt-4.1")
143
+ # pipeline.executors[:guard].params = { strict: true }
144
+ # pipeline.call
145
+ def executors
146
+ @executors
147
+ end
148
+
137
149
  def stopped?
138
150
  @stopped
139
151
  end
140
152
 
153
+ # Iterates over completed steps as (name, executor, result) tuples.
154
+ # Steps that did not run (pipeline stopped before reaching them) are skipped.
155
+ # Returns an Enumerator when called without a block.
156
+ #
157
+ # pipeline.steps { |name, executor, result| }
158
+ #
159
+ # name — step name symbol (:translate, :guard, …)
160
+ # executor — the instance that ran (TranslationAgent instance, …)
161
+ # result — Result struct (output, processed, usage, model, …)
162
+ #
163
+ # pipeline.steps.map { |name, executor, result| [name, result.output] }
164
+ # pipeline.steps.to_a
165
+ def steps
166
+ return enum_for(:steps) unless block_given?
167
+ self.class.pipeline_config[:steps].each do |step|
168
+ result = @step_results[step.name]
169
+ next unless result
170
+ yield step.name, @executors[step.name], result
171
+ end
172
+ self
173
+ end
174
+
141
175
  # Wraps pipeline outcome into a Result so a pipeline can be used as a step
142
176
  # inside another pipeline, matching the same interface as Agent and Tribunal.
143
177
  #
@@ -153,7 +187,15 @@ module ActiveHarness
153
187
  end
154
188
 
155
189
  # Execute all steps sequentially. Returns self for chaining.
156
- def call
190
+ # Accepts optional input, token, stream to match the Agent/Tribunal call interface.
191
+ def call(input = nil, token: nil, stream: nil)
192
+ if input
193
+ @original_input = input
194
+ @payload = input
195
+ end
196
+ @token = token if token
197
+ @stream = stream if stream
198
+
157
199
  config = self.class.pipeline_config
158
200
  t0 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
159
201
 
@@ -231,13 +273,40 @@ module ActiveHarness
231
273
  end
232
274
 
233
275
  def execute_step(step)
234
- step.agent_class.new(
235
- input: @payload,
236
- context: @context.dup,
237
- params: @params,
238
- token: @token,
239
- stream: @stream
240
- ).call.result
276
+ if step.lambda?
277
+ execute_lambda_step(step)
278
+ else
279
+ instance = @executors[step.name]
280
+ instance.context = @context.dup
281
+ instance.call(@payload).result
282
+ end
283
+ end
284
+
285
+ def execute_lambda_step(step)
286
+ lam = step.executor
287
+ has_kw = lam.parameters.any? { |type, _| [:key, :keyreq, :keyrest].include?(type) }
288
+ result = has_kw ? lam.call(@payload, context: @context.dup, params: @params) : lam.call(@payload)
289
+
290
+ unless result.is_a?(Result)
291
+ raise ArgumentError,
292
+ "Lambda step :#{step.name} must return ActiveHarness::Result, got #{result.class}"
293
+ end
294
+
295
+ result
296
+ end
297
+
298
+ def build_executors
299
+ self.class.pipeline_config[:steps].each_with_object({}) do |step, hash|
300
+ next if step.lambda?
301
+
302
+ hash[step.name] = step.executor.new(
303
+ input: @payload,
304
+ context: @context.dup,
305
+ params: @params,
306
+ token: @token,
307
+ stream: @stream
308
+ )
309
+ end
241
310
  end
242
311
  end
243
312
  end
@@ -105,11 +105,17 @@ module ActiveHarness
105
105
  # Run all agents in parallel, then compute the verdict.
106
106
  # Returns self so calls can be chained: tribunal.call.verdict
107
107
  #
108
+ # Accepts an optional input to update payload before running — matches
109
+ # the Agent#call(input) interface so tribunals work as pipeline executors.
110
+ #
108
111
  # Behaviour on failure:
109
112
  # - If some agents fail/timeout, their errors are in #errors and
110
113
  # #results contains only successful results.
111
114
  # - If ALL agents fail/timeout, raises Errors::AllAgentsFailed.
112
- def call
115
+ def call(input = nil, token: nil, stream: nil)
116
+ @input = input if input
117
+ @token = token if token
118
+ @stream = stream if stream
113
119
  agents = resolve_agents
114
120
  fire(:before_call)
115
121
 
@@ -32,7 +32,7 @@ require_relative "active_harness/pipeline"
32
32
  require_relative "active_harness/railtie" if defined?(Rails::Railtie)
33
33
 
34
34
  module ActiveHarness
35
- VERSION = "0.2.38"
35
+ VERSION = "0.2.39"
36
36
 
37
37
  class << self
38
38
  # 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.38
4
+ version: 0.2.39
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-14 00:00:00.000000000 Z
11
+ date: 2026-06-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby