smith-agents 0.4.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.
Files changed (115) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +139 -0
  3. data/CODE_OF_CONDUCT.md +128 -0
  4. data/LICENSE +21 -0
  5. data/README.md +226 -0
  6. data/Rakefile +14 -0
  7. data/UPSTREAM_PROPOSAL.md +141 -0
  8. data/docs/CONFIGURATION.md +123 -0
  9. data/docs/PATTERNS.md +492 -0
  10. data/docs/PERSISTENCE.md +169 -0
  11. data/docs/TOOLS_AND_GUARDRAILS.md +140 -0
  12. data/docs/workflow_claim.md +58 -0
  13. data/exe/smith +7 -0
  14. data/lib/generators/smith/install/install_generator.rb +22 -0
  15. data/lib/generators/smith/install/templates/smith.rb.tt +44 -0
  16. data/lib/smith/agent/lifecycle.rb +264 -0
  17. data/lib/smith/agent/registry.rb +128 -0
  18. data/lib/smith/agent.rb +259 -0
  19. data/lib/smith/artifacts/file.rb +59 -0
  20. data/lib/smith/artifacts/memory.rb +75 -0
  21. data/lib/smith/artifacts/scoped_store.rb +29 -0
  22. data/lib/smith/artifacts.rb +5 -0
  23. data/lib/smith/budget/ledger.rb +42 -0
  24. data/lib/smith/budget.rb +5 -0
  25. data/lib/smith/cli.rb +82 -0
  26. data/lib/smith/context/observation_masking.rb +19 -0
  27. data/lib/smith/context/session.rb +42 -0
  28. data/lib/smith/context/state_injection.rb +24 -0
  29. data/lib/smith/context.rb +61 -0
  30. data/lib/smith/doctor/check.rb +12 -0
  31. data/lib/smith/doctor/checks/baseline.rb +84 -0
  32. data/lib/smith/doctor/checks/configuration.rb +56 -0
  33. data/lib/smith/doctor/checks/durability.rb +103 -0
  34. data/lib/smith/doctor/checks/live.rb +55 -0
  35. data/lib/smith/doctor/checks/models_registry.rb +66 -0
  36. data/lib/smith/doctor/checks/openai_api_mode.rb +51 -0
  37. data/lib/smith/doctor/checks/persistence.rb +99 -0
  38. data/lib/smith/doctor/checks/persistence_capabilities.rb +60 -0
  39. data/lib/smith/doctor/checks/persistence_registry.rb +82 -0
  40. data/lib/smith/doctor/checks/rails.rb +39 -0
  41. data/lib/smith/doctor/checks/serialization.rb +78 -0
  42. data/lib/smith/doctor/installer.rb +103 -0
  43. data/lib/smith/doctor/printer.rb +62 -0
  44. data/lib/smith/doctor/report.rb +39 -0
  45. data/lib/smith/doctor.rb +53 -0
  46. data/lib/smith/errors.rb +191 -0
  47. data/lib/smith/event.rb +11 -0
  48. data/lib/smith/events/.keep +0 -0
  49. data/lib/smith/events/bus.rb +60 -0
  50. data/lib/smith/events/step_completed.rb +11 -0
  51. data/lib/smith/events/subscription.rb +24 -0
  52. data/lib/smith/events.rb +5 -0
  53. data/lib/smith/guardrails/runner.rb +44 -0
  54. data/lib/smith/guardrails/url_verifier.rb +7 -0
  55. data/lib/smith/guardrails.rb +35 -0
  56. data/lib/smith/models/inference.rb +199 -0
  57. data/lib/smith/models/normalizer.rb +186 -0
  58. data/lib/smith/models/profile.rb +39 -0
  59. data/lib/smith/models.rb +132 -0
  60. data/lib/smith/persistence_adapters/active_record_store.rb +99 -0
  61. data/lib/smith/persistence_adapters/cache_store.rb +79 -0
  62. data/lib/smith/persistence_adapters/memory.rb +105 -0
  63. data/lib/smith/persistence_adapters/rails_cache.rb +20 -0
  64. data/lib/smith/persistence_adapters/redis_store.rb +136 -0
  65. data/lib/smith/persistence_adapters/retry.rb +42 -0
  66. data/lib/smith/persistence_adapters.rb +112 -0
  67. data/lib/smith/pricing.rb +65 -0
  68. data/lib/smith/providers/openai/responses.rb +315 -0
  69. data/lib/smith/providers/openai/routing.rb +67 -0
  70. data/lib/smith/providers/openai/tools_extensions.rb +106 -0
  71. data/lib/smith/railtie.rb +9 -0
  72. data/lib/smith/tasks/doctor.rake +38 -0
  73. data/lib/smith/tool/budget_enforcement.rb +33 -0
  74. data/lib/smith/tool/capability_builder.rb +18 -0
  75. data/lib/smith/tool/capture.rb +22 -0
  76. data/lib/smith/tool/compatibility.rb +72 -0
  77. data/lib/smith/tool/policy.rb +40 -0
  78. data/lib/smith/tool.rb +171 -0
  79. data/lib/smith/tools/think.rb +25 -0
  80. data/lib/smith/tools/url_fetcher.rb +16 -0
  81. data/lib/smith/tools/web_search.rb +17 -0
  82. data/lib/smith/tools.rb +5 -0
  83. data/lib/smith/trace/logger.rb +46 -0
  84. data/lib/smith/trace/memory.rb +53 -0
  85. data/lib/smith/trace/open_telemetry.rb +57 -0
  86. data/lib/smith/trace.rb +89 -0
  87. data/lib/smith/types.rb +16 -0
  88. data/lib/smith/version.rb +5 -0
  89. data/lib/smith/workflow/artifact_integration.rb +41 -0
  90. data/lib/smith/workflow/budget_integration.rb +105 -0
  91. data/lib/smith/workflow/claim.rb +118 -0
  92. data/lib/smith/workflow/data_volume_policy.rb +36 -0
  93. data/lib/smith/workflow/deadline_enforcement.rb +100 -0
  94. data/lib/smith/workflow/deterministic_execution.rb +53 -0
  95. data/lib/smith/workflow/deterministic_step.rb +57 -0
  96. data/lib/smith/workflow/dsl.rb +223 -0
  97. data/lib/smith/workflow/durability.rb +369 -0
  98. data/lib/smith/workflow/evaluator_optimizer.rb +220 -0
  99. data/lib/smith/workflow/event_integration.rb +24 -0
  100. data/lib/smith/workflow/execution.rb +127 -0
  101. data/lib/smith/workflow/execution_frame.rb +166 -0
  102. data/lib/smith/workflow/guardrail_integration.rb +40 -0
  103. data/lib/smith/workflow/nested_execution.rb +69 -0
  104. data/lib/smith/workflow/orchestrator_worker.rb +145 -0
  105. data/lib/smith/workflow/parallel.rb +50 -0
  106. data/lib/smith/workflow/parallel_execution.rb +75 -0
  107. data/lib/smith/workflow/persistence.rb +358 -0
  108. data/lib/smith/workflow/pipeline.rb +117 -0
  109. data/lib/smith/workflow/router.rb +53 -0
  110. data/lib/smith/workflow/transition.rb +208 -0
  111. data/lib/smith/workflow.rb +555 -0
  112. data/lib/smith.rb +254 -0
  113. data/script/profile_tool_results.rb +94 -0
  114. data/sig/smith.rbs +4 -0
  115. metadata +258 -0
