Skip to content

Provider strategy: the researched proposal WORLD.md asks for (measured coupling, three options, one lean) #22

Open

Description

@minerva-sky

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

Activity

  1. added
    loop:selfForward-thinking improvement loop (WORLD.md-aligned)
    on Aug 21, 2026
  2. minerva-sky commented on Sep 20, 2026

    @minerva-sky
    CollaboratorAuthor

    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-process cancel, with_fallbacks with hooks, per-attempt Tokens/Cost that include retries and fallbacks (what #46 asks agentic to stop dropping), and RubyLLM.workflow/workflow.step instrumentation 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).

  3. minerva-sky commented on Oct 15, 2026

    @minerva-sky
    CollaboratorAuthor

    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.rb rewrite, 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, < 9 with the lockfile on 8.3.0 and touches only CHANGELOG.md, Gemfile.lock, and agentic.gemspec; llm_client.rb is 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    loop:selfForward-thinking improvement loop (WORLD.md-aligned)status:analyzedAnalyzed, awaiting decision

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions