active_harness 0.2.22 → 0.2.23

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: 33e2cd8c861873028568322fd36104414d698fb4888810388d0ac9b1c1b5b9d4
4
- data.tar.gz: cd792deb836b9cbb5237f9c8c02a6f436c3c48162b21edb412f9797a39a94e16
3
+ metadata.gz: 0bf8ac57d79582b07a2d82b29be4541fcaf10b3ee8492d362d6ac10f2843537d
4
+ data.tar.gz: cabffcc74dd594c5222a8e0233aa9d5f7276d65cfa06848d3e2684e19efe393e
5
5
  SHA512:
6
- metadata.gz: 93365a99d1965c177e379a48027f5fc2dee3fa06dba5061d41be5b7948ba134ed8307cf65c4e57951f21798150c05745311ee793c49fb08be11669ebb13f8e8d
7
- data.tar.gz: 3fb4814a98bde51eba1982f55cd22457bd96613be7f1b44b08434fe0c3c3dc467d7d6ccb0fe36cbbb590885139c1b047c9dc79b5293e7d5fd4f5be32df4e5ca7
6
+ metadata.gz: '018677f5498399d22c2a1b497649e4676023eae7b2578b8b0c13962b1c443970f20e2af5e7ca15051f75d2903287ed9f12012407cf9a671ad1d5d688d8def153'
7
+ data.tar.gz: 5219e6c2a208ff1a2215a71d924c24ade46b6abebc94e9a21c7a36d931d9e32919e7ddc5b054ff7bb616b46057761138aa1a01ce21e66ef6978bb5802304cabe
@@ -10,13 +10,11 @@ module ActiveHarness
10
10
  #
11
11
  # SupportAgent.call(input: "Hi")
12
12
  # SupportAgent.call(input: "Hi", context: { user_id: 42 })
13
- # SupportAgent.call(input: "Hi", memory: memory)
14
13
  def call(
15
14
  input: nil,
16
15
  context: {},
17
16
  params: {},
18
17
  models: nil,
19
- memory: nil,
20
18
  streams: {}
21
19
  )
22
20
  new(
@@ -24,7 +22,6 @@ module ActiveHarness
24
22
  context: context,
25
23
  params: params,
26
24
  models: models,
27
- memory: memory,
28
25
  streams: streams
29
26
  ).call
30
27
  end
@@ -62,16 +59,11 @@ module ActiveHarness
62
59
  @model_list_proxy = nil
63
60
  end
64
61
 
65
- def memory=(obj)
66
- @memory = obj
67
- end
68
-
69
62
  def initialize(
70
63
  input: nil,
71
64
  context: {},
72
65
  params: {},
73
66
  models: nil,
74
- memory: nil,
75
67
  streams: {}
76
68
  )
77
69
  @input = input
@@ -82,8 +74,6 @@ module ActiveHarness
82
74
  @models_override = Array(models) if models
83
75
  @token_stream = streams[:token]
84
76
  @event_stream = streams[:agent]
85
- # memory: can be passed directly or via context[:memory]
86
- @memory = memory || @context[:memory]
87
77
  fire(:setup)
88
78
  end
89
79
 
@@ -102,9 +92,8 @@ module ActiveHarness
102
92
  @token_stream = streams[:token] if streams.key?(:token)
103
93
  @event_stream = streams[:agent] if streams.key?(:agent)
104
94
  end
105
- @memory&.load
106
- @system_prompt = resolve_system_prompt
107
95
  fire(:before_call)
96
+ @system_prompt = resolve_system_prompt
108
97
  attempts = []
109
98
 
110
99
  cfg = ActiveHarness.config
@@ -118,7 +107,6 @@ module ActiveHarness
118
107
  response = retry_policy.run { attempt_model(entry, @system_prompt) }
119
108
  elapsed = (Process.clock_gettime(Process::CLOCK_MONOTONIC) - t0).round(3)
120
109
  result = build_result(response, entry, attempts, elapsed)
121
- save_to_memory(result)
122
110
  fire(:after_call, result)
123
111
  @result = result
124
112
  return self
@@ -171,21 +159,6 @@ module ActiveHarness
171
159
  )
172
160
  end
173
161
 
174
- # Auto-save to memory if no manual record was done in after_call hook.
175
- # Hooks fire after this method — if a hook calls memory.record manually,
176
- # the automatic save here is still the first save (hook overrides are additive).
177
- # To suppress auto-save, set @memory_auto_saved in the hook.
178
- def save_to_memory(result)
179
- return unless @memory
180
-
181
- @memory.record(
182
- request: @input,
183
- response: result.output,
184
- agent: self.class.name,
185
- model: result.model
186
- )
187
- end
188
-
189
162
  def normalize_input!
