active_harness 0.2.41 → 0.3.0

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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/lib/active_harness/configuration.rb +3 -1
  3. data/lib/active_harness/core/errors.rb +2 -2
  4. data/lib/active_harness/core/hooks.rb +2 -2
  5. data/lib/active_harness/memory.rb +11 -11
  6. data/lib/active_harness/pipeline/README.md +15 -15
  7. data/lib/active_harness/pipeline/step.rb +1 -1
  8. data/lib/active_harness/pipeline.rb +18 -18
  9. data/lib/active_harness/providers/PROVIDER_CONTRACT.md +1 -1
  10. data/lib/active_harness/providers/audio/openai.rb +127 -0
  11. data/lib/active_harness/providers/azure.rb +1 -1
  12. data/lib/active_harness/providers/bedrock.rb +2 -2
  13. data/lib/active_harness/providers/custom.rb +1 -1
  14. data/lib/active_harness/providers/images/openrouter.rb +49 -1
  15. data/lib/active_harness/providers/vertexai.rb +2 -2
  16. data/lib/active_harness/railtie.rb +1 -1
  17. data/lib/active_harness/{agent → request}/cost.rb +1 -1
  18. data/lib/active_harness/{agent → request}/custom_llm_backend.rb +4 -4
  19. data/lib/active_harness/{agent → request}/hooks.rb +4 -4
  20. data/lib/active_harness/{agent → request}/image.rb +7 -7
  21. data/lib/active_harness/{agent → request}/models.rb +15 -15
  22. data/lib/active_harness/{agent → request}/output_parser.rb +1 -1
  23. data/lib/active_harness/{agent → request}/prompt.rb +7 -7
  24. data/lib/active_harness/{agent → request}/providers.rb +2 -1
  25. data/lib/active_harness/{agent → request}/transcription.rb +7 -7
  26. data/lib/active_harness/{agent.rb → request.rb} +19 -19
  27. data/lib/active_harness/result.rb +3 -3
  28. data/lib/active_harness/tribunal/dsl.rb +7 -7
  29. data/lib/active_harness/tribunal/hooks.rb +9 -9
  30. data/lib/active_harness/tribunal.rb +62 -62
  31. data/lib/active_harness.rb +3 -2
  32. data/lib/generators/active_harness/install/install_generator.rb +1 -1
  33. data/lib/generators/active_harness/install/templates/controllers/ai_controller.rb +3 -3
  34. data/lib/generators/active_harness/install/templates/initializers/active_harness.rb +2 -2
  35. data/lib/generators/active_harness/install/templates/pipelines/support_pipeline.rb +2 -2
  36. data/lib/generators/active_harness/install/templates/{agents/support_guard_agent.rb → requests/support_guard_request.rb} +1 -1
  37. data/lib/generators/active_harness/install/templates/{agents/support_agent.rb → requests/support_request.rb} +1 -1
  38. data/lib/generators/active_harness/install/templates/tribunals/support_guard_tribunal.rb +3 -3
  39. data/lib/generators/active_harness/pipeline/templates/pipeline.rb.tt +1 -1
  40. data/lib/generators/active_harness/request/request_generator.rb +16 -0
  41. data/lib/generators/active_harness/{agent/templates/agent.rb.tt → request/templates/request.rb.tt} +1 -1
  42. data/lib/generators/active_harness/tribunal/templates/tribunal.rb.tt +1 -1
  43. metadata +18 -17
  44. data/lib/generators/active_harness/agent/agent_generator.rb +0 -16
@@ -1,9 +1,9 @@
1
1
  module ActiveHarness
2
- class Agent
2
+ class Request
3
3
  # -------------------------------------------------------------------------
4
4
  # Custom LLM backend DSL
5
5
  #
6
- # Allows an agent to delegate HTTP calls to any LLM client (e.g. `ruby_llm`)
6
+ # Allows a request to delegate HTTP calls to any LLM client (e.g. `ruby_llm`)
7
7
  # instead of ActiveHarness's built-in Net::HTTP providers.