@@ -0,0 +1,141 @@
1
+ # Upstream proposal: Capability profiles + before_complete hook for RubyLLM
2
+
3
+ ## Motivation
4
+
5
+ Smith (a workflow-first multi-agent orchestration library built on RubyLLM) currently maintains a "shadow registry" of model capabilities — pattern-based provider rules describing how each provider shapes its API payload (Anthropic Opus 4.7+ uses adaptive thinking; OpenAI gpt-5 family needs `/v1/responses` for tools+thinking; Gemini 2.5+ accepts `thinking_budget`; etc.). When the underlying RubyLLM client doesn't know these distinctions, Smith's normalizer rewrites the chat object's `@temperature`, `@thinking`, and `@params` ivars before the request leaves.
6
+
7
+ This works but requires Smith to:
8
+
9
+ 1. Maintain a parallel capability registry (`Smith::Models::Inference`)
10
+ 2. Mutate RubyLLM-owned chat ivars directly (`@temperature`, `@thinking`) because RubyLLM 1.15 has no `Chat#without_thinking` / `#without_temperature` public API
11
+ 3. Vendor PR #770's `/v1/responses` adapter ahead of upstream merge so gpt-5 family can use tools + reasoning_effort together
12
+
13
+ A cleaner design lives upstream in RubyLLM. This proposal describes three additive changes that would let Smith retire ~400 lines of vendored code and ~6 monkey-patches.
14
+
15
+ ## Proposed RubyLLM API
16
+
17
+ ### 1. `RubyLLM::Chat#without_thinking` and `#without_temperature`
18
+
19
+ Smith currently does `chat.instance_variable_set(:@thinking, nil)` and `chat.instance_variable_set(:@temperature, nil)` because there's no public way to clear these. `with_thinking` requires at least one of `effort:` or `budget:`. Add the no-arg clearers:
20
+
21
+ ```ruby
22
+ module RubyLLM
23
+ class Chat
24
+ def without_thinking
25
+ @thinking = nil
26
+ self
27
+ end
28
+
29
+ def without_temperature
30
+ @temperature = nil
31
+ self
32
+ end
33
+ end
34
+ end
35
+ ```
36
+
37
+ Small additive change. Smith retires its only remaining `instance_variable_set` calls.
38
+
39
+ ### 2. `RubyLLM::Capabilities::Profile` + `Model::Info#capabilities`
40
+
41
+ Add a structured capability profile to model info:
42
+
43
+ ```ruby
44
+ module RubyLLM
45
+ module Capabilities
46
+ Profile = Data.define(
47
+ :thinking_shape, # :budget_tokens | :reasoning_effort | :adaptive | nil
48
+ :accepts_temperature,
49
+ :tools_with_thinking_native,
50
+ :tools_with_thinking_route # :responses | nil for OpenAI
51
+ )
52
+
53
+ # Public registration API. Idempotent.
54
+ def self.register(model_id, profile)
55
+ registry[model_id.to_s] = profile
56
+ end
57
+
58
+ def self.find(model_id)
59
+ registry[model_id.to_s]
60
+ end
61
+
62
+ def self.registry
63
+ @registry ||= {}
64
+ end
65
+ end
66
+
67
+ class Model::Info
68
+ attr_reader :capabilities # RubyLLM::Capabilities::Profile?
69
+ end
70
+ end
71
+ ```
72
+
73
+ RubyLLM's `models.json` could ship default capability profiles for the models it bundles. Smith would migrate its `Smith::Models::Inference` defaults upstream as a `Capabilities.default_rules` table.
74
+
75
+ ### 3. `RubyLLM::Provider.before_complete` hook
76
+
77
+ Add an extension point for per-request shaping:
78
+
79
+ ```ruby
80
+ module RubyLLM
81
+ class Provider
82
+ # Hosts register normalizers that run AFTER chat construction but
83
+ # BEFORE render_payload. The hook receives the chat and the
84
+ # capabilities profile of the resolved model.
85
+ def self.before_complete(&block)
86
+ normalizers << block
87
+ end
88
+
89
+ def self.normalizers
90
+ @normalizers ||= []
91
+ end
92
+
93
+ # Existing complete(...) signature unchanged. Internally invokes
94
+ # the registered normalizers before render_payload.
95
+ end
96
+ end
97
+ ```
98
+
99
+ ## What Smith looks like after upstream lands
100
+
101
+ ```ruby
102
+ # lib/smith.rb (post-upstream)
103
+ # Register Smith's library-shipped pattern rules into RubyLLM's catalog
104
+ Smith::Models::Inference.rules.each do |rule|
105
+ RubyLLM::Capabilities.register_rule(
106
+ provider: rule.provider,
107
+ matcher: rule.matcher,
108
+ profile: rule.to_profile("anyone").to_h.except(:model_id)
109
+ )
110
+ end
111
+
112
+ # Register Smith's normalizer as a public RubyLLM hook
113
+ RubyLLM::Provider.before_complete do |chat, profile|
114
+ Smith::Models::Normalizer.apply!(chat, profile: profile) if profile
115
+ end
116
+ ```
117
+
118
+ Smith's `Models` registry, the `Smith::Agent.chat()` override, and the `lib/smith/providers/openai/routing.rb` vendor patch all retire. What remains in Smith: the capability defaults table and the normalizer's translation logic — still Smith-owned (orchestration concerns), just consumed through a public RubyLLM hook.
119
+
120
+ ## Retirement checklist
121
+
122
+ Once the upstream API ships and Smith adopts it, the following files retire:
123
+
124
+ - `lib/smith/models.rb` — becomes a thin wrapper around `RubyLLM::Capabilities`
125
+ - `lib/smith/models/profile.rb` — replaced by `RubyLLM::Capabilities::Profile`
126
+ - `lib/smith/agent.rb#chat` override — replaced by `RubyLLM::Provider.before_complete` hook
127
+ - `lib/smith/providers/openai/routing.rb` — replaced when PR #770 merges (independent track)
128
+ - `lib/smith/providers/openai/responses.rb` — same
129
+ - `lib/smith/providers/openai/tools_extensions.rb` — same
130
+
131
+ What stays Smith-owned (orchestration concerns, not provider-API concerns):
132
+
133
+ - `lib/smith/models/inference.rb` — pattern rules table; registers itself into RubyLLM via `Capabilities.register_rule`
134
+ - `lib/smith/models/normalizer.rb` — translation logic; registered via `Provider.before_complete`
135
+ - `lib/smith/tool/compatibility.rb` — tool-side compatibility checks
136
+ - The agent / workflow / tool DSLs
137
+
138
+ ## Tracking
139
+
140
+ - RubyLLM PR #770 (OpenAI `/v1/responses` support) is the related upstream track. Smith's vendored `Smith::Providers::OpenAI::Responses` retires when #770 merges.
141
+ - This proposal (capability profiles + before_complete) is a separate, additive RubyLLM RFC. Once accepted, Smith files a migration PR to consume it.
@@ -0,0 +1,123 @@
1
+ # Configuration
2
+
3
+ There are three different configuration scopes.
4
+
5
+ ### 1. Global runtime configuration: `Smith.configure`
6
+
7
+ Use this for shared runtime services:
8
+
9
+ - artifact backend
10
+ - tracing
11
+ - pricing catalog
12
+ - logger
13
+
14
+ ### 2. Agent configuration: `Smith::Agent`
15
+
16
+ Use agent classes for invocation behavior:
17
+
18
+ - `model`
19
+ - `tools`
20
+ - `instructions`
21
+ - `temperature`
22
+ - `thinking`
23
+ - `budget`
24
+ - `guardrails`
25
+ - `output_schema`
26
+ - `data_volume`
27
+ - `fallback_models`
28
+ - `register_as`
29
+
30
+ ### 3. Workflow configuration: `Smith::Workflow`
31
+
32
+ Use workflow classes for orchestration behavior:
33
+
34
+ - `initial_state`
35
+ - `state`
36
+ - `transition`
37
+ - `pipeline`
38
+ - `budget`
39
+ - `max_transitions`
40
+ - `guardrails`
41
+ - `context_manager`
42
+
43
+ ### If You Are Unsure Where Something Goes
44
+
45
+ - "Which model should this agent use?" -> agent class
46
+ - "How do I store artifacts or emit traces?" -> `Smith.configure`
47
+ - "What happens after this step succeeds or fails?" -> workflow class
48
+ - "How many tokens/cost/tool calls can this one invocation use?" -> agent budget
49
+ - "How much total budget can the whole workflow consume?" -> workflow budget
50
+ - "Which provider credentials should the app use?" -> RubyLLM, not Smith
51
+
52
+ ### Full `Smith.configure` Example
53
+
54
+ ```ruby
55
+ Smith.configure do |config|
56
+ config.artifact_store = Smith::Artifacts::Memory.new
57
+ config.artifact_retention = 3600
58
+ config.artifact_encryption = :none
59
+ config.artifact_tenant_isolation = false
60
+
61
+ config.trace_adapter = Smith::Trace::Logger
62
+ config.trace_transitions = true
63
+ config.trace_tool_calls = true
64
+ config.trace_token_usage = true
65
+ config.trace_cost = true
66
+ config.trace_fields = {
67
+ transition: %i[transition from to],
68
+ tool_call: %i[tool duration]
69
+ }
70
+ config.trace_content = false
71
+ config.trace_retention = 86_400
72
+ config.trace_tenant_isolation = false
73
+
74
+ config.pricing = {
75
+ "gpt-4.1-nano" => {
76
+ input_cost_per_token: 0.0000001,
77
+ output_cost_per_token: 0.0000004
78
+ }
79
+ }
80
+
81
+ config.logger = Logger.new($stdout)
82
+ end
83
+ ```
84
+
85
+ ### What Each `Smith.configure` Setting Is For
86
+
87
+ | Setting | What it controls | Typical first use |
88
+ | --- | --- | --- |
89
+ | `artifact_store` | Where large handoff payloads are stored | Start with `Smith::Artifacts::Memory.new` |
90
+ | `artifact_retention` | Default retention window for artifact expiry checks | Set once you have a cleanup policy |
91
+ | `artifact_encryption` | Metadata-level encryption policy flag | Leave at default until you wire a real backend |
92
+ | `artifact_tenant_isolation` | Require namespaced artifact writes | Enable in multi-tenant systems |
93
+ | `trace_adapter` | Where structural traces go | Use `Smith::Trace::Memory` or `Smith::Trace::Logger` first |
94
+ | `trace_transitions` | Emit transition traces | Usually leave on |
95
+ | `trace_tool_calls` | Emit tool call traces | Usually leave on |
96
+ | `trace_token_usage` | Emit usage traces | Useful for budget visibility |
97
+ | `trace_cost` | Emit cost traces | Useful once pricing is configured |
98
+ | `trace_fields` | Allowlist structural trace fields | Use when you want tighter trace output |
99
+ | `trace_content` | Whether content appears in traces | Leave `false` first |
100
+ | `trace_retention` | Trace retention policy hook | Useful when traces leave memory |
101
+ | `trace_tenant_isolation` | Trace multi-tenant isolation flag | Enable in multi-tenant systems |
102
+ | `pricing` | Best-known model-call cost catalog | Add once you care about `total_cost` |
103
+ | `logger` | Smith's runtime logger | Usually the first setting to add |
104
+ | `persistence_adapter` | Adapter for durable workflow state | `:redis`, `:rails_cache`, `:active_record`, `:memory`, or a custom object |
105
+ | `persistence_options` | Per-adapter options (client, namespace, model, columns) | See "Built-In Persistence Adapters" |
106
+ | `persistence_ttl` | Global TTL for persisted state (Integer/Float seconds; nil = no expiry) | Set when long-tail abandoned workflows accumulate in storage |
107
+ | `persistence_retry_policy` | Exponential-backoff policy for transient adapter I/O failures | Defaults to `{ attempts: 3, base_delay: 0.1, max_delay: 1.0 }` |
108
+ | `test_mode` | Auto-select `:memory` adapter when `persistence_adapter` is nil | Enable in `spec_helper.rb` to skip Redis/cache wiring in tests |
109
+ | `openai_api_mode` | `:auto` routes (gpt-5 family + tools + thinking) via `/v1/responses` using Smith's vendored Responses adapter (sync only; streaming over `/v1/responses` is not yet supported); `:off` drops incompatible tools instead | Leave `:auto` (default) unless you need streaming with the (gpt-5 + tools + thinking) combo, in which case set `:off` for graceful tool-dropping |
110
+ | `trace_normalizer` | Emit `:normalizer_decision` trace events from `Smith::Models::Normalizer` | Useful when debugging cross-provider request shaping |
111
+ | `ruby_llm_model_registry` | `:database` to require an AR-backed RubyLLM model registry; `:bundled` for the JSON fallback | Leave at default unless you've migrated to DB-backed |
112
+
113
+ ### Recommended First Additions
114
+
115
+ Add settings in this order:
116
+
117
+ 1. `config.logger`
118
+ 2. `config.trace_adapter`
119
+ 3. `config.artifact_store`
120
+ 4. `config.pricing`
121
+
122
+ Do not start by configuring every advanced switch at once.
123
+