active_harness 0.1.0 → 0.2.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 (57) hide show
  1. checksums.yaml +4 -4
  2. data/lib/active_harness/agent/hooks.rb +75 -0
  3. data/lib/active_harness/agent/models.rb +147 -0
  4. data/lib/active_harness/agent/output_parser.rb +57 -0
  5. data/lib/active_harness/agent/prompt.rb +58 -0
  6. data/lib/active_harness/agent/providers.rb +54 -0
  7. data/lib/active_harness/agent.rb +107 -228
  8. data/lib/active_harness/core/errors.rb +22 -28
  9. data/lib/active_harness/http/client.rb +8 -19
  10. data/lib/active_harness/http/streaming_client.rb +60 -0
  11. data/lib/active_harness/memory/adapter/base.rb +36 -0
  12. data/lib/active_harness/memory/adapter/file.rb +141 -0
  13. data/lib/active_harness/memory.rb +212 -0
  14. data/lib/active_harness/pipeline/step.rb +36 -0
  15. data/lib/active_harness/pipeline.rb +207 -0
  16. data/lib/active_harness/providers/PROVIDER_CONTRACT.md +54 -0
  17. data/lib/active_harness/providers/anthropic.rb +76 -4
  18. data/lib/active_harness/providers/base.rb +41 -13
  19. data/lib/active_harness/providers/gemini.rb +61 -0
  20. data/lib/active_harness/providers/groq.rb +64 -0
  21. data/lib/active_harness/providers/openai.rb +39 -47
  22. data/lib/active_harness/providers/openrouter.rb +40 -54
  23. data/lib/active_harness/railtie.rb +12 -0
  24. data/lib/active_harness/result.rb +10 -0
  25. data/lib/active_harness/tribunal.rb +215 -0
  26. data/lib/active_harness.rb +17 -46
  27. data/lib/generators/active_harness/install/install_generator.rb +54 -0
  28. data/lib/generators/active_harness/install/templates/agents/test_support_agent.rb +10 -0
  29. data/lib/generators/active_harness/install/templates/agents/test_support_guard_agent.rb +11 -0
  30. data/lib/generators/active_harness/install/templates/controllers/ai_controller.rb +105 -0
  31. data/lib/generators/active_harness/install/templates/memory/test_support_memory.rb +16 -0
  32. data/lib/generators/active_harness/install/templates/pipelines/test_support_pipeline.rb +31 -0
  33. data/lib/generators/active_harness/install/templates/prompts/test_support_guard_prompt.rb +9 -0
  34. data/lib/generators/active_harness/install/templates/prompts/test_support_prompt.rb +5 -0
  35. data/lib/generators/active_harness/install/templates/tribunals/test_support_guard_tribunal.rb +11 -0
  36. metadata +32 -72
  37. data/LICENSE +0 -21
  38. data/README.md +0 -113
  39. data/lib/active_harness/core/configuration.rb +0 -55
  40. data/lib/active_harness/core/version.rb +0 -3
  41. data/lib/active_harness/http/retry_policy.rb +0 -47
  42. data/lib/active_harness/models/model_request.rb +0 -14
  43. data/lib/active_harness/models/model_response.rb +0 -13
  44. data/lib/active_harness/payload.rb +0 -47
  45. data/lib/active_harness/pipeline/engine.rb +0 -251
  46. data/lib/active_harness/pipeline/fallback_runner.rb +0 -76
  47. data/lib/active_harness/pipeline/guard_runner.rb +0 -125
  48. data/lib/active_harness/pipeline/output_parser.rb +0 -43
  49. data/lib/active_harness/pipeline/prompt_builder.rb +0 -46
  50. data/lib/active_harness/pipeline/provider_registry.rb +0 -16
  51. data/lib/active_harness/prompts/guard_system_prompt.rb +0 -33
  52. data/lib/active_harness/providers/google.rb +0 -11
  53. data/lib/active_harness/rate_limit/request_limiter.rb +0 -50
  54. data/lib/active_harness/rate_limit/risk_holdback.rb +0 -69
  55. data/lib/active_harness/results/debug_result.rb +0 -19
  56. data/lib/active_harness/results/input_result.rb +0 -27
  57. data/lib/active_harness/results/result.rb +0 -55