8
8
  #
9
9
  # Usage:
@@ -31,9 +31,9 @@ module ActiveHarness
31
31
  BackendParams = Struct.new(:model, :provider, :temperature, keyword_init: true)
32
32
 
33
33
  class << self
34
- # Define the custom LLM backend block for this agent class.
34
+ # Define the custom LLM backend block for this request class.
35
35
  def custom_llm_backend(&block)
36
- agent_config[:custom_llm_backend] = block
36
+ request_config[:custom_llm_backend] = block
37
37
  end
38
38
  end
39
39
 
@@ -1,5 +1,5 @@
1
1
  module ActiveHarness
2
- class Agent
2
+ class Request
3
3
  VALID_HOOKS = %i[
4
4
  setup
5
5
  before_call
@@ -31,8 +31,8 @@ module ActiveHarness
31
31
  raise ArgumentError, "Unknown hook :#{event}. Valid hooks: #{VALID_HOOKS.map { |h| ":#{h}" }.join(", ")}"
32
32
  end
33
33
 
34
- agent_config[:hooks] ||= {}
35
- (agent_config[:hooks][event] ||= []) << block
34
+ request_config[:hooks] ||= {}
35
+ (request_config[:hooks][event] ||= []) << block
36
36
  end
37
37
 
38
38
  # Rails-style aliases for +on+:
@@ -69,7 +69,7 @@ module ActiveHarness
69
69
 
70
70
  def fire(event, *args)
71
71
  run_hook(event, *args)
72
- @stream&.call(:agent, event, *args)
72
+ @stream&.call(:request, event, *args)
73
73
  rescue IOError, ActionController::Live::ClientDisconnected
74
74
  end
75
75
  end
@@ -1,14 +1,14 @@
1
1
  module ActiveHarness
2
- class Agent
2
+ class Request
3
3
  class << self
4
- # Mark this agent as an image generation agent.
4
+ # Mark this request as an image generation request.
5
5
  #
6
- # class ImageAgent < ActiveHarness::Agent
6
+ # class ImageRequest < ActiveHarness::Request
7
7
  # image true
8
8
  # size "1024x1024"
9
9
  #
10
10
  # # Optional: base style/formatting guidance, prepended to @input
11
- # # to form the final image prompt (see Agent#build_image_prompt).
11
+ # # to form the final image prompt (see Request#build_image_prompt).
12
12
  # system_prompt "Watercolor style, soft pastel colors, no text overlays."
13
13
  #
14
14
  # model do
@@ -20,13 +20,13 @@ module ActiveHarness
20
20
  # result.output — base64 string or data-URI (provider-dependent)
21
21
  # result.processed — same as output (format :text default)
22
22
  def image(value = true)
23
- agent_config[:image] = value
23
+ request_config[:image] = value
24
24
  end
25
25
 
26
- # Default image size for all models in this agent's chain.
26
+ # Default image size for all models in this request's chain.
27
27
  # Can be overridden per-model via: use provider: :openai, model: "...", size: "1024x1792"
28
28
  def size(default_size)
29
- agent_config[:image_size] = default_size
29
+ request_config[:image_size] = default_size
30
30
  end
31
31
  end
32
32
 
@@ -1,5 +1,5 @@
1
1
  module ActiveHarness
2
- class Agent
2
+ class Request
3
3
  class << self
4
4
  # Array-style model list:
5
5
  #
@@ -8,10 +8,10 @@ module ActiveHarness
8
8
  # { provider: :openrouter, model: "mistralai/mistral-nemo" }
9
9
  # ]
10
10
  def models(list)
11
- agent_config[:models] = Array(list)
11
+ request_config[:models] = Array(list)
12
12
  end
13
13
 
14
- # Output format for this agent.
14
+ # Output format for this request.
15
15
  #
16
16
  # format :text # default — output is returned as-is
17
17
  # format :json # output is parsed; result.processed is a Ruby Hash/Array
@@ -20,7 +20,7 @@ module ActiveHarness
20
20
  raise ArgumentError, "Unknown format :#{type}. Valid values: :text, :json"
21
21
  end
22
22
 
23
- agent_config[:format] = type
23
+ request_config[:format] = type
24
24
  end
25
25
 
26
26
  # Block-based model DSL:
@@ -32,16 +32,16 @@ module ActiveHarness
32
32
  def model(&block)
33
33
  config = ModelConfig.new
34
34
  config.instance_eval(&block)
35
- agent_config[:model] = config.to_h
35
+ request_config[:model] = config.to_h
36
36
  end
37
37
  end
38
38
 
39
- # Public instance API — returns a ModelList proxy for this agent instance.
39
+ # Public instance API — returns a ModelList proxy for this request instance.
40
40
  #
41
- # Allows adding models at runtime before calling the agent:
41
+ # Allows adding models at runtime before calling the request:
42
42
  #
43
- # agent.models.prepend([{ provider: :openrouter, model: "..." }])
44
- # agent.models.push([{ provider: :openrouter, model: "..." }])
43
+ # request.models.prepend([{ provider: :openrouter, model: "..." }])
44
+ # request.models.push([{ provider: :openrouter, model: "..." }])
45
45
  #
46
46
  # Any prepended/pushed models are combined with the class-defined chain:
47
47
  # [prepended...] + [class chain / constructor override] + [pushed...]
@@ -69,7 +69,7 @@ module ActiveHarness
69
69
  end
70
70
 
71
71
  # ---------------------------------------------------------------------------
72
- # ModelList — mutable proxy returned by agent.models
72
+ # ModelList — mutable proxy returned by request.models
73
73
  # ---------------------------------------------------------------------------
74
74
  class ModelList
75
75
  def initialize(base_models)
@@ -93,7 +93,7 @@ module ActiveHarness
93
93
 
94
94
  # Replace the entire chain, discarding prepended/appended too.
95
95
  #
96
- # agent.models.replace([{ provider: :openrouter, model: "..." }])
96
+ # request.models.replace([{ provider: :openrouter, model: "..." }])
97
97
  def replace(models)
98
98
  @base = Array(models)
99
99
  @prepended = []
@@ -104,9 +104,9 @@ module ActiveHarness
104
104
  # Insert a single model entry at an arbitrary position in the final chain.
105
105
  # Position 0 is the same as prepend; -1 appends after the last element.
106
106
  #
107
- # agent.models.insert(0, { provider: :openrouter, model: "fast-model" })
108
- # agent.models.insert(2, { provider: :openrouter, model: "mid-model" })
109
- # agent.models.insert(-1, { provider: :openrouter, model: "last-resort" })
107
+ # request.models.insert(0, { provider: :openrouter, model: "fast-model" })
108
+ # request.models.insert(2, { provider: :openrouter, model: "mid-model" })
109
+ # request.models.insert(-1, { provider: :openrouter, model: "last-resort" })
110
110
  def insert(position, model)
111
111
  to_a.tap { |list| list.insert(position, model) }.then do |new_list|
112
112
  @prepended = []
@@ -127,7 +127,7 @@ module ActiveHarness
127
127
  end
128
128
 
129
129
  # ---------------------------------------------------------------------------
130
- # DSL builder — used by Agent.model { ... }
130
+ # DSL builder — used by Request.model { ... }
131
131
  # ---------------------------------------------------------------------------
132
132
  class ModelConfig
133
133
  def initialize
@@ -1,5 +1,5 @@
1
1
  module ActiveHarness
2
- class Agent
2
+ class Request
3
3
  private
4
4
 
5
5
  # Strips markdown code fences that some models add despite being told not to:
@@ -1,7 +1,7 @@
1
1
  module ActiveHarness
2
- class Agent
2
+ class Request
3
3
  class << self
4
- # System prompt for this agent.
4
+ # System prompt for this request.
5
5
  # Accepts:
6
6
  # - a String → used as-is
7
7
  # - a Class → instantiated and resolved via #call or #text
@@ -11,7 +11,7 @@ module ActiveHarness
11
11
  # system_prompt MyPromptClass
12
12
  # system_prompt -> { "Dynamic prompt built at #{Time.now}" }
13
13
  def system_prompt(text_or_class)
14
- agent_config[:system_prompt] = text_or_class
14
+ request_config[:system_prompt] = text_or_class
15
15
  end
16
16
  alias prompt system_prompt
17
17
  end
@@ -32,13 +32,13 @@ module ActiveHarness
32
32
  when Proc then instance_exec(&sp)
33
33
  when Class
34
34
  instance = sp.new
35
- inject_agent_state(instance)
35
+ inject_request_state(instance)
36
36
  if instance.respond_to?(:call) then instance.call
37
37
  elsif instance.respond_to?(:text) then instance.text
38
38
  else instance.to_s
39
39
  end
40
40
  else
41
- inject_agent_state(sp)
41
+ inject_request_state(sp)
42
42
  if sp.respond_to?(:call) then sp.call
43
43
  elsif sp.respond_to?(:text) then sp.text
44
44
  else sp.to_s
@@ -49,9 +49,9 @@ module ActiveHarness
49
49
  prompt
50
50
  end
51
51
 
52
- # Injects agent state into a prompt class instance before #call.
52
+ # Injects request state into a prompt class instance before #call.
53
53
  # Available in prompt classes: @input, @context, @params, @memory, @context_window, @config
54
- def inject_agent_state(obj)
54
+ def inject_request_state(obj)
55
55
  obj.instance_variable_set(:@input, @input)
56
56
  obj.instance_variable_set(:@context, @context)
57
57
  obj.instance_variable_set(:@params, @params)
@@ -1,5 +1,5 @@
1
1
  module ActiveHarness
2
- class Agent
2
+ class Request
3
3
  # Errors that allow retrying the next model in the chain.
4
4
  # InvalidRequestError is included here so that a bad model name (or any
5
5
  # per-model request failure) does not abort the entire chain — the next
@@ -44,6 +44,7 @@ module ActiveHarness
44
44
  }.freeze
