active_harness 0.3.7 → 0.3.9

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 (59) hide show
  1. checksums.yaml +4 -4
  2. data/lib/active_harness/agent/goal_check.rb +26 -0
  3. data/lib/active_harness/agent/state.rb +90 -0
  4. data/lib/active_harness/agent.rb +734 -0
  5. data/lib/active_harness/core/errors.rb +11 -0
  6. data/lib/active_harness/http/client.rb +58 -11
  7. data/lib/active_harness/http/streaming_client.rb +33 -1
  8. data/lib/active_harness/pipeline/hooks.rb +1 -1
  9. data/lib/active_harness/providers/anthropic.rb +57 -8
  10. data/lib/active_harness/providers/base.rb +6 -0
  11. data/lib/active_harness/providers/deepseek.rb +7 -4
  12. data/lib/active_harness/providers/gpustack.rb +7 -4
  13. data/lib/active_harness/providers/groq.rb +7 -4
  14. data/lib/active_harness/providers/hetzner.rb +7 -4
  15. data/lib/active_harness/providers/mistral.rb +7 -4
  16. data/lib/active_harness/providers/ollama.rb +7 -4
  17. data/lib/active_harness/providers/ollama_cloud.rb +7 -4
  18. data/lib/active_harness/providers/openai.rb +7 -4
  19. data/lib/active_harness/providers/openrouter.rb +7 -4
  20. data/lib/active_harness/providers/tool_calling.rb +95 -0
  21. data/lib/active_harness/providers/xai.rb +7 -4
  22. data/lib/active_harness/railtie.rb +1 -1
  23. data/lib/active_harness/request/hooks.rb +1 -1
  24. data/lib/active_harness/request/providers.rb +15 -0
  25. data/lib/active_harness/request.rb +39 -7
  26. data/lib/active_harness/result.rb +4 -0
  27. data/lib/active_harness/tool/path_policy.rb +28 -0
  28. data/lib/active_harness/tool.rb +348 -0
  29. data/lib/active_harness/tribunal/hooks.rb +1 -1
  30. data/lib/active_harness.rb +4 -1
  31. data/lib/generators/active_harness/agent/agent_generator.rb +16 -0
  32. data/lib/generators/active_harness/agent/templates/agent.rb.tt +14 -0
  33. data/lib/generators/active_harness/goal/goal_generator.rb +16 -0
  34. data/lib/generators/active_harness/goal/templates/goal.rb.tt +14 -0
  35. data/lib/generators/active_harness/install/install_generator.rb +30 -25
  36. data/lib/generators/active_harness/install/templates/agents/files_agent.rb +39 -0
  37. data/lib/generators/active_harness/install/templates/concerns/agent_tracing.rb +36 -0
  38. data/lib/generators/active_harness/install/templates/concerns/pipeline_tracing.rb +28 -0
  39. data/lib/generators/active_harness/install/templates/concerns/request_tracing.rb +44 -0
  40. data/lib/generators/active_harness/install/templates/concerns/tool_tracing.rb +21 -0
  41. data/lib/generators/active_harness/install/templates/concerns/tracing_log.rb +18 -0
  42. data/lib/generators/active_harness/install/templates/concerns/tribunal_tracing.rb +28 -0
  43. data/lib/generators/active_harness/install/templates/controllers/ai_controller.rb +23 -2
  44. data/lib/generators/active_harness/install/templates/goals/answer_present_goal.rb +22 -0
  45. data/lib/generators/active_harness/install/templates/pipelines/support_pipeline.rb +8 -18
  46. data/lib/generators/active_harness/install/templates/prompts/files_prompt.rb +9 -0
  47. data/lib/generators/active_harness/install/templates/prompts/support_prompt.rb +3 -0
  48. data/lib/generators/active_harness/install/templates/requests/files_request.rb +18 -0
  49. data/lib/generators/active_harness/install/templates/requests/support_guard_request.rb +12 -0
  50. data/lib/generators/active_harness/install/templates/requests/support_request.rb +6 -0
  51. data/lib/generators/active_harness/install/templates/tools/ls_tool.rb +40 -0
  52. data/lib/generators/active_harness/install/templates/tools/read_file_tool.rb +43 -0
  53. data/lib/generators/active_harness/install/templates/tribunals/support_guard_tribunal.rb +7 -0
  54. data/lib/generators/active_harness/pipeline/templates/pipeline.rb.tt +5 -0
  55. data/lib/generators/active_harness/request/templates/request.rb.tt +2 -0
  56. data/lib/generators/active_harness/tool/templates/tool.rb.tt +22 -0
  57. data/lib/generators/active_harness/tool/tool_generator.rb +16 -0
  58. data/lib/generators/active_harness/tribunal/templates/tribunal.rb.tt +3 -0
  59. metadata +31 -7
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 54990f7712275af0727b75a7518f30f2ecf5ea798fb14548b5872997a6b272bc
4
- data.tar.gz: d81370f86507d5c1d42bc12aac01209afb76bed63d2bb5545bb686ef85cb49ad
3
+ metadata.gz: 5549eddee812779586e86bbe67c2e72d077b7770b98421b0a43dbcb6dca7ab00
4
+ data.tar.gz: 9249bc587c75dfe7f0d5141b311f46ba0dd4b90b308873b31535044bd7f37ef2
5
5
  SHA512:
6
- metadata.gz: cc8f9d543c65fde9818611d68073b2a7de6285f37c815d81e6730c393103add7aed45ba9c9eaf7e9461ddbcf5062b3bd2e4978b3b690cbb190b0cf8eeff19209
7
- data.tar.gz: 73f6e6c7a606e4f36bebf8cdd330534d4a37fd028f5f53fd2133c8a365caa136e62e5044baa54518b9ff8883022a6c4e4af4a01f84ea0d4bb4798c0d9b584d01
6
+ metadata.gz: 6aebf953bbb38bb9c7f3ea943b2b1f10efefaa7371ba34b8aad0e8f921a42749731af2473723fc870a4581f84cbf364315435ce18d7f5f78809c175de86607dd
7
+ data.tar.gz: ee32ce687ef93c1616b659ed39ca49e0ac9c98705a7f2768e00bf5ba207cdae92938eaad8012cd8b9bad6413b489419cc7f3c2e3dc6138d8ad5af91dd8bc5ec1
@@ -0,0 +1,26 @@
1
+ module ActiveHarness
2
+ # Base class for goal checks — the judge of an Agent's final answer.
3
+ # Subclass it and override #call:
4
+ #
5
+ # class TestsPassedGoal < ActiveHarness::GoalCheck
6
+ # def call(candidate:, goal:, state:)
7
+ # ok = system("bin/rails test")
8
+ # ActiveHarness::GoalCheckResult.new(status: ok ? :achieved : :continue, feedback: "Tests are failing")
9
+ # end
10
+ # end
11
+ #
12
+ # class DevelopmentAgent < ActiveHarness::Agent
13
+ # goal TestsPassedGoal
14
+ # end
15
+ #
16
+ # The base class exists to fail loudly: forgetting to define #call raises an error
17
+ # that names the class, instead of an obscure NoMethodError mid-run. It is optional —
18
+ # any object (or a block) with call(candidate:, goal:, state:) is accepted by `goal`.
19
+ class GoalCheck
20
+ def call(candidate:, goal:, state:)
21
+ raise NotImplementedError,
22
+ "#{self.class.name || 'GoalCheck'} must define #call(candidate:, goal:, state:) " \
23
+ "returning an ActiveHarness::GoalCheckResult"
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,90 @@
1
+ module ActiveHarness
2
+ # Intent + verification criteria of one Agent run. Built by +setup+ (a Pipeline);
3
+ # without +setup+ the runtime makes a minimal Goal from the input.
4
+ Goal = Struct.new(:task, :constraints, :success_criteria, :plan, keyword_init: true) do
5
+ def self.from(value)
6
+ case value
7
+ when Goal then value
8
+ when String then value.strip.empty? ? nil : new(task: value)
9
+ when Hash
10
+ h = value.each_with_object({}) { |(k, v), acc| acc[k.to_sym] = v }
11
+ return nil if h[:task].to_s.strip.empty?
12
+
13
+ new(task: h[:task], constraints: h[:constraints], success_criteria: h[:success_criteria], plan: h[:plan])
14
+ end
15
+ end
16
+ end
17
+
18
+ # What a goal check returns.
19
+ #
20
+ # status — :achieved | :continue | :replan | :needs_input | :failed
21
+ # feedback — text for the model (for :continue / :replan)
22
+ # evidence — anything useful for the trace
23
+ GoalCheckResult = Struct.new(:status, :feedback, :evidence, keyword_init: true) do
24
+ def self.from(value)
25
+ case value
26
+ when GoalCheckResult then value
27
+ when true then new(status: :achieved)
28
+ when false, nil then new(status: :continue, feedback: "The goal is not reached yet.")
29
+ when Hash
30
+ new(status: (value[:status] || value["status"])&.to_sym, feedback: value[:feedback] || value["feedback"], evidence: value[:evidence] || value["evidence"])
31
+ else
32
+ raise ArgumentError, "A goal check must return GoalCheckResult (or true/false/Hash), got #{value.class}"
33
+ end
34
+ end
35
+ end
36
+
37
+ # One tool call requested by the model, with its outcome.
38
+ #
39
+ # id — provider call id; the tool message is bound to it
40
+ # name — wire name (os__ls)
41
+ # tool — Tool class (nil for an unknown name)
42
+ # input — validated arguments (symbol keys) or the raw value if invalid
43
+ # result — ToolResult
44
+ ToolCall = Struct.new(:id, :name, :tool, :input, :result, keyword_init: true) do
45
+ def full_name
46
+ tool ? tool.full_name : name
47
+ end
48
+
49
+ def denied?
50
+ !result.nil? && result.status == :denied
51
+ end
52
+
53
+ def error?
54
+ !result.nil? && result.status == :error
55
+ end
56
+ end
57
+
58
+ # One iteration of the Agent loop.
59
+ AgentTurn = Struct.new(:number, :request_name, :request, :response, :tool_calls, :steps, :verdict, :ms, keyword_init: true) do
60
+ def denied_calls
61
+ tool_calls.select(&:denied?)
62
+ end
63
+
64
+ def error_calls
65
+ tool_calls.select(&:error?)
66
+ end
67
+ end
68
+
69
+ # Trustworthy data of one run — the source of truth. The model-facing context is
70
+ # only a view built from it by the context builder.
71
+ #
72
+ # messages — normalized conversation (user / assistant / tool)
73
+ # events — full journal of turns, denials, tool calls and results; never summarized
74
+ AgentState = Struct.new(
75
+ :run_id, :status, :input, :goal, :plan,
76
+ :messages, :events, :artifacts,
77
+ :turn, :tool_calls_count, :usage, :stop_reason,
78
+ keyword_init: true
79
+ )
80
+
81
+ class Agent
82
+ # Default context builder: the full history. A custom builder is any object with
83
+ # call(state:) returning a messages array (a shortened view, never a rewrite of the state).
84
+ class ContextBuilder
85
+ def call(state:)
86
+ state.messages.map(&:dup)
87
+ end
88
+ end
89
+ end
90
+ end