agentic 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.
- checksums.yaml +4 -4
- data/.agentic.yml +2 -0
- data/.architecture/decisions/ArchitecturalFeatureBuilder.md +136 -0
- data/.architecture/decisions/ArchitectureConsiderations.md +200 -0
- data/.architecture/decisions/adr_001_observer_pattern_implementation.md +196 -0
- data/.architecture/decisions/adr_002_plan_orchestrator.md +320 -0
- data/.architecture/decisions/adr_003_plan_orchestrator_interface.md +179 -0
- data/.architecture/decisions/adrs/ADR-001-dependency-management.md +147 -0
- data/.architecture/decisions/adrs/ADR-002-system-boundaries.md +162 -0
- data/.architecture/decisions/adrs/ADR-003-content-safety.md +158 -0
- data/.architecture/decisions/adrs/ADR-004-agent-permissions.md +161 -0
- data/.architecture/decisions/adrs/ADR-005-adaptation-engine.md +127 -0
- data/.architecture/decisions/adrs/ADR-006-extension-system.md +273 -0
- data/.architecture/decisions/adrs/ADR-007-learning-system.md +156 -0
- data/.architecture/decisions/adrs/ADR-008-prompt-generation.md +325 -0
- data/.architecture/decisions/adrs/ADR-009-task-failure-handling.md +353 -0
- data/.architecture/decisions/adrs/ADR-010-task-input-handling.md +251 -0
- data/.architecture/decisions/adrs/ADR-011-task-observable-pattern.md +391 -0
- data/.architecture/decisions/adrs/ADR-012-task-output-handling.md +205 -0
- data/.architecture/decisions/adrs/ADR-013-architecture-alignment.md +211 -0
- data/.architecture/decisions/adrs/ADR-014-agent-capability-registry.md +80 -0
- data/.architecture/decisions/adrs/ADR-015-persistent-agent-store.md +100 -0
- data/.architecture/decisions/adrs/ADR-016-agent-assembly-engine.md +117 -0
- data/.architecture/decisions/adrs/ADR-017-streaming-observability.md +171 -0
- data/.architecture/decisions/capability_tools_distinction.md +150 -0
- data/.architecture/decisions/cli_command_structure.md +61 -0
- data/.architecture/implementation/agent_self_assembly_implementation.md +267 -0
- data/.architecture/implementation/agent_self_assembly_summary.md +138 -0
- data/.architecture/members.yml +187 -0
- data/.architecture/planning/self_implementation_exercise.md +295 -0
- data/.architecture/planning/session_compaction_rule.md +43 -0
- data/.architecture/planning/streaming_observability_feature.md +223 -0
- data/.architecture/principles.md +151 -0
- data/.architecture/recalibration/0-2-0.md +92 -0
- data/.architecture/recalibration/agent_self_assembly.md +238 -0
- data/.architecture/recalibration/cli_command_structure.md +91 -0
- data/.architecture/recalibration/implementation_roadmap_0-2-0.md +301 -0
- data/.architecture/recalibration/progress_tracking_0-2-0.md +114 -0
- data/.architecture/recalibration_process.md +127 -0
- data/.architecture/reviews/0-2-0.md +181 -0
- data/.architecture/reviews/cli_command_duplication.md +98 -0
- data/.architecture/templates/adr.md +105 -0
- data/.architecture/templates/implementation_roadmap.md +125 -0
- data/.architecture/templates/progress_tracking.md +89 -0
- data/.architecture/templates/recalibration_plan.md +70 -0
- data/.architecture/templates/version_comparison.md +124 -0
- data/.claude/settings.local.json +13 -0
- data/.claude-sessions/001-task-class-architecture-implementation.md +129 -0
- data/.claude-sessions/002-plan-orchestrator-interface-review.md +105 -0
- data/.claude-sessions/architecture-governance-implementation.md +37 -0
- data/.claude-sessions/architecture-review-session.md +27 -0
- data/ArchitecturalFeatureBuilder.md +136 -0
- data/ArchitectureConsiderations.md +229 -0
- data/CHANGELOG.md +57 -2
- data/CLAUDE.md +111 -0
- data/CONTRIBUTING.md +286 -0
- data/MAINTAINING.md +301 -0
- data/README.md +582 -28
- data/docs/agent_capabilities_api.md +259 -0
- data/docs/artifact_extension_points.md +757 -0
- data/docs/artifact_generation_architecture.md +323 -0
- data/docs/artifact_implementation_plan.md +596 -0
- data/docs/artifact_integration_points.md +345 -0
- data/docs/artifact_verification_strategies.md +581 -0
- data/docs/streaming_observability_architecture.md +510 -0
- data/exe/agentic +6 -1
- data/lefthook.yml +5 -0
- data/lib/agentic/adaptation_engine.rb +124 -0
- data/lib/agentic/agent.rb +181 -4
- data/lib/agentic/agent_assembly_engine.rb +442 -0
- data/lib/agentic/agent_capability_registry.rb +260 -0
- data/lib/agentic/agent_config.rb +63 -0
- data/lib/agentic/agent_specification.rb +46 -0
- data/lib/agentic/capabilities/examples.rb +530 -0
- data/lib/agentic/capabilities.rb +14 -0
- data/lib/agentic/capability_provider.rb +146 -0
- data/lib/agentic/capability_specification.rb +118 -0
- data/lib/agentic/cli/agent.rb +31 -0
- data/lib/agentic/cli/capabilities.rb +191 -0
- data/lib/agentic/cli/config.rb +134 -0
- data/lib/agentic/cli/execution_observer.rb +796 -0
- data/lib/agentic/cli.rb +1068 -0
- data/lib/agentic/default_agent_provider.rb +35 -0
- data/lib/agentic/errors/llm_error.rb +184 -0
- data/lib/agentic/execution_plan.rb +53 -0
- data/lib/agentic/execution_result.rb +91 -0
- data/lib/agentic/expected_answer_format.rb +46 -0
- data/lib/agentic/extension/domain_adapter.rb +109 -0
- data/lib/agentic/extension/plugin_manager.rb +163 -0
- data/lib/agentic/extension/protocol_handler.rb +116 -0
- data/lib/agentic/extension.rb +45 -0
- data/lib/agentic/factory_methods.rb +9 -1
- data/lib/agentic/generation_stats.rb +61 -0
- data/lib/agentic/learning/README.md +84 -0
- data/lib/agentic/learning/capability_optimizer.rb +613 -0
- data/lib/agentic/learning/execution_history_store.rb +251 -0
- data/lib/agentic/learning/pattern_recognizer.rb +500 -0
- data/lib/agentic/learning/strategy_optimizer.rb +706 -0
- data/lib/agentic/learning.rb +131 -0
- data/lib/agentic/llm_assisted_composition_strategy.rb +188 -0
- data/lib/agentic/llm_client.rb +215 -15
- data/lib/agentic/llm_config.rb +65 -1
- data/lib/agentic/llm_response.rb +163 -0
- data/lib/agentic/logger.rb +1 -1
- data/lib/agentic/observable.rb +51 -0
- data/lib/agentic/persistent_agent_store.rb +385 -0
- data/lib/agentic/plan_execution_result.rb +129 -0
- data/lib/agentic/plan_orchestrator.rb +464 -0
- data/lib/agentic/plan_orchestrator_config.rb +57 -0
- data/lib/agentic/retry_config.rb +63 -0
- data/lib/agentic/retry_handler.rb +125 -0
- data/lib/agentic/structured_outputs.rb +1 -1
- data/lib/agentic/task.rb +193 -0
- data/lib/agentic/task_definition.rb +39 -0
- data/lib/agentic/task_execution_result.rb +92 -0
- data/lib/agentic/task_failure.rb +66 -0
- data/lib/agentic/task_output_schemas.rb +112 -0
- data/lib/agentic/task_planner.rb +54 -19
- data/lib/agentic/task_result.rb +48 -0
- data/lib/agentic/ui.rb +244 -0
- data/lib/agentic/verification/critic_framework.rb +116 -0
- data/lib/agentic/verification/llm_verification_strategy.rb +60 -0
- data/lib/agentic/verification/schema_verification_strategy.rb +47 -0
- data/lib/agentic/verification/verification_hub.rb +62 -0
- data/lib/agentic/verification/verification_result.rb +50 -0
- data/lib/agentic/verification/verification_strategy.rb +26 -0
- data/lib/agentic/version.rb +1 -1
- data/lib/agentic.rb +74 -2
- data/plugins/README.md +41 -0
- metadata +245 -6
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Agentic
|
4
|
+
# Configuration object for the RetryHandler
|
5
|
+
class RetryConfig
|
6
|
+
# @return [Integer] The maximum number of retry attempts
|
7
|
+
attr_accessor :max_retries
|
8
|
+
|
9
|
+
# @return [Array<Class, String>] List of retryable error types/names
|
10
|
+
attr_accessor :retryable_errors
|
11
|
+
|
12
|
+
# @return [Symbol] The backoff strategy to use
|
13
|
+
attr_accessor :backoff_strategy
|
14
|
+
|
15
|
+
# @return [Hash] Options for the backoff strategy
|
16
|
+
attr_accessor :backoff_options
|
17
|
+
|
18
|
+
# @return [Proc, nil] Optional block to run before each retry
|
19
|
+
attr_accessor :before_retry
|
20
|
+
|
21
|
+
# @return [Proc, nil] Optional block to run after each retry
|
22
|
+
attr_accessor :after_retry
|
23
|
+
|
24
|
+
# Initializes a new retry configuration
|
25
|
+
# @param max_retries [Integer] The maximum number of retry attempts
|
26
|
+
# @param retryable_errors [Array<Class, String>] List of retryable error types/names
|
27
|
+
# @param backoff_strategy [Symbol] The backoff strategy (:constant, :linear, :exponential)
|
28
|
+
# @param backoff_options [Hash] Options for the backoff strategy
|
29
|
+
# @param before_retry [Proc, nil] Optional block to run before each retry
|
30
|
+
# @param after_retry [Proc, nil] Optional block to run after each retry
|
31
|
+
def initialize(
|
32
|
+
max_retries: 3,
|
33
|
+
retryable_errors: [Errors::LlmTimeoutError, Errors::LlmRateLimitError, Errors::LlmServerError, Errors::LlmNetworkError],
|
34
|
+
backoff_strategy: :exponential,
|
35
|
+
backoff_options: {},
|
36
|
+
before_retry: nil,
|
37
|
+
after_retry: nil
|
38
|
+
)
|
39
|
+
@max_retries = max_retries
|
40
|
+
@retryable_errors = retryable_errors
|
41
|
+
@backoff_strategy = backoff_strategy
|
42
|
+
@backoff_options = {
|
43
|
+
base_delay: 1.0,
|
44
|
+
jitter_factor: 0.25
|
45
|
+
}.merge(backoff_options)
|
46
|
+
@before_retry = before_retry
|
47
|
+
@after_retry = after_retry
|
48
|
+
end
|
49
|
+
|
50
|
+
# Creates a RetryHandler from this configuration
|
51
|
+
# @return [RetryHandler] A new retry handler
|
52
|
+
def to_handler
|
53
|
+
RetryHandler.new(
|
54
|
+
max_retries: @max_retries,
|
55
|
+
retryable_errors: @retryable_errors,
|
56
|
+
backoff_strategy: @backoff_strategy,
|
57
|
+
backoff_options: @backoff_options,
|
58
|
+
before_retry: @before_retry,
|
59
|
+
after_retry: @after_retry
|
60
|
+
)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Agentic
|
4
|
+
# Handles retrying operations with configurable backoff strategies
|
5
|
+
class RetryHandler
|
6
|
+
# @return [Integer] The maximum number of retry attempts
|
7
|
+
attr_reader :max_retries
|
8
|
+
|
9
|
+
# @return [Array<Class, String>] List of retryable error types/names
|
10
|
+
attr_reader :retryable_errors
|
11
|
+
|
12
|
+
# @return [Symbol] The backoff strategy to use
|
13
|
+
attr_reader :backoff_strategy
|
14
|
+
|
15
|
+
# @return [Proc, nil] Optional block to run before each retry
|
16
|
+
attr_reader :before_retry
|
17
|
+
|
18
|
+
# @return [Proc, nil] Optional block to run after each retry
|
19
|
+
attr_reader :after_retry
|
20
|
+
|
21
|
+
# Initializes a new RetryHandler
|
22
|
+
# @param max_retries [Integer] The maximum number of retry attempts
|
23
|
+
# @param retryable_errors [Array<Class, String>] List of retryable error types/names
|
24
|
+
# @param backoff_strategy [Symbol] The backoff strategy (:constant, :linear, :exponential)
|
25
|
+
# @param backoff_options [Hash] Options for the backoff strategy
|
26
|
+
# @param before_retry [Proc, nil] Optional block to run before each retry
|
27
|
+
# @param after_retry [Proc, nil] Optional block to run after each retry
|
28
|
+
# @option backoff_options [Float] :base_delay The base delay in seconds
|
29
|
+
# @option backoff_options [Float] :jitter_factor The jitter factor (0.0-1.0)
|
30
|
+
def initialize(
|
31
|
+
max_retries: 3,
|
32
|
+
retryable_errors: [Errors::LlmTimeoutError, Errors::LlmRateLimitError, Errors::LlmServerError, Errors::LlmNetworkError],
|
33
|
+
backoff_strategy: :exponential,
|
34
|
+
backoff_options: {},
|
35
|
+
before_retry: nil,
|
36
|
+
after_retry: nil
|
37
|
+
)
|
38
|
+
@max_retries = max_retries
|
39
|
+
@retryable_errors = retryable_errors
|
40
|
+
@backoff_strategy = backoff_strategy
|
41
|
+
@backoff_options = {
|
42
|
+
base_delay: 1.0,
|
43
|
+
jitter_factor: 0.25
|
44
|
+
}.merge(backoff_options)
|
45
|
+
@before_retry = before_retry
|
46
|
+
@after_retry = after_retry
|
47
|
+
end
|
48
|
+
|
49
|
+
# Executes the given block with retries
|
50
|
+
# @param block [Proc] The block to execute with retries
|
51
|
+
# @return [Object] The return value of the block
|
52
|
+
# @raise [StandardError] If the block failed after all retries
|
53
|
+
def with_retry(&block)
|
54
|
+
attempt = 0
|
55
|
+
|
56
|
+
begin
|
57
|
+
attempt += 1
|
58
|
+
block.call
|
59
|
+
rescue => e
|
60
|
+
error = e.is_a?(Errors::LlmError) ? e : Errors::LlmError.new(e.message, context: {original_error: e.class.name})
|
61
|
+
|
62
|
+
if retryable?(error) && attempt <= max_retries
|
63
|
+
delay = calculate_backoff_delay(attempt)
|
64
|
+
Agentic.logger.info("Retry #{attempt}/#{max_retries} for error: #{error.message}. Waiting #{delay.round(2)}s before retrying.")
|
65
|
+
|
66
|
+
@before_retry&.call(attempt: attempt, error: error, delay: delay)
|
67
|
+
sleep(delay)
|
68
|
+
@after_retry&.call(attempt: attempt, error: error, delay: delay)
|
69
|
+
|
70
|
+
retry
|
71
|
+
else
|
72
|
+
if attempt > max_retries
|
73
|
+
Agentic.logger.error("Max retries (#{max_retries}) exceeded for error: #{error.message}")
|
74
|
+
else
|
75
|
+
Agentic.logger.error("Non-retryable error: #{error.message}")
|
76
|
+
end
|
77
|
+
|
78
|
+
raise error
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
# Determines if an error is retryable
|
86
|
+
# @param error [StandardError] The error to check
|
87
|
+
# @return [Boolean] True if the error is retryable
|
88
|
+
def retryable?(error)
|
89
|
+
return true if error.respond_to?(:retryable?) && error.retryable?
|
90
|
+
|
91
|
+
@retryable_errors.any? do |retryable|
|
92
|
+
if retryable.is_a?(Class)
|
93
|
+
error.is_a?(retryable)
|
94
|
+
else
|
95
|
+
error.instance_of?(retryable).to_s
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# Calculates the backoff delay for a given attempt
|
101
|
+
# @param attempt [Integer] The current attempt number (1-based)
|
102
|
+
# @return [Float] The delay in seconds
|
103
|
+
def calculate_backoff_delay(attempt)
|
104
|
+
base_delay = @backoff_options[:base_delay]
|
105
|
+
|
106
|
+
delay = case @backoff_strategy
|
107
|
+
when :constant
|
108
|
+
base_delay
|
109
|
+
when :linear
|
110
|
+
base_delay * attempt
|
111
|
+
when :exponential
|
112
|
+
base_delay * (2**(attempt - 1))
|
113
|
+
else
|
114
|
+
base_delay
|
115
|
+
end
|
116
|
+
|
117
|
+
if @backoff_options[:jitter_factor] && @backoff_options[:jitter_factor] > 0
|
118
|
+
jitter = rand(-delay * @backoff_options[:jitter_factor]..delay * @backoff_options[:jitter_factor])
|
119
|
+
delay += jitter
|
120
|
+
end
|
121
|
+
|
122
|
+
[delay, 0].max # Ensure positive delay
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -117,7 +117,7 @@ module Agentic
|
|
117
117
|
max_child_depth = schema[:properties].values.map do |prop|
|
118
118
|
calculate_max_depth(prop, current_depth + 1)
|
119
119
|
end.max
|
120
|
-
[current_depth, max_child_depth].max
|
120
|
+
max_child_depth ? [current_depth, max_child_depth].max : current_depth
|
121
121
|
end
|
122
122
|
end
|
123
123
|
end
|
data/lib/agentic/task.rb
ADDED
@@ -0,0 +1,193 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "securerandom"
|
4
|
+
require "json"
|
5
|
+
require_relative "observable"
|
6
|
+
require_relative "task_definition"
|
7
|
+
require_relative "agent_specification"
|
8
|
+
|
9
|
+
module Agentic
|
10
|
+
# Represents an individual task to be executed by an agent
|
11
|
+
# @attr_reader [String] id Unique identifier for the task
|
12
|
+
# @attr_reader [String] description Human-readable description of the task
|
13
|
+
# @attr_reader [Hash] agent_spec Requirements for the agent that will execute this task
|
14
|
+
# @attr_reader [Hash] input Input data for the task
|
15
|
+
# @attr_reader [Hash, nil] output Output produced by the task, nil if not yet executed
|
16
|
+
# @attr_reader [Symbol] status Current status of the task (:pending, :in_progress, :completed, :failed)
|
17
|
+
# @attr_reader [TaskFailure, nil] failure Failure information if the task failed, nil otherwise
|
18
|
+
# @attr_reader [Boolean, nil] ready_to_execute Flag indicating if the task is ready to be executed
|
19
|
+
# @attr_accessor [Integer, nil] retry_count Number of times the task has been retried
|
20
|
+
# @attr_accessor [Symbol, nil] output_schema_name Name of the output schema to use
|
21
|
+
class Task
|
22
|
+
include Agentic::Observable
|
23
|
+
|
24
|
+
attr_reader :id, :description, :agent_spec, :input, :output, :status, :failure, :ready_to_execute
|
25
|
+
attr_accessor :retry_count, :output_schema_name
|
26
|
+
|
27
|
+
# Initializes a new task
|
28
|
+
# @param description [String] Human-readable description of the task
|
29
|
+
# @param agent_spec [Hash, AgentSpecification] Requirements for the agent that will execute this task
|
30
|
+
# @param input [Hash] Input data for the task
|
31
|
+
# @param output_schema_name [Symbol, nil] Name of the output schema to use for structured output
|
32
|
+
# @return [Task] A new task instance
|
33
|
+
def initialize(description:, agent_spec:, input: {}, output_schema_name: nil)
|
34
|
+
@id = SecureRandom.uuid
|
35
|
+
@description = description
|
36
|
+
|
37
|
+
# Convert agent_spec to AgentSpecification if it's a hash
|
38
|
+
@agent_spec = if agent_spec.is_a?(Hash)
|
39
|
+
AgentSpecification.new(
|
40
|
+
name: agent_spec["name"],
|
41
|
+
description: agent_spec["description"] || "",
|
42
|
+
instructions: agent_spec["instructions"]
|
43
|
+
)
|
44
|
+
else
|
45
|
+
agent_spec
|
46
|
+
end
|
47
|
+
|
48
|
+
@input = input
|
49
|
+
@output = nil
|
50
|
+
@failure = nil
|
51
|
+
@status = :pending
|
52
|
+
@ready_to_execute = nil
|
53
|
+
@output_schema_name = output_schema_name
|
54
|
+
end
|
55
|
+
|
56
|
+
# Creates a task from a TaskDefinition
|
57
|
+
# @param definition [TaskDefinition] The task definition
|
58
|
+
# @param input [Hash] Input data for the task
|
59
|
+
# @return [Task] A new task instance
|
60
|
+
def self.from_definition(definition, input = {})
|
61
|
+
new(
|
62
|
+
description: definition.description,
|
63
|
+
agent_spec: definition.agent,
|
64
|
+
input: input
|
65
|
+
)
|
66
|
+
end
|
67
|
+
|
68
|
+
# Executes the task using the given agent
|
69
|
+
# @param agent [Agent] The agent that will execute this task
|
70
|
+
# @return [TaskResult] The result of the task execution
|
71
|
+
def perform(agent)
|
72
|
+
old_status = @status
|
73
|
+
@status = :in_progress
|
74
|
+
|
75
|
+
notify_observers(:status_change, old_status, @status)
|
76
|
+
|
77
|
+
begin
|
78
|
+
@output = if has_output_schema?
|
79
|
+
agent.execute_with_schema(build_prompt, output_schema)
|
80
|
+
else
|
81
|
+
agent.execute(build_prompt)
|
82
|
+
end
|
83
|
+
old_status = @status
|
84
|
+
@status = :completed
|
85
|
+
|
86
|
+
notify_observers(:status_change, old_status, @status)
|
87
|
+
|
88
|
+
TaskResult.new(
|
89
|
+
task_id: @id,
|
90
|
+
success: true,
|
91
|
+
output: @output
|
92
|
+
)
|
93
|
+
rescue => e
|
94
|
+
@failure = TaskFailure.new(
|
95
|
+
message: e.message,
|
96
|
+
type: e.class.name,
|
97
|
+
context: {
|
98
|
+
backtrace: e.backtrace&.first(10),
|
99
|
+
agent_id: agent.respond_to?(:id) ? agent.id : nil
|
100
|
+
}
|
101
|
+
)
|
102
|
+
|
103
|
+
old_status = @status
|
104
|
+
@status = :failed
|
105
|
+
|
106
|
+
notify_observers(:status_change, old_status, @status)
|
107
|
+
notify_observers(:failure_occurred, @failure)
|
108
|
+
|
109
|
+
Agentic.logger.error("Task execution failed: #{e.message}")
|
110
|
+
|
111
|
+
TaskResult.new(
|
112
|
+
task_id: @id,
|
113
|
+
success: false,
|
114
|
+
failure: @failure
|
115
|
+
)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# Retries a failed task
|
120
|
+
# @param agent [Agent] The agent that will execute this task
|
121
|
+
# @return [TaskResult] The result of the task execution
|
122
|
+
# @raise [StandardError] If the task is not in a failed state
|
123
|
+
def retry(agent)
|
124
|
+
raise "Cannot retry a task that is not in a failed state" unless @status == :failed
|
125
|
+
|
126
|
+
old_status = @status
|
127
|
+
@status = :retrying
|
128
|
+
|
129
|
+
notify_observers(:status_change, old_status, @status)
|
130
|
+
|
131
|
+
perform(agent)
|
132
|
+
end
|
133
|
+
|
134
|
+
# Returns a serializable representation of the task
|
135
|
+
# @return [Hash] The task as a hash
|
136
|
+
def to_h
|
137
|
+
{
|
138
|
+
id: @id,
|
139
|
+
description: @description,
|
140
|
+
agent_spec: @agent_spec.is_a?(AgentSpecification) ? @agent_spec.to_h : @agent_spec,
|
141
|
+
input: @input,
|
142
|
+
output: @output,
|
143
|
+
status: @status,
|
144
|
+
failure: @failure&.to_h
|
145
|
+
}
|
146
|
+
end
|
147
|
+
|
148
|
+
# Returns the output schema for this task
|
149
|
+
# @return [Agentic::StructuredOutputs::Schema, nil] The schema or nil if none specified
|
150
|
+
def output_schema
|
151
|
+
return nil unless @output_schema_name
|
152
|
+
TaskOutputSchemas.get(@output_schema_name)
|
153
|
+
end
|
154
|
+
|
155
|
+
# Checks if this task has a structured output schema
|
156
|
+
# @return [Boolean] True if task has an output schema
|
157
|
+
def has_output_schema?
|
158
|
+
!@output_schema_name.nil? && TaskOutputSchemas.exists?(@output_schema_name)
|
159
|
+
end
|
160
|
+
|
161
|
+
# Sets the output schema for this task
|
162
|
+
# @param schema_name [Symbol] The name of the schema to use
|
163
|
+
def set_output_schema(schema_name)
|
164
|
+
@output_schema_name = schema_name
|
165
|
+
end
|
166
|
+
|
167
|
+
private
|
168
|
+
|
169
|
+
# Builds the prompt to be sent to the agent
|
170
|
+
# @return [String] The formatted prompt
|
171
|
+
def build_prompt
|
172
|
+
output_requirements = if has_output_schema?
|
173
|
+
"Provide your response as a structured JSON object that follows the specified schema. Do not include any markdown formatting, code blocks, or additional text - just the raw JSON."
|
174
|
+
else
|
175
|
+
"Provide your response as valid JSON only. Do not wrap the JSON in markdown code blocks or any other formatting. Return raw JSON that can be parsed directly."
|
176
|
+
end
|
177
|
+
|
178
|
+
<<~PROMPT
|
179
|
+
[System Instructions]
|
180
|
+
#{agent_spec.instructions}
|
181
|
+
|
182
|
+
[Task Description]
|
183
|
+
#{description}
|
184
|
+
|
185
|
+
[Input Parameters]
|
186
|
+
#{JSON.pretty_generate(input)}
|
187
|
+
|
188
|
+
[Output Requirements]
|
189
|
+
#{output_requirements}
|
190
|
+
PROMPT
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Agentic
|
4
|
+
# Value object representing a task definition
|
5
|
+
class TaskDefinition
|
6
|
+
# @return [String] A description of the task
|
7
|
+
attr_reader :description
|
8
|
+
|
9
|
+
# @return [AgentSpecification] The agent specification for this task
|
10
|
+
attr_reader :agent
|
11
|
+
|
12
|
+
# Initializes a new task definition
|
13
|
+
# @param description [String] A description of the task
|
14
|
+
# @param agent [AgentSpecification] The agent specification for this task
|
15
|
+
def initialize(description:, agent:)
|
16
|
+
@description = description
|
17
|
+
@agent = agent
|
18
|
+
end
|
19
|
+
|
20
|
+
# Returns a serializable representation of the task definition
|
21
|
+
# @return [Hash] The task definition as a hash
|
22
|
+
def to_h
|
23
|
+
{
|
24
|
+
"description" => @description,
|
25
|
+
"agent" => @agent.to_h
|
26
|
+
}
|
27
|
+
end
|
28
|
+
|
29
|
+
# Creates a TaskDefinition from a hash
|
30
|
+
# @param hash [Hash] The hash representation
|
31
|
+
# @return [TaskDefinition] A new task definition
|
32
|
+
def self.from_hash(hash)
|
33
|
+
new(
|
34
|
+
description: hash["description"],
|
35
|
+
agent: AgentSpecification.from_hash(hash["agent"])
|
36
|
+
)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "task_failure"
|
4
|
+
|
5
|
+
module Agentic
|
6
|
+
# Value object representing the execution result of a task
|
7
|
+
class TaskExecutionResult
|
8
|
+
# @return [Symbol] The status of the task execution (:completed, :failed, :canceled)
|
9
|
+
attr_reader :status
|
10
|
+
|
11
|
+
# @return [Hash, nil] The output produced by the task (only if successful)
|
12
|
+
attr_reader :output
|
13
|
+
|
14
|
+
# @return [TaskFailure, nil] The failure details (only if failed)
|
15
|
+
attr_reader :failure
|
16
|
+
|
17
|
+
# @param status [Symbol] The status of the task execution
|
18
|
+
# @param output [Hash, nil] The output produced by the task
|
19
|
+
# @param failure [TaskFailure, nil] The failure details
|
20
|
+
def initialize(status:, output: nil, failure: nil)
|
21
|
+
@status = status
|
22
|
+
@output = output
|
23
|
+
@failure = failure
|
24
|
+
end
|
25
|
+
|
26
|
+
# Creates a successful execution result
|
27
|
+
# @param output [Hash] The task output
|
28
|
+
# @return [TaskExecutionResult] A successful execution result
|
29
|
+
def self.success(output)
|
30
|
+
new(status: :completed, output: output)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Creates a failed execution result
|
34
|
+
# @param failure [TaskFailure] The failure details
|
35
|
+
# @return [TaskExecutionResult] A failed execution result
|
36
|
+
def self.failure(failure)
|
37
|
+
new(status: :failed, failure: failure)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Creates a canceled execution result
|
41
|
+
# @return [TaskExecutionResult] A canceled execution result
|
42
|
+
def self.canceled
|
43
|
+
new(status: :canceled)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Creates a task execution result from a hash
|
47
|
+
# @param hash [Hash] The hash representation of a task execution result
|
48
|
+
# @return [TaskExecutionResult] A task execution result
|
49
|
+
def self.from_hash(hash)
|
50
|
+
# Handle the case where hash is not actually a hash (could be nil, Integer, etc.)
|
51
|
+
return new(status: :completed) unless hash.is_a?(Hash)
|
52
|
+
|
53
|
+
# Convert string keys to symbols if necessary
|
54
|
+
hash = hash.transform_keys(&:to_sym) if hash.keys.first.is_a?(String)
|
55
|
+
|
56
|
+
failure = hash[:failure] ? TaskFailure.from_hash(hash[:failure]) : nil
|
57
|
+
new(
|
58
|
+
status: hash[:status] || :completed,
|
59
|
+
output: hash[:output],
|
60
|
+
failure: failure
|
61
|
+
)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Checks if the task execution was successful
|
65
|
+
# @return [Boolean] True if successful, false otherwise
|
66
|
+
def successful?
|
67
|
+
@status == :completed
|
68
|
+
end
|
69
|
+
|
70
|
+
# Checks if the task execution failed
|
71
|
+
# @return [Boolean] True if failed, false otherwise
|
72
|
+
def failed?
|
73
|
+
@status == :failed
|
74
|
+
end
|
75
|
+
|
76
|
+
# Checks if the task execution was canceled
|
77
|
+
# @return [Boolean] True if canceled, false otherwise
|
78
|
+
def canceled?
|
79
|
+
@status == :canceled
|
80
|
+
end
|
81
|
+
|
82
|
+
# Returns a hash representation of the execution result
|
83
|
+
# @return [Hash] The execution result as a hash
|
84
|
+
def to_h
|
85
|
+
{
|
86
|
+
status: @status,
|
87
|
+
output: @output,
|
88
|
+
failure: @failure&.to_h
|
89
|
+
}
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Agentic
|
4
|
+
# Represents a failure that occurred during task execution
|
5
|
+
# @attr_reader [String] message The failure message
|
6
|
+
# @attr_reader [String] type The type of failure
|
7
|
+
# @attr_reader [Time] timestamp When the failure occurred
|
8
|
+
# @attr_reader [Hash] context Additional context about the failure
|
9
|
+
class TaskFailure
|
10
|
+
attr_reader :message, :type, :timestamp, :context
|
11
|
+
|
12
|
+
# Initializes a new task failure
|
13
|
+
# @param message [String] The failure message
|
14
|
+
# @param type [String] The type of failure
|
15
|
+
# @param context [Hash] Additional context about the failure
|
16
|
+
# @return [TaskFailure] A new task failure instance
|
17
|
+
def initialize(message:, type:, context: {})
|
18
|
+
@message = message
|
19
|
+
@type = type
|
20
|
+
@timestamp = Time.now
|
21
|
+
@context = context
|
22
|
+
end
|
23
|
+
|
24
|
+
# Returns a serializable representation of the failure
|
25
|
+
# @return [Hash] The failure as a hash
|
26
|
+
def to_h
|
27
|
+
{
|
28
|
+
message: @message,
|
29
|
+
type: @type,
|
30
|
+
timestamp: @timestamp.iso8601,
|
31
|
+
context: @context
|
32
|
+
}
|
33
|
+
end
|
34
|
+
|
35
|
+
# Creates a task failure from an exception
|
36
|
+
# @param exception [Exception] The exception
|
37
|
+
# @param context [Hash] Additional context about the failure
|
38
|
+
# @return [TaskFailure] A new task failure instance
|
39
|
+
def self.from_exception(exception, context = {})
|
40
|
+
new(
|
41
|
+
message: exception.message,
|
42
|
+
type: exception.class.name,
|
43
|
+
context: context.merge(
|
44
|
+
backtrace: exception.backtrace&.first(10)
|
45
|
+
)
|
46
|
+
)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Creates a task failure from a hash
|
50
|
+
# @param hash [Hash] The hash representation of a task failure
|
51
|
+
# @return [TaskFailure] A new task failure instance
|
52
|
+
def self.from_hash(hash)
|
53
|
+
# Handle the case where hash is not actually a hash
|
54
|
+
return new(message: "Unknown error", type: "UnknownError") unless hash.is_a?(Hash)
|
55
|
+
|
56
|
+
# Convert string keys to symbols if necessary
|
57
|
+
hash = hash.transform_keys(&:to_sym) if hash.keys.first.is_a?(String)
|
58
|
+
|
59
|
+
new(
|
60
|
+
message: hash[:message] || "Unknown error",
|
61
|
+
type: hash[:type] || "UnknownError",
|
62
|
+
context: hash[:context] || {}
|
63
|
+
)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Agentic
|
4
|
+
# Registry for managing task output schemas
|
5
|
+
# Provides a centralized location for defining and accessing
|
6
|
+
# structured output schemas used by tasks
|
7
|
+
class TaskOutputSchemas
|
8
|
+
@schemas = {}
|
9
|
+
|
10
|
+
class << self
|
11
|
+
# Registers a new output schema
|
12
|
+
# @param name [Symbol] The schema name/identifier
|
13
|
+
# @param schema [Agentic::StructuredOutputs::Schema] The schema definition
|
14
|
+
def register(name, schema)
|
15
|
+
@schemas[name] = schema
|
16
|
+
end
|
17
|
+
|
18
|
+
# Retrieves a registered schema
|
19
|
+
# @param name [Symbol] The schema name/identifier
|
20
|
+
# @return [Agentic::StructuredOutputs::Schema, nil] The schema or nil if not found
|
21
|
+
def get(name)
|
22
|
+
@schemas[name] || ((name == :default) ? default_task_schema : nil)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Lists all registered schema names
|
26
|
+
# @return [Array<Symbol>] Array of schema names
|
27
|
+
def list_schemas
|
28
|
+
(@schemas.keys + [:default]).uniq
|
29
|
+
end
|
30
|
+
|
31
|
+
# Checks if a schema is registered
|
32
|
+
# @param name [Symbol] The schema name/identifier
|
33
|
+
# @return [Boolean] True if schema exists
|
34
|
+
def exists?(name)
|
35
|
+
@schemas.key?(name) || name == :default
|
36
|
+
end
|
37
|
+
|
38
|
+
# Returns the default task output schema for general task responses
|
39
|
+
# @return [Agentic::StructuredOutputs::Schema] Default schema for task outputs
|
40
|
+
def default_task_schema
|
41
|
+
@default_schema ||= StructuredOutputs::Schema.new("task_output") do |schema|
|
42
|
+
# Simple, flexible schema for task results
|
43
|
+
schema.string(:status, enum: ["completed", "partial", "failed"])
|
44
|
+
schema.object(:result) do |result_schema|
|
45
|
+
result_schema.string(:summary)
|
46
|
+
# Additional properties will be allowed for flexible task outputs
|
47
|
+
end
|
48
|
+
schema.array(:steps, items: {type: "string"})
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Returns a simple object schema for maximum flexibility
|
53
|
+
# @return [Agentic::StructuredOutputs::Schema] Simple object schema
|
54
|
+
def simple_object_schema
|
55
|
+
@simple_object_schema ||= StructuredOutputs::Schema.new("simple_object") do |schema|
|
56
|
+
# Minimal schema that accepts any structured JSON object
|
57
|
+
# This is useful when we want structured JSON but maximum flexibility
|
58
|
+
schema.string(:type)
|
59
|
+
schema.object(:data) do
|
60
|
+
# Flexible data structure
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Returns a schema for code generation tasks
|
66
|
+
# @return [Agentic::StructuredOutputs::Schema] Code generation schema
|
67
|
+
def code_generation_schema
|
68
|
+
@code_generation_schema ||= StructuredOutputs::Schema.new("code_generation") do |schema|
|
69
|
+
schema.string(:language)
|
70
|
+
schema.string(:filename)
|
71
|
+
schema.string(:code)
|
72
|
+
schema.string(:description)
|
73
|
+
schema.array(:dependencies, items: {type: "string"})
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Returns a schema for analysis/research tasks
|
78
|
+
# @return [Agentic::StructuredOutputs::Schema] Analysis task schema
|
79
|
+
def analysis_schema
|
80
|
+
@analysis_schema ||= StructuredOutputs::Schema.new("analysis_result") do |schema|
|
81
|
+
schema.string(:summary)
|
82
|
+
schema.array(:key_findings, items: {type: "string"})
|
83
|
+
schema.object(:data) do
|
84
|
+
# Flexible analysis data
|
85
|
+
end
|
86
|
+
schema.array(:recommendations, items: {type: "string"})
|
87
|
+
schema.string(:confidence_level, enum: ["high", "medium", "low"])
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# Resets all registered schemas (useful for testing)
|
92
|
+
def reset!
|
93
|
+
@schemas = {}
|
94
|
+
@default_schema = nil
|
95
|
+
@simple_object_schema = nil
|
96
|
+
@code_generation_schema = nil
|
97
|
+
@analysis_schema = nil
|
98
|
+
end
|
99
|
+
|
100
|
+
# Registers default schemas
|
101
|
+
def register_defaults!
|
102
|
+
register(:default, default_task_schema)
|
103
|
+
register(:simple_object, simple_object_schema)
|
104
|
+
register(:code_generation, code_generation_schema)
|
105
|
+
register(:analysis, analysis_schema)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# Register defaults when the class is loaded
|
110
|
+
register_defaults!
|
111
|
+
end
|
112
|
+
end
|