45
45
 
46
46
  TRANSCRIPTION_PROVIDERS = {
47
+ openai: -> { Providers::Audio::OpenAI.new },
47
48
  openrouter: -> { Providers::Audio::OpenRouter.new }
48
49
  }.freeze
49
50
 
@@ -1,9 +1,9 @@
1
1
  module ActiveHarness
2
- class Agent
2
+ class Request
3
3
  class << self
4
- # Mark this agent as an audio transcription agent.
4
+ # Mark this request as an audio transcription request.
5
5
  #
6
- # class TranscriptionAgent < ActiveHarness::Agent
6
+ # class TranscriptionRequest < ActiveHarness::Request
7
7
  # transcribe true
8
8
  #
9
9
  # model do
@@ -16,13 +16,13 @@ module ActiveHarness
16
16
  # result.output — transcribed text (String)
17
17
  # result.processed — same as output (format :text default)
18
18
  def transcribe(value = true)
19
- agent_config[:transcribe] = value
19
+ request_config[:transcribe] = value
20
20
  end
21
21
 
22
- # Default ISO-639-1 language hint for all models in this agent's chain.
22
+ # Default ISO-639-1 language hint for all models in this request's chain.
23
23
  # Can be overridden per-model via: use provider: :openrouter, model: "...", language: "ja"
