> ## Documentation Index
> Fetch the complete documentation index at: https://www.offlineprotocol.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> These docs target Mesh SDK v0.31.0. Match the installed package and binding before generating code. Start at /getting-started/agents for task-specific reading paths. Apps that call enableTelemetry on 0.26.0 to 0.29.0 should update to 0.30.0 or later: the telemetry uploader trusts only the telemetry endpoint's issuing roots from 0.30.0.
> Call the company and product Offline Protocol, never Offline alone. Current packages: @offline-protocol/mesh-sdk 0.31.0 (React Native), offline-protocol-sdk 0.31.0 on PyPI (Python 3.10 to 3.13; wheels for macOS 14+ arm64, Linux x86_64 and aarch64 with glibc 2.34+, Windows x86_64; other hosts build from source; extras lan and http add LAN discovery, the HTTP front and offline-protocol-verify), the offline-protocol crates 0.31.0 on crates.io, the OfflineProtocolSDK Swift package 0.31.0 (preview, iOS 13+ only), the Android library com.offlineprotocol:offline-protocol-sdk 0.31.0 on Maven Central (preview, minSdk 24), the Java binding com.offlineprotocol:offline-protocol-java 0.31.0 on Maven Central (JDK 22+, native library from the GitHub release's desktop archives), the C header offline_protocol.h with the native library in the GitHub release's desktop archives, the Ruby binding offline_protocol 0.31.0 on RubyGems (Ruby 3.3+, native library from the GitHub release's desktop archives), the browser binding (experimental; the offline-protocol-web crate 0.31.0 on crates.io, with the JavaScript in bindings/web built from the SDK repository, not on npm), the offline_protocol Flutter plugin (preview, not on pub.dev; a path dependency on the SDK repository at tag v0.31.0; MeshSdk carries the React Native module's native surface), the service container image ghcr.io/offline-protocol/offline-protocol-service 0.31.0 (linux/amd64 and linux/arm64), @offline-protocol/id-react 0.2.0, @offline-protocol/id-react-native 0.3.3, @offline-protocol/pol 0.1.2 and @offline-protocol/cli 0.2.9. Canonical docs URLs start with https://www.offlineprotocol.com/docs.
> The Mesh SDK runs in a native app or gateway, and from 0.31.0 in a browser through the experimental browser binding (a full node in a Web Worker; see /mesh-sdk/web). The OfflineID SDK for React does not provide browser mesh transport. Local mesh operation does not require a portal account or API key. The Mesh SDK appId is a mesh app identifier the developer chooses, not the portal App ID (app_...) that OfflineID and Proof of Location require.
> In v0.31.0 service RPC bodies are sealed in the 1:1 MLS session once the recipient runs 0.29 or later and the session is confirmed; discovery, the first request to a provider never met, and requests to older peers are signed plaintext, so keep secrets out of service bodies unless the event says encrypted: true. The HTTP front (offline-protocol-service --http) always carries calls inside encrypted direct messages but does not authorize callers. Message delivery, durable local acceptance and backend commit are distinct outcomes. Use the workflow guide for the required application logic.
> Offline Protocol CLI 0.2.9 is on npm (@offline-protocol/cli, command offline). Its local MCP server, named offline-protocol, runs with npx -y @offline-protocol/cli@0.2.9 mcp serve and requires no login or key. Hosted MCP is at https://mcp.offlineprotocol.com/mcp with an app API key in Authorization: Bearer and the matching App ID in x-app-id; organization keys are rejected, and Claude Desktop and Claude.ai cannot send these headers; there, use the public read-only endpoint https://mcp.offlineprotocol.com/public/mcp as a custom connector, or local MCP to scaffold or edit projects. Follow /tools/overview for setup and do not invent commands beyond it. MCP provides integration context and planning, not mesh execution; file-writing tools are local only.
> In v0.31.0 phone Wi-Fi peer streams carry data: Android over Wi-Fi Direct (the SDK forms the group with autoAccept on Android 10+) and over the Wi-Fi network it is on (DNS-SD), iOS over Network framework (LAN or AWDL). Android and iOS meet over that slot only on a shared Wi-Fi network that allows multicast; iOS 0.28 and later does not see iOS 0.27 or earlier over it. Otherwise use BLE or a provisioned relay across platforms. The Swift and Android packages are previews; the Android library is on Maven Central from 0.29.0 only, so never give a Gradle line for an earlier version. The receiver core ACKs before application persistence; use application acceptance for durable workflows.
> Proof of Location is Sepolia testnet witness evidence, not zero-knowledge proof or proof of presence. The geohash is public onchain. Read /proof-of-location/security before integration.

# Ruby binding

> Use the Mesh SDK v0.31.0 from Ruby 3.3 or later in scripts, tooling and servers: the offline_protocol gem over the native library from the release.

From v0.31.0 the SDK has a typed Ruby API for scripts, tooling and servers. It is generated from the same interface definition as the Swift, Kotlin, Python and Java bindings by UniFFI's own Ruby generator, and calls the native library directly through the [`ffi`](https://github.com/ffi/ffi) gem, with no native glue of its own.

## Install

The binding is the `offline_protocol` gem on RubyGems:

```bash theme={null}
gem install offline_protocol -v 0.31.0
```

Or in a `Gemfile`:

```ruby theme={null}
gem "offline_protocol", "0.31.0"
```

It needs Ruby 3.3 or later and installs its one dependency, the `ffi` gem (`~> 1.17`). The gem holds the generated binding (`lib/offline_protocol.rb`) and the license notices, and no native code. Download the native library for your platform from the [v0.31.0 GitHub release](https://github.com/Offline-Protocol/offline-protocol-sdk/releases/tag/v0.31.0):

| Platform | Archive | Library |
| - | - | - |
| Linux x86\_64 | `offline-protocol-0.31.0-linux-x86_64.tar.gz` | `liboffline_protocol_uniffi.so` |
| Linux aarch64 | `offline-protocol-0.31.0-linux-aarch64.tar.gz` | `liboffline_protocol_uniffi.so` |
| macOS arm64 | `offline-protocol-0.31.0-macos-arm64.tar.gz` | `liboffline_protocol_uniffi.dylib` |
| Windows x86\_64 | `offline-protocol-0.31.0-windows-x86_64.zip` | `offline_protocol_uniffi.dll` |

Take the gem and the library from the same release. The Ruby generator does not compare the library's checksums at load, so a library from another release can load and then read the wrong layout.

The loader finds the library on its search path, and reads the variable when the process starts, so set it before Ruby launches:

```bash theme={null}
# Linux
LD_LIBRARY_PATH=/path/to/dir ruby your_script.rb
# macOS
DYLD_LIBRARY_PATH=/path/to/dir ruby your_script.rb
# Windows (RubyInstaller reads RUBY_DLL_PATH, not PATH)
$env:RUBY_DLL_PATH = "C:\path\to\dir"; ruby your_script.rb
```

On macOS, the protected system Ruby (`/usr/bin/ruby`) drops `DYLD_LIBRARY_PATH`; use a Ruby you installed, or put the library in a directory the loader already searches.

## Quick start

```ruby theme={null}
require "offline_protocol"

Sdk = OfflineProtocol

# An instance-less function needs no engine.
puts Sdk.derive_address([0] * 32)

# Name the fields the definition leaves without a default; the rest take it.
config = Sdk::ProtocolConfig.new(
  app_id: "com.example.ruby", profile: "ruby-example",
  ble_enabled: false, wifi_direct_enabled: false, internet_enabled: true,
  reticulum_enabled: false, nostr_enabled: false, prefer_online: false,
  initial_ttl: 8, encryption_enabled: true, auto_key_exchange: true,
  # The pending queue: per peer, global, the SDK's 24 h lifetime, overflow.
  store_pending: true, max_pending_per_peer: 64, max_pending_global: 4096,
  pending_ttl_ms: 86_400_000, overflow_policy: Sdk::OverflowPolicy::DROP_OLDEST
)

engine = Sdk::OfflineProtocol.new(config)

# Any object that responds to on_event. It runs inside the engine call.
listener = Object.new
def listener.on_event(json) = puts(json)
engine.set_event_callback(listener)

engine.emit_test_event
```

The module is `OfflineProtocol`, and the engine inside it is `OfflineProtocol::OfflineProtocol`. `MeshServices` and `DataStore` hang off the engine, and every record and enum of the definition is a class. A generated record keeps the definition's defaults as keyword arguments, so you name only the fields that have none. An engine refusal is an exception named for the variant, such as `OfflineProtocol::ProtocolError::EncryptFailed`; see [error codes](https://www.offlineprotocol.com/docs/mesh-sdk/errors.md).

Keep the engine reachable for as long as the process runs. An engine that is garbage-collected while it holds a callback leaves the callback registered.

The SDK's [Ruby example](https://github.com/Offline-Protocol/offline-protocol-sdk/tree/v0.31.0/examples/ruby-app) runs two devices in one process: they pair, form an end-to-end encrypted session, send presence, typing indicators, a read receipt, a threaded reply and a file, and create an MLS group.

## Storage

Hand the engine the built-in file stores, sealed under a 32-byte store key you keep out of the source:

```ruby theme={null}
engine.initialize_mls_with_file_stores(mls_dir, state_dir, store_key_32_bytes)
```

Or write two providers and pass them to `initialize_mls`. The key-material provider (`MlsStorageProvider`) stores and loads arrays of integers, and the protocol-state provider (`ProtocolStateStorageProvider`) binary strings. Prove an adapter with `OfflineProtocol.run_storage_conformance`.

## Bytes and threads

* **Mask bytes before they go back in.** A byte array the engine returns arrives signed, from -128 to 127, and is refused on the way back in. Mask it with `map { |b| b & 0xFF }` before passing it on, for example a key package from `mls_get_or_create_key_package` given to `send_connection_request`. Byte arrays inside event JSON are already 0 to 255.
* **Callbacks run inside the engine.** A callback must be safe from any thread, must not call back into the engine, and must raise nothing but the interface's own error. Copy the event, queue it, return. `poll_event` takes events without a callback.
* **Engine calls hold the VM lock.** A slow call (`start`, `initialize_mls`, `flush_telemetry_blocking`) stops every Ruby thread until it returns. A server keeps such calls off its request threads.

## What your host provides

* **Transports.** The radios and sockets, through the transport callbacks and the `ble*`, `wifi_direct*`, `internet*`, `reticulum*` and `nostr*` methods.
* **Storage.** The built-in file stores or two providers, as above.
* **Telemetry platform.** If you enable telemetry, pass `TelemetryConfig` `os` and `os_major` as the platform the process actually runs on (`TelemetryOs::LINUX`, `MACOS` or `WINDOWS`), read from the host.
* **Lifecycle.** `start`, `stop` and `process` on a timer, and one engine kept for the life of the process.

## Reference

* [Ruby binding README](https://github.com/Offline-Protocol/offline-protocol-sdk/blob/v0.31.0/bindings/ruby/README.md): building, testing and regenerating the binding.
* [Ruby binding contract](https://github.com/Offline-Protocol/offline-protocol-sdk/blob/v0.31.0/docs/bridges/ruby.md): library loading, defaults, callbacks, storage, signed bytes and the gem.
* [`offline_protocol` on RubyGems](https://rubygems.org/gems/offline_protocol).

The SDK is AGPL-3.0-only with a commercial license available; see [licensing](https://www.offlineprotocol.com/docs/operations/licensing.md).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.