smith-agents 0.7.0 → 0.10.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/CHANGELOG.md +456 -0
- data/README.md +71 -1
- data/docs/CONFIGURATION.md +27 -6
- data/docs/PERSISTENCE.md +27 -1
- data/docs/TOOLS_AND_GUARDRAILS.md +233 -2
- data/lib/smith/agent/chat_construction.rb +112 -0
- data/lib/smith/agent/completion.rb +46 -0
- data/lib/smith/agent/completion_usage_recording.rb +43 -0
- data/lib/smith/agent/dynamic_configuration.rb +69 -0
- data/lib/smith/agent/fallback_configuration.rb +26 -0
- data/lib/smith/agent/invocation_preparation.rb +128 -0
- data/lib/smith/agent/lifecycle.rb +8 -275
- data/lib/smith/agent/model_reference.rb +76 -0
- data/lib/smith/agent/provider_attempt.rb +34 -0
- data/lib/smith/agent/provider_call_timing.rb +74 -0
- data/lib/smith/agent/provider_candidate_sequence.rb +55 -0
- data/lib/smith/agent/provider_completion.rb +148 -0
- data/lib/smith/agent/provider_failure_handling.rb +93 -0
- data/lib/smith/agent/provider_usage.rb +28 -0
- data/lib/smith/agent/reserved_input_bridge.rb +40 -0
- data/lib/smith/agent/usage_entry_recording.rb +66 -0
- data/lib/smith/agent/usage_traces.rb +49 -0
- data/lib/smith/agent/usage_tracking.rb +116 -0
- data/lib/smith/agent.rb +29 -197
- data/lib/smith/attribution.rb +115 -0
- data/lib/smith/context/observation_masking.rb +28 -4
- data/lib/smith/context/session.rb +7 -2
- data/lib/smith/diagnostic_text.rb +67 -0
- data/lib/smith/doctor/checks/configuration.rb +16 -3
- data/lib/smith/doctor/checks/models_registry.rb +75 -17
- data/lib/smith/errors.rb +34 -1
- data/lib/smith/event.rb +10 -2
- data/lib/smith/events/bus.rb +63 -5
- data/lib/smith/events/step_completed.rb +3 -0
- data/lib/smith/events/step_failed.rb +25 -0
- data/lib/smith/events/subscription.rb +8 -0
- data/lib/smith/models/ambiguous_profile_error.rb +9 -0
- data/lib/smith/models/collision_error.rb +9 -0
- data/lib/smith/models/inference.rb +14 -0
- data/lib/smith/models/normalizer.rb +11 -67
- data/lib/smith/models/provider_qualified_registry.rb +101 -0
- data/lib/smith/models/tool_routing.rb +140 -0
- data/lib/smith/models.rb +24 -78
- data/lib/smith/persisted_failure_invalid.rb +7 -0
- data/lib/smith/pricing.rb +138 -17
- data/lib/smith/pricing_configuration_error.rb +7 -0
- data/lib/smith/provider_permanent_failure.rb +16 -0
- data/lib/smith/tool/argument_container_reader.rb +97 -0
- data/lib/smith/tool/argument_scalar_snapshot.rb +99 -0
- data/lib/smith/tool/argument_snapshot.rb +19 -0
- data/lib/smith/tool/argument_snapshot_accounting.rb +69 -0
- data/lib/smith/tool/argument_snapshot_result.rb +15 -0
- data/lib/smith/tool/argument_snapshot_traversal.rb +119 -0
- data/lib/smith/tool/bounded_completion_context.rb +131 -0
- data/lib/smith/tool/bounded_completion_controls.rb +113 -0
- data/lib/smith/tool/bounded_completion_guard.rb +101 -0
- data/lib/smith/tool/bounded_completion_installation.rb +20 -0
- data/lib/smith/tool/bounded_completion_state.rb +57 -0
- data/lib/smith/tool/budget_enforcement.rb +15 -7
- data/lib/smith/tool/call_admission.rb +44 -0
- data/lib/smith/tool/call_allowance.rb +101 -28
- data/lib/smith/tool/call_allowance_counter.rb +43 -0
- data/lib/smith/tool/call_batch.rb +47 -0
- data/lib/smith/tool/call_budget.rb +51 -0
- data/lib/smith/tool/call_reservation.rb +42 -0
- data/lib/smith/tool/capture.rb +9 -1
- data/lib/smith/tool/chat_execution_callbacks.rb +58 -0
- data/lib/smith/tool/chat_execution_context.rb +7 -88
- data/lib/smith/tool/execution_authority.rb +42 -0
- data/lib/smith/tool/execution_authorization.rb +17 -0
- data/lib/smith/tool/execution_batch.rb +100 -0
- data/lib/smith/tool/execution_batch_admission.rb +56 -0
- data/lib/smith/tool/execution_batch_builder.rb +122 -0
- data/lib/smith/tool/execution_batch_collection.rb +70 -0
- data/lib/smith/tool/execution_batch_invocations.rb +28 -0
- data/lib/smith/tool/execution_batch_lifecycle.rb +91 -0
- data/lib/smith/tool/execution_batch_registry.rb +99 -0
- data/lib/smith/tool/execution_batch_requests.rb +59 -0
- data/lib/smith/tool/execution_batch_source_call.rb +36 -0
- data/lib/smith/tool/execution_batch_source_metadata.rb +107 -0
- data/lib/smith/tool/execution_batch_sources.rb +32 -0
- data/lib/smith/tool/execution_batch_state.rb +110 -0
- data/lib/smith/tool/execution_dispatch.rb +78 -0
- data/lib/smith/tool/execution_failure_handling.rb +101 -0
- data/lib/smith/tool/execution_lifecycle.rb +28 -0
- data/lib/smith/tool/execution_tracker.rb +20 -0
- data/lib/smith/tool/fail_fast_completion.rb +24 -0
- data/lib/smith/tool/graceful_completion.rb +20 -0
- data/lib/smith/tool/invocation.rb +27 -0
- data/lib/smith/tool/invocation_request.rb +37 -0
- data/lib/smith/tool/invocation_sequence.rb +26 -0
- data/lib/smith/tool/legacy_call_allowance.rb +57 -0
- data/lib/smith/tool/scoped_context.rb +60 -8
- data/lib/smith/tool.rb +81 -13
- data/lib/smith/tool_execution_not_admitted.rb +7 -0
- data/lib/smith/tool_failure_notification_failed.rb +94 -0
- data/lib/smith/trace/logger.rb +1 -0
- data/lib/smith/trace/memory.rb +40 -4
- data/lib/smith/trace/open_telemetry.rb +47 -3
- data/lib/smith/trace.rb +24 -5
- data/lib/smith/version.rb +2 -2
- data/lib/smith/workflow/agent_result.rb +4 -3
- data/lib/smith/workflow/artifact_integration.rb +2 -1
- data/lib/smith/workflow/composite/effects.rb +64 -31
- data/lib/smith/workflow/deadline_enforcement.rb +20 -2
- data/lib/smith/workflow/deterministic_execution.rb +2 -1
- data/lib/smith/workflow/deterministic_step.rb +14 -1
- data/lib/smith/workflow/dsl.rb +25 -1
- data/lib/smith/workflow/evaluator_optimizer.rb +3 -1
- data/lib/smith/workflow/event_integration.rb +48 -1
- data/lib/smith/workflow/execution.rb +10 -9
- data/lib/smith/workflow/failure_detail_snapshot.rb +33 -0
- data/lib/smith/workflow/failure_reconstructor.rb +79 -0
- data/lib/smith/workflow/failure_record.rb +139 -0
- data/lib/smith/workflow/failure_record_restore.rb +145 -0
- data/lib/smith/workflow/failure_record_text.rb +83 -0
- data/lib/smith/workflow/failure_record_validator.rb +113 -0
- data/lib/smith/workflow/fanout_execution.rb +1 -1
- data/lib/smith/workflow/guarded_step_execution.rb +39 -0
- data/lib/smith/workflow/guardrail_integration.rb +25 -0
- data/lib/smith/workflow/parallel.rb +14 -3
- data/lib/smith/workflow/parallel_execution.rb +6 -0
- data/lib/smith/workflow/persistence.rb +55 -20
- data/lib/smith/workflow/prepared_branch_execution.rb +13 -4
- data/lib/smith/workflow/retry_execution.rb +5 -1
- data/lib/smith/workflow/split_step_persistence/composite_branch_execution.rb +9 -1
- data/lib/smith/workflow/split_step_persistence/state_snapshot.rb +1 -0
- data/lib/smith/workflow/step_completion.rb +9 -0
- data/lib/smith/workflow/step_context.rb +46 -0
- data/lib/smith/workflow/thread_context_snapshot.rb +6 -2
- data/lib/smith/workflow/transition.rb +9 -4
- data/lib/smith/workflow/usage_entry.rb +43 -5
- data/lib/smith/workflow.rb +43 -131
- data/lib/smith.rb +10 -2
- metadata +82 -11
|
@@ -15,10 +15,14 @@ module Smith
|
|
|
15
15
|
current_deadline
|
|
16
16
|
current_ledger
|
|
17
17
|
current_tool_call_allowance
|
|
18
|
+
current_tool_execution_tracker
|
|
18
19
|
current_tool_result_collector
|
|
19
20
|
current_invocation_context
|
|
21
|
+
current_invocation_sequence
|
|
22
|
+
current_invocation
|
|
20
23
|
].freeze
|
|
21
24
|
THREAD_KEYS = %i[
|
|
25
|
+
smith_attribution
|
|
22
26
|
smith_call_deadline
|
|
23
27
|
smith_call_ledger
|
|
24
28
|
smith_failed_agent_results
|
|
@@ -68,14 +72,14 @@ module Smith
|
|
|
68
72
|
def restore_context!
|
|
69
73
|
raise WorkflowError, "thread context snapshot has already been restored" if @restored
|
|
70
74
|
|
|
71
|
-
@tool_values.each { |attribute, value| Tool.
|
|
75
|
+
@tool_values.each { |attribute, value| Tool.send("#{attribute}=", value) }
|
|
72
76
|
@thread_values.each { |key, value| Thread.current[key] = value }
|
|
73
77
|
Smith.scoped_artifacts = @artifact_store if scoped_artifacts
|
|
74
78
|
@restored = true
|
|
75
79
|
end
|
|
76
80
|
|
|
77
81
|
def capture_tool_values
|
|
78
|
-
tool_attributes.to_h { |attribute| [attribute, Tool.
|
|
82
|
+
tool_attributes.to_h { |attribute| [attribute, Tool.send(attribute)] }.freeze
|
|
79
83
|
end
|
|
80
84
|
|
|
81
85
|
def capture_thread_values
|
|
@@ -336,10 +336,7 @@ module Smith
|
|
|
336
336
|
|
|
337
337
|
raise WorkflowError, "retry_on error classes must inherit from StandardError"
|
|
338
338
|
end
|
|
339
|
-
|
|
340
|
-
message = "retry_on cannot retry Smith::ToolCaptureFailed because the tool outcome may be uncertain"
|
|
341
|
-
raise WorkflowError, message
|
|
342
|
-
end
|
|
339
|
+
reject_retry_forbidden!(error_classes)
|
|
343
340
|
|
|
344
341
|
ExponentialBackoff.new(
|
|
345
342
|
attempts:,
|
|
@@ -352,6 +349,14 @@ module Smith
|
|
|
352
349
|
raise WorkflowError, "retry_on #{e.message}"
|
|
353
350
|
end
|
|
354
351
|
|
|
352
|
+
def reject_retry_forbidden!(error_classes)
|
|
353
|
+
forbidden = error_classes.find { |error_class| Smith::Errors.retry_forbidden_class?(error_class) }
|
|
354
|
+
return unless forbidden
|
|
355
|
+
|
|
356
|
+
label = forbidden.name || "an anonymous terminal tool-evidence error class"
|
|
357
|
+
raise WorkflowError, "retry_on cannot retry #{label} because the tool outcome may be uncertain"
|
|
358
|
+
end
|
|
359
|
+
|
|
355
360
|
def normalize_deterministic_routes!(routes)
|
|
356
361
|
return nil if routes.nil?
|
|
357
362
|
raise WorkflowError, "deterministic routes must be an Array" unless routes.is_a?(Array)
|
|
@@ -2,26 +2,64 @@
|
|
|
2
2
|
|
|
3
3
|
module Smith
|
|
4
4
|
class Workflow
|
|
5
|
-
# One row per
|
|
6
|
-
#
|
|
7
|
-
# idempotency anchor.
|
|
5
|
+
# One row per provider RESPONSE within an agent provider attempt.
|
|
6
|
+
# `usage_id` is a UUID generated at recording time and stable across
|
|
7
|
+
# persist/restore so hosts can use it as an idempotency anchor.
|
|
8
|
+
#
|
|
9
|
+
# `transition`, `branch_key`, `round`, and `workflow` come from the
|
|
10
|
+
# ambient Smith::Attribution at recording time, so hosts can attribute
|
|
11
|
+
# usage to the exact step, fan-out branch, optimizer round, and (for
|
|
12
|
+
# nested workflows) the exact graph that spent it.
|
|
13
|
+
# `attempt_id` is shared by every entry the same provider attempt
|
|
14
|
+
# produced (an attempt with an N-round tool loop records N entries):
|
|
15
|
+
# join on it against the `:provider_call` trace event for the attempt's
|
|
16
|
+
# single measured duration; never sum a duration across entries. Every
|
|
17
|
+
# attempt emits its `:provider_call` (an attempt aborted by a
|
|
18
|
+
# non-provider error emits with `outcome: :aborted` before re-raising),
|
|
19
|
+
# so prefix-accounted entries always have their join target.
|
|
20
|
+
# All optional members are nil on entries restored from checkpoints written by
|
|
21
|
+
# earlier Smith versions, and nil attribution members are omitted from
|
|
22
|
+
# serialization so those documents re-serialize byte-identically.
|
|
8
23
|
# rubocop:disable Style/RedundantStructKeywordInit
|
|
9
24
|
UsageEntry = Struct.new(
|
|
10
25
|
:usage_id,
|
|
11
26
|
:agent_name,
|
|
12
27
|
:model,
|
|
28
|
+
:provider,
|
|
13
29
|
:input_tokens,
|
|
14
30
|
:output_tokens,
|
|
15
31
|
:cost,
|
|
16
32
|
:attempt_kind,
|
|
17
33
|
:recorded_at,
|
|
34
|
+
:transition,
|
|
35
|
+
:branch_key,
|
|
36
|
+
:round,
|
|
37
|
+
:attempt_id,
|
|
38
|
+
:workflow,
|
|
18
39
|
keyword_init: true
|
|
19
40
|
) do
|
|
41
|
+
def initialize(**attributes)
|
|
42
|
+
attributes = attributes.transform_values do |value|
|
|
43
|
+
value.is_a?(String) ? value.dup.freeze : value
|
|
44
|
+
end
|
|
45
|
+
super(**attributes) # rubocop:disable Style/SuperArguments
|
|
46
|
+
freeze
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Serialization is additive only when present: nil attribution members
|
|
50
|
+
# are omitted so an entry restored from a pre-attribution checkpoint
|
|
51
|
+
# re-serializes byte-identically. Hosts that digest whole persisted
|
|
52
|
+
# documents (exact-mutation proofs) depend on this stability.
|
|
53
|
+
def to_h
|
|
54
|
+
super.reject { |key, value| value.nil? && %i[transition branch_key round attempt_id workflow].include?(key) }
|
|
55
|
+
end
|
|
56
|
+
|
|
20
57
|
def self.from_h(hash)
|
|
21
58
|
sym = hash.transform_keys(&:to_sym)
|
|
22
59
|
filtered = sym.slice(*members)
|
|
23
|
-
|
|
24
|
-
|
|
60
|
+
%i[agent_name provider attempt_kind transition branch_key].each do |attribute|
|
|
61
|
+
filtered[attribute] = filtered[attribute].to_sym if filtered[attribute].is_a?(String)
|
|
62
|
+
end
|
|
25
63
|
new(**filtered)
|
|
26
64
|
end
|
|
27
65
|
end
|
data/lib/smith/workflow.rb
CHANGED
|
@@ -3,11 +3,13 @@
|
|
|
3
3
|
require "digest"
|
|
4
4
|
require "json"
|
|
5
5
|
require "securerandom"
|
|
6
|
-
require "set"
|
|
7
6
|
require "time"
|
|
8
7
|
|
|
9
8
|
require_relative "workflow/agent_result"
|
|
10
9
|
require_relative "workflow/branch_env"
|
|
10
|
+
require_relative "workflow/failure_record"
|
|
11
|
+
require_relative "workflow/failure_record_restore"
|
|
12
|
+
require_relative "workflow/failure_reconstructor"
|
|
11
13
|
require_relative "workflow/run_result"
|
|
12
14
|
require_relative "workflow/string_snapshot"
|
|
13
15
|
require_relative "workflow/usage_entry"
|
|
@@ -30,51 +32,6 @@ module Smith
|
|
|
30
32
|
|
|
31
33
|
DEFAULT_MAX_TRANSITIONS = 100
|
|
32
34
|
|
|
33
|
-
# Reconstruct Smith error classes from `@last_failed_step` snapshots.
|
|
34
|
-
# Order matters: more-specific subclasses first, so a real DSF doesn't
|
|
35
|
-
# get caught by the WorkflowError handler. Each lambda preserves the
|
|
36
|
-
# billing-critical attributes (`retryable`, `kind`, `details`) by
|
|
37
|
-
# routing through the original constructor — Smith's retryable errors
|
|
38
|
-
# expose `attr_reader :retryable` only, with no setter, so kwargs
|
|
39
|
-
# MUST flow through `initialize`.
|
|
40
|
-
KNOWN_RECONSTRUCTORS = {
|
|
41
|
-
"Smith::ToolCaptureFailed" => ->(s) {
|
|
42
|
-
Smith::ToolCaptureFailed.from_details(s.fetch(:error_details))
|
|
43
|
-
},
|
|
44
|
-
"Smith::ToolGuardrailFailed" => ->(s) {
|
|
45
|
-
Smith::ToolGuardrailFailed.new(s[:error_message], retryable: s[:error_retryable])
|
|
46
|
-
},
|
|
47
|
-
"Smith::DeterministicStepFailure" => ->(s) {
|
|
48
|
-
Smith::DeterministicStepFailure.new(
|
|
49
|
-
s[:error_message],
|
|
50
|
-
retryable: s[:error_retryable],
|
|
51
|
-
kind: s[:error_kind],
|
|
52
|
-
details: s[:error_details]
|
|
53
|
-
)
|
|
54
|
-
},
|
|
55
|
-
"Smith::AgentError" => ->(s) { Smith::AgentError.new(s[:error_message]) },
|
|
56
|
-
"Smith::DeadlineExceeded" => ->(s) { Smith::DeadlineExceeded.new(s[:error_message]) },
|
|
57
|
-
"Smith::WorkflowError" => ->(s) { Smith::WorkflowError.new(s[:error_message]) },
|
|
58
|
-
"Smith::Workflow::Composite::BranchFailure" => lambda { |s|
|
|
59
|
-
Smith::Workflow::Composite::BranchFailure.from_details(s[:error_details])
|
|
60
|
-
},
|
|
61
|
-
# Smith errors with non-message constructors map to compatible
|
|
62
|
-
# superclass — message preserved, original metadata (agent_name,
|
|
63
|
-
# model_used, requested_name, workflow_class, origin_state) lossy
|
|
64
|
-
# but `is_a?` classification round-trips via the superclass.
|
|
65
|
-
"Smith::BlankAgentOutputError" => ->(s) { Smith::AgentError.new(s[:error_message]) },
|
|
66
|
-
"Smith::UnresolvedTransitionError" => ->(s) { Smith::WorkflowError.new(s[:error_message]) }
|
|
67
|
-
}.freeze
|
|
68
|
-
private_constant :KNOWN_RECONSTRUCTORS
|
|
69
|
-
|
|
70
|
-
# Families whose retryable/kind/details attributes are billing-critical.
|
|
71
|
-
# For these, the reconstruction path bypasses `const_get(...).new(message)`
|
|
72
|
-
# (which would succeed for unknown subclasses with message-only
|
|
73
|
-
# constructors but discard the kwargs) and uses the family fallback
|
|
74
|
-
# directly so the parent-class constructor preserves the attrs.
|
|
75
|
-
RETRYABLE_BEARING_FAMILIES = %w[deterministic_step_failure tool_guardrail_failed].freeze
|
|
76
|
-
private_constant :RETRYABLE_BEARING_FAMILIES
|
|
77
|
-
|
|
78
35
|
attr_reader :state, :last_prepared_input, :ledger
|
|
79
36
|
|
|
80
37
|
def session_messages
|
|
@@ -101,6 +58,14 @@ module Smith
|
|
|
101
58
|
@usage_entries = []
|
|
102
59
|
@usage_mutex = Mutex.new
|
|
103
60
|
@last_output = nil
|
|
61
|
+
# Durable attribution of the most recent serial `execute :agent` step:
|
|
62
|
+
# { model:, provider: } of the model that actually served it (post
|
|
63
|
+
# fallback resolution), exposed to the following deterministic step as
|
|
64
|
+
# `last_agent_model` / `last_agent_provider`. `@pending_agent_execution`
|
|
65
|
+
# is the transient per-step carrier from `execute_serial_step` to
|
|
66
|
+
# `complete_step`; it is never persisted.
|
|
67
|
+
@last_agent_execution = nil
|
|
68
|
+
@pending_agent_execution = nil
|
|
104
69
|
@last_failed_step = nil
|
|
105
70
|
# Optimistic-locking version. Incremented on each persist!; restored
|
|
106
71
|
# from the persisted payload. Adapters that support store_versioned
|
|
@@ -113,6 +78,7 @@ module Smith
|
|
|
113
78
|
# seed_validation is :warn or :strict; nil when no seed builder
|
|
114
79
|
# ran or its output was empty.
|
|
115
80
|
@seed_digest = nil
|
|
81
|
+
@seed_message_count = 0
|
|
116
82
|
# Idempotency marker stamped between persist-before-advance and
|
|
117
83
|
# persist-after-advance under idempotency_mode :strict; restored
|
|
118
84
|
# workflows with the marker set raise
|
|
@@ -199,11 +165,23 @@ module Smith
|
|
|
199
165
|
end
|
|
200
166
|
end
|
|
201
167
|
|
|
168
|
+
# Public, read-only view of the per-provider-call usage ledger, so hosts
|
|
169
|
+
# can diff usage across a step boundary without paying a full to_state
|
|
170
|
+
# serialization. The entries themselves are frozen; the returned array is
|
|
171
|
+
# a frozen copy taken under the recording mutex. Tolerates allocated but
|
|
172
|
+
# unrestored instances (no mutex yet), which have no entries.
|
|
173
|
+
def usage_entries
|
|
174
|
+
mutex = @usage_mutex
|
|
175
|
+
return [].freeze unless mutex
|
|
176
|
+
|
|
177
|
+
mutex.synchronize { @usage_entries.dup.freeze }
|
|
178
|
+
end
|
|
179
|
+
|
|
202
180
|
private
|
|
203
181
|
|
|
204
182
|
def initial_persist_auto_seed
|
|
205
183
|
manager = self.class.context_manager
|
|
206
|
-
return [] unless manager
|
|
184
|
+
return [] unless manager.respond_to?(:persist_mode) && manager.persist_mode == :auto
|
|
207
185
|
|
|
208
186
|
manager.persist_auto_seed.map(&:to_sym)
|
|
209
187
|
end
|
|
@@ -223,31 +201,7 @@ module Smith
|
|
|
223
201
|
return unless step_result
|
|
224
202
|
|
|
225
203
|
if step_result[:error]
|
|
226
|
-
|
|
227
|
-
# error_family preserves classification across reconstruction
|
|
228
|
-
# even when the exact class can't be rebuilt. Order matters:
|
|
229
|
-
# specific subclasses first (DSF before WorkflowError, etc.)
|
|
230
|
-
# so a real DSF doesn't get classified as workflow_error.
|
|
231
|
-
error_family = case err
|
|
232
|
-
when Smith::DeterministicStepFailure then "deterministic_step_failure"
|
|
233
|
-
when Smith::ToolCaptureFailed then "tool_capture_failed"
|
|
234
|
-
when Smith::ToolGuardrailFailed then "tool_guardrail_failed"
|
|
235
|
-
when Smith::DeadlineExceeded then "deadline_exceeded"
|
|
236
|
-
when Smith::AgentError then "agent_error"
|
|
237
|
-
when Smith::WorkflowError then "workflow_error"
|
|
238
|
-
else "other"
|
|
239
|
-
end
|
|
240
|
-
@last_failed_step = {
|
|
241
|
-
transition: step_result[:transition],
|
|
242
|
-
from: step_result[:from],
|
|
243
|
-
to: step_result[:to],
|
|
244
|
-
error_class: err.class.name,
|
|
245
|
-
error_family: error_family,
|
|
246
|
-
error_message: err.message,
|
|
247
|
-
error_retryable: err.respond_to?(:retryable) ? err.retryable : nil,
|
|
248
|
-
error_kind: err.respond_to?(:kind) ? err.kind : nil,
|
|
249
|
-
error_details: err.respond_to?(:details) ? err.details : nil
|
|
250
|
-
}
|
|
204
|
+
@last_failed_step = FailureRecord.capture(step_result)
|
|
251
205
|
else
|
|
252
206
|
# Successful step: clear any prior failed-step snapshot
|
|
253
207
|
# (workflow handled the failure and continued) and capture
|
|
@@ -255,6 +209,10 @@ module Smith
|
|
|
255
209
|
# matches `RunResult#output`'s `.compact.first` semantics).
|
|
256
210
|
@last_failed_step = nil
|
|
257
211
|
@last_output = step_result[:output] if step_result.key?(:output) && !step_result[:output].nil?
|
|
212
|
+
# Capture serial agent attribution only on steps that carried it (an
|
|
213
|
+
# `execute :agent` step); deterministic steps have no :model key, so
|
|
214
|
+
# the last agent execution persists across intervening compute steps.
|
|
215
|
+
@last_agent_execution = { model: step_result[:model], provider: step_result[:provider] } if step_result.key?(:model)
|
|
258
216
|
end
|
|
259
217
|
end
|
|
260
218
|
|
|
@@ -275,7 +233,7 @@ module Smith
|
|
|
275
233
|
name = @next_transition_name
|
|
276
234
|
@next_transition_name = nil
|
|
277
235
|
transition = self.class.find_transition(name) ||
|
|
278
|
-
|
|
236
|
+
raise(UnresolvedTransitionError.new(name, self.class, @state))
|
|
279
237
|
validate_transition_origin!(transition)
|
|
280
238
|
transition
|
|
281
239
|
else
|
|
@@ -316,10 +274,10 @@ module Smith
|
|
|
316
274
|
# terminal state never produces a synthetic error even if the
|
|
317
275
|
# snapshot wasn't cleared.
|
|
318
276
|
effective_steps = if steps.empty? && failed? && @last_failed_step
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
277
|
+
[reconstruct_failed_step]
|
|
278
|
+
else
|
|
279
|
+
steps
|
|
280
|
+
end
|
|
323
281
|
|
|
324
282
|
RunResult.new(
|
|
325
283
|
state: @state,
|
|
@@ -336,59 +294,11 @@ module Smith
|
|
|
336
294
|
end
|
|
337
295
|
|
|
338
296
|
def reconstruct_failed_step
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
# Skip const_get for retryable-bearing families. An unknown
|
|
345
|
-
# subclass with a message-only constructor would const_get
|
|
346
|
-
# successfully but discard the snapshot's `retryable`/`kind`/
|
|
347
|
-
# `details` (defaults to nil), and host retry classification
|
|
348
|
-
# could misclassify a retryable failure as terminal.
|
|
349
|
-
# Family fallback rebuilds the parent class with kwargs intact.
|
|
350
|
-
family_fallback(snap)
|
|
351
|
-
else
|
|
352
|
-
# Unknown subclass without retryable-bearing semantics. Try
|
|
353
|
-
# the exact class for shape preservation; fall back via family
|
|
354
|
-
# if the constructor doesn't accept message-only args (or the
|
|
355
|
-
# class doesn't exist).
|
|
356
|
-
begin
|
|
357
|
-
Kernel.const_get(snap[:error_class]).new(snap[:error_message])
|
|
358
|
-
rescue NameError, ArgumentError
|
|
359
|
-
family_fallback(snap)
|
|
360
|
-
end
|
|
361
|
-
end
|
|
362
|
-
|
|
363
|
-
# Symbol coercion on the way out: live steps carry these as
|
|
364
|
-
# symbols; JSON round-trip stringifies them; coerce back to
|
|
365
|
-
# match fresh-run shape exactly.
|
|
366
|
-
{
|
|
367
|
-
transition: normalize_transition_name(snap[:transition]),
|
|
368
|
-
from: normalize_state_name(snap[:from]),
|
|
369
|
-
to: normalize_state_name(snap[:to]),
|
|
370
|
-
error: error
|
|
371
|
-
}
|
|
372
|
-
end
|
|
373
|
-
|
|
374
|
-
def family_fallback(snap)
|
|
375
|
-
case snap[:error_family]
|
|
376
|
-
when "deterministic_step_failure"
|
|
377
|
-
Smith::DeterministicStepFailure.new(
|
|
378
|
-
snap[:error_message],
|
|
379
|
-
retryable: snap[:error_retryable],
|
|
380
|
-
kind: snap[:error_kind],
|
|
381
|
-
details: snap[:error_details]
|
|
382
|
-
)
|
|
383
|
-
when "tool_guardrail_failed"
|
|
384
|
-
Smith::ToolGuardrailFailed.new(snap[:error_message], retryable: snap[:error_retryable])
|
|
385
|
-
when "tool_capture_failed"
|
|
386
|
-
Smith::ToolCaptureFailed.from_details(snap.fetch(:error_details))
|
|
387
|
-
when "deadline_exceeded" then Smith::DeadlineExceeded.new(snap[:error_message])
|
|
388
|
-
when "agent_error" then Smith::AgentError.new(snap[:error_message])
|
|
389
|
-
when "workflow_error" then Smith::WorkflowError.new(snap[:error_message])
|
|
390
|
-
else RuntimeError.new(snap[:error_message])
|
|
391
|
-
end
|
|
297
|
+
FailureReconstructor.new(
|
|
298
|
+
snapshot: @last_failed_step,
|
|
299
|
+
transition_normalizer: method(:normalize_transition_name),
|
|
300
|
+
state_normalizer: method(:normalize_state_name)
|
|
301
|
+
).call
|
|
392
302
|
end
|
|
393
303
|
|
|
394
304
|
def seed_initial_session_messages
|
|
@@ -396,6 +306,7 @@ module Smith
|
|
|
396
306
|
return if messages.nil?
|
|
397
307
|
|
|
398
308
|
@session_messages = messages
|
|
309
|
+
@seed_message_count = messages.length
|
|
399
310
|
@seed_digest = compute_seed_digest(messages)
|
|
400
311
|
end
|
|
401
312
|
|
|
@@ -453,7 +364,8 @@ module Smith
|
|
|
453
364
|
# Same rule applies to nested-workflow rollup (see
|
|
454
365
|
# `nested_execution.rb`).
|
|
455
366
|
def snapshot_usage_entries
|
|
456
|
-
@
|
|
367
|
+
entries = @usage_mutex.synchronize { @usage_entries.dup }
|
|
368
|
+
entries.map { |entry| Workflow::UsageEntry.from_h(snapshot_value(entry.to_h)) }
|
|
457
369
|
end
|
|
458
370
|
|
|
459
371
|
def tool_result_collector
|
data/lib/smith.rb
CHANGED
|
@@ -40,11 +40,15 @@ module Smith
|
|
|
40
40
|
setting :trace_transitions, default: true
|
|
41
41
|
setting :trace_tool_calls, default: true
|
|
42
42
|
setting :trace_token_usage, default: true
|
|
43
|
+
setting :trace_provider_calls, default: true
|
|
43
44
|
setting :trace_cost, default: true
|
|
44
45
|
setting :trace_fields
|
|
45
46
|
setting :trace_content, default: false
|
|
46
|
-
|
|
47
|
-
|
|
47
|
+
# Merge the ambient Smith::Attribution fields (execution_key, transition,
|
|
48
|
+
# branch_key, round, ...) into every trace payload. Attribution keys are
|
|
49
|
+
# identifiers, not content; they still pass through any configured
|
|
50
|
+
# trace_fields allowlist, and caller-supplied keys win on conflict.
|
|
51
|
+
setting :trace_attribution, default: true
|
|
48
52
|
|
|
49
53
|
# Pricing (§4.5 — model-call cost computation)
|
|
50
54
|
setting :pricing, default: nil
|
|
@@ -177,6 +181,7 @@ end
|
|
|
177
181
|
# Leaf modules (no internal dependencies)
|
|
178
182
|
require_relative "smith/types"
|
|
179
183
|
require_relative "smith/errors"
|
|
184
|
+
require_relative "smith/tool_failure_notification_failed"
|
|
180
185
|
require_relative "smith/persistence_payload_conflict"
|
|
181
186
|
require_relative "smith/exponential_backoff"
|
|
182
187
|
|
|
@@ -186,6 +191,7 @@ require_relative "smith/events"
|
|
|
186
191
|
require_relative "smith/events/subscription"
|
|
187
192
|
require_relative "smith/events/bus"
|
|
188
193
|
require_relative "smith/events/step_completed"
|
|
194
|
+
require_relative "smith/events/step_failed"
|
|
189
195
|
|
|
190
196
|
# Budget (depends on Errors)
|
|
191
197
|
require_relative "smith/budget"
|
|
@@ -205,6 +211,7 @@ require_relative "smith/pricing"
|
|
|
205
211
|
require_relative "smith/models/profile"
|
|
206
212
|
require_relative "smith/models"
|
|
207
213
|
require_relative "smith/models/inference"
|
|
214
|
+
require_relative "smith/models/tool_routing"
|
|
208
215
|
require_relative "smith/models/normalizer"
|
|
209
216
|
|
|
210
217
|
# OpenAI /v1/responses routing prepend. Dormant until
|
|
@@ -254,6 +261,7 @@ require_relative "smith/context/session"
|
|
|
254
261
|
|
|
255
262
|
# Agent (depends on RubyLLM::Agent)
|
|
256
263
|
require_relative "smith/agent"
|
|
264
|
+
require_relative "smith/agent/completion"
|
|
257
265
|
require_relative "smith/agent/lifecycle"
|
|
258
266
|
require_relative "smith/agent/registry_binding"
|
|
259
267
|
require_relative "smith/agent/registry"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: smith-agents
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.10.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Samuel Ralak
|
|
@@ -117,22 +117,16 @@ dependencies:
|
|
|
117
117
|
name: ruby_llm
|
|
118
118
|
requirement: !ruby/object:Gem::Requirement
|
|
119
119
|
requirements:
|
|
120
|
-
- -
|
|
121
|
-
- !ruby/object:Gem::Version
|
|
122
|
-
version: '1.15'
|
|
123
|
-
- - "<"
|
|
120
|
+
- - '='
|
|
124
121
|
- !ruby/object:Gem::Version
|
|
125
|
-
version:
|
|
122
|
+
version: 1.16.0
|
|
126
123
|
type: :runtime
|
|
127
124
|
prerelease: false
|
|
128
125
|
version_requirements: !ruby/object:Gem::Requirement
|
|
129
126
|
requirements:
|
|
130
|
-
- -
|
|
131
|
-
- !ruby/object:Gem::Version
|
|
132
|
-
version: '1.15'
|
|
133
|
-
- - "<"
|
|
127
|
+
- - '='
|
|
134
128
|
- !ruby/object:Gem::Version
|
|
135
|
-
version:
|
|
129
|
+
version: 1.16.0
|
|
136
130
|
description: Smith is a workflow-first multi-agent orchestration library built on
|
|
137
131
|
RubyLLM. It provides state machine modeling, typed contracts, budget enforcement,
|
|
138
132
|
guardrails, and observability for agent workflows.
|
|
@@ -159,16 +153,34 @@ files:
|
|
|
159
153
|
- lib/generators/smith/install/templates/smith.rb.tt
|
|
160
154
|
- lib/smith.rb
|
|
161
155
|
- lib/smith/agent.rb
|
|
156
|
+
- lib/smith/agent/chat_construction.rb
|
|
157
|
+
- lib/smith/agent/completion.rb
|
|
158
|
+
- lib/smith/agent/completion_usage_recording.rb
|
|
159
|
+
- lib/smith/agent/dynamic_configuration.rb
|
|
160
|
+
- lib/smith/agent/fallback_configuration.rb
|
|
161
|
+
- lib/smith/agent/invocation_preparation.rb
|
|
162
162
|
- lib/smith/agent/lifecycle.rb
|
|
163
|
+
- lib/smith/agent/model_reference.rb
|
|
164
|
+
- lib/smith/agent/provider_attempt.rb
|
|
165
|
+
- lib/smith/agent/provider_call_timing.rb
|
|
166
|
+
- lib/smith/agent/provider_candidate_sequence.rb
|
|
167
|
+
- lib/smith/agent/provider_completion.rb
|
|
168
|
+
- lib/smith/agent/provider_failure_handling.rb
|
|
169
|
+
- lib/smith/agent/provider_usage.rb
|
|
163
170
|
- lib/smith/agent/registry.rb
|
|
164
171
|
- lib/smith/agent/registry/execution_binding_capture.rb
|
|
165
172
|
- lib/smith/agent/registry/introspection.rb
|
|
166
173
|
- lib/smith/agent/registry/mutation_boundary.rb
|
|
167
174
|
- lib/smith/agent/registry_binding.rb
|
|
175
|
+
- lib/smith/agent/reserved_input_bridge.rb
|
|
176
|
+
- lib/smith/agent/usage_entry_recording.rb
|
|
177
|
+
- lib/smith/agent/usage_traces.rb
|
|
178
|
+
- lib/smith/agent/usage_tracking.rb
|
|
168
179
|
- lib/smith/artifacts.rb
|
|
169
180
|
- lib/smith/artifacts/file.rb
|
|
170
181
|
- lib/smith/artifacts/memory.rb
|
|
171
182
|
- lib/smith/artifacts/scoped_store.rb
|
|
183
|
+
- lib/smith/attribution.rb
|
|
172
184
|
- lib/smith/budget.rb
|
|
173
185
|
- lib/smith/budget/amount_contract.rb
|
|
174
186
|
- lib/smith/budget/amount_representation.rb
|
|
@@ -185,6 +197,7 @@ files:
|
|
|
185
197
|
- lib/smith/context/observation_masking.rb
|
|
186
198
|
- lib/smith/context/session.rb
|
|
187
199
|
- lib/smith/context/state_injection.rb
|
|
200
|
+
- lib/smith/diagnostic_text.rb
|
|
188
201
|
- lib/smith/doctor.rb
|
|
189
202
|
- lib/smith/doctor/check.rb
|
|
190
203
|
- lib/smith/doctor/checks/baseline.rb
|
|
@@ -208,15 +221,21 @@ files:
|
|
|
208
221
|
- lib/smith/events/.keep
|
|
209
222
|
- lib/smith/events/bus.rb
|
|
210
223
|
- lib/smith/events/step_completed.rb
|
|
224
|
+
- lib/smith/events/step_failed.rb
|
|
211
225
|
- lib/smith/events/subscription.rb
|
|
212
226
|
- lib/smith/exponential_backoff.rb
|
|
213
227
|
- lib/smith/guardrails.rb
|
|
214
228
|
- lib/smith/guardrails/runner.rb
|
|
215
229
|
- lib/smith/guardrails/url_verifier.rb
|
|
216
230
|
- lib/smith/models.rb
|
|
231
|
+
- lib/smith/models/ambiguous_profile_error.rb
|
|
232
|
+
- lib/smith/models/collision_error.rb
|
|
217
233
|
- lib/smith/models/inference.rb
|
|
218
234
|
- lib/smith/models/normalizer.rb
|
|
219
235
|
- lib/smith/models/profile.rb
|
|
236
|
+
- lib/smith/models/provider_qualified_registry.rb
|
|
237
|
+
- lib/smith/models/tool_routing.rb
|
|
238
|
+
- lib/smith/persisted_failure_invalid.rb
|
|
220
239
|
- lib/smith/persistence_adapters.rb
|
|
221
240
|
- lib/smith/persistence_adapters/active_record_connection_errors.rb
|
|
222
241
|
- lib/smith/persistence_adapters/active_record_exact_predicate.rb
|
|
@@ -236,22 +255,67 @@ files:
|
|
|
236
255
|
- lib/smith/persistence_adapters/version_expectation.rb
|
|
237
256
|
- lib/smith/persistence_payload_conflict.rb
|
|
238
257
|
- lib/smith/pricing.rb
|
|
258
|
+
- lib/smith/pricing_configuration_error.rb
|
|
259
|
+
- lib/smith/provider_permanent_failure.rb
|
|
239
260
|
- lib/smith/providers/openai/responses.rb
|
|
240
261
|
- lib/smith/providers/openai/routing.rb
|
|
241
262
|
- lib/smith/providers/openai/tools_extensions.rb
|
|
242
263
|
- lib/smith/railtie.rb
|
|
243
264
|
- lib/smith/tasks/doctor.rake
|
|
244
265
|
- lib/smith/tool.rb
|
|
266
|
+
- lib/smith/tool/argument_container_reader.rb
|
|
267
|
+
- lib/smith/tool/argument_scalar_snapshot.rb
|
|
268
|
+
- lib/smith/tool/argument_snapshot.rb
|
|
269
|
+
- lib/smith/tool/argument_snapshot_accounting.rb
|
|
270
|
+
- lib/smith/tool/argument_snapshot_result.rb
|
|
271
|
+
- lib/smith/tool/argument_snapshot_traversal.rb
|
|
272
|
+
- lib/smith/tool/bounded_completion_context.rb
|
|
273
|
+
- lib/smith/tool/bounded_completion_controls.rb
|
|
274
|
+
- lib/smith/tool/bounded_completion_guard.rb
|
|
275
|
+
- lib/smith/tool/bounded_completion_installation.rb
|
|
276
|
+
- lib/smith/tool/bounded_completion_state.rb
|
|
245
277
|
- lib/smith/tool/budget_enforcement.rb
|
|
278
|
+
- lib/smith/tool/call_admission.rb
|
|
246
279
|
- lib/smith/tool/call_allowance.rb
|
|
280
|
+
- lib/smith/tool/call_allowance_counter.rb
|
|
281
|
+
- lib/smith/tool/call_batch.rb
|
|
282
|
+
- lib/smith/tool/call_budget.rb
|
|
283
|
+
- lib/smith/tool/call_reservation.rb
|
|
247
284
|
- lib/smith/tool/capability_builder.rb
|
|
248
285
|
- lib/smith/tool/capture.rb
|
|
249
286
|
- lib/smith/tool/capture_configuration.rb
|
|
287
|
+
- lib/smith/tool/chat_execution_callbacks.rb
|
|
250
288
|
- lib/smith/tool/chat_execution_context.rb
|
|
251
289
|
- lib/smith/tool/compatibility.rb
|
|
290
|
+
- lib/smith/tool/execution_authority.rb
|
|
291
|
+
- lib/smith/tool/execution_authorization.rb
|
|
292
|
+
- lib/smith/tool/execution_batch.rb
|
|
293
|
+
- lib/smith/tool/execution_batch_admission.rb
|
|
294
|
+
- lib/smith/tool/execution_batch_builder.rb
|
|
295
|
+
- lib/smith/tool/execution_batch_collection.rb
|
|
296
|
+
- lib/smith/tool/execution_batch_invocations.rb
|
|
297
|
+
- lib/smith/tool/execution_batch_lifecycle.rb
|
|
298
|
+
- lib/smith/tool/execution_batch_registry.rb
|
|
299
|
+
- lib/smith/tool/execution_batch_requests.rb
|
|
300
|
+
- lib/smith/tool/execution_batch_source_call.rb
|
|
301
|
+
- lib/smith/tool/execution_batch_source_metadata.rb
|
|
302
|
+
- lib/smith/tool/execution_batch_sources.rb
|
|
303
|
+
- lib/smith/tool/execution_batch_state.rb
|
|
304
|
+
- lib/smith/tool/execution_dispatch.rb
|
|
305
|
+
- lib/smith/tool/execution_failure_handling.rb
|
|
306
|
+
- lib/smith/tool/execution_lifecycle.rb
|
|
307
|
+
- lib/smith/tool/execution_tracker.rb
|
|
308
|
+
- lib/smith/tool/fail_fast_completion.rb
|
|
309
|
+
- lib/smith/tool/graceful_completion.rb
|
|
310
|
+
- lib/smith/tool/invocation.rb
|
|
311
|
+
- lib/smith/tool/invocation_request.rb
|
|
312
|
+
- lib/smith/tool/invocation_sequence.rb
|
|
313
|
+
- lib/smith/tool/legacy_call_allowance.rb
|
|
252
314
|
- lib/smith/tool/policy.rb
|
|
253
315
|
- lib/smith/tool/scoped_context.rb
|
|
254
316
|
- lib/smith/tool_capture_failed.rb
|
|
317
|
+
- lib/smith/tool_execution_not_admitted.rb
|
|
318
|
+
- lib/smith/tool_failure_notification_failed.rb
|
|
255
319
|
- lib/smith/tools.rb
|
|
256
320
|
- lib/smith/tools/think.rb
|
|
257
321
|
- lib/smith/tools/url_fetcher.rb
|
|
@@ -314,6 +378,12 @@ files:
|
|
|
314
378
|
- lib/smith/workflow/execution_binding_resolution.rb
|
|
315
379
|
- lib/smith/workflow/execution_frame.rb
|
|
316
380
|
- lib/smith/workflow/execution_result_snapshot.rb
|
|
381
|
+
- lib/smith/workflow/failure_detail_snapshot.rb
|
|
382
|
+
- lib/smith/workflow/failure_reconstructor.rb
|
|
383
|
+
- lib/smith/workflow/failure_record.rb
|
|
384
|
+
- lib/smith/workflow/failure_record_restore.rb
|
|
385
|
+
- lib/smith/workflow/failure_record_text.rb
|
|
386
|
+
- lib/smith/workflow/failure_record_validator.rb
|
|
317
387
|
- lib/smith/workflow/fanout_execution.rb
|
|
318
388
|
- lib/smith/workflow/graph.rb
|
|
319
389
|
- lib/smith/workflow/graph/contract_helpers.rb
|
|
@@ -348,6 +418,7 @@ files:
|
|
|
348
418
|
- lib/smith/workflow/graph/transition_snapshot.rb
|
|
349
419
|
- lib/smith/workflow/graph/validator.rb
|
|
350
420
|
- lib/smith/workflow/graph_dsl.rb
|
|
421
|
+
- lib/smith/workflow/guarded_step_execution.rb
|
|
351
422
|
- lib/smith/workflow/guardrail_integration.rb
|
|
352
423
|
- lib/smith/workflow/identifier.rb
|
|
353
424
|
- lib/smith/workflow/message_admission.rb
|