24
24
  def language(default_language)
25
- agent_config[:transcribe_language] = default_language
25
+ request_config[:transcribe_language] = default_language
26
26
  end
27
27
  end
28
28
 
@@ -38,7 +38,7 @@ module ActiveHarness
38
38
 
39
39
  alias_method :_normalize_input_before_transcribe, :normalize_input!
40
40
 
41
- # @input is a file path for transcription agents, not free text —
41
+ # @input is a file path for transcription requests, not free text —
42
42
  # stripping/collapsing whitespace could corrupt a path. Skip it.
43
43
  def normalize_input!
44
44
  return if @config[:transcribe]
@@ -1,15 +1,15 @@
1
1
  require "json"
2
2
 
3
3
  module ActiveHarness
4
- class Agent
4
+ class Request
5
5
  # -------------------------------------------------------------------------
6
6
  # Class-level DSL — core
7
7
  # -------------------------------------------------------------------------
8
8
  class << self
9
9
  # Class-level entry point.
10
10
  #
11
- # SupportAgent.call(input: "Hi")
12
- # SupportAgent.call(input: "Hi", context: { user_id: 42 })
11
+ # SupportRequest.call(input: "Hi")
12
+ # SupportRequest.call(input: "Hi", context: { user_id: 42 })
13
13
  def call(
14
14
  input: nil,
15
15
  context: {},
@@ -31,12 +31,12 @@ module ActiveHarness
31
31
  end
32
32
 
33
33
  # Each subclass gets its own isolated config hash.
34
- def agent_config
35
- @agent_config ||= {}
34
+ def request_config
35
+ @request_config ||= {}
36
36
  end
37
37
 
38
38
  def inherited(subclass)
39
- subclass.instance_variable_set(:@agent_config, {})
39
+ subclass.instance_variable_set(:@request_config, {})
40
40
  end
41
41
 
42
42
  # Automatically strip and collapse whitespace in @input before each call.
@@ -44,7 +44,7 @@ module ActiveHarness
44
44
  #
45
45
  # normalize_input false
46
46
  def normalize_input(value = true)
47
- agent_config[:normalize_input] = value
47
+ request_config[:normalize_input] = value
48
48
  end
49
49
  end
50
50
 
@@ -74,7 +74,7 @@ module ActiveHarness
74
74
  stream: nil
75
75
  )
