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 — 23 OpenAI references: client construction, error taxonomy mapping, response envelope parsing. This is ~all of it.
lib/agentic/structured_outputs.rb — schema DSL emits OpenAI response_format JSON-schema shape. Load-bearing: TaskPlanner, agent assembly, and task output schemas all depend on structured outputs working.
lib/agentic/cli.rb — OPENAI_ACCESS_TOKEN env 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
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
openaigem (openai/openai-ruby). OpenAI-maintained, ships typed structured-output helpers (OpenAI::BaseModel,ArrayOf,EnumOf,UnionOf— docs). Cost: rewritellm_client.rbinternals + error mapping; ourStructuredOutputs::SchemaDSL 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 existingLlmClientseam and keep our own plan/capability/orchestration identity. Cost: samellm_client.rbrewrite 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.rbinternals 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
LlmClientinternals to the candidate, leave the publiccomplete(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:selfsession 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