active_harness 0.2.21 → 0.2.22

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: de49ac66334a5feb91a5d5f0314ab2b9b3460a5d3a549f08b77538677eae619b
4
- data.tar.gz: 0bffb6e77d1b8def25121b144dbc956c2c1b0c684826910e3dd0a66ab595a73e
3
+ metadata.gz: 33e2cd8c861873028568322fd36104414d698fb4888810388d0ac9b1c1b5b9d4
4
+ data.tar.gz: cd792deb836b9cbb5237f9c8c02a6f436c3c48162b21edb412f9797a39a94e16
5
5
  SHA512:
6
- metadata.gz: e51c5ab8c3911d2597f6269f695c2f4992f960d771b4284fdd27121939aca14dfea53e568ecbfc069af26b405725106c18b3beebf0232e751ed8144b3134cea7
7
- data.tar.gz: ea95a9f48993b4dc930addeee4260e6056815ec48d3c2534a6bf6f5001168cad2104fcd988c98e442f940f32faa4161f9b59a4692bc6fa3adf7655a221d5d56b
6
+ metadata.gz: 93365a99d1965c177e379a48027f5fc2dee3fa06dba5061d41be5b7948ba134ed8307cf65c4e57951f21798150c05745311ee793c49fb08be11669ebb13f8e8d
7
+ data.tar.gz: 3fb4814a98bde51eba1982f55cd22457bd96613be7f1b44b08434fe0c3c3dc467d7d6ccb0fe36cbbb590885139c1b047c9dc79b5293e7d5fd4f5be32df4e5ca7
@@ -32,7 +32,7 @@ module ActiveHarness
32
32
  end
33
33
 
34
34
  agent_config[:hooks] ||= {}
35
- agent_config[:hooks][event] = block
35
+ (agent_config[:hooks][event] ||= []) << block
36
36
  end
37
37
 
38
38
  # Rails-style aliases for +on+:
@@ -59,27 +59,20 @@ module ActiveHarness
59
59
  end
60
60
  end
61
61
 
62
+ include Core::HookRunner
63
+
62
64
  private
63
65
 
64
66
  def run_hook(event, *args)
65
- hooks = @config[:hooks] || {}
66
- return unless hooks[event]
67
-
68
- if args.any?
69
- instance_exec(*args, &hooks[event])
70
- else
71
- instance_eval(&hooks[event])
72
- end
67
+ run_hooks(@config[:hooks] || {}, event, *args)
73
68
  end
74
69
 
75
70
  # Unified internal method: fires the DSL hook AND the external event_stream lambda.
76
71
  # Consistent with Tribunal#fire and Pipeline#fire.
77
72
  def fire(event, *args)
78
- result = run_hook(event, *args)
73
+ run_hook(event, *args)
79
74
  @event_stream&.call(event, *args)
80
- result
81
75
  rescue IOError, ActionController::Live::ClientDisconnected
82
- result
83
76
  end
84
77
  end
85
78
  end
@@ -11,8 +11,22 @@ 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, streams: {})
15
- new(input: input, context: context, models: models, memory: memory, streams: streams).call
14
+ def call(
15
+ input: nil,
16
+ context: {},
17
+ params: {},
18
+ models: nil,
19
+ memory: nil,
20
+ streams: {}
21
+ )
22
+ new(
23
+ input: input,
24
+ context: context,
25
+ params: params,
26
+ models: models,
27
+ memory: memory,
28
+ streams: streams
29
+ ).call
16
30
  end
17
31
 
18
32
  # Each subclass gets its own isolated config hash.
@@ -36,8 +50,12 @@ module ActiveHarness
36
50
  # -------------------------------------------------------------------------
37
51
  # Instance API
38
52
  # -------------------------------------------------------------------------
39
- attr_accessor :input, :context
40
- attr_reader :result, :token_stream, :event_stream
53
+ attr_accessor :input,
54
+ :context,
55
+ :params
56
+ attr_reader :result,
57
+ :token_stream,
58
+ :event_stream
41
59
 
42
60
  def models=(list)
43
61
  @models_override = Array(list)