metadata CHANGED
@@ -1,110 +1,70 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_harness
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
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-06 00:00:00.000000000 Z
11
+ date: 2026-05-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: json
14
+ name: concurrent-ruby
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '2.0'
19
+ version: '1.3'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '2.0'
27
- - !ruby/object:Gem::Dependency
28
- name: minitest
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - "~>"
32
- - !ruby/object:Gem::Version
33
- version: '5.0'
34
- type: :development
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - "~>"
39
- - !ruby/object:Gem::Version
40
- version: '5.0'
41
- - !ruby/object:Gem::Dependency
42
- name: minitest-reporters
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - "~>"
46
- - !ruby/object:Gem::Version
47
- version: '1.0'
48
- type: :development
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - "~>"
53
- - !ruby/object:Gem::Version
54
- version: '1.0'
55
- - !ruby/object:Gem::Dependency
56
- name: mocha
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - "~>"
60
- - !ruby/object:Gem::Version
61
- version: '2.0'
62
- type: :development
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - "~>"
67
- - !ruby/object:Gem::Version
68
- version: '2.0'
69
- description: |
70
- ActiveHarness provides a DSL for describing AI agents and an engine for
71
- their execution, with built-in prompt-injection protection (guard layer),
72
- provider fallback chains, and a structured result API.
26
+ version: '1.3'
27
+ description:
73
28
  email:
74
29
  - the-teacher@github.com
75
30
  executables: []
76
31
  extensions: []
77
32
  extra_rdoc_files: []
78
33
  files:
79
- - LICENSE
80
- - README.md
81
34
  - lib/active_harness.rb
82
35
  - lib/active_harness/agent.rb
83
- - lib/active_harness/core/configuration.rb
36
+ - lib/active_harness/agent/hooks.rb
37
+ - lib/active_harness/agent/models.rb
38
+ - lib/active_harness/agent/output_parser.rb
39
+ - lib/active_harness/agent/prompt.rb
40
+ - lib/active_harness/agent/providers.rb
84
41
  - lib/active_harness/core/errors.rb
85
- - lib/active_harness/core/version.rb
86
42
  - lib/active_harness/http/client.rb
87
- - lib/active_harness/http/retry_policy.rb
88
- - lib/active_harness/models/model_request.rb
89
- - lib/active_harness/models/model_response.rb
90
- - lib/active_harness/payload.rb
91
- - lib/active_harness/pipeline/engine.rb
92
- - lib/active_harness/pipeline/fallback_runner.rb
93
- - lib/active_harness/pipeline/guard_runner.rb
94
- - lib/active_harness/pipeline/output_parser.rb
95
- - lib/active_harness/pipeline/prompt_builder.rb
96
- - lib/active_harness/pipeline/provider_registry.rb
97
- - lib/active_harness/prompts/guard_system_prompt.rb
43
+ - lib/active_harness/http/streaming_client.rb
44
+ - lib/active_harness/memory.rb
45
+ - lib/active_harness/memory/adapter/base.rb
46
+ - lib/active_harness/memory/adapter/file.rb
47
+ - lib/active_harness/pipeline.rb
48
+ - lib/active_harness/pipeline/step.rb
49
+ - lib/active_harness/providers/PROVIDER_CONTRACT.md
98
50
  - lib/active_harness/providers/anthropic.rb
99
51
  - lib/active_harness/providers/base.rb
100
- - lib/active_harness/providers/google.rb
52
+ - lib/active_harness/providers/gemini.rb
53
+ - lib/active_harness/providers/groq.rb
101
54
  - lib/active_harness/providers/openai.rb
102
55
  - lib/active_harness/providers/openrouter.rb
103
- - lib/active_harness/rate_limit/request_limiter.rb
104
- - lib/active_harness/rate_limit/risk_holdback.rb
105
- - lib/active_harness/results/debug_result.rb
106
- - lib/active_harness/results/input_result.rb
107
- - lib/active_harness/results/result.rb
56
+ - lib/active_harness/railtie.rb
57
+ - lib/active_harness/result.rb
58
+ - lib/active_harness/tribunal.rb
59
+ - lib/generators/active_harness/install/install_generator.rb
60
+ - lib/generators/active_harness/install/templates/agents/test_support_agent.rb
61
+ - lib/generators/active_harness/install/templates/agents/test_support_guard_agent.rb
62
+ - lib/generators/active_harness/install/templates/controllers/ai_controller.rb
63
+ - lib/generators/active_harness/install/templates/memory/test_support_memory.rb
64
+ - lib/generators/active_harness/install/templates/pipelines/test_support_pipeline.rb
65
+ - lib/generators/active_harness/install/templates/prompts/test_support_guard_prompt.rb
66
+ - lib/generators/active_harness/install/templates/prompts/test_support_prompt.rb
67
+ - lib/generators/active_harness/install/templates/tribunals/test_support_guard_tribunal.rb
108
68
  homepage: https://github.com/the-teacher/active_harness
109
69
  licenses:
110
70
  - MIT
