smith-agents 0.7.0 → 0.8.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 +226 -0
- data/README.md +71 -1
- data/docs/CONFIGURATION.md +22 -1
- 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 +25 -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 +104 -0
- data/lib/smith/agent/lifecycle.rb +7 -276
- data/lib/smith/agent/model_reference.rb +76 -0
- data/lib/smith/agent/provider_attempt.rb +28 -0
- data/lib/smith/agent/provider_candidate_sequence.rb +55 -0
- data/lib/smith/agent/provider_completion.rb +130 -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 +43 -0
- data/lib/smith/agent/usage_tracking.rb +113 -0
- data/lib/smith/agent.rb +29 -197
- 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/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/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 +71 -12
- data/lib/smith/tool_execution_not_admitted.rb +7 -0
- data/lib/smith/tool_failure_notification_failed.rb +94 -0
- 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 +3 -1
- data/lib/smith/workflow/deadline_enforcement.rb +20 -2
- data/lib/smith/workflow/execution.rb +2 -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/guarded_step_execution.rb +39 -0
- data/lib/smith/workflow/parallel.rb +14 -3
- data/lib/smith/workflow/persistence.rb +22 -19
- data/lib/smith/workflow/retry_execution.rb +5 -1
- data/lib/smith/workflow/thread_context_snapshot.rb +5 -2
- data/lib/smith/workflow/transition.rb +9 -4
- data/lib/smith/workflow/usage_entry.rb +12 -2
- data/lib/smith/workflow.rb +17 -130
- data/lib/smith.rb +3 -0
- metadata +78 -11
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1fe948e343baa2573448f7a95a964729e96a71b0b2e6ef16226694400ba0fb97
|
|
4
|
+
data.tar.gz: 8f3d35e4ee6998ca41a4be0e19dcce0b5e843b44c0f28167aff3d5326d1be573
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 85bc7336ef0cb273979c4c7df1a4c0c724eb74cb34c4b49c1b23768e9454c700a1f33827ac9a1b33ff9680efd55b302b282db0fc553c5e20c52b4d13970044fa
|
|
7
|
+
data.tar.gz: afe1281c6ef7967292c1c43b7370ca5ab3795abcc98722aa8e10c03266347dcd2411d9c44878f1c0d4aa41df5f88ffd636d46084ff996f2008f60baa650ce5fc
|
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,232 @@ 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.8.0] - 2026-07-25
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- Add opt-in graceful agent tool-budget exhaustion with a finite
|
|
12
|
+
`tool_calls` budget and `tool_budget_exhaustion :complete`. Smith consumes
|
|
13
|
+
model-requested calls atomically, rejects oversized batches without partial
|
|
14
|
+
execution, pairs every rejected call id, and performs one tool-disabled final
|
|
15
|
+
completion.
|
|
16
|
+
- Add one-use call admissions so an admitted Smith tool is not double charged,
|
|
17
|
+
while exact tool identity prevents nested tools from stealing an admission.
|
|
18
|
+
- Aggregate trustworthy token usage across every assistant response in a
|
|
19
|
+
successful RubyLLM tool loop, record one durable usage entry per provider
|
|
20
|
+
response, and preserve completed-prefix usage when a later provider round
|
|
21
|
+
fails.
|
|
22
|
+
- Expose immutable normalized tool-call metadata and one execution-scoped,
|
|
23
|
+
host-seedable invocation sequence across Smith-managed chats, branches,
|
|
24
|
+
threads, and fibers without introducing host persistence semantics.
|
|
25
|
+
- Add immutable exact tool-call budgets with aggregate and per-tool limits for
|
|
26
|
+
host-controlled execution scopes.
|
|
27
|
+
- Add a host-neutral whole-batch admission callback and per-invocation dispatch
|
|
28
|
+
failure callback. Smith snapshots immutable requests, admits the complete
|
|
29
|
+
Smith-managed subset of the provider batch before dispatch, and never reports
|
|
30
|
+
failure for a batch whose host admission callback raised.
|
|
31
|
+
- Add a Claude 5+ family inference rule: Fable, Mythos, Opus, Sonnet, and Haiku
|
|
32
|
+
ids with major version 5 or higher resolve to adaptive thinking, no
|
|
33
|
+
temperature accepted, and native tools-with-thinking, so an agent-declared
|
|
34
|
+
`temperature` is stripped instead of being sent to a provider that rejects
|
|
35
|
+
it. Dotted 4.x ids (for example `claude-haiku-4-5`) keep matching the 4.x
|
|
36
|
+
budget-tokens rule.
|
|
37
|
+
- Add new public error classes hosts may rescue or allowlist:
|
|
38
|
+
`Smith::PricingConfigurationError`, `Smith::ProviderPermanentFailure`
|
|
39
|
+
(carries `provider`, `model_id`, and `source_error_class`),
|
|
40
|
+
`Smith::Models::AmbiguousProfileError`, `Smith::Models::CollisionError`,
|
|
41
|
+
`Smith::ToolDispatchRejected`, `Smith::ToolOutcomeUncertain`,
|
|
42
|
+
`Smith::ToolExecutionNotAdmitted`, `Smith::ToolFailureNotificationFailed`,
|
|
43
|
+
`Smith::BoundedCompletionError`, and `Smith::PersistedFailureInvalid`.
|
|
44
|
+
Terminal tool-evidence families surface through
|
|
45
|
+
`Smith::Errors.retry_forbidden?` / `retry_forbidden_class?` and must never be
|
|
46
|
+
retried by host jobs.
|
|
47
|
+
- `Smith::Doctor` reports a dedicated failing `models.ambiguity` check when a
|
|
48
|
+
registered agent declares an unqualified model id registered under multiple
|
|
49
|
+
providers, instead of aborting the run with `AmbiguousProfileError`, and now
|
|
50
|
+
validates the assigned pricing catalog (legacy model-only keys, malformed
|
|
51
|
+
entries, and colliding keys fail the `config.pricing` check).
|
|
52
|
+
- Capture the causal failure classification behind an uncertain tool outcome
|
|
53
|
+
into the durable failure record: a persisted `Smith::ToolOutcomeUncertain`
|
|
54
|
+
carries bounded `error_cause_class`, `error_cause_family`, and
|
|
55
|
+
`error_cause_message` from its cause so a restored host can distinguish a
|
|
56
|
+
deadline, cancellation, or defect. The three attributes travel as one unit;
|
|
57
|
+
legacy records omit all three, and a partial or unknown-family set fails
|
|
58
|
+
closed at state admission.
|
|
59
|
+
|
|
60
|
+
### Changed
|
|
61
|
+
|
|
62
|
+
- Bump `Smith::EXECUTION_SEMANTICS_VERSION` from `3` directly to `5` for exact
|
|
63
|
+
per-tool budgets, shared composite admission, bounded graceful completion,
|
|
64
|
+
and cumulative tool-loop usage. The value `4` was consumed transiently during
|
|
65
|
+
development of this slice and is intentionally skipped so a composite plan
|
|
66
|
+
persisted against an interim build can never read as compatible with the
|
|
67
|
+
released semantics; consumers compare by exact equality, so every pre-`5`
|
|
68
|
+
plan is invalidated either way.
|
|
69
|
+
- Key model profiles, fallback candidates, usage telemetry, and pricing by exact
|
|
70
|
+
provider/model identity. Provider-qualified pricing never falls back to a
|
|
71
|
+
legacy model-only rate, and fallback declarations must name their provider.
|
|
72
|
+
- Let observation masking preserve the exact immutable seed-message prefix
|
|
73
|
+
while bounding only later workflow observations. The prefix length is
|
|
74
|
+
persisted and validated across restart; legacy state defaults to zero.
|
|
75
|
+
- Require the exactly qualified RubyLLM `1.16.0`; graceful completion is
|
|
76
|
+
isolated behind that verified chat interface and fails closed on incompatible
|
|
77
|
+
hooks. Future RubyLLM versions require an explicit compatibility pass.
|
|
78
|
+
- Enforce the pricing-catalog key policy at admission time instead of inside
|
|
79
|
+
accounting: `Smith::Pricing.validate_catalog!` rejects legacy model-only
|
|
80
|
+
keys, unrecognized key shapes, malformed entries, and post-normalization
|
|
81
|
+
collisions, and the doctor runs it against the assigned catalog.
|
|
82
|
+
`Pricing.compute_cost` itself never raises: a provider-qualified lookup reads
|
|
83
|
+
only provider-qualified entries and returns nil (visibly unpriced) when only
|
|
84
|
+
a legacy rate exists, so an in-flight accounting path can never mask a
|
|
85
|
+
provider error with a pricing configuration error.
|
|
86
|
+
- Registering a model profile whose capabilities differ from the profile
|
|
87
|
+
already registered for the same provider/model identity now raises
|
|
88
|
+
`Smith::Models::CollisionError`; earlier releases silently replaced the
|
|
89
|
+
profile on Rails reload. Re-registering a value-identical profile stays
|
|
90
|
+
idempotent (see Migration notes).
|
|
91
|
+
- `Smith::Models::Normalizer` tool/endpoint routing no longer requires active
|
|
92
|
+
thinking: endpoint compatibility is evaluated on every chat construction, the
|
|
93
|
+
endpoint preserving the strictly larger compatible tool subset wins (ties
|
|
94
|
+
keep the current endpoint; a single forced tool that only one endpoint can
|
|
95
|
+
carry takes precedence), and each dropped tool records a `:tool_dropped`
|
|
96
|
+
decision. See docs/TOOLS_AND_GUARDRAILS.md.
|
|
97
|
+
- Agent tool evidence is tracked per transition: once any tool starts inside a
|
|
98
|
+
transition, a later provider failure in that transition (including one from a
|
|
99
|
+
concurrently executing parallel branch that never ran a tool itself) refuses
|
|
100
|
+
model fallback and surfaces `Smith::ToolOutcomeUncertain`. This is
|
|
101
|
+
deliberately conservative and fail-closed; branch-scoped evidence that
|
|
102
|
+
restores fallback for provably tool-free sibling branches is planned, and
|
|
103
|
+
child-workflow tool evidence does not yet mark the parent transition.
|
|
104
|
+
- `from_state` failure-record problems raise `Smith::PersistedFailureInvalid`
|
|
105
|
+
while other persisted-shape problems keep raising
|
|
106
|
+
`Smith::SerializationError`; both descend from `Smith::Error`, and hosts
|
|
107
|
+
rescuing `SerializationError` around restore must handle both.
|
|
108
|
+
- Restored failure records no longer resolve or construct arbitrary error
|
|
109
|
+
classes named by persisted data: host-defined subclasses reconstruct as their
|
|
110
|
+
Smith family parent (family `"other"` restores as `RuntimeError`), so
|
|
111
|
+
exact-class matching against restored `last_error` values must move to family
|
|
112
|
+
or `is_a?` checks.
|
|
113
|
+
- `Smith::Agent.fallback_models` now returns a frozen array of
|
|
114
|
+
`Smith::Agent::ModelReference` values (previously raw strings), block-form
|
|
115
|
+
`model {}` declarations must return a provider-qualified reference (a bare
|
|
116
|
+
string raises `Smith::AgentError`), permanent provider failures raise
|
|
117
|
+
`Smith::ProviderPermanentFailure` instead of a retryable `AgentError`,
|
|
118
|
+
non-provider `StandardError`s raised inside a provider attempt now propagate
|
|
119
|
+
raw instead of being wrapped, and `Workflow::UsageEntry` is frozen and gained
|
|
120
|
+
a `:provider` member.
|
|
121
|
+
|
|
122
|
+
### Fixed
|
|
123
|
+
|
|
124
|
+
- Preserve the provider and model actually selected by RubyLLM in completion
|
|
125
|
+
usage, keep inherited fallback configuration immutable, and treat model-level
|
|
126
|
+
permission failures as model scoped so an eligible fallback on the same
|
|
127
|
+
provider remains available. Only account-wide authentication and payment
|
|
128
|
+
failures suppress later candidates from that provider.
|
|
129
|
+
- Prevent unavailable provider tool calls from creating an unbounded correction
|
|
130
|
+
loop by consuming the model-request allowance before dispatch.
|
|
131
|
+
- Prevent fallback-model restart after a Smith tool begins executing, because a
|
|
132
|
+
fresh chat would discard tool evidence and violate the provider protocol.
|
|
133
|
+
- Prevent transition retry, including broad explicit retry classes, when any
|
|
134
|
+
later provider, completion hook, output validation, or guardrail failure occurs
|
|
135
|
+
after a bound tool dispatch makes the external outcome uncertain.
|
|
136
|
+
- Reserve provider batches atomically against both the agent allowance and the
|
|
137
|
+
effective workflow tool budget, then reconcile unexecuted calls.
|
|
138
|
+
- Execute bounded tool loops iteratively so finite call allowances do not grow
|
|
139
|
+
Ruby stack depth, reject concurrent or callback reentry on one bounded chat,
|
|
140
|
+
and retain RubyLLM's native forced-tool-choice reset.
|
|
141
|
+
- Validate `Smith::Tool#perform` keyword arguments before budget charge and tool
|
|
142
|
+
execution, reject non-Smith bindings from the opt-in bounded policy, and fail
|
|
143
|
+
closed when raw provider params try to override reserved tool controls.
|
|
144
|
+
- Keep provider-facing tool controls and RubyLLM instrumentation aligned, and
|
|
145
|
+
restore tools, call preferences, and concurrency after success or failure.
|
|
146
|
+
- Permit bounded providers to return multiple calls in one response while more
|
|
147
|
+
than one call remains. Smith retains atomic batch reservation, deterministic
|
|
148
|
+
sequential execution, and fail-closed oversized-batch handling.
|
|
149
|
+
- Propagate one enclosing tool-call allowance into same-agent parallel and
|
|
150
|
+
heterogeneous fan-out worker threads so branches cannot multiply a host's
|
|
151
|
+
signed transition budget.
|
|
152
|
+
- Materialize one immutable admitted dispatch batch before host admission, so
|
|
153
|
+
later mutation of the provider collection, call identity, or arguments cannot
|
|
154
|
+
alter which Smith tools execute or the values they receive. The host callback
|
|
155
|
+
receives immutable source-call evidence while RubyLLM dispatches a separately
|
|
156
|
+
owned admitted call, and an atomic dispatch claim prevents callback re-entry
|
|
157
|
+
from executing the same admission twice. Continue to reject registered-tool
|
|
158
|
+
replacement and classify only guaranteed pre-`perform` failures as
|
|
159
|
+
`ToolDispatchRejected`.
|
|
160
|
+
- Capture provider batches with native `Hash` operations, bound the complete
|
|
161
|
+
provider batch to 100 calls and 1 MiB of UTF-8 call metadata, reject malformed
|
|
162
|
+
call protocol before host admission, and stop aggregate metadata capture as
|
|
163
|
+
soon as the bound is crossed.
|
|
164
|
+
- Bound immutable argument copying before child allocation, account for expanded
|
|
165
|
+
serialized occurrences even when a caller reuses a shared object graph,
|
|
166
|
+
admit each mutable container's native size before one shallow copy, verify the
|
|
167
|
+
copy retained that size before child allocation, reject hostile container
|
|
168
|
+
overrides and malformed UTF-8, and keep traversal iterative with linear time
|
|
169
|
+
and bounded space.
|
|
170
|
+
- Require one exact execution authority for every managed Smith tool dispatch.
|
|
171
|
+
Direct tool calls from provider callbacks and nested Smith tool calls now fail
|
|
172
|
+
closed; nested execution requires a future separately admitted primitive.
|
|
173
|
+
- Give terminal tool-evidence failures precedence during parallel arbitration
|
|
174
|
+
and centralize their non-retryable classification across declaration and
|
|
175
|
+
execution. Persist and restore terminal notification failures without resolving
|
|
176
|
+
or constructing arbitrary classes named by persisted data. Bound and normalize
|
|
177
|
+
failure diagnostics, capture failure class identity with native Ruby operations,
|
|
178
|
+
and reject inconsistent family/retry metadata or malformed typed failure details
|
|
179
|
+
at state admission.
|
|
180
|
+
- Cover the split execution files with direct-load contracts so each new file
|
|
181
|
+
loads standalone; the released artifact's file manifest is verified against
|
|
182
|
+
`lib/` at release time as part of the release procedure.
|
|
183
|
+
- Fail closed with a typed `Smith::AgentError` when an agent has no executable
|
|
184
|
+
model candidate (empty model chain), instead of leaking the exhausted
|
|
185
|
+
candidate sequence into nil destructuring from optimizer and orchestrator
|
|
186
|
+
paths.
|
|
187
|
+
- Attribute an account-wide authentication or payment failure to the attempted
|
|
188
|
+
reference's declared provider when the chat is unobservable, so a dead
|
|
189
|
+
provider account is not billed a second same-provider attempt.
|
|
190
|
+
- Deduplicate model candidates by physical identity, so a provider-unqualified
|
|
191
|
+
primary and a provider-qualified fallback naming the same model cannot
|
|
192
|
+
produce a duplicate attempt, and `ModelReference.coerce` now parses the
|
|
193
|
+
`"provider/model"` string form that `#to_s` emits (the first slash splits, so
|
|
194
|
+
slashed model ids round-trip).
|
|
195
|
+
- Keep captured and restored failure records symmetric for blank messages:
|
|
196
|
+
capture substitutes a deterministic placeholder for a blank error message, so
|
|
197
|
+
a workflow state whose last failed step had an empty message restores instead
|
|
198
|
+
of raising `Smith::PersistedFailureInvalid` and poisoning crash/resume.
|
|
199
|
+
- Restore legacy failure records whose `error_message` exceeds the 64 KiB
|
|
200
|
+
diagnostic bound by truncating exactly as capture truncates, instead of
|
|
201
|
+
rejecting the persisted workflow state for message length alone; non-text and
|
|
202
|
+
invalid-UTF-8 persisted values still fail closed.
|
|
203
|
+
- Scope an agent `tool_calls` budget under a legacy Hash tool-call allowance to
|
|
204
|
+
a typed rejection at both call sites instead of an untyped `NoMethodError`
|
|
205
|
+
mid-transition.
|
|
206
|
+
- Propagate a settled or exhausted batch reservation's refusal through
|
|
207
|
+
`CallAdmission#claim`, so a post-settlement execution can never run against
|
|
208
|
+
an already reconciled ledger.
|
|
209
|
+
- Keep a queued process-fatal sibling error ahead of a
|
|
210
|
+
`ToolFailureNotificationFailed` raised while notifying unsettled batch
|
|
211
|
+
failures, so notification problems cannot mask fatal arbitration outcomes.
|
|
212
|
+
|
|
213
|
+
### Migration notes
|
|
214
|
+
|
|
215
|
+
- Pricing catalogs must move to provider-qualified keys (`%w[provider model]`
|
|
216
|
+
arrays or `"provider/model"` strings). Legacy model-only keys still price
|
|
217
|
+
provider-unqualified usage for compatibility, but they fail
|
|
218
|
+
`Smith::Pricing.validate_catalog!` (now run by the doctor), and
|
|
219
|
+
provider-qualified usage never reads them: such usage records nil cost until
|
|
220
|
+
the catalog is qualified.
|
|
221
|
+
- `fallback_models` entries must name their provider (`"provider/model"`, a
|
|
222
|
+
Hash, or a `ModelReference`); bare model ids fail closed at class definition.
|
|
223
|
+
- Hosts registering `Smith::Models` profiles from reloadable code must move
|
|
224
|
+
registration to boot-once initializers or restart after editing a profile;
|
|
225
|
+
value-different re-registration now raises `Smith::Models::CollisionError`.
|
|
226
|
+
- The gem now requires exactly `ruby_llm 1.16.0` (previously `>= 1.15,
|
|
227
|
+
< 1.17`); hosts on 1.15.x must upgrade together with this release.
|
|
228
|
+
- Hosts that rescued `Smith::AgentError` for permanent provider failures should
|
|
229
|
+
rescue `Smith::ProviderPermanentFailure`, and restore-time rescues of
|
|
230
|
+
`Smith::SerializationError` must also handle
|
|
231
|
+
`Smith::PersistedFailureInvalid`.
|
|
232
|
+
|
|
7
233
|
## [0.7.0] - 2026-07-21
|
|
8
234
|
|
|
9
235
|
### 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
|
|
@@ -72,7 +87,7 @@ Smith.configure do |config|
|
|
|
72
87
|
config.trace_tenant_isolation = false
|
|
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 |
|
data/docs/PERSISTENCE.md
CHANGED
|
@@ -12,7 +12,7 @@ Example:
|
|
|
12
12
|
class ReviewContext < Smith::Context
|
|
13
13
|
persist :ticket_id, :current_findings, :source_urls
|
|
14
14
|
|
|
15
|
-
session_strategy :observation_masking, window: 6
|
|
15
|
+
session_strategy :observation_masking, window: 6, preserve_seed: true
|
|
16
16
|
|
|
17
17
|
inject_state do |persisted|
|
|
18
18
|
<<~TEXT
|
|
@@ -37,12 +37,22 @@ end
|
|
|
37
37
|
What Smith does for you:
|
|
38
38
|
|
|
39
39
|
- prepares masked session input at step boundaries
|
|
40
|
+
- optionally preserves the complete immutable `seed_messages` prefix, with its
|
|
41
|
+
original roles and order, while bounding only later workflow observations
|
|
40
42
|
- injects a state summary message into that prepared input
|
|
41
43
|
- persists declared workflow context keys
|
|
42
44
|
- persists accepted session history
|
|
43
45
|
- preserves chosen next transitions across persistence
|
|
44
46
|
- supports JSON host round-trips through `to_state` and `.from_state`
|
|
45
47
|
|
|
48
|
+
`preserve_seed: true` is intended for workflows whose seed is durable external
|
|
49
|
+
conversation and whose later accepted outputs are internal stage handoffs. Smith
|
|
50
|
+
persists and validates the exact seed-prefix length, so restart does not infer
|
|
51
|
+
the boundary from message content. The masking pass is linear in session length
|
|
52
|
+
and retains only the seed prefix, injected system messages, and the configured
|
|
53
|
+
observation tail. Legacy state without `seed_message_count` restores with no
|
|
54
|
+
preserved prefix.
|
|
55
|
+
|
|
46
56
|
Example host-controlled persistence:
|
|
47
57
|
|
|
48
58
|
```ruby
|
|
@@ -305,6 +315,22 @@ and JSON serialization hooks. It is bound by object identity to one workflow
|
|
|
305
315
|
instance and by process id to the issuing Ruby process, so a forked child cannot
|
|
306
316
|
consume inherited authority. It is not a lease, cross-process fence, or durable
|
|
307
317
|
attempt receipt.
|
|
318
|
+
|
|
319
|
+
Persisted workflow failures use an explicit reconstruction registry. Smith does
|
|
320
|
+
not resolve arbitrary class names from persisted data or invoke their
|
|
321
|
+
constructors. Known retry-forbidden families retain their non-retryable
|
|
322
|
+
semantics even when the original class is no longer available; unknown classes
|
|
323
|
+
restore as `RuntimeError`. Failure messages and details are normalized into
|
|
324
|
+
bounded JSON-safe evidence before persistence; invalid or oversized details are
|
|
325
|
+
represented by an explicit omission marker. Inconsistent persisted class,
|
|
326
|
+
family, retry-policy, or known typed-detail evidence fails closed during
|
|
327
|
+
`from_state` as `Smith::PersistedFailureInvalid`. Failure capture uses native
|
|
328
|
+
Ruby class and message operations so an exception cannot override diagnostic
|
|
329
|
+
methods to suppress its durable terminal record. Terminal
|
|
330
|
+
`Smith::ToolFailureNotificationFailed` records preserve bounded diagnostic
|
|
331
|
+
details for both the dispatch failure and the failed host notification and
|
|
332
|
+
restore as the same non-retryable failure family.
|
|
333
|
+
|
|
308
334
|
Before consuming the capability, a host may inspect the exact captured agent
|
|
309
335
|
bindings with a block:
|
|
310
336
|
|