active_harness 0.2.19 → 0.2.21

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: 9562f506c8c0b6a219b085fc33a123a39538f368d5fa0255400cc5632b3395f2
4
- data.tar.gz: c23b9fddc6356c71b37495fce0654d1777314d96ca43001b41c1e97f3b59c8d8
3
+ metadata.gz: de49ac66334a5feb91a5d5f0314ab2b9b3460a5d3a549f08b77538677eae619b
4
+ data.tar.gz: 0bffb6e77d1b8def25121b144dbc956c2c1b0c684826910e3dd0a66ab595a73e
5
5
  SHA512:
6
- metadata.gz: 6def9d9a139113f948c7da6acd1ed0f99e88257c273b3434d236829b700d156e6feeda60ac7fbadef183840ca76e9e12b08b70f40b9d2bdb634f14a4cee19ce7
7
- data.tar.gz: 76e1a91a7cbd321e56900562b9c471d7313a0553d2a72764000768e7e7c4fcdd0ecc09d321ebb95f3cc43335f66ad81d11d7d354fdc8204bab48d404a969710c
6
+ metadata.gz: e51c5ab8c3911d2597f6269f695c2f4992f960d771b4284fdd27121939aca14dfea53e568ecbfc069af26b405725106c18b3beebf0232e751ed8144b3134cea7
7
+ data.tar.gz: ea95a9f48993b4dc930addeee4260e6056815ec48d3c2534a6bf6f5001168cad2104fcd988c98e442f940f32faa4161f9b59a4692bc6fa3adf7655a221d5d56b
@@ -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
 
@@ -0,0 +1,77 @@
1
+ module ActiveHarness
2
+ class Pipeline
3
+ VALID_HOOKS = %i[before_step after_step stopped complete].freeze
4
+ VALID_STEP_HOOKS = %i[before_step after_step].freeze
5
+
6
+ class << self
7
+ # Register a global or per-step hook.
8
+ #
9
+ # Global hooks fire on every step:
10
+ # on :before_step do |step_name, payload| ... end
11
+ # on :after_step do |step_name, result| ... end
12
+ # on :stopped do |step_name, result| ... end
13
+ # on :complete do |last_result| ... end
14
+ #
15
+ # Per-step hooks fire only for the named step (no step_name passed):
16
+ # on :before_step, :translate do |payload| ... end
17
+ # on :after_step, :translate do |result| ... end
18
+ def on(event, step_name = nil, &block)
19
+ if step_name
20
+ unless VALID_STEP_HOOKS.include?(event)
21
+ raise ArgumentError,
22
+ "Per-step hooks support: #{VALID_STEP_HOOKS.join(", ")}. Got :#{event}"
23
+ end
24
+ pipeline_config[:step_hooks][step_name] ||= {}
25
+ pipeline_config[:step_hooks][step_name][event] = block
26
+ else
27
+ unless VALID_HOOKS.include?(event)
28
+ raise ArgumentError,
29
+ "Unknown Pipeline hook :#{event}. Valid: #{VALID_HOOKS.join(", ")}"
30
+ end
31
+ pipeline_config[:hooks][event] = block
32
+ end
33
+ end
34
+
35
+ # Rails-style aliases for +on+:
36
+ #
37
+ # Global:
38
+ # before :step do |name, payload| end # → on :before_step
39
+ # after :step do |name, result| end # → on :after_step
40
+ # callback :stopped do |name, result| end # → on :stopped
41
+ # callback :complete do |result| end # → on :complete
42
+ #
43
+ # Per-step:
44
+ # after :step, :translate do |result| end
45
+ # before :step, :translate do |payload| end
46
+ def before(event, step_name = nil, &block)
47
+ on(:"before_#{event}", step_name, &block)
48
+ end
49
+
50
+ def after(event, step_name = nil, &block)
51
+ on(:"after_#{event}", step_name, &block)
52
+ end
53
+
54
+ def callback(event, &block)
55
+ on(event, &block)
56
+ end
57
+ end
58
+
59
+ private
60
+
61
+ # Fires global hook AND pipeline_event_stream. Consistent with Agent#fire and Tribunal#fire.
62
+ def fire(event, step_name, data, config)
63
+ blk = config[:hooks][event]
64
+ instance_exec(step_name, data, &blk) if blk
65
+ @pipeline_event_stream&.call(event, step_name, data)
66
+ rescue IOError, ActionController::Live::ClientDisconnected
67
+ nil
68
+ end
69
+
70
+ # Per-step hook: receives (data) only — not forwarded to pipeline_event_stream
71
+ # (global fire already covers the step event with step_name context).
72
+ def fire_step(event, step_name, data, config)
73
+ blk = config[:step_hooks][step_name]&.dig(event)
74
+ instance_exec(data, &blk) if blk
75
+ end
76
+ end
77
+ end
@@ -32,9 +32,6 @@ module ActiveHarness
32
32
  # pipeline.step_results # => { translate: <Result>, ... }