@@ -48,11 +66,19 @@ module ActiveHarness
48
66
  @memory = obj
49
67
  end
50
68
 
51
- def initialize(input: nil, context: {}, models: nil, memory: nil, streams: {})
69
+ def initialize(
70
+ input: nil,
71
+ context: {},
72
+ params: {},
73
+ models: nil,
74
+ memory: nil,
75
+ streams: {}
76
+ )
52
77
  @input = input
53
78
  @config = self.class.agent_config
54
79
  normalize_input!
55
80
  @context = context
81
+ @params = params
56
82
  @models_override = Array(models) if models
57
83
  @token_stream = streams[:token]
58
84
  @event_stream = streams[:agent]
@@ -0,0 +1,26 @@
1
+ module ActiveHarness
2
+ module Core
3
+ # Shared hook execution logic included by Agent, Tribunal, and Pipeline.
4
+ #
5
+ # Hooks are stored in arrays so multiple +on+/+before+/+after+/+callback+
6
+ # calls with the same event name accumulate — later registrations append
7
+ # rather than overwrite. This lets modules register default hooks without
8
+ # blocking user-defined hooks on the same event.
9
+ #
10
+ # class MyAgent < ActiveHarness::Agent
11
+ # include SomeTracingConcern # registers before(:call) internally
12
+ # before(:call) { ... } # appends — both hooks run in order
13
+ # end
14
+ module HookRunner
15
+ private
16
+
17
+ # Execute every block registered for +event+, passing +args+ to each.
18
+ # Blocks run in the receiver's instance context (instance_exec / instance_eval).
19
+ def run_hooks(hooks_hash, event, *args)
20
+ Array(hooks_hash[event]).each do |blk|
21
+ args.any? ? instance_exec(*args, &blk) : instance_eval(&blk)
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -22,13 +22,13 @@ module ActiveHarness
22
22
  "Per-step hooks support: #{VALID_STEP_HOOKS.join(", ")}. Got :#{event}"
23
23
  end
24
24
  pipeline_config[:step_hooks][step_name] ||= {}
25
- pipeline_config[:step_hooks][step_name][event] = block
25
+ (pipeline_config[:step_hooks][step_name][event] ||= []) << block
26
26
  else
27
27
  unless VALID_HOOKS.include?(event)
28
28
  raise ArgumentError,
29
29
  "Unknown Pipeline hook :#{event}. Valid: #{VALID_HOOKS.join(", ")}"
30
30
  end
31
- pipeline_config[:hooks][event] = block
31
+ (pipeline_config[:hooks][event] ||= []) << block
32
32
  end
33
33
  end
34
34
 
@@ -56,12 +56,13 @@ module ActiveHarness
56
56
  end
57
57
  end
58
58
 
59
+ include Core::HookRunner
60
+
59
61
  private
60
62
 
61
63
  # Fires global hook AND pipeline_event_stream. Consistent with Agent#fire and Tribunal#fire.
62
64
  def fire(event, step_name, data, config)
63
- blk = config[:hooks][event]
64
- instance_exec(step_name, data, &blk) if blk
65
+ run_hooks(config[:hooks], event, step_name, data)
65
66
  @pipeline_event_stream&.call(event, step_name, data)
66
67
  rescue IOError, ActionController::Live::ClientDisconnected
67
68
  nil
@@ -70,8 +71,7 @@ module ActiveHarness
70
71
  # Per-step hook: receives (data) only — not forwarded to pipeline_event_stream
71
72
  # (global fire already covers the step event with step_name context).
72
73
  def fire_step(event, step_name, data, config)
73
- blk = config[:step_hooks][step_name]&.dig(event)
74
- instance_exec(data, &blk) if blk
74
+ run_hooks(config[:step_hooks][step_name] || {}, event, data)
75
75
  end
76
76
  end
77
77
  end
@@ -66,19 +66,32 @@ module ActiveHarness
66
66
  # -------------------------------------------------------------------------
67
67
  # Instance API
68
68
  # -------------------------------------------------------------------------
