SDKs
Seven Loams client libraries, generated from one set of protos, held to one runtime contract and checked against one recorded fixture corpus.
What they are
Loams has one application API: Connect and gRPC services in loams.<service>.v1 packages, served by the loams binary on its main port. The SDKs are thin clients for it in seven languages: TypeScript, Python, Rust, C#, Go, Swift and C++. Each gives you one Loams client object with namespaced modules (loams.instance, loams.live, loams.tables, and so on), typed errors with stable reasons, safe retries with idempotency keys, and streams as the language's own async iteration.
Use an SDK when you call Loams' own API: Loams Live, the instance and admin services, and the generated types for the rest. You do not need one for the compatibility surfaces. Those exist so that unmodified third-party clients work: use qdrant-client, elasticsearch-py, a Postgres or MySQL driver, an ADBC or Flight SQL driver, or a ClickHouse HTTP client against the matching endpoint. And if your language is not in the seven, the API is plain Connect: a unary call is an HTTP POST with a JSON body, so curl or any HTTP client works.
Status
The seven SDKs are available in Loams 0.1.0 (released 2026-10-18), published from the release tag at version 0.1.0. As with all of 0.x, an API may change between minor versions.
| Release | What shipped |
|---|---|
| 0.1.0-preview.1 (2026-10-12) | Seven SDKs build and pass the shared conformance suite against a live server in CI, from the repository only (sdks/<language>) |
| 0.1.0-preview.2 (2026-10-15) | One workflow, sdk-release.yml, builds, tests and publishes all seven from the release tag; preview versions on npm (dist-tag preview), PyPI (0.1.0a2), crates.io, NuGet, the Go module proxy, SwiftPM and a C++ archive on the GitHub release |
| 0.1.0 (2026-10-18, current) | All seven at 0.1.0, installed without prerelease flags. The Swift SDK deploys Live apps through live.admin |
Java, Kotlin, Ruby, PHP, Dart and Objective-C are in the repository and pass the required fixtures there, but are not published; the feature table lists them as in progress.
Architecture
| Piece | Where | What it does |
|---|---|---|
| The protos | proto/loams | loams.instance.v1, auth.v1, admin.v1, collection.v1, live.v1, graph.v1, house.v1, postgres.v1, repos.v1, the app packages (approvals, devices, notifications, operations), errors.v1 and options.v1 |
loams.options.v1 | proto/loams/options/v1 | Service and method options: the module a service belongs to, the facade name of each call, whether it is safe to retry, how it pages |
buf generate | sdks/<lang>/buf.gen.yaml, scripts/sdk/pins.lock | Messages and stubs per language, with remote plugins at pinned versions |
protoc-gen-loams-facade | crates/loams-facade-gen | A buf plugin in Rust: reads the descriptors and renders the TypeScript, Python, Rust and Go facades, plus facade-index.json (every module, call, retry class and pagination rule) |
| The runtime | sdks/<lang>/ | Hand-written once per language: transport, auth, retries, error mapping, pagination and the version check |
| The conformance corpus | sdks/fixtures, sdks/conformance | Recorded server answers, a fixture server that replays them, and the runner every SDK's suite uses |
How it works
One API, one port
Every application call is an RPC (D600). The loams main port serves the Connect protocol, gRPC and gRPC-Web at once, plus gRPC health and reflection, so a browser, a server and grpcurl all reach the same handlers. The bespoke REST APIs were removed rather than kept beside it (D602), and the compatibility surfaces (Qdrant, Elasticsearch, Postgres and MySQL wire, Flight SQL and others) stay exactly as they are, because their whole point is that existing clients work unchanged (D603). A service that a build does not carry answers UNIMPLEMENTED with the reason feature_not_in_variant, and GetInstance lists the services a server offers, so an SDK can feature-detect before it calls.
Generated where possible
The SDKs follow one rule: generate everything that can be generated, and hand-write only the runtime (D606).
- Messages and stubs come from
buf generate, never hand-edited. - The facade, the
loams.<module>.<call>surface, is generated from theloams.options.v1annotations byprotoc-gen-loams-facadefor TypeScript, Python, Rust and Go. Go generatesgen/facadeand hand-writes its module file. - C#, Swift and C++ facades are hand-written today, and each is checked against
facade-index.jsonby a test, so a module or call added to the protos and missing from the SDK fails CI. The API2 plan's owner ruling (Q604) moves Swift and C# to generated facades once their renderers exist. - The runtime (transport, auth, retry, errors, pagination, the version check) is written once per language.
Names follow each language's case: loams.instance.getInstance in TypeScript, loams.instance with snake_case methods in Python, client.Instance().GetInstance(ctx, ...) in Go.
The runtime contract
What every SDK must do is written down as ten clauses in docs/sdk/runtime-contract.md and decided in D608 to D611:
| Clause | Behaviour |
|---|---|
| R1 Credentials | A token source gives a bearer (an API key, a static token, an OIDC token exchange, workload identity, or the environment); a token_expired error triggers one refresh and one retry. Tokens travel in Authorization, never in a URL |
| R2 Retries | The retry class comes from the proto: NO_SIDE_EFFECTS and IDEMPOTENT calls retry on UNAVAILABLE, DEADLINE_EXCEEDED before a response started, and RESOURCE_EXHAUSTED with RetryInfo. Capped exponential backoff with full jitter: base 100 ms, doubling, cap 2 s, 3 retries |
| R3 Idempotency | A mutating call retries only with an idempotency_key, which the SDK generates (UUIDv7) once per logical call and reuses on every retry |
| R4 Consistency tokens | Writes return a token; reads accept STRONG (the default), EVENTUAL or "at least this token". An optional session store merges tokens and attaches them to later reads, for read-your-writes. See consistency tokens |
| R5 Unavailable services | The catalogue and the feature_not_in_variant refusal |
| R6 Pagination | page_size and page_token in, next_page_token out, and an iterator that follows the tokens |
| R7 Streams | Server streams only, as the language's async iteration, resuming from a cursor without loss |
| R8 Errors | A typed error hierarchy from the Connect or gRPC code, with reason (a stable snake_case string registered in docs/api/reasons.md) as the thing to branch on |
| R9 Version reporting | The SDK records the proto revision it was generated from and checks it against the server's api_versions |
| R10 The browser | gRPC-Web with no Node built-in, so the TypeScript client runs in a browser, Node, Deno and Bun |
There is no client or bidirectional streaming in the API: browsers cannot do it, and half-duplex works through every proxy.
The conformance corpus
The fixtures are recorded, not written (D617). sdks/conformance/record-fixtures.mjs captures real answers from loams dev, and from loams-apps-mock for the app packages and for faults a real server cannot produce on demand. sdks/conformance/fixture-server.mjs replays them byte for byte. A hand-written expectation would only prove that an SDK agrees with whoever wrote it; a recording proves it agrees with the server.
Each unary case is recorded in four encodings (application/json, application/proto, application/grpc-web+proto and application/grpc-web+json), because the bodies are not interchangeable and an SDK's transport picks one. The corpus also keeps two traps apart: Connect reports an error as an HTTP status with a JSON body, while gRPC-Web answers 200 and puts the code in the trailers; and a refusal on a stream arrives inside the streaming envelope, not as a status.
sdks/fixtures/manifest.json is the single definition of "passes": a language passes when it has run every fixture marked required. Today that is 28 of 33 fixtures. The suite runs through each SDK's public facade, not its generated stubs.
The corpus is honest about its gaps. Clauses R2 (retries), R4 (consistency tokens) and R6 (pagination) have no recorded fixture yet, so each language pins them against a stub instead. A retryable unavailable needs a dependency to be down, which neither recording server can produce on demand, and recording consistency tokens and paging end to end is part of API2's corpus work.
Versioning
buf breaking runs on every pull request against the last release tag for every package not marked unstable (D616), and SDKs and protos are additive-only within a major version (D939): an SDK minor never removes or renames a public symbol, and the fixtures check it. A reason is never renamed or removed within a major version. Packages marked unstable (Loams Live's loams.live.v1 is one) are excluded from the breaking check, which is why the SDKs label their Live modules as unstable.
Key decisions and why
From the decision log and the design, §44 The Unified Connect API and the Multi-Language SDKs. The D600 to D619 rows are recorded as proposed with the owner's "do suggested for all" defaults; the API2 plan's owner rulings of 2026-10-10 are binding for execution.
| Decision | What | Why, and what was rejected |
|---|---|---|
| D600 | One API: Connect and gRPC in loams.<service>.v1 on one port | The owner's direction: remove the REST API and make one gRPC API for all services. Connect keeps it curl-able without a bespoke REST layer |
| D602, D604 | Remove the bespoke REST APIs; no OpenAPI, no hand-written REST SDKs | Supersedes D101 (OpenAPI then a Go SDK) and the earlier Python and TypeScript REST SDKs. The API2 owner ruling Q600 deletes REST outright, without shims |
| D603 | Keep every compatibility surface | Unmodified third-party clients are the drop-in promise |
| D606 | One SDK shape, with the facade generated from proto annotations | Thirteen hand-wrapped surfaces would diverge within a quarter; a language may fall back to a hand-written facade checked by the same tests |
| D608 to D611 | One auth, consistency, retry and error behaviour in every SDK | The same call behaves the same way in any language |
| D612 | Connect first, gRPC only where no official Connect library exists | Connect libraries work in browsers and over HTTP/1.1. As built, the C# and C++ SDKs speak Connect and gRPC-Web over HTTP themselves (C# with Google.Protobuf only; C++ with protobuf and libcurl) instead of carrying a full gRPC stack |
| D614 | Waves: TypeScript, Python, Go and Rust first | A priority order, not a gate; it decides which SDKs carry the product's own consumers |
| D617 | One recorded corpus; publishable only at 100% of required fixtures | The same behaviour, proven against the same bytes |
| D616, D939 | buf breaking against the last release; additive-only SDKs and protos | Upgrades of the server never force an SDK upgrade within a major version |
loams.agentd.v1, Loams Studio's daemon API, is deliberately outside the SDK catalogue (Q816): the facade generator's NOT_SDK_PACKAGES keeps it out.
What is compatible
Install commands and versions from the 0.1.0 release guide; toolchain and library versions from each SDK's own manifest.
| SDK | Install | Version | Toolchain | Built on |
|---|---|---|---|---|
| TypeScript | npm i @loams/client (and @loams/live) | 0.1.0 | Browsers, Node, Deno, Bun | @connectrpc/connect 2.2.0, @bufbuild/protobuf 2.16.0 |
| Python | pip install loams | 0.1.0 | Python 3.10 or later | connectrpc 0.12.1, protobuf 7.36.2 |
| Rust | cargo add loams | 0.1.0 | The workspace's Rust | connectrpc 0.9.1, buffa 0.9.2; no tonic |
| C# | dotnet add package Loams | 0.1.0 | .NET 8 (net8.0) | Google.Protobuf 3.28.3, its own HTTP transport |
| Go | go get loams.dev/go@v0.1.0 | 0.1.0 | Go 1.25 or later | connectrpc.com/connect v1.21.0 |
| Swift | SwiftPM: https://github.com/ostrium-labs/loams, from 0.1.0 | 0.1.0 | Swift tools 6.0; macOS 12, iOS 15 | No package dependencies; its own URLSession transport |
| C++ | loams-cpp-0.1.0.tar.gz from the GitHub release, CMake find_package(loams) | 0.1.0 | C++17 | protobuf (exactly 36.1 for the generated stubs) and libcurl |
What the facades cover today: the TypeScript SDK's modules are instance, auth, admin.org, admin.projects, admin.agents, admin.keys, admin.audit, live, live.admin and tables; the Python SDK has instance, live, tables and system. The generated message types and stubs cover every SDK package, including loams.collection.v1, graph.v1, house.v1, postgres.v1 and repos.v1; facade modules for those come with API2.
How it compares
There are two common ways to ship database client libraries. Many projects hand-write a client per language and keep them in line with a shared specification and test suite; MongoDB's drivers are the best-known example. Others generate clients from a machine-readable API definition: Google Cloud's client libraries are generated from protobuf service definitions, and Stripe's from its OpenAPI specification.
| Loams SDKs | Hand-written per language (MongoDB drivers) | Generated from an API definition (Google Cloud, Stripe) | |
|---|---|---|---|
| Source of truth | proto/, with facade annotations | A specification document per feature, plus each driver's code | The API definition |
| Idiomatic surface | Generated facade where a renderer exists, hand-written and index-checked elsewhere | Fully idiomatic, written by people who know the language | Generated, often with hand-written helpers on top |
| Drift control | A recorded server corpus plus an index test per hand-written facade | Shared spec tests each driver runs | Regeneration from the definition |
| Wire | One port, Connect, gRPC and gRPC-Web | The database's own wire protocol | HTTP and JSON (Stripe) or gRPC and REST (Google Cloud) |
Where the others are better today: mature hand-written drivers cover every feature of their database in idiomatic depth, and large generated SDKs cover their whole API. The Loams facades cover a few modules so far, and three of the clauses in the runtime contract are pinned against stubs rather than recorded fixtures. Where Loams' approach helps: a new RPC reaches seven languages from one proto change, and every SDK is tested against the same recorded bytes.
What is missing and what is planned
The API2 plan finishes and supersedes API1, SDK1 and SDK2.
- Six more languages. Java, Kotlin, Ruby, PHP, Dart and Objective-C build and pass the required fixtures in the repository but are not published. The owner's ruling keeps all thirteen; Objective-C is the first to drop if upkeep gets too heavy, and it would ship as generated sources and a SwiftPM target, not CocoaPods (D614).
- The whole API in every facade. Facade modules for collections, graph, House, Postgres and repositories, and a corpus that covers every stable package and every runtime clause (API2 Tasks 13 to 23).
- Generated facades for Swift and C# (Q604; each needs a renderer).
- Recorded fixtures for retries, consistency tokens and pagination (R2, R4, R6), with a fault server that can inject retryable errors.
- Reference docs generated from the proto comments, with snippets taken from the tested fixtures (D618).
- C++ packaging. The C++ stubs build against protobuf 36.1 exactly, so there are no vcpkg or Conan ports yet. Building
loams-protofrom crates.io needsprotocinstalled. - Bulk data. Large results and bulk ingest go through Arrow Flight SQL with an ADBC or Flight driver, not through the SDKs yet; the design's
loams.bulkmodule is planned.
How to try it
Run an engine (see Loams Desktop and the loams CLI) and create an API key with loams auth new-key, with LOAMS_AUTH_MODE=apikey on the stack. Then, in TypeScript:
npm i @loams/clientimport { Loams, apiKey, FeatureNotInVariantError } from '@loams/client';
const loams = new Loams({
endpoint: 'https://acme.loams.dev',
auth: apiKey(process.env.LOAMS_API_KEY!),
});
const info = await loams.instance.getInstance({});
await loams.system.guard('live');
try {
await loams.tables.mutate(request);
} catch (error) {
if (error instanceof FeatureNotInVariantError) {
console.warn(error.variant);
}
}In Python:
pip install loamsfrom loams import Loams
loams = Loams("https://acme.loams.dev", api_key="...")
print(loams.instance.version())In Go:
go get loams.dev/go@v0.1.0client := loams.New(loams.Options{
Endpoint: os.Getenv("LOAMS_ENDPOINT"),
Auth: loams.APIKey(os.Getenv("LOAMS_API_KEY")),
})
info, err := client.Instance().GetInstance(ctx, &loams.GetInstanceRequest{})Use your own engine's URL as the endpoint, such as https://loams.<tailnet>.ts.net for a compose stack on your tailnet. If a registry does not serve a package yet, use the SDK from the repository at the release tag (sdks/<language>).
Source
- Design: §44 The Unified Connect API and the Multi-Language SDKs
- Plans: API2, API1, SDK1, SDK2
- Contracts:
docs/sdk/runtime-contract.md,docs/api/reasons.md - Code:
sdks/,proto/,crates/loams-facade-gen,sdks/fixtures,sdks/conformance
Loams Desktop and the loams CLI
The Electron desktop app that runs the console, a local engine and the agent daemon on your machine, and the one loams binary that runs, upgrades and administers a Loams stack.
Key decisions
The two dozen decisions that shaped Loams, why each was made, what was rejected, and which ones were later reversed.