Repository navigation
Provider strategy: the researched proposal WORLD.md asks for (measured coupling, three options, one lean) #22
Description
WORLD.md names provider strategy an open question and says it "needs a researched proposal with a real cost, not a drive-by PR." This is that proposal. Nothing lands from this issue; it exists so the decision can be made on evidence.
Measured coupling (the real cost baseline)
OpenAI specifics touch exactly three files (grep over lib/):
lib/agentic/llm_client.rb— 23OpenAIreferences: client construction, error taxonomy mapping, response envelope parsing. This is ~all of it.lib/agentic/structured_outputs.rb— schema DSL emits OpenAIresponse_formatJSON-schema shape. Load-bearing:TaskPlanner, agent assembly, and task output schemas all depend on structured outputs working.lib/agentic/cli.rb—OPENAI_ACCESS_TOKENenv check.
Everything else (orchestrator, journal, planner logic, capability system) is already provider-blind behind LlmClient/LlmResponse/LlmConfig. The abstraction seam exists; the question is only what plugs into it. Related: #16 (ruby-openai is unbounded in the gemspec, locked at 7.1.0 while consumers resolve 8.3.0).
The options
1. Status quo — ruby-openai (community gem). Cost: zero today. Risk: it's a community shim OpenAI now competes with (official SDK exists); our 7.1.0→8.3.0 constraint gap (#16) shows the maintenance current we're swimming against. Structured outputs remain hand-rolled.
2. Official openai gem (openai/openai-ruby). OpenAI-maintained, ships typed structured-output helpers (OpenAI::BaseModel, ArrayOf, EnumOf, UnionOf — docs). Cost: rewrite llm_client.rb internals + error mapping; our StructuredOutputs::Schema DSL could delegate or stay as-is. Stays single-provider — honest if that's the strategy.
3. ruby_llm (crmne/ruby_llm) as the provider layer. One dependency replacing ruby-openai, providers become config (repo, site). The fact that decides it for me: as of its June 2026 releases it does cross-provider structured outputs (Anthropic and Bedrock added — releases) — which is this gem's one hard provider requirement. Ruby-idiomatic, active, and it is not a framework adoption (anti-goal safe): we'd consume its client + schema layer behind our existing LlmClient seam and keep our own plan/capability/orchestration identity. Cost: same llm_client.rb rewrite as option 2, plus mapping our retry/rate-limit config onto its knobs; dependency count stays at parity (swap, not add).
(Also surveyed and rejected: langchainrb — framework adoption, exactly the anti-goal; llm.rb/instructor-rb — narrower than what we'd replace.)
My lean, held loosely
Option 3, but only when a second provider has a concrete user — WORLD.md's own rule is two concrete uses before one abstraction, and "which second provider earns the abstraction" is still unanswered. Until then, option 2 is the defensible near-term move if #16 forces a ruby-openai major-version decision anyway: if we must rewrite llm_client.rb internals for a constraint bump, rewriting against the SDK that can't be abandoned by a third party is the better spend. Concretely: decide #16 and this together, not separately.
Experiment that would validate before committing
Spike branch, no merge: swap LlmClient internals to the candidate, leave the public complete(messages, output_schema:) contract untouched, run the full suite (~610 examples) + benchmark/. Deliverables: diff size (predicted <400 lines confined to the three files above), suite verdict, latency delta. One spike per candidate, ~an afternoon each — cheap enough to buy the decision with data instead of taste.
Origin: loop:self session 2026-08-21. Per WORLD.md, no provider abstraction lands until this is decided; this issue is L0 decision-support only.
Sources: openai/openai-ruby · OpenAI Ruby API reference · crmne/ruby_llm · ruby_llm releases · rubyllm.com
Activity
minerva-sky commentedon Sep 20, 2026
New evidence, not a re-raise. RubyLLM 2.0.0 shipped 2026-09-18 and changes option 3's ledger on both sides.
Benefits that did not exist when this was written: human tool approvals persisted across processes (
requires_approval,approve/deny), step-driven loop control (ask_later/generate/run_tools/step), cross-processcancel,with_fallbackswith hooks, per-attemptTokens/Costthat include retries and fallbacks (what #46 asks agentic to stop dropping), andRubyLLM.workflow/workflow.stepinstrumentation identifiers. Those overlap #21, #46, and #23's C3.Costs that grew: 17 providers and Rails-owned tables is a large surface for a gem whose charter is "auditable in an afternoon"; the 1.16 to 2.0 upgrade is a phased migration with a copy-mode rollback, so any consumer of agentic still on ruby_llm 1.x would be forced to move; ruby_llm-skills and ruby_llm-agents are now split across the 1.x/2.0 line themselves.
Unchanged: 2.0 has no plan object. Its notes say "Ordinary Ruby handles branching, loops, and concurrency." Delegating the provider layer would not touch the part of agentic that is the product.
Full context in the 2026-09-19 research digest (#48, Finding 1).
minerva-sky commentedon Oct 15, 2026
Drain-loop status, 2026-10-14. The accepted lean (2026-09-10) was conditional: "decide it with #16; if bounding ruby-openai forces an
llm_client.rbrewrite, take option 2 now; option 3 only once a second provider has a concrete user." The condition did not fire. PR #29 bounds ruby-openai at>= 7.1, < 9with the lockfile on 8.3.0 and touches onlyCHANGELOG.md,Gemfile.lock, andagentic.gemspec;llm_client.rbis unchanged and the suite passes on 8.x.So the standing decision is: stay OpenAI-only on ruby-openai, no provider abstraction, revisit when a second provider has a concrete user (the RubyLLM 2.0 evidence above is the ledger for that day). Nothing to implement from this issue now; the one remaining artifact would be a WORLD.md line recording that, which belongs in the six-heading restructure (#47) rather than a competing edit to the same file. Leaving this open as the record until #47 lands.