69
- attr_reader :original_input, :output, :stopped_at, :stop_reason,
70
- :execution_time, :step_results, :context
71
- attr_writer :context
69
+ attr_reader :original_input,
70
+ :output,
71
+ :stopped_at,
72
+ :stop_reason,
73
+ :execution_time,
74
+ :step_results,
75
+ :context
76
+ attr_writer :context
77
+ attr_accessor :params
72
78
 
73
79
  def input=(value)
74
80
  @original_input = value
75
81
  @payload = value
76
82
  end
77
83
 
78
- def initialize(input:, context: {}, memory: nil, streams: {})
84
+ def initialize(
85
+ input:,
86
+ context: {},
87
+ params: {},
88
+ memory: nil,
89
+ streams: {}
90
+ )
79
91
  @original_input = input
80
92
  @payload = input
81
93
  @context = context.dup
94
+ @params = params
82
95
  @memory = memory
83
96
  @token_stream = streams[:token]
84
97
  @agent_event_stream = streams[:agent]
@@ -120,8 +133,7 @@ module ActiveHarness
120
133
  @stopped = true
121
134
  @stopped_at = step.name
122
135
  @stop_reason = result
123
- blk = config[:hooks][:stopped]
124
- instance_exec(step.name, result, &blk) if blk
136
+ run_hooks(config[:hooks], :stopped, step.name, result)
125
137
  @pipeline_event_stream&.call(:stopped, step.name, result)
126
138
  break
127
139
  end
@@ -138,8 +150,7 @@ module ActiveHarness
138
150
  )
139
151
 
140
152
  last_result = @step_results[@step_results.keys.last]
141
- blk = config[:hooks][:complete]
142
- instance_exec(last_result, &blk) if blk
153
+ run_hooks(config[:hooks], :complete, last_result)
143
154
  @pipeline_event_stream&.call(:complete, last_result)
144
155
  end
145
156
 
@@ -154,6 +165,7 @@ module ActiveHarness
154
165
  step.agent_class.new(
155
166
  input: @payload,
156
167
  context: @context.dup,
168
+ params: @params,
157
169
  streams: agent_streams
158
170
  ).call
159
171
  else
@@ -161,6 +173,7 @@ module ActiveHarness
161
173
  step.agent_class.new(
162
174
  input: @payload,
163
175
  context: @context.dup,
176
+ params: @params,
164
177
  streams: agent_streams
165
178
  ).call.result
166
179
  end
@@ -26,7 +26,7 @@ module ActiveHarness
26
26
  "Unknown Tribunal hook :#{event}. Valid hooks: #{VALID_HOOKS.map { |h| ":#{h}" }.join(", ")}"
27
27
  end
28
28
 
29
- tribunal_config[:hooks][event] = block
29
+ (tribunal_config[:hooks][event] ||= []) << block
30
30
  end
31
31
 
32
32
  # Rails-style aliases for +on+:
@@ -51,7 +51,7 @@ module ActiveHarness
51
51
  end
52
52
  end
53
53
 
54
- # Instance-level hook registration — overrides class-level hooks for this instance.
54
+ # Instance-level hook registration — appends to class-level hooks for this event.
55
55
  # :before_verdict is a transform hook: its return value replaces the results array
56
56
  # passed to the process block.
57
57
  def on(event, &block)
@@ -60,20 +60,16 @@ module ActiveHarness
60
60
  "Unknown Tribunal hook :#{event}. Valid hooks: #{VALID_HOOKS.map { |h| ":#{h}" }.join(", ")}"
61
61
  end
62
62
 
63
- @hooks[event] = block
63
+ (@hooks[event] ||= []) << block
64
64
  self
65
65
  end
66
66
 
67
+ include Core::HookRunner
68
+
67
69
  private
68
70
 
69
71
  def run_hook(event, *args)
70
- return unless @hooks[event]
71
-
72
- if args.any?
73
- instance_exec(*args, &@hooks[event])
74
- else
75
- instance_eval(&@hooks[event])
76
- end
72
+ run_hooks(@hooks, event, *args)
77
73
  end
78
74
 
79
75
  # Fire the DSL-registered hook AND the external tribunal_event_stream lambda (if set).