190
163
  return if @config.fetch(:normalize_input, true) == false
191
164
  @input = @input&.strip&.gsub(/\s+/, " ")
@@ -51,16 +51,46 @@ module ActiveHarness
51
51
  end
52
52
 
53
53
  def pipeline_config
54
- @pipeline_config ||= { steps: [], hooks: {}, step_hooks: {} }
54
+ @pipeline_config ||= { steps: [], hooks: {}, step_hooks: {}, streams: {} }
55
55
  end
56
56
 
57
57
  # Each subclass gets its own isolated config.
58
58
  def inherited(subclass)
59
59
  subclass.instance_variable_set(
60
60
  :@pipeline_config,
61
- { steps: [], hooks: {}, step_hooks: {} }
61
+ { steps: [], hooks: {}, step_hooks: {}, streams: {} }
62
62
  )
63
63
  end
64
+
65
+ # Class-level event stream handlers — fired for every matching event from
66
+ # any agent or tribunal executed within this pipeline (including agents
67
+ # running inside tribunals). Multiple blocks can be registered; all fire.
68
+ #
69
+ # The handler receives the same (event, *args) signature that the runtime
70
+ # streams: { agent: lambda } would receive.
71
+ #
72
+ # on_agent_event do |event, result|
73
+ # Rails.logger.info "[Agent #{event}] #{result.model}" if event == :after_call
74
+ # end
75
+ #
76
+ # on_tribunal_event do |event, verdict|
77
+ # Rails.logger.info "[Tribunal #{event}] verdict=#{verdict}" if event == :after_verdict
78
+ # end
79
+ #
80
+ # on_pipeline_event do |event, step_name, _data|
81
+ # Rails.logger.info "[Pipeline #{event}] step=#{step_name}"
82
+ # end
83
+ def on_agent_event(&block)
84
+ (pipeline_config[:streams][:agent] ||= []) << block
85
+ end
86
+
87
+ def on_tribunal_event(&block)
88
+ (pipeline_config[:streams][:tribunal] ||= []) << block
89
+ end
90
+
91
+ def on_pipeline_event(&block)
92
+ (pipeline_config[:streams][:pipeline] ||= []) << block
93
+ end
64
94
  end
65
95
 
66
96
  # -------------------------------------------------------------------------
@@ -94,9 +124,10 @@ module ActiveHarness
94
124
  @params = params
95
125
  @memory = memory
96
126
  @token_stream = streams[:token]
97
- @agent_event_stream = streams[:agent]
98
- @tribunal_event_stream = streams[:tribunal]
99
- @pipeline_event_stream = streams[:pipeline]
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])
100
131
  @step_results = {}
101
132
  @stopped = false
102
133
  @stopped_at = nil
@@ -159,6 +190,25 @@ module ActiveHarness
159
190
 
160
191
  private
161
192
 
193
+ # Combines a runtime-passed stream lambda with zero or more class-level handler
194
+ # blocks registered via on_agent_event / on_tribunal_event / on_pipeline_event.
195
+ # Returns nil when there are no handlers at all, preserving the existing
196
+ # "no stream" fast path in agents and tribunals.
197
+ #
198
+ # Each class-level handler is evaluated via instance_exec so that blocks
199
+ # written in the pipeline class body can access pipeline instance variables
200
+ # (e.g. @otel_pipeline_span, @params) and call pipeline instance methods.
201
+ def merge_stream(passed_in, class_handlers)
202
+ class_handlers = Array(class_handlers).compact
203
+ return passed_in if class_handlers.empty?
204
+
205
+ pipeline_instance = self
206
+ ->(event, *args) {
207
+ class_handlers.each { |h| pipeline_instance.instance_exec(event, *args, &h) }
208
+ passed_in&.call(event, *args)
209
+ }
210
+ end
211
+
162
212
  def execute_step(step)
163
213
  if step.tribunal?
164
214
  agent_streams = { token: @token_stream, agent: @agent_event_stream, tribunal: @tribunal_event_stream }.compact
@@ -30,7 +30,7 @@ require_relative "active_harness/pipeline"
30
30
  require_relative "active_harness/railtie" if defined?(Rails::Railtie)
31
31
 
32
32
  module ActiveHarness
33
- VERSION = "0.2.22"
33
+ VERSION = "0.2.23"
34
34
 
35
35
  class << self
36
36
  # 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.22
4
+ version: 0.2.23
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-28 00:00:00.000000000 Z
11
+ date: 2026-06-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby