{
  "markdown": "# Client libraries\n\nbatchwatch only works if people instrument their pipelines, and nobody writes\nraw HTTP calls to donate data. These are the libraries that make it two\nlines.\n\n| Package | Language | Tests | State |\n|---|---|---|---|\n| [`python/`](python) | Python 3.8+ | 96, all passing | works; PyPI release on the way |\n| [`typescript/`](typescript) | TypeScript / JS, Node 20+ | 84, all passing (built + tested in CI) | works; built and tested in CI; npm release on the way |\n| [`dotnet/`](dotnet) | C#, `net8.0` | built and tested in CI (xunit) | works; NuGet release on the way |\n| [`go/`](go) | Go 1.21+ | 83, all passing (incl. `-race`) | works; module-proxy release on the way |\n| [`ruby/`](ruby) | Ruby 3.0+ | 87, all passing (minitest) | works; RubyGems release on the way |\n| [`php/`](php) | PHP 8.2+ | all passing | works; on Packagist, tagged release on the way |\n| [`java/`](java) | Java 17+ | 88, all passing | works; Maven Central release on the way |\n| [`rust/`](rust) | Rust 1.63+ | 94, all passing (`cargo test`) | works, std-only; http-only (see note); crates.io release on the way |\n| [`cpp/`](cpp) | C++17 (POSIX) | 85, all passing | works, stdlib+sockets only; source-only by design; http-only (see note) |\n\nAll nine expose the **same surface**, and a [conformance check](conformance) fails CI if\nany language falls behind — see [The same surface, in all nine](#the-same-surface-in-all-nine).\n\n`client/batchwatch.py` in the repo root is the original single-file client\nand is left untouched. `clients/python/` is the packaged version of it, plus\nspooling.\n\n**A note on TLS (Rust and C++).** Every client except Rust and C++ gets\nTLS from its standard library and talks to `https://batchwatch.dev` directly.\nRust's and C++'s standard libraries have no TLS, and both are written with\nzero external dependencies on purpose, so their transport is **`http://`\nonly**. Pointed at the default `https://` URL they *spool* rather than\ndeliver (the measurement is kept, not lost) until you point them at an\n`http://` endpoint or a local TLS-terminating proxy. Their READMEs say so.\n\n## What every client does the same way\n\n**It fails open.** A batchwatch outage must never stop a user's job. Every\nsubmission happens off the caller's thread with a short timeout, every error\nis swallowed and logged at debug level, and the only call you await —\n`should_batch()` — returns *your* default when it cannot answer, never a\nguess. The default is \"run it synchronously\": being wrong that way costs\nmoney, being wrong the other way blows a deadline. Each package has a test\nthat runs against a dead port and a hung socket.\n\n**Two lines to adopt.** `should_batch()` before you submit, `track()` around\nthe call.\n\n**It never sends content.** No prompts, no completions, no file names. The\nbody is built from one allowlist — provider, model, mode, endpoint, request\ncount, token counts, timestamps, status — and everything else is dropped by a\nsingle function - `_scrub` in Python, `Scrub` in Go, `clean` in TypeScript,\n`sanitize` in PHP, Ruby and Java, `strip` in Rust and C++ - on the way out.\nEach package has\na test that asserts this on what the server actually received, with a\npositive control so it cannot pass by sending nothing at all.\n\n**`output_tokens` defaults to null, never 0.** Output costs five to six times\nas much as input, so a saving computed on zero output is systematically too\nlow — 3.4x too low in the case that led to this rule — and nothing in the\nresponse reveals it. Absence must stay absence all the way to the server.\nExplicitly passing `0` still sends `0`: zero is a measurement.\n\n**It spools to disk.** An undeliverable *completed* measurement is appended\nto a JSONL file and replayed later via `POST /v1/calls/complete`. Losing\nmeasurements when the network is bad means losing them exactly when they are\nmost interesting.\n\n**It does the annoying parts.** Beyond the two-line advisory path, every client\ncarries the same high-level surface so you never hand-roll it:\n\n- **The high-level batch job** — `batch(...)` hands the client the two callables\n  (batch-create + a synchronous fallback) and it owns the rest: a **deadline\n  guard** that shifts to the fallback when the wait runs long, a **poll loop**\n  with exponential backoff, jitter, a rate-limit floor and a first cadence\n  informed by the model's measured p50, and **partial-completion** handling that\n  splits a batch into landed / failed / expired mapped by `custom_id` (never by\n  index) with an idempotent retry of only the failed subset. We take the\n  callable, never the payload — the deadline fallback is reported down the same\n  accuracy path a completion uses, so nothing new is sent.\n- **Read your own contributions** — `my_calls()` and `key_status()`\n  (`GET /v1/calls/mine`, `/v1/keys/current`): the per-key readback for verifying\n  a measurement landed and checking your tier/quota.\n- **Subscribe to outage alerts** — `subscribe()` / `subscriptions()` /\n  `unsubscribe()` against `/v1/subscriptions`, the \"own the outage moment\"\n  channel.\n\nUnlike the measurement path, these last two do **not** fail open: they are\nexplicit actions against a per-key route, so without a key they raise rather than\nsilently pretend. The job path is the user's own job, so a misuse (a result\nbefore a submit, a deadline with no fallback) raises loudly too — only telemetry\nfails open.\n\n**No dependencies.** Standard library only, in all nine.\n\n## The same surface, in all nine\n\nEvery client promises the same capabilities, and — since a feature can land in\none language, its card be closed in good faith, and the other eight silently lag\n— [`conformance/`](conformance) is the check that stops that. `manifest.json`\ndeclares the promised surface; `check.py` greps each SDK's own source **and its\nown tests** (per language, never a loose cross-language match) and fails CI on\nany unexplained gap. Exemptions must be explicit and justified in the manifest.\nRun it with `python clients/conformance/check.py --list`.\n\n## The spool format\n\nOne JSON object per line, in the shape `/v1/calls/complete` accepts:\n\n```json\n{\"provider\":\"openai\",\"model\":\"gpt-5.6-sol\",\"mode\":\"batch\",\"requests\":1,\n \"endpoint\":null,\"input_tokens\":9720,\"output_tokens\":null,\"status\":\"completed\",\n \"started_at\":\"2026-08-25T10:00:00Z\",\"ended_at\":\"2026-08-25T10:04:00Z\"}\n```\n\nIdentical across all nine clients, so a file written by one can be flushed\nby another. Default location is `$BATCHWATCH_SPOOL`, otherwise\n`batchwatch-spool.jsonl` in the temp directory.\n\nTwo consequences worth knowing before you rely on it:\n\n- **Spooling needs an API key.** `/v1/calls/complete` takes the caller's own\n  timestamps, so it is closed to anonymous callers — see the reasoning in\n  `src/index.js`. A client without a token therefore does not spool at all:\n  a file that can never be sent is a disk leak, not data safety.\n- **Replay can duplicate.** If the original `PATCH` reached the server but\n  the response did not, the spooled copy arrives as a second row. That is the\n  deliberate trade: a duplicate is visible in the dataset, a lost measurement\n  is not.\n\nThe file is capped (5 MB by default). Past the cap, measurements are dropped\nrather than filling the user's disk.\n\n## Publishing\n\nEvery client works today: install it from the repo (each README shows how) and\nit runs. Registry publishing is the next step, and it is in flight — the PHP\npackage is already on Packagist, the .NET and TypeScript clients build and test\nin CI on every push, and the rest are being wired up (PyPI, npm, a Go module\nproxy, RubyGems, Maven Central, crates.io). Client CI itself lands in\n[#184](https://github.com/batchwatch/client/issues/184).\n\nTwo properties are deliberate design decisions, not gaps, and each has a\nworkaround in the relevant README:\n\n- **Rust and C++ speak `http://` only** — their standard libraries carry no TLS\n  and both are zero-dependency by design. Point them at a local TLS-terminating\n  proxy in front of `batchwatch.dev`, and they deliver directly (see the TLS\n  note above).\n- **The POSIX-socket clients (C++) are first-class on Linux and other POSIX\n  platforms;** Windows needs a Winsock shim.\n",
  "bytes": 8162,
  "sha": "23ca7ad4ded14bf28ac8db910c7bb8cdf56f8b437c6d5f9450d3f49826efb6f1",
  "repo_slug": "batchwatch/client",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_batchwatch_batchwatch_mcp_d6b4d454/readme"
}