33
33
  #
34
34
  class Pipeline
35
- VALID_HOOKS = %i[before_step after_step stopped complete].freeze
36
- VALID_STEP_HOOKS = %i[before_step after_step].freeze
37
-
38
35
  # -------------------------------------------------------------------------
39
36
  # Class-level DSL
40
37
  # -------------------------------------------------------------------------
@@ -53,57 +50,6 @@ module ActiveHarness
53
50
  pipeline_config[:steps] << Pipeline::Step.new(name, agent_class, &block)
54
51
  end
55
52
 
56
- # Register a global or per-step hook.
57
- #
58
- # Global hooks fire on every step:
59
- # on :before_step do |step_name, payload| ... end
60
- # on :after_step do |step_name, result| ... end
61
- # on :stopped do |step_name, result| ... end
62
- # on :complete do |last_result| ... end
63
- #
64
- # Per-step hooks fire only for the named step (no step_name passed):
65
- # on :before_step, :translate do |payload| ... end
66
- # on :after_step, :translate do |result| ... end
67
- def on(event, step_name = nil, &block)
68
- if step_name
69
- unless VALID_STEP_HOOKS.include?(event)
70
- raise ArgumentError,
71
- "Per-step hooks support: #{VALID_STEP_HOOKS.join(", ")}. Got :#{event}"
72
- end
73
- pipeline_config[:step_hooks][step_name] ||= {}
74
- pipeline_config[:step_hooks][step_name][event] = block
75
- else
76
- unless VALID_HOOKS.include?(event)
77
- raise ArgumentError,
78
- "Unknown Pipeline hook :#{event}. Valid: #{VALID_HOOKS.join(", ")}"
79
- end
80
- pipeline_config[:hooks][event] = block
81
- end
82
- end
83
-
84
- # Rails-style aliases for +on+:
85
- #
86
- # Global:
87
- # before :step do |name, payload| end # → on :before_step
88
- # after :step do |name, result| end # → on :after_step
89
- # callback :stopped do |name, result| end # → on :stopped
90
- # callback :complete do |result| end # → on :complete
91
- #
92
- # Per-step:
93
- # after :step, :translate do |result| end
94
- # before :step, :translate do |payload| end
95
- def before(event, step_name = nil, &block)
96
- on(:"before_#{event}", step_name, &block)
97
- end
98
-
99
- def after(event, step_name = nil, &block)
100
- on(:"after_#{event}", step_name, &block)
101
- end
102
-
103
- def callback(event, &block)
104
- on(event, &block)
105
- end
106
-
107
53
  def pipeline_config
108
54
  @pipeline_config ||= { steps: [], hooks: {}, step_hooks: {} }
109
55
  end
@@ -129,23 +75,21 @@ module ActiveHarness
129
75
  @payload = value
130
76
  end