data/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 the-teacher
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
data/README.md DELETED
@@ -1,113 +0,0 @@
1
- # ActiveHarness
2
-
3
- ActiveHarness is a lightweight framework for building production-ready AI agents in Ruby and Rails.
4
-
5
- Instead of relying on complex orchestration frameworks, ActiveHarness provides a simple and transparent DSL to define agents as plain Ruby classes.
6
-
7
- ## Features
8
-
9
- - Agent-based architecture (Rails-style DSL)
10
- - Built-in guard layer (prompt injection, toxicity, relevance checks)
11
- - Multi-provider support: OpenAI, Anthropic, Google, OpenRouter
12
- - Automatic fallback between models and providers
13
- - Per-call language and translation support
14
- - Structured outputs (text / JSON with schema validation)
15
- - Input constraints (e.g. `max_input_length`)
16
- - Debug mode with full prompt visibility
17
- - Minimal dependencies, no magic
18
-
19
- ## Installation
20
-
21
- ```ruby
22
- # Gemfile
23
- gem "active_harness"
24
- ```
25
-
26
- ```bash
27
- bundle install
28
- ```
29
-
30
- Or install directly:
31
-
32
- ```bash
33
- gem install active_harness
34
- ```
35
-
36
- ## Quick Start
37
-
38
- ### 1. Configure
39
-
40
- ```ruby
41
- ActiveHarness.configure do |config|
42
- config.openai_api_key = ENV["OPENAI_API_KEY"]
43
- config.openrouter_api_key = ENV["OPENROUTER_API_KEY"]
44
- config.default_temperature = 0.2
45
- config.default_timeout = 30
46
- end
47
- ```
48
-
49
- ### 2. Define an agent
50
-
51
- ```ruby
52
- class SupportAgent < ActiveHarness::Agent
53
- guard InjectionGuard
54
-
55
- model do
56
- use provider: :openai, model: "gpt-4.1-mini"
57
- fallback provider: :openrouter, model: "meta-llama/llama-3.3-70b-instruct:free"
58
- end
59
-
60
- system_prompt "You are a helpful support assistant."
61
- output :text
62
- end
63
- ```
64
-
65
- ### 3. Call it
66
-
67
- ```ruby
68
- # One-liner
69
- result = SupportAgent.call(input: "How do I get started?")
70
-
71
- # Instance style
72
- agent = SupportAgent.new(input: "How do I get started?", language: :ru)
73
- result = agent.call
74
-
75
- puts result.output if result.success?
76
- puts result.output if result.blocked? # default_error_answer
77
- ```
78
-
79
- ## Guards
80
-
81
- Guards run before the main request and can block it:
82
-
83
- ```ruby
84
- class InjectionGuard < ActiveHarness::Agent
85
- model { use provider: :openai, model: "gpt-4.1-mini" }
86
- system_language :en
87
- risk_tolerance :low
88
- end
89
-
90
- # Register on the main agent:
91
- guard InjectionGuard, name: :injection_guard
92
- guard ToxicityGuard, name: :toxicity_guard
93
- ```
94
-
95
- ## Result API
96
-
97
- ```ruby
98
- result.success? # true / false
99
- result.blocked? # true / false (guard rejected)
100
- result.failed? # true / false (error / timeout)
101
- result.output # String
102
- result.model # "gpt-4.1-mini"
103
- result.provider # :openai
104
- ```
105
-
106
- ## Requirements
107
-
108
- - Ruby >= 2.6
109
- - API key for at least one supported provider (OpenAI, Anthropic, Google, OpenRouter)
110
-
111
- ## License
112
-
113
- MIT — see [LICENSE](LICENSE).
@@ -1,55 +0,0 @@
1
- module ActiveHarness
2
- class Configuration
3
- attr_accessor :openai_api_key,
4
- :openrouter_api_key,
5
- :anthropic_api_key,
6
- :google_api_key,
7
- :default_timeout,
8
- :default_temperature,
9
- :default_language,
10
- :guard_retries,
11
- :log_requests,
12
- :log_responses,
13
- :debug,
14
- :on_model_attempt,
15
- :on_model_failure
16
-
17
- def initialize
18
- @openai_api_key = ENV["OPENAI_API_KEY"]
19
- @openrouter_api_key = ENV["OPENROUTER_API_KEY"]
20
- @anthropic_api_key = ENV["ANTHROPIC_API_KEY"]
21
- @google_api_key = ENV["GOOGLE_API_KEY"]
22
-
23
- @default_timeout = 20
24
- @default_temperature = 0.2
25
- @default_language = :en
26
- @guard_retries = 2 # up to 3 total attempts (1 initial + 2 retries)
27
-
28
- @log_requests = true
29
- @log_responses = false
30
- @debug = false
31
- end
32
-
33
- # HTTP transport. Swap for a Faraday-backed client if needed:
34
- # config.http_client = MyFaradayClient.new
35
- # Set to nil to let providers manage their own transport.
36
- def http_client
37
- @http_client ||= Http::Client.new
38
- end
39
- attr_writer :http_client
40
-
41
- # Sliding-window rate limiter (10 req/min per user_id by default).
42
- # Set to nil to disable.
43
- def request_limiter
44
- @request_limiter ||= RateLimit::RequestLimiter.new
45
- end
46
- attr_writer :request_limiter
47
-
48
- # Progressive hold-back after repeated risky requests.
49
- # Set to nil to disable.
50
- def risk_holdback
51
- @risk_holdback ||= RateLimit::RiskHoldback.new
52
- end
53
- attr_writer :risk_holdback
54
- end
55
- end
@@ -1,3 +0,0 @@
1
- module ActiveHarness
2
- VERSION = "0.1.0"
3
- end
@@ -1,47 +0,0 @@
1
- module ActiveHarness
2
- module Http
3
- # Wraps a block with automatic retry on transient errors.
4
- # Uses exponential backoff: delay doubles after each failed attempt.
5
- #
6
- # Example:
7
- # RetryPolicy.new(max_attempts: 3, base_delay: 0.5).run do
8
- # http_client.post(...)
9
- # end
10
- #
11
- # Custom errors:
12
- # RetryPolicy.new(errors: [MyTransientError]).run { ... }
13
- #
14
- class RetryPolicy
15
- DEFAULT_ERRORS = [
16
- Errors::TimeoutError,
17
- Errors::RateLimitError,
18
- Errors::ProviderUnavailableError,
19
- Errors::ServerError
20
- ].freeze
21
-
22
- # @param max_attempts [Integer] total number of attempts (first + retries)
23
- # @param base_delay [Float] delay before 1st retry in seconds; doubles each round
24
- # @param errors [Array] error classes that trigger a retry
25
- def initialize(max_attempts: 3, base_delay: 1.0, errors: DEFAULT_ERRORS)
26
- @max_attempts = max_attempts
27
- @base_delay = base_delay
28
- @errors = errors
29
- end
30
-
31
- # @yieldreturn [Object] result of the block on success
32
- # @raise last error after all attempts are exhausted
33
- def run
34
- attempt = 0
35
- begin
36
- attempt += 1
37
- yield
38
- rescue *@errors
39
- raise if attempt >= @max_attempts
40
-
41
- sleep(@base_delay * (2**(attempt - 1)))
42
- retry
43
- end
44
- end
45
- end
46
- end
47
- end
@@ -1,14 +0,0 @@
1
- module ActiveHarness
2
- class ModelRequest
3
- attr_reader :provider, :model, :messages, :temperature, :timeout, :response_format
4
-
5
- def initialize(provider:, model:, messages:, temperature: nil, timeout: nil, response_format: nil)
6
- @provider = provider
7
- @model = model
8
- @messages = messages
9
- @temperature = temperature || ActiveHarness.config.default_temperature
10
- @timeout = timeout || ActiveHarness.config.default_timeout
11
- @response_format = response_format
12
- end
13
- end
14
- end
@@ -1,13 +0,0 @@
1
- module ActiveHarness
2
- class ModelResponse
3
- attr_reader :content, :provider, :model, :usage, :raw
4
-
5
- def initialize(content:, provider:, model:, usage: {}, raw: nil)
6
- @content = content
7
- @provider = provider
8
- @model = model
9
- @usage = usage
10
- @raw = raw
11
- end
12
- end
13
- end
@@ -1,47 +0,0 @@
1
- module ActiveHarness
2
- # Unified request context — the single object available in every hook.
3
- #
4
- # Passed as the first argument to ALL before/after callbacks and to +setup+:
5
- #
6
- # setup do |payload|
7
- # payload.meta[:started_at] = Time.now
8
- # payload # must return payload
9
- # end
10
- #
11
- # before :guards do |payload, input| # input = String
12
- # input.strip # return new String
13
- # end
14
- #
15
- # after :guards do |payload, result| # result = InputResult
16
- # result # return InputResult
17
- # end
18
- #
19
- # before :request do |payload, prompt| # prompt = { system:, user: }
20
- # prompt # return Hash
21
- # end
22
- #
23
- # after :request do |payload, response| # response = ModelResponse
24
- # response # return ModelResponse
25
- # end
26
- #
27
- # Fields:
28
- # input — raw input text; can be modified in +setup+
29
- # context — runtime context Hash (e.g. user_id, session data)
30
- # language — language hint for guards / responses (e.g. :ru, :en)
31
- # translate — callable: translate.(key) → localized string; key format: "scope.agent.message"
32
- # typically built by an I18n module: Playground::I18n.translator(locale: language)
33
- # options — per-guard static options set at registration time
34
- # meta — free-form Hash for user-defined data (timestamps, flags, …)
35
- class Payload
36
- attr_accessor :input, :context, :language, :translate, :options, :meta
37
-
38
- def initialize(input:, context: {}, language: nil, translate: nil, options: {}, meta: {})
39
- @input = input
40
- @context = context
41
- @language = language
42
- @translate = translate
43
- @options = options
44
- @meta = meta
45
- end
46
- end
47
- end