76
76
  @input = input
77
- @config = self.class.agent_config
77
+ @config = self.class.request_config
78
78
  normalize_input!
79
79
  @context = context
80
80
  @params = params
@@ -89,8 +89,8 @@ module ActiveHarness
89
89
  # Raises Errors::AllModelsFailed if every model in the chain fails.
90
90
  #
91
91
  # Optionally accepts input and stream callback inline:
92
- # agent.call("What is the capital of Japan?")
93
- # agent.call("...", stream: ->(token) { print token })
92
+ # request.call("What is the capital of Japan?")
93
+ # request.call("...", stream: ->(token) { print token })
94
94
  def call(input = nil, token: nil, stream: nil)
95
95
  if input
96
96
  @input = input
@@ -205,13 +205,13 @@ module ActiveHarness
205
205
  end
206
206
  end
207
207
 
208
- require_relative "agent/prompt"
209
- require_relative "agent/hooks"
210
- require_relative "agent/models"
211
- require_relative "agent/providers"
212
- require_relative "agent/output_parser"
213
- require_relative "agent/custom_llm_backend"
214
- require_relative "agent/cost"
215
- require_relative "agent/image"
216
- require_relative "agent/transcription"
208
+ require_relative "request/prompt"
209
+ require_relative "request/hooks"
210
+ require_relative "request/models"
211
+ require_relative "request/providers"
212
+ require_relative "request/output_parser"
213
+ require_relative "request/custom_llm_backend"
214
+ require_relative "request/cost"
215
+ require_relative "request/image"
216
+ require_relative "request/transcription"
217
217
 
@@ -33,16 +33,16 @@ module ActiveHarness
33
33
  # nil when pricing data is unavailable.
34
34
  CostBreakdown = Struct.new(:input, :output, :total, keyword_init: true)
35
35
 
36
- # Combined token + cost stats for a single agent call.
36
+ # Combined token + cost stats for a single request call.
37
37
  # tokens is always present (raw provider data).