131
77
 
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
78
+ def initialize(input:, context: {}, memory: nil, streams: {})
79
+ @original_input = input
80
+ @payload = input
81
+ @context = context.dup
82
+ @memory = memory
83
+ @token_stream = streams[:token]
84
+ @agent_event_stream = streams[:agent]
85
+ @tribunal_event_stream = streams[:tribunal]
86
+ @pipeline_event_stream = streams[:pipeline]
87
+ @step_results = {}
88
+ @stopped = false
89
+ @stopped_at = nil
90
+ @stop_reason = nil
91
+ @execution_time = nil
92
+ @output = nil
149
93
  end
150
94
 
151
95
  def stopped?
@@ -160,7 +104,7 @@ module ActiveHarness
160
104
  @memory&.load
161
105
 
162
106
  config[:steps].each do |step|
163
- fire_global(:before_step, step.name, @payload, config)
107
+ fire(:before_step, step.name, @payload, config)
164
108
  fire_step(:before_step, step.name, @payload, config)
165
109
 
166
110
  result = execute_step(step)
@@ -169,7 +113,7 @@ module ActiveHarness
169
113
  @context[step.name] = result
170
114
  @payload = result.output if step.transform?
171
115
 
172
- fire_global(:after_step, step.name, result, config)
116
+ fire(:after_step, step.name, result, config)
173
117
  fire_step(:after_step, step.name, result, config)
174
118
 
175
119
  if step.stop_if && step.stop_if.call(result)
@@ -178,6 +122,7 @@ module ActiveHarness
178
122
  @stop_reason = result
179
123
  blk = config[:hooks][:stopped]
180
124
  instance_exec(step.name, result, &blk) if blk
125
+ @pipeline_event_stream&.call(:stopped, step.name, result)
181
126
  break
182
127
  end
183
128
  end
@@ -195,6 +140,7 @@ module ActiveHarness
195
140
  last_result = @step_results[@step_results.keys.last]
196
141
  blk = config[:hooks][:complete]
197
142
  instance_exec(last_result, &blk) if blk
143
+ @pipeline_event_stream&.call(:complete, last_result)
198
144
  end
199
145
 
200
146
  self
@@ -204,37 +150,23 @@ module ActiveHarness
204
150
 
205
151
  def execute_step(step)
206
152
  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
153
+ agent_streams = { token: @token_stream, agent: @agent_event_stream, tribunal: @tribunal_event_stream }.compact
154
+ step.agent_class.new(
155
+ input: @payload,
156
+ context: @context.dup,
157
+ streams: agent_streams
158
+ ).call
215
159
  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
160
+ agent_streams = { token: @token_stream, agent: @agent_event_stream }.compact
161
+ step.agent_class.new(
162
+ input: @payload,
163
+ context: @context.dup,
164
+ streams: agent_streams
165
+ ).call.result
223
166
  end
224
167
  end
225
-
226
- # Global hook: receives (step_name, data)
227
- def fire_global(event, step_name, data, config)
228
- blk = config[:hooks][event]
229
- instance_exec(step_name, data, &blk) if blk
230
- end
231
-
232
- # Per-step hook: receives (data) only
233
- def fire_step(event, step_name, data, config)
234
- blk = config[:step_hooks][step_name]&.dig(event)
235
- instance_exec(data, &blk) if blk
236
- end
237
168
  end
238
169
  end
239
170
 
171
+ require_relative "pipeline/hooks"
240
172
  require_relative "pipeline/step"
@@ -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.19"
32
+ VERSION = "0.2.21"
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.19
4
+ version: 0.2.21
5
5
  platform: ruby
6
6
  authors:
7
7
  - the-teacher
@@ -51,6 +51,7 @@ files:
51
51
  - lib/active_harness/memory/adapter/base.rb
52
52
  - lib/active_harness/memory/adapter/file.rb
53
53
  - lib/active_harness/pipeline.rb
54
+ - lib/active_harness/pipeline/hooks.rb
54
55
  - lib/active_harness/pipeline/step.rb
55
56
  - lib/active_harness/providers/PROVIDER_CONTRACT.md
56
57
  - lib/active_harness/providers/anthropic.rb