sdk/ruby: add Ruby SDK for on-device semantic search - #463
Conversation
Adds a Ruby SDK over the Moss runtime with parity on index management, search, and metadata filtering, following the two-layer structure of the existing language SDKs. Two gems under sdks/ruby/: - moss-core (sdks/ruby/bindings): FFI bindings over the prebuilt libmoss C SDK, exposing ManageClient, IndexManager, and Session. Attaches libmoss lazily and degrades to a cloud-query fallback / BindingsUnavailableError when it is absent (mirrors the Go SDK). - moss (sdks/ruby/sdk): the high-level Moss::Client with async job polling, local sub-10ms queries with cloud fallback, metadata filtering, custom embeddings, and sessions. Includes unit tests, an env-gated live integration test, runnable samples, a live validation harness, shared RuboCop config, CHANGELOGs, and READMEs. Documents the SDK in AGENTS.md. Closes usemoss#430. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Fm5bzWU8BRe6WtmFMRKify
There was a problem hiding this comment.
Pull request overview
Adds a new Ruby SDK to Moss under sdks/ruby/, following the repository’s two-layer pattern: a high-level moss gem and a low-level moss-core FFI bindings gem over libmoss, plus tests, samples, and a live validation harness.
Changes:
- Introduces
moss-coreRuby FFI bindings exposingManageClient,IndexManager, andSessionover thelibmossC SDK. - Introduces the high-level
mossgem (Moss::Client) with cloud-backed mutations, local-query routing with cloud fallback, metadata filtering, custom embeddings, and sessions. - Adds unit tests, env-gated live integration tests, runnable samples, and a credential-scrubbed validation script; updates repo agent docs.
Reviewed changes
Copilot reviewed 48 out of 48 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| sdks/ruby/sdk/test/test_helper.rb | Test scaffolding (fake clients/managers + simple stubbing helper). |
| sdks/ruby/sdk/test/models_test.rb | Unit tests for Ruby SDK models and CloudQuery parsing/errors. |
| sdks/ruby/sdk/test/integration_test.rb | Env/libmoss-gated end-to-end integration test against live Moss. |
| sdks/ruby/sdk/test/client_test.rb | Unit tests for Moss::Client behavior (validation, polling, routing, mapping). |
| sdks/ruby/sdk/scripts/validate.rb | Live validation harness (loads creds, provisions libmoss, runs full workflow). |
| sdks/ruby/sdk/samples/session_usage_sample.rb | Runnable sessions sample (enterprise-plan feature). |
| sdks/ruby/sdk/samples/metadata_filtering_sample.rb | Runnable metadata filtering sample demonstrating operators. |
| sdks/ruby/sdk/samples/custom_embeddings_sample.rb | Runnable custom-embedding index + embedding query sample. |
| sdks/ruby/sdk/samples/comprehensive_sample.rb | Runnable end-to-end “create/load/query/read/cleanup” sample. |
| sdks/ruby/sdk/samples/.env.template | Sample env template for credentials + libmoss location. |
| sdks/ruby/sdk/README.md | End-user documentation for installation, usage, and features. |
| sdks/ruby/sdk/Rakefile | Test + RuboCop rake tasks for the moss gem. |
| sdks/ruby/sdk/moss.gemspec | Gem metadata and dependencies for the high-level Ruby SDK. |
| sdks/ruby/sdk/LICENSE | License file for the moss gem. |
| sdks/ruby/sdk/lib/moss/version.rb | Ruby SDK version constant. |
| sdks/ruby/sdk/lib/moss/session.rb | High-level session wrapper around Moss::Core::Session. |
| sdks/ruby/sdk/lib/moss/models.rb | High-level Ruby SDK value objects and constants. |
| sdks/ruby/sdk/lib/moss/errors.rb | Ruby SDK error hierarchy (configuration, args, job, HTTP, unsupported query). |
| sdks/ruby/sdk/lib/moss/cloud_query.rb | Cloud query fallback implementation (Net::HTTP POST + response mapping). |
| sdks/ruby/sdk/lib/moss/client.rb | Main Moss::Client implementation (polling, routing, conversions, lifecycle). |
| sdks/ruby/sdk/lib/moss.rb | Gem entrypoint and helpers (native_runtime_available?, libmoss version). |
| sdks/ruby/sdk/Gemfile | Development Gemfile pointing to sibling bindings during in-repo development. |
| sdks/ruby/sdk/CHANGELOG.md | Changelog for the Ruby SDK gem. |
| sdks/ruby/sdk/.rubocop.yml | RuboCop config inheriting from shared sdks/ruby/.rubocop.yml. |
| sdks/ruby/README.md | Top-level Ruby SDK overview describing the two-gem structure. |
| sdks/ruby/bindings/test/test_helper.rb | Test helper for bindings gem. |
| sdks/ruby/bindings/test/library_test.rb | Bindings tests for library resolution and (optional) attach/version smoke test. |
| sdks/ruby/bindings/README.md | Documentation for moss-core bindings usage and libmoss provisioning. |
| sdks/ruby/bindings/Rakefile | Test + RuboCop rake tasks for the moss-core gem. |
| sdks/ruby/bindings/moss-core.gemspec | Gem metadata and dependencies for the bindings gem (ffi). |
| sdks/ruby/bindings/LICENSE | License file for the bindings gem. |
| sdks/ruby/bindings/lib/moss/core/version.rb | Bindings gem version + pinned libmoss ABI version. |
| sdks/ruby/bindings/lib/moss/core/session.rb | FFI-backed Session implementation wrapping MossSession*. |
| sdks/ruby/bindings/lib/moss/core/models.rb | Core-layer value objects and option structs for binding surfaces. |
| sdks/ruby/bindings/lib/moss/core/marshalling.rb | Ruby<->C struct marshalling and result-code checking utilities. |
| sdks/ruby/bindings/lib/moss/core/manage_client.rb | FFI-backed manage client for cloud mutations/reads + session creation. |
| sdks/ruby/bindings/lib/moss/core/library.rb | Lazy libmoss discovery/attach logic with env overrides and graceful fallback. |
| sdks/ruby/bindings/lib/moss/core/index_manager.rb | FFI-backed local runtime (load/unload/query/refresh + loaded-index bookkeeping). |
| sdks/ruby/bindings/lib/moss/core/ffi.rb | Raw FFI ABI mapping to libmoss symbols and C structs. |
| sdks/ruby/bindings/lib/moss/core/errors.rb | Bindings-layer error types (unavailable, closed, native error codes). |
| sdks/ruby/bindings/lib/moss/core/client_handle.rb | Thread-safe owner of native MossClient* handle with finalizer safety net. |
| sdks/ruby/bindings/lib/moss/core.rb | Bindings gem entrypoint exposing availability + libmoss version helpers. |
| sdks/ruby/bindings/Gemfile | Development Gemfile for bindings (includes RuboCop). |
| sdks/ruby/bindings/CHANGELOG.md | Changelog for moss-core. |
| sdks/ruby/bindings/.rubocop.yml | RuboCop config inheriting from shared sdks/ruby/.rubocop.yml. |
| sdks/ruby/.rubocop.yml | Shared RuboCop configuration for both Ruby gems. |
| sdks/ruby/.gitignore | Gitignore for vendored libmoss and Ruby build artifacts. |
| AGENTS.md | Updates agent guidance/docs to include Ruby SDK and related commands. |
Comments suppressed due to low confidence (1)
sdks/ruby/bindings/lib/moss/core/manage_client.rb:145
- SessionOptions uses an instance variable to retain the model_id pointer; in concurrent calls this can be overwritten before the native call executes, leading to a dangling pointer/use-after-free. Build the options struct (and the model_id MemoryPointer) as locals so they stay alive for the duration of the FFI call and remain thread-safe.
def session(name, options = nil)
opts_ptr = session_options_pointer(options)
out = ::FFI::MemoryPointer.new(:pointer)
@handle.with_handle do |client|
Marshalling.check!(FFIBindings.moss_client_session(client, name.to_s, opts_ptr, out))
end
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| def add_docs(name, docs, options = nil) | ||
| input = Marshalling.build_documents(docs) | ||
| opts_ptr = mutation_options_pointer(options) | ||
| out = ::FFI::MemoryPointer.new(:pointer) | ||
|
|
||
| @handle.with_handle do |client| | ||
| Marshalling.check!( | ||
| FFIBindings.moss_client_add_docs( | ||
| client, name.to_s, input.pointer, docs.length, opts_ptr, out | ||
| ) | ||
| ) | ||
| end | ||
| _retain(input) | ||
|
|
||
| read_and_free_mutation_result(out) | ||
| end |
| def load_index(index_name, options = nil) | ||
| opts_ptr = load_index_options_pointer(options) | ||
| out = ::FFI::MemoryPointer.new(:pointer) | ||
|
|
||
| @handle.with_handle do |client| | ||
| Marshalling.check!( | ||
| FFIBindings.moss_client_load_index(client, index_name.to_s, opts_ptr, out) | ||
| ) | ||
| end |
| def add_docs(docs, options = nil) | ||
| input = Marshalling.build_documents(docs) | ||
| opts_ptr = add_docs_options_pointer(options) | ||
| added = ::FFI::MemoryPointer.new(:size_t) | ||
| updated = ::FFI::MemoryPointer.new(:size_t) | ||
|
|
||
| with_handle do |session| | ||
| Marshalling.check!( | ||
| FFIBindings.moss_session_add_docs( | ||
| session, input.pointer, docs.length, opts_ptr, added, updated | ||
| ) | ||
| ) | ||
| end | ||
| _retain(input) | ||
|
|
||
| Core::SessionAddResult.new(added: added.read(:size_t), updated: updated.read(:size_t)) | ||
| end |
| def resolve_query_url(explicit) | ||
| value = string_or_nil(explicit) || string_or_nil(ENV.fetch("MOSS_CLOUD_QUERY_URL", nil)) | ||
| return value if value | ||
| return nil if @manage_url.nil? || @manage_url.empty? | ||
|
|
||
| @manage_url.sub("/v1/manage", "/query") | ||
| end |
…y-URL guard Follow-up to review feedback on usemoss#463: - Route the mutation / add-docs / load-index / session option structs through Marshalling.build_*_options, which keep the FFI::Struct (and any backing strings) referenced for the duration of the native call via the same Allocation retain pattern already used for query options. FFI::Struct#to_ptr returns the owning MemoryPointer, so this was already memory-safe on CRuby; the change makes the lifetime explicit and consistent and removes an instance-variable hack in ManageClient#session. - resolve_query_url returns nil when the manage->query substitution does not change the URL, so the cloud fallback fails fast with a ConfigurationError instead of silently POSTing to the manage endpoint. Adds unit tests. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Thanks for the review! Addressed the feedback in c8f4297. FFI option-struct lifetimes (
RuboCop is clean and the unit suite + full live end-to-end run (create → load → query → |
Pull Request Checklist
Please ensure that your PR meets the following requirements:
Description
Adds a Ruby SDK over the Moss runtime so Ruby and Rails developers get
on-device semantic search without leaving their stack. It has parity with the
existing SDKs on index management, search, and metadata filtering, and
follows the same two-layer structure.
Two gems under
sdks/ruby/:moss-core(sdks/ruby/bindings/) — FFI bindings over the prebuiltlibmossC SDK, exposingManageClient,IndexManager, andSession.libmossis attached lazily; when it is absent the SDK degrades to acloud-query fallback /
BindingsUnavailableError(mirrors the Go SDK).moss(sdks/ruby/sdk/) — the high-levelMoss::Client: async jobpolling, local sub-10ms queries with cloud fallback, metadata filtering,
custom embeddings, and sessions.
Also includes unit tests, an env-gated live integration test, runnable samples,
a live validation harness, shared RuboCop config, CHANGELOGs, and READMEs;
AGENTS.mdis updated to document the SDK.Testing:
bundle exec rake testgreen for both gems andbundle exec rubocopclean (0 offenses). Validated live against a real Moss project (nativelibmossruntime): create + async job polling, get/list/delete index,load/unload, semantic query,
$eqand$inmetadata filters, andcustom-embedding indexes. Sessions are implemented and unit-tested; the live
session round-trip requires an enterprise plan and is skipped gracefully
otherwise. Native/E2E tests auto-skip when
libmossor credentials are absent,matching the other SDKs.
Note for reviewers: local search needs the
libmossC SDK at runtime(download from the
c-sdk-v0.9.0release and pointMOSS_LIB_DIRat it); cloudqueries work without it. Targets Ruby 3.0+.
Fixes #430
Type of Change