38
38
  # cost is nil when pricing is unavailable.
39
39
  UsageInfo = Struct.new(:tokens, :cost, keyword_init: true)
40
40
 
41
- # Result returned by Agent#call (accessible via agent.result).
41
+ # Result returned by Request#call (accessible via request.result).
42
42
  #
43
43
  # result.input — original input string
44
44
  # result.output — raw string from the provider
45
- # result.processed — parsed Hash/Array for :json agents, raw string for :text
45
+ # result.processed — parsed Hash/Array for :json requests, raw string for :text
46
46
  # result.system_prompt — resolved system prompt string
47
47
  # result.model — ModelInfo (name, provider, temperature, context_window, pricing)
48
48
  # result.model_list — full model chain proxy
@@ -1,12 +1,12 @@
1
1
  module ActiveHarness
2
2
  class Tribunal
3
3
  class << self
4
- # Declare agents at the class level.
4
+ # Declare requests at the class level.
5
5
  #
6
- # agents PolitenessAgent, ConstructivenessAgent
7
- # agents [PolitenessAgent, ConstructivenessAgent]
8
- def agents(*list)
9
- tribunal_config[:agents] = list.flatten
6
+ # requests PolitenessRequest, ConstructivenessRequest
7
+ # requests [PolitenessRequest, ConstructivenessRequest]
8
+ def requests(*list)
9
+ tribunal_config[:requests] = list.flatten
10
10
  end
11
11
 
12
12
  # Class-level process block — defines how to compute the verdict from all results.
@@ -25,8 +25,8 @@ module ActiveHarness
25
25
  # :majority — verdict true when more than half of successful results evaluate to true
26
26
  #
27
27
  # Options:
28
- # may_fail: N — tolerate up to N agent errors before raising AllAgentsFailed
29
- # (default: nil — raise only when all agents fail, preserving legacy behavior)
28
+ # may_fail: N — tolerate up to N request errors before raising AllRequestsFailed
29
+ # (default: nil — raise only when all requests fail, preserving legacy behavior)
30
30
  #
31
31
  # The block receives a single Result and must return a truthy/falsy value.
32
32
  #
@@ -2,9 +2,9 @@ module ActiveHarness
2
2
  class Tribunal
3
3
  VALID_HOOKS = %i[
4
4
  before_call
5
- before_agent
6
- after_agent
7
- agent_error
5
+ before_request
6
+ after_request
7
+ request_error
8
8
  after_call
9
9
  before_verdict
10
10
  after_verdict
@@ -14,9 +14,9 @@ module ActiveHarness
14
14
  # Class-level hook registration.
15
15
  #
16
16
  # on :before_call do ... end
17
- # on :before_agent do |agent| ... end
18
- # on :after_agent do |result| ... end
19
- # on :agent_error do |name, error| ... end
17
+ # on :before_request do |request| ... end
18
+ # on :after_request do |result| ... end
19
+ # on :request_error do |name, error| ... end
20
20
  # on :after_call do |results, errors| ... end
21
21
  # on :before_verdict do |results| results end # transform hook
22
22
  # on :after_verdict do |verdict| ... end
@@ -32,12 +32,12 @@ module ActiveHarness
32
32
  # Rails-style aliases for +on+:
33
33
  #
34
34
  # before :call do ... end # → on :before_call
35
- # before :agent do |agent| end # → on :before_agent
35
+ # before :request do |request| end # → on :before_request
36
36
  # before :verdict do |results| end # → on :before_verdict (transform)
37
37
  # after :call do |r, e| end # → on :after_call
38
- # after :agent do |result| end # → on :after_agent
38
+ # after :request do |result| end # → on :after_request
39
39
  # after :verdict do |verdict| end # → on :after_verdict
40
- # callback :agent_error do |name, e| end # → on :agent_error
40
+ # callback :request_error do |name, e| end # → on :request_error
41
41
  def before(event, &block)
42
42
  on(:"before_#{event}", &block)
43
43
  end