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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 06665b52b660baf1f846ebd104caec677044595bd4aaf71ecf6ac3120e18d478
|
|
4
|
+
data.tar.gz: '09dd67e047e0f2a3dc6afd0f6b320ab7d5b7f2f23460ddae9576860e7b2e1329'
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 76771bcb4d58d45f110b16a590579a34f698f1e9e2b75cca7806a5c5864f50c8209afc4a258a587069c6dd97f6ae87c1d0a62d434afd4b415f04e199561e79a6
|
|
7
|
+
data.tar.gz: 5160511ae311634881c774fa8040d3b69f93171e20e76aca595821c9eb7a767c76d0165ffe562190b16f624a4ba2e3e272822b3a1cf6b852ae2d2af7d7521994
|
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,462 @@ All notable changes to Smith are documented in this file.
|
|
|
4
4
|
|
|
5
5
|
Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Versioning follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html). Smith is pre-1.0 and under active development; expect occasional contract tightening between minor versions until 1.0.
|
|
6
6
|
|
|
7
|
+
## [0.10.0] - 2026-08-24
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- Last-serial-agent-execution attribution. A deterministic (`compute`) step can
|
|
12
|
+
now read the model and provider that actually served the most recent serial
|
|
13
|
+
`execute :agent` step via `DeterministicStep#last_agent_model` /
|
|
14
|
+
`#last_agent_provider` (symmetric with `#last_output`, which returns that
|
|
15
|
+
step's content). The value is the resolved model/provider after fallback
|
|
16
|
+
resolution, so a step that failed its primary and completed on a
|
|
17
|
+
`fallback_models` entry reports the model that actually ran, never the
|
|
18
|
+
configured primary. It is durable lifecycle state (persisted in `to_state` as
|
|
19
|
+
`last_agent_execution`, restored across crash/resume), not a trace scrape, and
|
|
20
|
+
is `nil` until a serial agent step runs. Backward compatible: pre-upgrade
|
|
21
|
+
states have no `last_agent_execution` key and restore to `nil`; old readers
|
|
22
|
+
slice off the unknown key.
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
|
|
26
|
+
- Structured agent outputs recorded as session messages (Hash or Array
|
|
27
|
+
content) are now serialized to JSON at the RubyLLM provider boundary when
|
|
28
|
+
replayed to a later agent step. RubyLLM treats a Hash message content as
|
|
29
|
+
attachments and opens each value as a file, so replaying a prior structured
|
|
30
|
+
output to the next agent in a workflow session raised `Errno::ENOENT`. The
|
|
31
|
+
session store is unaffected: `last_output` and persisted `session_messages`
|
|
32
|
+
keep the raw structured value; only the provider-facing copy is serialized.
|
|
33
|
+
|
|
34
|
+
## [0.9.0] - 2026-08-03
|
|
35
|
+
|
|
36
|
+
### Upgrade notes
|
|
37
|
+
|
|
38
|
+
- Rollback after running fan-out branches under this version is a one-way
|
|
39
|
+
door for those runs: composite branch effects written with attribution
|
|
40
|
+
values (usage entries) or batch-correlated `tool_call_id` capture entries
|
|
41
|
+
fail an older gem's exact-key validation at reduction or recovery.
|
|
42
|
+
Drain in-flight composite runs before rolling the gem back; plain
|
|
43
|
+
checkpoint payloads are unaffected (restored pre-attribution documents
|
|
44
|
+
re-serialize byte-identically, and old readers slice off unknown keys).
|
|
45
|
+
- Hosts with a configured trace adapter see new output on upgrade without
|
|
46
|
+
any host change: one `:provider_call` line per provider attempt, one
|
|
47
|
+
`:cost` line per priced completed invocation, failed `:transition` lines
|
|
48
|
+
marked `outcome: :failed`, and ambient attribution keys (including
|
|
49
|
+
`execution_key`, the Smith persistence key) merged into every payload.
|
|
50
|
+
Identifier-only, but plan for the volume and shape change, especially
|
|
51
|
+
with `Smith::Trace::Logger` in production.
|
|
52
|
+
- Hosts with a `trace_fields` allowlist for `:transition` must add
|
|
53
|
+
`:outcome` (and the error keys they want) or failed transitions render
|
|
54
|
+
indistinguishable from successes under the allowlist.
|
|
55
|
+
- `Smith::Trace::Memory` is now bounded (default 10,000 entries, silent
|
|
56
|
+
drop with `dropped_count`); previously it accumulated without limit.
|
|
57
|
+
|
|
58
|
+
### Added
|
|
59
|
+
|
|
60
|
+
- Add `Smith::Attribution`, an immutable thread-local execution attribution
|
|
61
|
+
context (`execution_key`, `transition`, `from`, `to`, `branch_key`,
|
|
62
|
+
`round`). Workflow execution installs it per step (the `execution_key`
|
|
63
|
+
defaults to the persistence key of a persisted run), fan-out carries it
|
|
64
|
+
into branch threads and overlays the branch key, and evaluator-optimizer
|
|
65
|
+
rounds overlay the round index. Hosts can seed an outer scope with
|
|
66
|
+
`Smith::Attribution.with(execution_key: ...)` around non-persisted runs.
|
|
67
|
+
Restoration inside workflow execution rides `ThreadContextSnapshot`, which
|
|
68
|
+
now tracks the attribution thread key. Scope overlays are nil-ignoring
|
|
69
|
+
(`Context#merge`), but the per-step facts (`transition`, `from`, `to`)
|
|
70
|
+
are replaced verbatim, nil included (`Context#override`): a nested
|
|
71
|
+
child's `from`-less transition never inherits the parent step's `from`,
|
|
72
|
+
and the failed `:transition` trace keeps `from`/`to` present even when
|
|
73
|
+
nil for the same reason.
|
|
74
|
+
- Merge ambient attribution fields into every `Smith::Trace.record` payload.
|
|
75
|
+
Attribution keys are identifiers, not content: caller-supplied keys win on
|
|
76
|
+
conflict, the content policy is unaffected, and a configured
|
|
77
|
+
`trace_fields` allowlist stays authoritative (add attribution keys to an
|
|
78
|
+
allowlist to receive them). Disable with `Smith.config.trace_attribution =
|
|
79
|
+
false` (default true).
|
|
80
|
+
- Bound `Smith::Trace::Memory` (default 10,000 entries) with a
|
|
81
|
+
`dropped_count` reader and a `snapshot` method for readers racing
|
|
82
|
+
concurrent recording.
|
|
83
|
+
- Tag `Workflow::UsageEntry` with the ambient attribution at recording time:
|
|
84
|
+
new optional members `transition`, `branch_key`, `round`, and `attempt_id`.
|
|
85
|
+
All four are nil on entries restored from checkpoints written by earlier
|
|
86
|
+
Smith versions and are omitted from serialization when nil, so restored
|
|
87
|
+
pre-attribution documents re-serialize byte-identically (hosts that digest
|
|
88
|
+
whole persisted documents in exact-mutation proofs depend on this). A
|
|
89
|
+
rolled-back gem drops the new keys from plain checkpoint payloads
|
|
90
|
+
(`from_h` slices to known members). Composite branch effects are the
|
|
91
|
+
exception: effects written by this version from a fan-out branch carry
|
|
92
|
+
real attribution values, and an older gem's exact-key effects validation
|
|
93
|
+
rejects them, so see the upgrade notes below before rolling back.
|
|
94
|
+
`recorded_at` now carries microsecond precision (`iso8601(6)`) on new
|
|
95
|
+
entries. Recording symbolizes `transition`/`branch_key` Strings exactly
|
|
96
|
+
as `from_h` does on restore, so a host seeding String attribution through
|
|
97
|
+
`Smith::Attribution.with` gets entries equal to their restored form.
|
|
98
|
+
- Measure each provider attempt with a monotonic clock around the whole chat
|
|
99
|
+
completion (including any provider tool loop) and emit one
|
|
100
|
+
`:provider_call` trace per attempt (success or failure) carrying `model`,
|
|
101
|
+
`provider`, `duration_ms`, `attempt_id`, `attempt_index`, and `outcome`.
|
|
102
|
+
Every usage entry the attempt produced shares its `attempt_id` (an attempt
|
|
103
|
+
with an N-round tool loop records N entries): join there for the attempt's
|
|
104
|
+
single duration, never sum across entries. Gate with
|
|
105
|
+
`Smith.config.trace_provider_calls` (default true). `ProviderAttempt`
|
|
106
|
+
gains optional `attempt_id` and `duration_ms`.
|
|
107
|
+
- Add a public read-only `Workflow#usage_entries` (frozen copy under the
|
|
108
|
+
recording mutex) so hosts can diff usage across a step boundary without a
|
|
109
|
+
full `to_state` serialization; `to_state` and `snapshot_usage_entries` now
|
|
110
|
+
read the ledger under the same mutex, so a state written mid-fan-out never
|
|
111
|
+
captures a torn array.
|
|
112
|
+
- Thread real correlation identity through events: `Smith::Event#execution_id`
|
|
113
|
+
and `#trace_id` default to the ambient attribution execution key (the
|
|
114
|
+
persistence key during persisted runs) instead of a fresh random UUID per
|
|
115
|
+
event; the random fallback remains for events built outside any execution
|
|
116
|
+
scope. The `:tool_call` trace gains a nullable `tool_call_id` when the
|
|
117
|
+
invocation came from a provider batch (the tool-results capture entry
|
|
118
|
+
gains the same key under a later bullet in this release; composite
|
|
119
|
+
effects accept the extended shape with bounded value validation).
|
|
120
|
+
|
|
121
|
+
- Emit from the step-failure paths, closing the success-only observation
|
|
122
|
+
gap: both `handle_step_failure` and the unresolved-transition handler now
|
|
123
|
+
record a `:transition` trace with `outcome: :failed` plus bounded
|
|
124
|
+
classification (`error_class`, `error_family` from FailureRecord's
|
|
125
|
+
taxonomy, `retryable`) and emit a new `Smith::Events::StepFailed` event.
|
|
126
|
+
Raw error messages never ride either; an emission failure is logged and
|
|
127
|
+
can never mask the original step error. The marker key is `outcome`
|
|
128
|
+
because `result` is a reserved content key in the trace pipeline. An
|
|
129
|
+
unresolved transition with no `:fail` transition still re-raises without
|
|
130
|
+
emitting: that path was never treated as a step. The unresolved handler
|
|
131
|
+
runs outside any step context, so it seeds the run identity explicitly;
|
|
132
|
+
both failure paths stamp persisted-run events with the persistence key.
|
|
133
|
+
Emission is terminal-per-step: a step that retries internally and then
|
|
134
|
+
succeeds emits only its `StepCompleted`; intermediate step-body retry
|
|
135
|
+
attempts stay dark at the step layer (provider-level failures remain
|
|
136
|
+
visible as `:provider_call` failure traces). `StepFailed` handlers run
|
|
137
|
+
outside the step snapshot's interrupt-masked region (emission is staged
|
|
138
|
+
in the failure rescue and flushed after the mask closes), interruptible
|
|
139
|
+
exactly like `StepCompleted` handlers. A step body that surfaces Smith's
|
|
140
|
+
own `UnresolvedTransitionError` emits exactly one `StepFailed` under the
|
|
141
|
+
real step identity; the unresolved handler recognizes the already-emitted
|
|
142
|
+
error instead of emitting a second event under the requested (never
|
|
143
|
+
executed) name.
|
|
144
|
+
- Emit the long-advertised `:cost` trace: one per completed agent
|
|
145
|
+
invocation, whose value is the sum of that invocation's per-response
|
|
146
|
+
usage-entry costs. Summing per response is what tiered catalogs actually
|
|
147
|
+
bill; pricing the aggregate token totals as one call would resolve the
|
|
148
|
+
wrong tier for multi-response tool loops. Emitted only for fully metered,
|
|
149
|
+
fully priced invocations (a partially priced or partially metered
|
|
150
|
+
invocation emits nothing rather than presenting an incomplete figure);
|
|
151
|
+
gated by the existing `trace_cost` setting (the per-type gates live in
|
|
152
|
+
the built-in adapters; a custom adapter receives every type). `:cost`
|
|
153
|
+
traces are not a spend total: billed failed and partial attempts appear
|
|
154
|
+
only in usage entries. The same per-response sum now becomes
|
|
155
|
+
`agent_result.cost`, so budget settlement, result surfaces, recorded
|
|
156
|
+
entries, and the trace all agree on one invocation cost.
|
|
157
|
+
- The OpenTelemetry adapter now creates retroactive spans with real
|
|
158
|
+
durations (span start backdated by `:tool_call` seconds or
|
|
159
|
+
`:provider_call` milliseconds; instant spans otherwise), preserves
|
|
160
|
+
numeric attribute types instead of stringifying everything, and uses only
|
|
161
|
+
the documented opentelemetry-api surface (`Tracer#start_span` with
|
|
162
|
+
`start_timestamp`, `Span#finish` with `end_timestamp`).
|
|
163
|
+
|
|
164
|
+
- Add a `workflow` discriminator to the ambient attribution, every trace
|
|
165
|
+
payload, usage entries, and the `StepCompleted`/`StepFailed` events: the
|
|
166
|
+
emitting workflow's class name ("anonymous" when unnamed), so
|
|
167
|
+
nested-child graph facts are distinguishable from parent facts under the
|
|
168
|
+
shared root execution identity. Nil-omitted from serialized entries like
|
|
169
|
+
the other attribution members.
|
|
170
|
+
- Every provider attempt now emits its `:provider_call` trace: `outcome` is
|
|
171
|
+
`:success`, `:failure` (provider failure, fallback may continue), or
|
|
172
|
+
`:aborted` (a non-provider error that re-raises), so prefix-accounted
|
|
173
|
+
usage entries always have their attempt join target.
|
|
174
|
+
- The tool-results capture entry gains `tool_call_id` when the invocation
|
|
175
|
+
came from a provider batch (omitted otherwise, so direct-invocation and
|
|
176
|
+
pre-existing payloads keep their exact two-key shape); composite effects
|
|
177
|
+
accept the extended shape while still rejecting unknown keys.
|
|
178
|
+
- `StepFailed` handlers now run outside the step snapshot's
|
|
179
|
+
interrupt-masked region: emission is staged in the failure rescue and
|
|
180
|
+
flushed after the mask closes with explicitly seeded run identity, so a
|
|
181
|
+
slow host handler can no longer make the workflow thread unkillable and
|
|
182
|
+
handlers match `StepCompleted`'s interruptibility.
|
|
183
|
+
|
|
184
|
+
### Fixed
|
|
185
|
+
|
|
186
|
+
- A user-declared `:fail` transition no longer inherits the early order
|
|
187
|
+
position of the auto-generated placeholder created by `state :failed`:
|
|
188
|
+
redeclaring a generated transition takes a fresh declaration position, so
|
|
189
|
+
it can no longer shadow a same-origin primary transition at run time.
|
|
190
|
+
Genuine user redefinitions keep their original position, and subclasses
|
|
191
|
+
inherit the bookkeeping.
|
|
192
|
+
- Budget cost settlement now consumes the per-response priced sum instead
|
|
193
|
+
of pricing the invocation's aggregate token totals. Under linear pricing
|
|
194
|
+
the figures are identical; under tiered pricing the aggregate resolved
|
|
195
|
+
the wrong tier (or missed every tier and settled zero), so a
|
|
196
|
+
cost-budgeted workflow could keep spending after its real billed cost
|
|
197
|
+
exceeded the budget.
|
|
198
|
+
|
|
199
|
+
### Removed
|
|
200
|
+
|
|
201
|
+
- Remove the never-read `trace_retention` and `trace_tenant_isolation`
|
|
202
|
+
settings. Both were silent no-ops since introduction; reading or writing
|
|
203
|
+
them now raises, so a host relying on the illusion fails loudly instead
|
|
204
|
+
of silently.
|
|
205
|
+
|
|
206
|
+
### Changed
|
|
207
|
+
|
|
208
|
+
- Workflow step failure is now observable: subscribers to the events bus
|
|
209
|
+
receive `StepFailed` where previously failures emitted nothing (the
|
|
210
|
+
success-only scope is gone), and trace consumers see failed `:transition`
|
|
211
|
+
payloads distinguished by `outcome: :failed`.
|
|
212
|
+
- `Workflow::Composite::Effects` validates usage-entry keys as
|
|
213
|
+
required-plus-allowed instead of exact: entries from an older producer
|
|
214
|
+
(missing the optional attribution keys) stay valid, current entries with
|
|
215
|
+
attribution pass, and unknown keys still reject. The optional keys are
|
|
216
|
+
bounded values, not just bounded keys: `transition`, `branch_key`, and
|
|
217
|
+
`workflow` must be non-empty Strings up to 256 characters, `round` a
|
|
218
|
+
non-negative Integer, and `attempt_id` a UUID when present. Tool results
|
|
219
|
+
accept the extended capture shape: `tool_call_id`, when present, must be
|
|
220
|
+
a non-empty String up to 1024 characters (`tool` keeps its exact prior
|
|
221
|
+
validation).
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
- `Smith::Events` subscriptions now live in per-class buckets guarded by a
|
|
225
|
+
mutex: emit touches only the buckets for the event's ancestors instead of
|
|
226
|
+
scanning every subscription, dispatch order remains registration order,
|
|
227
|
+
and `is_a?` matching semantics are unchanged (instance-extended modules
|
|
228
|
+
dispatch through the singleton class; immediate values, which have no
|
|
229
|
+
singleton class and cannot be extended, dispatch through their class
|
|
230
|
+
ancestors). `Subscription#cancel` now
|
|
231
|
+
detaches from the registry, so cancelled subscriptions (including
|
|
232
|
+
`Events.within` scopes) no longer leak. Handlers run outside the registry
|
|
233
|
+
lock, so a handler may subscribe or cancel without deadlocking.
|
|
234
|
+
- `Smith::Trace::Memory#record` and `#clear!` are mutex-guarded and safe
|
|
235
|
+
under parallel fan-out branches.
|
|
236
|
+
|
|
237
|
+
## [0.8.0] - 2026-07-25
|
|
238
|
+
|
|
239
|
+
### Added
|
|
240
|
+
|
|
241
|
+
- Add opt-in graceful agent tool-budget exhaustion with a finite
|
|
242
|
+
`tool_calls` budget and `tool_budget_exhaustion :complete`. Smith consumes
|
|
243
|
+
model-requested calls atomically, rejects oversized batches without partial
|
|
244
|
+
execution, pairs every rejected call id, and performs one tool-disabled final
|
|
245
|
+
completion.
|
|
246
|
+
- Add one-use call admissions so an admitted Smith tool is not double charged,
|
|
247
|
+
while exact tool identity prevents nested tools from stealing an admission.
|
|
248
|
+
- Aggregate trustworthy token usage across every assistant response in a
|
|
249
|
+
successful RubyLLM tool loop, record one durable usage entry per provider
|
|
250
|
+
response, and preserve completed-prefix usage when a later provider round
|
|
251
|
+
fails.
|
|
252
|
+
- Expose immutable normalized tool-call metadata and one execution-scoped,
|
|
253
|
+
host-seedable invocation sequence across Smith-managed chats, branches,
|
|
254
|
+
threads, and fibers without introducing host persistence semantics.
|
|
255
|
+
- Add immutable exact tool-call budgets with aggregate and per-tool limits for
|
|
256
|
+
host-controlled execution scopes.
|
|
257
|
+
- Add a host-neutral whole-batch admission callback and per-invocation dispatch
|
|
258
|
+
failure callback. Smith snapshots immutable requests, admits the complete
|
|
259
|
+
Smith-managed subset of the provider batch before dispatch, and never reports
|
|
260
|
+
failure for a batch whose host admission callback raised.
|
|
261
|
+
- Add a Claude 5+ family inference rule: Fable, Mythos, Opus, Sonnet, and Haiku
|
|
262
|
+
ids with major version 5 or higher resolve to adaptive thinking, no
|
|
263
|
+
temperature accepted, and native tools-with-thinking, so an agent-declared
|
|
264
|
+
`temperature` is stripped instead of being sent to a provider that rejects
|
|
265
|
+
it. Dotted 4.x ids (for example `claude-haiku-4-5`) keep matching the 4.x
|
|
266
|
+
budget-tokens rule.
|
|
267
|
+
- Add new public error classes hosts may rescue or allowlist:
|
|
268
|
+
`Smith::PricingConfigurationError`, `Smith::ProviderPermanentFailure`
|
|
269
|
+
(carries `provider`, `model_id`, and `source_error_class`),
|
|
270
|
+
`Smith::Models::AmbiguousProfileError`, `Smith::Models::CollisionError`,
|
|
271
|
+
`Smith::ToolDispatchRejected`, `Smith::ToolOutcomeUncertain`,
|
|
272
|
+
`Smith::ToolExecutionNotAdmitted`, `Smith::ToolFailureNotificationFailed`,
|
|
273
|
+
`Smith::BoundedCompletionError`, and `Smith::PersistedFailureInvalid`.
|
|
274
|
+
Terminal tool-evidence families surface through
|
|
275
|
+
`Smith::Errors.retry_forbidden?` / `retry_forbidden_class?` and must never be
|
|
276
|
+
retried by host jobs.
|
|
277
|
+
- `Smith::Doctor` reports a dedicated failing `models.ambiguity` check when a
|
|
278
|
+
registered agent declares an unqualified model id registered under multiple
|
|
279
|
+
providers, instead of aborting the run with `AmbiguousProfileError`, and now
|
|
280
|
+
validates the assigned pricing catalog (legacy model-only keys, malformed
|
|
281
|
+
entries, and colliding keys fail the `config.pricing` check).
|
|
282
|
+
- Capture the causal failure classification behind an uncertain tool outcome
|
|
283
|
+
into the durable failure record: a persisted `Smith::ToolOutcomeUncertain`
|
|
284
|
+
carries bounded `error_cause_class`, `error_cause_family`, and
|
|
285
|
+
`error_cause_message` from its cause so a restored host can distinguish a
|
|
286
|
+
deadline, cancellation, or defect. The three attributes travel as one unit;
|
|
287
|
+
legacy records omit all three, and a partial or unknown-family set fails
|
|
288
|
+
closed at state admission.
|
|
289
|
+
|
|
290
|
+
### Changed
|
|
291
|
+
|
|
292
|
+
- Bump `Smith::EXECUTION_SEMANTICS_VERSION` from `3` directly to `5` for exact
|
|
293
|
+
per-tool budgets, shared composite admission, bounded graceful completion,
|
|
294
|
+
and cumulative tool-loop usage. The value `4` was consumed transiently during
|
|
295
|
+
development of this slice and is intentionally skipped so a composite plan
|
|
296
|
+
persisted against an interim build can never read as compatible with the
|
|
297
|
+
released semantics; consumers compare by exact equality, so every pre-`5`
|
|
298
|
+
plan is invalidated either way.
|
|
299
|
+
- Key model profiles, fallback candidates, usage telemetry, and pricing by exact
|
|
300
|
+
provider/model identity. Provider-qualified pricing never falls back to a
|
|
301
|
+
legacy model-only rate, and fallback declarations must name their provider.
|
|
302
|
+
- Let observation masking preserve the exact immutable seed-message prefix
|
|
303
|
+
while bounding only later workflow observations. The prefix length is
|
|
304
|
+
persisted and validated across restart; legacy state defaults to zero.
|
|
305
|
+
- Require the exactly qualified RubyLLM `1.16.0`; graceful completion is
|
|
306
|
+
isolated behind that verified chat interface and fails closed on incompatible
|
|
307
|
+
hooks. Future RubyLLM versions require an explicit compatibility pass.
|
|
308
|
+
- Enforce the pricing-catalog key policy at admission time instead of inside
|
|
309
|
+
accounting: `Smith::Pricing.validate_catalog!` rejects legacy model-only
|
|
310
|
+
keys, unrecognized key shapes, malformed entries, and post-normalization
|
|
311
|
+
collisions, and the doctor runs it against the assigned catalog.
|
|
312
|
+
`Pricing.compute_cost` itself never raises: a provider-qualified lookup reads
|
|
313
|
+
only provider-qualified entries and returns nil (visibly unpriced) when only
|
|
314
|
+
a legacy rate exists, so an in-flight accounting path can never mask a
|
|
315
|
+
provider error with a pricing configuration error.
|
|
316
|
+
- Registering a model profile whose capabilities differ from the profile
|
|
317
|
+
already registered for the same provider/model identity now raises
|
|
318
|
+
`Smith::Models::CollisionError`; earlier releases silently replaced the
|
|
319
|
+
profile on Rails reload. Re-registering a value-identical profile stays
|
|
320
|
+
idempotent (see Migration notes).
|
|
321
|
+
- `Smith::Models::Normalizer` tool/endpoint routing no longer requires active
|
|
322
|
+
thinking: endpoint compatibility is evaluated on every chat construction, the
|
|
323
|
+
endpoint preserving the strictly larger compatible tool subset wins (ties
|
|
324
|
+
keep the current endpoint; a single forced tool that only one endpoint can
|
|
325
|
+
carry takes precedence), and each dropped tool records a `:tool_dropped`
|
|
326
|
+
decision. See docs/TOOLS_AND_GUARDRAILS.md.
|
|
327
|
+
- Agent tool evidence is tracked per transition: once any tool starts inside a
|
|
328
|
+
transition, a later provider failure in that transition (including one from a
|
|
329
|
+
concurrently executing parallel branch that never ran a tool itself) refuses
|
|
330
|
+
model fallback and surfaces `Smith::ToolOutcomeUncertain`. This is
|
|
331
|
+
deliberately conservative and fail-closed; branch-scoped evidence that
|
|
332
|
+
restores fallback for provably tool-free sibling branches is planned, and
|
|
333
|
+
child-workflow tool evidence does not yet mark the parent transition.
|
|
334
|
+
- `from_state` failure-record problems raise `Smith::PersistedFailureInvalid`
|
|
335
|
+
while other persisted-shape problems keep raising
|
|
336
|
+
`Smith::SerializationError`; both descend from `Smith::Error`, and hosts
|
|
337
|
+
rescuing `SerializationError` around restore must handle both.
|
|
338
|
+
- Restored failure records no longer resolve or construct arbitrary error
|
|
339
|
+
classes named by persisted data: host-defined subclasses reconstruct as their
|
|
340
|
+
Smith family parent (family `"other"` restores as `RuntimeError`), so
|
|
341
|
+
exact-class matching against restored `last_error` values must move to family
|
|
342
|
+
or `is_a?` checks.
|
|
343
|
+
- `Smith::Agent.fallback_models` now returns a frozen array of
|
|
344
|
+
`Smith::Agent::ModelReference` values (previously raw strings), block-form
|
|
345
|
+
`model {}` declarations must return a provider-qualified reference (a bare
|
|
346
|
+
string raises `Smith::AgentError`), permanent provider failures raise
|
|
347
|
+
`Smith::ProviderPermanentFailure` instead of a retryable `AgentError`,
|
|
348
|
+
non-provider `StandardError`s raised inside a provider attempt now propagate
|
|
349
|
+
raw instead of being wrapped, and `Workflow::UsageEntry` is frozen and gained
|
|
350
|
+
a `:provider` member.
|
|
351
|
+
|
|
352
|
+
### Fixed
|
|
353
|
+
|
|
354
|
+
- Preserve the provider and model actually selected by RubyLLM in completion
|
|
355
|
+
usage, keep inherited fallback configuration immutable, and treat model-level
|
|
356
|
+
permission failures as model scoped so an eligible fallback on the same
|
|
357
|
+
provider remains available. Only account-wide authentication and payment
|
|
358
|
+
failures suppress later candidates from that provider.
|
|
359
|
+
- Prevent unavailable provider tool calls from creating an unbounded correction
|
|
360
|
+
loop by consuming the model-request allowance before dispatch.
|
|
361
|
+
- Prevent fallback-model restart after a Smith tool begins executing, because a
|
|
362
|
+
fresh chat would discard tool evidence and violate the provider protocol.
|
|
363
|
+
- Prevent transition retry, including broad explicit retry classes, when any
|
|
364
|
+
later provider, completion hook, output validation, or guardrail failure occurs
|
|
365
|
+
after a bound tool dispatch makes the external outcome uncertain.
|
|
366
|
+
- Reserve provider batches atomically against both the agent allowance and the
|
|
367
|
+
effective workflow tool budget, then reconcile unexecuted calls.
|
|
368
|
+
- Execute bounded tool loops iteratively so finite call allowances do not grow
|
|
369
|
+
Ruby stack depth, reject concurrent or callback reentry on one bounded chat,
|
|
370
|
+
and retain RubyLLM's native forced-tool-choice reset.
|
|
371
|
+
- Validate `Smith::Tool#perform` keyword arguments before budget charge and tool
|
|
372
|
+
execution, reject non-Smith bindings from the opt-in bounded policy, and fail
|
|
373
|
+
closed when raw provider params try to override reserved tool controls.
|
|
374
|
+
- Keep provider-facing tool controls and RubyLLM instrumentation aligned, and
|
|
375
|
+
restore tools, call preferences, and concurrency after success or failure.
|
|
376
|
+
- Permit bounded providers to return multiple calls in one response while more
|
|
377
|
+
than one call remains. Smith retains atomic batch reservation, deterministic
|
|
378
|
+
sequential execution, and fail-closed oversized-batch handling.
|
|
379
|
+
- Propagate one enclosing tool-call allowance into same-agent parallel and
|
|
380
|
+
heterogeneous fan-out worker threads so branches cannot multiply a host's
|
|
381
|
+
signed transition budget.
|
|
382
|
+
- Materialize one immutable admitted dispatch batch before host admission, so
|
|
383
|
+
later mutation of the provider collection, call identity, or arguments cannot
|
|
384
|
+
alter which Smith tools execute or the values they receive. The host callback
|
|
385
|
+
receives immutable source-call evidence while RubyLLM dispatches a separately
|
|
386
|
+
owned admitted call, and an atomic dispatch claim prevents callback re-entry
|
|
387
|
+
from executing the same admission twice. Continue to reject registered-tool
|
|
388
|
+
replacement and classify only guaranteed pre-`perform` failures as
|
|
389
|
+
`ToolDispatchRejected`.
|
|
390
|
+
- Capture provider batches with native `Hash` operations, bound the complete
|
|
391
|
+
provider batch to 100 calls and 1 MiB of UTF-8 call metadata, reject malformed
|
|
392
|
+
call protocol before host admission, and stop aggregate metadata capture as
|
|
393
|
+
soon as the bound is crossed.
|
|
394
|
+
- Bound immutable argument copying before child allocation, account for expanded
|
|
395
|
+
serialized occurrences even when a caller reuses a shared object graph,
|
|
396
|
+
admit each mutable container's native size before one shallow copy, verify the
|
|
397
|
+
copy retained that size before child allocation, reject hostile container
|
|
398
|
+
overrides and malformed UTF-8, and keep traversal iterative with linear time
|
|
399
|
+
and bounded space.
|
|
400
|
+
- Require one exact execution authority for every managed Smith tool dispatch.
|
|
401
|
+
Direct tool calls from provider callbacks and nested Smith tool calls now fail
|
|
402
|
+
closed; nested execution requires a future separately admitted primitive.
|
|
403
|
+
- Give terminal tool-evidence failures precedence during parallel arbitration
|
|
404
|
+
and centralize their non-retryable classification across declaration and
|
|
405
|
+
execution. Persist and restore terminal notification failures without resolving
|
|
406
|
+
or constructing arbitrary classes named by persisted data. Bound and normalize
|
|
407
|
+
failure diagnostics, capture failure class identity with native Ruby operations,
|
|
408
|
+
and reject inconsistent family/retry metadata or malformed typed failure details
|
|
409
|
+
at state admission.
|
|
410
|
+
- Cover the split execution files with direct-load contracts so each new file
|
|
411
|
+
loads standalone; the released artifact's file manifest is verified against
|
|
412
|
+
`lib/` at release time as part of the release procedure.
|
|
413
|
+
- Fail closed with a typed `Smith::AgentError` when an agent has no executable
|
|
414
|
+
model candidate (empty model chain), instead of leaking the exhausted
|
|
415
|
+
candidate sequence into nil destructuring from optimizer and orchestrator
|
|
416
|
+
paths.
|
|
417
|
+
- Attribute an account-wide authentication or payment failure to the attempted
|
|
418
|
+
reference's declared provider when the chat is unobservable, so a dead
|
|
419
|
+
provider account is not billed a second same-provider attempt.
|
|
420
|
+
- Deduplicate model candidates by physical identity, so a provider-unqualified
|
|
421
|
+
primary and a provider-qualified fallback naming the same model cannot
|
|
422
|
+
produce a duplicate attempt, and `ModelReference.coerce` now parses the
|
|
423
|
+
`"provider/model"` string form that `#to_s` emits (the first slash splits, so
|
|
424
|
+
slashed model ids round-trip).
|
|
425
|
+
- Keep captured and restored failure records symmetric for blank messages:
|
|
426
|
+
capture substitutes a deterministic placeholder for a blank error message, so
|
|
427
|
+
a workflow state whose last failed step had an empty message restores instead
|
|
428
|
+
of raising `Smith::PersistedFailureInvalid` and poisoning crash/resume.
|
|
429
|
+
- Restore legacy failure records whose `error_message` exceeds the 64 KiB
|
|
430
|
+
diagnostic bound by truncating exactly as capture truncates, instead of
|
|
431
|
+
rejecting the persisted workflow state for message length alone; non-text and
|
|
432
|
+
invalid-UTF-8 persisted values still fail closed.
|
|
433
|
+
- Scope an agent `tool_calls` budget under a legacy Hash tool-call allowance to
|
|
434
|
+
a typed rejection at both call sites instead of an untyped `NoMethodError`
|
|
435
|
+
mid-transition.
|
|
436
|
+
- Propagate a settled or exhausted batch reservation's refusal through
|
|
437
|
+
`CallAdmission#claim`, so a post-settlement execution can never run against
|
|
438
|
+
an already reconciled ledger.
|
|
439
|
+
- Keep a queued process-fatal sibling error ahead of a
|
|
440
|
+
`ToolFailureNotificationFailed` raised while notifying unsettled batch
|
|
441
|
+
failures, so notification problems cannot mask fatal arbitration outcomes.
|
|
442
|
+
|
|
443
|
+
### Migration notes
|
|
444
|
+
|
|
445
|
+
- Pricing catalogs must move to provider-qualified keys (`%w[provider model]`
|
|
446
|
+
arrays or `"provider/model"` strings). Legacy model-only keys still price
|
|
447
|
+
provider-unqualified usage for compatibility, but they fail
|
|
448
|
+
`Smith::Pricing.validate_catalog!` (now run by the doctor), and
|
|
449
|
+
provider-qualified usage never reads them: such usage records nil cost until
|
|
450
|
+
the catalog is qualified.
|
|
451
|
+
- `fallback_models` entries must name their provider (`"provider/model"`, a
|
|
452
|
+
Hash, or a `ModelReference`); bare model ids fail closed at class definition.
|
|
453
|
+
- Hosts registering `Smith::Models` profiles from reloadable code must move
|
|
454
|
+
registration to boot-once initializers or restart after editing a profile;
|
|
455
|
+
value-different re-registration now raises `Smith::Models::CollisionError`.
|
|
456
|
+
- The gem now requires exactly `ruby_llm 1.16.0` (previously `>= 1.15,
|
|
457
|
+
< 1.17`); hosts on 1.15.x must upgrade together with this release.
|
|
458
|
+
- Hosts that rescued `Smith::AgentError` for permanent provider failures should
|
|
459
|
+
rescue `Smith::ProviderPermanentFailure`, and restore-time rescues of
|
|
460
|
+
`Smith::SerializationError` must also handle
|
|
461
|
+
`Smith::PersistedFailureInvalid`.
|
|
462
|
+
|
|
7
463
|
## [0.7.0] - 2026-07-21
|
|
8
464
|
|
|
9
465
|
### Added
|
data/README.md
CHANGED
|
@@ -366,9 +366,41 @@ class SearchAgent < Smith::Agent
|
|
|
366
366
|
register_as :search_agent
|
|
367
367
|
model "claude-opus-4-7"
|
|
368
368
|
tools Smith::Tools::WebSearch, Smith::Tools::UrlFetcher
|
|
369
|
+
budget tool_calls: 3
|
|
370
|
+
tool_budget_exhaustion :complete
|
|
369
371
|
end
|
|
370
372
|
```
|
|
371
373
|
|
|
374
|
+
Agent tool-call budgets default to raising `Smith::BudgetExceeded` when a tool
|
|
375
|
+
tries to execute beyond the configured limit. Agents that declare
|
|
376
|
+
`tool_budget_exhaustion :complete` must also declare a finite `tool_calls`
|
|
377
|
+
budget. Smith then consumes each model-requested call before dispatch, executes
|
|
378
|
+
only a complete batch admitted against both agent and workflow limits, and
|
|
379
|
+
performs one tool-disabled completion when the allowance is exhausted or the
|
|
380
|
+
provider returns an oversized batch. The bounded provider loop is iterative,
|
|
381
|
+
not recursive. Calls to unavailable tools consume allowance as well, so a
|
|
382
|
+
provider cannot create an unbounded correction loop. Workflow reservations are
|
|
383
|
+
reconciled to actual Smith tool execution.
|
|
384
|
+
|
|
385
|
+
Hosts may install `Smith::Tool::CallBudget` with exact per-tool limits through
|
|
386
|
+
`Smith::Tool.with_call_budget`. Smith propagates that same root allowance into
|
|
387
|
+
parallel and heterogeneous fan-out worker threads; agent budgets narrow the
|
|
388
|
+
root and all branches share its atomic counters.
|
|
389
|
+
|
|
390
|
+
Graceful exhaustion is an in-process protocol guarantee, not durable recovery
|
|
391
|
+
for the model/tool loop. If the process exits after a tool begins and before the
|
|
392
|
+
agent completes, the host must treat the outcome as uncertain unless it owns a
|
|
393
|
+
durable per-invocation receipt and reconciliation contract. Smith also refuses
|
|
394
|
+
to start a fresh fallback-model chat after tool execution has begun because that
|
|
395
|
+
would discard the captured evidence and conversation protocol.
|
|
396
|
+
|
|
397
|
+
Within a host invocation scope, `Smith::Tool.current_invocation` exposes the
|
|
398
|
+
normalized tool-call ID and deterministic execution/batch ordinals. The ID is
|
|
399
|
+
RubyLLM correlation metadata and must not be treated as a provider idempotency
|
|
400
|
+
key. Durable hosts can pass an explicitly seeded
|
|
401
|
+
`Smith::Tool::InvocationSequence` to `with_invocation_context`; Smith itself does
|
|
402
|
+
not persist or reconcile operation receipts.
|
|
403
|
+
|
|
372
404
|
Guardrails run as input/output gates around agent calls. See [`docs/TOOLS_AND_GUARDRAILS.md`](docs/TOOLS_AND_GUARDRAILS.md).
|
|
373
405
|
|
|
374
406
|
## Budgets and Deadlines
|
|
@@ -434,7 +466,25 @@ Smith ships a per-attempt normalizer that translates the request payload to what
|
|
|
434
466
|
- OpenAI gpt-5 family reasoning_effort with `/v1/responses` routing when tools + thinking are combined
|
|
435
467
|
- Gemini 2.5+ budget_tokens
|
|
436
468
|
|
|
437
|
-
Override the inferred profile per-app via `Smith::Models.register(Profile.new(...))`.
|
|
469
|
+
Override the inferred profile per-app via `Smith::Models.register(Profile.new(...))`.
|
|
470
|
+
Profiles are keyed by both provider and model id. Pass `provider:` to
|
|
471
|
+
`Smith::Models.find` or `find_or_infer` whenever an id exists under more than
|
|
472
|
+
one provider; an ambiguous unqualified lookup fails closed. Smith never
|
|
473
|
+
hardcodes model ids in the library.
|
|
474
|
+
|
|
475
|
+
Provider-qualified fallback entries use the same exact identity:
|
|
476
|
+
|
|
477
|
+
```ruby
|
|
478
|
+
fallback_models(
|
|
479
|
+
{ model: "claude-sonnet-4-6", provider: :anthropic },
|
|
480
|
+
{ model: "openai/gpt-5", provider: :openrouter }
|
|
481
|
+
)
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
Fallback entries must include an explicit provider. Smith does not infer a
|
|
485
|
+
fallback provider from the primary model or from a model id because the same id
|
|
486
|
+
may be registered by more than one provider. Unqualified fallback entries fail
|
|
487
|
+
closed during agent configuration.
|
|
438
488
|
|
|
439
489
|
## Errors and retry
|
|
440
490
|
|
|
@@ -446,6 +496,9 @@ Smith::Errors.retryable?(error)
|
|
|
446
496
|
|
|
447
497
|
Smith::Errors.retryable_classes
|
|
448
498
|
# => [Smith::AgentError, Smith::DeadlineExceeded] (for ActiveJob retry_on)
|
|
499
|
+
|
|
500
|
+
Smith::Errors.retry_forbidden?(error)
|
|
501
|
+
# => true for terminal tool-evidence failures that must never be replayed
|
|
449
502
|
```
|
|
450
503
|
|
|
451
504
|
Workflow transitions can also declare a bounded local retry policy:
|
|
@@ -465,6 +518,23 @@ and external idempotency guarantees remain host-owned.
|
|
|
465
518
|
|
|
466
519
|
- Executable graph reachability is iterative `O(V + E)` time and `O(V)` space,
|
|
467
520
|
using indexed outgoing transitions and runtime-equivalent successor rules.
|
|
521
|
+
- Smith-managed tool arguments are copied iteratively in `O(N + B)` time and
|
|
522
|
+
`O(N + B)` bounded space for `N` expanded JSON value occurrences and `B`
|
|
523
|
+
expanded encoded bytes. Reused subgraphs are charged for every serialized
|
|
524
|
+
occurrence, native `Array` and `Hash` operations bypass hostile overrides, and
|
|
525
|
+
every mutable container's native size is admitted before one shallow copy is
|
|
526
|
+
made. Smith verifies the copy retained the admitted size before allocating or
|
|
527
|
+
traversing children. One immutable dispatch collection remains authoritative
|
|
528
|
+
from host admission through RubyLLM execution. The complete provider batch is
|
|
529
|
+
bounded to 100 calls and 1 MiB of normalized UTF-8 call metadata. The capture
|
|
530
|
+
boundary transfers temporary ownership of the provider collection and argument
|
|
531
|
+
graph to Smith: callers must not mutate them concurrently. Smith detects changed
|
|
532
|
+
cardinality and fails closed, but Ruby cannot prevent an unrelated writer from
|
|
533
|
+
growing a caller-owned container between native size admission and the native
|
|
534
|
+
copy without freezing that caller-owned object.
|
|
535
|
+
- Admitted tool-receipt settlement scans each batch once in `O(B)` time and
|
|
536
|
+
`O(B)` state for `B` admitted Smith calls, including when an earlier terminal
|
|
537
|
+
callback fails.
|
|
468
538
|
- Parallel execution is `O(B)` scheduling and result space for `B` branches,
|
|
469
539
|
with at most `C = parallel_concurrency` active worker threads per top-level
|
|
470
540
|
execution. Re-entrant fan-out inherits the same cancellation signal and uses
|
data/docs/CONFIGURATION.md
CHANGED
|
@@ -49,6 +49,21 @@ Use workflow classes for orchestration behavior:
|
|
|
49
49
|
- "How much total budget can the whole workflow consume?" -> workflow budget
|
|
50
50
|
- "Which provider credentials should the app use?" -> RubyLLM, not Smith
|
|
51
51
|
|
|
52
|
+
### Provider-qualified fallback models
|
|
53
|
+
|
|
54
|
+
Fallback models are exact model/provider identities:
|
|
55
|
+
|
|
56
|
+
```ruby
|
|
57
|
+
fallback_models(
|
|
58
|
+
{ model: "claude-sonnet-4-6", provider: :anthropic },
|
|
59
|
+
{ model: "openai/gpt-5", provider: :openrouter }
|
|
60
|
+
)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Every fallback must name its provider. Smith does not inherit the primary
|
|
64
|
+
model's provider or infer one from the model id; an unqualified fallback fails
|
|
65
|
+
closed during agent configuration.
|
|
66
|
+
|
|
52
67
|
### Full `Smith.configure` Example
|
|
53
68
|
|
|
54
69
|
```ruby
|
|
@@ -68,11 +83,11 @@ Smith.configure do |config|
|
|
|
68
83
|
tool_call: %i[tool duration]
|
|
69
84
|
}
|
|
70
85
|
config.trace_content = false
|
|
71
|
-
config.
|
|
72
|
-
config.
|
|
86
|
+
config.trace_provider_calls = true
|
|
87
|
+
config.trace_attribution = true
|
|
73
88
|
|
|
74
89
|
config.pricing = {
|
|
75
|
-
|
|
90
|
+
%w[openai gpt-4.1-nano] => {
|
|
76
91
|
input_cost_per_token: 0.0000001,
|
|
77
92
|
output_cost_per_token: 0.0000004
|
|
78
93
|
}
|
|
@@ -82,6 +97,12 @@ Smith.configure do |config|
|
|
|
82
97
|
end
|
|
83
98
|
```
|
|
84
99
|
|
|
100
|
+
Provider-qualified model calls require provider-qualified pricing keys. Smith
|
|
101
|
+
also accepts the string form `"openai/gpt-4.1-nano"`. Legacy model-only keys
|
|
102
|
+
remain available only for calls whose provider is genuinely unknown; if Smith
|
|
103
|
+
observes a provider but finds only a model-only rate, it raises
|
|
104
|
+
`Smith::PricingConfigurationError` instead of silently undercounting cost.
|
|
105
|
+
|
|
85
106
|
### What Each `Smith.configure` Setting Is For
|
|
86
107
|
|
|
87
108
|
| Setting | What it controls | Typical first use |
|
|
@@ -94,11 +115,11 @@ end
|
|
|
94
115
|
| `trace_transitions` | Emit transition traces | Usually leave on |
|
|
95
116
|
| `trace_tool_calls` | Emit tool call traces | Usually leave on |
|
|
96
117
|
| `trace_token_usage` | Emit usage traces | Useful for budget visibility |
|
|
97
|
-
| `
|
|
118
|
+
| `trace_provider_calls` | Emit one timed `:provider_call` trace per provider attempt | Usually leave on |
|
|
119
|
+
| `trace_cost` | Emit one `:cost` trace per completed agent invocation | Emits only once pricing is configured |
|
|
98
120
|
| `trace_fields` | Allowlist structural trace fields | Use when you want tighter trace output |
|
|
99
121
|
| `trace_content` | Whether content appears in traces | Leave `false` first |
|
|
100
|
-
| `
|
|
101
|
-
| `trace_tenant_isolation` | Trace multi-tenant isolation flag | Enable in multi-tenant systems |
|
|
122
|
+
| `trace_attribution` | Merge ambient execution attribution into trace payloads | Usually leave on |
|
|
102
123
|
| `pricing` | Best-known model-call cost catalog | Add once you care about `total_cost` |
|
|
103
124
|
| `logger` | Smith's runtime logger | Usually the first setting to add |
|
|
104
125
|
| `persistence_adapter` | Adapter for durable workflow state | `:redis`, `:rails_cache`, `:active_record`, `:memory`, or a custom object |
|