@@ -84,12 +80,10 @@ module ActiveHarness
84
80
  nil
85
81
  end
86
82
 
87
- # Like run_hook but uses the return value to replace the passed value.
83
+ # Like run_hook but chains all blocks, passing each return value to the next.
88
84
  # Used by :before_verdict to allow results transformation before verdict computation.
89
85
  def transform_hook(event, value)
90
- return value unless @hooks[event]
91
-
92
- instance_exec(value, &@hooks[event])
86
+ Array(@hooks[event]).reduce(value) { |val, blk| instance_exec(val, &blk) }
93
87
  end
94
88
  end
95
89
  end
@@ -46,24 +46,39 @@ module ActiveHarness
46
46
  # -------------------------------------------------------------------------
47
47
  # Instance API
48
48
  # -------------------------------------------------------------------------
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
52
-
53
- def initialize(input: nil, context: {}, agents: nil, timeout: 7,
54
- streams: {},
55
- may_fail: :_unset)
49
+ attr_accessor :input,
50
+ :context,
51
+ :params
52
+ attr_reader :results,
53
+ :errors,
54
+ :verdict,
55
+ :execution_time,
56
+ :agent_execution_times,
57
+ :token_stream,
58
+ :agent_event_stream,
59
+ :tribunal_event_stream
60
+
61
+ def initialize(
62
+ input: nil,
63
+ context: {},
64
+ params: {},
65
+ agents: nil,
66
+ timeout: 7,
67
+ streams: {},
68
+ may_fail: :_unset
69
+ )
56
70
  config = self.class.tribunal_config
57
71
 
58
72
  @input = input
59
73
  @context = context
74
+ @params = params
60
75
  @agents = agents || config[:agents]
61
76
  @timeout = timeout
62
77
  @process_block = config[:process]
63
78
  @strategy = config[:strategy]
64
79
  @evaluate_block = config[:evaluate_block]
65
80
  @may_fail = may_fail == :_unset ? config[:may_fail] : may_fail
66
- @hooks = config[:hooks].dup
81
+ @hooks = config[:hooks].transform_values { |v| Array(v).dup }
67
82
  @token_stream = streams[:token]
68
83
  @agent_event_stream = streams[:agent]
69
84
  @tribunal_event_stream = streams[:tribunal]
@@ -155,7 +170,7 @@ module ActiveHarness
155
170
  agent_streams = { token: @token_stream, agent: @agent_event_stream }.compact
156
171
  @agents.map do |agent|
157
172
  if agent.is_a?(Class)
158
- agent.new(input: @input, context: @context.dup, streams: agent_streams)
173
+ agent.new(input: @input, context: @context.dup, params: @params, streams: agent_streams)
159
174
  else
160
175
  agent.input = @input if @input
161
176
  agent.instance_variable_set(:@token_stream, @token_stream) if @token_stream
@@ -1,5 +1,6 @@
1
1
  require_relative "active_harness/configuration"
2
2
  require_relative "active_harness/core/errors"
3
+ require_relative "active_harness/core/hooks"
3
4
  require_relative "active_harness/result"
4
5
  require_relative "active_harness/http/client"
5
6
  require_relative "active_harness/http/streaming_client"
@@ -29,7 +30,7 @@ require_relative "active_harness/pipeline"
29
30
  require_relative "active_harness/railtie" if defined?(Rails::Railtie)
30
31
 
31
32
  module ActiveHarness
32
- VERSION = "0.2.21"
33
+ VERSION = "0.2.22"
33
34
 
34
35
  class << self
35
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.21
4
+ version: 0.2.22
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-27 00:00:00.000000000 Z
11
+ date: 2026-05-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
@@ -42,6 +42,7 @@ files:
42
42
  - lib/active_harness/agent/providers.rb
43
43
  - lib/active_harness/configuration.rb
44
44
  - lib/active_harness/core/errors.rb
45
+ - lib/active_harness/core/hooks.rb
45
46
  - lib/active_harness/costs.rb
46
47
  - lib/active_harness/data/models.json
47
48
  - lib/active_harness/http/client.rb